Intrinsic Curve Analysis

The intrinsic curve module provides tools for analyzing curves using their intrinsic representations: the Cesaro form (curvature vs arc length) and Whewell form (tangent angle vs arc length).

Mathematical Background

Intrinsic representations describe curves independent of their position and orientation:

Cesaro Form: Expresses curvature kappa as a function of arc length s:

\[\kappa = \kappa(s)\]

Whewell Form: Expresses tangent angle phi as a function of arc length s:

\[\phi = \phi(s)\]

These representations are related by:

\[\kappa(s) = \frac{d\phi}{ds}\]

Representations

class analytic_continuation.CesaroRepresentation[source]

Bases: object

Cesàro intrinsic form: κ(s) - curvature as function of arc length.

arc_lengths

Cumulative arc length values s[i] at sample points

Type:

np.ndarray

curvatures

Curvature κ(s[i]) at each sample point

Type:

np.ndarray

total_arc_length

Total perimeter L = s[-1]

Type:

float

samples

Number of sample points

Type:

int

Parameters:
arc_lengths: ndarray
curvatures: ndarray
total_arc_length: float
samples: int
kappa_at(s)[source]

Interpolate curvature at arc length s.

Parameters:

s (float)

Return type:

float

to_dict()[source]
Return type:

dict

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

CesaroRepresentation

__init__(arc_lengths, curvatures, total_arc_length, samples)
Parameters:
Return type:

None

class analytic_continuation.WhewellRepresentation[source]

Bases: object

Whewell intrinsic form: φ(s) - tangent angle as function of arc length.

The tangent angle φ(s) = ∫₀ˢ κ(t) dt + φ₀

arc_lengths

Cumulative arc length values

Type:

np.ndarray

tangent_angles

Tangent angle φ(s[i]) at each sample point (radians)

Type:

np.ndarray

total_arc_length

Total perimeter

Type:

float

winding_number

φ(L) - φ(0) = 2π·n for winding number n (should be 2π for simple curves)

Type:

float

samples

Number of sample points

Type:

int

Parameters:
arc_lengths: ndarray
tangent_angles: ndarray
total_arc_length: float
winding_number: float
samples: int
phi_at(s)[source]

Interpolate tangent angle at arc length s.

Parameters:

s (float)

Return type:

float

to_dict()[source]
Return type:

dict

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

WhewellRepresentation

__init__(arc_lengths, tangent_angles, total_arc_length, winding_number, samples)
Parameters:
Return type:

None

Computation Functions

analytic_continuation.compute_cesaro_form(log_data)[source]

Convert log bijection data to Cesàro form κ(s).

The curvature for a parametric curve z(θ) is:

κ = Im[z̄’ · z’’] / |z’|³

where z’ = dz/dθ.

Parameters:

log_data (LogBijectionData) – The log-transformed bijection data

Returns:

The Cesàro (curvature) representation

Return type:

CesaroRepresentation

analytic_continuation.compute_whewell_form(cesaro, initial_angle=0.0)[source]

Convert Cesàro form to Whewell form φ(s) by integration.

φ(s) = φ₀ + ∫₀ˢ κ(t) dt

Parameters:
  • cesaro (CesaroRepresentation) – The Cesàro (curvature) representation

  • initial_angle (float) – Initial tangent angle φ(0)

Returns:

The Whewell (tangent angle) representation

Return type:

WhewellRepresentation

Example:

from analytic_continuation import compute_cesaro_form, compute_whewell_form
import numpy as np

# Circle parameterized by angle
t = np.linspace(0, 2 * np.pi, 1000)
curve = np.exp(1j * t)

# Compute intrinsic forms
cesaro = compute_cesaro_form(curve)
whewell = compute_whewell_form(curve)

# For a circle, curvature should be constant
print(f"Curvature range: {cesaro.kappa.min():.3f} to {cesaro.kappa.max():.3f}")

Log Bijection Analysis

class analytic_continuation.LogBijectionData[source]

Bases: object

Natural log of the bijection z(ζ), storing both the original Laurent coefficients and derived intrinsic representations.

Taking log linearizes the multiplicative structure:

log(z(ζ)) = log|z| + i·arg(z)

The derivative relationship:

d/dζ[log(z(ζ))] = z’(ζ)/z(ζ)

Parameters:
laurent_N: int
laurent_a0: complex
laurent_a: ndarray
laurent_b: ndarray
curve_scale: float
theta_samples: ndarray
log_z_samples: ndarray
z_samples: ndarray
log_derivative_samples: ndarray
to_dict()[source]
Return type:

dict

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

LogBijectionData

__init__(laurent_N, laurent_a0, laurent_a, laurent_b, curve_scale, theta_samples, log_z_samples, z_samples, log_derivative_samples)
Parameters:
Return type:

None

analytic_continuation.compute_log_bijection(lmap, curve_scale, samples=4096)[source]

Compute the natural log of the bijection z(ζ) on the unit circle.

Uses continuous branch selection for log(z) to avoid discontinuities.

Parameters:
  • lmap (LaurentMapResult) – The fitted Laurent map

  • curve_scale (float) – The diameter/scale of the curve

  • samples (int) – Number of samples on the unit circle

Returns:

The log-transformed bijection data

Return type:

LogBijectionData

Analyzes the logarithmic structure of a bijection between curves.

Complexity Estimation

class analytic_continuation.ComplexityEstimates[source]

Bases: object

Computational complexity estimates derived from intrinsic curve analysis.

These predict the relative difficulty of: - Inverting z(ζ) = z_query (finding ζ given z) - Evaluating the continuation at many points - Achieving a target accuracy

Parameters:
  • total_curvature (float)

  • curvature_variation (float)

  • max_curvature (float)

  • mean_curvature (float)

  • curvature_std (float)

  • winding_number (float)

  • total_arc_length (float)

  • log_deriv_variation (float)

  • arg_deriv_variation (float)

  • min_jacobian (float)

  • max_jacobian (float)

  • jacobian_ratio (float)

  • inversion_difficulty (float)

  • sampling_density_factor (float)

  • newton_convergence_factor (float)

total_curvature: float
curvature_variation: float
max_curvature: float
mean_curvature: float
curvature_std: float
winding_number: float
total_arc_length: float
log_deriv_variation: float
arg_deriv_variation: float
min_jacobian: float
max_jacobian: float
jacobian_ratio: float
inversion_difficulty: float
sampling_density_factor: float
newton_convergence_factor: float
to_dict()[source]
Return type:

dict

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

ComplexityEstimates

summary()[source]

Human-readable summary of complexity estimates.

Return type:

str

__init__(total_curvature, curvature_variation, max_curvature, mean_curvature, curvature_std, winding_number, total_arc_length, log_deriv_variation, arg_deriv_variation, min_jacobian, max_jacobian, jacobian_ratio, inversion_difficulty, sampling_density_factor, newton_convergence_factor)
Parameters:
  • total_curvature (float)

  • curvature_variation (float)

  • max_curvature (float)

  • mean_curvature (float)

  • curvature_std (float)

  • winding_number (float)

  • total_arc_length (float)

  • log_deriv_variation (float)

  • arg_deriv_variation (float)

  • min_jacobian (float)

  • max_jacobian (float)

  • jacobian_ratio (float)

  • inversion_difficulty (float)

  • sampling_density_factor (float)

  • newton_convergence_factor (float)

Return type:

None

analytic_continuation.estimate_complexity(log_data, cesaro, whewell)[source]

Compute complexity estimates from intrinsic curve representations.

The estimates predict computational costs for: - Newton iteration for inversion - Sampling density requirements - Overall pipeline complexity

Parameters:
Returns:

The complexity analysis results

Return type:

ComplexityEstimates

Estimates the complexity of a curve for determining fitting parameters:

from analytic_continuation import estimate_complexity

estimates = estimate_complexity(curve_points)

print(f"Suggested N_min: {estimates.suggested_N_min}")
print(f"Suggested N_max: {estimates.suggested_N_max}")
print(f"Winding number: {estimates.winding_number}")

Inversion Configuration

analytic_continuation.suggest_inversion_config(complexity, base_theta_grid=256, base_max_iters=40)[source]

Suggest inversion configuration based on complexity analysis.

Parameters:
  • complexity (ComplexityEstimates) – The complexity analysis

  • base_theta_grid (int) – Base number of theta samples

  • base_max_iters (int) – Base max Newton iterations

Returns:

Suggested InvertConfig parameters

Return type:

dict

Suggests configuration parameters based on curve analysis:

from analytic_continuation import suggest_inversion_config

config = suggest_inversion_config(curve_points)

print(f"Suggested tolerance: {config.tol}")
print(f"Suggested max iterations: {config.max_iter}")

Complete Analysis

class analytic_continuation.IntrinsicCurveAnalysis[source]

Bases: object

Complete intrinsic curve analysis of a bijection.

Bundles together: - Log bijection data - Cesàro representation - Whewell representation - Complexity estimates

Parameters:
log_data: LogBijectionData
cesaro: CesaroRepresentation
whewell: WhewellRepresentation
complexity: ComplexityEstimates
to_dict()[source]
Return type:

dict

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

IntrinsicCurveAnalysis

summary()[source]

Human-readable summary.

Return type:

str

__init__(log_data, cesaro, whewell, complexity)
Parameters:
Return type:

None

analytic_continuation.analyze_bijection(lmap, curve_scale, samples=4096)[source]

Perform complete intrinsic curve analysis of a Laurent bijection.

This is the main entry point for complexity estimation.

Parameters:
  • lmap (LaurentMapResult) – The fitted Laurent map z(ζ)

  • curve_scale (float) – The diameter/scale of the curve

  • samples (int) – Number of samples for analysis

Returns:

Complete analysis including Cesàro, Whewell, and complexity estimates

Return type:

IntrinsicCurveAnalysis

Example

>>> from analytic_continuation.laurent import fit_laurent_map
>>> from analytic_continuation.intrinsic_curve import analyze_bijection
>>>
>>> # After fitting a Laurent map
>>> result = fit_laurent_map(spline_export)
>>> if result.ok:
...     analysis = analyze_bijection(result.laurent_map, result.curve_scale)
...     print(analysis.summary())
...
...     # Use complexity estimates to tune parameters
...     if analysis.complexity.inversion_difficulty > 2.0:
...         # Increase Newton iterations
...         pass

Performs complete intrinsic curve analysis:

from analytic_continuation import analyze_bijection
import numpy as np

# Source and target curves
source = np.exp(1j * np.linspace(0, 2 * np.pi, 1000))
target = 2 * np.exp(1j * np.linspace(0, 2 * np.pi, 1000))

analysis = analyze_bijection(source, target)

print(f"Source total arc length: {analysis.source_cesaro.total_arc_length}")
print(f"Target total arc length: {analysis.target_cesaro.total_arc_length}")

Contour Pre-check (Stage 1)

class analytic_continuation.ContourPreCheckResult[source]

Bases: object

Result of quick pre-check on a raw user-drawn contour.

This is a fast “fail early” gate before expensive Laurent fitting.

Parameters:
ok: bool
proceed: bool
is_closed: bool
is_simple: bool
has_sufficient_points: bool
has_reasonable_aspect: bool
has_reasonable_curvature: bool
num_points: int
perimeter: float
bounding_box: Tuple[float, float, float, float]
aspect_ratio: float
estimated_diameter: float
min_segment_length: float
max_segment_length: float
max_turning_angle: float
warnings: List[str]
errors: List[str]
estimated_difficulty: str
estimated_fit_time_seconds: float | None = None
to_dict()[source]
Return type:

dict

__init__(ok, proceed, is_closed, is_simple, has_sufficient_points, has_reasonable_aspect, has_reasonable_curvature, num_points, perimeter, bounding_box, aspect_ratio, estimated_diameter, min_segment_length, max_segment_length, max_turning_angle, warnings, errors, estimated_difficulty, estimated_fit_time_seconds=None)
Parameters:
Return type:

None

analytic_continuation.precheck_contour(points, closed=True, min_points=8, max_aspect_ratio=20.0, max_turning_angle_deg=170.0, min_segment_ratio=0.001)[source]

Quick pre-check on a raw user-drawn contour before Laurent fitting.

This is a fast “fail early” gate at Stage 1 to catch obviously bad contours before wasting time on expensive computations.

Parameters:
  • points (List[Tuple[float, float]]) – The contour points (x, y) from user input or adaptive polyline

  • closed (bool) – Whether the contour should be treated as closed

  • min_points (int) – Minimum number of points required

  • max_aspect_ratio (float) – Maximum allowed aspect ratio (rejects very thin curves)

  • max_turning_angle_deg (float) – Maximum turning angle in degrees (rejects near-cusps)

  • min_segment_ratio (float) – Minimum segment length as fraction of perimeter

Returns:

Pre-check results with pass/fail and diagnostics

Return type:

ContourPreCheckResult

Quick validation before proceeding with full analysis:

from analytic_continuation import precheck_contour

result = precheck_contour(curve_points)

if result.ok:
    print("Contour is suitable for analysis")
else:
    print(f"Pre-check failed: {result.failure_reason}")
    print(f"Winding number: {result.winding_number}")
analytic_continuation.precheck_contour_from_spline_export(control_points, adaptive_polyline=None, closed=True)[source]

Pre-check a contour from SplineExport data.

Uses adaptive polyline if available (more accurate), otherwise control points.

Parameters:
  • control_points (List[Tuple[float, float]]) – Control points from the spline

  • adaptive_polyline (List[Tuple[float, float]], optional) – Adaptive polyline (if available, more accurate)

  • closed (bool) – Whether the contour is closed

Return type:

ContourPreCheckResult

Convenience function for SplineExport data:

from analytic_continuation import precheck_contour_from_spline_export, SplineExport

export = SplineExport.from_json(json_data)
result = precheck_contour_from_spline_export(export)

Workflow Integration

The intrinsic curve analysis is typically used in the early stages of the pipeline:

from analytic_continuation import (
    precheck_contour,
    estimate_complexity,
    suggest_inversion_config,
    LaurentFitConfig,
    fit_laurent_map,
)

# Stage 1: Quick pre-check
precheck = precheck_contour(curve_points)
if not precheck.ok:
    raise ValueError(f"Contour failed pre-check: {precheck.failure_reason}")

# Stage 2: Analyze complexity
complexity = estimate_complexity(curve_points)

# Configure fitting based on analysis
fit_config = LaurentFitConfig(
    N_min=complexity.suggested_N_min,
    N_max=complexity.suggested_N_max,
)

# Stage 3: Fit Laurent map
result = fit_laurent_map(export, fit_config)