Spaces:
Sleeping
Sleeping
| """ | |
| OASIS Evaluation Script | |
| ========================= | |
| Runs the PID controller baseline and a constant-basal fallback agent through | |
| all OASIS tasks, printing a comparison table suitable for the README. | |
| Optionally connects to an LLM agent via the inference server if GLUCORL_ENV_URL | |
| is set (requires a running server and LLM credentials). | |
| Usage: | |
| python eval.py # Direct evaluation (no server needed) | |
| python eval.py --with-server # Also test via server + client | |
| """ | |
| import argparse | |
| import sys | |
| import time | |
| from models import GlucoAction | |
| from server.glucorl_environment import GlucoRLEnvironment | |
| from server.pid_controller import PIDController | |
| from server.graders import grade, score_task_3_single | |
| from server.constants import TASK3_EVAL_PATIENTS | |
| def run_episode(env: GlucoRLEnvironment, task_id: int, agent_fn, seed: int = 42) -> dict: | |
| """ | |
| Run a single episode and return metrics. | |
| Args: | |
| env: OASIS environment instance. | |
| task_id: Task to evaluate (1, 2, or 3). | |
| agent_fn: Callable(glucose: float, obs) -> GlucoAction. | |
| seed: Random seed for reproducibility. | |
| Returns: | |
| Dict with tir, score, hypo_events, severe_hypo_events, hyper_events, steps. | |
| """ | |
| obs = env.reset(task_id=task_id, seed=seed) | |
| glucose = obs.glucose_mg_dl | |
| for _ in range(480): | |
| action = agent_fn(glucose, obs) | |
| obs = env.step(action) | |
| glucose = obs.glucose_mg_dl | |
| if obs.done: | |
| break | |
| state = env.state | |
| score = grade(task_id, state) | |
| return { | |
| "tir": state.tir_current, | |
| "score": score, | |
| "hypo_events": state.hypo_events, | |
| "severe_hypo_events": state.severe_hypo_events, | |
| "hyper_events": state.hyper_events, | |
| "steps": state.step, | |
| "reward": state.episode_reward_total, | |
| "glucose_min": min(state.glucose_history), | |
| "glucose_max": max(state.glucose_history), | |
| } | |
| def run_task3_full(env: GlucoRLEnvironment, agent_fn) -> dict: | |
| """ | |
| Run the full Task 3 evaluation: 5 episodes with fixed patient set. | |
| Args: | |
| env: OASIS environment instance. | |
| agent_fn: Callable(glucose: float, obs) -> GlucoAction. | |
| Returns: | |
| Dict with per-patient scores and average. | |
| """ | |
| patient_scores = [] | |
| for patient in TASK3_EVAL_PATIENTS: | |
| # Manually set up the environment for a specific patient | |
| env._task_id = 3 | |
| env._patient_name = patient | |
| initial_g = env._patient_mgr.reset(patient) | |
| env._step_count = 0 | |
| env._done = False | |
| env._glucose_history = [initial_g] | |
| env._reward_history = [] | |
| env._action_history = [] | |
| env._episode_reward = 0.0 | |
| env._consecutive_severe_hypo = 0 | |
| env._hypo_events = 0 | |
| env._severe_hypo_events = 0 | |
| env._hyper_events = 0 | |
| env._episode_id = f"eval-{patient}" | |
| glucose = initial_g | |
| obs = env._build_observation(glucose) | |
| for _ in range(480): | |
| action = agent_fn(glucose, obs) | |
| obs = env.step(action) | |
| glucose = obs.glucose_mg_dl | |
| if obs.done: | |
| break | |
| state = env.state | |
| s = score_task_3_single(state) | |
| patient_scores.append({ | |
| "patient": patient, | |
| "score": s, | |
| "tir": state.tir_current, | |
| "steps": state.step, | |
| "severe_hypo": state.severe_hypo_events, | |
| }) | |
| avg_score = sum(p["score"] for p in patient_scores) / len(patient_scores) | |
| return { | |
| "patients": patient_scores, | |
| "average_score": avg_score, | |
| } | |
| def pid_agent(glucose: float, obs) -> GlucoAction: | |
| """PID controller agent β uses global PID instance.""" | |
| return _pid.act(glucose) | |
| def fallback_agent(glucose: float, obs) -> GlucoAction: | |
| """Constant basal agent β no intelligence.""" | |
| return GlucoAction(basal_rate=1.0, bolus_dose=0.0) | |
| # Global PID instance (reset between tasks) | |
| _pid = PIDController() | |
| def print_separator(): | |
| print(f"{'β' * 72}") | |
| def main(): | |
| parser = argparse.ArgumentParser(description="OASIS Evaluation") | |
| parser.add_argument( | |
| "--with-server", | |
| action="store_true", | |
| help="Also test via running server (requires uvicorn on port 8000)", | |
| ) | |
| args = parser.parse_args() | |
| env = GlucoRLEnvironment() | |
| agents = { | |
| "Constant Basal": fallback_agent, | |
| "PID Controller": pid_agent, | |
| } | |
| print() | |
| print("OASIS Evaluation") | |
| print("==================") | |
| print() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Task 1 & 2: single-episode evaluation | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| results = {} | |
| for task_id in [1, 2]: | |
| print_separator() | |
| print(f" Task {task_id}") | |
| print_separator() | |
| for agent_name, agent_fn in agents.items(): | |
| if agent_name == "PID Controller": | |
| _pid.reset() | |
| r = run_episode(env, task_id, agent_fn, seed=42) | |
| results[(agent_name, task_id)] = r | |
| print( | |
| f" {agent_name:20s} β TIR {r['tir']:5.1%} β " | |
| f"Score {r['score']:.3f} β Hypo {r['hypo_events']:3d} β " | |
| f"SevHypo {r['severe_hypo_events']:2d} β " | |
| f"Hyper {r['hyper_events']:3d} β " | |
| f"Steps {r['steps']:3d} β " | |
| f"Glucose [{r['glucose_min']:.0f}β{r['glucose_max']:.0f}]" | |
| ) | |
| print() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Task 3: multi-patient evaluation | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| print_separator() | |
| print(" Task 3 β Cross-Patient Evaluation (5 patients)") | |
| print_separator() | |
| for agent_name, agent_fn in agents.items(): | |
| if agent_name == "PID Controller": | |
| _pid.reset() | |
| t3 = run_task3_full(env, agent_fn) | |
| results[(agent_name, 3)] = {"score": t3["average_score"]} | |
| print(f" {agent_name}:") | |
| for p in t3["patients"]: | |
| status = "β" if p["steps"] == 480 else f"terminated@{p['steps']}" | |
| print( | |
| f" {p['patient']:18s} β TIR {p['tir']:5.1%} β " | |
| f"Score {p['score']:.3f} β SevHypo {p['severe_hypo']:2d} β " | |
| f"{status}" | |
| ) | |
| print(f" {'AVERAGE':18s} β β Score {t3['average_score']:.3f}") | |
| print() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Summary table | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| print_separator() | |
| print(" SUMMARY") | |
| print_separator() | |
| print(f" {'Agent':20s} β {'Task 1':>8s} β {'Task 2':>8s} β {'Task 3':>8s}") | |
| print(f" {'β' * 20} β {'β' * 8} β {'β' * 8} β {'β' * 8}") | |
| for agent_name in agents: | |
| scores = [] | |
| for task_id in [1, 2, 3]: | |
| s = results.get((agent_name, task_id), {}).get("score", 0.0) | |
| scores.append(s) | |
| print( | |
| f" {agent_name:20s} β {scores[0]:8.3f} β {scores[1]:8.3f} β {scores[2]:8.3f}" | |
| ) | |
| print() | |
| print(" All scores are in [0.0, 1.0]. Higher is better.") | |
| print(" Task 3 score is averaged over 5 patients with fixed seed.") | |
| print() | |
| if __name__ == "__main__": | |
| main() | |