"""Three deterministic scheduling rules and exact reward accounting on a synthetic two-resource workload; no learned policy or production scheduler benchmark.
Run with Python 3.10+; standard library only. Writes results.json in the current directory.
"""
jobs = {"A": (3, 1, 2), "B": (3, 1, 1), "C": (1, 3, 2)}

def simulate(order, strict):
    waiting = list(order)
    running = {}
    finished = {}
    reward = 0.0
    time = 0
    while len(finished) < len(jobs):
        used_cpu = sum(jobs[job][0] for job in running)
        used_memory = sum(jobs[job][1] for job in running)
        for job in list(waiting):
            cpu, memory, duration = jobs[job]
            fits = used_cpu + cpu <= 4 and used_memory + memory <= 4
            if fits:
                running[job] = duration
                waiting.remove(job)
                used_cpu += cpu
                used_memory += memory
            elif strict:
                break
        reward -= sum(1 / jobs[job][2] for job in jobs if job not in finished)
        assert used_cpu <= 4 and used_memory <= 4
        time += 1
        for job in list(running):
            running[job] -= 1
            if running[job] == 0:
                finished[job] = time
                del running[job]
        assert time < 20
    slowdown = sum(finished[job] / jobs[job][2] for job in jobs)
    assert abs(reward + slowdown) < 1e-12
    return finished, slowdown / len(jobs), reward

for name, order, strict in [("FIFO", "ABC", True),
                            ("scan", "ABC", False),
                            ("shortest", "BAC", False)]:
    completion, mean, reward = simulate(order, strict)
    print(f"{name}: finish={dict(sorted(completion.items()))} "
          f"mean_slowdown={mean:.3f} reward={reward:.3f}")

# Extended experiment: deterministic parameter sweep and invariants.
from itertools import permutations
def resource_schedule(order,strict,capacity=(4,4)):
    waiting=list(order);running={};finished={};starts={};reward=0.;time=0;occupancy=[]
    while len(finished)<len(jobs):
        cpu=sum(jobs[j][0] for j in running);memory=sum(jobs[j][1] for j in running)
        for job in list(waiting):
            c,m,d=jobs[job]
            if cpu+c<=capacity[0] and memory+m<=capacity[1]:
                running[job]=d;starts[job]=time;waiting.remove(job);cpu+=c;memory+=m
            elif strict:break
        assert running and cpu<=capacity[0] and memory<=capacity[1]
        occupancy.append(dict(time=time,active=sorted(running),cpu=cpu,memory=memory))
        reward-=sum(1/jobs[j][2] for j in jobs if j not in finished)
        time+=1
        for job in list(running):
            running[job]-=1
            if running[job]==0:finished[job]=time;del running[job]
    slowdown=sum(finished[j]/jobs[j][2] for j in jobs)/len(jobs)
    assert abs(reward+slowdown*len(jobs))<1e-12
    return dict(order=''.join(order),strict=strict,capacity=list(capacity),mean_slowdown=slowdown,makespan=time,
                reward=reward,starts=starts,finished=finished,occupancy=occupancy)
policies=[dict(name=name,**resource_schedule(order,strict)) for name,order,strict in [('FIFO','ABC',True),('scan','ABC',False),('shortest','BAC',False)]]
capacity_sweep=[]
for cpu in range(3,7):
    for memory in range(3,7):
        candidates=[resource_schedule(order,False,(cpu,memory)) for order in permutations('ABC')]
        best=min(candidates,key=lambda r:r['mean_slowdown'])
        capacity_sweep.append(dict(cpu=cpu,memory=memory,best_scan_mean_slowdown=best['mean_slowdown'],best_order=best['order'],tied_best_orders=[r['order'] for r in candidates if abs(r['mean_slowdown']-best['mean_slowdown'])<1e-12]))
data=dict(jobs=jobs,policies=policies,capacity_sweep=capacity_sweep)
print('Capacity sweep checked all six scan orders at each of 16 CPU/memory combinations.')
print('Every schedule obeys both capacity constraints and exactly reconciles cumulative reward with total slowdown.')

import json
from pathlib import Path
Path("results.json").write_text(json.dumps(data,indent=2,allow_nan=False)+"\n",encoding="utf-8")
