Iteration archival: solve on master model, archive outputs to studies/iterations/iterNNN/
- Each iteration gets: params.json, results.json, OP2, F06, mass files - Model directory stays clean (no solver output buildup) - Study folder is self-contained with full trial history
This commit is contained in:
@@ -153,15 +153,18 @@ class AtomizerNXSolver:
|
|||||||
if not model_dir.exists():
|
if not model_dir.exists():
|
||||||
raise FileNotFoundError(f"Model directory not found: {model_dir}")
|
raise FileNotFoundError(f"Model directory not found: {model_dir}")
|
||||||
|
|
||||||
self.model_dir = model_dir
|
self.model_dir = model_dir.resolve()
|
||||||
self.nx_version = nx_version
|
self.nx_version = nx_version
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.use_iteration_folders = use_iteration_folders
|
self.use_iteration_folders = use_iteration_folders
|
||||||
self._iteration = 0
|
self._iteration = 0
|
||||||
|
|
||||||
# Set up iteration base directory
|
# Iteration outputs go inside the study folder, not models/
|
||||||
self.iterations_dir = model_dir.parent / "2_iterations"
|
# study_dir = the directory where run_doe.py lives
|
||||||
|
self.study_dir = Path(__file__).parent.resolve()
|
||||||
|
self.iterations_dir = self.study_dir / "iterations"
|
||||||
self.iterations_dir.mkdir(parents=True, exist_ok=True)
|
self.iterations_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
logger.info("Iterations dir: %s", self.iterations_dir)
|
||||||
|
|
||||||
# Find the .sim file
|
# Find the .sim file
|
||||||
sim_files = list(model_dir.glob("*.sim"))
|
sim_files = list(model_dir.glob("*.sim"))
|
||||||
@@ -277,25 +280,34 @@ class AtomizerNXSolver:
|
|||||||
trial.hole_count,
|
trial.hole_count,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
# Create iteration output folder inside the study
|
||||||
# Step 1: Create iteration folder with fresh model copies + .exp file
|
iter_dir = self.iterations_dir / f"iter{self._iteration:03d}"
|
||||||
if self.use_iteration_folders:
|
iter_dir.mkdir(parents=True, exist_ok=True)
|
||||||
iter_dir = self._nx_solver.create_iteration_folder(
|
|
||||||
iterations_base_dir=self.iterations_dir,
|
|
||||||
iteration_number=self._iteration,
|
|
||||||
expression_updates=expressions,
|
|
||||||
)
|
|
||||||
sim_file = iter_dir / self.sim_file.name
|
|
||||||
prt_file = iter_dir / self.prt_file.name
|
|
||||||
else:
|
|
||||||
iter_dir = self.model_dir
|
|
||||||
sim_file = self.sim_file
|
|
||||||
prt_file = self.prt_file
|
|
||||||
|
|
||||||
# Step 2: Run NX journal (update expressions + solve)
|
try:
|
||||||
|
# Step 1: Solve directly on master model (no file copying)
|
||||||
|
# NX file references stay intact — expressions updated in-place by journal
|
||||||
|
sim_file = self.sim_file
|
||||||
|
prt_file = self.prt_file
|
||||||
|
|
||||||
|
# Save trial params to iteration folder
|
||||||
|
import json
|
||||||
|
params_file = iter_dir / "params.json"
|
||||||
|
params_file.write_text(json.dumps({
|
||||||
|
"iteration": self._iteration,
|
||||||
|
"expressions": expressions,
|
||||||
|
"trial_input": {
|
||||||
|
"beam_half_core_thickness": trial.beam_half_core_thickness,
|
||||||
|
"beam_face_thickness": trial.beam_face_thickness,
|
||||||
|
"holes_diameter": trial.holes_diameter,
|
||||||
|
"hole_count": trial.hole_count,
|
||||||
|
},
|
||||||
|
}, indent=2))
|
||||||
|
|
||||||
|
# Step 2: Run NX journal (update expressions + solve) on master model
|
||||||
solve_result = self._nx_solver.run_simulation(
|
solve_result = self._nx_solver.run_simulation(
|
||||||
sim_file=sim_file,
|
sim_file=sim_file,
|
||||||
working_dir=iter_dir,
|
working_dir=self.model_dir,
|
||||||
expression_updates=expressions,
|
expression_updates=expressions,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -348,6 +360,9 @@ class AtomizerNXSolver:
|
|||||||
logger.warning("Stress extraction failed: %s", e)
|
logger.warning("Stress extraction failed: %s", e)
|
||||||
max_vm_stress = float("nan")
|
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)
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.time() - start_time
|
||||||
logger.info(
|
logger.info(
|
||||||
"Trial %d complete: mass=%.2f kg, disp=%.3f mm, stress=%.1f MPa (%.1fs)",
|
"Trial %d complete: mass=%.2f kg, disp=%.3f mm, stress=%.1f MPa (%.1fs)",
|
||||||
@@ -373,6 +388,52 @@ class AtomizerNXSolver:
|
|||||||
iteration_dir=str(iter_dir) if 'iter_dir' in locals() else None,
|
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:
|
def close(self) -> None:
|
||||||
"""Clean up NX solver resources."""
|
"""Clean up NX solver resources."""
|
||||||
logger.info("AtomizerNXSolver closed. %d iterations completed.", self._iteration)
|
logger.info("AtomizerNXSolver closed. %d iterations completed.", self._iteration)
|
||||||
|
|||||||
Reference in New Issue
Block a user