test_p4_m5_l0.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. """P4-M5: L0 pre-screening promoted to shared core (single implementation).
  2. Run: python scripts/test_p4_m5_l0.py (exit 0 = PASS)
  3. Verifies:
  4. 1. The single implementation lives at src/afmcore/l0/prescreening.py.
  5. 2. The web re-export references the same classes (no drift).
  6. 3. Core behavior is unchanged: geometric/electrical/thermal/manufacturing
  7. checks, evaluate(), filter_feasible(), empty-input gate.
  8. 4. Consumers (feasibility_search) still work through the re-export.
  9. """
  10. import os
  11. import sys
  12. _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  13. _BACKEND = os.path.join(_ROOT, "web", "backend")
  14. sys.path.insert(0, _BACKEND)
  15. sys.path.insert(0, _ROOT)
  16. from src.afmcore.l0.prescreening import ( # noqa: E402
  17. L0PreScreeningEngine as SrcEngine,
  18. FeasibilityReport, ConstraintResult,
  19. )
  20. from app.services.l0_prescreening import L0PreScreeningEngine as WebEngine # noqa: E402
  21. # 1) single definition, no drift
  22. assert SrcEngine is WebEngine, "re-export must point at the same class"
  23. assert SrcEngine.__module__ == "src.afmcore.l0.prescreening"
  24. print("[1] single implementation in shared core OK (%s)" % SrcEngine.__module__)
  25. eng = SrcEngine()
  26. # 2) geometric: valid vs invalid diameter ratio
  27. r = eng.evaluate({"outer_diameter_mm": 150, "inner_diameter_mm": 80})
  28. assert r.feasible
  29. r_bad = eng.evaluate({"outer_diameter_mm": 150, "inner_diameter_mm": 10})
  30. assert not r_bad.feasible # ratio 0.067 < 0.2
  31. print("[2] geometric diameter-ratio gate OK")
  32. # 3) electrical: current density limit
  33. r = eng.evaluate({"current_a": 10, "conductor_area_mm2": 2.0}) # 5 A/mm2
  34. assert r.feasible
  35. r2 = eng.evaluate({"current_a": 100, "conductor_area_mm2": 2.0}) # 50 A/mm2
  36. assert not r2.feasible
  37. print("[3] electrical current-density gate OK")
  38. # 4) thermal: winding temp limit
  39. r = eng.evaluate({"winding_temp_c": 120}) # below 150
  40. assert r.feasible
  41. r2 = eng.evaluate({"winding_temp_c": 160}) # above 150
  42. assert not r2.feasible
  43. print("[4] thermal winding-temp gate OK")
  44. # 5) manufacturing: PCB line width
  45. r = eng.evaluate({"pcb_line_width_mm": 0.5})
  46. assert r.feasible
  47. r2 = eng.evaluate({"pcb_line_width_mm": 0.05})
  48. assert not r2.feasible
  49. print("[5] manufacturing PCB line-width gate OK")
  50. # 6) empty input -> infeasible with UNKNOWN risk item (C3 gate)
  51. r = eng.evaluate({})
  52. assert not r.feasible
  53. assert any("[UNKNOWN]" in x for x in r.risk_items)
  54. print("[6] empty-input gate OK")
  55. # 7) filter_feasible splits sets
  56. sets = [
  57. {"outer_diameter_mm": 150, "inner_diameter_mm": 80},
  58. {"outer_diameter_mm": 150, "inner_diameter_mm": 10},
  59. ]
  60. feas, infeas = eng.filter_feasible(sets)
  61. assert len(feas) == 1 and len(infeas) == 1
  62. print("[7] filter_feasible split OK")
  63. # 8) consumers still work through the re-export
  64. from app.services.feasibility_search import ( # noqa: E402
  65. FeasibilityFirstSearch, ParameterRange,
  66. )
  67. search = FeasibilityFirstSearch(
  68. parameters=[ParameterRange(name="outer_diameter_mm", min_value=100, max_value=300),
  69. ParameterRange(name="inner_diameter_mm", min_value=50, max_value=150)],
  70. l0_engine=WebEngine(),
  71. total_budget=20, batch_size=4, initial_samples=6,
  72. objective_metric="tavg_nm", objective_direction="maximize", seed=1,
  73. )
  74. ini = search.generate_initial_batch()
  75. assert len(ini) > 0
  76. print("[8] feasibility_search L0 integration OK (%d initial pts)" % len(ini))
  77. print("\nALL P4-M5 L0 PROMOTION TESTS PASSED")