## 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>
11 KiB
Assembly FEM Optimization Workflow
This document describes the multi-part assembly FEM workflow used when optimizing complex assemblies with .afm (Assembly FEM) files.
CRITICAL: Working Copy Requirement
NEVER run optimization directly on user's master model files.
Before any optimization run, ALL model files must be copied to the study's working directory:
Source (NEVER MODIFY) Working Copy (optimization runs here)
────────────────────────────────────────────────────────────────────────────
C:/Users/.../M1-Gigabit/Latest/ studies/{study}/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
├── M1_Vertical_Support_Skeleton.prt → ├── M1_Vertical_Support_Skeleton.prt
├── ASSY_M1_assyfem1.afm → ├── ASSY_M1_assyfem1.afm
└── ASSY_M1_assyfem1_sim1.sim → └── ASSY_M1_assyfem1_sim1.sim
Why: Optimization iteratively modifies expressions, meshes, and saves files. If corruption occurs during iteration (solver crash, bad parameter combo), the working copy can be deleted and re-copied. Master files remain safe.
Files to Copy:
*.prt- All part files (geometry + idealized)*.fem- All FEM files*.afm- Assembly FEM files*.sim- Simulation files*.exp- Expression files (if any)
Overview
Assembly FEMs have a more complex dependency chain than single-part simulations:
.prt (geometry) → _fem1.fem (component mesh) → .afm (assembly mesh) → .sim (solution)
Each level must be updated in sequence when design parameters change.
When This Workflow Applies
This workflow is automatically triggered when:
- The working directory contains
.afmfiles - Multiple
.femfiles exist (component meshes) - Multiple
.prtfiles exist (component geometry)
Examples:
- M1 Mirror assembly (M1_Blank + M1_Vertical_Support_Skeleton)
- Multi-component mechanical assemblies
- Any NX assembly where components have separate FEM files
The 4-Step Workflow
Step 1: Update Expressions in Geometry Part (.prt)
Open M1_Blank.prt
├── Find and update design expressions
│ ├── whiffle_min = 42.5
│ ├── whiffle_outer_to_vertical = 75.0
│ └── inner_circular_rib_dia = 550.0
├── Rebuild geometry (DoUpdate)
└── Save part
The .prt file contains the parametric CAD model with expressions that drive dimensions. These expressions are updated with new design parameter values, then the geometry is rebuilt.
Step 1b: Update ALL Linked Geometry Parts (CRITICAL!)
⚠️ THIS STEP IS CRITICAL - SKIPPING IT CAUSES CORRUPT RESULTS ⚠️
For each geometry part with linked expressions:
├── Open M1_Vertical_Support_Skeleton.prt
├── DoUpdate() - propagate linked expression changes
├── Geometry rebuilds to match M1_Blank
└── Save part
Why this is critical:
- M1_Vertical_Support_Skeleton has expressions linked to M1_Blank
- When M1_Blank geometry changes, the support skeleton MUST also update
- If not updated, FEM nodes will be at OLD positions → nodes not coincident → merge fails
- Result: "billion nm" RMS values (corrupt displacement data)
Rule: YOU MUST UPDATE ALL GEOMETRY PARTS UNDER THE .sim FILE!
- If there are 5 geometry parts, update all 5
- If there are 10 geometry parts, update all 10
- Unless explicitly told otherwise in the study config
Step 2: Update Component FEM Files (.fem)
For each component FEM:
├── Open M1_Blank_fem1.fem
│ ├── UpdateFemodel() - regenerates mesh from updated geometry
│ └── Save FEM
├── Open M1_Vertical_Support_Skeleton_fem1.fem
│ ├── UpdateFemodel()
│ └── Save FEM
└── ... (repeat for all component FEMs)
Each component FEM is linked to its source geometry. UpdateFemodel() regenerates the mesh based on the updated geometry.
Step 3: Update Assembly FEM (.afm)
Open ASSY_M1_assyfem1.afm
├── UpdateFemodel() - updates assembly mesh
├── Merge coincident nodes (at component interfaces)
├── Resolve labeling conflicts (duplicate node/element IDs)
└── Save AFM
The assembly FEM combines component meshes. This step:
- Reconnects meshes at shared interfaces
- Resolves numbering conflicts between component meshes
- Ensures mesh continuity for accurate analysis
Step 4: Solve Simulation (.sim)
Open ASSY_M1_assyfem1_sim1.sim
├── Execute solve
│ ├── Foreground mode for all solutions
│ └── or Background mode for specific solution
└── Save simulation
The simulation file references the assembly FEM and contains solution setup (loads, constraints, subcases).
File Dependencies
M1 Mirror Example:
M1_Blank.prt ─────────────────────> M1_Blank_fem1.fem ─────────┐
│ │ │
│ (expressions) │ (component mesh) │
↓ ↓ │
M1_Vertical_Support_Skeleton.prt ──> M1_..._Skeleton_fem1.fem ─┤
│
↓
ASSY_M1_assyfem1.afm ──> ASSY_M1_assyfem1_sim1.sim
(assembly mesh) (solution)
API Functions Used
| Step | NX API Call | Purpose |
|---|---|---|
| 1 | OpenBase() |
Open .prt file |
| 1 | ImportFromFile() |
Import expressions from .exp file (preferred) |
| 1 | DoUpdate() |
Rebuild geometry |
| 2-3 | UpdateFemodel() |
Regenerate mesh from geometry |
| 3 | DuplicateNodesCheckBuilder |
Merge coincident nodes at interfaces |
| 3 | MergeOccurrenceNodes = True |
Critical: enables cross-component merge |
| 4 | SolveAllSolutions() |
Execute FEA (Foreground mode recommended) |
Expression Update Method
The recommended approach uses expression file import:
# Write expressions to .exp file
with open(exp_path, 'w') as f:
for name, value in expressions.items():
unit = get_unit_for_expression(name)
f.write(f"[{unit}]{name}={value}\n")
# Import into part
modified, errors = workPart.Expressions.ImportFromFile(
exp_path,
NXOpen.ExpressionCollection.ImportMode.Replace
)
This is more reliable than EditExpressionWithUnits() for batch updates.
Error Handling
Common issues and solutions:
"Update undo happened"
- Geometry update failed due to constraint violations
- Check expression values are within valid ranges
- May need to adjust parameter bounds
"This operation can only be done on the work part"
- Work part not properly set before operation
- Use
SetWork()to make target part the work part
Node merge warnings
- Manual intervention may be needed for complex interfaces
- Check mesh connectivity in NX after solve
"Billion nm" RMS values
- Indicates node merging failed - coincident nodes not properly merged
- Check
MergeOccurrenceNodes = Trueis set - Verify tolerance (0.01 mm recommended)
- Run node merge after every FEM update, not just once
Configuration
The workflow auto-detects assembly FEMs, but you can configure behavior:
{
"nx_settings": {
"expression_part": "M1_Blank", // Override auto-detection
"component_fems": [ // Explicit list of FEMs to update
"M1_Blank_fem1.fem",
"M1_Vertical_Support_Skeleton_fem1.fem"
],
"afm_file": "ASSY_M1_assyfem1.afm"
}
}
Implementation Reference
See optimization_engine/solve_simulation.py for the full implementation:
detect_assembly_fem()- Detects if assembly workflow neededupdate_expressions_in_part()- Step 1 implementationupdate_fem_part()- Step 2 implementationupdate_assembly_fem()- Step 3 implementationsolve_simulation_file()- Step 4 implementation
HEEDS-Style Iteration Folder Management (V9+)
For complex assemblies, each optimization trial uses a fresh copy of the master model:
study_name/
├── 1_setup/
│ └── model/ # Master model files (NEVER MODIFY)
│ ├── ASSY_M1.prt
│ ├── ASSY_M1_assyfem1.afm
│ ├── ASSY_M1_assyfem1_sim1.sim
│ ├── M1_Blank.prt
│ ├── M1_Blank_fem1.fem
│ └── ...
├── 2_iterations/
│ ├── iter0/ # Trial 0 working copy
│ │ ├── [all model files]
│ │ ├── params.exp # Expression values for this trial
│ │ └── results/ # OP2, Zernike CSV, etc.
│ ├── iter1/ # Trial 1 working copy
│ └── ...
└── 3_results/
└── study.db # Optuna database
Why Fresh Copies Per Iteration?
- Corruption isolation: If mesh regeneration fails mid-trial, only that iteration is affected
- Reproducibility: Can re-run any trial by using its params.exp
- Debugging: All intermediate files preserved for post-mortem analysis
- Parallelization: Multiple NX sessions could run different iterations (future)
Iteration Folder Contents
| File | Purpose |
|---|---|
*.prt, *.fem, *.afm, *.sim |
Fresh copy of all NX model files |
params.exp |
Expression file with trial parameter values |
*-solution_1.op2 |
Nastran results (after solve) |
results/zernike_trial_N.csv |
Extracted Zernike metrics |
0-Based Iteration Numbering
Iterations are numbered starting from 0 to match Optuna trial numbers:
iter0= Optuna trial 0 = Dashboard shows trial 0iter1= Optuna trial 1 = Dashboard shows trial 1
This ensures cross-referencing between dashboard, database, and file system is straightforward.
Multi-Subcase Solutions
For gravity analysis at multiple orientations, use subcases:
Simulation Setup in NX:
├── Subcase 1: 90 deg elevation (zenith/polishing)
├── Subcase 2: 20 deg elevation (low angle reference)
├── Subcase 3: 40 deg elevation
└── Subcase 4: 60 deg elevation
Solving All Subcases
Use solution_name=None or solve_all_subcases=True to ensure all subcases are solved:
"nx_settings": {
"solution_name": "Solution 1",
"solve_all_subcases": true
}
Subcase ID Mapping
NX subcase IDs (1, 2, 3, 4) may not match the angle labels. Always define explicit mapping:
"zernike_settings": {
"subcases": ["1", "2", "3", "4"],
"subcase_labels": {
"1": "90deg",
"2": "20deg",
"3": "40deg",
"4": "60deg"
},
"reference_subcase": "2"
}
Tips
- Start with baseline solve: Before optimization, manually verify the full workflow completes in NX
- Check mesh quality: Poor mesh quality after updates can cause solve failures
- Monitor memory: Assembly FEMs with many components use significant memory
- Use Foreground mode: For multi-subcase solutions, Foreground mode ensures all subcases complete
- Validate OP2 data: Check for corrupt results (all zeros, unrealistic magnitudes) before processing
- Preserve user NX sessions: NXSessionManager tracks PIDs to avoid closing user's NX instances