Fix NX solve: backup/restore master model, archive outputs to iterations
NX .sim files store absolute internal references to .fem/.prt files. Copying them to iteration folders breaks these references (Parts.Open returns None). Instead: 1. Backup master model once at study start 2. Restore from backup before each trial (isolation) 3. Solve on master model in-place (NX references intact) 4. Archive solver outputs (OP2/F06) + params.exp to iterations/iterNNN/ 5. params.exp in each iteration: import into NX to recreate any trial iteration_manager.py kept for future use but not wired in.
This commit is contained in:
@@ -159,13 +159,28 @@ class AtomizerNXSolver:
|
||||
self.use_iteration_folders = use_iteration_folders
|
||||
self._iteration = 0
|
||||
|
||||
# Smart iteration manager — handles folder creation, model copies, retention
|
||||
from iteration_manager import IterationManager
|
||||
self.study_dir = Path(__file__).parent.resolve()
|
||||
self._iter_mgr = IterationManager(
|
||||
study_dir=self.study_dir,
|
||||
master_model_dir=self.model_dir,
|
||||
)
|
||||
|
||||
# Iteration output folders (solver outputs + params, NOT model copies)
|
||||
self.iterations_dir = self.study_dir / "iterations"
|
||||
self.iterations_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# One-time backup of master model (restored before each trial for isolation)
|
||||
# NX .sim files store absolute internal references to .fem/.prt — copying
|
||||
# them to iteration folders breaks these references. Instead we solve on
|
||||
# the master model in-place and archive outputs to iteration folders.
|
||||
import shutil
|
||||
self._backup_dir = self.study_dir / "_model_backup"
|
||||
if not self._backup_dir.exists():
|
||||
logger.info("Creating master model backup at %s", self._backup_dir)
|
||||
self._backup_dir.mkdir(parents=True)
|
||||
for f in model_dir.iterdir():
|
||||
if f.is_file():
|
||||
shutil.copy2(f, self._backup_dir / f.name)
|
||||
n_backed = len(list(self._backup_dir.iterdir()))
|
||||
logger.info("Backed up %d model files", n_backed)
|
||||
else:
|
||||
logger.info("Using existing model backup at %s", self._backup_dir)
|
||||
|
||||
# Find the .sim file
|
||||
sim_files = list(model_dir.glob("*.sim"))
|
||||
@@ -281,17 +296,22 @@ class AtomizerNXSolver:
|
||||
trial.hole_count,
|
||||
)
|
||||
|
||||
try:
|
||||
# Step 0: Prepare iteration folder with fresh model copies
|
||||
# All paths resolved to absolute — fixes NX reference issues
|
||||
iter_dir = self._iter_mgr.prepare_iteration(self._iteration)
|
||||
# Create iteration output folder
|
||||
iter_dir = self.iterations_dir / f"iter{self._iteration:03d}"
|
||||
iter_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Sim and prt files are now in the iteration folder
|
||||
sim_file = iter_dir / self.sim_file.name
|
||||
prt_file = iter_dir / self.prt_file.name
|
||||
try:
|
||||
# Step 0: Restore master model from backup (clean state)
|
||||
import shutil
|
||||
import json
|
||||
restored = 0
|
||||
for bf in self._backup_dir.iterdir():
|
||||
if bf.is_file():
|
||||
shutil.copy2(bf, self.model_dir / bf.name)
|
||||
restored += 1
|
||||
logger.info("Restored %d model files from backup", restored)
|
||||
|
||||
# Save trial params to iteration folder
|
||||
import json
|
||||
params_file = iter_dir / "params.json"
|
||||
params_file.write_text(json.dumps({
|
||||
"iteration": self._iteration,
|
||||
@@ -304,11 +324,19 @@ class AtomizerNXSolver:
|
||||
},
|
||||
}, indent=2))
|
||||
|
||||
# Step 2: Run NX journal (update expressions + solve) in iteration folder
|
||||
# All paths are absolute — critical for NX to resolve file references
|
||||
# Also write .exp file to iteration folder (import into NX to recreate)
|
||||
exp_file = iter_dir / "params.exp"
|
||||
with open(exp_file, "w") as f:
|
||||
for name, val in expressions.items():
|
||||
unit = "Constant" if name in ("hole_count",) else "MilliMeter"
|
||||
f.write(f'{name}={val} [{unit}]\n')
|
||||
|
||||
# Step 1: Solve on MASTER model (NX internal references intact)
|
||||
sim_file = self.sim_file
|
||||
prt_file = self.prt_file
|
||||
solve_result = self._nx_solver.run_simulation(
|
||||
sim_file=sim_file,
|
||||
working_dir=iter_dir,
|
||||
working_dir=self.model_dir,
|
||||
expression_updates=expressions,
|
||||
)
|
||||
|
||||
@@ -361,13 +389,22 @@ class AtomizerNXSolver:
|
||||
logger.warning("Stress extraction failed: %s", e)
|
||||
max_vm_stress = float("nan")
|
||||
|
||||
# Step 6: Record results + write summary to iteration folder
|
||||
self._iter_mgr.record_result(
|
||||
self._iteration,
|
||||
mass=mass_kg,
|
||||
displacement=tip_displacement,
|
||||
stress=max_vm_stress,
|
||||
)
|
||||
# Step 6: Archive solver outputs to iteration folder
|
||||
# Copy OP2, F06, and other solver outputs from models/ dir
|
||||
for suffix in (".op2", ".f06", ".log", ".dat"):
|
||||
for src in self.model_dir.glob(f"*{suffix}"):
|
||||
try:
|
||||
shutil.copy2(src, iter_dir / src.name)
|
||||
except Exception as e:
|
||||
logger.warning("Could not archive %s: %s", src.name, e)
|
||||
|
||||
# Copy temp files (mass extraction, etc.)
|
||||
for pattern in ("_temp_*",):
|
||||
for src in self.model_dir.glob(pattern):
|
||||
try:
|
||||
shutil.copy2(src, iter_dir / src.name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Write results summary JSON
|
||||
results_file = iter_dir / "results.json"
|
||||
@@ -380,6 +417,8 @@ class AtomizerNXSolver:
|
||||
"op2_file": op2_path.name if op2_path else None,
|
||||
}, indent=2))
|
||||
|
||||
logger.info("Archived iter%03d: results + solver outputs", self._iteration)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
logger.info(
|
||||
"Trial %d complete: mass=%.2f kg, disp=%.3f mm, stress=%.1f MPa (%.1fs)",
|
||||
@@ -406,13 +445,7 @@ class AtomizerNXSolver:
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Clean up NX solver resources and run final retention."""
|
||||
self._iter_mgr.apply_retention()
|
||||
best = self._iter_mgr.get_best_iterations(3)
|
||||
if best:
|
||||
logger.info("Best iterations: %s",
|
||||
[(f"iter{b.number:03d}", f"{b.mass:.1f}kg",
|
||||
"✓" if b.feasible else "✗") for b in best])
|
||||
"""Clean up NX solver resources."""
|
||||
logger.info("AtomizerNXSolver closed. %d iterations completed.", self._iteration)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user