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>
234 lines
6.9 KiB
Python
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)
|