fix: Rewrite run-baseline to use NXSolver iteration folder pattern

- Use same approach as run_optimization.py with use_iteration_folders=True
- NXSolver.create_iteration_folder() handles proper file copying
- Read NX settings from atomizer_spec.json or optimization_config.json
- Extract Nastran version from NX install path
- Creates iter0 folder for baseline (consistent with optimization numbering)

This fixes the issue where manually copying files didn't preserve
NX file dependency chain (.sim -> .afm -> .fem -> _i.prt -> .prt)
This commit is contained in:
2026-01-20 19:06:40 -05:00
parent 14354a2606
commit 3229c31349

View File

@@ -4711,15 +4711,12 @@ async def run_baseline_simulation(study_id: str):
"""
Run a baseline FEA simulation with current/default parameter values.
This creates a 'baseline' trial folder in 2_iterations/ with:
- All NX model files copied
- Simulation run with baseline parameters
- Result files (.op2, .f06, .bdf) for extractor testing
Uses the same NXSolver pattern as run_optimization.py:
1. NXSolver with use_iteration_folders=True and master_model_dir
2. create_iteration_folder() to copy model files properly
3. run_simulation() with the copied .sim file
Use this to:
1. Verify the FEA pipeline works before optimization
2. Get result files for testing custom extractors
3. Validate boundary conditions and loads are correct
This ensures all NX file dependencies are handled correctly.
Args:
study_id: Study identifier
@@ -4727,6 +4724,9 @@ async def run_baseline_simulation(study_id: str):
Returns:
JSON with baseline run status and paths to result files
"""
import re
import time
try:
study_dir = resolve_study_path(study_id)
print(f"[run-baseline] study_id={study_id}, study_dir={study_dir}")
@@ -4758,52 +4758,43 @@ async def run_baseline_simulation(study_id: str):
raise HTTPException(status_code=404, detail=f"No .sim file found in {model_dir}")
sim_file = sim_files[0]
print(f"[run-baseline] sim_file={sim_file}")
print(f"[run-baseline] sim_file={sim_file.name}")
print(f"[run-baseline] model_dir={model_dir}")
# Create baseline trial folder
# Ensure iterations directory exists
iterations_dir = study_dir / "2_iterations"
iterations_dir.mkdir(exist_ok=True)
baseline_dir = iterations_dir / "trial_baseline"
# Clean up existing baseline if present
if baseline_dir.exists():
import shutil
shutil.rmtree(baseline_dir)
baseline_dir.mkdir()
# Copy all model files to baseline folder
import shutil
model_extensions = [".prt", ".fem", ".afm", ".sim", ".exp"]
copied_files = []
for ext in model_extensions:
for src_file in model_dir.glob(f"*{ext}"):
dst_file = baseline_dir / src_file.name
shutil.copy2(src_file, dst_file)
copied_files.append(src_file.name)
print(f"[run-baseline] Copied {len(copied_files)} files to baseline folder")
# Find the copied sim file in baseline dir
baseline_sim = baseline_dir / sim_file.name
# Try to run the solver
# Try to run the solver using the same pattern as run_optimization.py
try:
from optimization_engine.nx.solver import NXSolver
from pathlib import Path as P
# Try to get NX settings from spec
# Load config/spec for NX settings
spec_file = study_dir / "atomizer_spec.json"
config_file = study_dir / "1_setup" / "optimization_config.json"
nx_install_path = None
sim_file_name = sim_file.name
solution_name = "Solution 1"
# Try to get settings from spec or config
if spec_file.exists():
with open(spec_file) as f:
spec_data = json.load(f)
nx_install_path = (
spec_data.get("model", {}).get("nx_settings", {}).get("nx_install_path")
)
nx_settings = spec_data.get("model", {}).get("nx_settings", {})
nx_install_path = nx_settings.get("nx_install_path")
sim_file_name = nx_settings.get("sim_file", sim_file.name)
solution_name = nx_settings.get("solution_name", "Solution 1")
elif config_file.exists():
with open(config_file) as f:
config_data = json.load(f)
nx_settings = config_data.get("nx_settings", {})
nx_install_path = nx_settings.get("nx_install_path")
sim_file_name = nx_settings.get("sim_file", sim_file.name)
solution_name = nx_settings.get("solution_name", "Solution 1")
# Default to common installation path if not in spec
# Default to common installation path if not in spec/config
if not nx_install_path:
for candidate in [
"C:/Program Files/Siemens/DesigncenterNX2512",
@@ -4814,28 +4805,69 @@ async def run_baseline_simulation(study_id: str):
nx_install_path = candidate
break
# Extract version from path
version_match = re.search(r"NX(\d+)|DesigncenterNX(\d+)", nx_install_path or "")
nastran_version = (
(version_match.group(1) or version_match.group(2)) if version_match else "2512"
)
print(f"[run-baseline] NX install: {nx_install_path}")
print(f"[run-baseline] Nastran version: {nastran_version}")
print(f"[run-baseline] Solution: {solution_name}")
# Create solver with iteration folder support (same as run_optimization.py)
solver = NXSolver(
nx_install_dir=P(nx_install_path) if nx_install_path else None,
nastran_version="2512",
master_model_dir=str(model_dir), # Source of model files
nx_install_dir=nx_install_path,
nastran_version=nastran_version,
timeout=600,
use_journal=True,
enable_session_management=True,
use_iteration_folders=True, # Key: let NXSolver handle file copying
study_name=study_id,
)
print(f"[run-baseline] Starting solver...")
# Create iteration folder (this copies all model files properly)
# Use iteration number 0 for baseline
print(f"[run-baseline] Creating iteration folder...")
iter_folder = solver.create_iteration_folder(
iterations_base_dir=iterations_dir,
iteration_number=0, # baseline = iter0
expression_updates=None, # No expression changes for baseline
)
print(f"[run-baseline] Iteration folder: {iter_folder}")
# Find the sim file in the iteration folder
iter_sim_file = iter_folder / sim_file_name
if not iter_sim_file.exists():
# Try to find any .sim file
sim_files_in_iter = list(iter_folder.glob("*.sim"))
if sim_files_in_iter:
iter_sim_file = sim_files_in_iter[0]
else:
raise FileNotFoundError(f"No .sim file found in {iter_folder}")
print(f"[run-baseline] Running simulation: {iter_sim_file.name}")
t_start = time.time()
result = solver.run_simulation(
sim_file=baseline_sim,
working_dir=baseline_dir,
sim_file=iter_sim_file,
working_dir=iter_folder,
cleanup=False, # Keep all files for inspection
expression_updates=None, # Use baseline values
solution_name=solution_name,
)
elapsed = time.time() - t_start
print(
f"[run-baseline] Simulation completed in {elapsed:.1f}s, success={result.get('success')}"
)
# Find result files
op2_files = list(baseline_dir.glob("*.op2"))
f06_files = list(baseline_dir.glob("*.f06"))
bdf_files = list(baseline_dir.glob("*.bdf")) + list(baseline_dir.glob("*.dat"))
log_files = list(baseline_dir.glob("*.log"))
op2_files = list(iter_folder.glob("*.op2"))
f06_files = list(iter_folder.glob("*.f06"))
bdf_files = list(iter_folder.glob("*.bdf")) + list(iter_folder.glob("*.dat"))
log_files = list(iter_folder.glob("*.log"))
# Parse Nastran log for specific error messages
error_details = result.get("errors", [])
@@ -4846,16 +4878,13 @@ async def run_baseline_simulation(study_id: str):
log_content = f.read()
if "Unable to allocate requested memory" in log_content:
memory_error = True
# Extract memory request info
import re
mem_match = re.search(r"Requested size = (\d+) Gbytes", log_content)
avail_match = re.search(
r"Physical memory available:\s+(\d+) MB", log_content
)
if mem_match and avail_match:
requested = int(mem_match.group(1))
available = int(avail_match.group(1)) / 1024 # Convert to GB
available = int(avail_match.group(1)) / 1024
error_details.append(
f"Nastran memory allocation failed: Requested {requested}GB but only {available:.1f}GB available. "
"Try closing other applications or reduce memory in nastran.rcf"
@@ -4866,9 +4895,9 @@ async def run_baseline_simulation(study_id: str):
return {
"success": result.get("success", False),
"study_id": study_id,
"baseline_dir": str(baseline_dir),
"sim_file": str(baseline_sim),
"elapsed_time": result.get("elapsed_time"),
"baseline_dir": str(iter_folder),
"sim_file": str(iter_sim_file),
"elapsed_time": elapsed,
"result_files": {
"op2": [f.name for f in op2_files],
"f06": [f.name for f in f06_files],