#!/usr/bin/env python3 """One engine, one job: the adapters that put a corpus circuit to each engine under test. python3 -I engines.py ENGINE JOB.json ANSWER.json ENGINE is one of ADAPTERS below, named `.`. Each runs in a process of its own, so its peak memory is its own. A job carries the circuit's steps (corpus.py), its width and what is asked: state the whole state, written to job["state_out"] as complex128 pairs, qubit 0 the lowest bit of the index counts job["shots"] samples of the classical bits at job["seed"]: {bits, bit 0 last: n} amplitudes the amplitudes of job["bitstrings"] (qubit 0 last, as OpenQASM writes them) stabilizers the stabilizer group, as signed strings of XYZ_ with qubit 0 first expectation job["observable"] under job["noise"] qec_model a circuit text's detector error model: [[p, [detectors], [observables]]] qec_sample how often each detector and observable fired over job["shots"] The third-party engines take each gate by their own name for it, where they have one, so a gate an engine defines differently shows as a difference. Where an engine has no such gate, the adapter says so in `notes` and gives it the gate's matrix from exact.py instead. An answer also names what the engine was set to (precision, fusion, threads), the seconds its own call took (`seconds`; for Aer the simulator's own time for the experiment, its whole `run` call beside it as `call_seconds`), and the peak resident memory of the process that ran the engine, as the operating system counts it for that process alone (`engine_peak_rss_bytes`: on Linux VmHWM, which a child does not inherit from its parent the way ru_maxrss does). A job an engine cannot run is answered {"ok": false, "unsupported": why}. A job may carry `inject`, one of compare.py's faults for the engine under test: the adapter then refuses, crashes, hangs or moves the angles before the engine reads them, as compare.py asks. """ import json import os import struct import subprocess import sys import time sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import exact # noqa: E402 class Unsupported(Exception): pass def threads(job): return int(job.get("threads") or 1) def steps_of(job): return job["steps"] def gates_only(job): for s in job["steps"]: if "g" not in s: raise Unsupported("this adapter runs programs with no measurement in the middle") return job["steps"] def matrix_np(name, args): import numpy as np m = exact.one_qubit_matrix(name, args) s = float(exact.ONE) return np.array([[complex(m[0][0] / s, m[0][1] / s), complex(m[1][0] / s, m[1][1] / s)], [complex(m[2][0] / s, m[2][1] / s), complex(m[3][0] / s, m[3][1] / s)]]) def write_state(path, amps_le): """complex128 pairs, little-endian, qubit 0 the lowest index bit.""" import numpy as np np.ascontiguousarray(amps_le, dtype=np.complex128).tofile(path) def reverse_qubits(vec, n): """A state with qubit 0 as the highest index bit, reordered to qubit 0 lowest.""" import numpy as np return np.ascontiguousarray(np.asarray(vec).reshape([2] * n).transpose(list(range(n - 1, -1, -1))).reshape(-1)) if n > 1 else np.asarray(vec) # ------------------------------------------------------------------ the engine under test def own_peak_rss(): """This process's peak resident set, in bytes: VmHWM on Linux (its own address space only), ru_maxrss on macOS (bytes there).""" if sys.platform.startswith("linux"): for line in open("/proc/self/status"): if line.startswith("VmHWM:"): return int(line.split()[1]) * 1024 import resource r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss return r if sys.platform == "darwin" else r * 1024 def wai_driver(job_kind, payload, inject=None): exe = os.environ.get("WAI_CROSS_SIMULATOR") if not exe: raise Unsupported("WAI_CROSS_SIMULATOR names no driver") import tempfile if inject == "refuse": return {"ok": False, "refused": "an injected refusal"} if inject == "crash": raise RuntimeError("the driver exited 101: an injected crash") if inject == "hang" and job_kind == "qec_sample": time.sleep(10**6) if inject == "qec_words" and job_kind == "qec_model": return {"ok": False, "refused": "line 3: an injected refusal"} if inject == "qec_crash" and job_kind == "qec_model": raise RuntimeError("the driver exited 101: an injected crash") if inject == "exact_words" and job_kind == "state": return {"ok": False, "refused": "an injected refusal, in other words"} payload = dict(payload, kind=job_kind) with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f: json.dump(payload, f) path = f.name with tempfile.TemporaryFile() as so, tempfile.TemporaryFile() as se: try: p = subprocess.Popen([exe, path], stdout=so, stderr=se) # wait4 gives the driver's own peak on macOS; on Linux the driver reports VmHWM itself. _, status, ru = os.wait4(p.pid, 0) finally: os.unlink(path) so.seek(0) se.seek(0) stdout, stderr = so.read().decode(errors="replace"), se.read().decode(errors="replace") code = os.waitstatus_to_exitcode(status) if code != 0: raise RuntimeError("the driver exited %d: %s" % (code, stderr[-2000:])) v = json.loads(stdout) peak = v.get("peak_rss_bytes") or (ru.ru_maxrss if sys.platform == "darwin" else None) if not v["ok"]: return {"ok": False, "refused": v["refused"]} return {"ok": True, **v["answer"], "engine_peak_rss_bytes": peak} def rounded_angles(steps): """Every angle moved to the nearest m/2^32 of a turn: what a reader that rounds would read.""" from fractions import Fraction out = [] for s in steps: if "a" in s: new = [] for a in s["a"]: kind, v = exact.parse_angle(a) turns = v if kind == "t" else v / (2 * Fraction(exact._PI, 1 << (exact.PREC + exact.GUARD))) new.append("t:%d/32" % round(turns * (1 << 32))) s = dict(s, a=new) out.append(s) return out def wai_state(job, method, controls=None): from corpus import qasm_of steps = gates_only(job) if job.get("inject") == "accept": steps = rounded_angles(steps) q = qasm_of({"n": job["n"], "bits": 0, "steps": steps}) if job.get("perf"): # Timed: the state is made and not written. return wai_driver("state", {"qasm": q, "method": method, "fusion": job.get("fusion", 3), "ops": False, "controls": controls}, job.get("inject")) raw = job["state_out"] + ".raw" a = wai_driver("state", {"qasm": q, "method": method, "state_out": raw, "fusion": job.get("fusion", 3), "ops": method == "exact", "controls": controls}, job.get("inject")) if not a["ok"]: return a import numpy as np if method == "exact": ints = np.fromfile(raw, dtype="