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.
|
Run a baseline FEA simulation with current/default parameter values.
|
||||||
|
|
||||||
This creates a 'baseline' trial folder in 2_iterations/ with:
|
Uses the same NXSolver pattern as run_optimization.py:
|
||||||
- All NX model files copied
|
1. NXSolver with use_iteration_folders=True and master_model_dir
|
||||||
- Simulation run with baseline parameters
|
2. create_iteration_folder() to copy model files properly
|
||||||
- Result files (.op2, .f06, .bdf) for extractor testing
|
3. run_simulation() with the copied .sim file
|
||||||
|
|
||||||
Use this to:
|
This ensures all NX file dependencies are handled correctly.
|
||||||
1. Verify the FEA pipeline works before optimization
|
|
||||||
2. Get result files for testing custom extractors
|
|
||||||
3. Validate boundary conditions and loads are correct
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
study_id: Study identifier
|
study_id: Study identifier
|
||||||
@@ -4727,6 +4724,9 @@ async def run_baseline_simulation(study_id: str):
|
|||||||
Returns:
|
Returns:
|
||||||
JSON with baseline run status and paths to result files
|
JSON with baseline run status and paths to result files
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
study_dir = resolve_study_path(study_id)
|
study_dir = resolve_study_path(study_id)
|
||||||
print(f"[run-baseline] study_id={study_id}, study_dir={study_dir}")
|
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}")
|
raise HTTPException(status_code=404, detail=f"No .sim file found in {model_dir}")
|
||||||
|
|
||||||
sim_file = sim_files[0]
|
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 = study_dir / "2_iterations"
|
||||||
iterations_dir.mkdir(exist_ok=True)
|
iterations_dir.mkdir(exist_ok=True)
|
||||||
baseline_dir = iterations_dir / "trial_baseline"
|
|
||||||
|
|
||||||
# Clean up existing baseline if present
|
# Try to run the solver using the same pattern as run_optimization.py
|
||||||
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:
|
try:
|
||||||
from optimization_engine.nx.solver import NXSolver
|
from optimization_engine.nx.solver import NXSolver
|
||||||
from pathlib import Path as P
|
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"
|
spec_file = study_dir / "atomizer_spec.json"
|
||||||
|
config_file = study_dir / "1_setup" / "optimization_config.json"
|
||||||
|
|
||||||
nx_install_path = None
|
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():
|
if spec_file.exists():
|
||||||
with open(spec_file) as f:
|
with open(spec_file) as f:
|
||||||
spec_data = json.load(f)
|
spec_data = json.load(f)
|
||||||
nx_install_path = (
|
nx_settings = spec_data.get("model", {}).get("nx_settings", {})
|
||||||
spec_data.get("model", {}).get("nx_settings", {}).get("nx_install_path")
|
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:
|
if not nx_install_path:
|
||||||
for candidate in [
|
for candidate in [
|
||||||
"C:/Program Files/Siemens/DesigncenterNX2512",
|
"C:/Program Files/Siemens/DesigncenterNX2512",
|
||||||
@@ -4814,28 +4805,69 @@ async def run_baseline_simulation(study_id: str):
|
|||||||
nx_install_path = candidate
|
nx_install_path = candidate
|
||||||
break
|
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(
|
solver = NXSolver(
|
||||||
nx_install_dir=P(nx_install_path) if nx_install_path else None,
|
master_model_dir=str(model_dir), # Source of model files
|
||||||
nastran_version="2512",
|
nx_install_dir=nx_install_path,
|
||||||
|
nastran_version=nastran_version,
|
||||||
timeout=600,
|
timeout=600,
|
||||||
use_journal=True,
|
use_journal=True,
|
||||||
enable_session_management=True,
|
use_iteration_folders=True, # Key: let NXSolver handle file copying
|
||||||
study_name=study_id,
|
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(
|
result = solver.run_simulation(
|
||||||
sim_file=baseline_sim,
|
sim_file=iter_sim_file,
|
||||||
working_dir=baseline_dir,
|
working_dir=iter_folder,
|
||||||
cleanup=False, # Keep all files for inspection
|
cleanup=False, # Keep all files for inspection
|
||||||
expression_updates=None, # Use baseline values
|
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
|
# Find result files
|
||||||
op2_files = list(baseline_dir.glob("*.op2"))
|
op2_files = list(iter_folder.glob("*.op2"))
|
||||||
f06_files = list(baseline_dir.glob("*.f06"))
|
f06_files = list(iter_folder.glob("*.f06"))
|
||||||
bdf_files = list(baseline_dir.glob("*.bdf")) + list(baseline_dir.glob("*.dat"))
|
bdf_files = list(iter_folder.glob("*.bdf")) + list(iter_folder.glob("*.dat"))
|
||||||
log_files = list(baseline_dir.glob("*.log"))
|
log_files = list(iter_folder.glob("*.log"))
|
||||||
|
|
||||||
# Parse Nastran log for specific error messages
|
# Parse Nastran log for specific error messages
|
||||||
error_details = result.get("errors", [])
|
error_details = result.get("errors", [])
|
||||||
@@ -4846,16 +4878,13 @@ async def run_baseline_simulation(study_id: str):
|
|||||||
log_content = f.read()
|
log_content = f.read()
|
||||||
if "Unable to allocate requested memory" in log_content:
|
if "Unable to allocate requested memory" in log_content:
|
||||||
memory_error = True
|
memory_error = True
|
||||||
# Extract memory request info
|
|
||||||
import re
|
|
||||||
|
|
||||||
mem_match = re.search(r"Requested size = (\d+) Gbytes", log_content)
|
mem_match = re.search(r"Requested size = (\d+) Gbytes", log_content)
|
||||||
avail_match = re.search(
|
avail_match = re.search(
|
||||||
r"Physical memory available:\s+(\d+) MB", log_content
|
r"Physical memory available:\s+(\d+) MB", log_content
|
||||||
)
|
)
|
||||||
if mem_match and avail_match:
|
if mem_match and avail_match:
|
||||||
requested = int(mem_match.group(1))
|
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(
|
error_details.append(
|
||||||
f"Nastran memory allocation failed: Requested {requested}GB but only {available:.1f}GB available. "
|
f"Nastran memory allocation failed: Requested {requested}GB but only {available:.1f}GB available. "
|
||||||
"Try closing other applications or reduce memory in nastran.rcf"
|
"Try closing other applications or reduce memory in nastran.rcf"
|
||||||
@@ -4866,9 +4895,9 @@ async def run_baseline_simulation(study_id: str):
|
|||||||
return {
|
return {
|
||||||
"success": result.get("success", False),
|
"success": result.get("success", False),
|
||||||
"study_id": study_id,
|
"study_id": study_id,
|
||||||
"baseline_dir": str(baseline_dir),
|
"baseline_dir": str(iter_folder),
|
||||||
"sim_file": str(baseline_sim),
|
"sim_file": str(iter_sim_file),
|
||||||
"elapsed_time": result.get("elapsed_time"),
|
"elapsed_time": elapsed,
|
||||||
"result_files": {
|
"result_files": {
|
||||||
"op2": [f.name for f in op2_files],
|
"op2": [f.name for f in op2_files],
|
||||||
"f06": [f.name for f in f06_files],
|
"f06": [f.name for f in f06_files],
|
||||||
|
|||||||
Reference in New Issue
Block a user