diff --git a/docs/examples/trajectory_optimization_config.yaml b/docs/examples/trajectory_optimization_config.yaml new file mode 100644 index 00000000..36522c3b --- /dev/null +++ b/docs/examples/trajectory_optimization_config.yaml @@ -0,0 +1,164 @@ +# Example: Trajectory-Based Optimization for M1 Mirror +# ===================================================== +# +# This config uses the Zernike Trajectory Method for mode-specific optimization. +# Instead of weighted RMS at discrete angles, we optimize integrated metrics +# across the full 20°-60° operating range. +# +# Recommended optimizer: SAT (Surrogate-Assisted Tuning) +# - Handles expensive FEA evaluations efficiently +# - Good for 10-20 design variables +# - Builds surrogate model for intelligent sampling + +study: + name: "M1_Trajectory_SAT3" + description: "Mode-specific optimization using Zernike trajectory metrics" + +optimizer: + type: sat # Surrogate-Assisted Tuning + config: + n_initial: 20 # Initial random samples + n_iterations: 100 # Total iterations + surrogate: gp # Gaussian Process surrogate + acquisition: ei # Expected Improvement + +# Alternative: TPE (faster, good for single objective) +# optimizer: +# type: tpe +# config: +# n_trials: 150 +# n_startup_trials: 20 + +# ============================================================================= +# DESIGN VARIABLES +# ============================================================================= +# These are your wiffle tree and geometry parameters. +# Adjust ranges based on your current best design. + +design_variables: + # Wiffle tree radial positions (mm) + - id: dv_wiffle_r1 + name: "Wiffle R1 (inner ring)" + type: continuous + bounds: [180, 220] + + - id: dv_wiffle_r2 + name: "Wiffle R2 (middle ring)" + type: continuous + bounds: [320, 380] + + - id: dv_wiffle_r3 + name: "Wiffle R3 (outer ring)" + type: continuous + bounds: [480, 540] + + # Wiffle tree angular offsets (degrees) + - id: dv_wiffle_theta + name: "Wiffle angular offset" + type: continuous + bounds: [-5, 5] + + # Rib geometry + - id: dv_rib_thickness + name: "Rib thickness" + type: continuous + bounds: [8, 15] + +# ============================================================================= +# EXTRACTORS +# ============================================================================= + +extractors: + # Primary: Trajectory-based analysis (5 angles) + - id: ext_trajectory + name: "Zernike Trajectory" + type: zernike_trajectory + config: + reference_angle: 20.0 + # Angles auto-detected from OP2 subcase labels + + # Fallback: Standard Zernike (for comparison) + - id: ext_zernike_40 + name: "Zernike 40° vs 20°" + type: zernike_opd + config: + subcase: "40" + reference_subcase: "20" + +# ============================================================================= +# OBJECTIVES +# ============================================================================= +# +# RECOMMENDED: Start with total_filtered_rms_nm as single objective. +# Once you find a good region, switch to multi-objective with mode-specific. + +objectives: + # PRIMARY: Total integrated RMS (single number for optimizer) + - id: obj_total + name: "Total Integrated RMS" + source: + extractor_id: ext_trajectory + output_name: total_filtered_rms_nm + direction: minimize + weight: 1.0 + + # SECONDARY: Coma (almost entirely lateral-driven) + # Useful if you want to specifically target coma reduction + - id: obj_coma + name: "Coma Integrated RMS" + source: + extractor_id: ext_trajectory + output_name: coma_rms_nm + direction: minimize + weight: 0.0 # Set to 0 for logging only, increase for multi-objective + + # SECONDARY: Astigmatism + - id: obj_astig + name: "Astigmatism Integrated RMS" + source: + extractor_id: ext_trajectory + output_name: astigmatism_rms_nm + direction: minimize + weight: 0.0 + +# ============================================================================= +# CONSTRAINTS +# ============================================================================= + +constraints: + # Model quality check: R² should stay high + - id: con_r2 + name: "Linear fit quality" + source: + extractor_id: ext_trajectory + output_name: linear_fit_r2 + type: hard + operator: ">=" + threshold: 0.95 + + # Performance constraint from requirements + - id: con_wfe_40 + name: "WFE at 40° < 14nm" + source: + extractor_id: ext_zernike_40 + output_name: filtered_rms_nm + type: hard + operator: "<=" + threshold: 14.0 + +# ============================================================================= +# EXECUTION +# ============================================================================= + +execution: + # NX journal for FEA + journal: "nx_journals/run_fea_multi_subcase.py" + + # Subcases to solve (must include all trajectory angles) + subcases: [90, 20, 30, 40, 50, 60] + + # Timeout per iteration + timeout_seconds: 600 + + # Parallelism (if NX licenses available) + parallel: 1 diff --git a/docs/physics/ZERNIKE_TRAJECTORY_METHOD.md b/docs/physics/ZERNIKE_TRAJECTORY_METHOD.md new file mode 100644 index 00000000..40359045 --- /dev/null +++ b/docs/physics/ZERNIKE_TRAJECTORY_METHOD.md @@ -0,0 +1,269 @@ +# 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*