feat: Add Protocol 13 adaptive optimization, Plotly charts, and dashboard improvements

## Protocol 13: Adaptive Multi-Objective Optimization
- Iterative FEA + Neural Network surrogate workflow
- Initial FEA sampling, NN training, NN-accelerated search
- FEA validation of top NN predictions, retraining loop
- adaptive_state.json tracks iteration history and best values
- M1 mirror study (V11) with 103 FEA, 3000 NN trials

## Dashboard Visualization Enhancements
- Added Plotly.js interactive charts (parallel coords, Pareto, convergence)
- Lazy loading with React.lazy() for performance
- Code splitting: plotly.js-basic-dist (~1MB vs 3.5MB)
- Chart library toggle (Recharts default, Plotly on-demand)
- ExpandableChart component for full-screen modal views
- ConsoleOutput component for real-time log viewing

## Documentation
- Protocol 13 detailed documentation
- Dashboard visualization guide
- Plotly components README
- Updated run-optimization skill with Mode 5 (adaptive)

## Bug Fixes
- Fixed TypeScript errors in dashboard components
- Fixed Card component to accept ReactNode title
- Removed unused imports across components

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Antoine
2025-12-04 07:41:54 -05:00
parent e74f1ccf36
commit 8cbdbcad78
270 changed files with 15471 additions and 517 deletions

View File

@@ -24,6 +24,9 @@ This section defines ALL available components. When generating `run_optimization
| E5 | **CAD Expression Mass** | `optimization_engine.extractors.extract_mass_from_expression` | `extract_mass_from_expression(prt_file, expression_name='p173')` | `.prt` + `_temp_mass.txt` | kg | `float` (mass in kg) |
| E6 | **Field Data** | `optimization_engine.extractors.field_data_extractor` | `FieldDataExtractor(field_file, result_column, aggregation)` | `.fld`/`.csv` | varies | `{'value': float, 'stats': dict}` |
| E7 | **Stiffness** | `optimization_engine.extractors.stiffness_calculator` | `StiffnessCalculator(field_file, op2_file, force_component, displacement_component)` | `.fld` + `.op2` | N/mm | `{'stiffness': float, 'displacement': float, 'force': float}` |
| E8 | **Zernike WFE** | `optimization_engine.extractors.extract_zernike` | `extract_zernike_from_op2(op2_file, bdf_file, subcase)` | `.op2` + `.bdf` | nm | `{'global_rms_nm': float, 'filtered_rms_nm': float, 'coefficients': list, ...}` |
| E9 | **Zernike Relative** | `optimization_engine.extractors.extract_zernike` | `extract_zernike_relative_rms(op2_file, bdf_file, target_subcase, ref_subcase)` | `.op2` + `.bdf` | nm | `{'relative_filtered_rms_nm': float, 'delta_coefficients': list, ...}` |
| E10 | **Zernike Helpers** | `optimization_engine.extractors.zernike_helpers` | `create_zernike_objective(op2_finder, subcase, metric)` | `.op2` | nm | Callable returning metric value |
### PR.2 Extractor Code Snippets (COPY-PASTE)
@@ -76,6 +79,59 @@ applied_force = 1000.0 # N - MUST MATCH YOUR MODEL'S APPLIED LOAD
stiffness = applied_force / max(abs(max_displacement), 1e-6) # N/mm
```
**E8: Zernike Wavefront Error Extraction (Telescope Mirrors)**
```python
from optimization_engine.extractors.extract_zernike import extract_zernike_from_op2
# Extract Zernike coefficients and RMS metrics for a single subcase
result = extract_zernike_from_op2(
op2_file,
bdf_file=None, # Auto-detect from op2 location
subcase="20", # Subcase label (e.g., "20" = 20 deg elevation)
displacement_unit="mm"
)
global_rms = result['global_rms_nm'] # Total surface RMS in nm
filtered_rms = result['filtered_rms_nm'] # RMS with low orders (piston, tip, tilt, defocus) removed
coefficients = result['coefficients'] # List of 50 Zernike coefficients
```
**E9: Zernike Relative RMS (Between Subcases)**
```python
from optimization_engine.extractors.extract_zernike import extract_zernike_relative_rms
# Compare wavefront error between subcases (e.g., 40 deg vs 20 deg reference)
result = extract_zernike_relative_rms(
op2_file,
bdf_file=None,
target_subcase="40", # Target orientation
reference_subcase="20", # Reference (usually polishing orientation)
displacement_unit="mm"
)
relative_rms = result['relative_filtered_rms_nm'] # Differential WFE in nm
delta_coeffs = result['delta_coefficients'] # Coefficient differences
```
**E10: Zernike Objective Builder (Multi-Subcase Optimization)**
```python
from optimization_engine.extractors.zernike_helpers import ZernikeObjectiveBuilder
# Build objectives for multiple subcases in one extractor
builder = ZernikeObjectiveBuilder(
op2_finder=lambda: model_dir / "ASSY_M1-solution_1.op2"
)
# Add relative objectives (target vs reference)
builder.add_relative_objective("40", "20", metric="relative_filtered_rms_nm", weight=5.0)
builder.add_relative_objective("60", "20", metric="relative_filtered_rms_nm", weight=5.0)
# Add absolute objective for polishing orientation
builder.add_subcase_objective("90", metric="rms_filter_j1to3", weight=1.0)
# Evaluate all at once (efficient - parses OP2 only once)
results = builder.evaluate_all()
# Returns: {'rel_40_vs_20': 4.2, 'rel_60_vs_20': 8.7, 'rms_90': 15.3}
```
### PR.3 NXSolver Interface
**Module**: `optimization_engine.nx_solver`
@@ -430,6 +486,136 @@ if __name__ == "__main__":
main()
```
### PR.11 Adding New Features to Atomizer Framework (REUSABILITY)
**CRITICAL: When developing extractors, calculators, or post-processing logic for a study, ALWAYS add them to the Atomizer framework for reuse!**
#### Why Reusability Matters
Each study should build on the framework, not duplicate code:
- **WRONG**: Embed 500 lines of Zernike analysis in `run_optimization.py`
- **CORRECT**: Create `extract_zernike.py` in `optimization_engine/extractors/` and import it
#### When to Create a New Extractor
Create a new extractor when:
1. The study needs result extraction not covered by existing extractors (E1-E10)
2. The logic is reusable across different studies
3. The extraction involves non-trivial calculations (>20 lines of code)
#### Workflow for Adding New Extractors
```
STEP 1: Check existing extractors in PR.1 Catalog
├── If exists → IMPORT and USE it (done!)
└── If missing → Continue to STEP 2
STEP 2: Create extractor in optimization_engine/extractors/
├── File: extract_{feature}.py
├── Follow existing extractor patterns
└── Include comprehensive docstrings
STEP 3: Add to __init__.py
└── Export functions in optimization_engine/extractors/__init__.py
STEP 4: Update this skill (create-study.md)
├── Add to PR.1 Extractor Catalog table
└── Add code snippet to PR.2
STEP 5: Document in CLAUDE.md (if major feature)
└── Add to Available Extractors table
```
#### New Extractor Template
```python
"""
{Feature} Extractor for Atomizer Optimization
=============================================
Extract {description} from {input_type} files.
Usage:
from optimization_engine.extractors.extract_{feature} import extract_{feature}
result = extract_{feature}(input_file, **params)
"""
from pathlib import Path
from typing import Dict, Any, Optional
import logging
logger = logging.getLogger(__name__)
def extract_{feature}(
input_file: Path,
param1: str = "default",
**kwargs
) -> Dict[str, Any]:
"""
Extract {feature} from {input_type}.
Args:
input_file: Path to input file
param1: Description of param
**kwargs: Additional parameters
Returns:
Dict with keys:
- primary_result: Main extracted value
- metadata: Additional extraction info
Example:
>>> result = extract_{feature}("model.op2")
>>> print(result['primary_result'])
"""
# Implementation here
pass
# Export for __init__.py
__all__ = ['extract_{feature}']
```
#### Example: Adding Thermal Gradient Extractor
If a study needs thermal gradient analysis:
1. **Create**: `optimization_engine/extractors/extract_thermal_gradient.py`
2. **Implement**: Functions for parsing thermal OP2 data
3. **Export**: Add to `__init__.py`
4. **Document**: Add E11 to catalog here
5. **Use**: Import in `run_optimization.py`
```python
# In run_optimization.py - CORRECT
from optimization_engine.extractors.extract_thermal_gradient import extract_thermal_gradient
result = extract_thermal_gradient(op2_file, subcase=1)
max_gradient = result['max_gradient_K_per_mm']
```
#### NEVER Do This
```python
# In run_optimization.py - WRONG!
def calculate_thermal_gradient(op2_file, subcase):
"""200 lines of thermal gradient calculation..."""
# This should be in optimization_engine/extractors/!
pass
result = calculate_thermal_gradient(op2_file, 1) # Not reusable!
```
#### Updating This Skill After Adding Extractor
When you add a new extractor to the framework:
1. **PR.1**: Add row to Extractor Catalog table with ID, name, module, function, input, output, returns
2. **PR.2**: Add code snippet showing usage
3. **Common Patterns**: Add new pattern if this creates a new optimization type
---
## Document Philosophy
@@ -608,10 +794,11 @@ An Atomizer study is a self-contained optimization project that combines:
```
studies/{study_name}/
├── 1_setup/ # INPUT: Configuration & Model
│ ├── model/ # NX Files
│ │ ├── {Model}.prt # Parametric part (USER PROVIDES)
│ │ ├── {Model}_sim1.sim # Simulation setup (USER PROVIDES)
│ ├── model/ # WORKING COPY of NX Files (AUTO-COPIED)
│ │ ├── {Model}.prt # Parametric part (COPIED FROM SOURCE)
│ │ ├── {Model}_sim1.sim # Simulation setup (COPIED FROM SOURCE)
│ │ ├── {Model}_fem1.fem # FEM mesh (AUTO-GENERATED)
│ │ ├── {Model}_fem1_i.prt # Idealized part (COPIED if Assembly FEM)
│ │ └── *.dat, *.op2, *.f06 # Solver outputs (AUTO-GENERATED)
│ ├── optimization_config.json # Study configuration (SKILL GENERATES)
│ └── workflow_config.json # Workflow metadata (SKILL GENERATES)
@@ -624,6 +811,80 @@ studies/{study_name}/
└── README.md # Study documentation (SKILL GENERATES)
```
### CRITICAL: Model File Protection
**NEVER modify the user's original/master model files.** Always work on copies.
**Why This Matters**:
- Optimization iteratively modifies expressions, meshes, and geometry
- NX saves changes automatically - corruption during iteration can damage master files
- Broken geometry/mesh can make files unrecoverable
- Users may have months of CAD work in master files
**Mandatory Workflow**:
```
User's Source Files Study Working Copy
─────────────────────────────────────────────────────────────
C:/Projects/M1-Gigabit/Latest/ studies/{study_name}/1_setup/model/
├── M1_Blank.prt ──► ├── M1_Blank.prt
├── M1_Blank_fem1.fem ──► ├── M1_Blank_fem1.fem
├── M1_Blank_fem1_i.prt ──► ├── M1_Blank_fem1_i.prt
├── ASSY_*.afm ──► ├── ASSY_*.afm
└── *_sim1.sim ──► └── *_sim1.sim
↓ COPY ALL FILES ↓
OPTIMIZATION RUNS ON WORKING COPY ONLY
↓ IF CORRUPTION ↓
Delete working copy, re-copy from source
No damage to master files
```
**Copy Script (generated in run_optimization.py)**:
```python
import shutil
from pathlib import Path
def setup_working_copy(source_dir: Path, model_dir: Path, file_patterns: list):
"""
Copy model files from user's source to study working directory.
Args:
source_dir: Path to user's original model files (NEVER MODIFY)
model_dir: Path to study's 1_setup/model/ directory (WORKING COPY)
file_patterns: List of glob patterns to copy (e.g., ['*.prt', '*.fem', '*.sim'])
"""
model_dir.mkdir(parents=True, exist_ok=True)
for pattern in file_patterns:
for src_file in source_dir.glob(pattern):
dst_file = model_dir / src_file.name
if not dst_file.exists() or src_file.stat().st_mtime > dst_file.stat().st_mtime:
print(f"Copying: {src_file.name}")
shutil.copy2(src_file, dst_file)
print(f"Working copy ready in: {model_dir}")
```
**Assembly FEM Files to Copy**:
| File Pattern | Purpose | Required |
|--------------|---------|----------|
| `*.prt` | Parametric geometry parts | Yes |
| `*_fem1.fem` | Component FEM meshes | Yes |
| `*_fem1_i.prt` | Idealized parts (geometry link) | Yes (if exists) |
| `*.afm` | Assembly FEM | Yes (if assembly) |
| `*_sim1.sim` | Simulation setup | Yes |
| `*.exp` | Expression files | If used |
**When User Provides Source Path**:
1. **ASK**: "Where are your model files?"
2. **STORE**: Record source path in `optimization_config.json` as `"source_model_dir"`
3. **COPY**: All relevant NX files to `1_setup/model/`
4. **NEVER**: Point optimization directly at source files
5. **DOCUMENT**: In README, show both source and working paths
### Optimization Trial Loop
Each optimization trial follows this execution flow:
@@ -1753,16 +2014,110 @@ Design Variables: {list}
Multi-Solution: {Yes/No}
```
2. **File Checklist**:
2. **File Checklist** (ALL MANDATORY):
```
✓ studies/{study_name}/1_setup/optimization_config.json
✓ studies/{study_name}/1_setup/workflow_config.json
✓ studies/{study_name}/run_optimization.py
✓ studies/{study_name}/reset_study.py
✓ studies/{study_name}/README.md
✓ studies/{study_name}/README.md # Engineering blueprint
✓ studies/{study_name}/STUDY_REPORT.md # MANDATORY - Results report template
[✓] studies/{study_name}/NX_FILE_MODIFICATIONS_REQUIRED.md (if needed)
```
### STUDY_REPORT.md - MANDATORY FOR ALL STUDIES
**CRITICAL**: Every study MUST have a STUDY_REPORT.md file. This is the living results document that gets updated as optimization progresses. Create it at study setup time with placeholder sections.
**Location**: `studies/{study_name}/STUDY_REPORT.md`
**Purpose**:
- Documents optimization results as they come in
- Tracks best solutions found
- Records convergence history
- Compares FEA vs Neural predictions (if applicable)
- Provides engineering recommendations
**Template**:
```markdown
# {Study Name} - Optimization Report
**Study**: {study_name}
**Created**: {date}
**Status**: 🔄 In Progress / ✅ Complete
---
## Executive Summary
| Metric | Value |
|--------|-------|
| Total Trials | - |
| FEA Trials | - |
| NN Trials | - |
| Best {Objective1} | - |
| Best {Objective2} | - |
*Summary will be updated as optimization progresses.*
---
## 1. Optimization Progress
### Trial History
| Trial | {Obj1} | {Obj2} | Source | Status |
|-------|--------|--------|--------|--------|
| - | - | - | - | - |
### Convergence
*Convergence plots will be added after sufficient trials.*
---
## 2. Best Designs Found
### Best {Objective1}
| Parameter | Value |
|-----------|-------|
| - | - |
### Best {Objective2}
| Parameter | Value |
|-----------|-------|
| - | - |
### Pareto Front (if multi-objective)
*Pareto solutions will be listed here.*
---
## 3. Neural Surrogate Performance (if applicable)
| Metric | {Obj1} | {Obj2} |
|--------|--------|--------|
| R² Score | - | - |
| MAE | - | - |
| Prediction Time | - | - |
---
## 4. Engineering Recommendations
*Recommendations based on optimization results.*
---
## 5. Next Steps
- [ ] Continue optimization
- [ ] Validate best design with detailed FEA
- [ ] Manufacturing review
---
*Report auto-generated by Atomizer. Last updated: {date}*
```
3. **Next Steps** (as shown earlier)
## Remember