Buckets:
| #!/usr/bin/env python | |
| """Reproduce DynaSchedBench Claim 3 heuristic side. | |
| Runs all 24 composite priority dispatch rules (PDR: <OP_RULE>:<MACHINE_RULE>) | |
| on the 70 released DynaSched-Subset instances by importing the repo's OWN | |
| simulator/env/agent/metrics code (faithful in-process replica of the | |
| `dsbx-agent run` rollout loop), and records Cmax (makespan) per (instance, PDR). | |
| Output: outputs/pdr_makespans.json { instance_key: { "OP:MACHINE": Cmax, ... } } | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| REPO = Path("/home/ubuntu/samuel/dynasched-repro/repo") | |
| sys.path.insert(0, str(REPO / "src")) | |
| # Silence the very chatty loguru logging from the framework. | |
| from loguru import logger # noqa: E402 | |
| logger.remove() | |
| from dsbx.Env import DynaSchedEnv # noqa: E402 | |
| from dsbx.Sim.Loader import load_instance_from_events # noqa: E402 | |
| from dsbx.Eval.Metrics import evaluate_trajectory # noqa: E402 | |
| from dsbx.Agents.PDRs import PDRAgent # noqa: E402 | |
| OP_RULES = ["SPT", "LPT", "MWKR", "LWKR", "MOPNR", "LOPNR", "FIFO", "LIFO"] | |
| MACHINE_RULES = ["LIT", "LWL", "SPT"] | |
| SUBSET = Path("/home/ubuntu/samuel/dynasched-repro/repo/data/DynaSched-Subset") | |
| OUT = Path("/home/ubuntu/samuel/dynasched-repro/outputs/pdr_makespans.json") | |
| MAX_STEPS = 100_000 | |
| def instance_dirs(): | |
| dirs = [] | |
| for sub in ["fromGrid", "fromSweep"]: | |
| for events in sorted((SUBSET / sub).rglob("events.jsonl")): | |
| dirs.append(events.parent) | |
| return dirs | |
| def rollout(instance_dir: Path, op_rule: str, machine_rule: str) -> float: | |
| """Faithful replica of dsbx-agent run core loop for a PDR agent.""" | |
| events_file = instance_dir / "events.jsonl" | |
| model, events = load_instance_from_events(events_file) | |
| # track_trajectory=False: the makespan is computed from the FINAL snapshot's | |
| # per-job completion times (identical to evaluate_trajectory's | |
| # traj.last_snapshot path, which is what the metric uses), so accumulating | |
| # every step's snapshot is pure memory overhead. Disabling it drops peak RSS | |
| # from ~6.5 GB to ~0.2 GB per large rollout (validated bit-identical to the | |
| # trajectory path), which is what lets the sweep run at high parallelism. | |
| env = DynaSchedEnv( | |
| model, | |
| events=events, | |
| auto_generate_events=False, | |
| traj_stream_path=None, | |
| track_trajectory=False, | |
| ) | |
| # Emergency jobs: EXACT replica of dsbx-agent run detection logic. | |
| from dsbx.Sim.Events import PriorityChangeEvent | |
| emergency = set() | |
| emergency_threshold = getattr( | |
| getattr(model, "dynamic_scenarios", None), "emergency_priority", -1 | |
| ) | |
| for ev in events: | |
| if isinstance(ev, PriorityChangeEvent): | |
| new_p = getattr(ev, "new_priority", 0) | |
| if new_p <= emergency_threshold: | |
| emergency.add(str(ev.job_id)) | |
| sj = instance_dir / "static_jobs.json" | |
| if sj.exists(): | |
| try: | |
| data = json.loads(sj.read_text(encoding="utf-8")) | |
| jobs_info = data.get("jobs", {}) or {} | |
| for jid, info in jobs_info.items(): | |
| if info.get("arrival_type") in ("emergency", "dynamic"): | |
| emergency.add(str(jid)) | |
| except Exception: | |
| pass | |
| scenario_info = {"events_path": str(events_file)} | |
| if emergency: | |
| scenario_info["emergency_jobs"] = sorted(emergency) | |
| try: | |
| env._static_emergency_jobs = set(emergency) | |
| except Exception: | |
| pass | |
| # Seed the global RNG for reproducibility. The framework uses unseeded | |
| # random.choice() for tie-breaking, making the CLI non-deterministic | |
| # (verified: same instance/rule yields makespans differing at ~0.005%). | |
| # A fixed seed makes our reproduction bit-for-bit repeatable without | |
| # changing any scheduling logic. | |
| ag = PDRAgent(op_rule=op_rule, machine_rule=machine_rule, random_seed=0) | |
| obs = env.reset() | |
| ag.reset(scenario_info) | |
| done = env.done() | |
| steps = 0 | |
| while (not done) and steps < MAX_STEPS: | |
| legal = env.legal_actions() | |
| if not legal: | |
| obs = env.advance_if_idle() | |
| done = env.done() | |
| continue | |
| act = ag.act(obs, legal, env) | |
| if act is None: | |
| obs = env.advance_if_idle() | |
| steps += 1 | |
| done = env.done() | |
| continue | |
| obs, reward, done, info = env.step(act) | |
| steps += 1 | |
| # Silent-failure guard: a makespan from a schedule that did not run to | |
| # completion (hit the max_steps cap) is meaningless and would corrupt the | |
| # gap aggregation. Fail loudly instead. | |
| if not done: | |
| env.close() | |
| raise RuntimeError( | |
| f"Rollout {instance_dir.name} {op_rule}:{machine_rule} hit " | |
| f"max_steps={MAX_STEPS} without env.done(); makespan is invalid." | |
| ) | |
| # Makespan = max completion time over jobs in the final snapshot. This is | |
| # exactly what dsbx.Eval.Metrics computes from traj.last_snapshot; using the | |
| # live final snapshot avoids retaining the whole trajectory. | |
| snap = env.get_snapshot() | |
| completion_times = [ | |
| float(j.completion_time) if j.completion_time is not None else float(snap.time) | |
| for j in snap.jobs | |
| ] | |
| makespan = max(completion_times) if completion_times else float(snap.time) | |
| env.close() | |
| return float(makespan) | |
| def main(): | |
| only = sys.argv[1] if len(sys.argv) > 1 else None # optional: single instance path | |
| dirs = instance_dirs() | |
| print(f"Found {len(dirs)} instances", flush=True) | |
| assert len(dirs) == 70, f"expected 70 instances, got {len(dirs)}" | |
| results = {} | |
| if OUT.exists(): | |
| results = json.loads(OUT.read_text()) | |
| t0 = time.time() | |
| for i, d in enumerate(dirs): | |
| key = str(d.relative_to(SUBSET)) | |
| if only and only not in str(d): | |
| continue | |
| results.setdefault(key, {}) | |
| for op in OP_RULES: | |
| for mac in MACHINE_RULES: | |
| rk = f"{op}:{mac}" | |
| if rk in results[key]: | |
| continue | |
| cmax = rollout(d, op, mac) | |
| results[key][rk] = cmax | |
| # checkpoint after each instance | |
| OUT.parent.mkdir(parents=True, exist_ok=True) | |
| OUT.write_text(json.dumps(results, indent=2)) | |
| el = time.time() - t0 | |
| print(f"[{i+1}/{len(dirs)}] {key} done ({el:.0f}s elapsed)", flush=True) | |
| print(f"All done in {time.time()-t0:.0f}s -> {OUT}", flush=True) | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 6.53 kB
- Xet hash:
- ce7074b9119cba0f120b758861de1e9c07fae7e4fb8359fdae5ece96173b21f4
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.