poolcoach / scripts /verify_drills.py
masterdanh's picture
deploy: snapshot for HF Space
78738de
Raw
History Blame Contribute Delete
7.31 kB
"""Verify kha thi cua thu vien drill (app/drills.json) — BRIEF 04/08/2026 buoc 1.
CURATION, KHONG PHAI EVAL (tien le 11b ban giao 9): so "cu dat goal" o day chi
de chung minh drill TAP DUOC — dung de lan vao so do engine trong research log.
Voi moi drill: quet dung grid ky thuat x luc cua engine (11 ky thuat x nac
2-10 = 99 o, `v2_specs`) tren phi ngam ghost-ball vao lo goal (goal.pot.pocket
= "any" thi quet moi lo kha thi tu `feasible_pockets_full`), sim tung cu bang
`simulate_shot_multi`, dem:
n_pot : cu pot SACH bi muc tieu (khong scratch, first_contact dung bi,
bi vao lo) — gate cung: moi drill phai co >= 1
n_pass : cu dat TRON goal = pot sach + cue dung trong cue_zone
(scoring.pass "pot && cue_zone") — drill 0 cu dat la drill
khong tap duoc: chinh zone/khoang cach roi quet lai
Gate G1: 100% drill layout hop le (validate_full khong nem) VA n_pot >= 1.
Bang so n_pass tung drill di vao HANDOFF.
Chay SERIAL co chu dich, khong pool: tong scan ~9 drill x 99-500 sim ~ 10-30s
sau warmup, trong khi moi worker process phai tra ~40s JIT Numba rieng —
pool o day cham hon serial. Khong hardcode so worker vi khong co worker nao
(POOLCOACH_WORKERS chi danh cho cho nao that su co pool).
python scripts/verify_drills.py [--drill stop_shot_short] [--drills-file ...]
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT / "src") not in sys.path:
sys.path.insert(0, str(ROOT / "src"))
DRILLS_FILE = ROOT / "app" / "drills.json"
def load_drills(path: Path) -> list[dict]:
data = json.loads(path.read_text(encoding="utf-8"))
return data["drills"]
def check_layout(env_h, balls, target):
"""Layout hop le = validate_full khong nem + bi muc tieu co mat.
Tra ve None neu ok, message loi neu khong."""
from poolcoach_rl.recommend import validate_full
if target not in balls:
return f"goal.pot.ball={target!r} khong co trong layout"
try:
validate_full(env_h, balls)
except ValueError as e:
return str(e)
return None
def scan_drill(env_h, drill):
"""Quet grid engine tren MOT drill. Tra dict so lieu cho bang bao cao."""
import numpy as np
from poolcoach_rl.recommend import feasible_pockets_full
from poolcoach_rl.recommend.zone_v2 import v2_specs
from poolcoach_rl.recommend import simulate_shot_multi
balls = {bid: np.array([p["x"], p["y"]], dtype=np.float64)
for bid, p in drill["layout"].items()}
target = drill["goal"]["pot"]["ball"]
want_pocket = drill["goal"]["pot"]["pocket"]
zone = drill["goal"]["cue_zone"]
zc = np.array([zone["cx"], zone["cy"]], dtype=np.float64)
err = check_layout(env_h, balls, target)
if err:
return {"id": drill["id"], "layout_err": err}
pockets = feasible_pockets_full(env_h, balls, target)
if want_pocket != "any":
pockets = [p for p in pockets if p[0] == int(want_pocket)]
specs = v2_specs()
n_sim = n_pot = n_pass = 0
example = None
t0 = time.time()
for idx, phi, _cut, _blocked in pockets:
for side, vert, v0 in specs:
m = simulate_shot_multi(env_h, balls, phi, v0, side, vert)
n_sim += 1
if m is None:
continue
pot_ok = (not m["scratch"]
and m["first_contact"] == target
and list(m["potted"]) == [target])
if not pot_ok:
continue
n_pot += 1
cue_final = m["balls_final"].get("cue")
if cue_final is None:
continue
d_zone = float(np.linalg.norm(
np.asarray(cue_final, dtype=np.float64) - zc))
if d_zone <= zone["r"]:
n_pass += 1
if example is None:
example = (side, vert, v0, d_zone)
return {"id": drill["id"], "layout_err": None,
"n_pockets": len(pockets), "n_sim": n_sim,
"n_pot": n_pot, "n_pass": n_pass,
"example": example, "elapsed_s": time.time() - t0}
def main() -> int:
# Console Windows co the la cp1252 — title tieng Viet khong duoc lam chet script
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--drill", help="chi quet 1 drill theo id (chinh sua nhanh)")
ap.add_argument("--drills-file", type=Path, default=DRILLS_FILE)
args = ap.parse_args()
drills = load_drills(args.drills_file)
if args.drill:
drills = [d for d in drills if d["id"] == args.drill]
if not drills:
print(f"khong co drill id={args.drill!r}")
return 2
print(f"[verify_drills] {len(drills)} drill tu {args.drills_file}")
print("[verify_drills] dung env + warmup JIT Numba (~40s lan dau)...",
flush=True)
t0 = time.time()
from poolcoach_rl.envs import PositionPlayEnv
from poolcoach_rl.recommend import warmup
env_h = PositionPlayEnv()
warmup(env_h)
print(f"[verify_drills] warmup xong sau {time.time() - t0:.1f}s", flush=True)
rows = []
for d in drills:
r = scan_drill(env_h, d)
rows.append(r)
if r["layout_err"]:
print(f" {r['id']:18s} LAYOUT LOI: {r['layout_err']}", flush=True)
else:
ex = r["example"]
ex_txt = (f"side {ex[0]:+.1f} vert {ex[1]:+.1f} v0 {ex[2]:.2f} "
f"(cach tam zone {ex[3]:.3f} m)" if ex else "-")
print(f" {r['id']:18s} pockets {r['n_pockets']} sim {r['n_sim']:4d}"
f" pot {r['n_pot']:3d} PASS {r['n_pass']:3d}"
f" vd: {ex_txt} [{r['elapsed_s']:.1f}s]", flush=True)
# ------------------------------------------------------------- gate G1
bad_layout = [r["id"] for r in rows if r["layout_err"]]
no_pot = [r["id"] for r in rows
if not r["layout_err"] and r["n_pot"] == 0]
no_pass = [r["id"] for r in rows
if not r["layout_err"] and r["n_pot"] > 0 and r["n_pass"] == 0]
print()
print("| drill | pockets | sim | pot sach | dat tron goal |")
print("|---|---|---|---|---|")
for r in rows:
if r["layout_err"]:
print(f"| {r['id']} | - | - | - | LAYOUT LOI |")
else:
print(f"| {r['id']} | {r['n_pockets']} | {r['n_sim']} "
f"| {r['n_pot']} | {r['n_pass']} |")
print()
if bad_layout:
print(f"GATE DO: layout loi: {', '.join(bad_layout)}")
return 1
if no_pot:
print(f"GATE DO: khong ton tai cu pot: {', '.join(no_pot)}")
return 1
if no_pass:
# khong phai gate cung cua G1, nhung drill 0 cu dat tron goal la drill
# khong tap duoc — BRIEF bat chinh zone/khoang cach roi quet lai
print(f"CANH BAO: pot duoc nhung 0 cu dat tron goal (phai chinh + quet "
f"lai): {', '.join(no_pass)}")
return 1
print("GATE G1 DAT: 100% layout hop le, moi drill co cu pot va co cu dat "
"tron goal.")
return 0
if __name__ == "__main__":
raise SystemExit(main())