feat: Add MLP surrogate with Turbo Mode for 100x faster optimization

Neural Acceleration (MLP Surrogate):
- Add run_nn_optimization.py with hybrid FEA/NN workflow
- MLP architecture: 4-layer (64->128->128->64) with BatchNorm/Dropout
- Three workflow modes:
  - --all: Sequential export->train->optimize->validate
  - --hybrid-loop: Iterative Train->NN->Validate->Retrain cycle
  - --turbo: Aggressive single-best validation (RECOMMENDED)
- Turbo mode: 5000 NN trials + 50 FEA validations in ~12 minutes
- Separate nn_study.db to avoid overloading dashboard

Performance Results (bracket_pareto_3obj study):
- NN prediction errors: mass 1-5%, stress 1-4%, stiffness 5-15%
- Found minimum mass designs at boundary (angle~30deg, thick~30mm)
- 100x speedup vs pure FEA exploration

Protocol Operating System:
- Add .claude/skills/ with Bootstrap, Cheatsheet, Context Loader
- Add docs/protocols/ with operations (OP_01-06) and system (SYS_10-14)
- Update SYS_14_NEURAL_ACCELERATION.md with MLP Turbo Mode docs

NX Automation:
- Add optimization_engine/hooks/ for NX CAD/CAE automation
- Add study_wizard.py for guided study creation
- Fix FEM mesh update: load idealized part before UpdateFemodel()

New Study:
- bracket_pareto_3obj: 3-objective Pareto (mass, stress, stiffness)
- 167 FEA trials + 5000 NN trials completed
- Demonstrates full hybrid workflow

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Antoine
2025-12-06 20:01:59 -05:00
parent 0cb2808c44
commit 602560c46a
70 changed files with 31018 additions and 289 deletions

Binary file not shown.

View File

@@ -0,0 +1,146 @@
{
"study_name": "bracket_pareto_3obj",
"description": "Three-objective Pareto optimization: minimize mass, minimize stress, maximize stiffness",
"engineering_context": "Generated by StudyWizard on 2025-12-06 14:43",
"template_info": {
"category": "structural",
"analysis_type": "static",
"typical_applications": [],
"neural_enabled": false
},
"optimization_settings": {
"protocol": "protocol_11_multi",
"n_trials": 100,
"sampler": "NSGAIISampler",
"pruner": null,
"timeout_per_trial": 400
},
"design_variables": [
{
"parameter": "support_angle",
"bounds": [
20,
70
],
"description": "Angle of support arm relative to base",
"units": "degrees"
},
{
"parameter": "tip_thickness",
"bounds": [
30,
60
],
"description": "Thickness at bracket tip where load is applied",
"units": "mm"
}
],
"objectives": [
{
"name": "mass",
"goal": "minimize",
"weight": 1.0,
"description": "Total bracket mass (kg)",
"extraction": {
"action": "extract_mass_from_bdf",
"domain": "result_extraction",
"params": {}
}
},
{
"name": "stress",
"goal": "minimize",
"weight": 1.0,
"description": "Maximum von Mises stress (MPa)",
"extraction": {
"action": "extract_solid_stress",
"domain": "result_extraction",
"params": {
"metric": "max_von_mises"
}
}
},
{
"name": "stiffness",
"goal": "maximize",
"weight": 1.0,
"description": "Structural stiffness = Force/Displacement (N/mm)",
"extraction": {
"action": "extract_displacement",
"domain": "result_extraction",
"params": {
"invert_for_stiffness": true
}
}
}
],
"constraints": [
{
"name": "stress_limit",
"type": "less_than",
"threshold": 300,
"description": "Keep stress below 300 MPa for safety margin",
"extraction": {
"action": "extract_solid_stress",
"domain": "result_extraction",
"params": {}
}
}
],
"simulation": {
"model_file": "Bracket.prt",
"sim_file": "Bracket_sim1.sim",
"fem_file": "Bracket_fem1.fem",
"solver": "nastran",
"analysis_types": [
"static"
],
"solution_name": "Solution 1",
"dat_file": "bracket_sim1-solution_1.dat",
"op2_file": "bracket_sim1-solution_1.op2"
},
"result_extraction": {
"mass": {
"method": "extract_mass_from_bdf",
"extractor_module": "optimization_engine.extractors.bdf_mass_extractor",
"function": "extract_mass_from_bdf",
"output_unit": "kg"
},
"stress": {
"method": "extract_solid_stress",
"extractor_module": "optimization_engine.extractors.extract_von_mises_stress",
"function": "extract_solid_stress",
"output_unit": "MPa"
},
"stiffness": {
"method": "extract_displacement",
"extractor_module": "optimization_engine.extractors.extract_displacement",
"function": "extract_displacement",
"output_unit": "mm"
},
"stress_limit": {
"method": "extract_solid_stress",
"extractor_module": "optimization_engine.extractors.extract_von_mises_stress",
"function": "extract_solid_stress",
"output_unit": "MPa"
}
},
"reporting": {
"generate_plots": true,
"save_incremental": true,
"llm_summary": true,
"generate_pareto_front": true
},
"neural_acceleration": {
"enabled": true,
"min_training_points": 50,
"auto_train": true,
"epochs": 300,
"validation_split": 0.2,
"nn_trials": 1000,
"validate_top_n": 10,
"model_file": "surrogate_best.pt",
"separate_nn_database": true,
"description": "NN results stored in nn_study.db to avoid overloading dashboard"
}
}

View File

@@ -0,0 +1,5 @@
{
"workflow_id": "bracket_pareto_3obj_workflow",
"description": "Workflow for bracket_pareto_3obj",
"steps": []
}

View File

@@ -0,0 +1,228 @@
{
"phase": "nn_optimization",
"timestamp": "2025-12-06T19:05:54.740375",
"n_trials": 1000,
"n_pareto": 661,
"best_candidates": [
{
"params": {
"support_angle": 38.72700594236812,
"tip_thickness": 58.52142919229749
},
"nn_objectives": [
0.15462589263916016,
90.49411010742188,
-19956.513671875
]
},
{
"params": {
"support_angle": 56.59969709057025,
"tip_thickness": 47.959754525911094
},
"nn_objectives": [
0.1316341757774353,
80.95538330078125,
-15403.2138671875
]
},
{
"params": {
"support_angle": 27.800932022121827,
"tip_thickness": 34.67983561008608
},
"nn_objectives": [
0.1059565469622612,
75.57935333251953,
-8278.44921875
]
},
{
"params": {
"support_angle": 50.05575058716044,
"tip_thickness": 51.242177333881365
},
"nn_objectives": [
0.13515426218509674,
73.69579315185547,
-15871.068359375
]
},
{
"params": {
"support_angle": 29.09124836035503,
"tip_thickness": 35.50213529560301
},
"nn_objectives": [
0.10616718232631683,
75.49954986572266,
-8333.7919921875
]
},
{
"params": {
"support_angle": 41.59725093210579,
"tip_thickness": 38.736874205941255
},
"nn_objectives": [
0.10606641322374344,
77.42456817626953,
-8482.6328125
]
},
{
"params": {
"support_angle": 50.59264473611897,
"tip_thickness": 34.18481581956125
},
"nn_objectives": [
0.11001653969287872,
78.32686614990234,
-9909.66015625
]
},
{
"params": {
"support_angle": 34.60723242676091,
"tip_thickness": 40.99085529881075
},
"nn_objectives": [
0.11470890045166016,
71.76973724365234,
-10232.564453125
]
},
{
"params": {
"support_angle": 42.8034992108518,
"tip_thickness": 53.55527884179041
},
"nn_objectives": [
0.1554829478263855,
89.65568542480469,
-20128.802734375
]
},
{
"params": {
"support_angle": 49.620728443102124,
"tip_thickness": 31.393512381599933
},
"nn_objectives": [
0.10854113101959229,
78.32325744628906,
-9371.779296875
]
},
{
"params": {
"support_angle": 50.37724259507192,
"tip_thickness": 35.115723710618745
},
"nn_objectives": [
0.11040062457323074,
78.3082275390625,
-10054.8271484375
]
},
{
"params": {
"support_angle": 68.28160165372796,
"tip_thickness": 54.25192044349383
},
"nn_objectives": [
0.15124832093715668,
83.46127319335938,
-19232.740234375
]
},
{
"params": {
"support_angle": 35.23068845866854,
"tip_thickness": 32.93016342019152
},
"nn_objectives": [
0.10423046350479126,
77.35694122314453,
-7934.9453125
]
},
{
"params": {
"support_angle": 47.33551396716398,
"tip_thickness": 35.54563366576581
},
"nn_objectives": [
0.10879749059677124,
78.18163299560547,
-9440.0771484375
]
},
{
"params": {
"support_angle": 68.47923138822793,
"tip_thickness": 53.253984700833435
},
"nn_objectives": [
0.14725860953330994,
82.43916320800781,
-18467.29296875
]
},
{
"params": {
"support_angle": 66.97494707820945,
"tip_thickness": 56.844820512829465
},
"nn_objectives": [
0.15847891569137573,
86.1897201538086,
-20743.28515625
]
},
{
"params": {
"support_angle": 49.89499894055426,
"tip_thickness": 57.6562270506935
},
"nn_objectives": [
0.1606408655643463,
90.43415832519531,
-21159.50390625
]
},
{
"params": {
"support_angle": 24.424625102595975,
"tip_thickness": 35.87948587257436
},
"nn_objectives": [
0.10864812880754471,
73.66149139404297,
-8813.439453125
]
},
{
"params": {
"support_angle": 39.4338644844741,
"tip_thickness": 38.14047095321688
},
"nn_objectives": [
0.10515307635068893,
77.20490264892578,
-8183.75244140625
]
},
{
"params": {
"support_angle": 55.34286719238086,
"tip_thickness": 51.87021504122962
},
"nn_objectives": [
0.14633406698703766,
79.53317260742188,
-18268.1171875
]
}
]
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
{
"mode": "turbo",
"total_nn_trials": 5000,
"fea_validations": 50,
"time_minutes": 12.065277910232544,
"best_solutions": [
{
"iteration": 31,
"params": {
"support_angle": 31.847281190596824,
"tip_thickness": 32.91164052283733
},
"fea": [
0.10370742238857288,
75.331484375,
-7673.294824045775
],
"nn_error": [
1.0860589212456762,
1.8689438405308587
]
},
{
"iteration": 32,
"params": {
"support_angle": 35.78134982929724,
"tip_thickness": 35.42681622195606
},
"fea": [
0.10953498495777715,
74.246125,
-9104.355438099408
],
"nn_error": [
5.9983784586009286,
3.442366247886034
]
},
{
"iteration": 33,
"params": {
"support_angle": 30.994512918956225,
"tip_thickness": 31.052314916198533
},
"fea": [
0.0998217013424325,
77.4071796875,
-6775.567320757415
],
"nn_error": [
2.62213154769254,
0.6237176551876354
]
},
{
"iteration": 34,
"params": {
"support_angle": 33.099819835866754,
"tip_thickness": 32.89301733006174
},
"fea": [
0.10396239429164271,
75.584921875,
-7760.270535172856
],
"nn_error": [
1.3055871373511414,
1.7371954997844847
]
},
{
"iteration": 35,
"params": {
"support_angle": 30.898541287011337,
"tip_thickness": 34.418250550014
},
"fea": [
0.1065015994297987,
74.408234375,
-8241.342422091839
],
"nn_error": [
2.9174895410063533,
2.2559274228984143
]
},
{
"iteration": 36,
"params": {
"support_angle": 33.473891105805734,
"tip_thickness": 34.16062542894516
},
"fea": [
0.10656349355439027,
75.102046875,
-8326.35651590611
],
"nn_error": [
3.6174682481860545,
2.1680046671133515
]
},
{
"iteration": 37,
"params": {
"support_angle": 31.876112833251945,
"tip_thickness": 32.64558622955443
},
"fea": [
0.10316854746371616,
76.0821640625,
-7551.884666556311
],
"nn_error": [
0.616586592199277,
0.9385311503281267
]
},
{
"iteration": 38,
"params": {
"support_angle": 30.714982000638024,
"tip_thickness": 30.67768874508055
},
"fea": [
0.09900839247305124,
77.738234375,
-6613.818689996269
],
"nn_error": [
3.445733195248999,
1.0253383054399168
]
},
{
"iteration": 39,
"params": {
"support_angle": 28.913554019167456,
"tip_thickness": 30.483198120379658
},
"fea": [
0.09815608468915514,
77.3044140625,
-6401.798601024496
],
"nn_error": [
4.31900669557528,
0.6715572168522086
]
},
{
"iteration": 40,
"params": {
"support_angle": 30.64103130907421,
"tip_thickness": 32.225435935347505
},
"fea": [
0.10203815917423766,
76.404703125,
-7263.383668463729
],
"nn_error": [
0.5053920341375967,
0.3872153898156662
]
},
{
"iteration": 41,
"params": {
"support_angle": 25.379887341054648,
"tip_thickness": 31.7995059368559
},
"fea": [
0.09989812757495894,
76.9576796875,
-6664.024314617181
],
"nn_error": [
4.447284090430112,
1.5796573759898327
]
},
{
"iteration": 42,
"params": {
"support_angle": 31.731587709716017,
"tip_thickness": 30.897825980216872
},
"fea": [
0.09972626857174226,
77.77390625,
-6787.919099905275
],
"nn_error": [
3.6536017763654174,
1.4414725087041111
]
},
{
"iteration": 43,
"params": {
"support_angle": 33.10878057556627,
"tip_thickness": 33.355298773540355
},
"fea": [
0.1048663080654111,
75.480953125,
-7947.3954282813875
],
"nn_error": [
1.1127142382050441,
1.24740755881399
]
},
{
"iteration": 44,
"params": {
"support_angle": 33.486603646649684,
"tip_thickness": 30.362623804600066
},
"fea": [
0.09923041195413426,
79.016015625,
-6713.039943213783
],
"nn_error": [
4.287407722991723,
2.630846755256295
]
},
{
"iteration": 45,
"params": {
"support_angle": 28.114078180607912,
"tip_thickness": 31.737991396793802
},
"fea": [
0.10039508543743812,
77.6226171875,
-6820.132648794927
],
"nn_error": [
3.5140537947946973,
1.8965874116002928
]
},
{
"iteration": 46,
"params": {
"support_angle": 32.00933223521479,
"tip_thickness": 30.3146054439274
},
"fea": [
0.09865586146399362,
78.773390625,
-6537.562541889428
],
"nn_error": [
4.747051326710379,
2.548631636595247
]
},
{
"iteration": 47,
"params": {
"support_angle": 33.13530006102697,
"tip_thickness": 33.39675764700238
},
"fea": [
0.10495349474799269,
75.4744296875,
-7967.975581083746
],
"nn_error": [
1.1881318255229905,
1.2499923821726795
]
},
{
"iteration": 48,
"params": {
"support_angle": 31.37280375169122,
"tip_thickness": 32.20022793873885
},
"fea": [
0.10217431187937046,
76.5387421875,
-7300.86967873889
],
"nn_error": [
1.4111097241955246,
0.18087978882019146
]
},
{
"iteration": 49,
"params": {
"support_angle": 31.633966114017845,
"tip_thickness": 30.14620749968385
},
"fea": [
0.0982228321492226,
78.6505,
-6436.600331762441
],
"nn_error": [
5.183933182520313,
2.4268434241418446
]
},
{
"iteration": 50,
"params": {
"support_angle": 30.835096541574387,
"tip_thickness": 31.83135554844258
},
"fea": [
0.10131094537705086,
76.825890625,
-7117.327055357855
],
"nn_error": [
2.2561942677161455,
0.5555181135021817
]
}
]
}

View File

@@ -0,0 +1,221 @@
{
"timestamp": "2025-12-06T19:08:19.427388",
"n_validated": 10,
"average_errors_percent": {
"mass": 3.718643367122823,
"stress": 2.020364475341075,
"stiffness": 7.782164972196007
},
"results": [
{
"params": {
"support_angle": 38.72700594236812,
"tip_thickness": 58.52142919229749
},
"nn_objectives": [
0.15462589263916016,
90.49411010742188,
-19956.513671875
],
"fea_objectives": [
0.1594800904665372,
89.4502578125,
-20960.59592691965
],
"errors_percent": [
3.0437641546206433,
1.1669639869679682,
4.790332577114896
]
},
{
"params": {
"support_angle": 56.59969709057025,
"tip_thickness": 47.959754525911094
},
"nn_objectives": [
0.1316341757774353,
80.95538330078125,
-15403.2138671875
],
"fea_objectives": [
0.1370984916826118,
80.2043046875,
-16381.0655256764
],
"errors_percent": [
3.9856863763509414,
0.9364567353431696,
5.969402032829749
]
},
{
"params": {
"support_angle": 27.800932022121827,
"tip_thickness": 34.67983561008608
},
"nn_objectives": [
0.1059565469622612,
75.57935333251953,
-8278.44921875
],
"fea_objectives": [
0.10630918746092984,
75.5471015625,
-8142.120566330409
],
"errors_percent": [
0.3317121568615468,
0.0426909429382223,
1.6743629784032203
]
},
{
"params": {
"support_angle": 50.05575058716044,
"tip_thickness": 51.242177333881365
},
"nn_objectives": [
0.13515426218509674,
73.69579315185547,
-15871.068359375
],
"fea_objectives": [
0.14318368576930707,
73.3545859375,
-17662.840771637857
],
"errors_percent": [
5.607778247269787,
0.46514776137674774,
10.14430484557161
]
},
{
"params": {
"support_angle": 29.09124836035503,
"tip_thickness": 35.50213529560301
},
"nn_objectives": [
0.10616718232631683,
75.49954986572266,
-8333.7919921875
],
"fea_objectives": [
0.10827058249925942,
72.4169921875,
-8632.595914157022
],
"errors_percent": [
1.9427254609597853,
4.256677314409008,
3.461344941207066
]
},
{
"params": {
"support_angle": 41.59725093210579,
"tip_thickness": 38.736874205941255
},
"nn_objectives": [
0.10606641322374344,
77.42456817626953,
-8482.6328125
],
"fea_objectives": [
0.11718762744364532,
75.1669609375,
-11092.555729424334
],
"errors_percent": [
9.490092480326041,
3.0034568520692204,
23.52859864387429
]
},
{
"params": {
"support_angle": 50.59264473611897,
"tip_thickness": 34.18481581956125
},
"nn_objectives": [
0.11001653969287872,
78.32686614990234,
-9909.66015625
],
"fea_objectives": [
0.11190565081078178,
76.7876328125,
-10422.469553635548
],
"errors_percent": [
1.6881284405354184,
2.0045328668496007,
4.920229267608377
]
},
{
"params": {
"support_angle": 34.60723242676091,
"tip_thickness": 40.99085529881075
},
"nn_objectives": [
0.11470890045166016,
71.76973724365234,
-10232.564453125
],
"fea_objectives": [
0.12047991649273775,
70.5054453125,
-11692.113952912616
],
"errors_percent": [
4.790023274481149,
1.7931833854089492,
12.483195987189537
]
},
{
"params": {
"support_angle": 42.8034992108518,
"tip_thickness": 53.55527884179041
},
"nn_objectives": [
0.1554829478263855,
89.65568542480469,
-20128.802734375
],
"fea_objectives": [
0.14802894076279258,
92.6986484375,
-18351.580922756133
],
"errors_percent": [
5.03550658755136,
3.282640107473584,
9.684298149022656
]
},
{
"params": {
"support_angle": 49.620728443102124,
"tip_thickness": 31.393512381599933
},
"nn_objectives": [
0.10854113101959229,
78.32325744628906,
-9371.779296875
],
"fea_objectives": [
0.107178869906846,
75.856484375,
-9263.802242979662
],
"errors_percent": [
1.2710164922715559,
3.2518948005742834,
1.1655802991386794
]
}
]
}

View File

@@ -0,0 +1,60 @@
# Model Introspection Report
**Study**: bracket_pareto_3obj
**Generated**: 2025-12-06 14:43
**Introspection Version**: 1.0
---
## 1. Files Discovered
| Type | File | Status |
|------|------|--------|
| Part (.prt) | Bracket.prt | ✓ Found |
| Simulation (.sim) | Bracket_sim1.sim | ✓ Found |
| FEM (.fem) | Bracket_fem1.fem | ✓ Found |
---
## 2. Expressions (Potential Design Variables)
*Run introspection to discover expressions.*
---
## 3. Solutions
*Run introspection to discover solutions.*
---
## 4. Available Results
| Result Type | Available | Subcases |
|-------------|-----------|----------|
| Displacement | ? | - |
| Stress | ? | - |
| SPC Forces | ? | - |
---
## 5. Optimization Configuration
### Selected Design Variables
- `support_angle`: [20, 70] degrees
- `tip_thickness`: [30, 60] mm
### Selected Objectives
- Minimize `mass` using `extract_mass_from_bdf`
- Minimize `stress` using `extract_solid_stress`
- Maximize `stiffness` using `extract_displacement`
### Selected Constraints
- `stress_limit` less_than 300 MPa
---
*Ready to create optimization study? Run `python run_optimization.py --discover` to proceed.*

View File

@@ -0,0 +1,130 @@
# bracket_pareto_3obj
Three-objective Pareto optimization: minimize mass, minimize stress, maximize stiffness
**Generated**: 2025-12-06 14:43
**Protocol**: Multi-Objective NSGA-II
**Trials**: 100
---
## 1. Engineering Problem
Three-objective Pareto optimization: minimize mass, minimize stress, maximize stiffness
---
## 2. Mathematical Formulation
### Design Variables
| Parameter | Bounds | Units | Description |
|-----------|--------|-------|-------------|
| `support_angle` | [20, 70] | degrees | Angle of support arm relative to base |
| `tip_thickness` | [30, 60] | mm | Thickness at bracket tip where load is applied |
### Objectives
| Objective | Goal | Extractor | Weight |
|-----------|------|-----------|--------|
| mass | minimize | `extract_mass_from_bdf` | 1.0 |
| stress | minimize | `extract_solid_stress` | 1.0 |
| stiffness | maximize | `extract_displacement` | 1.0 |
### Constraints
| Constraint | Type | Threshold | Units |
|------------|------|-----------|-------|
| stress_limit | less_than | 300 | MPa |
---
## 3. Optimization Algorithm
- **Protocol**: protocol_11_multi
- **Sampler**: NSGAIISampler
- **Trials**: 100
- **Neural Acceleration**: Disabled
---
## 4. Simulation Pipeline
```
Design Variables → NX Expression Update → Nastran Solve → Result Extraction → Objective Evaluation
```
---
## 5. Result Extraction Methods
| Result | Extractor | Source |
|--------|-----------|--------|
| mass | `extract_mass_from_bdf` | OP2/DAT |
| stress | `extract_solid_stress` | OP2/DAT |
| stiffness | `extract_displacement` | OP2/DAT |
---
## 6. Study File Structure
```
bracket_pareto_3obj/
├── 1_setup/
│ ├── model/
│ │ ├── Bracket.prt
│ │ ├── Bracket_sim1.sim
│ │ └── Bracket_fem1.fem
│ ├── optimization_config.json
│ └── workflow_config.json
├── 2_results/
│ ├── study.db
│ └── optimization.log
├── run_optimization.py
├── reset_study.py
├── README.md
├── STUDY_REPORT.md
└── MODEL_INTROSPECTION.md
```
---
## 7. Quick Start
```bash
# 1. Discover model outputs
python run_optimization.py --discover
# 2. Validate setup with single trial
python run_optimization.py --validate
# 3. Run integration test (3 trials)
python run_optimization.py --test
# 4. Run full optimization
python run_optimization.py --run --trials 100
# 5. Resume if interrupted
python run_optimization.py --run --trials 50 --resume
```
---
## 8. Results Location
| File | Description |
|------|-------------|
| `2_results/study.db` | Optuna SQLite database |
| `2_results/optimization.log` | Structured log file |
| `2_results/pareto_front.json` | Pareto-optimal solutions |
---
## 9. References
- [Atomizer Documentation](../../docs/)
- [Protocol protocol_11_multi](../../docs/protocols/system/)
- [Extractor Library](../../docs/protocols/system/SYS_12_EXTRACTOR_LIBRARY.md)

View File

@@ -0,0 +1,60 @@
# Study Report: bracket_pareto_3obj
**Status**: Not Started
**Created**: 2025-12-06 14:43
**Last Updated**: 2025-12-06 14:43
---
## 1. Optimization Progress
| Metric | Value |
|--------|-------|
| Total Trials | 0 |
| Successful Trials | 0 |
| Best Objective | - |
| Duration | - |
---
## 2. Best Solutions
*No optimization runs completed yet.*
---
## 3. Pareto Front (if multi-objective)
*No Pareto front generated yet.*
---
## 4. Design Variable Sensitivity
*Analysis pending optimization runs.*
---
## 5. Constraint Satisfaction
*Analysis pending optimization runs.*
---
## 6. Recommendations
*Recommendations will be added after optimization runs.*
---
## 7. Next Steps
1. [ ] Run `python run_optimization.py --discover`
2. [ ] Run `python run_optimization.py --validate`
3. [ ] Run `python run_optimization.py --test`
4. [ ] Run `python run_optimization.py --run --trials 100`
5. [ ] Analyze results and update this report
---
*Generated by StudyWizard*

View File

@@ -0,0 +1,48 @@
"""
Reset study - Delete results database and logs.
Usage:
python reset_study.py
python reset_study.py --confirm # Skip confirmation
"""
from pathlib import Path
import shutil
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--confirm', action='store_true', help='Skip confirmation')
args = parser.parse_args()
study_dir = Path(__file__).parent
results_dir = study_dir / "2_results"
if not args.confirm:
print(f"This will delete all results in: {results_dir}")
response = input("Are you sure? (y/N): ")
if response.lower() != 'y':
print("Cancelled.")
return
# Delete database files
for f in results_dir.glob("*.db"):
f.unlink()
print(f"Deleted: {f.name}")
# Delete log files
for f in results_dir.glob("*.log"):
f.unlink()
print(f"Deleted: {f.name}")
# Delete JSON results
for f in results_dir.glob("*.json"):
f.unlink()
print(f"Deleted: {f.name}")
print("Study reset complete.")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,245 @@
"""
bracket_pareto_3obj - Optimization Script
============================================================
Three-objective Pareto optimization: minimize mass, minimize stress, maximize stiffness
Protocol: Multi-Objective NSGA-II
Staged Workflow:
----------------
1. DISCOVER: python run_optimization.py --discover
2. VALIDATE: python run_optimization.py --validate
3. TEST: python run_optimization.py --test
4. RUN: python run_optimization.py --run --trials 100
Generated by StudyWizard on 2025-12-06 14:43
"""
from pathlib import Path
import sys
import json
import argparse
from datetime import datetime
from typing import Optional, Tuple, List
# Add parent directory to path
project_root = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(project_root))
import optuna
from optuna.samplers import NSGAIISampler
# Core imports
from optimization_engine.nx_solver import NXSolver
from optimization_engine.logger import get_logger
# Extractor imports
from optimization_engine.extractors.bdf_mass_extractor import extract_mass_from_bdf
from optimization_engine.extractors.extract_displacement import extract_displacement
from optimization_engine.extractors.extract_von_mises_stress import extract_solid_stress
def load_config(config_file: Path) -> dict:
"""Load configuration from JSON file."""
with open(config_file, 'r') as f:
return json.load(f)
def clean_nastran_files(model_dir: Path, logger) -> List[Path]:
"""Remove old Nastran solver output files."""
patterns = ['*.op2', '*.f06', '*.log', '*.f04', '*.pch', '*.DBALL', '*.MASTER', '_temp*.txt']
deleted = []
for pattern in patterns:
for f in model_dir.glob(pattern):
try:
f.unlink()
deleted.append(f)
logger.info(f" Deleted: {f.name}")
except Exception as e:
logger.warning(f" Failed to delete {f.name}: {e}")
return deleted
def objective(trial: optuna.Trial, config: dict, nx_solver: NXSolver,
model_dir: Path, logger) -> Tuple[float, float, float]:
"""
Objective function for optimization.
Returns tuple of objectives for multi-objective optimization.
"""
# Sample design variables
design_vars = {}
for var in config['design_variables']:
param_name = var['parameter']
bounds = var['bounds']
design_vars[param_name] = trial.suggest_float(param_name, bounds[0], bounds[1])
logger.trial_start(trial.number, design_vars)
try:
# Get file paths
sim_file = model_dir / config['simulation']['sim_file']
# Run FEA simulation
result = nx_solver.run_simulation(
sim_file=sim_file,
working_dir=model_dir,
expression_updates=design_vars,
solution_name=config['simulation'].get('solution_name'),
cleanup=True
)
if not result['success']:
logger.trial_failed(trial.number, f"Simulation failed: {result.get('error', 'Unknown')}")
return (float('inf'), float('inf'), float('inf'))
op2_file = result['op2_file']
dat_file = model_dir / config['simulation']['dat_file']
# Extract results
obj_mass = extract_mass_from_bdf(str(dat_file))
logger.info(f' mass: {obj_mass}')
stress_result = extract_solid_stress(op2_file, subcase=1, element_type='chexa')
obj_stress = stress_result.get('max_von_mises', float('inf')) / 1000.0 # kPa -> MPa
logger.info(f' stress: {obj_stress:.2f} MPa')
disp_result = extract_displacement(op2_file, subcase=1)
max_displacement = disp_result['max_displacement']
# For stiffness maximization, use inverse of displacement
applied_force = 1000.0 # N - adjust based on your model
obj_stiffness = -applied_force / max(abs(max_displacement), 1e-6)
logger.info(f' stiffness: {obj_stiffness}')
# Check constraints
feasible = True
constraint_results = {}
# Check stress_limit (stress from OP2 is in kPa for mm/kg units, convert to MPa)
const_stress_limit = extract_solid_stress(op2_file, element_type='chexa')
stress_mpa = const_stress_limit.get('max_von_mises', float('inf')) / 1000.0 # kPa -> MPa
constraint_results['stress_limit'] = stress_mpa
if stress_mpa > 300:
feasible = False
logger.warning(f' Constraint violation: stress_limit = {stress_mpa:.1f} MPa vs 300 MPa')
# Set user attributes
trial.set_user_attr('mass', obj_mass)
trial.set_user_attr('stress', obj_stress)
trial.set_user_attr('stiffness', obj_stiffness)
trial.set_user_attr('feasible', feasible)
objectives = {'mass': obj_mass, 'stress': obj_stress, 'stiffness': obj_stiffness}
logger.trial_complete(trial.number, objectives, constraint_results, feasible)
return (obj_mass, obj_stress, obj_stiffness)
except Exception as e:
logger.trial_failed(trial.number, str(e))
return (float('inf'), float('inf'), float('inf'))
def main():
"""Main optimization workflow."""
parser = argparse.ArgumentParser(description='bracket_pareto_3obj')
stage_group = parser.add_mutually_exclusive_group()
stage_group.add_argument('--discover', action='store_true', help='Discover model outputs')
stage_group.add_argument('--validate', action='store_true', help='Run single validation trial')
stage_group.add_argument('--test', action='store_true', help='Run 3-trial test')
stage_group.add_argument('--run', action='store_true', help='Run optimization')
parser.add_argument('--trials', type=int, default=100, help='Number of trials')
parser.add_argument('--resume', action='store_true', help='Resume existing study')
parser.add_argument('--clean', action='store_true', help='Clean old files first')
args = parser.parse_args()
if not any([args.discover, args.validate, args.test, args.run]):
print("No stage specified. Use --discover, --validate, --test, or --run")
return 1
# Setup paths
study_dir = Path(__file__).parent
config_path = study_dir / "1_setup" / "optimization_config.json"
model_dir = study_dir / "1_setup" / "model"
results_dir = study_dir / "2_results"
results_dir.mkdir(exist_ok=True)
study_name = "bracket_pareto_3obj"
# Initialize
logger = get_logger(study_name, study_dir=results_dir)
config = load_config(config_path)
nx_solver = NXSolver(nastran_version="2506")
if args.clean:
clean_nastran_files(model_dir, logger)
# Run appropriate stage
if args.discover or args.validate or args.test:
# Run limited trials for these stages
n = 1 if args.discover or args.validate else 3
storage = f"sqlite:///{results_dir / 'study_test.db'}"
study = optuna.create_study(
study_name=f"{study_name}_test",
storage=storage,
sampler=NSGAIISampler(population_size=5, seed=42),
directions=['minimize'] * 3,
load_if_exists=False
)
study.optimize(
lambda trial: objective(trial, config, nx_solver, model_dir, logger),
n_trials=n,
show_progress_bar=True
)
logger.info(f"Completed {len(study.trials)} trial(s)")
return 0
# Full optimization run
storage = f"sqlite:///{results_dir / 'study.db'}"
if args.resume:
study = optuna.load_study(
study_name=study_name,
storage=storage,
sampler=NSGAIISampler(population_size=20, seed=42)
)
else:
study = optuna.create_study(
study_name=study_name,
storage=storage,
sampler=NSGAIISampler(population_size=20, seed=42),
directions=['minimize'] * 3,
load_if_exists=True
)
logger.study_start(study_name, args.trials, "NSGAIISampler")
study.optimize(
lambda trial: objective(trial, config, nx_solver, model_dir, logger),
n_trials=args.trials,
show_progress_bar=True
)
n_complete = len([t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE])
logger.study_complete(study_name, len(study.trials), n_complete)
# Report results
pareto_trials = study.best_trials
logger.info(f"\nOptimization Complete!")
logger.info(f"Total trials: {len(study.trials)}")
logger.info(f"Successful: {n_complete}")
return 0
if __name__ == "__main__":
exit(main())