Neural Acceleration (MLP Surrogate): - Add run_nn_optimization.py with hybrid FEA/NN workflow - MLP architecture: 4-layer (64->128->128->64) with BatchNorm/Dropout - Three workflow modes: - --all: Sequential export->train->optimize->validate - --hybrid-loop: Iterative Train->NN->Validate->Retrain cycle - --turbo: Aggressive single-best validation (RECOMMENDED) - Turbo mode: 5000 NN trials + 50 FEA validations in ~12 minutes - Separate nn_study.db to avoid overloading dashboard Performance Results (bracket_pareto_3obj study): - NN prediction errors: mass 1-5%, stress 1-4%, stiffness 5-15% - Found minimum mass designs at boundary (angle~30deg, thick~30mm) - 100x speedup vs pure FEA exploration Protocol Operating System: - Add .claude/skills/ with Bootstrap, Cheatsheet, Context Loader - Add docs/protocols/ with operations (OP_01-06) and system (SYS_10-14) - Update SYS_14_NEURAL_ACCELERATION.md with MLP Turbo Mode docs NX Automation: - Add optimization_engine/hooks/ for NX CAD/CAE automation - Add study_wizard.py for guided study creation - Fix FEM mesh update: load idealized part before UpdateFemodel() New Study: - bracket_pareto_3obj: 3-objective Pareto (mass, stress, stiffness) - 167 FEA trials + 5000 NN trials completed - Demonstrates full hybrid workflow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
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).
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:
{
'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).
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:
{
'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).
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) - standardrms_filter_j1to3: Removes only J1-J3 (keeps defocus) - for focus-sensitive applications
Common Zernike Optimization Patterns
Pattern 1: Minimize Relative WFE Across Elevations
# 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
# 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
# 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
{
"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
- Parse OP2 Once: Use
ZernikeObjectiveBuilderto parse the OP2 file only once per trial - Subcase Labels: Match exact subcase labels from NX simulation
- Node Selection: Zernike extraction uses surface nodes only (auto-detected from BDF)
- 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:
"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:
if is_new_study:
sampler = TPESampler(seed=42)
else:
sampler = TPESampler() # No seed for resume
2. OP2 Data Validation
Always validate before processing:
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:
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
- System Protocol: SYS_12_EXTRACTOR_LIBRARY
- Core Skill: study-creation-core