feat: Add M1 mirror Zernike optimization with correct RMS calculation
Major improvements to telescope mirror optimization workflow: Assembly FEM Workflow (solve_simulation.py): - Fixed multi-part assembly FEM update sequence - Use ImportFromFile() for reliable expression updates - Add DuplicateNodesCheckBuilder with MergeOccurrenceNodes=True - Switch to Foreground solve mode for multi-subcase solutions - Add detailed logging and diagnostics for node merge operations Zernike RMS Calculation: - CRITICAL FIX: Use correct surface-based RMS formula - Global RMS = sqrt(mean(W^2)) from actual WFE values - Filtered RMS = sqrt(mean(W_residual^2)) after removing low-order fit - This matches zernike_Post_Script_NX.py (optical standard) - Previous WRONG formula was: sqrt(sum(coeffs^2)) - Add compute_rms_filter_j1to3() for optician workload metric Subcase Mapping: - Fix subcase mapping to match NX model: - Subcase 1 = 90 deg (polishing orientation) - Subcase 2 = 20 deg (reference) - Subcase 3 = 40 deg - Subcase 4 = 60 deg New Study: M1 Mirror Zernike Optimization - Full optimization config with 11 design variables - 3 objectives: rel_filtered_rms_40_vs_20, rel_filtered_rms_60_vs_20, mfg_90_optician_workload - Neural surrogate support for accelerated optimization Documentation: - Update ZERNIKE_INTEGRATION.md with correct RMS formula - Update ASSEMBLY_FEM_WORKFLOW.md with expression import and node merge details - Add reference scripts from original zernike_Post_Script_NX.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"$schema": "Atomizer M1 Mirror Zernike Optimization",
|
||||
"study_name": "m1_mirror_zernike_optimization",
|
||||
"description": "Telescope primary mirror support structure optimization using Zernike wavefront error metrics with neural acceleration",
|
||||
|
||||
"design_variables": [
|
||||
{
|
||||
"name": "lateral_inner_angle",
|
||||
"expression_name": "lateral_inner_angle",
|
||||
"min": 25.0,
|
||||
"max": 28.5,
|
||||
"baseline": 26.79,
|
||||
"units": "degrees",
|
||||
"description": "Lateral support inner angle",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "lateral_outer_angle",
|
||||
"expression_name": "lateral_outer_angle",
|
||||
"min": 13.0,
|
||||
"max": 17.0,
|
||||
"baseline": 14.64,
|
||||
"units": "degrees",
|
||||
"description": "Lateral support outer angle",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "lateral_outer_pivot",
|
||||
"expression_name": "lateral_outer_pivot",
|
||||
"min": 9.0,
|
||||
"max": 12.0,
|
||||
"baseline": 10.40,
|
||||
"units": "mm",
|
||||
"description": "Lateral outer pivot position",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "lateral_inner_pivot",
|
||||
"expression_name": "lateral_inner_pivot",
|
||||
"min": 9.0,
|
||||
"max": 12.0,
|
||||
"baseline": 10.07,
|
||||
"units": "mm",
|
||||
"description": "Lateral inner pivot position",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "lateral_middle_pivot",
|
||||
"expression_name": "lateral_middle_pivot",
|
||||
"min": 18.0,
|
||||
"max": 23.0,
|
||||
"baseline": 20.73,
|
||||
"units": "mm",
|
||||
"description": "Lateral middle pivot position",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "lateral_closeness",
|
||||
"expression_name": "lateral_closeness",
|
||||
"min": 9.5,
|
||||
"max": 12.5,
|
||||
"baseline": 11.02,
|
||||
"units": "mm",
|
||||
"description": "Lateral support closeness parameter",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "whiffle_min",
|
||||
"expression_name": "whiffle_min",
|
||||
"min": 35.0,
|
||||
"max": 55.0,
|
||||
"baseline": 40.55,
|
||||
"units": "mm",
|
||||
"description": "Whiffle tree minimum parameter",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "whiffle_outer_to_vertical",
|
||||
"expression_name": "whiffle_outer_to_vertical",
|
||||
"min": 68.0,
|
||||
"max": 80.0,
|
||||
"baseline": 75.67,
|
||||
"units": "degrees",
|
||||
"description": "Whiffle tree outer to vertical angle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "whiffle_triangle_closeness",
|
||||
"expression_name": "whiffle_triangle_closeness",
|
||||
"min": 50.0,
|
||||
"max": 65.0,
|
||||
"baseline": 60.00,
|
||||
"units": "mm",
|
||||
"description": "Whiffle tree triangle closeness",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "blank_backface_angle",
|
||||
"expression_name": "blank_backface_angle",
|
||||
"min": 3.5,
|
||||
"max": 5.0,
|
||||
"baseline": 4.23,
|
||||
"units": "degrees",
|
||||
"description": "Mirror blank backface angle",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"name": "inner_circular_rib_dia",
|
||||
"expression_name": "inner_circular_rib_dia",
|
||||
"min": 480.0,
|
||||
"max": 620.0,
|
||||
"baseline": 534.00,
|
||||
"units": "mm",
|
||||
"description": "Inner circular rib diameter",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
|
||||
"objectives": [
|
||||
{
|
||||
"name": "rel_filtered_rms_40_vs_20",
|
||||
"description": "Filtered RMS WFE at 40 deg relative to 20 deg reference",
|
||||
"direction": "minimize",
|
||||
"weight": 5.0,
|
||||
"target": 4.0,
|
||||
"units": "nm",
|
||||
"extractor": "zernike_relative",
|
||||
"extractor_config": {
|
||||
"target_subcase": "40",
|
||||
"reference_subcase": "20",
|
||||
"metric": "relative_filtered_rms_nm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rel_filtered_rms_60_vs_20",
|
||||
"description": "Filtered RMS WFE at 60 deg relative to 20 deg reference",
|
||||
"direction": "minimize",
|
||||
"weight": 5.0,
|
||||
"target": 10.0,
|
||||
"units": "nm",
|
||||
"extractor": "zernike_relative",
|
||||
"extractor_config": {
|
||||
"target_subcase": "60",
|
||||
"reference_subcase": "20",
|
||||
"metric": "relative_filtered_rms_nm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mfg_90_optician_workload",
|
||||
"description": "Optician workload at 90 deg polishing orientation (filtered RMS with J1-J3)",
|
||||
"direction": "minimize",
|
||||
"weight": 1.0,
|
||||
"target": 20.0,
|
||||
"units": "nm",
|
||||
"extractor": "zernike",
|
||||
"extractor_config": {
|
||||
"subcase": "90",
|
||||
"metric": "rms_filter_j1to3",
|
||||
"reference_subcase": "20"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"constraints": [
|
||||
{
|
||||
"name": "max_stress",
|
||||
"description": "Maximum von Mises stress in mirror assembly",
|
||||
"type": "upper_bound",
|
||||
"threshold": 10.0,
|
||||
"units": "MPa",
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
|
||||
"zernike_settings": {
|
||||
"n_modes": 50,
|
||||
"filter_low_orders": 4,
|
||||
"displacement_unit": "mm",
|
||||
"subcases": ["1", "2", "3", "4"],
|
||||
"subcase_labels": {"1": "90deg", "2": "20deg", "3": "40deg", "4": "60deg"},
|
||||
"reference_subcase": "2",
|
||||
"polishing_subcase": "1",
|
||||
"output_full_coefficients": true,
|
||||
"_note": "Subcase mapping matches NX: 1=90deg, 2=20deg(ref), 3=40deg, 4=60deg"
|
||||
},
|
||||
|
||||
"optimization_settings": {
|
||||
"n_trials": 100,
|
||||
"n_fea_trials": 40,
|
||||
"n_neural_trials": 500,
|
||||
"sampler": "TPE",
|
||||
"seed": 42,
|
||||
"n_startup_trials": 15,
|
||||
"tpe_n_ei_candidates": 150,
|
||||
"tpe_multivariate": true,
|
||||
"objective_strategy": "weighted_sum",
|
||||
"objective_direction": "minimize"
|
||||
},
|
||||
|
||||
"surrogate_settings": {
|
||||
"enabled": true,
|
||||
"model_type": "ParametricZernikePredictor",
|
||||
"training_config": {
|
||||
"hidden_channels": 128,
|
||||
"num_layers": 4,
|
||||
"learning_rate": 0.001,
|
||||
"epochs": 200,
|
||||
"batch_size": 8,
|
||||
"train_split": 0.8
|
||||
},
|
||||
"outputs": {
|
||||
"description": "50 Zernike coefficients x 4 subcases = 200 outputs",
|
||||
"coefficients_per_subcase": 50,
|
||||
"subcases": ["20", "40", "60", "90"]
|
||||
}
|
||||
},
|
||||
|
||||
"nx_settings": {
|
||||
"nx_install_path": "C:\\Program Files\\Siemens\\NX2506",
|
||||
"model_dir": "C:\\Users\\Antoine\\CADTOMASTE\\Atomizer\\M1-Gigabit\\Latest",
|
||||
"sim_file": "ASSY_M1_assyfem1_sim1.sim",
|
||||
"solution_name": "Solution 1",
|
||||
"solve_all_subcases": true,
|
||||
"op2_pattern": "*-solution_1.op2",
|
||||
"op2_timeout_s": 1800,
|
||||
"op2_stable_s": 5,
|
||||
"post_solve_delay_s": 5
|
||||
},
|
||||
|
||||
"output_settings": {
|
||||
"save_zernike_coefficients": true,
|
||||
"save_field_data": true,
|
||||
"generate_reports": true,
|
||||
"archive_results": true
|
||||
}
|
||||
}
|
||||
BIN
studies/m1_mirror_zernike_optimization/2_results/study.db
Normal file
BIN
studies/m1_mirror_zernike_optimization/2_results/study.db
Normal file
Binary file not shown.
134
studies/m1_mirror_zernike_optimization/DASHBOARD.md
Normal file
134
studies/m1_mirror_zernike_optimization/DASHBOARD.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# M1 Mirror Zernike Optimization Dashboard
|
||||
|
||||
## Study Overview
|
||||
|
||||
**Objective**: Optimize telescope primary mirror (M1) support structure to minimize wavefront error across different gravity orientations.
|
||||
|
||||
**Method**: Hybrid FEA + Neural Network acceleration using Zernike polynomial decomposition.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Prepare Model Files
|
||||
|
||||
Copy your NX model files to:
|
||||
```
|
||||
studies/m1_mirror_zernike_optimization/1_setup/model/
|
||||
```
|
||||
|
||||
Required files:
|
||||
- `ASSY_M1.prt` (or your assembly name)
|
||||
- `ASSY_M1_assyfem1.afm`
|
||||
- `ASSY_M1_assyfem1_sim1.sim`
|
||||
- Associated `.fem` and `_i.prt` files
|
||||
|
||||
### 2. Run FEA Trials (Build Training Data)
|
||||
|
||||
```bash
|
||||
cd studies/m1_mirror_zernike_optimization
|
||||
python run_optimization.py --run --trials 40
|
||||
```
|
||||
|
||||
This will:
|
||||
- Run ~40 FEA trials (10-15 min each = ~8-10 hours)
|
||||
- Extract 50 Zernike coefficients for each subcase (20/40/60/90 deg)
|
||||
- Store all data in Optuna database
|
||||
|
||||
### 3. Train Neural Surrogate
|
||||
|
||||
```bash
|
||||
python run_optimization.py --train-surrogate
|
||||
```
|
||||
|
||||
Trains MLP to predict 200 outputs (50 coefficients x 4 subcases) from design variables.
|
||||
|
||||
### 4. Run Neural-Accelerated Optimization
|
||||
|
||||
```bash
|
||||
python run_optimization.py --run --trials 1000 --enable-nn
|
||||
```
|
||||
|
||||
1000 trials in ~seconds!
|
||||
|
||||
### 5. View Results
|
||||
|
||||
**Optuna Dashboard:**
|
||||
```bash
|
||||
optuna-dashboard sqlite:///2_results/study.db --port 8081
|
||||
```
|
||||
Open http://localhost:8081
|
||||
|
||||
## Design Variables
|
||||
|
||||
| Variable | Range | Baseline | Units | Status |
|
||||
|----------|-------|----------|-------|--------|
|
||||
| whiffle_min | 35-55 | 40.55 | mm | **Enabled** |
|
||||
| whiffle_outer_to_vertical | 68-80 | 75.67 | deg | **Enabled** |
|
||||
| inner_circular_rib_dia | 480-620 | 534.00 | mm | **Enabled** |
|
||||
| whiffle_triangle_closeness | 50-65 | 60.00 | mm | Disabled |
|
||||
| blank_backface_angle | 3.5-5.0 | 4.23 | deg | Disabled |
|
||||
| lateral_inner_angle | 25-28.5 | 26.79 | deg | Disabled |
|
||||
| lateral_outer_angle | 13-17 | 14.64 | deg | Disabled |
|
||||
| lateral_outer_pivot | 9-12 | 10.40 | mm | Disabled |
|
||||
| lateral_inner_pivot | 9-12 | 10.07 | mm | Disabled |
|
||||
| lateral_middle_pivot | 18-23 | 20.73 | mm | Disabled |
|
||||
| lateral_closeness | 9.5-12.5 | 11.02 | mm | Disabled |
|
||||
|
||||
Edit `1_setup/optimization_config.json` to enable/disable variables.
|
||||
|
||||
## Objectives
|
||||
|
||||
| Objective | Weight | Target | Description |
|
||||
|-----------|--------|--------|-------------|
|
||||
| rel_filtered_rms_40_vs_20 | 5 | 4 nm | WFE at 40° relative to 20° reference |
|
||||
| rel_filtered_rms_60_vs_20 | 5 | 10 nm | WFE at 60° relative to 20° reference |
|
||||
| mfg_90_optician_workload | 1 | 20 nm | Polishing workload at 90° orientation |
|
||||
|
||||
**Strategy**: Weighted sum minimization (normalized by targets)
|
||||
|
||||
## Neural Surrogate Architecture
|
||||
|
||||
- **Input**: Design variables (3-11 depending on enabled)
|
||||
- **Output**: 200 values (50 Zernike coefficients × 4 subcases)
|
||||
- **Architecture**: MLP with 4 layers, 128 hidden units
|
||||
- **Training**: ~40 FEA samples, 200 epochs
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
m1_mirror_zernike_optimization/
|
||||
├── 1_setup/
|
||||
│ ├── optimization_config.json # Configuration
|
||||
│ └── model/ # NX model files (add yours here)
|
||||
├── 2_results/
|
||||
│ ├── study.db # Optuna database
|
||||
│ └── zernike_surrogate/ # Trained neural model
|
||||
│ └── checkpoint_best.pt
|
||||
├── run_optimization.py # Main script
|
||||
└── DASHBOARD.md # This file
|
||||
```
|
||||
|
||||
## Commands Reference
|
||||
|
||||
```bash
|
||||
# Run FEA optimization
|
||||
python run_optimization.py --run --trials 40
|
||||
|
||||
# Train neural surrogate
|
||||
python run_optimization.py --train-surrogate
|
||||
|
||||
# Run with neural acceleration
|
||||
python run_optimization.py --run --trials 1000 --enable-nn
|
||||
|
||||
# Check status
|
||||
python run_optimization.py --status
|
||||
|
||||
# Launch Optuna dashboard
|
||||
optuna-dashboard sqlite:///2_results/study.db --port 8081
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Start small**: Run 5-10 FEA trials first to verify everything works
|
||||
2. **Check Zernike extraction**: Verify OP2 has correct subcases (20/40/60/90)
|
||||
3. **Enable variables gradually**: Start with 3, add more after initial exploration
|
||||
4. **Neural validation**: After finding good neural designs, verify top candidates with FEA
|
||||
429
studies/m1_mirror_zernike_optimization/README.md
Normal file
429
studies/m1_mirror_zernike_optimization/README.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# M1 Mirror Zernike Optimization
|
||||
|
||||
Multi-objective telescope primary mirror support structure optimization using Zernike wavefront error decomposition with neural network acceleration.
|
||||
|
||||
**Created**: 2025-11-28
|
||||
**Protocol**: Protocol 12 (Hybrid FEA/Neural with Zernike)
|
||||
**Status**: Setup Complete - Requires Expression Path Fix
|
||||
|
||||
---
|
||||
|
||||
## 1. Engineering Problem
|
||||
|
||||
### 1.1 Objective
|
||||
|
||||
Optimize the telescope primary mirror (M1) support structure to minimize wavefront error (WFE) across different gravity orientations (zenith angles), ensuring consistent optical performance from 20° to 90° elevation.
|
||||
|
||||
### 1.2 Physical System
|
||||
|
||||
- **Component**: M1 primary mirror assembly with whiffle tree support
|
||||
- **Material**: Borosilicate glass (mirror blank), steel (support structure)
|
||||
- **Loading**: Gravity at multiple zenith angles (20°, 40°, 60°, 90°)
|
||||
- **Boundary Conditions**: Whiffle tree kinematic mount
|
||||
- **Analysis Type**: Linear static multi-subcase (Nastran SOL 101)
|
||||
- **Output**: Surface deformation → Zernike polynomial decomposition
|
||||
|
||||
---
|
||||
|
||||
## 2. Mathematical Formulation
|
||||
|
||||
### 2.1 Objectives
|
||||
|
||||
| Objective | Goal | Weight | Formula | Units | Target |
|
||||
|-----------|------|--------|---------|-------|--------|
|
||||
| rel_filtered_rms_40_vs_20 | minimize | 5.0 | $\sigma_{40/20} = \sqrt{\sum_{j=5}^{50} (Z_j^{rel})^2}$ | nm | 4 nm |
|
||||
| rel_filtered_rms_60_vs_20 | minimize | 5.0 | $\sigma_{60/20} = \sqrt{\sum_{j=5}^{50} (Z_j^{rel})^2}$ | nm | 10 nm |
|
||||
| mfg_90_optician_workload | minimize | 1.0 | $\sigma_{90}^{J4+} = \sqrt{\sum_{j=4}^{50} (Z_j^{rel})^2}$ | nm | 20 nm |
|
||||
|
||||
Where:
|
||||
- $Z_j^{rel}$ = Relative Zernike coefficient (target subcase minus reference)
|
||||
- Filtered RMS excludes J1-J4 (piston, tip, tilt, defocus) - correctable by alignment
|
||||
- Manufacturing workload keeps J4 (defocus) since it represents optician correction effort
|
||||
|
||||
### 2.2 Zernike Decomposition
|
||||
|
||||
The wavefront error $W(r,\theta)$ is decomposed into Zernike polynomials:
|
||||
|
||||
$$W(r,\theta) = \sum_{j=1}^{50} Z_j \cdot P_j(r,\theta)$$
|
||||
|
||||
Where $P_j$ are Noll-indexed Zernike polynomials on the unit disk.
|
||||
|
||||
**WFE from Displacement**:
|
||||
$$W_{nm} = 2 \cdot \delta_z \cdot 10^6$$
|
||||
|
||||
Where $\delta_z$ is the Z-displacement in mm (factor of 2 for reflection).
|
||||
|
||||
### 2.3 Design Variables
|
||||
|
||||
| Parameter | Symbol | Bounds | Baseline | Units | Description |
|
||||
|-----------|--------|--------|----------|-------|-------------|
|
||||
| whiffle_min | $w_{min}$ | [35, 55] | 40.55 | mm | Whiffle tree minimum parameter |
|
||||
| whiffle_outer_to_vertical | $\alpha$ | [68, 80] | 75.67 | deg | Outer support angle to vertical |
|
||||
| inner_circular_rib_dia | $D_{rib}$ | [480, 620] | 534.00 | mm | Inner circular rib diameter |
|
||||
|
||||
**Design Space**:
|
||||
$$\mathbf{x} = [w_{min}, \alpha, D_{rib}]^T \in \mathbb{R}^3$$
|
||||
|
||||
**Additional Variables (Disabled)**:
|
||||
- lateral_inner_angle, lateral_outer_angle (lateral support angles)
|
||||
- lateral_outer_pivot, lateral_inner_pivot, lateral_middle_pivot (pivot positions)
|
||||
- lateral_closeness (lateral support spacing)
|
||||
- whiffle_triangle_closeness (whiffle tree geometry)
|
||||
- blank_backface_angle (mirror blank geometry)
|
||||
|
||||
### 2.4 Objective Strategy
|
||||
|
||||
**Weighted Sum Minimization**:
|
||||
$$J(\mathbf{x}) = \sum_{i=1}^{3} w_i \cdot \frac{f_i(\mathbf{x})}{t_i}$$
|
||||
|
||||
Where:
|
||||
- $w_i$ = weight for objective $i$
|
||||
- $f_i(\mathbf{x})$ = objective value
|
||||
- $t_i$ = target value (normalization)
|
||||
|
||||
---
|
||||
|
||||
## 3. Optimization Algorithm
|
||||
|
||||
### 3.1 TPE Configuration
|
||||
|
||||
| Parameter | Value | Description |
|
||||
|-----------|-------|-------------|
|
||||
| Algorithm | TPE | Tree-structured Parzen Estimator |
|
||||
| Sampler | `TPESampler` | Bayesian optimization |
|
||||
| n_startup_trials | 15 | Random exploration before modeling |
|
||||
| n_ei_candidates | 150 | Expected improvement candidates |
|
||||
| multivariate | true | Model parameter correlations |
|
||||
| Trials | 100 | 40 FEA + neural acceleration |
|
||||
| Seed | 42 | Reproducibility |
|
||||
|
||||
**TPE Properties**:
|
||||
- Models $p(x|y<y^*)$ and $p(x|y \geq y^*)$ separately
|
||||
- Expected Improvement: $EI(x) = \int_{-\infty}^{y^*} (y^* - y) p(y|x) dy$
|
||||
- Handles high-dimensional continuous spaces efficiently
|
||||
|
||||
### 3.2 Return Format
|
||||
|
||||
```python
|
||||
def fea_objective(trial) -> Tuple[float, dict]:
|
||||
# ... simulation and Zernike extraction ...
|
||||
weighted_obj = compute_weighted_objective(objectives, config)
|
||||
return weighted_obj, trial_data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Simulation Pipeline
|
||||
|
||||
### 4.1 Trial Execution Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ TRIAL n EXECUTION │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. OPTUNA SAMPLES (TPE) │
|
||||
│ whiffle_min = trial.suggest_float("whiffle_min", 35, 55) │
|
||||
│ whiffle_outer_to_vertical = trial.suggest_float(..., 68, 80) │
|
||||
│ inner_circular_rib_dia = trial.suggest_float(..., 480, 620) │
|
||||
│ │
|
||||
│ 2. NX PARAMETER UPDATE │
|
||||
│ Module: optimization_engine/solve_simulation.py │
|
||||
│ Target Part: M1_Blank.prt │
|
||||
│ Action: Update expressions with new design values │
|
||||
│ │
|
||||
│ 3. NX SIMULATION (Nastran SOL 101 - 4 Subcases) │
|
||||
│ Module: optimization_engine/solve_simulation.py │
|
||||
│ Input: ASSY_M1_assyfem1_sim1.sim │
|
||||
│ Subcases: 1=20°, 2=40°, 3=60°, 4=90° zenith │
|
||||
│ Output: .dat, .op2, .f06 │
|
||||
│ │
|
||||
│ 4. ZERNIKE EXTRACTION (Displacement-Based) │
|
||||
│ a. Read node coordinates from BDF/DAT │
|
||||
│ b. Read Z-displacements from OP2 for each subcase │
|
||||
│ c. Compute RELATIVE displacement (subcase - reference) │
|
||||
│ d. Convert to WFE: W = 2 * Δδz * 10^6 nm │
|
||||
│ e. Fit 50 Zernike coefficients via least-squares │
|
||||
│ f. Compute filtered RMS (exclude J1-J4) │
|
||||
│ │
|
||||
│ 5. OBJECTIVE COMPUTATION │
|
||||
│ rel_filtered_rms_40_vs_20 ← Zernike RMS (subcase 2 - 1) │
|
||||
│ rel_filtered_rms_60_vs_20 ← Zernike RMS (subcase 3 - 1) │
|
||||
│ mfg_90_optician_workload ← Zernike RMS J4+ (subcase 4 - 1) │
|
||||
│ │
|
||||
│ 6. WEIGHTED SUM │
|
||||
│ J = Σ (weight × objective / target) │
|
||||
│ │
|
||||
│ 7. RETURN TO OPTUNA │
|
||||
│ return weighted_objective │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Multi-Subcase Structure
|
||||
|
||||
| Subcase | Zenith Angle | Role | Description |
|
||||
|---------|--------------|------|-------------|
|
||||
| 1 | 20° | Reference | Near-zenith baseline orientation |
|
||||
| 2 | 40° | Target | Mid-elevation performance |
|
||||
| 3 | 60° | Target | Low-elevation performance |
|
||||
| 4 | 90° | Polishing | Horizontal (manufacturing reference) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Result Extraction Methods
|
||||
|
||||
### 5.1 Zernike Extraction (Displacement-Based Subtraction)
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| **Method** | `extract_zernike_with_relative()` |
|
||||
| **Location** | `run_optimization.py` (inline) |
|
||||
| **Geometry Source** | `.dat` (BDF format) |
|
||||
| **Displacement Source** | `.op2` (OP2 binary) |
|
||||
| **Output** | 50 Zernike coefficients per subcase |
|
||||
|
||||
**Algorithm (Correct Approach - Matches Original Script)**:
|
||||
|
||||
1. **Load Geometry**: Read node coordinates $(X_i, Y_i)$ from BDF
|
||||
2. **Load Displacements**: Read $\delta_{z,i}$ from OP2 for each subcase
|
||||
3. **Compute Relative Displacement** (node-by-node):
|
||||
$$\Delta\delta_{z,i} = \delta_{z,i}^{target} - \delta_{z,i}^{reference}$$
|
||||
4. **Convert to WFE**:
|
||||
$$W_i = 2 \cdot \Delta\delta_{z,i} \cdot 10^6 \text{ nm}$$
|
||||
5. **Fit Zernike** (least-squares on unit disk):
|
||||
$$\min_{\mathbf{Z}} \| \mathbf{W} - \mathbf{P} \mathbf{Z} \|^2$$
|
||||
6. **Compute RMS**:
|
||||
$$\sigma_{filtered} = \sqrt{\sum_{j=5}^{50} Z_j^2}$$
|
||||
|
||||
**Critical Implementation Note**:
|
||||
The relative calculation MUST subtract displacements first, then fit Zernike - NOT subtract Zernike coefficients directly. This matches the original `zernike_Post_Script_NX.py` implementation.
|
||||
|
||||
### 5.2 Code Pattern
|
||||
|
||||
```python
|
||||
from pyNastran.op2.op2 import OP2
|
||||
from pyNastran.bdf.bdf import BDF
|
||||
|
||||
# Read geometry
|
||||
bdf = BDF()
|
||||
bdf.read_bdf(str(bdf_path))
|
||||
node_geo = {nid: node.get_position() for nid, node in bdf.nodes.items()}
|
||||
|
||||
# Read displacements
|
||||
op2 = OP2()
|
||||
op2.read_op2(str(op2_path))
|
||||
|
||||
# Compute relative displacement (node-by-node)
|
||||
for i, nid in enumerate(node_ids):
|
||||
rel_dz = disp_z_target[i] - disp_z_reference[nid]
|
||||
|
||||
# Convert to WFE and fit Zernike
|
||||
rel_wfe_nm = 2.0 * rel_disp_z * 1e6
|
||||
coeffs, R_max = compute_zernike_from_wfe(X, Y, rel_wfe_nm, n_modes=50)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Neural Acceleration (AtomizerField)
|
||||
|
||||
### 6.1 Configuration
|
||||
|
||||
| Setting | Value | Description |
|
||||
|---------|-------|-------------|
|
||||
| `enabled` | `true` | Neural surrogate active |
|
||||
| `model_type` | `ParametricZernikePredictor` | Predicts Zernike coefficients |
|
||||
| `hidden_channels` | 128 | MLP width |
|
||||
| `num_layers` | 4 | MLP depth |
|
||||
| `learning_rate` | 0.001 | Adam optimizer |
|
||||
| `epochs` | 200 | Training iterations |
|
||||
| `batch_size` | 8 | Mini-batch size |
|
||||
| `train_split` | 0.8 | Training fraction |
|
||||
|
||||
### 6.2 Surrogate Model
|
||||
|
||||
**Input**: $\mathbf{x} = [w_{min}, \alpha, D_{rib}]^T \in \mathbb{R}^3$
|
||||
|
||||
**Output**: $\hat{\mathbf{Z}} \in \mathbb{R}^{200}$ (50 coefficients × 4 subcases)
|
||||
|
||||
**Architecture**: Multi-Layer Perceptron
|
||||
```
|
||||
Input(3) → Linear(128) → ReLU → Linear(128) → ReLU →
|
||||
Linear(128) → ReLU → Linear(128) → ReLU → Linear(200)
|
||||
```
|
||||
|
||||
**Training Objective**:
|
||||
$$\mathcal{L} = \frac{1}{N} \sum_{i=1}^{N} \| \mathbf{Z}_i - \hat{\mathbf{Z}}_i \|^2$$
|
||||
|
||||
### 6.3 Training Data Location
|
||||
|
||||
```
|
||||
studies/m1_mirror_zernike_optimization/2_results/zernike_surrogate/
|
||||
├── checkpoint_best.pt # Best model weights
|
||||
├── training_history.json # Loss curves
|
||||
└── validation_metrics.json # R², MAE per coefficient
|
||||
```
|
||||
|
||||
### 6.4 Expected Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| FEA time per trial | 10-15 min |
|
||||
| Neural time per trial | ~10 ms |
|
||||
| Speedup | ~60,000x |
|
||||
| Expected R² | > 0.95 (after 40 samples) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Study File Structure
|
||||
|
||||
```
|
||||
m1_mirror_zernike_optimization/
|
||||
│
|
||||
├── 1_setup/ # INPUT CONFIGURATION
|
||||
│ ├── model/ # NX Model Files (symlinked/referenced)
|
||||
│ │ └── → C:\Users\Antoine\CADTOMASTE\Atomizer\M1-Gigabit\Latest\
|
||||
│ │ ├── ASSY_M1.prt # Top-level assembly
|
||||
│ │ ├── M1_Blank.prt # Mirror blank (EXPRESSIONS HERE)
|
||||
│ │ ├── ASSY_M1_assyfem1.afm # Assembly FEM
|
||||
│ │ ├── ASSY_M1_assyfem1_sim1.sim # Simulation file
|
||||
│ │ └── assy_m1_assyfem1_sim1-solution_1.op2 # Results
|
||||
│ │
|
||||
│ └── optimization_config.json # Study configuration
|
||||
│
|
||||
├── 2_results/ # OUTPUT (auto-generated)
|
||||
│ ├── study.db # Optuna SQLite database
|
||||
│ ├── zernike_surrogate/ # Neural model checkpoints
|
||||
│ └── reports/ # Generated reports
|
||||
│
|
||||
├── run_optimization.py # Main entry point
|
||||
├── DASHBOARD.md # Quick reference
|
||||
└── README.md # This blueprint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Results Location
|
||||
|
||||
After optimization completes, results are stored in `2_results/`:
|
||||
|
||||
| File | Description | Format |
|
||||
|------|-------------|--------|
|
||||
| `study.db` | Optuna database with all trials | SQLite |
|
||||
| `zernike_surrogate/checkpoint_best.pt` | Trained neural model | PyTorch |
|
||||
| `reports/optimization_report.md` | Full results report | Markdown |
|
||||
|
||||
### 8.1 Results Report Contents
|
||||
|
||||
The generated report will contain:
|
||||
|
||||
1. **Optimization Summary** - Best WFE configurations found
|
||||
2. **Zernike Analysis** - Coefficient distributions per subcase
|
||||
3. **Parameter Sensitivity** - Design variable vs WFE relationships
|
||||
4. **Convergence History** - Weighted objective over trials
|
||||
5. **Neural Surrogate Performance** - R² per Zernike mode
|
||||
6. **Recommended Configurations** - Top designs for production
|
||||
|
||||
### 8.2 Zernike-Specific Analysis
|
||||
|
||||
| Mode | Name | Physical Meaning |
|
||||
|------|------|------------------|
|
||||
| J1 | Piston | Constant offset (ignored) |
|
||||
| J2, J3 | Tip/Tilt | Angular misalignment (correctable) |
|
||||
| J4 | Defocus | Power error (correctable) |
|
||||
| J5, J6 | Astigmatism | Cylindrical error |
|
||||
| J7, J8 | Coma | Off-axis aberration |
|
||||
| J9-J11 | Trefoil, Spherical | Higher-order terms |
|
||||
|
||||
---
|
||||
|
||||
## 9. Quick Start
|
||||
|
||||
### Staged Workflow (Recommended)
|
||||
|
||||
```bash
|
||||
cd studies/m1_mirror_zernike_optimization
|
||||
|
||||
# Check current status
|
||||
python run_optimization.py --status
|
||||
|
||||
# Run FEA trials (builds training data)
|
||||
python run_optimization.py --run --trials 40
|
||||
|
||||
# Train neural surrogate
|
||||
python run_optimization.py --train-surrogate
|
||||
|
||||
# Run neural-accelerated optimization
|
||||
python run_optimization.py --run --trials 500 --enable-nn
|
||||
```
|
||||
|
||||
### Stage Descriptions
|
||||
|
||||
| Stage | Command | Purpose | When to Use |
|
||||
|-------|---------|---------|-------------|
|
||||
| **STATUS** | `--status` | Check database, trial count | Anytime |
|
||||
| **RUN** | `--run --trials N` | Run FEA optimization | Initial exploration |
|
||||
| **TRAIN** | `--train-surrogate` | Train neural model | After ~40 FEA trials |
|
||||
| **NEURAL** | `--run --enable-nn` | Fast neural trials | After training |
|
||||
|
||||
### Dashboard Access
|
||||
|
||||
| Dashboard | URL | Purpose |
|
||||
|-----------|-----|---------|
|
||||
| **Optuna Dashboard** | `optuna-dashboard sqlite:///2_results/study.db` | Trial history |
|
||||
|
||||
```bash
|
||||
# Launch Optuna dashboard
|
||||
cd studies/m1_mirror_zernike_optimization
|
||||
optuna-dashboard sqlite:///2_results/study.db --port 8081
|
||||
# Open http://localhost:8081
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Configuration Reference
|
||||
|
||||
**File**: `1_setup/optimization_config.json`
|
||||
|
||||
| Section | Key | Description |
|
||||
|---------|-----|-------------|
|
||||
| `design_variables[]` | 11 parameters | 3 enabled, 8 disabled |
|
||||
| `objectives[]` | 3 WFE metrics | Relative filtered RMS |
|
||||
| `zernike_settings.n_modes` | 50 | Zernike polynomial count |
|
||||
| `zernike_settings.filter_low_orders` | 4 | Exclude J1-J4 |
|
||||
| `zernike_settings.subcases` | ["1","2","3","4"] | OP2 subcase IDs |
|
||||
| `zernike_settings.reference_subcase` | "1" | 20° baseline |
|
||||
| `optimization_settings.n_trials` | 100 | Total FEA trials |
|
||||
| `surrogate_settings.model_type` | ParametricZernikePredictor | Neural architecture |
|
||||
| `nx_settings.model_dir` | M1-Gigabit/Latest | NX model location |
|
||||
| `nx_settings.sim_file` | ASSY_M1_assyfem1_sim1.sim | Simulation file |
|
||||
|
||||
---
|
||||
|
||||
## 11. Known Issues & Solutions
|
||||
|
||||
### 11.1 Expression Update Failure
|
||||
|
||||
**Issue**: NX journal cannot find expressions in assembly FEM.
|
||||
|
||||
**Cause**: Expressions are in component part `M1_Blank.prt`, not in `ASSY_M1_assyfem1`.
|
||||
|
||||
**Solution**: The `solve_simulation.py` journal now searches for `M1_Blank` part to update expressions. If still failing, verify:
|
||||
1. `M1_Blank.prt` is loaded in the assembly
|
||||
2. Expression names match exactly (case-sensitive)
|
||||
3. Part is not read-only
|
||||
|
||||
### 11.2 Subcase Numbering
|
||||
|
||||
**Issue**: OP2 file uses numeric subcases (1,2,3,4) not angle labels (20,40,60,90).
|
||||
|
||||
**Solution**: Config uses `subcases: ["1","2","3","4"]` with `subcase_labels` mapping.
|
||||
|
||||
---
|
||||
|
||||
## 12. References
|
||||
|
||||
- **Noll, R.J.** (1976). Zernike polynomials and atmospheric turbulence. *JOSA*.
|
||||
- **Wilson, R.N.** (2004). *Reflecting Telescope Optics I*. Springer.
|
||||
- **pyNastran Documentation**: BDF/OP2 parsing for FEA post-processing
|
||||
- **Optuna Documentation**: TPE sampler for black-box optimization
|
||||
1377
studies/m1_mirror_zernike_optimization/run_optimization.py
Normal file
1377
studies/m1_mirror_zernike_optimization/run_optimization.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user