Files
Atomizer/docs/physics/ZERNIKE_TRAJECTORY_METHOD.md

6.8 KiB
Raw Blame 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

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

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

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

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:

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


Document maintained by Atomizer Framework. Last updated: 2026-01-29