Add persistent trial history DB (append-only, survives --clean)
- history.db: SQLite append-only, never deleted by --clean - history.csv: Auto-exported after each trial (live updates) - Logs: DVs, results, feasibility, status, solve time, iter path - Cross-study queries: full lineage across all runs/phases - --clean only resets Optuna DB, history preserved
This commit is contained in:
@@ -41,6 +41,7 @@ from geometric_checks import (
|
||||
FeasibilityResult,
|
||||
check_feasibility,
|
||||
)
|
||||
from history import TrialHistory
|
||||
from nx_interface import TrialInput, TrialResult, create_solver
|
||||
from sampling import DV_DEFINITIONS, generate_lhs_samples, points_to_dicts
|
||||
|
||||
@@ -103,12 +104,16 @@ def constraints_func(trial: optuna.trial.FrozenTrial) -> list[float]:
|
||||
def evaluate_trial(
|
||||
trial: optuna.Trial,
|
||||
solver: Any,
|
||||
history: TrialHistory | None = None,
|
||||
study_name: str = "",
|
||||
) -> float:
|
||||
"""Evaluate a single trial: geometric check → NX solve → extract.
|
||||
|
||||
Args:
|
||||
trial: Optuna trial (with parameters already suggested/enqueued).
|
||||
solver: NX solver instance (stub or real).
|
||||
history: Persistent trial history logger (append-only).
|
||||
study_name: Study name for history logging.
|
||||
|
||||
Returns:
|
||||
Objective value (mass in kg). Returns INFEASIBLE_MASS for
|
||||
@@ -152,6 +157,13 @@ def evaluate_trial(
|
||||
trial.set_user_attr("ligament", geo_result.ligament)
|
||||
trial.set_user_attr("web_clearance", geo_result.web_clearance)
|
||||
|
||||
params = {
|
||||
"beam_half_core_thickness": dv1,
|
||||
"beam_face_thickness": dv2,
|
||||
"holes_diameter": dv3,
|
||||
"hole_count": dv4,
|
||||
}
|
||||
|
||||
if not geo_result.feasible:
|
||||
logger.warning(
|
||||
"Trial %d: GEOMETRICALLY INFEASIBLE — %s",
|
||||
@@ -162,6 +174,13 @@ def evaluate_trial(
|
||||
trial.set_user_attr("tip_displacement", INFEASIBLE_DISPLACEMENT)
|
||||
trial.set_user_attr("max_von_mises", INFEASIBLE_STRESS)
|
||||
trial.set_user_attr("mass", INFEASIBLE_MASS)
|
||||
if history:
|
||||
history.log_trial(
|
||||
study_name=study_name, trial_id=trial_num, params=params,
|
||||
geo_feasible=False, status="GEO_INFEASIBLE",
|
||||
error_message=geo_result.reason,
|
||||
iteration_number=trial_num + 1,
|
||||
)
|
||||
return INFEASIBLE_MASS
|
||||
|
||||
# NX evaluation
|
||||
@@ -188,6 +207,14 @@ def evaluate_trial(
|
||||
trial.set_user_attr("tip_displacement", INFEASIBLE_DISPLACEMENT)
|
||||
trial.set_user_attr("max_von_mises", INFEASIBLE_STRESS)
|
||||
trial.set_user_attr("mass", INFEASIBLE_MASS)
|
||||
if history:
|
||||
history.log_trial(
|
||||
study_name=study_name, trial_id=trial_num, params=params,
|
||||
status="FAILED", error_message=nx_result.error_message,
|
||||
solve_time_s=round(t_elapsed, 2),
|
||||
iter_path=nx_result.iteration_dir,
|
||||
iteration_number=trial_num + 1,
|
||||
)
|
||||
return INFEASIBLE_MASS
|
||||
|
||||
# Record successful results
|
||||
@@ -214,6 +241,20 @@ def evaluate_trial(
|
||||
t_elapsed,
|
||||
)
|
||||
|
||||
if history:
|
||||
history.log_trial(
|
||||
study_name=study_name, trial_id=trial_num, params=params,
|
||||
mass_kg=nx_result.mass,
|
||||
tip_displacement_mm=nx_result.tip_displacement,
|
||||
max_von_mises_mpa=nx_result.max_von_mises,
|
||||
geo_feasible=True,
|
||||
status="COMPLETE",
|
||||
solve_time_s=round(t_elapsed, 2),
|
||||
iter_path=nx_result.iteration_dir,
|
||||
iteration_number=trial_num + 1,
|
||||
)
|
||||
history.export_csv() # Live update CSV after each trial
|
||||
|
||||
return nx_result.mass
|
||||
|
||||
|
||||
@@ -419,7 +460,7 @@ def run_study(args: argparse.Namespace) -> None:
|
||||
storage = f"sqlite:///{db_path}"
|
||||
|
||||
if args.clean and db_path.exists():
|
||||
logger.info("--clean flag: deleting existing DB at %s", db_path)
|
||||
logger.info("--clean flag: deleting Optuna DB at %s (history.db preserved)", db_path)
|
||||
db_path.unlink()
|
||||
|
||||
if args.resume:
|
||||
@@ -449,13 +490,17 @@ def run_study(args: argparse.Namespace) -> None:
|
||||
logger.info("Enqueued %d trials (1 baseline + %d LHS)", n_trials, n_trials - 1)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 3. Create solver
|
||||
# 3. Create solver + history
|
||||
# -----------------------------------------------------------------------
|
||||
solver = create_solver(
|
||||
backend=args.backend,
|
||||
model_dir=args.model_dir,
|
||||
)
|
||||
|
||||
# Persistent history — NEVER deleted by --clean
|
||||
history = TrialHistory(results_dir)
|
||||
study_name = args.study_name
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 4. Run all trials
|
||||
# -----------------------------------------------------------------------
|
||||
@@ -469,7 +514,7 @@ def run_study(args: argparse.Namespace) -> None:
|
||||
optuna.logging.set_verbosity(optuna.logging.WARNING)
|
||||
|
||||
study.optimize(
|
||||
lambda trial: evaluate_trial(trial, solver),
|
||||
lambda trial: evaluate_trial(trial, solver, history, study_name),
|
||||
n_trials=n_trials,
|
||||
callbacks=[_progress_callback],
|
||||
)
|
||||
@@ -497,6 +542,11 @@ def run_study(args: argparse.Namespace) -> None:
|
||||
# Cleanup
|
||||
solver.close()
|
||||
|
||||
# Final history export + summary
|
||||
history.close()
|
||||
hist_summary = history.get_study_summary(study_name)
|
||||
logger.info("History DB: %d total records across all studies", hist_summary["total"])
|
||||
|
||||
|
||||
def _progress_callback(study: optuna.Study, trial: optuna.trial.FrozenTrial) -> None:
|
||||
"""Log progress after each trial."""
|
||||
|
||||
Reference in New Issue
Block a user