- Fixed drone gimbal optimization to use proper semantic directions - Changed from ['minimize', 'minimize'] to ['minimize', 'maximize'] - Updated Claude skill (v2.0) with Phase 3.3 integration - Added centralized extractor library documentation - Added multi-objective optimization (Protocol 11) section - Added NX multi-solution protocol documentation - Added dashboard integration documentation - Fixed Pareto front degenerate issue with proper NSGA-II configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
6.4 KiB
NX Multi-Solution Solve Protocol
Critical Finding: SolveAllSolutions API Required for Multi-Solution Models
Date: November 23, 2025 Last Updated: November 23, 2025 Protocol: Multi-Solution Nastran Solve Affected Models: Any NX simulation with multiple solutions (e.g., static + modal, thermal + structural)
Problem Statement
When an NX simulation contains multiple solutions (e.g., Solution 1 = Static Analysis, Solution 2 = Modal Analysis), using SolveChainOfSolutions() with Background mode does not wait for all solutions to complete before returning control to Python. This causes:
- Missing OP2 Files: Only the first solution's OP2 file is generated
- Stale Data: Subsequent trials read old OP2 files from previous runs
- Identical Results: All trials show the same values for results from missing solutions
- Silent Failures: No error is raised - the solve completes but files are not written
Example Scenario
Drone Gimbal Arm Optimization:
- Solution 1: Static analysis (stress, displacement)
- Solution 2: Modal analysis (frequency)
Symptoms:
- All 100 trials showed identical frequency (27.476 Hz)
- Only
beam_sim1-solution_1.op2was created beam_sim1-solution_2.op2was never regenerated after Trial 0- Both
.datfiles were written correctly, but solve didn't wait for completion
Root Cause
# WRONG APPROACH (doesn't wait for completion)
psolutions1 = []
solution_idx = 1
while True:
solution_obj_name = f"Solution[Solution {solution_idx}]"
simSolution = simSimulation1.FindObject(solution_obj_name)
if simSolution:
psolutions1.append(simSolution)
solution_idx += 1
else:
break
theCAESimSolveManager.SolveChainOfSolutions(
psolutions1,
NXOpen.CAE.SimSolution.SolveOption.Solve,
NXOpen.CAE.SimSolution.SetupCheckOption.CompleteDeepCheckAndOutputErrors,
NXOpen.CAE.SimSolution.SolveMode.Background # ❌ Returns immediately!
)
Issue: Background mode runs asynchronously and returns control to Python before all solutions finish solving.
Correct Solution
For Solving All Solutions
Use SolveAllSolutions() API with Foreground mode:
# CORRECT APPROACH (waits for completion)
if solution_name:
# Solve specific solution in background mode
solution_obj_name = f"Solution[{solution_name}]"
simSolution1 = simSimulation1.FindObject(solution_obj_name)
psolutions1 = [simSolution1]
numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveChainOfSolutions(
psolutions1,
NXOpen.CAE.SimSolution.SolveOption.Solve,
NXOpen.CAE.SimSolution.SetupCheckOption.CompleteDeepCheckAndOutputErrors,
NXOpen.CAE.SimSolution.SolveMode.Background
)
else:
# Solve ALL solutions using SolveAllSolutions API (Foreground mode)
# This ensures all solutions (static + modal, etc.) complete before returning
print(f"[JOURNAL] Solving all solutions using SolveAllSolutions API (Foreground mode)...")
numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveAllSolutions(
NXOpen.CAE.SimSolution.SolveOption.Solve,
NXOpen.CAE.SimSolution.SetupCheckOption.CompleteCheckAndOutputErrors,
NXOpen.CAE.SimSolution.SolveMode.Foreground, # ✅ Blocks until complete
False
)
Key Differences
| Aspect | SolveChainOfSolutions | SolveAllSolutions |
|---|---|---|
| Manual enumeration | Required (loop through solutions) | Automatic (handles all solutions) |
| Background mode behavior | Returns immediately, async | N/A (Foreground recommended) |
| Foreground mode behavior | Blocks until complete | Blocks until complete ✅ |
| Use case | Specific solution selection | Solve all solutions |
Implementation Location
File: optimization_engine/solve_simulation.py
Lines: 271-295
When to use this protocol:
- When
solution_name=Noneis passed toNXSolver.run_simulation() - Any simulation with multiple solutions that must all complete
- Multi-objective optimization requiring results from different analysis types
Verification Steps
After implementing the fix, verify:
-
Both .dat files are written (one per solution)
beam_sim1-solution_1.dat # Static analysis beam_sim1-solution_2.dat # Modal analysis -
Both .op2 files are created with updated timestamps
beam_sim1-solution_1.op2 # Contains stress, displacement beam_sim1-solution_2.op2 # Contains eigenvalues, mode shapes -
Results are unique per trial - check that frequency values vary across trials
-
Journal log shows:
[JOURNAL] Solving all solutions using SolveAllSolutions API (Foreground mode)... [JOURNAL] Solve completed! [JOURNAL] Solutions solved: 2
Related Issues Fixed
- All trials showing identical frequency: Fixed by ensuring modal solution runs
- Only one data point in dashboard: Fixed by all trials succeeding
- Parallel coordinates with NaN: Fixed by having complete data from all solutions
References
- User's Example:
nx_journals/user_generated_journals/journal_solve_all_solution.py(line 27) - NX Open Documentation: SimSolveManager.SolveAllSolutions() method
- Implementation:
optimization_engine/solve_simulation.py
Best Practices
- Always use Foreground mode when solving all solutions
- Verify OP2 timestamp changes to ensure fresh solves
- Check solve counts in journal output to confirm both solutions ran
- Test with 5 trials before running large optimizations
- Monitor unique frequency values as a smoke test for multi-solution models
Example Use Cases
✅ Correct Usage
# Multi-objective optimization with static + modal
result = nx_solver.run_simulation(
sim_file=sim_file,
working_dir=model_dir,
expression_updates=design_vars,
solution_name=None # Solve ALL solutions
)
❌ Incorrect Usage (Don't Do This)
# Running modal separately - inefficient and error-prone
result1 = nx_solver.run_simulation(..., solution_name="Solution 1") # Static
result2 = nx_solver.run_simulation(..., solution_name="Solution 2") # Modal
# This doubles the solve time and requires managing two result objects
Status: ✅ Implemented and Verified Impact: Critical for all multi-solution optimization workflows