# Zernike Optimization Module **Last Updated**: December 5, 2025 **Version**: 1.0 **Type**: Optional Module This module provides specialized guidance for telescope mirror and optical surface optimization using Zernike polynomial decomposition. --- ## When to Load - User mentions "telescope", "mirror", "optical", "wavefront" - Optimization involves surface deformation analysis - Need to extract Zernike coefficients from FEA results - Working with multi-subcase elevation angle comparisons --- ## Zernike Extractors (E8-E10) | ID | Extractor | Function | Input | Output | Use Case | |----|-----------|----------|-------|--------|----------| | E8 | **Zernike WFE** | `extract_zernike_from_op2()` | `.op2` + `.bdf` | nm | Single subcase wavefront error | | E9 | **Zernike Relative** | `extract_zernike_relative_rms()` | `.op2` + `.bdf` | nm | Compare target vs reference subcase | | E10 | **Zernike Helpers** | `ZernikeObjectiveBuilder` | `.op2` | nm | Multi-subcase optimization builder | --- ## E8: Single Subcase Zernike Extraction Extract Zernike coefficients and RMS metrics for a single subcase (e.g., one elevation angle). ```python from optimization_engine.extractors.extract_zernike import extract_zernike_from_op2 # Extract Zernike coefficients and RMS metrics for a single subcase result = extract_zernike_from_op2( op2_file, bdf_file=None, # Auto-detect from op2 location subcase="20", # Subcase label (e.g., "20" = 20 deg elevation) displacement_unit="mm" ) global_rms = result['global_rms_nm'] # Total surface RMS in nm filtered_rms = result['filtered_rms_nm'] # RMS with low orders removed coefficients = result['coefficients'] # List of 50 Zernike coefficients ``` **Return Dictionary**: ```python { 'global_rms_nm': 45.2, # Total surface RMS (nm) 'filtered_rms_nm': 12.8, # RMS with J1-J4 (piston, tip, tilt, defocus) removed 'coefficients': [0.0, 12.3, ...], # 50 Zernike coefficients (Noll indexing) 'n_nodes': 5432, # Number of surface nodes 'rms_per_mode': {...} # RMS contribution per Zernike mode } ``` **When to Use**: - Single elevation angle analysis - Polishing orientation (zenith) wavefront error - Absolute surface quality metrics --- ## E9: Relative RMS Between Subcases Compare wavefront error between two subcases (e.g., 40° vs 20° reference). ```python from optimization_engine.extractors.extract_zernike import extract_zernike_relative_rms # Compare wavefront error between subcases (e.g., 40 deg vs 20 deg reference) result = extract_zernike_relative_rms( op2_file, bdf_file=None, target_subcase="40", # Target orientation reference_subcase="20", # Reference (usually polishing orientation) displacement_unit="mm" ) relative_rms = result['relative_filtered_rms_nm'] # Differential WFE in nm delta_coeffs = result['delta_coefficients'] # Coefficient differences ``` **Return Dictionary**: ```python { 'relative_filtered_rms_nm': 8.7, # Differential WFE (target - reference) 'delta_coefficients': [...], # Coefficient differences 'target_rms_nm': 52.3, # Target subcase absolute RMS 'reference_rms_nm': 45.2, # Reference subcase absolute RMS 'improvement_percent': -15.7 # Negative = worse than reference } ``` **When to Use**: - Comparing performance across elevation angles - Minimizing deformation relative to polishing orientation - Multi-angle telescope mirror optimization --- ## E10: Multi-Subcase Objective Builder Build objectives for multiple subcases in a single extractor (most efficient for complex optimization). ```python from optimization_engine.extractors.zernike_helpers import ZernikeObjectiveBuilder # Build objectives for multiple subcases in one extractor builder = ZernikeObjectiveBuilder( op2_finder=lambda: model_dir / "ASSY_M1-solution_1.op2" ) # Add relative objectives (target vs reference) builder.add_relative_objective( "40", "20", # 40° vs 20° reference metric="relative_filtered_rms_nm", weight=5.0 ) builder.add_relative_objective( "60", "20", # 60° vs 20° reference metric="relative_filtered_rms_nm", weight=5.0 ) # Add absolute objective for polishing orientation builder.add_subcase_objective( "90", # Zenith (polishing orientation) metric="rms_filter_j1to3", # Only remove piston, tip, tilt weight=1.0 ) # Evaluate all at once (efficient - parses OP2 only once) results = builder.evaluate_all() # Returns: {'rel_40_vs_20': 4.2, 'rel_60_vs_20': 8.7, 'rms_90': 15.3} ``` **When to Use**: - Multi-objective telescope optimization - Multiple elevation angles to optimize - Weighted combination of absolute and relative WFE --- ## Zernike Modes Reference | Noll Index | Name | Physical Meaning | Correctability | |------------|------|------------------|----------------| | J1 | Piston | Constant offset | Easily corrected | | J2 | Tip | X-tilt | Easily corrected | | J3 | Tilt | Y-tilt | Easily corrected | | J4 | Defocus | Power error | Easily corrected | | J5 | Astigmatism (0°) | Cylindrical error | Correctable | | J6 | Astigmatism (45°) | Cylindrical error | Correctable | | J7 | Coma (x) | Off-axis aberration | Harder to correct | | J8 | Coma (y) | Off-axis aberration | Harder to correct | | J9-J10 | Trefoil | Triangular error | Hard to correct | | J11+ | Higher order | Complex aberrations | Very hard to correct | **Filtering Convention**: - `filtered_rms`: Removes J1-J4 (piston, tip, tilt, defocus) - standard - `rms_filter_j1to3`: Removes only J1-J3 (keeps defocus) - for focus-sensitive applications --- ## Common Zernike Optimization Patterns ### Pattern 1: Minimize Relative WFE Across Elevations ```python # Objective: Minimize max relative WFE across all elevation angles objectives = [ {"name": "rel_40_vs_20", "goal": "minimize"}, {"name": "rel_60_vs_20", "goal": "minimize"}, ] # Use weighted sum or multi-objective def objective(trial): results = builder.evaluate_all() return (results['rel_40_vs_20'], results['rel_60_vs_20']) ``` ### Pattern 2: Single Elevation + Mass ```python # Objective: Minimize WFE at 45° while minimizing mass objectives = [ {"name": "wfe_45", "goal": "minimize"}, # Wavefront error {"name": "mass", "goal": "minimize"}, # Mirror mass ] ``` ### Pattern 3: Weighted Multi-Angle ```python # Weighted combination of multiple angles def combined_wfe(trial): results = builder.evaluate_all() weighted_wfe = ( 5.0 * results['rel_40_vs_20'] + 5.0 * results['rel_60_vs_20'] + 1.0 * results['rms_90'] ) return weighted_wfe ``` --- ## Telescope Mirror Study Configuration ```json { "study_name": "m1_mirror_optimization", "description": "Minimize wavefront error across elevation angles", "objectives": [ { "name": "wfe_40_vs_20", "goal": "minimize", "unit": "nm", "extraction": { "action": "extract_zernike_relative_rms", "params": { "target_subcase": "40", "reference_subcase": "20" } } } ], "simulation": { "analysis_types": ["static"], "subcases": ["20", "40", "60", "90"], "solution_name": null } } ``` --- ## Performance Considerations 1. **Parse OP2 Once**: Use `ZernikeObjectiveBuilder` to parse the OP2 file only once per trial 2. **Subcase Labels**: Match exact subcase labels from NX simulation 3. **Node Selection**: Zernike extraction uses surface nodes only (auto-detected from BDF) 4. **Memory**: Large meshes (>50k nodes) may require chunked processing --- ## Troubleshooting | Symptom | Cause | Solution | |---------|-------|----------| | "Subcase not found" | Wrong subcase label | Check NX .sim for exact labels | | High J1-J4 coefficients | Rigid body motion not constrained | Check boundary conditions | | NaN in coefficients | Insufficient nodes for polynomial order | Reduce max Zernike order | | Inconsistent RMS | Different node sets per subcase | Verify mesh consistency | | "Billion nm" RMS values | Node merge failed in AFEM | Check `MergeOccurrenceNodes = True` | | Corrupt OP2 data | All-zero displacements | Validate OP2 before processing | --- ## Assembly FEM (AFEM) Structure for Mirrors Telescope mirror assemblies in NX typically consist of: ``` ASSY_M1.prt # Master assembly part ASSY_M1_assyfem1.afm # Assembly FEM container ASSY_M1_assyfem1_sim1.sim # Simulation file (solve this) M1_Blank.prt # Mirror blank part M1_Blank_fem1.fem # Mirror blank mesh M1_Vertical_Support_Skeleton.prt # Support structure ``` **Key Point**: Expressions in master `.prt` propagate through assembly → AFEM updates automatically. --- ## Multi-Subcase Gravity Analysis For telescope mirrors, analyze multiple gravity orientations: | Subcase | Elevation Angle | Purpose | |---------|-----------------|---------| | 1 | 90° (zenith) | Polishing orientation - manufacturing reference | | 2 | 20° | Low elevation - reference for relative metrics | | 3 | 40° | Mid-low elevation | | 4 | 60° | Mid-high elevation | **CRITICAL**: NX subcase numbers don't always match angle labels! Use explicit mapping: ```json "subcase_labels": { "1": "90deg", "2": "20deg", "3": "40deg", "4": "60deg" } ``` --- ## Lessons Learned (M1 Mirror V1-V9) ### 1. TPE Sampler Seed Issue **Problem**: Resuming study with fixed seed causes duplicate parameters. **Solution**: ```python if is_new_study: sampler = TPESampler(seed=42) else: sampler = TPESampler() # No seed for resume ``` ### 2. OP2 Data Validation **Always validate before processing**: ```python unique_values = len(np.unique(disp_z)) if unique_values < 10: raise RuntimeError("CORRUPT OP2: insufficient unique values") if np.abs(disp_z).max() > 1e6: raise RuntimeError("CORRUPT OP2: unrealistic displacement") ``` ### 3. Reference Subcase Selection Use lowest operational elevation (typically 20°) as reference. Higher elevations show positive relative WFE as gravity effects increase. ### 4. Optical Convention For mirror surface to wavefront error: ```python WFE = 2 * surface_displacement # Reflection doubles path difference wfe_nm = 2.0 * displacement_mm * 1e6 # Convert mm to nm ``` --- ## Typical Mirror Design Variables | Parameter | Description | Typical Range | |-----------|-------------|---------------| | `whiffle_min` | Whiffle tree minimum dimension | 35-55 mm | | `whiffle_outer_to_vertical` | Whiffle arm angle | 68-80 deg | | `inner_circular_rib_dia` | Rib diameter | 480-620 mm | | `lateral_inner_angle` | Lateral support angle | 25-28.5 deg | | `blank_backface_angle` | Mirror blank geometry | 3.5-5.0 deg | --- ## Cross-References - **Extractor Catalog**: [extractors-catalog module](./extractors-catalog.md) - **System Protocol**: [SYS_12_EXTRACTOR_LIBRARY](../../docs/protocols/system/SYS_12_EXTRACTOR_LIBRARY.md) - **Core Skill**: [study-creation-core](../core/study-creation-core.md)