Files
Atomizer/docs/physics/ZERNIKE_TRAJECTORY_METHOD.md

270 lines
6.8 KiB
Markdown
Raw Permalink Normal View History

# 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*