"""
Type definitions for analytic continuation package.
Matches the schemas defined in laurent_pipeline_bundle/schemas/types.json
"""
from dataclasses import dataclass, field
from typing import List, Optional
import json
[docs]
@dataclass
class Point:
"""A 2D point, used for both screen and logical coordinates."""
x: float
y: float
index: Optional[int] = None
[docs]
def to_complex(self) -> complex:
"""Convert to complex number (x + iy)."""
return complex(self.x, self.y)
[docs]
@classmethod
def from_complex(cls, z: complex, index: Optional[int] = None) -> "Point":
"""Create from complex number."""
return cls(x=z.real, y=z.imag, index=index)
[docs]
def to_dict(self) -> dict:
d = {"x": self.x, "y": self.y}
if self.index is not None:
d["index"] = self.index
return d
[docs]
@classmethod
def from_dict(cls, d: dict) -> "Point":
return cls(x=d["x"], y=d["y"], index=d.get("index"))
[docs]
@dataclass
class Spline:
"""A sequence of points forming a spline or polyline."""
points: List[Point]
closed: bool = False
[docs]
def to_dict(self) -> dict:
return {
"points": [p.to_dict() for p in self.points],
"closed": self.closed,
}
[docs]
@classmethod
def from_dict(cls, d: dict) -> "Spline":
return cls(
points=[Point.from_dict(p) for p in d["points"]],
closed=d.get("closed", False),
)
[docs]
@dataclass
class SplineParameters:
"""Parameters from a SplineExport."""
tension: float = 0.5
adaptiveTolerance: float = 3.0
minDistance: float = 15.0
[docs]
def to_dict(self) -> dict:
return {
"tension": self.tension,
"adaptiveTolerance": self.adaptiveTolerance,
"minDistance": self.minDistance,
}
[docs]
@classmethod
def from_dict(cls, d: dict) -> "SplineParameters":
return cls(
tension=d.get("tension", 0.5),
adaptiveTolerance=d.get("adaptiveTolerance", 3.0),
minDistance=d.get("minDistance", 15.0),
)
[docs]
@dataclass
class SplineExport:
"""
Full spline export structure matching the React frontend format.
Contains control points, interpolated spline, and adaptive polyline.
"""
version: str
timestamp: str
closed: bool
parameters: SplineParameters
controlPoints: List[Point]
spline: List[Point] = field(default_factory=list)
adaptivePolyline: List[Point] = field(default_factory=list)
stats: Optional[dict] = None
[docs]
def get_polyline(self, prefer: str = "adaptive") -> List[Point]:
"""
Get the best available polyline representation.
Parameters
----------
prefer : str
Which representation to prefer: 'adaptive', 'spline', or 'control'
Returns
-------
List[Point]
The polyline points
"""
if prefer == "adaptive" and self.adaptivePolyline:
return self.adaptivePolyline
elif prefer == "spline" and self.spline:
return self.spline
elif self.controlPoints:
return self.controlPoints
# Fallback chain
return self.adaptivePolyline or self.spline or self.controlPoints
[docs]
def to_dict(self) -> dict:
d = {
"version": self.version,
"timestamp": self.timestamp,
"closed": self.closed,
"parameters": self.parameters.to_dict(),
"controlPoints": [p.to_dict() for p in self.controlPoints],
}
if self.spline:
d["spline"] = [p.to_dict() for p in self.spline]
if self.adaptivePolyline:
d["adaptivePolyline"] = [p.to_dict() for p in self.adaptivePolyline]
if self.stats:
d["stats"] = self.stats
return d
[docs]
@classmethod
def from_dict(cls, d: dict) -> "SplineExport":
return cls(
version=d["version"],
timestamp=d["timestamp"],
closed=d["closed"],
parameters=SplineParameters.from_dict(d["parameters"]),
controlPoints=[Point.from_dict(p) for p in d["controlPoints"]],
spline=[Point.from_dict(p) for p in d.get("spline", [])],
adaptivePolyline=[Point.from_dict(p) for p in d.get("adaptivePolyline", [])],
stats=d.get("stats"),
)
[docs]
@classmethod
def from_json(cls, json_str: str) -> "SplineExport":
return cls.from_dict(json.loads(json_str))
[docs]
def to_json(self, indent: int = 2) -> str:
return json.dumps(self.to_dict(), indent=indent)
[docs]
@dataclass
class Complex:
"""Complex number representation matching the schema."""
re: float
im: float
[docs]
def to_complex(self) -> complex:
return complex(self.re, self.im)
[docs]
@classmethod
def from_complex(cls, z: complex) -> "Complex":
return cls(re=z.real, im=z.imag)
[docs]
def to_dict(self) -> dict:
return {"re": self.re, "im": self.im}
[docs]
@classmethod
def from_dict(cls, d: dict) -> "Complex":
return cls(re=d["re"], im=d["im"])
[docs]
@dataclass
class LaurentMap:
"""
Laurent series map φ(ζ) = a₀ + Σₙ aₙζⁿ + Σₙ bₙζ⁻ⁿ
Maps the unit circle to the curve boundary.
"""
N: int # Number of terms
a0: Complex
a: List[Complex] # Positive power coefficients [a₁, a₂, ..., aₙ]
b: List[Complex] # Negative power coefficients [b₁, b₂, ..., bₙ]
[docs]
def to_dict(self) -> dict:
return {
"N": self.N,
"a0": self.a0.to_dict(),
"a": [c.to_dict() for c in self.a],
"b": [c.to_dict() for c in self.b],
}
[docs]
@classmethod
def from_dict(cls, d: dict) -> "LaurentMap":
return cls(
N=d["N"],
a0=Complex.from_dict(d["a0"]),
a=[Complex.from_dict(c) for c in d["a"]],
b=[Complex.from_dict(c) for c in d["b"]],
)