def simulate(config):
    calls = config["iterations"] * ((config["groups"] + config["batch"] - 1) // config["batch"])
    completed_shots = config["iterations"] * config["groups"] * config["shots"]
    spans = {"queue":config["dispatch_s"] * calls, "batch_wait":config["batch_wait_s"] * (config["batch"] - 1) * config["iterations"], "qpu":config["shot_s"] * completed_shots, "classical":config["classical_s"]}
    return {"calls":calls, "shots":completed_shots, "bytes":completed_shots * config["bytes_per_result"], "spans":spans, "parents":{name:"workflow" for name in spans}, "total":sum(spans.values()), "task":(config["objective"], config["input_id"])}
config = {"iterations":20,"groups":4,"shots":1000,"batch":5,"dispatch_s":.2,"batch_wait_s":0.0,"shot_s":4e-6,"classical_s":1.8,"bytes_per_result":8,"objective":"energy-v1","input_id":"molecule-X"}
baseline = simulate(config)
unbatched = simulate({**config, "batch":1})
counterfactual = simulate({**config, "dispatch_s":.01, "batch_wait_s":.03})
counter_unbatched = simulate({**config, "batch":1, "dispatch_s":.01, "batch_wait_s":.03})
classical_baseline = {"task":("energy-v1","molecule-X"), "seconds":2.4}
assert baseline["calls"] == 20 and baseline["shots"] == 80000 and baseline["bytes"] == 640000
assert abs(baseline["total"] - 6.12) < 1e-12 and baseline["spans"]["qpu"] < baseline["total"]
assert baseline["total"] < unbatched["total"] and counterfactual["total"] > counter_unbatched["total"]
assert set(baseline["parents"].values()) == {"workflow"} and baseline["task"] == classical_baseline["task"]
print(f"PASS: 63 workflow evidence calls={baseline['calls']} total={baseline['total']:.2f}s qpu={baseline['spans']['qpu']:.2f}s batching_reversal={counterfactual['total']:.2f}>{counter_unbatched['total']:.2f}")
