Source code for analytic_continuation_server.codegen

"""
Code generation utilities.

Generates standalone Python code for evaluating analytic continuations.
"""

from .models import ContinuationDefinition


[docs] def generate_python_continuation_code(cont: ContinuationDefinition) -> str: """Generate standalone Python code to evaluate the continuation.""" coeffs_neg = cont.laurent_map.coeffs_neg coeffs_pos = cont.laurent_map.coeffs_pos code = f'''""" Analytic Continuation: F(z) = A(f(B(z))) Generated from session: {cont.session_id or "unknown"} Created: {cont.created_at or "unknown"} Expression: {cont.expression or "rational function from zeros/poles"} This evaluates the Schwarz reflection composition through a Jordan curve. """ import numpy as np from typing import Optional # Laurent map coefficients for z(zeta) N = {cont.laurent_map.N} CURVE_SCALE = {cont.laurent_map.curve_scale} # a_0, a_1, ..., a_N (positive powers) COEFFS_POS = np.array({coeffs_pos}, dtype=complex) # Convert to complex COEFFS_POS = np.array([c[0] + 1j*c[1] for c in COEFFS_POS]) # b_1, ..., b_N (negative powers) COEFFS_NEG = np.array({coeffs_neg}, dtype=complex) COEFFS_NEG = np.array([c[0] + 1j*c[1] for c in COEFFS_NEG]) # Zeros and poles of the meromorphic function ZEROS = {[(z["re"], z["im"], z.get("multiplicity", 1)) for z in cont.zeros]} POLES = {[(p["re"], p["im"], p.get("multiplicity", 1)) for p in cont.poles]} ''' if cont.expression: code += f''' # Symbolic expression EXPRESSION = "{cont.expression}" ''' code += ''' def eval_laurent_map(zeta: complex) -> complex: """Evaluate z(zeta) = a_0 + sum a_k*zeta^k + sum b_k*zeta^-k""" z = COEFFS_POS[0] # a_0 # Positive powers p = zeta for k in range(N): z += COEFFS_POS[k+1] * p p *= zeta # Negative powers q = 1.0 / zeta p = q for k in range(N): z += COEFFS_NEG[k] * p p *= q return z def eval_laurent_deriv(zeta: complex) -> complex: """Evaluate z'(zeta)""" dz = 0.0 # Positive powers: k * a_k * zeta^(k-1) p = 1.0 for k in range(N): dz += (k + 1) * COEFFS_POS[k+1] * p p *= zeta # Negative powers: -k * b_k * zeta^(-k-1) q = 1.0 / zeta p = q * q for k in range(N): dz -= (k + 1) * COEFFS_NEG[k] * p p *= q return dz def invert_laurent_map(z_query: complex, max_iters: int = 40, tol: float = 1e-10) -> Optional[complex]: """ Invert z(zeta) = z_query using Newton iteration. Returns zeta such that z(zeta) approx z_query, or None if inversion fails. """ # Multi-start: try several initial guesses n_theta = 64 best_zeta = None best_residual = float('inf') for r in [0.99, 1.0, 1.01]: for theta in np.linspace(0, 2*np.pi, n_theta, endpoint=False): zeta = r * np.exp(1j * theta) for _ in range(max_iters): z = eval_laurent_map(zeta) residual = z - z_query if abs(residual) < tol * CURVE_SCALE: if abs(residual) < best_residual: best_residual = abs(residual) best_zeta = zeta break dz = eval_laurent_deriv(zeta) if abs(dz) < 1e-14: break # Damped Newton step step = residual / dz alpha = 1.0 for _ in range(8): zeta_new = zeta - alpha * step if abs(eval_laurent_map(zeta_new) - z_query) < abs(residual): break alpha *= 0.5 zeta = zeta_new return best_zeta def eval_meromorphic(z: complex) -> complex: """Evaluate the meromorphic function f(z) from zeros and poles.""" result = 1.0 for re, im, mult in ZEROS: result *= (z - complex(re, im)) ** mult for re, im, mult in POLES: result /= (z - complex(re, im)) ** mult return result def eval_continuation(z_query: complex) -> Optional[complex]: """ Evaluate the analytic continuation F(z) at a point. F(z) = A(f(B(z))) where A, B are Schwarz reflections. Due to the shared parameterization, this simplifies to f(z(zeta)) where zeta is found by inverting z(zeta) = z_query. Parameters ---------- z_query : complex The point at which to evaluate the continuation Returns ------- complex or None The value F(z_query), or None if inversion failed """ # Step 1: Find zeta such that z(zeta) = z_query zeta = invert_laurent_map(z_query) if zeta is None: return None # Step 2: Evaluate f(z(zeta)) z_on_curve = eval_laurent_map(zeta) return eval_meromorphic(z_on_curve) if __name__ == "__main__": # Example usage import sys if len(sys.argv) > 1: # Parse complex number from command line z_str = sys.argv[1] try: z = complex(z_str.replace('i', 'j')) except ValueError: print(f"Could not parse '{z_str}' as complex number") sys.exit(1) else: z = 0.5 + 0.5j result = eval_continuation(z) if result is not None: print(f"F({z}) = {result}") else: print(f"Failed to evaluate F({z}) - inversion did not converge") ''' return code