# 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**: 1. Builds interpolator from undeformed BDF mesh geometry 2. For each deformed node at `(x+dx, y+dy, z+dz)`, interpolates `Z_ideal` at new XY position 3. Computes `WFE = z_deformed - Z_ideal(x_def, y_def)` 4. 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 ```python 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) ```python 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 ```python @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 ```python @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 ```python 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 ```bash # 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 ```python 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**: ```python config = InsightConfig( extra={ 'n_modes': 50, 'filter_low_orders': 4, 'theme': 'light', # Light theme for reports } ) ``` **Summary Output**: ```python { '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**: ```python 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**: ```python { '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**: ```python 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**: ```python { '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**: ```python config = InsightConfig( colorscale='Hot', extra={ 'yield_stress': 250, # MPa - shows safety factor 'stress_unit': 'MPa', } ) ``` **Summary Output**: ```python { '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**: ```python 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**: ```python { '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**: ```python config = InsightConfig( colorscale='Thermal', extra={ 'temp_unit': 'K', # or 'C', 'F' } ) ``` **Summary Output**: ```python { '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**: ```python config = InsightConfig( extra={ 'primary_objective': 'filtered_rms', # Color by this objective } ) ``` **Summary Output**: ```python { '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 ```python # 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` ```python from .my_insight import MyInsight ``` ### 3. Test ```bash 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 ```json { "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 |