Smart iteration management: full model copies + retention policy
- Each iteration gets full model files in iterations/iterNNN/ (openable in NX) - Retention: keep last 10 + best 3 with full models, strip the rest - Stripped iterations keep solver outputs (OP2, F06, params, results) - All paths resolved to absolute before passing to NX (fixes reference issue) - iteration_manager.py: reusable for future studies
This commit is contained in:
@@ -159,25 +159,13 @@ class AtomizerNXSolver:
|
||||
self.use_iteration_folders = use_iteration_folders
|
||||
self._iteration = 0
|
||||
|
||||
# Iteration outputs go inside the study folder
|
||||
# Smart iteration manager — handles folder creation, model copies, retention
|
||||
from iteration_manager import IterationManager
|
||||
self.study_dir = Path(__file__).parent.resolve()
|
||||
self.iterations_dir = self.study_dir / "iterations"
|
||||
self.iterations_dir.mkdir(parents=True, exist_ok=True)
|
||||
logger.info("Iterations dir: %s", self.iterations_dir)
|
||||
|
||||
# Create one-time backup of master model files (clean state)
|
||||
# Restored before each trial to ensure isolation
|
||||
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 ext in ("*.prt", "*.fem", "*.sim"):
|
||||
for f in model_dir.glob(ext):
|
||||
shutil.copy2(f, self._backup_dir / f.name)
|
||||
logger.info("Backed up %d model files", len(list(self._backup_dir.iterdir())))
|
||||
else:
|
||||
logger.info("Using existing model backup at %s", self._backup_dir)
|
||||
self._iter_mgr = IterationManager(
|
||||
study_dir=self.study_dir,
|
||||
master_model_dir=self.model_dir,
|
||||
)
|
||||
|
||||
# Find the .sim file
|
||||
sim_files = list(model_dir.glob("*.sim"))
|
||||
@@ -293,25 +281,14 @@ class AtomizerNXSolver:
|
||||
trial.hole_count,
|
||||
)
|
||||
|
||||
# Create iteration output folder inside the study
|
||||
iter_dir = self.iterations_dir / f"iter{self._iteration:03d}"
|
||||
iter_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
# Step 0: Restore master model from backup (clean state each trial)
|
||||
# Files stay in models/ so NX references are intact, but content is fresh
|
||||
import shutil
|
||||
restored = 0
|
||||
for backup_file in self._backup_dir.iterdir():
|
||||
dest = self.model_dir / backup_file.name
|
||||
shutil.copy2(backup_file, dest)
|
||||
restored += 1
|
||||
logger.info("Restored %d model files from backup", restored)
|
||||
# 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)
|
||||
|
||||
# Step 1: Solve directly on master model
|
||||
# NX file references stay intact — expressions updated in-place by journal
|
||||
sim_file = self.sim_file
|
||||
prt_file = self.prt_file
|
||||
# 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
|
||||
|
||||
# Save trial params to iteration folder
|
||||
import json
|
||||
@@ -327,10 +304,11 @@ class AtomizerNXSolver:
|
||||
},
|
||||
}, indent=2))
|
||||
|
||||
# Step 2: Run NX journal (update expressions + solve) on master model
|
||||
# Step 2: Run NX journal (update expressions + solve) in iteration folder
|
||||
# All paths are absolute — critical for NX to resolve file references
|
||||
solve_result = self._nx_solver.run_simulation(
|
||||
sim_file=sim_file,
|
||||
working_dir=self.model_dir,
|
||||
working_dir=iter_dir,
|
||||
expression_updates=expressions,
|
||||
)
|
||||
|
||||
@@ -383,8 +361,24 @@ class AtomizerNXSolver:
|
||||
logger.warning("Stress extraction failed: %s", e)
|
||||
max_vm_stress = float("nan")
|
||||
|
||||
# Step 6: Copy solver outputs to iteration folder for archival
|
||||
self._archive_iteration(iter_dir, op2_path, mass_kg, tip_displacement, max_vm_stress)
|
||||
# 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,
|
||||
)
|
||||
|
||||
# Write results summary JSON
|
||||
results_file = iter_dir / "results.json"
|
||||
results_file.write_text(json.dumps({
|
||||
"iteration": self._iteration,
|
||||
"mass_kg": mass_kg,
|
||||
"tip_displacement_mm": tip_displacement,
|
||||
"max_von_mises_mpa": max_vm_stress,
|
||||
"feasible": tip_displacement <= 10.0 and max_vm_stress <= 130.0,
|
||||
"op2_file": op2_path.name if op2_path else None,
|
||||
}, indent=2))
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
logger.info(
|
||||
@@ -411,54 +405,14 @@ class AtomizerNXSolver:
|
||||
iteration_dir=str(iter_dir) if 'iter_dir' in locals() else None,
|
||||
)
|
||||
|
||||
def _archive_iteration(
|
||||
self,
|
||||
iter_dir: Path,
|
||||
op2_path: Path,
|
||||
mass: float,
|
||||
displacement: float,
|
||||
stress: float,
|
||||
) -> None:
|
||||
"""Copy solver outputs to iteration folder for archival.
|
||||
|
||||
Keeps the models/ directory clean — solver outputs go to the study's
|
||||
iterations/ folder. Each iteration gets: OP2, F06, mass file, and
|
||||
a results summary JSON.
|
||||
"""
|
||||
import json
|
||||
import shutil
|
||||
|
||||
# Copy OP2 and F06 files
|
||||
for suffix in [".op2", ".f06", ".log"]:
|
||||
src = op2_path.with_suffix(suffix)
|
||||
if src.exists():
|
||||
try:
|
||||
shutil.copy2(src, iter_dir / src.name)
|
||||
except Exception as e:
|
||||
logger.warning("Could not copy %s: %s", src.name, e)
|
||||
|
||||
# Copy mass temp file if it exists
|
||||
for fname in ["_temp_mass.txt", "_temp_part_properties.json"]:
|
||||
src = self.model_dir / fname
|
||||
if src.exists():
|
||||
try:
|
||||
shutil.copy2(src, iter_dir / fname)
|
||||
except Exception as e:
|
||||
logger.warning("Could not copy %s: %s", fname, e)
|
||||
|
||||
# Write results summary
|
||||
results_file = iter_dir / "results.json"
|
||||
results_file.write_text(json.dumps({
|
||||
"mass_kg": mass,
|
||||
"tip_displacement_mm": displacement,
|
||||
"max_von_mises_mpa": stress,
|
||||
"op2_file": op2_path.name,
|
||||
}, indent=2))
|
||||
|
||||
logger.info("Archived iteration %d to %s", self._iteration, iter_dir.name)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Clean up NX solver resources."""
|
||||
"""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])
|
||||
logger.info("AtomizerNXSolver closed. %d iterations completed.", self._iteration)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user