From 3229c3134978dd495ca28092ae78d9827b06422f Mon Sep 17 00:00:00 2001 From: Anto01 Date: Tue, 20 Jan 2026 19:06:40 -0500 Subject: [PATCH] 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) --- .../backend/api/routes/optimization.py | 143 +++++++++++------- 1 file changed, 86 insertions(+), 57 deletions(-) diff --git a/atomizer-dashboard/backend/api/routes/optimization.py b/atomizer-dashboard/backend/api/routes/optimization.py index d0e3280c..874d3d26 100644 --- a/atomizer-dashboard/backend/api/routes/optimization.py +++ b/atomizer-dashboard/backend/api/routes/optimization.py @@ -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],