"""
Pydantic models for the Analytic Continuation Server API.
This module contains all request/response models organized by domain:
- Transform models: coordinate transformation requests
- Meromorphic models: function building from zeros/poles
- Laurent models: Laurent series fitting and inversion
- Session models: session management and progress tracking
- WebGL models: WebGL-friendly data formats for rendering
"""
from typing import List, Optional, Tuple, Dict, Any
from pydantic import BaseModel, Field
# =============================================================================
# Base Models
# =============================================================================
[docs]
class PointModel(BaseModel):
"""A 2D point with optional index."""
x: float
y: float
index: Optional[int] = None
[docs]
class ComplexModel(BaseModel):
"""Complex number representation."""
re: float
im: float
[docs]
class ViewBoundsModel(BaseModel):
"""Define transform by screen size and logical view bounds."""
screen_width: float
screen_height: float
logical_x_min: float
logical_x_max: float
logical_y_min: float
logical_y_max: float
uniform: bool = True
[docs]
class SingularityModel(BaseModel):
"""A zero or pole location with optional multiplicity."""
x: float
y: float
multiplicity: int = 1
# =============================================================================
# Spline Export Models
# =============================================================================
[docs]
class SplineParametersModel(BaseModel):
"""Spline drawing parameters."""
tension: float = 0.5
adaptiveTolerance: float = 3.0
minDistance: float = 15.0
[docs]
class SplineExportModel(BaseModel):
"""Complete spline export data structure."""
version: str
timestamp: str
closed: bool
parameters: SplineParametersModel
controlPoints: List[PointModel]
spline: List[PointModel] = []
adaptivePolyline: List[PointModel] = []
stats: Optional[dict] = None
# =============================================================================
# Transform Request/Response Models
# =============================================================================
[docs]
class ZoomRequest(BaseModel):
"""Request to apply zoom to transform parameters."""
params: TransformParamsModel
factor: float
center_x: Optional[float] = None
center_y: Optional[float] = None
[docs]
class PanRequest(BaseModel):
"""Request to apply pan to transform parameters."""
params: TransformParamsModel
delta_x: float
delta_y: float
# =============================================================================
# Domain Coloring Models
# =============================================================================
[docs]
class DomainColorRequest(BaseModel):
"""Request for domain coloring image generation."""
expression: str = Field(..., description="Complex function expression, e.g., 'z^2 + 1'")
x_range: Tuple[float, float] = (-2.0, 2.0)
y_range: Optional[Tuple[float, float]] = None
resolution: int = Field(800, ge=100, le=4000)
mode: str = Field("standard", pattern="^(standard|phase|modulus)$")
mod_contours: bool = False
arg_contours: bool = False
show_legend: bool = True
dark_theme: bool = True
[docs]
class ValidationRenderRequest(BaseModel):
"""Request for validation render with zeros/poles overlay."""
expression: str = Field(..., description="Complex function expression")
zeros: List[SingularityModel] = []
poles: List[SingularityModel] = []
x_range: Tuple[float, float] = (-3.0, 3.0)
y_range: Optional[Tuple[float, float]] = None
resolution: int = Field(600, ge=100, le=2000)
mod_contours: bool = False
arg_contours: bool = False
# Curve overlay
curve_points: Optional[List[Dict[str, float]]] = None # [{re, im}, ...]
curve_color: str = "white"
curve_width: float = 2.0
# Axis ticks
show_axis_ticks: bool = True
tick_interval: float = 1.0
# =============================================================================
# Meromorphic Function Models
# =============================================================================
[docs]
class MeromorphicRequest(BaseModel):
"""Request to build a meromorphic function from zeros and poles."""
zeros: List[SingularityModel] = []
poles: List[SingularityModel] = []
params: Optional[TransformParamsModel] = None
coords: str = Field("logical", pattern="^(logical|screen)$")
[docs]
class MeromorphicResponse(BaseModel):
"""Response with the built expression and logical coordinates."""
expression: str
zeros: List[SingularityModel]
poles: List[SingularityModel]
# =============================================================================
# Laurent Pipeline Models
# =============================================================================
[docs]
class LaurentMapModel(BaseModel):
"""Serializable Laurent map."""
N: int
a0: ComplexModel
a: List[ComplexModel]
b: List[ComplexModel]
[docs]
class LaurentFitRequest(BaseModel):
"""Request to fit a Laurent map."""
export: SplineExportModel
N_min: int = 6
N_max: int = 64
m_samples: int = 2048
[docs]
class LaurentFitResponse(BaseModel):
"""Response from Laurent fitting."""
ok: bool
failure_reason: Optional[str] = None
curve_scale: float = 0.0
laurent_map: Optional[LaurentMapModel] = None
fit_max_err: float = 0.0
fit_rms_err: float = 0.0
checks: dict = {}
[docs]
class PoleModel(BaseModel):
"""A pole of a meromorphic function."""
z_re: float
z_im: float
multiplicity: int = 1
[docs]
class InvertRequest(BaseModel):
"""Request to invert a point through the Laurent map."""
z_re: float
z_im: float
laurent_map: LaurentMapModel
curve_scale: float
[docs]
class InvertResponse(BaseModel):
"""Response from point inversion."""
converged: bool
zeta_re: Optional[float] = None
zeta_im: Optional[float] = None
residual: float = 0.0
iters: int = 0
[docs]
class HolomorphicCheckRequest(BaseModel):
"""Request to check if poles are outside annulus image."""
poles: List[PoleModel]
laurent_map: LaurentMapModel
curve_scale: float
min_distance_param: float
[docs]
class HolomorphicCheckResponse(BaseModel):
"""Response from holomorphic check."""
ok: bool
min_pole_distance: float
closest_pole_re: Optional[float] = None
closest_pole_im: Optional[float] = None
failure_reason: Optional[str] = None
[docs]
class CompositionRequest(BaseModel):
"""Request to compute the analytic continuation composition."""
z_re: float
z_im: float
expression: str
laurent_map: LaurentMapModel
curve_scale: float
[docs]
class CompositionResponse(BaseModel):
"""Response from composition computation."""
ok: bool
value_re: Optional[float] = None
value_im: Optional[float] = None
zeta_re: Optional[float] = None
zeta_im: Optional[float] = None
residual: Optional[float] = None
failure_reason: Optional[str] = None
# =============================================================================
# Contour Pre-Check Models
# =============================================================================
[docs]
class ContourPreCheckRequest(BaseModel):
"""Request for quick pre-check on a raw user-drawn contour."""
points: List[PointModel] = Field(
..., description="Contour points from user input or adaptive polyline"
)
closed: bool = Field(default=True, description="Whether the contour is closed")
adaptive_polyline: Optional[List[PointModel]] = Field(
None, description="Adaptive polyline (if available, more accurate)"
)
[docs]
class ContourPreCheckResponse(BaseModel):
"""Response from quick contour pre-check."""
ok: bool = Field(..., description="True if no errors detected")
proceed: bool = Field(..., description="True if safe to continue to Laurent fitting")
is_closed: bool
is_simple: bool = Field(..., description="No self-intersections")
has_sufficient_points: bool
has_reasonable_aspect: bool = Field(..., description="Not too thin/elongated")
has_reasonable_curvature: bool = Field(..., description="No extremely sharp turns")
num_points: int
perimeter: float
bounding_box: List[float] = Field(..., description="[x_min, y_min, x_max, y_max]")
aspect_ratio: float = Field(..., description="width/height or height/width, whichever > 1")
estimated_diameter: float
min_segment_length: float
max_segment_length: float
max_turning_angle_degrees: float = Field(
..., description="Maximum angle change between segments"
)
warnings: List[str]
errors: List[str]
estimated_difficulty: str = Field(
..., description="easy, moderate, hard, extreme, or infeasible"
)
estimated_fit_time_seconds: Optional[float] = Field(
None, description="Rough estimate of Laurent fitting time"
)
# =============================================================================
# Intrinsic Curve Analysis Models
# =============================================================================
[docs]
class IntrinsicCurveRequest(BaseModel):
"""Request for intrinsic curve analysis of a bijection."""
laurent_map: LaurentMapModel
curve_scale: float
samples: int = Field(default=2048, ge=32, le=8192, description="Number of samples")
[docs]
class CurvatureMetricsModel(BaseModel):
"""Curvature-based complexity metrics."""
total_curvature: float = Field(..., description="Integral of |kappa(s)|ds")
curvature_variation: float = Field(..., description="Integral of |kappa'(s)|ds")
max_curvature: float = Field(..., description="max|kappa(s)|")
mean_curvature: float = Field(..., description="(1/L) * integral of |kappa(s)|ds")
curvature_std: float = Field(..., description="Standard deviation of |kappa(s)|")
[docs]
class JacobianMetricsModel(BaseModel):
"""Jacobian (conformal distortion) metrics."""
min_jacobian: float = Field(..., description="min|z'(zeta)|")
max_jacobian: float = Field(..., description="max|z'(zeta)|")
jacobian_ratio: float = Field(..., description="max/min - condition number analog")
log_deriv_variation: float = Field(..., description="Variation of log|z'(zeta)|")
arg_deriv_variation: float = Field(..., description="Total variation of arg(z'(zeta))")
[docs]
class ComplexityScoresModel(BaseModel):
"""Derived complexity scores for computational cost estimation."""
inversion_difficulty: float = Field(
..., description="Estimated relative cost of inversion (1.0 = baseline)"
)
sampling_density_factor: float = Field(
..., description="Suggested sampling density multiplier"
)
newton_convergence_factor: float = Field(
..., description="Expected Newton iterations multiplier"
)
[docs]
class SuggestedConfigModel(BaseModel):
"""Suggested inversion configuration based on complexity analysis."""
theta_grid: int = Field(..., description="Suggested number of theta samples")
max_iters: int = Field(..., description="Suggested max Newton iterations")
max_backtracks: int = Field(..., description="Suggested max backtracking steps")
damping: bool = Field(..., description="Whether to use damping")
[docs]
class IntrinsicCurveResponse(BaseModel):
"""Response from intrinsic curve analysis."""
winding_number: float = Field(..., description="Should be 1 for simple closed curves")
total_arc_length: float = Field(..., description="Perimeter L")
curvature: CurvatureMetricsModel
jacobian: JacobianMetricsModel
complexity: ComplexityScoresModel
suggested_config: SuggestedConfigModel
cesaro_arc_lengths: Optional[List[float]] = Field(None, description="Arc length samples")
cesaro_curvatures: Optional[List[float]] = Field(None, description="Curvature samples")
whewell_tangent_angles: Optional[List[float]] = Field(None, description="Tangent angle samples")
summary: str = Field(..., description="Human-readable analysis summary")
# =============================================================================
# WebGL Render Data Models
# =============================================================================
[docs]
class WebGLLaurentCoeffs(BaseModel):
"""Laurent coefficients formatted for WebGL shader consumption."""
N: int
coeffs_neg: List[List[float]] # [[re, im], ...] for a_{-N} to a_{-1}
coeffs_pos: List[List[float]] # [[re, im], ...] for a_0 to a_N
curve_scale: float
[docs]
class ContinuationDefinition(BaseModel):
"""
Complete definition of an analytic continuation.
This captures everything needed to:
1. Evaluate the continuation F(z) = A(f(B(z))) at any point
2. Reconstruct the visualization
3. Export/import the continuation
"""
version: str = "1.0"
laurent_map: WebGLLaurentCoeffs
expression: Optional[str] = None
zeros: List[Dict[str, float]] = []
poles: List[Dict[str, float]] = []
created_at: Optional[str] = None
session_id: Optional[str] = None
input_hash: Optional[str] = None
fit_max_error: Optional[float] = None
fit_rms_error: Optional[float] = None
curve_point_count: Optional[int] = None
curve_closed: bool = True
[docs]
def to_evaluation_info(self) -> Dict[str, Any]:
"""Return info needed to evaluate F(z) at a point."""
return {
"method": "schwarz_reflection_composition",
"description": "F(z) = A(f(B(z))) where A,B are Schwarz reflections through the curve",
"simplified": "Due to shared parameterization, F(z) = f(z(zeta)) where zeta = z^-1(z)",
"laurent_map": {
"N": self.laurent_map.N,
"curve_scale": self.laurent_map.curve_scale,
"evaluation": "z(zeta) = a_0 + sum_k a_k*zeta^k + sum_k b_k*zeta^-k",
},
"function": self.expression
or f"rational with {len(self.zeros)} zeros, {len(self.poles)} poles",
}
[docs]
class WebGLRenderDataRequest(BaseModel):
"""Request for WebGL render data."""
export: SplineExportModel
zeros: List[SingularityModel] = []
poles: List[SingularityModel] = []
transform_params: Optional[TransformParamsModel] = None
expression: Optional[str] = None
N_min: int = 6
N_max: int = 64
[docs]
class WebGLRenderDataResponse(BaseModel):
"""Complete data needed for WebGL domain coloring of analytic continuation."""
ok: bool
failure_reason: Optional[str] = None
laurent_coeffs: Optional[WebGLLaurentCoeffs] = None
zeros: List[List[float]] = [] # [[re, im], ...]
poles: List[List[float]] = [] # [[re, im], ...]
expression: Optional[str] = None
continuation: Optional[ContinuationDefinition] = None
session_id: Optional[str] = None
[docs]
class WebGLRenderWithProgressRequest(BaseModel):
"""Request for WebGL render data with progress tracking."""
session_id: Optional[str] = None
export: SplineExportModel
zeros: List[SingularityModel] = []
poles: List[SingularityModel] = []
expression: Optional[str] = None
transform_params: Optional[TransformParamsModel] = None
N_min: int = 6
N_max: int = 64
auto_resume: bool = True
# =============================================================================
# Session Management Models
# =============================================================================
[docs]
class SessionStartRequest(BaseModel):
"""Request to start a new pipeline session."""
expression: Optional[str] = None
curve_data: Optional[dict] = None
zeros: List[SingularityModel] = []
poles: List[SingularityModel] = []
[docs]
class SessionResponse(BaseModel):
"""Response with session information."""
session_id: str
status: str
created_at: str
expression: Optional[str] = None
[docs]
class CheckResumableRequest(BaseModel):
"""Request to check if a computation can be resumed from cache."""
export: Optional[SplineExportModel] = None
zeros: List[SingularityModel] = []
poles: List[SingularityModel] = []
expression: Optional[str] = None