Major improvements to Zernike WFE visualization: - Add ZernikeDashboardInsight: Unified dashboard with all orientations (40°, 60°, 90°) on one page with light theme and executive summary - Add OPD method toggle: Switch between Standard (Z-only) and OPD (X,Y,Z) methods in ZernikeWFEInsight with interactive buttons - Add lateral displacement maps: Visualize X,Y displacement for each orientation - Add displacement component views: Toggle between WFE, ΔX, ΔY, ΔZ in relative views - Add metrics comparison table showing both methods side-by-side New extractors: - extract_zernike_figure.py: ZernikeOPDExtractor using BDF geometry interpolation - extract_zernike_opd.py: Parabola-based OPD with focal length Key finding: OPD method gives 8-11% higher WFE values than Standard method (more conservative/accurate for surfaces with lateral displacement under gravity) Documentation updates: - SYS_12: Added E22 ZernikeOPD as recommended method - SYS_16: Added ZernikeDashboard, updated ZernikeWFE with OPD features - Cheatsheet: Added Zernike method comparison table 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
SYS_16: Study Insights
Version: 1.0.0 Status: Active Purpose: Physics-focused visualizations for FEA optimization results
Overview
Study Insights provide physics understanding of optimization results through interactive 3D visualizations. Unlike the Analysis page (which shows optimizer metrics like convergence and Pareto fronts), Insights answer the question: "What does this design actually look like?"
Analysis vs Insights
| Aspect | Analysis | Insights |
|---|---|---|
| Focus | Optimization performance | Physics understanding |
| Questions | "Is the optimizer converging?" | "What does the best design look like?" |
| Data Source | study.db (trials, objectives) |
Simulation outputs (OP2, mesh, fields) |
| Typical Plots | Convergence, Pareto, parameters | 3D surfaces, stress contours, mode shapes |
| When Used | During/after optimization | After specific trial of interest |
Available Insight Types
| Type ID | Name | Applicable To | Data Required |
|---|---|---|---|
zernike_dashboard |
Zernike Dashboard (RECOMMENDED) | Mirror, optics | OP2 with displacement subcases |
zernike_wfe |
Zernike WFE Analysis | Mirror, optics | OP2 with displacement subcases |
zernike_opd_comparison |
Zernike OPD Method Comparison | Mirror, optics, lateral | OP2 with displacement subcases |
msf_zernike |
MSF Zernike Analysis | Mirror, optics | OP2 with displacement subcases |
stress_field |
Stress Distribution | Structural, bracket, beam | OP2 with stress results |
modal |
Modal Analysis | Vibration, dynamic | OP2 with eigenvalue/eigenvector |
thermal |
Thermal Analysis | Thermo-structural | OP2 with temperature results |
design_space |
Design Space Explorer | All optimization studies | study.db with 5+ trials |
Zernike Method Comparison: Standard vs OPD
The Zernike insights now support two WFE computation methods:
| Method | Description | When to Use |
|---|---|---|
| Standard (Z-only) | Uses only Z-displacement at original (x,y) coordinates | Quick analysis, negligible lateral displacement |
| OPD (X,Y,Z) ← RECOMMENDED | Accounts for lateral (X,Y) displacement via interpolation | Any surface with gravity loads, most rigorous |
How OPD method works:
- Builds interpolator from undeformed BDF mesh geometry
- For each deformed node at
(x+dx, y+dy, z+dz), interpolatesZ_idealat new XY position - Computes
WFE = z_deformed - Z_ideal(x_def, y_def) - Fits Zernike polynomials to the surface error map
Typical difference: OPD method gives 8-11% higher WFE values than Standard (more conservative/accurate).
Architecture
Module Structure
optimization_engine/insights/
├── __init__.py # Registry and public API
├── base.py # StudyInsight base class, InsightConfig, InsightResult
├── zernike_wfe.py # Mirror wavefront error visualization (50 modes)
├── zernike_opd_comparison.py # OPD vs Standard method comparison (lateral disp. analysis)
├── msf_zernike.py # MSF band decomposition (100 modes, LSF/MSF/HSF)
├── stress_field.py # Stress contour visualization
├── modal_analysis.py # Mode shape visualization
├── thermal_field.py # Temperature distribution
└── design_space.py # Parameter-objective exploration
Class Hierarchy
StudyInsight (ABC)
├── ZernikeDashboardInsight # RECOMMENDED: Unified dashboard with all views
├── ZernikeWFEInsight # Standard 50-mode WFE analysis (with OPD toggle)
├── ZernikeOPDComparisonInsight # OPD method comparison (lateral displacement)
├── MSFZernikeInsight # 100-mode MSF band analysis
├── StressFieldInsight
├── ModalInsight
├── ThermalInsight
└── DesignSpaceInsight
Key Classes
StudyInsight (Base Class)
class StudyInsight(ABC):
insight_type: str # Unique identifier (e.g., 'zernike_wfe')
name: str # Human-readable name
description: str # What this insight shows
applicable_to: List[str] # Study types this applies to
def can_generate(self) -> bool:
"""Check if required data exists."""
def generate(self, config: InsightConfig) -> InsightResult:
"""Generate visualization."""
def generate_html(self, trial_id=None, **kwargs) -> Path:
"""Generate standalone HTML file."""
def get_plotly_data(self, trial_id=None, **kwargs) -> dict:
"""Get Plotly figure for dashboard embedding."""
InsightConfig
@dataclass
class InsightConfig:
trial_id: Optional[int] = None # Which trial to visualize
colorscale: str = 'Turbo' # Plotly colorscale
amplification: float = 1.0 # Deformation scale factor
lighting: bool = True # 3D lighting effects
output_dir: Optional[Path] = None # Where to save HTML
extra: Dict[str, Any] = {} # Type-specific config
InsightResult
@dataclass
class InsightResult:
success: bool
html_path: Optional[Path] = None # Generated HTML file
plotly_figure: Optional[dict] = None # Figure for dashboard
summary: Optional[dict] = None # Key metrics
error: Optional[str] = None # Error message if failed
Usage
Python API
from optimization_engine.insights import get_insight, list_available_insights
from pathlib import Path
study_path = Path("studies/my_mirror_study")
# List what's available
available = list_available_insights(study_path)
for info in available:
print(f"{info['type']}: {info['name']}")
# Generate specific insight
insight = get_insight('zernike_wfe', study_path)
if insight and insight.can_generate():
result = insight.generate()
print(f"Generated: {result.html_path}")
print(f"40-20 Filtered RMS: {result.summary['40_vs_20_filtered_rms']:.2f} nm")
CLI
# List all insight types
python -m optimization_engine.insights list
# Generate all available insights for a study
python -m optimization_engine.insights generate studies/my_study
# Generate specific insight
python -m optimization_engine.insights generate studies/my_study --type zernike_wfe
With Configuration
from optimization_engine.insights import get_insight, InsightConfig
insight = get_insight('stress_field', study_path)
config = InsightConfig(
colorscale='Hot',
extra={
'yield_stress': 250, # MPa
'stress_unit': 'MPa'
}
)
result = insight.generate(config)
Insight Type Details
0. Zernike Dashboard (zernike_dashboard) - RECOMMENDED
Purpose: Unified dashboard with all orientations (40°, 60°, 90°) and MSF band analysis on one page. Light theme, executive summary, and method comparison.
Generates: 1 comprehensive HTML file with:
- Executive summary with metric cards (40-20, 60-20, MFG workload)
- MSF band analysis (LSF/MSF/HSF decomposition)
- 3D surface plots for each orientation
- Zernike coefficient bar charts color-coded by band
Configuration:
config = InsightConfig(
extra={
'n_modes': 50,
'filter_low_orders': 4,
'theme': 'light', # Light theme for reports
}
)
Summary Output:
{
'40_vs_20_filtered_rms': 6.53, # nm (OPD method)
'60_vs_20_filtered_rms': 14.21, # nm (OPD method)
'90_optician_workload': 26.34, # nm (J1-J3 filtered)
'msf_rss_40': 2.1, # nm (MSF band contribution)
}
1. Zernike WFE Analysis (zernike_wfe)
Purpose: Visualize wavefront error for mirror optimization with Zernike polynomial decomposition. Now includes Standard/OPD method toggle and lateral displacement maps.
Generates: 6 HTML files
zernike_*_40_vs_20.html- 40° vs 20° relative WFE (with method toggle)zernike_*_40_lateral.html- Lateral displacement map for 40°zernike_*_60_vs_20.html- 60° vs 20° relative WFE (with method toggle)zernike_*_60_lateral.html- Lateral displacement map for 60°zernike_*_90_mfg.html- 90° manufacturing (with method toggle)zernike_*_90_mfg_lateral.html- Lateral displacement map for 90°
Features:
- Toggle buttons to switch between Standard (Z-only) and OPD (X,Y,Z) methods
- Toggle between WFE view and ΔX, ΔY, ΔZ displacement components
- Metrics comparison table showing both methods side-by-side
- Lateral displacement statistics (Max, RMS in µm)
Configuration:
config = InsightConfig(
amplification=0.5, # Reduce deformation scaling
colorscale='Turbo',
extra={
'n_modes': 50,
'filter_low_orders': 4, # Remove piston, tip, tilt, defocus
'disp_unit': 'mm',
}
)
Summary Output:
{
'40_vs_20_filtered_rms_std': 6.01, # nm (Standard method)
'40_vs_20_filtered_rms_opd': 6.53, # nm (OPD method)
'60_vs_20_filtered_rms_std': 12.81, # nm
'60_vs_20_filtered_rms_opd': 14.21, # nm
'90_mfg_filtered_rms_std': 24.5, # nm
'90_mfg_filtered_rms_opd': 26.34, # nm
'90_optician_workload': 26.34, # nm (J1-J3 filtered)
'lateral_40_max_um': 0.234, # µm max lateral displacement
'lateral_60_max_um': 0.312, # µm
'lateral_90_max_um': 0.089, # µm
}
2. MSF Zernike Analysis (msf_zernike)
Purpose: Detailed mid-spatial frequency analysis for telescope mirrors with gravity-induced support print-through.
Generates: 1 comprehensive HTML file with:
- Band decomposition table (LSF/MSF/HSF RSS metrics)
- MSF-only 3D surface visualization
- Coefficient bar chart color-coded by band
- Dominant MSF mode identification
- Mesh resolution analysis
Band Definitions (for 1.2m class mirror):
| Band | Zernike Order | Feature Size | Physical Meaning |
|---|---|---|---|
| LSF | n ≤ 10 | > 120 mm | M2 hexapod correctable |
| MSF | n = 11-50 | 24-109 mm | Support print-through |
| HSF | n > 50 | < 24 mm | Near mesh resolution limit |
Configuration:
config = InsightConfig(
extra={
'n_modes': 100, # Higher than zernike_wfe (100 vs 50)
'lsf_max': 10, # n ≤ 10 is LSF
'msf_max': 50, # n = 11-50 is MSF
'disp_unit': 'mm',
}
)
Analyses Performed:
- Absolute WFE at each orientation (40°, 60°, 90°)
- Relative to 20° (operational reference)
- Relative to 90° (manufacturing/polishing reference)
Summary Output:
{
'n_modes': 100,
'lsf_max_order': 10,
'msf_max_order': 50,
'mesh_nodes': 78290,
'mesh_spacing_mm': 4.1,
'max_resolvable_order': 157,
'40deg_vs_20deg_lsf_rss': 12.3, # nm
'40deg_vs_20deg_msf_rss': 8.7, # nm - KEY METRIC
'40deg_vs_20deg_total_rss': 15.2, # nm
'40deg_vs_20deg_msf_pct': 33.0, # % of total in MSF band
# ... similar for 60deg, 90deg
}
When to Use:
- Analyzing support structure print-through
- Quantifying gravity-induced MSF content
- Comparing MSF at different orientations
- Validating mesh resolution is adequate for MSF capture
3. Stress Distribution (stress_field)
Purpose: Visualize Von Mises stress distribution with hot spot identification.
Configuration:
config = InsightConfig(
colorscale='Hot',
extra={
'yield_stress': 250, # MPa - shows safety factor
'stress_unit': 'MPa',
}
)
Summary Output:
{
'max_stress': 187.5, # MPa
'mean_stress': 45.2, # MPa
'p95_stress': 120.3, # 95th percentile
'p99_stress': 165.8, # 99th percentile
'safety_factor': 1.33, # If yield_stress provided
}
4. Modal Analysis (modal)
Purpose: Visualize natural frequencies and mode shapes.
Configuration:
config = InsightConfig(
amplification=50.0, # Mode shape scale
extra={
'n_modes': 20, # Number of modes to show
'show_mode': 1, # Which mode shape to display
}
)
Summary Output:
{
'n_modes': 20,
'first_frequency_hz': 125.4,
'frequencies_hz': [125.4, 287.8, 312.5, ...],
}
5. Thermal Analysis (thermal)
Purpose: Visualize temperature distribution and gradients.
Configuration:
config = InsightConfig(
colorscale='Thermal',
extra={
'temp_unit': 'K', # or 'C', 'F'
}
)
Summary Output:
{
'max_temp': 423.5, # K
'min_temp': 293.0, # K
'mean_temp': 345.2, # K
'temp_range': 130.5, # K
}
6. Design Space Explorer (design_space)
Purpose: Visualize parameter-objective relationships from optimization trials.
Configuration:
config = InsightConfig(
extra={
'primary_objective': 'filtered_rms', # Color by this objective
}
)
Summary Output:
{
'n_trials': 100,
'n_params': 4,
'n_objectives': 2,
'best_trial_id': 47,
'best_params': {'p1': 0.5, 'p2': 1.2, ...},
'best_values': {'filtered_rms': 45.2, 'mass': 2.34},
}
Output Directory
Insights are saved to {study}/3_insights/:
studies/my_study/
├── 1_setup/
├── 2_results/
└── 3_insights/ # Created by insights module
├── zernike_20241220_143022_40_vs_20.html
├── zernike_20241220_143022_60_vs_20.html
├── zernike_20241220_143022_90_mfg.html
├── stress_20241220_143025.html
└── design_space_20241220_143030.html
Creating New Insight Types
To add a new insight type (power_user+):
1. Create the insight class
# optimization_engine/insights/my_insight.py
from .base import StudyInsight, InsightConfig, InsightResult, register_insight
@register_insight
class MyInsight(StudyInsight):
insight_type = "my_insight"
name = "My Custom Insight"
description = "Description of what it shows"
applicable_to = ["structural", "all"]
def can_generate(self) -> bool:
# Check if required data exists
return self.results_path.exists()
def _generate(self, config: InsightConfig) -> InsightResult:
# Generate visualization
# ... build Plotly figure ...
html_path = config.output_dir / f"my_insight_{timestamp}.html"
html_path.write_text(fig.to_html(...))
return InsightResult(
success=True,
html_path=html_path,
summary={'key_metric': value}
)
2. Register in __init__.py
from .my_insight import MyInsight
3. Test
python -m optimization_engine.insights list
# Should show "my_insight" in the list
Dashboard Integration
The Insights tab in the Atomizer Dashboard provides a 3-step workflow:
Step 1: Select Iteration
- Lists all available iterations (iter1, iter2, etc.) and best_design_archive
- Shows OP2 file name and modification timestamp
- Auto-selects "Best Design (Recommended)" if available
Step 2: Choose Insight Type
- Groups insights by category (Optical, Structural, Thermal, etc.)
- Shows insight name and description
- Click to select, then "Generate Insight"
Step 3: View Result
- Displays summary metrics (RMS values, etc.)
- Embedded Plotly visualization (if available)
- "Open Full View" button for multi-file insights (like Zernike WFE)
- Fullscreen mode for detailed analysis
API Endpoints
GET /api/insights/studies/{id}/iterations # List available iterations
GET /api/insights/studies/{id}/available # List available insight types
GET /api/insights/studies/{id}/generated # List previously generated files
POST /api/insights/studies/{id}/generate/{type} # Generate insight for iteration
GET /api/insights/studies/{id}/view/{type} # View generated HTML
Generate Request Body
{
"iteration": "best_design_archive", // or "iter5", etc.
"trial_id": null, // Optional specific trial
"config": {} // Insight-specific config
}
Version History
| Version | Date | Changes |
|---|---|---|
| 1.3.0 | 2025-12-22 | Added ZernikeDashboardInsight (unified view), OPD method toggle, lateral displacement maps |
| 1.2.0 | 2024-12-22 | Dashboard overhaul: 3-step workflow, iteration selection, faster loading |
| 1.1.0 | 2024-12-21 | Added MSF Zernike Analysis insight (6 insight types) |
| 1.0.0 | 2024-12-20 | Initial release with 5 insight types |