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:
Antoine
2025-11-28 16:30:15 -05:00
parent 8ee031342a
commit ec5e42d733
16 changed files with 11452 additions and 304 deletions

View File

@@ -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
}
}