Files
Atomizer/optimization_engine/insights/__init__.py
Anto01 1612991d0d feat: Add Study Insights module (SYS_16) for physics visualizations
Introduces a new plugin architecture for study-specific physics
visualizations, separating "optimizer perspective" (Analysis) from
"engineer perspective" (Insights).

New module: optimization_engine/insights/
- base.py: StudyInsight base class, InsightConfig, InsightResult, registry
- zernike_wfe.py: Mirror WFE with 3D surface and Zernike decomposition
- stress_field.py: Von Mises stress contours with safety factors
- modal_analysis.py: Natural frequencies and mode shapes
- thermal_field.py: Temperature distribution visualization
- design_space.py: Parameter-objective landscape exploration

Features:
- 5 insight types: zernike_wfe, stress_field, modal, thermal, design_space
- CLI: python -m optimization_engine.insights generate <study>
- Standalone HTML generation with Plotly
- Enhanced Zernike viz: Turbo colorscale, smooth shading, 0.5x AMP
- Dashboard API fix: Added include_coefficients param to extract_relative()

Documentation:
- docs/protocols/system/SYS_16_STUDY_INSIGHTS.md
- Updated ATOMIZER_CONTEXT.md (v1.7)
- Updated 01_CHEATSHEET.md with insights section

Tools:
- tools/zernike_html_generator.py: Standalone WFE HTML generator
- tools/analyze_wfe.bat: Double-click to analyze OP2 files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 13:46:28 -05:00

234 lines
6.9 KiB
Python

"""
Atomizer Study Insights Module
Provides physics-focused visualizations for FEA optimization results.
Unlike the Analysis page (optimizer-centric), Insights show the engineering
reality of specific designs through interactive 3D visualizations.
Architecture:
- StudyInsight: Abstract base class for all insight types
- InsightRegistry: Central registry for available insight types
- Each insight generates standalone HTML or Plotly data for dashboard
Available Insight Types:
-----------------------
| Type ID | Name | Description |
|----------------|------------------------|------------------------------------------|
| zernike_wfe | Zernike WFE Analysis | 3D wavefront error with Zernike decomp |
| stress_field | Stress Distribution | Von Mises stress contours |
| modal | Modal Analysis | Natural frequencies and mode shapes |
| thermal | Thermal Analysis | Temperature distribution |
| design_space | Design Space Explorer | Parameter-objective relationships |
Quick Start:
-----------
```python
from optimization_engine.insights import get_insight, list_available_insights
from pathlib import Path
study_path = Path("studies/my_study")
# List available insights for a study
available = list_available_insights(study_path)
print(available)
# Generate a 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"Summary: {result.summary}")
```
CLI Usage:
---------
```bash
# Generate all available insights for a study
python -m optimization_engine.insights generate studies/my_study
# Generate specific insight type
python -m optimization_engine.insights generate studies/my_study --type zernike_wfe
# List available insight types
python -m optimization_engine.insights list
```
"""
# Import base classes first
from .base import (
StudyInsight,
InsightConfig,
InsightResult,
InsightRegistry,
register_insight,
get_insight,
list_insights,
list_available_insights,
)
# Import insight implementations (triggers @register_insight decorators)
from .zernike_wfe import ZernikeWFEInsight
from .stress_field import StressFieldInsight
from .modal_analysis import ModalInsight
from .thermal_field import ThermalInsight
from .design_space import DesignSpaceInsight
# Public API
__all__ = [
# Base classes
'StudyInsight',
'InsightConfig',
'InsightResult',
'InsightRegistry',
'register_insight',
# API functions
'get_insight',
'list_insights',
'list_available_insights',
# Insight implementations
'ZernikeWFEInsight',
'StressFieldInsight',
'ModalInsight',
'ThermalInsight',
'DesignSpaceInsight',
]
def generate_all_insights(study_path, output_dir=None):
"""
Generate all available insights for a study.
Args:
study_path: Path to study directory
output_dir: Optional output directory (defaults to study/3_insights/)
Returns:
List of InsightResult objects
"""
from pathlib import Path
study_path = Path(study_path)
results = []
available = list_available_insights(study_path)
for info in available:
insight = get_insight(info['type'], study_path)
if insight:
config = InsightConfig()
if output_dir:
config.output_dir = Path(output_dir)
result = insight.generate(config)
results.append({
'type': info['type'],
'name': info['name'],
'result': result
})
return results
# CLI entry point
if __name__ == '__main__':
import sys
from pathlib import Path
def print_usage():
print("Atomizer Study Insights")
print("=" * 50)
print()
print("Usage:")
print(" python -m optimization_engine.insights list")
print(" python -m optimization_engine.insights generate <study_path> [--type TYPE]")
print()
print("Commands:")
print(" list - List all registered insight types")
print(" generate - Generate insights for a study")
print()
print("Options:")
print(" --type TYPE Generate only the specified insight type")
print()
if len(sys.argv) < 2:
print_usage()
sys.exit(0)
command = sys.argv[1]
if command == 'list':
print("\nRegistered Insight Types:")
print("-" * 60)
for info in list_insights():
print(f" {info['type']:15} - {info['name']}")
print(f" {info['description']}")
print(f" Applies to: {', '.join(info['applicable_to'])}")
print()
elif command == 'generate':
if len(sys.argv) < 3:
print("Error: Missing study path")
print_usage()
sys.exit(1)
study_path = Path(sys.argv[2])
if not study_path.exists():
print(f"Error: Study path does not exist: {study_path}")
sys.exit(1)
# Parse options
insight_type = None
for i, arg in enumerate(sys.argv[3:], 3):
if arg == '--type' and i + 1 < len(sys.argv):
insight_type = sys.argv[i + 1]
print(f"\nGenerating insights for: {study_path}")
print("-" * 60)
if insight_type:
# Generate specific type
insight = get_insight(insight_type, study_path)
if insight is None:
print(f"Error: Unknown insight type: {insight_type}")
sys.exit(1)
if not insight.can_generate():
print(f"Cannot generate {insight_type}: required data not found")
sys.exit(1)
result = insight.generate()
if result.success:
print(f"Generated: {result.html_path}")
if result.summary:
print(f"Summary: {result.summary}")
else:
print(f"Error: {result.error}")
else:
# Generate all available
available = list_available_insights(study_path)
if not available:
print("No insights available for this study")
sys.exit(0)
print(f"Found {len(available)} available insight(s)")
print()
for info in available:
print(f"Generating {info['name']}...")
insight = get_insight(info['type'], study_path)
result = insight.generate()
if result.success:
print(f" Created: {result.html_path}")
else:
print(f" Error: {result.error}")
print()
print("Done!")
else:
print(f"Unknown command: {command}")
print_usage()
sys.exit(1)