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:
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user