# Zernike Trajectory Method for Elevation-Dependent Optimization **Document Version**: 1.0 **Created**: 2026-01-29 **Author**: Mario (Clawdbot) + Antoine Letarte **Status**: Validated ✅ --- ## Executive Summary The Zernike Trajectory Method provides **mode-specific optimization objectives** for telescope mirrors operating across multiple elevation angles. Instead of optimizing a weighted sum of discrete WFE values, this method: 1. **Tracks how each Zernike mode evolves** with elevation angle 2. **Fits a physics-based linear model** to the trajectory 3. **Provides integrated RMS metrics** for each aberration type 4. **Reveals which modes respond to axial vs lateral gravity loads** **Key result**: For a well-designed support system, the linear model achieves R² ≈ 1.0, meaning deformation is entirely predictable from the gravity load decomposition. --- ## 1. Physics Background ### 1.1 Gravity Load Decomposition At elevation angle θ from horizontal: ``` Axial load (along optical axis): F_axial ∝ sin(θ) Lateral load (perpendicular): F_lateral ∝ cos(θ) ``` For a linear elastic structure, surface deformation is linear in these load components. ### 1.2 Zernike Coefficient Evolution Each Zernike coefficient c_j follows: ``` c_j(θ) = a_j · (sin(θ) - sin(θ_ref)) + b_j · (cos(θ) - cos(θ_ref)) ``` Where: - `θ_ref` = reference angle (typically 20° for polishing/measurement) - `a_j` = sensitivity of mode j to axial load change - `b_j` = sensitivity of mode j to lateral load change ### 1.3 The Sensitivity Matrix Define trajectory parameters: ``` τ(θ) = [sin(θ) - sin(θ_ref), cos(θ) - cos(θ_ref)]ᵀ ``` The full coefficient vector evolves as: ``` c⃗(θ) = S · τ(θ) ``` Where **S** is the sensitivity matrix (N_modes × 2). --- ## 2. Implementation ### 2.1 Extractor Location ``` optimization_engine/extractors/extract_zernike_trajectory.py ``` ### 2.2 Basic Usage ```python from optimization_engine.extractors.extract_zernike_trajectory import extract_zernike_trajectory result = extract_zernike_trajectory( 'path/to/solution.op2', reference_angle=20.0, # Reference elevation (degrees) ) # Mode-specific integrated RMS (nm) print(result['coma_rms_nm']) print(result['astigmatism_rms_nm']) print(result['trefoil_rms_nm']) print(result['spherical_rms_nm']) # Total filtered RMS across all angles print(result['total_filtered_rms_nm']) # Linear model fit quality (should be > 0.95) print(result['linear_fit_r2']) # Sensitivity analysis print(result['sensitivity_matrix']) # {'coma': {'axial': 0.63, 'lateral': 36.04, 'total': 36.05}, ...} ``` ### 2.3 Auto-Detection Features The extractor automatically: - Reads subcase labels from OP2 metadata (e.g., "20", "40", "60") - Excludes manufacturing angle (90°) by default - Sorts angles for proper trajectory fitting ### 2.4 Output Dictionary ```python { # Mode-specific integrated RMS (nm) 'coma_rms_nm': float, 'astigmatism_rms_nm': float, 'trefoil_rms_nm': float, 'spherical_rms_nm': float, 'secondary_astig_rms_nm': float, 'secondary_coma_rms_nm': float, 'quadrafoil_rms_nm': float, # Total filtered RMS (all modes, integrated) 'total_filtered_rms_nm': float, # Model quality 'linear_fit_r2': float, # Should be > 0.95 # Sensitivity analysis 'sensitivity_matrix': { 'coma': {'axial': float, 'lateral': float, 'total': float}, 'astigmatism': {...}, ... }, # Mode ranking (by sensitivity) 'mode_ranking': ['spherical', 'coma', 'astigmatism', ...], 'dominant_mode': str, # Metadata 'angles_deg': [20.0, 30.0, 40.0, 50.0, 60.0], 'reference_angle': 20.0, } ``` --- ## 3. Optimization Integration ### 3.1 AtomizerSpec Configuration ```yaml extractors: - id: ext_trajectory name: "Zernike Trajectory" type: zernike_trajectory config: reference_angle: 20.0 # Subcases auto-detected from OP2 labels objectives: # Option A: Single combined objective - id: obj_total name: "Total Integrated RMS" source: extractor_id: ext_trajectory output_name: total_filtered_rms_nm direction: minimize weight: 1.0 # Option B: Mode-specific objectives (multi-objective) - id: obj_coma name: "Coma RMS" source: extractor_id: ext_trajectory output_name: coma_rms_nm direction: minimize weight: 1.0 - id: obj_astig name: "Astigmatism RMS" source: extractor_id: ext_trajectory output_name: astigmatism_rms_nm direction: minimize weight: 0.8 ``` ### 3.2 Recommended Optimization Strategy **For SAT (Surrogate-Assisted Tuning):** - Use `total_filtered_rms_nm` as primary objective - Add mode-specific objectives as secondary for Pareto analysis - SAT handles the expensive FEA evaluations efficiently **For TPE (Tree-Parzen Estimator):** - Good for single-objective optimization - Use weighted combination if needed: ``` J = w_coma * coma_rms + w_astig * astig_rms + w_total * total_rms ``` --- ## 4. Validation Results (M1 Mirror, 2026-01-29) ### 4.1 Test Configuration - **Model**: M1 GigaBIT mirror (1.2m Zerodur) - **Angles**: 20°, 30°, 40°, 50°, 60° (5 subcases) - **Reference**: 20° (polishing/measurement) ### 4.2 Linear Fit Quality ``` R² = 1.0000 ← PERFECT FIT ``` The physics model is **exactly correct** for this mirror design. ### 4.3 Mode-Specific Results | Mode | Integrated RMS (nm) | Axial Sensitivity | Lateral Sensitivity | |------|---------------------|-------------------|---------------------| | Secondary Astig | 12.95 | 1.93 | 48.91 | | Spherical | 10.57 | 19.84 | 68.08 | | Coma | 9.14 | 0.63 | **36.04** | | Trefoil | 6.83 | — | — | | Astigmatism | 6.80 | — | — | **Key insight**: Coma is almost entirely driven by lateral loads (axial/lateral ratio = 0.02). Optimizing lateral support locations will have the biggest impact on coma. --- ## 5. Troubleshooting ### 5.1 Low R² Value If R² < 0.9: - Check for contact nonlinearity (supports lifting off) - Check for material nonlinearity - Verify subcases have consistent boundary conditions ### 5.2 Missing Subcases Ensure your FEA includes all required angles. Minimum recommended: - 20° (reference) - 40° (primary operational) - 60° (secondary operational) Adding 30° and 50° improves trajectory fit quality. ### 5.3 Subcase Label Detection If auto-detection fails, specify manually: ```python result = extract_zernike_trajectory( 'solution.op2', subcases=[2, 5, 3, 6, 4], # Subcase IDs angles=[20.0, 30.0, 40.0, 50.0, 60.0], # Corresponding angles ) ``` --- ## 6. References - [ZERNIKE_OPD_METHOD.md](ZERNIKE_OPD_METHOD.md) — OPD correction for lateral displacement - [ZERNIKE_FUNDAMENTALS.md](../ZERNIKE_FUNDAMENTALS.md) — Zernike polynomial basics - AtomizerSpec v2.0 — Objective/extractor configuration --- *Document maintained by Atomizer Framework. Last updated: 2026-01-29*