Source code for pysmithchart.moebius_transform

"""This module contains the implementation for moebius transform."""

from collections.abc import Iterable

from matplotlib.patches import Arc
from matplotlib.path import Path
from matplotlib.transforms import Transform, Affine2D
import numpy as np

from .utils import z_to_xy
from .constants import SC_INFINITY

__all__ = ["MoebiusTransform", "InvertedMoebiusTransform"]


class BaseMoebiusTransform(Transform):
    """Abstract class to work around circular imports."""

    #: The number of input dimensions (always 2).
    input_dims = 2
    #: The number of output dimensions (always 2).
    output_dims = 2
    #: Whether the transform is separable (always False).
    is_separable = False

    def __init__(self, axes, *args, **kwargs):
        """Initialize the inverse polar translation transformation."""
        super().__init__(*args, **kwargs)
        self.axes = axes

    def inverted(self):
        """To be implemented in subclasses."""
        raise NotImplementedError("Subclasses must implement this method.")


[docs] class MoebiusTransform(BaseMoebiusTransform): """ Transform points and paths into Smith chart data space. This class implements the Möbius transformation required to map Cartesian coordinates to the Smith chart's complex data space. It supports point transformations and path transformations for visualizing data on the Smith chart. """
[docs] def transform_non_affine(self, values): """ Apply the non-affine Möbius transformation to input data. Args: values (array-like): The input data to transform. Can be a single point (x, y) or an iterable of points. Returns: list or tuple: The transformed points in Smith chart data space. """ def moebius_xy(_xy): return z_to_xy(self.axes.moebius_z(*_xy)) if isinstance(values[0], Iterable): return list(map(moebius_xy, values)) return moebius_xy(values)
[docs] def transform_path_non_affine(self, path): """ Transform a path using the Möbius transformation. This uses path._interpolation identify if the is a x or y gridline. This method generates arcs based on the Möbius transformation. The method supports linear interpolation (linetype=1) for non-gridline paths. Args: path (matplotlib.path.Path): The input path to transform. Returns: matplotlib.path.Path: The transformed path in Smith chart data space. """ vertices = path.vertices codes = path.codes linetype = path._interpolation_steps # pylint: disable=protected-access if linetype in ["x_gridline", "y_gridline"]: assert len(vertices) == 2 x, y = np.array(list(zip(*vertices))) z = self.axes.moebius_z(x, y) if linetype == "x_gridline": assert x[0] == x[1] zm = 0.5 * (1 + self.axes.moebius_z(x[0])) else: assert y[0] == y[1] zm = 1 + 1j / y[0] d = 2 * abs(zm - 1) # For y_gridlines, calculate the full arc span # The arc should go from the leftmost point to the rightmost point on the circle if linetype == "y_gridline": # The circle is centered at zm with radius d/2 # It intersects the real axis (Im=0) at two points # Left intersection: zm - d/2, Right intersection: zm + d/2 # But we want angles measured from center zm # The full arc should span from one side to the other # For a reactance circle, this is typically from -180° to 0° or 0° to 180° # depending on sign, but let's calculate based on actual geometry # The arc goes from the leftmost to rightmost point # In S-parameter space, the leftmost point is at x=0 (which maps to s=-1) # Calculate where the line at x=0 intersects our circle z_left = self.axes.moebius_z(0, y[0]) # Left edge # For right edge, we want to go as far as possible # The circle might complete before reaching infinity # Calculate the rightmost intersection: where the circle intersects Re(z)=1 (right edge) # or where it completes (Im(z)=0) # Simpler approach: use a very large x value to approximate infinity z_right = self.axes.moebius_z(SC_INFINITY, y[0]) ang0, ang1 = np.angle([z_left - zm, z_right - zm], deg=True) % 360 else: # For x_gridlines, use the transformed endpoints as before ang0, ang1 = np.angle(z - zm, deg=True) % 360 reverse = ang0 > ang1 if reverse: ang0, ang1 = (ang1, ang0) arc = Arc( z_to_xy(zm), d, d, theta1=ang0, theta2=ang1, transform=Affine2D(), ) arc._path = Path.arc(ang0, ang1) # pylint: disable=protected-access arc_path = arc.get_patch_transform().transform_path(arc.get_path()) if reverse: new_vertices = arc_path.vertices[::-1] else: new_vertices = arc_path.vertices new_codes = arc_path.codes elif linetype == 1: new_vertices = self.transform_non_affine(vertices) new_codes = codes else: raise NotImplementedError("Value for 'path_interpolation' cannot be interpreted.") return Path(new_vertices, new_codes)
[docs] def inverted(self): """ Return the inverse Möbius transformation. This method provides the inverse transformation for mapping points or paths back from the Smith chart's data space to Cartesian coordinates. Returns: SmithAxes.InvertedMoebiusTransform: The inverted transformation instance. Example: >>> transform = MoebiusTransform(axes) >>> inverted_transform = transform.inverted() """ return InvertedMoebiusTransform(self.axes)
[docs] class InvertedMoebiusTransform(BaseMoebiusTransform): """ Perform the inverse transformation for points and paths in Smith chart data space. This class implements the inverse Möbius transformation, which maps points and paths from the Smith chart's data space back to Cartesian coordinates. It is typically used as the inverse of the `MoebiusTransform` class. """
[docs] def transform_non_affine(self, values): """ Apply the non-affine inverse Möbius transformation to input data. Args: values (array-like): The input data to transform, given as a list of (x, y) points. Returns: list: The transformed points, mapped from the Smith chart data space back to Cartesian coordinates. """ def _moebius_inv_xy(_xy): return z_to_xy(self.axes.moebius_inv_z(*_xy)) return list(map(_moebius_inv_xy, values))
[docs] def inverted(self): """ Return the forward Möbius transformation. This method provides the forward Möbius transformation to map points from Cartesian coordinates to Smith chart data space. Returns: SmithAxes.MoebiusTransform: The forward Möbius transformation instance. Example: >>> inverted_transform = InvertedMoebiusTransform(axes) >>> forward_transform = inverted_transform.inverted() """ return MoebiusTransform(self.axes)