""" 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 - InsightSpec: Config-driven insight specification (from optimization_config.json) - InsightReport: Aggregates insights into HTML/PDF report with appendix - Each insight generates standalone HTML or Plotly data for dashboard Available Insight Types: ----------------------- | Type ID | Name | Description | |------------------|------------------------|------------------------------------------| | zernike_dashboard| Zernike Dashboard | RECOMMENDED: Unified WFE dashboard | | zernike_wfe | Zernike WFE Analysis | 3D wavefront error with Zernike decomp | | msf_zernike | MSF Zernike Analysis | Mid-spatial frequency band decomposition | | 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 | Config Schema (in optimization_config.json): ------------------------------------------- ```json "insights": [ { "type": "zernike_wfe", "name": "WFE at 40 vs 20 deg", "enabled": true, "linked_objective": "rel_filtered_rms_40_vs_20", "config": { "target_subcase": "3", "reference_subcase": "2" }, "include_in_report": true } ] ``` Quick Start: ----------- ```python from optimization_engine.insights import ( get_insight, list_available_insights, get_configured_insights, recommend_insights_for_study, InsightReport ) from pathlib import Path study_path = Path("studies/my_study") # Get recommended insights based on objectives recommendations = recommend_insights_for_study(study_path) for rec in recommendations: print(f"{rec['type']}: {rec['reason']}") # Generate report from configured insights report = InsightReport(study_path) results = report.generate_all() # Uses specs from optimization_config.json report_path = report.generate_report_html() # Creates STUDY_INSIGHTS_REPORT.html ``` 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 # Generate full report from config python -m optimization_engine.insights report studies/my_study # Get recommendations for a study python -m optimization_engine.insights recommend studies/my_study # List available insight types python -m optimization_engine.insights list ``` """ # Import base classes first from .base import ( StudyInsight, InsightConfig, InsightResult, InsightSpec, InsightReport, InsightRegistry, register_insight, get_insight, list_insights, list_available_insights, get_configured_insights, recommend_insights_for_study, ) # Import insight implementations (triggers @register_insight decorators) from .zernike_wfe import ZernikeWFEInsight from .zernike_opd_comparison import ZernikeOPDComparisonInsight from .msf_zernike import MSFZernikeInsight from .zernike_dashboard import ZernikeDashboardInsight # NEW: Unified dashboard 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', 'InsightSpec', 'InsightReport', 'InsightRegistry', 'register_insight', # API functions 'get_insight', 'list_insights', 'list_available_insights', 'get_configured_insights', 'recommend_insights_for_study', # Insight implementations 'ZernikeWFEInsight', 'ZernikeOPDComparisonInsight', 'MSFZernikeInsight', 'ZernikeDashboardInsight', # NEW: Unified dashboard '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 [--type TYPE]") print(" python -m optimization_engine.insights report ") print(" python -m optimization_engine.insights recommend ") print() print("Commands:") print(" list - List all registered insight types") print(" generate - Generate insights for a study") print(" report - Generate full report from config") print(" recommend - Get AI recommendations for insights") 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 == 'recommend': 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) print(f"\nRecommended Insights for: {study_path.name}") print("-" * 60) recommendations = recommend_insights_for_study(study_path) if not recommendations: print("No recommendations available (no objectives found)") sys.exit(0) for rec in recommendations: linked = f" -> {rec['linked_objective']}" if rec.get('linked_objective') else "" print(f"\n {rec['type']}{linked}") print(f" Name: {rec['name']}") print(f" Reason: {rec['reason']}") print() print("Add these to optimization_config.json under 'insights' key") elif command == 'report': 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) print(f"\nGenerating Insight Report for: {study_path.name}") print("-" * 60) # Check for configured insights specs = get_configured_insights(study_path) if not specs: print("No insights configured in optimization_config.json") print("Using recommendations based on objectives...") recommendations = recommend_insights_for_study(study_path) specs = [ InsightSpec( type=rec['type'], name=rec['name'], linked_objective=rec.get('linked_objective'), config=rec.get('config', {}) ) for rec in recommendations ] if not specs: print("No insights to generate") sys.exit(0) print(f"Generating {len(specs)} insight(s)...") report = InsightReport(study_path) results = report.generate_all(specs) for result in results: status = "OK" if result.success else f"FAIL: {result.error}" print(f" {result.insight_name}: {status}") if any(r.success for r in results): report_path = report.generate_report_html() print(f"\nReport generated: {report_path}") print("Open in browser and use 'Save as PDF' button to export") else: print("\nNo insights generated successfully") 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)