| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- """P3 checkpoint test: search import_state + AdaptiveLoop export/restore.
- Verifies the /resume path:
- 1. FeasibilityFirstSearch.export_state -> import_state round-trip keeps
- run_id / budget / points / objective / convergence and can select the
- next batch.
- 2. AdaptiveLoop.export_state -> restore_state survives a simulated process
- restart (registry cleared) and can keep driving batches.
- Run: python scripts/test_p3_checkpoint.py (exit 0 = PASS)
- Isolated temp SQLite DB; no real Motor-CAD involved.
- """
- import os
- import sys
- import tempfile
- _TMP = os.path.join(tempfile.mkdtemp(), "ck.db")
- os.environ["AFM_DB_PATH"] = _TMP
- os.environ["KIMI_API_KEY"] = ""
- _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- sys.path.insert(0, os.path.join(_ROOT, "web", "backend"))
- sys.path.insert(0, _ROOT)
- from app.database import init_db # noqa: E402
- init_db()
- from app.services.feasibility_search import ( # noqa: E402
- FeasibilityFirstSearch, ParameterRange, L0PreScreeningEngine,
- )
- # ---------------------------------------------------------------- 1. search round-trip
- params = [ParameterRange(name="airgap_mm", min_value=0.8, max_value=2.0,
- step=0.1, unit="mm")]
- s1 = FeasibilityFirstSearch(parameters=params, l0_engine=L0PreScreeningEngine(),
- total_budget=8, batch_size=4, initial_samples=4,
- objective_metric="tavg_nm", objective_direction="maximize")
- batch = s1.generate_initial_batch()
- for i, p in enumerate(batch[:3]):
- s1.report_result(p.id, {"tavg_nm": 8.0 + 0.5 * p.id}, "ok")
- ex = s1.export_state()
- s2 = FeasibilityFirstSearch.import_state(ex, l0_engine=L0PreScreeningEngine())
- assert s2.state.run_id == s1.state.run_id
- assert s2.state.used_budget == s1.state.used_budget == 3
- assert len(s2.state.points) == len(s1.state.points)
- assert s2.objective_metric == "tavg_nm" and s2.objective_direction == "maximize"
- assert s2.state.convergence_status == s1.state.convergence_status
- nb = s2.select_next_batch()
- assert nb, "restored search must be able to select the next batch"
- print("[1] search export->import round-trip OK (next batch: %d pts)" % len(nb))
- # ---------------------------------------------------------------- 2. AdaptiveLoop resume
- from app.services.adaptive_loop import ( # noqa: E402
- AdaptiveLoop, create_loop, _loops,
- )
- loop = create_loop(user_requirement="max torque", total_budget=8, batch_size=4)
- loop.plan = {
- "plan_name": "p", "topology": "SSSR",
- "scan_variables": [
- {"name": "airgap_mm", "min_value": 0.8, "max_value": 2.0},
- {"name": "current_a", "min_value": 5.0, "max_value": 20.0},
- ],
- "search_strategy": {"max_solver_calls": 8, "batch_size": 4,
- "initial_samples": 4},
- "acceptance_criteria": {"objective_metric": "tavg_nm",
- "objective_direction": "maximize",
- "hard_constraints": []},
- }
- loop.initialize_search()
- sub = loop.submit_batch_to_executor()
- assert sub.get("task_id"), sub
- exported = loop.export_state()
- assert exported["search"] is not None
- assert exported["loop_id"] == loop.loop_id
- assert exported["phase"] == loop.phase.value
- # simulate process restart: in-memory registry is lost
- _loops.clear()
- assert len(_loops) == 0
- loop2 = AdaptiveLoop.restore_state(exported)
- assert loop2.loop_id == loop.loop_id
- assert loop2.phase.value == exported["phase"]
- assert loop2.search is not None
- sub2 = loop2.submit_batch_to_executor()
- assert sub2.get("task_id"), "restored loop must be able to submit a batch"
- print("[2] AdaptiveLoop export->restore after restart OK (batch resubmitted)")
- print("\nALL P3 CHECKPOINT TESTS PASSED")
|