Source code for cline

r"""
Cline class for representing circles and lines in the complex plane.

A cline is a circle or line that can be represented by the equation:

.. math::

   cz\bar{z} + \alpha z + \bar{\alpha}\bar{z} + d = 0

where:
- c and d are real numbers
- alpha is a complex number

Based on the parameters, we have: 
- It's a circle if :math:`|\alpha|^2 > c \cdot d` and :math:`c \neq 0`
- It's a line if :math:`c = 0`
- It's a point if :math:`|\alpha|^2 = c \cdot d` and :math:`c \neq 0`
- Is not a valid geometric object (no solutions) if $|\alpha|^2 < c \cdot d$ and $c \neq 0$
"""

import matplotlib.pyplot as plt
import numpy as np


[docs] class Cline: r"""Class representing a circle or line in the complex plane using the general equation. The equation is: .. math:: cz\bar{z} + \alpha z + \bar{\alpha}\bar{z} + d = 0 where c and d are real numbers and alpha is complex. """
[docs] def __init__(self, c=0.0, alpha=0.0 + 0.0j, d=0.0): r"""Initialize a cline with its equation parameters. Args: c (float): Real coefficient of :math:`z\bar{z}` alpha (complex): Complex coefficient of z d (float): Real constant term Mathematical Formulation: A cline is determined by the general equation: .. math:: cz\bar{z} + \alpha z + \bar{\alpha}\bar{z} + d = 0 where: * :math:`c` and :math:`d` are real numbers * :math:`\alpha` is a complex number The discriminant of the cline is defined as: .. math:: \Delta = |\alpha|^2 - c \cdot d Based on the parameters, the cline represents: * A circle if :math:`\Delta > 0` and :math:`c \neq 0` * A point if :math:`\Delta = 0` and :math:`c \neq 0` * A line if :math:`c = 0` * An invalid geometric object (no solutions) if :math:`\Delta < 0` and :math:`c \neq 0` For a circle (:math:`\Delta > 0` and :math:`c \neq 0`): * Center: :math:`z_0 = -\frac{\alpha}{c} = -\frac{\text{Re}(\alpha) + i\cdot\text{Im}(\alpha)}{c}` * Radius: :math:`r = \frac{\sqrt{\Delta}}{|c|} = \frac{\sqrt{|\alpha|^2 - c \cdot d}}{|c|}` For a point (:math:`\Delta = 0` and :math:`c \neq 0`): * Point location: :math:`z_0 = -\frac{\alpha}{c}` For a line (:math:`c = 0`): * Normal vector: :math:`\alpha = a + bi` * Direction vector: :math:`v = b - ai` (perpendicular to normal) * Cartesian form: :math:`ax - by + \frac{d}{2} = 0` * Distance from origin: :math:`\frac{|d|}{2|\alpha|}` * Parametric form: :math:`z(t) = z_0 + t \cdot v` where :math:`z_0` is a point on the line Algorithm: 1. Calculate the discriminant :math:`\Delta = |\alpha|^2 - c \cdot d` 2. Determine the type of cline: * If :math:`|c| < \epsilon` (near zero): Line * If :math:`|c| \geq \epsilon` and :math:`\Delta > \epsilon`: Circle * If :math:`|c| \geq \epsilon` and :math:`|\Delta| < \epsilon`: Point * If :math:`|c| \geq \epsilon` and :math:`\Delta < -\epsilon`: Invalid object 3. For a circle, calculate: * Center: :math:`z_0 = -\frac{\text{Re}(\alpha)}{c} - i\frac{\text{Im}(\alpha)}{c}` * Radius: :math:`r = \frac{\sqrt{\Delta}}{|c|}` 4. For a point, calculate: * Point location: same as circle center 5. For a line, calculate: * Normal vector: :math:`\alpha` * Direction vector: :math:`v = \text{Im}(\alpha) - i \cdot \text{Re}(\alpha)` * Distance from origin: :math:`\frac{|d|}{2|\alpha|}` * A point on the line by setting either x=0 or y=0 in the Cartesian form """ # Ensure c and d are real self.c = float(c) self.d = float(d) # alpha is complex self.alpha = complex(alpha) # Initialize points attribute to None (will be set if created from points) self.points = None # Compute discriminant |alpha|^2 - c*d self.discriminant = abs(self.alpha) ** 2 - self.c * self.d # Determine if it's a circle, point, or line if abs(self.c) > 1e-10: # c ≠ 0 if self.discriminant > 1e-10: # Discriminant > 0 self.is_circle = True self.is_point = False self.is_line = False elif abs(self.discriminant) < 1e-10: # Discriminant ≈ 0 self.is_circle = False self.is_point = True self.is_line = False else: # Discriminant < 0 self.is_circle = False self.is_point = False self.is_line = False else: # c = 0 self.is_circle = False self.is_point = False self.is_line = True # Compute center and radius for circles if self.is_circle: # Center: z_0 = (-Re(alpha)/c, Im(alpha)/c) real_part = -np.real(self.alpha) / self.c imag_part = np.imag(self.alpha) / self.c self.center = complex(real_part, imag_part) # Radius: r = sqrt((|alpha|^2-cd) / c^2) self.radius = np.sqrt(self.discriminant) / abs(self.c) # Compute point location when discriminant = 0 if self.is_point: real_part = -np.real(self.alpha) / self.c imag_part = np.imag(self.alpha) / self.c self.point = complex(real_part, imag_part) # Compute line properties when c = 0 if self.is_line: # Extract real and imaginary parts of alpha self.a = np.real(self.alpha) self.b = np.imag(self.alpha) # Normal vector: alpha self.normal_vector = self.alpha # Direction vector: v = b - ia (perpendicular to alpha) self.direction_vector = complex(self.b, -self.a) # Distance from origin: |d|/(2|alpha|) if abs(self.alpha) > 1e-10: self.distance_from_origin = abs(self.d) / (2 * abs(self.alpha)) else: self.distance_from_origin = float("inf") # Find a point on the line for parametric form # For the equation ax - by + d/2 = 0 if abs(self.a) > abs(self.b): # If |a| > |b|, set y = 0 and solve for x x = -self.d / (2 * self.a) y = 0 else: # Otherwise, set x = 0 and solve for y x = 0 y = self.d / (2 * self.b) self.point_on_line = complex(x, y)
def _format_complex(self, z, precision=4): """Format a complex number with specified precision.""" real = round(z.real, precision) imag = round(z.imag, precision) # Remove trailing zeros if real == int(real): real = int(real) if imag == int(imag): imag = int(imag) if imag == 0: return f"{real}" elif imag > 0: return f"{real}+{imag}j" else: return f"{real}{imag}j" def _format_float(self, x, precision=4): """Format a float with specified precision.""" rounded = round(x, precision) # Remove trailing zeros if rounded == int(rounded): return str(int(rounded)) else: return str(rounded)
[docs] @classmethod def from_three_points(cls, z0, z1, z2): r"""Construct a cline from three points in the complex plane. Args: z0 (complex or tuple): First point, either complex number or tuple (real, imag) z1 (complex or tuple): Second point, either complex number or tuple (real, imag) z2 (complex or tuple): Third point, either complex number or tuple (real, imag) Returns: Cline: A cline passing through the three points Mathematical Derivation: Substituting three points into the general cline equation: .. math:: c|z|^2 + \alpha z + \bar{\alpha}\bar{z} + d = 0 For each point :math:`z_i`, we get: .. math:: c|z_i|^2 + \alpha z_i + \bar{\alpha}\bar{z_i} + d = 0, \qquad i=0,1,2. With three points, we have three equations with four unknowns (:math:`c`, :math:`\alpha = \alpha_1+i*\alpha_2`, and :math:`d`). We know already that the case :math:`c=0` represents a line, so when the three points are collinear we can set c=0 and solve for the rest. Otherwise since the equation is overdetermined, we can set :math:`c=1` and solve for the circle case. To determine which value of :math:`c` to use, we check if the points are collinear: * When the points are collinear: * Set :math:`c=0` (representing a line) * Calculate :math:`\alpha = i(z_1 - z_0)` perpendicular to the line direction * Solve for :math:`d = -2\text{Re}(\alpha z_0)` * When the points are not collinear: * Set :math:`c=1` (representing a circle) * Solve a system of linear equations for the remaining parameters To check collinearity, we compute: .. math:: \text{Im}\left(\frac{z_2 - z_0}{z_1 - z_0}\right) = 0 When the points are not collinear, we set :math:`c=1` and solve a system of linear equations. We can subtract the equation for :math:`z_0` from the equations for :math:`z_1` and :math:`z_2`: .. math:: c(|z_1|^2 - |z_0|^2) + \alpha(z_1 - z_0) + \bar{\alpha}(\bar{z_1} - \bar{z_0}) &= 0 \\ c(|z_2|^2 - |z_0|^2) + \alpha(z_2 - z_0) + \bar{\alpha}(\bar{z_2} - \bar{z_0}) &= 0 With :math:`c=1`, :math:`\Delta_1 = z_1 - z_0`, :math:`\Delta_2 = z_2 - z_0`, :math:`S_1 = |z_1|^2 - |z_0|^2`, and :math:`S_2 = |z_2|^2 - |z_0|^2`, we get: .. math:: S_1 + \alpha\Delta_1 + \bar{\alpha}\bar{\Delta_1} &= 0 \\ S_2 + \alpha\Delta_2 + \bar{\alpha}\bar{\Delta_2} &= 0 This can be rewritten as: .. math:: S_1 + 2\text{Re}(\alpha\Delta_1) &= 0 \\ S_2 + 2\text{Re}(\alpha\Delta_2) &= 0 Leading to: .. math:: \text{Re}(\alpha\Delta_1) &= -\frac{S_1}{2} \\ \text{Re}(\alpha\Delta_2) &= -\frac{S_2}{2} Expanding :math:`\alpha = a + bi` and :math:`\Delta_j = x_j + y_j i`, we get: .. math:: a x_1 - b y_1 &= -\frac{S_1}{2} \\ a x_2 - b y_2 &= -\frac{S_2}{2} This 2×2 system is solved for :math:`a` and :math:`b` to find :math:`\alpha`. Finally, we compute :math:`d` using the original equation and the value of :math:`z_0`. Algorithm: 1. Check if the points are collinear by testing :math:`\text{Im}((z_2 - z_0)/(z_1 - z_0)) = 0` 2. If collinear: * Set :math:`c = 0` * Calculate :math:`\alpha = i(z_1 - z_0)` * Calculate :math:`d = -2\text{Re}(\alpha z_0)` 3. If not collinear: * Set :math:`c = 1` * Calculate :math:`\Delta_1 = z_1 - z_0, \Delta_2 = z_2 - z_0` * Calculate :math:`S_1 = |z_1|^2 - |z_0|^2, S_2 = |z_2|^2 - |z_0|^2` * Calculate :math:`\alpha` by solving the linear system: .. math:: \begin{pmatrix} \text{Re}(\Delta_1) & -\text{Im}(\Delta_1) \\ \text{Re}(\Delta_2) & -\text{Im}(\Delta_2) \end{pmatrix} \begin{pmatrix} \text{Re}(\alpha) \\ \text{Im}(\alpha) \end{pmatrix} = \begin{pmatrix} -S_1/2 \\ -S_2/2 \end{pmatrix} The solution is: .. math:: \begin{pmatrix} \text{Re}(\alpha) \\ \text{Im}(\alpha) \end{pmatrix} = \begin{pmatrix} \text{Re}(\Delta_1) & -\text{Im}(\Delta_1) \\ \text{Re}(\Delta_2) & -\text{Im}(\Delta_2) \end{pmatrix}^{-1} \begin{pmatrix} -S_1/2 \\ -S_2/2 \end{pmatrix} * Calculate :math:`d = -(|z_0|^2 + 2\text{Re}(\alpha z_0))` """ # Convert inputs to complex numbers # Handle tuples as (real, imag) coordinates if isinstance(z0, tuple): z0 = complex(z0[0], z0[1]) else: z0 = complex(z0) if isinstance(z1, tuple): z1 = complex(z1[0], z1[1]) else: z1 = complex(z1) if isinstance(z2, tuple): z2 = complex(z2[0], z2[1]) else: z2 = complex(z2) # Check if the points are collinear if abs(z1 - z0) < 1e-10: # z0 and z1 are the same point if abs(z2 - z0) < 1e-10: # All three points are the same # Return a point (degenerate case) cline = cls(c=1.0, alpha=-z0, d=abs(z0) ** 2) cline.points = [z0, z1, z2] # Store the points return cline else: # z0 = z1 ≠ z2, so we have a line through z0 and z2 delta = z2 - z0 alpha = 1j * delta # Perpendicular to the direction d = -2 * np.real(alpha * z0) cline = cls(c=0.0, alpha=alpha, d=d) cline.points = [z0, z1, z2] # Store the points return cline # Check collinearity using the imaginary part of the ratio ratio = (z2 - z0) / (z1 - z0) if abs(np.imag(ratio)) < 1e-10: # Points are collinear # Set c = 0 (line) c = 0.0 # Calculate α = i(z₁ - z₀) alpha = 1j * (z1 - z0) # Calculate d = -2Re(αz₀) d = -2 * np.real(alpha * z0) else: # Points are not collinear, so we have a circle # Set c = 1 c = 1.0 # Calculate Δ₁ = z₁ - z₀, Δ₂ = z₂ - z₀ delta1 = z1 - z0 delta2 = z2 - z0 # Calculate S₁ = |z₁|² - |z₀|², S₂ = |z₂|² - |z₀|² S1 = abs(z1) ** 2 - abs(z0) ** 2 S2 = abs(z2) ** 2 - abs(z0) ** 2 # Calculate α by solving the system of equations: # S₁ = -2Re(αΔ₁) # S₂ = -2Re(αΔ₂) # This can be rewritten as: # S₁ = -(αΔ₁ + ᾱΔ̄₁) # S₂ = -(αΔ₂ + ᾱΔ̄₂) # We can solve this by setting up a 2x2 system: # [Re(Δ₁) -Im(Δ₁)] [Re(α)] = [-S₁/2] # [Re(Δ₂) -Im(Δ₂)] [Im(α)] [-S₂/2] A = np.array([[np.real(delta1), -np.imag(delta1)], [np.real(delta2), -np.imag(delta2)]]) b = np.array([-S1 / 2, -S2 / 2]) try: x = np.linalg.solve(A, b) alpha = complex(x[0], x[1]) except np.linalg.LinAlgError: # If the system is singular, the points might be collinear # or have some other special configuration # Fall back to a simpler approach alpha = -np.conjugate(z0) # Calculate d = -(|z₀|² + 2Re(αz₀)) d = -(abs(z0) ** 2 + 2 * np.real(alpha * z0)) # Create the cline and store the points cline = cls(c=c, alpha=alpha, d=d) cline.points = [z0, z1, z2] return cline
[docs] @classmethod def from_line(cls, z0, z1): r"""Construct a cline representing a line through two points. Args: z0 (complex or tuple): First point, either complex number or tuple (real, imag) z1 (complex or tuple): Second point, either complex number or tuple (real, imag) Returns: Cline: A cline representing the line through z0 and z1 For a line, we set :math:`c = 0`, and the parameters are calculated as: - :math:`\alpha = i \cdot (z_1 - z_0)` (perpendicular to the line direction) - :math:`d = -2 \cdot \text{Re}(\alpha \cdot z_0)` """ # Convert inputs to complex numbers # Handle tuples as (real, imag) coordinates if isinstance(z0, tuple): z0 = complex(z0[0], z0[1]) else: z0 = complex(z0) if isinstance(z1, tuple): z1 = complex(z1[0], z1[1]) else: z1 = complex(z1) # Check if points are distinct if abs(z1 - z0) < 1e-10: raise ValueError("Points must be distinct to define a line") # For a line, set c = 0 c = 0.0 # Calculate alpha = i*(z₁ - z₀) alpha = 1j * (z1 - z0) # Calculate d = -2*Re(alpha*z0) d = -2 * np.real(alpha * z0) # Create the cline and store the points cline = cls(c=c, alpha=alpha, d=d) cline.points = [z0, z1] return cline
[docs] @classmethod def from_circle(cls, center, radius): r"""Construct a cline representing a circle with specified center and radius. Args: center (complex or tuple): Center of the circle, either complex number or tuple (real, imag) radius (float): Radius of the circle (must be positive) Returns: Cline: A cline representing the circle with given center and radius For a circle, we set :math:`c = 1`, and the parameters are calculated as: - :math:`\alpha = -\text{center}` - :math:`d = |\text{center}|^2 - \text{radius}^2` """ # Convert center to complex number if isinstance(center, tuple): center = complex(center[0], center[1]) else: center = complex(center) # Validate radius radius = float(radius) if radius <= 0: raise ValueError("Radius must be positive") # For a circle, set c = 1 c = 1.0 # Calculate alpha = -center alpha = -center # Calculate d = |center|^2 - radius^2 d = abs(center) ** 2 - radius**2 # Create the cline cline = cls(c=c, alpha=alpha, d=d) # Store the center and radius for reference cline.center = center cline.radius = radius return cline
[docs] def plot( self, ax=None, figsize=(8, 8), xlim=None, ylim=None, color="blue", point_color="red", label=None, show_points=True, num_points=100, precision=4, **kwargs, ): r"""Plot the cline in the complex plane. Args: ax (matplotlib.axes.Axes, optional): Axes to plot on. If None, a new figure is created. figsize (tuple, optional): Figure size if creating a new figure. Defaults to (8, 8). xlim (tuple, optional): x-axis limits. If None, automatically calculated. ylim (tuple, optional): y-axis limits. If None, automatically calculated. color (str, optional): Color of the cline. Defaults to 'blue'. point_color (str, optional): Color of the points. Defaults to 'red'. label (str, optional): Label for the cline in the legend. Defaults to None. show_points (bool, optional): Whether to show the points used to create the cline. Defaults to True. num_points (int, optional): Number of points to use when plotting a circle. Defaults to 100. precision (int, optional): Number of decimal places to round to. Defaults to 4. **kwargs: Additional keyword arguments passed to the plot function. Returns: matplotlib.axes.Axes: The axes containing the plot. """ # Create a new figure if ax is not provided if ax is None: fig, ax = plt.subplots(figsize=figsize) # Set the aspect ratio to equal ax.set_aspect("equal") # Calculate appropriate limits if not provided auto_xlim = None auto_ylim = None # Auto-calculate limits based on cline type if self.is_circle: # For circles, use center and radius radius_margin = 1.5 * self.radius auto_xlim = (self.center.real - radius_margin, self.center.real + radius_margin) auto_ylim = (self.center.imag - radius_margin, self.center.imag + radius_margin) elif self.is_point: # For points, use a small window around the point auto_xlim = (self.point.real - 1, self.point.real + 1) auto_ylim = (self.point.imag - 1, self.point.imag + 1) elif self.is_line: if self.points is not None and len(self.points) >= 2: # For lines, use the points with a margin p1, p2 = self.points[0], self.points[1] x_diff = abs(p1.real - p2.real) y_diff = abs(p1.imag - p2.imag) x_margin = max(2 * x_diff, 2) y_margin = max(2 * y_diff, 2) # Handle different orderings of coordinates x_min = min(p1.real, p2.real) - x_margin x_max = max(p1.real, p2.real) + x_margin y_min = min(p1.imag, p2.imag) - y_margin y_max = max(p1.imag, p2.imag) + y_margin auto_xlim = (x_min, x_max) auto_ylim = (y_min, y_max) else: # Fallback for lines without stored points auto_xlim = (-5, 5) auto_ylim = (-5, 5) # Apply limits, prioritizing provided values over auto-calculated ones ax.set_xlim(xlim if xlim is not None else auto_xlim) ax.set_ylim(ylim if ylim is not None else auto_ylim) # Add grid ax.grid(True, linestyle="--", alpha=0.7) # Plot the cline based on its type if self.is_circle: # Plot a circle theta = np.linspace(0, 2 * np.pi, num_points) x = self.center.real + self.radius * np.cos(theta) y = self.center.imag + self.radius * np.sin(theta) ax.plot(x, y, color=color, label=label, **kwargs) # Mark the center ax.plot(self.center.real, self.center.imag, "o", color=color, markersize=5) center_str = self._format_complex(self.center, precision) ax.text( self.center.real, self.center.imag, f" center: {center_str}", color=color, fontsize=8, verticalalignment="bottom", ) elif self.is_point: # Plot a point ax.plot( self.point.real, self.point.imag, "o", color=color, markersize=8, label=label, **kwargs, ) point_str = self._format_complex(self.point, precision) ax.text( self.point.real, self.point.imag, f" point: {point_str}", color=color, fontsize=8, verticalalignment="bottom", ) elif self.is_line: # Completely rewritten line plotting logic for maximum robustness if self.points is not None and len(self.points) >= 2: # Get the two points that define the line z0, z1 = self.points[0], self.points[1] # Get the current axis limits x_min, x_max = ax.get_xlim() y_min, y_max = ax.get_ylim() # Calculate the slope dx = z1.real - z0.real dy = z1.imag - z0.imag # Handle vertical lines (or nearly vertical lines) if abs(dx) < 1e-10: # Use a vertical line at the x-coordinate x_coords = [z0.real, z0.real] y_coords = [y_min, y_max] else: # For non-vertical lines, calculate y = m(x - x0) + y0 slope = dy / dx # Calculate y-coordinates at the min and max x values y_at_xmin = slope * (x_min - z0.real) + z0.imag y_at_xmax = slope * (x_max - z0.real) + z0.imag # Use the boundaries to create the line x_coords = [x_min, x_max] y_coords = [y_at_xmin, y_at_xmax] # If the line runs outside the y-axis boundaries, calculate intersections if ( y_at_xmin < y_min or y_at_xmin > y_max or y_at_xmax < y_min or y_at_xmax > y_max ): points = [] # Calculate intersection with each boundary line # Left boundary (x = x_min) if y_min <= y_at_xmin <= y_max: points.append((x_min, y_at_xmin)) # Right boundary (x = x_max) if y_min <= y_at_xmax <= y_max: points.append((x_max, y_at_xmax)) # Calculate x-coordinates at min and max y values if abs(slope) > 1e-10: # Avoid division by zero # Calculate x at minimum y value x_at_ymin = (y_min - z0.imag) / slope + z0.real # Calculate x at maximum y value x_at_ymax = (y_max - z0.imag) / slope + z0.real # Bottom boundary (y = y_min) if x_min <= x_at_ymin <= x_max: points.append((x_at_ymin, y_min)) # Top boundary (y = y_max) if x_min <= x_at_ymax <= x_max: points.append((x_at_ymax, y_max)) # If we found at least 2 intersection points, use them if len(points) >= 2: # Sort the points by x-coordinate for consistency points.sort() x_coords = [p[0] for p in points[:2]] y_coords = [p[1] for p in points[:2]] # Double-check if the line actually intersects the plot area if len(x_coords) >= 2: # Plot the line ax.plot(x_coords, y_coords, color=color, label=label, **kwargs) else: # Fallback approach if points are not available # Draw a line in the direction of the normal vector through the origin direction = self.direction_vector # Calculate a reasonable length for the line max_extent = max( abs(ax.get_xlim()[0]), abs(ax.get_xlim()[1]), abs(ax.get_ylim()[0]), abs(ax.get_ylim()[1]), ) # Generate points along the line t_values = np.linspace(-2 * max_extent, 2 * max_extent, num_points) points = [self.point_on_line + t * direction for t in t_values] # Extract x and y coordinates x_coords = [p.real for p in points] y_coords = [p.imag for p in points] # Plot the line ax.plot(x_coords, y_coords, color=color, label=label, **kwargs) # Plot the points used to create the cline if available and requested if self.points is not None and show_points: for i, point in enumerate(self.points): ax.plot(point.real, point.imag, "o", color=point_color, markersize=8) point_str = self._format_complex(point, precision) ax.text( point.real, point.imag, f" $z_{i}$: {point_str}", color=point_color, fontsize=10, verticalalignment="bottom", ) # Add title based on the cline type if label is None: if self.is_circle: center_str = self._format_complex(self.center, precision) radius_str = self._format_float(self.radius, precision) ax.set_title(f"Circle: center={center_str}, radius={radius_str}") elif self.is_point: point_str = self._format_complex(self.point, precision) ax.set_title(f"Point: {point_str}") elif self.is_line: normal_str = self._format_complex(self.normal_vector, precision) distance_str = self._format_float(self.distance_from_origin, precision) ax.set_title(f"Line: normal={normal_str}, distance={distance_str}") else: ax.set_title("Invalid Cline") else: ax.set_title(label) # Add legend if there's a label if label is not None: ax.legend() # Add axis labels ax.set_xlabel("Re(z)") ax.set_ylabel("Im(z)") return ax
[docs] def __str__(self): """Return a string representation of the cline.""" # Format the equation with rounded values c_str = self._format_float(self.c) alpha_str = self._format_complex(self.alpha) alpha_conj_str = self._format_complex(np.conjugate(self.alpha)) d_str = self._format_float(self.d) equation = f"{c_str}·|z|² + {alpha_str}·z + {alpha_conj_str}·z̄ + {d_str} = 0" # Format the discriminant disc_str = self._format_float(self.discriminant) result = f"A cline with equation: {equation}\n" result += f"Discriminant: |alpha|^2-c*d = {disc_str}\n" result += "The cline describes: " if self.is_circle: center_str = self._format_complex(self.center) radius_str = self._format_float(self.radius) result += f"a circle with center {center_str} and radius {radius_str}" elif self.is_point: point_str = self._format_complex(self.point) result += f"a point at {point_str}" elif self.is_line: a_str = self._format_float(self.a) b_str = self._format_float(self.b) d_half_str = self._format_float(-self.d / 2) normal_str = self._format_complex(self.normal_vector) dir_str = self._format_complex(self.direction_vector) dist_str = self._format_float(self.distance_from_origin) point_str = self._format_complex(self.point_on_line) result += "a line with the following properties:\n" result += ( f" 1. Cartesian Form: {a_str}x - {b_str}y = {d_half_str} " f"where alpha = {a_str} + {b_str}i\n" ) result += f" 2. Normal Vector: {normal_str}\n" result += f" 3. Direction Vector: {dir_str} (perpendicular to normal)\n" result += f" 4. Distance from Origin: {dist_str}\n" result += f" 5. Parametric Form: z(t) = {point_str} + t·{dir_str}" else: result += "not a valid geometric object (discriminant < 0)" return result
[docs] def __repr__(self): """Return a string representation for debugging.""" return self.__str__()