Method, Pass, Channel and FrontEnd, and one registry the planner, the run, the compile, the conversions and the verdict go through. Every built-in registers through the same trait a host's own implements: the twelve methods and the pulse simulator, the eight passes, the five channels and the three formats. Registry::with_builtins() and register(...) are explicit and stay in the process. The engine checks what a registered component gives: a method's class and the members the engine writes, a pass's output against its input on the whole circuit by the ladder or a distance bound, a channel's Kraus operators completely positive and trace preserving at each job's values, a format's program reading. A result a registered component took part in carries `components`, each identity (name, version, descriptor digest) marked registered and certified false, sealed by its receipt; a result of built-ins alone gives the bytes it gave, the eight pinned job digests holding through any registry. Refusal codes J060 to J065. The example examples/extension_points.rs, docs/extension-points.md, and the CI leg wai-quantum-registry.yml. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1299 lines
63 KiB
Rust
1299 lines
63 KiB
Rust
//! Compilation with a certificate: a program carried through named passes,
|
||
//! each checked where it acts, and one record of what every pass did, how it
|
||
//! was checked and how far the output is from the program.
|
||
//!
|
||
//! The passes ([`PASSES`]):
|
||
//!
|
||
//! - `lower`: every gate in the basis `h x y z s sdg t tdg cx cz swap rz`,
|
||
//! each decomposition held to its gate's matrix on the gate's qubits.
|
||
//! - `simplify`: the engine's rewrites ([`super::rewrite`]), each checked on
|
||
//! the qubits it touches.
|
||
//! - `phase_fold`: phase gates on one parity merged ([`super::synth::phase`]),
|
||
//! checked by recomputing the phase polynomial: a proof when every angle is
|
||
//! a multiple of `π/4`.
|
||
//! - `resynth_1q`: each run of one-qubit gates written again, when that is
|
||
//! shorter: a run of Clifford+T gates in its normal form with the fewest
|
||
//! `T` (an exact identity in ring arithmetic), any other run as one
|
||
//! rotation (compared on its 2×2 matrix).
|
||
//! - `resynth_2q`: each block of gates on one pair of qubits written again
|
||
//! by the KAK decomposition when that uses fewer CNOTs, compared on its
|
||
//! 4×4 matrix.
|
||
//! - `approximate` (basis `clifford_t`): every rotation at an angle that is
|
||
//! not a multiple of `π/4` written as a Clifford+T word within its share
|
||
//! of the ε budget ([`super::synth::rz`]). The word is checked in exact
|
||
//! arithmetic and its distance recomputed from the word and rounded up.
|
||
//!
|
||
//! - `twirl` (asked for with `"twirl"`): each two-qubit Clifford gate (`cx`,
|
||
//! `cz`, `swap`) framed by a Pauli drawn from a seeded stream and the Pauli
|
||
//! it becomes through the gate, so that the gate is unchanged and its noise,
|
||
//! averaged over such instances, becomes a Pauli channel. One seed is one
|
||
//! instance; averaging over instances is the caller's (a mitigated job does
|
||
//! it exactly).
|
||
//! - `dd` (asked for with `"dd"`): dynamical decoupling, a sequence of
|
||
//! Paulis (`xx`, or `xy4`: `x y x y`) placed in each window of at least
|
||
//! that many layers in which a qubit is idle between gates or before the
|
||
//! end, spread across the window. It suppresses coherent noise on idle
|
||
//! qubits; the engine's noise models follow gates, so in its simulations it
|
||
//! only adds gates, and it is offered for programs that leave here for a
|
||
//! device.
|
||
//!
|
||
//! Both are checked to be the program without noise by the equivalence
|
||
//! ladder ([`super::equiv`]): on the whole circuit where the ladder decides
|
||
//! it exactly (a tableau, every basis state, the rewrites), and, at any
|
||
//! width, on each distinct framed gate or sequence on its own qubits (a
|
||
//! tableau each), with the reordering of whole layers kept per qubit.
|
||
//!
|
||
//! A level is an ordered list of them ([`pipeline`]); `twirl` and `dd` come
|
||
//! last, in that order. A pass whose check fails is not applied, and its
|
||
//! record says so; the program goes on as it was.
|
||
//!
|
||
//! The certificate states each pass's counts before and after, its check
|
||
//! (`how`) and verdict: `proof` (an exact argument: a tableau, ring
|
||
//! arithmetic, a phase polynomial in whole eighths of a turn), `exact` (equal
|
||
//! to the rounding of doubles), `approximate` (a stated distance), and the
|
||
//! budget: the operator-norm distance between the output and the program,
|
||
//! up to a global phase, is at most `epsilon.bound` (the sum of the passes'
|
||
//! distances, by the triangle inequality), which is at most
|
||
//! `epsilon.budget`. Up to [`WHOLE_MAX_QUBITS`] qubits the whole output is
|
||
//! also measured against the whole program on their dense unitaries.
|
||
|
||
use super::registry::{Check, Kind as ComponentKind, Pass, PassInput, PassOutput, Registry};
|
||
use super::rewrite;
|
||
use super::synth::{euler, kak, lower, mat, phase, ring, rz};
|
||
use crate::qasm::{Kind, Op, Program};
|
||
use serde_json::{Value, json};
|
||
use std::collections::BTreeMap;
|
||
|
||
/// The ε budget a compile takes when none is given.
|
||
pub const DEFAULT_EPSILON: f64 = 1e-6;
|
||
/// The level a compile runs when none is given.
|
||
pub const DEFAULT_LEVEL: u8 = 1;
|
||
pub const MAX_LEVEL: u8 = 2;
|
||
/// The widest program whose whole output is measured on dense unitaries.
|
||
pub const WHOLE_MAX_QUBITS: u32 = super::equiv::EXACT_MAX_QUBITS;
|
||
/// Gate applications times dense entries the whole measurement may spend.
|
||
const WHOLE_MAX_WORK: f64 = 2e8;
|
||
/// The most gates a compile takes.
|
||
pub const MAX_GATES: usize = 200_000;
|
||
/// The most gates the rewriting pass takes; past it, the pass is skipped.
|
||
const SIMPLIFY_MAX_GATES: usize = 20_000;
|
||
|
||
/// Every pass, by name, with what it does.
|
||
pub const PASSES: &[(&str, &str)] = &[
|
||
("lower", "every gate in the basis h x y z s sdg t tdg cx cz swap rz, each decomposition checked against its gate's matrix"),
|
||
("simplify", "the engine's rewrites (cancel, merge, conjugate, carry past), each checked on the qubits it touches"),
|
||
("phase_fold", "phase gates on one parity merged into one, checked by recomputing the phase polynomial"),
|
||
("resynth_1q", "each run of one-qubit gates written again when shorter: Clifford+T runs in normal form (fewest T), others as one rotation"),
|
||
("resynth_2q", "each block on one pair of qubits written again with at most three CNOTs (KAK) when that uses fewer"),
|
||
("approximate", "every rotation off the multiples of π/4 written as a Clifford+T word within its share of the ε budget"),
|
||
("twirl", "each two-qubit Clifford gate framed by a seeded random Pauli and its image through the gate: the same gate, its noise made Pauli on average"),
|
||
("dd", "a dynamical-decoupling sequence (xx or xy4) in each window where a qubit idles long enough, spread across the window"),
|
||
];
|
||
|
||
/// A dynamical-decoupling sequence.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub enum Dd {
|
||
/// `x x`.
|
||
Xx,
|
||
/// `x y x y`.
|
||
Xy4,
|
||
}
|
||
|
||
impl Dd {
|
||
pub fn name(self) -> &'static str {
|
||
match self {
|
||
Dd::Xx => "xx",
|
||
Dd::Xy4 => "xy4",
|
||
}
|
||
}
|
||
fn gates(self) -> &'static [Kind] {
|
||
match self {
|
||
Dd::Xx => &[Kind::X, Kind::X],
|
||
Dd::Xy4 => &[Kind::X, Kind::Y, Kind::X, Kind::Y],
|
||
}
|
||
}
|
||
}
|
||
|
||
/// What the compile writes the program in.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub enum Basis {
|
||
/// Clifford+T: every rotation approximated within the budget. Runs on the
|
||
/// bit-exact method.
|
||
CliffordT,
|
||
/// The standard gates at any angle: nothing approximated.
|
||
Standard,
|
||
}
|
||
|
||
impl Basis {
|
||
pub fn name(self) -> &'static str {
|
||
match self {
|
||
Basis::CliffordT => "clifford_t",
|
||
Basis::Standard => "standard",
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A compile's options.
|
||
#[derive(Clone, Debug, PartialEq)]
|
||
pub struct Options {
|
||
/// The most the output may differ from the program, in the operator norm
|
||
/// up to a global phase.
|
||
pub epsilon: f64,
|
||
pub level: u8,
|
||
pub basis: Basis,
|
||
/// Also keep the circuit after each pass ([`Compiled::stages`]).
|
||
pub stages: bool,
|
||
/// Twirl the two-qubit Clifford gates, with this seed.
|
||
pub twirl: Option<u64>,
|
||
/// Decouple idle qubits with this sequence.
|
||
pub dd: Option<Dd>,
|
||
}
|
||
|
||
impl Default for Options {
|
||
fn default() -> Options {
|
||
Options { epsilon: DEFAULT_EPSILON, level: DEFAULT_LEVEL, basis: Basis::CliffordT, stages: false, twirl: None, dd: None }
|
||
}
|
||
}
|
||
|
||
impl Options {
|
||
/// `{"epsilon", "level", "basis", "stages"}`, each optional.
|
||
pub fn read(v: &Value) -> Result<Options, String> {
|
||
let mut o = Options::default();
|
||
if !v.is_object() && !v.is_null() {
|
||
return Err("compile options are an object: {\"epsilon\", \"level\", \"basis\"}".into());
|
||
}
|
||
if let Some(e) = v.get("epsilon").filter(|x| !x.is_null()) {
|
||
o.epsilon = e.as_f64().filter(|x| x.is_finite() && *x > 0.0).ok_or("\"epsilon\" is a positive number: the most the output may differ from the program")?;
|
||
}
|
||
if let Some(l) = v.get("level").filter(|x| !x.is_null()) {
|
||
o.level = l.as_u64().filter(|x| *x <= u64::from(MAX_LEVEL)).ok_or(format!("\"level\" is 0 to {MAX_LEVEL}"))? as u8;
|
||
}
|
||
// The gate set is the compile's basis; a target is a device, which a
|
||
// compile is not given. Left unread, `target` would leave the default,
|
||
// Clifford+T, where another gate set was asked for.
|
||
if v.get("target").is_some_and(|x| !x.is_null()) {
|
||
return Err("\"target\" names a device; the gate set a compile writes is its \"basis\": \"clifford_t\" or \"standard\"".into());
|
||
}
|
||
if let Some(t) = v.get("basis").filter(|x| !x.is_null()) {
|
||
o.basis = match t.as_str() {
|
||
Some("clifford_t") => Basis::CliffordT,
|
||
Some("standard") => Basis::Standard,
|
||
_ => return Err("\"basis\" is \"clifford_t\" (every rotation approximated within ε) or \"standard\" (nothing approximated)".into()),
|
||
};
|
||
}
|
||
if let Some(x) = v.get("stages").filter(|x| !x.is_null()) {
|
||
o.stages = x.as_bool().ok_or("\"stages\" is true or false: whether to give the circuit after each pass")?;
|
||
}
|
||
match v.get("twirl") {
|
||
None | Some(Value::Null) | Some(Value::Bool(false)) => {}
|
||
Some(Value::Bool(true)) => o.twirl = Some(1),
|
||
Some(t) => {
|
||
let seed = t.get("seed").and_then(Value::as_u64).filter(|s| *s < 1 << 53).ok_or("\"twirl\" is true or {\"seed\": N}: the seed of the Paulis drawn")?;
|
||
o.twirl = Some(seed);
|
||
}
|
||
}
|
||
match v.get("dd") {
|
||
None | Some(Value::Null) | Some(Value::Bool(false)) => {}
|
||
Some(d) => {
|
||
let name = d.as_str().or_else(|| d.get("sequence").and_then(Value::as_str)).unwrap_or(if d.as_bool() == Some(true) { "xx" } else { "" });
|
||
o.dd = Some(match name {
|
||
"xx" => Dd::Xx,
|
||
"xy4" => Dd::Xy4,
|
||
_ => return Err("\"dd\" is \"xx\" or \"xy4\" (or {\"sequence\": …}): the decoupling sequence".into()),
|
||
});
|
||
}
|
||
}
|
||
if o.basis == Basis::CliffordT && o.epsilon > rz::MAX_EPSILON {
|
||
return Err(format!("ε = {}: at most {} for Clifford+T", o.epsilon, rz::MAX_EPSILON));
|
||
}
|
||
Ok(o)
|
||
}
|
||
|
||
pub fn to_json(&self) -> Value {
|
||
let mut o = json!({ "epsilon": self.epsilon, "level": self.level, "basis": self.basis.name() });
|
||
if let Some(seed) = self.twirl {
|
||
o["twirl"] = json!({ "seed": seed });
|
||
}
|
||
if let Some(d) = self.dd {
|
||
o["dd"] = json!(d.name());
|
||
}
|
||
o
|
||
}
|
||
}
|
||
|
||
/// The passes a level runs, in order. A rewrite may write `h·rz·h` as `rx`
|
||
/// or `h·s·h` as `sx`, so `lower` comes again before the approximation and
|
||
/// last.
|
||
pub fn pipeline(o: &Options) -> Vec<&'static str> {
|
||
let mut p = base_pipeline(o);
|
||
if o.twirl.is_some() {
|
||
p.push("twirl");
|
||
}
|
||
if o.dd.is_some() {
|
||
p.push("dd");
|
||
}
|
||
p
|
||
}
|
||
|
||
fn base_pipeline(o: &Options) -> Vec<&'static str> {
|
||
match (o.basis, o.level) {
|
||
(Basis::CliffordT, 0) => vec!["lower", "approximate"],
|
||
(Basis::CliffordT, 1) => vec!["simplify", "lower", "simplify", "phase_fold", "lower", "approximate", "phase_fold", "simplify", "lower"],
|
||
(Basis::CliffordT, _) => vec!["simplify", "lower", "simplify", "resynth_2q", "lower", "resynth_1q", "phase_fold", "simplify", "lower", "approximate", "resynth_1q", "phase_fold", "simplify", "lower"],
|
||
(Basis::Standard, 0) => vec!["lower"],
|
||
(Basis::Standard, 1) => vec!["simplify", "lower", "simplify", "phase_fold", "simplify"],
|
||
(Basis::Standard, _) => vec!["simplify", "lower", "simplify", "resynth_2q", "lower", "phase_fold", "resynth_1q", "simplify"],
|
||
}
|
||
}
|
||
|
||
/// Whether a gate has an angle off the multiples of `π/4`.
|
||
fn arbitrary(o: &Op) -> bool {
|
||
!o.params.is_empty() && o.params.iter().any(|&p| lower::eighths(p).is_none())
|
||
}
|
||
|
||
/// A circuit's counts, each gate taken as the compile basis writes it
|
||
/// ([`lower::lower`]): gates by width as given, two-qubit gates as CNOTs (a
|
||
/// `swap` three), `T` gates (so `rx(π/4)` counts as the `T` it is), rotations
|
||
/// at angles off the multiples of `π/4`, and depth.
|
||
pub fn counts(n: u32, ops: &[Op]) -> Value {
|
||
let (mut one, mut two, mut three, mut t, mut rot, mut cx) = (0u64, 0u64, 0u64, 0u64, 0u64, 0u64);
|
||
let mut front = vec![0u64; n as usize];
|
||
let mut depth = 0u64;
|
||
for o in ops {
|
||
match o.qubits.len() {
|
||
1 => one += 1,
|
||
2 => two += 1,
|
||
_ => three += 1,
|
||
}
|
||
let low = lower::lower(o);
|
||
for x in &low {
|
||
match x.kind {
|
||
Kind::T | Kind::Tdg => t += 1,
|
||
Kind::Rz => rot += 1,
|
||
Kind::Swap => cx += 3,
|
||
_ if x.qubits.len() == 2 => cx += 1,
|
||
_ => {}
|
||
}
|
||
}
|
||
let d = o.qubits.iter().map(|&q| front[q as usize]).max().unwrap_or(0) + 1;
|
||
for &q in &o.qubits {
|
||
front[q as usize] = d;
|
||
}
|
||
depth = depth.max(d);
|
||
}
|
||
json!({ "gates": ops.len(), "one_qubit": one, "two_qubit": two, "three_qubit": three, "cnots": cx, "t_count": t, "rotations": rot, "depth": depth })
|
||
}
|
||
|
||
/// The counts a pass weighs, in order: for Clifford+T the rotations first
|
||
/// (each costs about `3·log₂(1/ε)` T gates), then CNOTs, T gates and gates;
|
||
/// for the standard gates CNOTs, then gates.
|
||
fn cost(ops: &[Op], basis: Basis) -> (u64, u64, u64, u64) {
|
||
let c = counts(1 + ops.iter().flat_map(|o| o.qubits.iter()).copied().max().unwrap_or(0), ops);
|
||
let g = |k: &str| c[k].as_u64().unwrap_or(0);
|
||
match basis {
|
||
Basis::CliffordT => (g("rotations"), g("cnots"), g("t_count"), g("gates")),
|
||
Basis::Standard => (g("cnots"), g("gates"), 0, 0),
|
||
}
|
||
}
|
||
|
||
/// A pass's outcome: the gates, its check, and the distance it adds.
|
||
struct Outcome {
|
||
ops: Vec<Op>,
|
||
check: Value,
|
||
distance: f64,
|
||
}
|
||
|
||
fn unchanged(ops: &[Op], why: &str) -> Outcome {
|
||
Outcome { ops: ops.to_vec(), check: json!({ "how": "none", "verdict": "skipped", "why": why }), distance: 0.0 }
|
||
}
|
||
|
||
fn pass_lower(ops: &[Op]) -> Result<Outcome, String> {
|
||
let mut out = Vec::with_capacity(ops.len());
|
||
let mut seen: BTreeMap<String, f64> = BTreeMap::new();
|
||
for o in ops {
|
||
let named_rz = o.kind == Kind::Rz && lower::eighths(o.params[0]).is_some();
|
||
if lower::in_basis(o.kind) && !named_rz {
|
||
out.push(o.clone());
|
||
continue;
|
||
}
|
||
let key = format!("{:?}{}{:?}", o.kind, o.qubits.len(), o.params.iter().map(|p| p.to_bits()).collect::<Vec<_>>());
|
||
let d = *seen.entry(key).or_insert_with(|| lower::check(o));
|
||
if d > 1e-12 {
|
||
return Err(format!("the decomposition of {:?} is {d:e} from its matrix", o.kind));
|
||
}
|
||
out.extend(lower::lower(o));
|
||
}
|
||
let worst = seen.values().fold(0.0f64, |a, &b| a.max(b));
|
||
Ok(Outcome {
|
||
ops: out,
|
||
check: json!({ "how": "each kind of gate against its decomposition, on its own qubits", "verdict": "exact", "checked": seen.len(), "distance": worst }),
|
||
distance: 0.0,
|
||
})
|
||
}
|
||
|
||
fn pass_simplify(n: u32, ops: &[Op]) -> Outcome {
|
||
if ops.len() > SIMPLIFY_MAX_GATES {
|
||
return unchanged(ops, &format!("{} gates, more than the rewriting pass takes ({SIMPLIFY_MAX_GATES})", ops.len()));
|
||
}
|
||
let p = Program { n_qubits: n, ops: ops.to_vec(), measured: Vec::new(), n_bits: 0, dynamic: None, extra: Default::default() };
|
||
let (out, steps) = rewrite::simplify(&p);
|
||
Outcome { ops: out, check: json!({ "how": "rewrites, each checked on the qubits it touches by tableau or on every basis state", "verdict": "exact", "steps": steps.len() }), distance: 0.0 }
|
||
}
|
||
|
||
fn pass_phase_fold(n: u32, ops: &[Op]) -> Outcome {
|
||
let (out, merged) = phase::fold(n, ops);
|
||
match phase::polynomial(n, ops, &out) {
|
||
phase::Same::Exactly => Outcome { ops: out, check: json!({ "how": "the phase polynomial recomputed: the same gates once the phases are taken out, and the same angle on every parity, in whole eighths of a turn", "verdict": "proof", "merged": merged }), distance: 0.0 },
|
||
phase::Same::ToRounding => Outcome { ops: out, check: json!({ "how": "the phase polynomial recomputed: the same gates once the phases are taken out, and the same angle on every parity to the rounding of the sums", "verdict": "exact", "merged": merged }), distance: 0.0 },
|
||
phase::Same::Different(why) => Outcome { ops: ops.to_vec(), check: json!({ "how": "the phase polynomial recomputed", "verdict": "rejected", "why": why }), distance: 0.0 },
|
||
}
|
||
}
|
||
|
||
/// Maximal runs of one-qubit gates on one qubit with no other gate on it
|
||
/// between: each as the indices of its gates, in order.
|
||
fn runs(n: u32, ops: &[Op]) -> Vec<Vec<usize>> {
|
||
let mut open: Vec<Vec<usize>> = vec![Vec::new(); n as usize];
|
||
let mut out = Vec::new();
|
||
for (i, o) in ops.iter().enumerate() {
|
||
if o.qubits.len() == 1 {
|
||
open[o.qubits[0] as usize].push(i);
|
||
} else {
|
||
for &q in &o.qubits {
|
||
let r = std::mem::take(&mut open[q as usize]);
|
||
if !r.is_empty() {
|
||
out.push(r);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
out.extend(open.into_iter().filter(|r| !r.is_empty()));
|
||
out.sort();
|
||
out
|
||
}
|
||
|
||
/// `ops` with the gates at each run's indices replaced: the new gates go
|
||
/// where the run's first gate was.
|
||
fn splice(ops: &[Op], replaced: &BTreeMap<usize, (Vec<usize>, Vec<Op>)>) -> Vec<Op> {
|
||
let mut drop = vec![false; ops.len()];
|
||
let mut put: BTreeMap<usize, &Vec<Op>> = BTreeMap::new();
|
||
for (first, (idx, new)) in replaced {
|
||
for &i in idx {
|
||
drop[i] = true;
|
||
}
|
||
put.insert(*first, new);
|
||
}
|
||
let mut out = Vec::with_capacity(ops.len());
|
||
for (i, o) in ops.iter().enumerate() {
|
||
if let Some(new) = put.get(&i) {
|
||
out.extend(new.iter().cloned());
|
||
}
|
||
if !drop[i] {
|
||
out.push(o.clone());
|
||
}
|
||
}
|
||
out
|
||
}
|
||
|
||
fn exact_gate(o: &Op) -> Option<Vec<Kind>> {
|
||
match o.kind {
|
||
Kind::H | Kind::X | Kind::Y | Kind::Z | Kind::S | Kind::Sdg | Kind::T | Kind::Tdg | Kind::Id => Some(vec![o.kind]),
|
||
Kind::Rz | Kind::P => lower::eighths(o.params[0]).map(|m| lower::quarter_gates(m, 0).into_iter().map(|g| g.kind).collect()),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
fn t_of(ops: &[Op]) -> usize {
|
||
ops.iter().filter(|o| matches!(o.kind, Kind::T | Kind::Tdg)).count()
|
||
}
|
||
|
||
fn pass_resynth_1q(n: u32, ops: &[Op], basis: Basis) -> Outcome {
|
||
let mut replaced = BTreeMap::new();
|
||
let (mut proved, mut measured, mut worst) = (0usize, 0usize, 0.0f64);
|
||
for run in runs(n, ops) {
|
||
if run.len() < 2 {
|
||
continue;
|
||
}
|
||
let q = ops[run[0]].qubits[0];
|
||
let old: Vec<Op> = run.iter().map(|&i| ops[i].clone()).collect();
|
||
let word: Option<Vec<Kind>> = old.iter().map(exact_gate).collect::<Option<Vec<_>>>().map(|w| w.concat());
|
||
if let Some(word) = word {
|
||
// Exact: the normal form, checked to multiply back.
|
||
let Ok(w) = ring::synthesize(&ring::word_matrix(&word)) else { continue };
|
||
let new: Vec<Op> = w.gates.iter().map(|&k| Op { kind: k, qubits: vec![q], params: vec![] }).collect();
|
||
if (t_of(&new), new.len()) < (t_of(&old), old.len()) {
|
||
replaced.insert(run[0], (run.clone(), new));
|
||
proved += 1;
|
||
}
|
||
continue;
|
||
}
|
||
let local: Vec<Op> = old.iter().map(|o| Op { qubits: vec![0], ..o.clone() }).collect();
|
||
let u = mat::one_qubit(&local);
|
||
let new: Vec<Op> = match basis {
|
||
Basis::Standard => euler::ops(&u, 0),
|
||
Basis::CliffordT => {
|
||
// Rz(β)·Ry(γ)·Rz(δ) = Rz(β + π/2)·H·Rz(γ)·H·Rz(δ − π/2).
|
||
let e = euler::zyz(&u);
|
||
let h = Op { kind: Kind::H, qubits: vec![0], params: vec![] };
|
||
let mut v = lower::rz(e.delta - std::f64::consts::FRAC_PI_2, 0);
|
||
if lower::eighths(e.gamma) == Some(0) {
|
||
v = lower::rz(e.beta + e.delta, 0);
|
||
} else {
|
||
v.push(h.clone());
|
||
v.extend(lower::rz(e.gamma, 0));
|
||
v.push(h);
|
||
v.extend(lower::rz(e.beta + std::f64::consts::FRAC_PI_2, 0));
|
||
}
|
||
v
|
||
}
|
||
};
|
||
if cost(&new, basis) >= cost(&local, basis) {
|
||
continue;
|
||
}
|
||
let d = mat::distance(&mat::Dense::of(1, &new), &mat::Dense::of(1, &local));
|
||
if d > 1e-12 {
|
||
continue;
|
||
}
|
||
worst = worst.max(d);
|
||
measured += 1;
|
||
replaced.insert(run[0], (run.clone(), new.into_iter().map(|o| Op { qubits: vec![q], ..o }).collect()));
|
||
}
|
||
let verdict = if measured > 0 { "exact" } else { "proof" };
|
||
Outcome {
|
||
ops: splice(ops, &replaced),
|
||
check: json!({ "how": "Clifford+T runs multiplied out in ring arithmetic against their normal form; other runs compared on their 2×2 matrices", "verdict": verdict, "exact_runs": proved, "measured_runs": measured, "distance": worst }),
|
||
distance: worst,
|
||
}
|
||
}
|
||
|
||
/// The CNOTs a block's two-qubit gates cost.
|
||
fn cnot_cost(ops: &[Op]) -> usize {
|
||
ops.iter().filter(|o| o.qubits.len() == 2).map(|o| if o.kind == Kind::Swap { 3 } else { 1 }).sum()
|
||
}
|
||
|
||
fn pass_resynth_2q(n: u32, ops: &[Op], basis: Basis) -> Outcome {
|
||
// Blocks: gates on one pair with nothing else on either qubit between.
|
||
struct Block {
|
||
pair: (u32, u32),
|
||
idx: Vec<usize>,
|
||
}
|
||
let mut blocks: Vec<Block> = Vec::new();
|
||
let mut open: Vec<Option<usize>> = vec![None; n as usize];
|
||
let mut pending: Vec<Vec<usize>> = vec![Vec::new(); n as usize];
|
||
let close = |open: &mut Vec<Option<usize>>, blocks: &Vec<Block>, b: usize| {
|
||
let (p, q) = blocks[b].pair;
|
||
open[p as usize] = None;
|
||
open[q as usize] = None;
|
||
};
|
||
for (i, o) in ops.iter().enumerate() {
|
||
match o.qubits[..] {
|
||
[q] => match open[q as usize] {
|
||
Some(b) => blocks[b].idx.push(i),
|
||
None => pending[q as usize].push(i),
|
||
},
|
||
[a, b2] => {
|
||
let pair = (a.min(b2), a.max(b2));
|
||
match (open[a as usize], open[b2 as usize]) {
|
||
(Some(x), Some(y)) if x == y && blocks[x].pair == pair => blocks[x].idx.push(i),
|
||
(x, y) => {
|
||
for b in [x, y].into_iter().flatten() {
|
||
close(&mut open, &blocks, b);
|
||
}
|
||
let mut idx: Vec<usize> = std::mem::take(&mut pending[a as usize]);
|
||
idx.extend(std::mem::take(&mut pending[b2 as usize]));
|
||
idx.push(i);
|
||
idx.sort_unstable();
|
||
blocks.push(Block { pair, idx });
|
||
open[a as usize] = Some(blocks.len() - 1);
|
||
open[b2 as usize] = Some(blocks.len() - 1);
|
||
}
|
||
}
|
||
}
|
||
_ => {
|
||
for &q in &o.qubits {
|
||
if let Some(b) = open[q as usize] {
|
||
close(&mut open, &blocks, b);
|
||
}
|
||
pending[q as usize].clear();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
let mut replaced = BTreeMap::new();
|
||
let (mut done, mut worst) = (0usize, 0.0f64);
|
||
for b in &blocks {
|
||
let old: Vec<Op> = b.idx.iter().map(|&i| ops[i].clone()).collect();
|
||
let cnots = cnot_cost(&old);
|
||
if cnots < 3 && old.iter().filter(|o| o.qubits.len() == 2).count() < 2 {
|
||
continue;
|
||
}
|
||
let Some((new, d)) = kak::resynthesize(&old, b.pair.0, b.pair.1) else { continue };
|
||
if kak::cnots(&new) < cnots && self::cost(&new, basis) < self::cost(&old, basis) {
|
||
worst = worst.max(d);
|
||
done += 1;
|
||
// The new gates go where the block's last gate was: every gate
|
||
// between touches neither qubit.
|
||
let last = *b.idx.last().expect("a block has a gate");
|
||
replaced.insert(last, (b.idx.clone(), new));
|
||
}
|
||
}
|
||
// splice puts the replacement at the map's key: here the last index.
|
||
Outcome {
|
||
ops: splice(ops, &replaced),
|
||
check: json!({ "how": "each block compared with its replacement on their 4×4 matrices", "verdict": "exact", "blocks": done, "distance": worst }),
|
||
distance: worst,
|
||
}
|
||
}
|
||
|
||
fn pass_approximate(ops: &[Op], each: f64) -> Result<(Outcome, Vec<Value>), String> {
|
||
let mut out = Vec::with_capacity(ops.len());
|
||
let mut cache: BTreeMap<u64, rz::Rotation> = BTreeMap::new();
|
||
let (mut bound, mut count, mut t_added) = (0.0f64, 0usize, 0usize);
|
||
let mut detail = Vec::new();
|
||
for o in ops {
|
||
if o.kind != Kind::Rz || lower::eighths(o.params[0]).is_some() {
|
||
if arbitrary(o) {
|
||
return Err(format!("{:?} at an arbitrary angle reached the approximation pass unlowered", o.kind));
|
||
}
|
||
out.push(o.clone());
|
||
continue;
|
||
}
|
||
let theta = o.params[0];
|
||
let r = match cache.get(&theta.to_bits()) {
|
||
Some(r) => r.clone(),
|
||
None => {
|
||
let r = rz::rz(theta, each)?;
|
||
cache.insert(theta.to_bits(), r.clone());
|
||
r
|
||
}
|
||
};
|
||
// The word's distance, recomputed from the gates emitted.
|
||
let d = rz::word_distance(&r.gates, theta);
|
||
if d > each {
|
||
return Err(format!("the word for rz({theta}) is {d:e} away, more than its share {each:e}"));
|
||
}
|
||
bound += d;
|
||
count += 1;
|
||
t_added += r.t_count;
|
||
if detail.len() < 64 {
|
||
detail.push(json!({ "angle": theta, "t_count": r.t_count, "distance": d, "k": r.k }));
|
||
}
|
||
out.extend(r.gates.iter().map(|&k| Op { kind: k, qubits: o.qubits.clone(), params: vec![] }));
|
||
}
|
||
Ok((
|
||
Outcome {
|
||
ops: out,
|
||
check: json!({
|
||
"how": "each rotation's Clifford+T word multiplied out in exact ring arithmetic, and its operator-norm distance to the rotation recomputed from the word in fixed point and rounded up",
|
||
"verdict": if count > 0 { "approximate" } else { "proof" },
|
||
"rotations": count,
|
||
"epsilon_each": each,
|
||
"t_count": t_added,
|
||
"distance": bound,
|
||
}),
|
||
distance: bound,
|
||
},
|
||
detail,
|
||
))
|
||
}
|
||
|
||
/// A Pauli on one qubit as its symplectic bits `(x, z)`, and back.
|
||
fn pauli_kind(x: bool, z: bool) -> Option<Kind> {
|
||
match (x, z) {
|
||
(false, false) => None,
|
||
(true, false) => Some(Kind::X),
|
||
(true, true) => Some(Kind::Y),
|
||
(false, true) => Some(Kind::Z),
|
||
}
|
||
}
|
||
|
||
/// `P` on `(a, b)` carried through a two-qubit Clifford gate: the Pauli `G P G†`
|
||
/// is, up to a sign, as bits `(xa, za, xb, zb)`.
|
||
fn through(kind: Kind, p: (bool, bool, bool, bool)) -> (bool, bool, bool, bool) {
|
||
let (mut xa, mut za, mut xb, mut zb) = p;
|
||
match kind {
|
||
Kind::Cx => {
|
||
xb ^= xa;
|
||
za ^= zb;
|
||
}
|
||
Kind::Cz => {
|
||
za ^= xb;
|
||
zb ^= xa;
|
||
}
|
||
_ => {
|
||
std::mem::swap(&mut xa, &mut xb);
|
||
std::mem::swap(&mut za, &mut zb);
|
||
}
|
||
}
|
||
(xa, za, xb, zb)
|
||
}
|
||
|
||
fn program_of(n: u32, ops: Vec<Op>) -> Program {
|
||
Program { n_qubits: n, ops, measured: Vec::new(), n_bits: 0, dynamic: None, extra: Default::default() }
|
||
}
|
||
|
||
/// Whether two short circuits on the same few qubits are proved equal by the
|
||
/// ladder (renumbered onto qubits `0..k`).
|
||
fn window_equal(a: &[Op], b: &[Op]) -> bool {
|
||
let mut qs: Vec<u32> = a.iter().chain(b).flat_map(|o| o.qubits.iter().copied()).collect();
|
||
qs.sort_unstable();
|
||
qs.dedup();
|
||
let map = |ops: &[Op]| -> Vec<Op> { ops.iter().map(|o| Op { qubits: o.qubits.iter().map(|q| qs.binary_search(q).unwrap() as u32).collect(), ..o.clone() }).collect() };
|
||
let (pa, pb) = (program_of(qs.len() as u32, map(a)), program_of(qs.len() as u32, map(b)));
|
||
super::equiv::proved_equal(&pa, &pb)
|
||
}
|
||
|
||
/// The whole circuit's check by the ladder, where it decides exactly.
|
||
fn whole_check(n: u32, before: &[Op], after: &[Op]) -> Value {
|
||
let v = super::equiv::equivalent_programs(&program_of(n, before.to_vec()), &program_of(n, after.to_vec()));
|
||
let how = v["how"].as_str().unwrap_or("none").to_string();
|
||
json!({ "verdict": v["verdict"], "how": how })
|
||
}
|
||
|
||
fn pass_twirl(n: u32, ops: &[Op], seed: u64) -> Outcome {
|
||
let mut state = seed ^ 0x7417_1D00_F00D_5EED;
|
||
let mut draw = || {
|
||
state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
||
let mut z = state;
|
||
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
||
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
||
(z ^ (z >> 31)) >> 60
|
||
};
|
||
let mut out = Vec::with_capacity(ops.len() * 2);
|
||
let mut checked: BTreeMap<(String, u64), bool> = BTreeMap::new();
|
||
let mut twirled = 0usize;
|
||
for o in ops {
|
||
if !matches!(o.kind, Kind::Cx | Kind::Cz | Kind::Swap) || o.qubits.len() != 2 {
|
||
out.push(o.clone());
|
||
continue;
|
||
}
|
||
let r = draw();
|
||
let p = (r & 8 != 0, r & 4 != 0, r & 2 != 0, r & 1 != 0);
|
||
let q = through(o.kind, p);
|
||
let (a, b) = (o.qubits[0], o.qubits[1]);
|
||
let one = |k: Option<Kind>, at: u32| k.map(|kind| Op { kind, qubits: vec![at], params: vec![] });
|
||
let mut window: Vec<Op> = [one(pauli_kind(p.0, p.1), a), one(pauli_kind(p.2, p.3), b)].into_iter().flatten().collect();
|
||
window.push(o.clone());
|
||
window.extend([one(pauli_kind(q.0, q.1), a), one(pauli_kind(q.2, q.3), b)].into_iter().flatten());
|
||
let key = (format!("{:?}", o.kind), r);
|
||
let ok = *checked.entry(key).or_insert_with(|| window_equal(&window, std::slice::from_ref(o)));
|
||
if !ok {
|
||
return Outcome { ops: ops.to_vec(), check: json!({ "how": "each framed gate against the gate, by the equivalence ladder on its two qubits", "verdict": "rejected", "why": format!("a framed {:?} is not the gate", o.kind) }), distance: 0.0 };
|
||
}
|
||
twirled += 1;
|
||
out.extend(window);
|
||
}
|
||
let whole = whole_check(n, ops, &out);
|
||
if whole["verdict"] == "different" {
|
||
return Outcome { ops: ops.to_vec(), check: json!({ "how": "the whole circuit by the equivalence ladder", "verdict": "rejected", "why": "the twirled circuit is not the program" }), distance: 0.0 };
|
||
}
|
||
Outcome {
|
||
ops: out,
|
||
check: json!({
|
||
"how": "each distinct framed gate against the gate by the equivalence ladder on its qubits (a tableau each), and the whole circuit by the ladder where it decides",
|
||
"verdict": "proof",
|
||
"seed": seed,
|
||
"twirled": twirled,
|
||
"frames_checked": checked.len(),
|
||
"whole": whole,
|
||
}),
|
||
distance: 0.0,
|
||
}
|
||
}
|
||
|
||
fn pass_dd(n: u32, ops: &[Op], seq: Dd) -> Outcome {
|
||
// ASAP layers: each op at the first layer after the qubits it touches.
|
||
let mut front = vec![0usize; n as usize];
|
||
let mut layer_of = Vec::with_capacity(ops.len());
|
||
for o in ops {
|
||
let l = o.qubits.iter().map(|&q| front[q as usize]).max().unwrap_or(0);
|
||
for &q in &o.qubits {
|
||
front[q as usize] = l + 1;
|
||
}
|
||
layer_of.push(l);
|
||
}
|
||
let layers = front.iter().copied().max().unwrap_or(0);
|
||
let s = seq.gates();
|
||
// Each qubit's busy layers, then its idle windows between its first gate
|
||
// and the end.
|
||
let mut busy: Vec<Vec<usize>> = vec![Vec::new(); n as usize];
|
||
for (i, o) in ops.iter().enumerate() {
|
||
for &q in &o.qubits {
|
||
busy[q as usize].push(layer_of[i]);
|
||
}
|
||
}
|
||
let mut pulses: BTreeMap<usize, Vec<Op>> = BTreeMap::new();
|
||
let mut windows = 0usize;
|
||
for (q, b) in busy.iter().enumerate() {
|
||
let Some(&first) = b.first() else { continue };
|
||
let mut edges: Vec<usize> = b.clone();
|
||
edges.push(layers);
|
||
let mut prev = first;
|
||
for &next in &edges[1..] {
|
||
let (start, w) = (prev + 1, next.saturating_sub(prev + 1));
|
||
if w >= s.len() {
|
||
windows += 1;
|
||
for (k, &kind) in s.iter().enumerate() {
|
||
let at = start + ((2 * k + 1) * w) / (2 * s.len());
|
||
pulses.entry(at).or_default().push(Op { kind, qubits: vec![q as u32], params: vec![] });
|
||
}
|
||
}
|
||
prev = next;
|
||
}
|
||
}
|
||
if windows == 0 {
|
||
return unchanged(ops, &format!("no qubit idles for {} layers or more", s.len()));
|
||
}
|
||
// Layer by layer: the pulses of a layer, then its gates; each qubit's own
|
||
// order is kept.
|
||
let mut out = Vec::with_capacity(ops.len() + windows * s.len());
|
||
for l in 0..layers {
|
||
if let Some(p) = pulses.get(&l) {
|
||
out.extend(p.iter().cloned());
|
||
}
|
||
out.extend(ops.iter().zip(&layer_of).filter(|(_, x)| **x == l).map(|(o, _)| o.clone()));
|
||
}
|
||
let seq_ops: Vec<Op> = s.iter().map(|&kind| Op { kind, qubits: vec![0], params: vec![] }).collect();
|
||
let seq_ok = window_equal(&seq_ops, &[]);
|
||
let whole = whole_check(n, ops, &out);
|
||
if !seq_ok || whole["verdict"] == "different" {
|
||
return Outcome { ops: ops.to_vec(), check: json!({ "how": "the sequence against nothing, and the whole circuit, by the equivalence ladder", "verdict": "rejected", "why": "the decoupled circuit is not the program" }), distance: 0.0 };
|
||
}
|
||
Outcome {
|
||
ops: out,
|
||
check: json!({
|
||
"how": "the sequence against nothing by the equivalence ladder (a tableau), each qubit's gates kept in their order, and the whole circuit by the ladder where it decides",
|
||
"verdict": "proof",
|
||
"sequence": seq.name(),
|
||
"windows": windows,
|
||
"pulses": windows * s.len(),
|
||
"whole": whole,
|
||
"note": "decoupling suppresses coherent noise on idle qubits; the engine's noise models follow gates, so in its simulations the sequence only adds gates",
|
||
}),
|
||
distance: 0.0,
|
||
}
|
||
}
|
||
|
||
/// The rotations the approximation pass would write: `rz` gates off the
|
||
/// multiples of `π/4`.
|
||
fn rotations(ops: &[Op]) -> usize {
|
||
ops.iter().filter(|o| o.kind == Kind::Rz && lower::eighths(o.params[0]).is_none()).count()
|
||
}
|
||
|
||
/// A compiled program and its certificate.
|
||
#[derive(Clone, Debug)]
|
||
pub struct Compiled {
|
||
pub program: Program,
|
||
pub qasm: String,
|
||
pub certificate: Value,
|
||
/// With [`Options::stages`]: each pass's name and the circuit after it,
|
||
/// as OpenQASM 3, in order (not part of the certificate).
|
||
pub stages: Vec<(String, String)>,
|
||
}
|
||
|
||
fn hash_hex(bytes: &[u8]) -> String {
|
||
blake3::hash(bytes).as_bytes().iter().map(|b| format!("{b:02x}")).collect()
|
||
}
|
||
|
||
/// The BLAKE3 of a program's text, in hex, as a store names it.
|
||
pub fn hash_of_text(text: &str) -> String {
|
||
hash_hex(text.as_bytes())
|
||
}
|
||
|
||
/// A built-in pass's run (its registry entry's,
|
||
/// [`super::registry::Pass::run`]): the pass named `name` on `input`, with
|
||
/// its own check where it acts.
|
||
pub(crate) fn builtin_pass(name: &str, input: &PassInput<'_>) -> Result<PassOutput, String> {
|
||
let (n, ops, o) = (input.qubits, input.ops, input.options);
|
||
let out = match name {
|
||
"lower" => pass_lower(ops)?,
|
||
"simplify" => pass_simplify(n, ops),
|
||
"phase_fold" => pass_phase_fold(n, ops),
|
||
"resynth_1q" => pass_resynth_1q(n, ops, o.basis),
|
||
"resynth_2q" => pass_resynth_2q(n, ops, o.basis),
|
||
"twirl" => pass_twirl(n, ops, o.twirl.unwrap_or(1)),
|
||
"dd" => pass_dd(n, ops, o.dd.unwrap_or(Dd::Xx)),
|
||
"approximate" => {
|
||
let m = rotations(ops);
|
||
let left = o.epsilon - input.spent;
|
||
let each = if m == 0 { o.epsilon.min(rz::MAX_EPSILON) } else { (left / m as f64).min(rz::MAX_EPSILON) };
|
||
if m > 0 && each < rz::MIN_EPSILON {
|
||
return Err(format!("ε = {:e} shared by {m} rotations leaves {each:e} each, below the {:e} a rotation is approximated to; give a larger ε or fewer rotations", o.epsilon, rz::MIN_EPSILON));
|
||
}
|
||
let (out, detail) = pass_approximate(ops, each)?;
|
||
return Ok(PassOutput { ops: out.ops, check: out.check, distance: out.distance, epsilon: Some((m, each)), rotations: Some(detail) });
|
||
}
|
||
other => return Err(format!("no pass {other}")),
|
||
};
|
||
Ok(PassOutput { ops: out.ops, check: out.check, distance: out.distance, epsilon: None, rotations: None })
|
||
}
|
||
|
||
/// The pass names a compile's `passes` gives, each one `reg` holds: run
|
||
/// after the level's own and before twirling and decoupling. Refused: a name
|
||
/// no pass here has (`J060`), or `passes` that is not a list of names.
|
||
pub(crate) fn passes_of<'a>(reg: &Registry, v: Option<&'a Value>) -> Result<Vec<&'a str>, String> {
|
||
use super::refusal::{COMPILE, UNKNOWN_COMPONENT, tag};
|
||
let Some(v) = v.filter(|v| !v.is_null()) else { return Ok(Vec::new()) };
|
||
let names = v.as_array().and_then(|a| a.iter().map(Value::as_str).collect::<Option<Vec<&str>>>()).ok_or_else(|| tag(COMPILE, "compile: \"passes\" is a list of the names of passes to run after the level's"))?;
|
||
for name in &names {
|
||
if reg.pass(name).is_none() {
|
||
let here: Vec<&str> = reg.passes().map(|p| p.name()).collect();
|
||
return Err(tag(UNKNOWN_COMPONENT, format!("compile: no pass {name} here; the passes here are {}", here.join(", "))));
|
||
}
|
||
}
|
||
Ok(names)
|
||
}
|
||
|
||
/// A registered pass on `input`, checked by the engine on the whole circuit
|
||
/// by the check it declares ([`Check`]): passed over when the circuit holds
|
||
/// gates it does not take, and rejected (left out, the program going on as
|
||
/// it was) when its gates are not ones it declared it gives or its check does
|
||
/// not hold. Refused (`J064`): gates on qubits the program does not have, or
|
||
/// that do not read back.
|
||
fn registered_pass(pass: &dyn Pass, input: &PassInput<'_>) -> Result<PassOutput, String> {
|
||
use super::refusal::{CONTRACT, tag};
|
||
let (n, before, o) = (input.qubits, input.ops, input.options);
|
||
let name = pass.name();
|
||
let keep = |check: Value| PassOutput { ops: before.to_vec(), check, distance: 0.0, epsilon: None, rotations: None };
|
||
if !pass.takes().holds(before) {
|
||
return Ok(keep(json!({ "how": "none", "verdict": "skipped", "why": format!("the circuit holds gates outside what the pass takes ({})", pass.takes().as_str()) })));
|
||
}
|
||
let given = pass.run(input)?;
|
||
for g in &given.ops {
|
||
if let Some(&q) = g.qubits.iter().find(|&&q| q >= n) {
|
||
return Err(tag(CONTRACT, format!("the pass {name} gave a gate on qubit {q} of a {n}-qubit program")));
|
||
}
|
||
if (1..g.qubits.len()).any(|i| g.qubits[..i].contains(&g.qubits[i])) {
|
||
return Err(tag(CONTRACT, format!("the pass {name} gave a {:?} on one qubit twice", g.kind)));
|
||
}
|
||
}
|
||
let text = rewrite::render(&given.ops, n, &[], 0);
|
||
match crate::qasm::parse(&text) {
|
||
Ok(back) if back.ops.len() == given.ops.len() => {}
|
||
_ => return Err(tag(CONTRACT, format!("the pass {name} gave gates that do not read back as the same circuit"))),
|
||
}
|
||
let said = given.check.clone();
|
||
if !pass.gives().holds(&given.ops) {
|
||
return Ok(keep(json!({ "how": "the gates it gives, against what it declares", "verdict": "rejected", "why": format!("its output holds gates outside what it declares it gives ({})", pass.gives().as_str()), "by": "engine" })));
|
||
}
|
||
let (a, b) = (program_of(n, before.to_vec()), program_of(n, given.ops.clone()));
|
||
let (mut check, distance) = match pass.check() {
|
||
Check::Ladder => {
|
||
let v = super::equiv::equivalent_programs(&a, &b);
|
||
let how = v["how"].as_str().unwrap_or("none").to_string();
|
||
let verdict = match (v["verdict"].as_str(), how.as_str()) {
|
||
(Some("equivalent"), "tableau" | "rewrites") => "proof",
|
||
(Some("equivalent"), "exact") => "exact",
|
||
_ => "rejected",
|
||
};
|
||
let mut c = json!({ "how": format!("the whole circuit against the circuit before, by the equivalence ladder ({how})"), "verdict": verdict, "by": "engine", "ladder": v });
|
||
if verdict == "rejected" {
|
||
c["why"] = json!(match v["verdict"].as_str() {
|
||
Some("equivalent") => "the ladder found the two equal on random inputs alone, which is evidence, not a check a compile certifies".to_string(),
|
||
Some("different") => "the output is not the circuit before".to_string(),
|
||
_ => format!("the ladder does not decide it: {}", v["why"].as_str().unwrap_or("")),
|
||
});
|
||
}
|
||
(c, 0.0)
|
||
}
|
||
Check::Fidelity { max_distance } => {
|
||
let total = (before.len() + given.ops.len()) as f64;
|
||
if n > WHOLE_MAX_QUBITS || total * (1u64 << (2 * n)) as f64 > WHOLE_MAX_WORK {
|
||
(json!({ "how": "the dense unitaries of the circuit before and after", "verdict": "rejected", "why": format!("{n} qubits and {} gates: wider than the dense check runs ({WHOLE_MAX_QUBITS} qubits)", before.len() + given.ops.len()), "by": "engine" }), 0.0)
|
||
} else {
|
||
let d = mat::distance(&mat::Dense::of(n, &given.ops), &mat::Dense::of(n, before));
|
||
let left = o.epsilon - input.spent;
|
||
let mut c = json!({ "how": "the dense unitaries of the circuit before and after, their operator-norm distance up to a global phase", "by": "engine", "distance": d, "max_distance": max_distance });
|
||
if d > max_distance {
|
||
c["verdict"] = json!("rejected");
|
||
c["why"] = json!(format!("the output is {d:e} from the circuit before, more than the pass's bound {max_distance:e}"));
|
||
(c, 0.0)
|
||
} else if d > left {
|
||
c["verdict"] = json!("rejected");
|
||
c["why"] = json!(format!("the output is {d:e} from the circuit before, more than the {left:e} left of the compile's budget"));
|
||
(c, 0.0)
|
||
} else {
|
||
c["verdict"] = json!(if d <= 1e-12 { "exact" } else { "approximate" });
|
||
(c, d)
|
||
}
|
||
}
|
||
}
|
||
};
|
||
if !said.is_null() {
|
||
check["said"] = said;
|
||
}
|
||
let applied = check["verdict"] != "rejected";
|
||
Ok(PassOutput { ops: if applied { given.ops } else { before.to_vec() }, check, distance, epsilon: None, rotations: None })
|
||
}
|
||
|
||
/// `p` compiled under `o`; refused for a program that measures in the
|
||
/// middle, one larger than [`MAX_GATES`], or a budget the rotations cannot
|
||
/// share.
|
||
pub fn compile(p: &Program, o: &Options) -> Result<Compiled, String> {
|
||
compile_in(Registry::builtins(), p, o, &[])
|
||
}
|
||
|
||
/// [`compile`] through `reg`, with `extra` (passes `reg` holds) run after
|
||
/// the level's own and before twirling and decoupling. A registered pass's
|
||
/// record names it (`component`), and its check is the engine's.
|
||
pub(crate) fn compile_in(reg: &Registry, p: &Program, o: &Options, extra: &[&str]) -> Result<Compiled, String> {
|
||
if p.dynamic.is_some() {
|
||
return Err("the program measures in the middle, resets after a gate or acts on what it measured; compiling takes programs whose measurements all come at the end".into());
|
||
}
|
||
if p.ops.len() > MAX_GATES {
|
||
return Err(format!("{} gates; a compile takes at most {MAX_GATES}", p.ops.len()));
|
||
}
|
||
let n = p.n_qubits;
|
||
let mut ops = p.ops.clone();
|
||
let mut records = Vec::new();
|
||
let mut bound = 0.0f64;
|
||
let mut epsilon = json!({ "budget": o.epsilon });
|
||
let mut rotation_detail = Vec::new();
|
||
let mut stages = Vec::new();
|
||
let mut names = base_pipeline(o);
|
||
names.extend(extra.iter().copied());
|
||
if o.twirl.is_some() {
|
||
names.push("twirl");
|
||
}
|
||
if o.dd.is_some() {
|
||
names.push("dd");
|
||
}
|
||
for &name in &names {
|
||
let before = counts(n, &ops);
|
||
let pass = reg.pass(name).ok_or_else(|| super::refusal::tag(super::refusal::UNKNOWN_COMPONENT, format!("no pass {name} here")))?;
|
||
let id = reg.identity(ComponentKind::Pass, name).ok_or("internal: a pass without an identity")?;
|
||
let input = PassInput { qubits: n, ops: &ops, options: o, spent: bound };
|
||
let out = if id.is_builtin() { pass.run(&input)? } else { registered_pass(pass, &input)? };
|
||
if let Some((m, each)) = out.epsilon {
|
||
epsilon["rotations"] = json!(m);
|
||
epsilon["each"] = json!(each);
|
||
}
|
||
if let Some(detail) = out.rotations {
|
||
rotation_detail = detail;
|
||
}
|
||
let applied = out.check["verdict"] != "rejected" && out.check["verdict"] != "skipped";
|
||
if applied {
|
||
bound += out.distance;
|
||
ops = out.ops;
|
||
}
|
||
let mut record = json!({ "pass": name, "before": before, "after": counts(n, &ops), "check": out.check });
|
||
if !id.is_builtin() {
|
||
record["component"] = id.to_json();
|
||
}
|
||
records.push(record);
|
||
if o.stages {
|
||
stages.push((name.to_string(), rewrite::render(&ops, n, &p.measured, p.n_bits)));
|
||
}
|
||
}
|
||
epsilon["bound"] = json!(bound);
|
||
if o.basis == Basis::CliffordT && ops.iter().any(|x| arbitrary(x) || !lower::in_basis(x.kind) || x.kind == Kind::Rz) {
|
||
return Err("a gate off Clifford+T is left after the passes: refused".into());
|
||
}
|
||
if bound > o.epsilon {
|
||
return Err(format!("the passes' distances add to {bound:e}, more than the budget {:e}", o.epsilon));
|
||
}
|
||
// The whole output against the whole program, where dense unitaries fit.
|
||
let total = (p.ops.len() + ops.len()) as f64;
|
||
let whole = if n <= WHOLE_MAX_QUBITS && total * (1u64 << (2 * n)) as f64 <= WHOLE_MAX_WORK {
|
||
let d = mat::distance(&mat::Dense::of(n, &ops), &mat::Dense::of(n, &p.ops));
|
||
if d > bound + 1e-9 {
|
||
return Err(format!("the output is {d:e} from the program, more than the passes' bound {bound:e}: refused"));
|
||
}
|
||
json!({ "how": "the dense unitaries of the whole program and the whole output", "distance": d, "verdict": if bound > 0.0 { "approximate" } else { "exact" } })
|
||
} else {
|
||
json!({ "how": "the passes' own checks, composed: each acts where it acts, and distances add by the triangle inequality", "verdict": "composition" })
|
||
};
|
||
let verdicts: Vec<&str> = records.iter().filter_map(|r| r["check"]["verdict"].as_str()).collect();
|
||
let verdict = if verdicts.contains(&"approximate") {
|
||
"approximate"
|
||
} else if verdicts.contains(&"exact") {
|
||
"exact"
|
||
} else {
|
||
"proof"
|
||
};
|
||
// The outputs and the calibrations kept verbatim carry over; the timing and
|
||
// where each controlled gate was lowered name places in the ops given, which
|
||
// the passes rewrite, so the compiled program holds neither.
|
||
let extra = crate::qasm::Extra { outputs: p.extra.outputs.clone(), calibrations: p.extra.calibrations.clone(), ..Default::default() };
|
||
let program = Program { n_qubits: n, ops, measured: p.measured.clone(), n_bits: p.n_bits, dynamic: None, extra };
|
||
let qasm = rewrite::render(&program.ops, n, &program.measured, program.n_bits);
|
||
// The text reads back to the same gates.
|
||
let back = crate::qasm::parse(&qasm).map_err(|e| format!("the output does not read back: {e}"))?;
|
||
if back.ops.len() != program.ops.len() || back.measured != program.measured {
|
||
return Err("the output does not read back to the same gates".into());
|
||
}
|
||
let source_text = rewrite::render(&p.ops, n, &p.measured, p.n_bits);
|
||
let certificate = json!({
|
||
"format": "wai.quantum.compile/1",
|
||
"basis": o.basis.name(),
|
||
"level": o.level,
|
||
"source": { "hash": hash_hex(source_text.as_bytes()), "qubits": n, "counts": counts(n, &p.ops) },
|
||
"output": { "hash": hash_hex(qasm.as_bytes()), "counts": counts(n, &program.ops) },
|
||
"epsilon": epsilon,
|
||
"passes": records,
|
||
"rotations": rotation_detail,
|
||
"whole": whole,
|
||
"verdict": verdict,
|
||
"reading": format!(
|
||
"The output equals the program up to a global phase {}. On any input, the output distribution is then within bound of the program's in total variation (so is every outcome's probability), and an observable's expectation within 2·bound·‖O‖. Hashes are BLAKE3 of the program as read (written out as OpenQASM 3) and of the output text.",
|
||
if bound > 0.0 { format!("to within {bound:e} in the operator norm (the budget was {:e})", o.epsilon) } else { "exactly, to the rounding of doubles at most".to_string() }
|
||
),
|
||
});
|
||
Ok(Compiled { program, qasm, certificate, stages })
|
||
}
|
||
|
||
/// What a job's result carries of the compile its program went through.
|
||
pub fn summary(c: &Compiled) -> Value {
|
||
let cert = &c.certificate;
|
||
json!({
|
||
"basis": cert["basis"],
|
||
"level": cert["level"],
|
||
"epsilon": cert["epsilon"]["budget"],
|
||
"distance": cert["epsilon"]["bound"],
|
||
"verdict": cert["verdict"],
|
||
"source": cert["source"]["counts"],
|
||
"output": cert["output"]["counts"],
|
||
"certificate": certificate_hash(cert),
|
||
"note": "the result is of the compiled circuit, which is within `distance` of the program in the operator norm up to a global phase: its distribution within `distance` in total variation (every outcome's probability too), each expectation value within 2·distance·‖O‖, and amplitudes up to a global phase; `certificate` is the BLAKE3 of the compile certificate's canonical JSON (wai-quantum compile prints it as its digest)",
|
||
})
|
||
}
|
||
|
||
/// The BLAKE3 of a certificate's RFC 8785 canonical JSON, in hex.
|
||
#[cfg(feature = "quantum_receipt")]
|
||
pub fn certificate_hash(cert: &Value) -> Value {
|
||
json!(hash_hex(&super::receipt::canonical(cert)))
|
||
}
|
||
|
||
/// Without the receipt feature there is no canonical form to hash.
|
||
#[cfg(not(feature = "quantum_receipt"))]
|
||
pub fn certificate_hash(_: &Value) -> Value {
|
||
Value::Null
|
||
}
|
||
|
||
/// The service's form: `{"qasm", "epsilon", "level", "basis", "stages"}`
|
||
/// in, `{"ok": true, "options", "qasm", "certificate"}` out (with `stages`,
|
||
/// also each pass's circuit), not yet sealed; or the reason it is refused.
|
||
pub fn call(request: &Value) -> Result<Value, String> {
|
||
call_in(Registry::builtins(), request)
|
||
}
|
||
|
||
/// [`call`] through `reg`, with the request's `passes` (passes `reg` holds)
|
||
/// run after the level's own. An answer a registered pass took part in
|
||
/// carries `components`, the identity of every component that made it.
|
||
pub(crate) fn call_in(reg: &Registry, request: &Value) -> Result<Value, String> {
|
||
let src = request.get("qasm").and_then(Value::as_str).ok_or("give \"qasm\": the program")?;
|
||
let p = crate::qasm::parse(src)?;
|
||
let o = Options::read(request)?;
|
||
let extra = passes_of(reg, request.get("passes"))?;
|
||
let c = compile_in(reg, &p, &o, &extra)?;
|
||
let mut out = json!({ "ok": true, "options": o.to_json(), "qasm": c.qasm, "certificate": c.certificate });
|
||
if o.stages {
|
||
out["stages"] = c.stages.iter().map(|(pass, qasm)| json!({ "pass": pass, "qasm": qasm })).collect();
|
||
}
|
||
// The engine that compiled it, and the schema it follows, inside what the
|
||
// receipt seals.
|
||
super::schema::stamp_as(&mut out, super::schema::COMPILE_RESULT);
|
||
let mut used: Vec<&super::registry::Identity> = reg.identity(ComponentKind::FrontEnd, "qasm").into_iter().collect();
|
||
for r in c.certificate["passes"].as_array().into_iter().flatten() {
|
||
if let Some(i) = r["pass"].as_str().and_then(|n| reg.identity(ComponentKind::Pass, n))
|
||
&& !used.contains(&i)
|
||
{
|
||
used.push(i);
|
||
}
|
||
}
|
||
if used.iter().any(|i| !i.is_builtin()) {
|
||
out["components"] = used.iter().map(|i| i.to_json()).collect();
|
||
}
|
||
Ok(out)
|
||
}
|
||
|
||
/// `{"request", "result"}` with the result sealed by `key` in the form every
|
||
/// job's receipt takes, so [`super::job::verify`] checks it.
|
||
#[cfg(feature = "quantum_receipt")]
|
||
pub fn seal(request: &Value, mut result: Value, key: &ed25519_dalek::SigningKey) -> Value {
|
||
let r = super::receipt::seal(key, request, &result);
|
||
result["receipt"] = r;
|
||
json!({ "request": request, "result": result })
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use crate::engine::equiv;
|
||
|
||
fn compile_text(src: &str, o: &Options) -> Compiled {
|
||
compile(&crate::qasm::parse(src).unwrap(), o).unwrap()
|
||
}
|
||
|
||
#[test]
|
||
fn any_angle_compiles_to_clifford_t_within_the_budget_and_every_pass_is_recorded() {
|
||
let src = "OPENQASM 3.0;\nqubit[3] q;\nh q;\nrz(0.3) q[0];\ncx q[0], q[1];\nry(1.1) q[1];\ncp(0.7) q[1], q[2];\nrx(-0.4) q[2];\nccx q[0], q[1], q[2];\nt q[0];\nt q[0];\n";
|
||
for level in 0..=MAX_LEVEL {
|
||
let o = Options { epsilon: 1e-4, level, basis: Basis::CliffordT, ..Default::default() };
|
||
let c = compile_text(src, &o);
|
||
let cert = &c.certificate;
|
||
let bound = cert["epsilon"]["bound"].as_f64().unwrap();
|
||
assert!(bound > 0.0 && bound <= 1e-4, "level {level}: {cert}");
|
||
assert_eq!(cert["output"]["counts"]["rotations"], 0, "level {level}");
|
||
assert!(c.program.ops.iter().all(|o| matches!(o.kind, Kind::H | Kind::X | Kind::Y | Kind::Z | Kind::S | Kind::Sdg | Kind::T | Kind::Tdg | Kind::Cx | Kind::Cz | Kind::Swap)), "level {level}");
|
||
// The whole output, measured against the program, is within the bound.
|
||
let d = cert["whole"]["distance"].as_f64().unwrap();
|
||
assert!(d <= bound + 1e-12, "level {level}: {d} against {bound}");
|
||
assert_eq!(cert["verdict"], "approximate");
|
||
let names: Vec<&str> = cert["passes"].as_array().unwrap().iter().map(|r| r["pass"].as_str().unwrap()).collect();
|
||
assert_eq!(names, pipeline(&o));
|
||
// It runs on the bit-exact method.
|
||
let job = json!({ "qasm": c.qasm, "shots": 100 });
|
||
let plan = super::super::catalog::plan(&job, 1 << 30).unwrap();
|
||
assert!(plan.method == "exact" || plan.method == "stabilizer", "{}", plan.method);
|
||
// The same compile gives the same bytes.
|
||
let again = compile_text(src, &o);
|
||
assert_eq!(again.qasm, c.qasm);
|
||
assert_eq!(super::super::receipt::canonical(&again.certificate), super::super::receipt::canonical(cert));
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn exact_passes_keep_the_unitary_and_lower_the_counts() {
|
||
// A Toffoli pair cancels; T gates on one parity merge; a two-qubit
|
||
// block of five CNOTs takes three or fewer.
|
||
let src = "OPENQASM 3.0;\nqubit[3] q;\nccx q[0], q[1], q[2];\nccx q[0], q[1], q[2];\nt q[0];\ncx q[0], q[1];\ncx q[0], q[1];\nt q[0];\ncx q[1], q[2];\nrz(0.2) q[2];\ncx q[2], q[1];\ncx q[1], q[2];\ncx q[2], q[1];\nh q[2];\ncx q[1], q[2];\n";
|
||
let p = crate::qasm::parse(src).unwrap();
|
||
for level in 0..=MAX_LEVEL {
|
||
let o = Options { epsilon: 1e-9, level, basis: Basis::Standard, ..Default::default() };
|
||
let c = compile(&p, &o).unwrap();
|
||
let v = equiv::equivalent_programs(&p, &c.program);
|
||
assert_eq!(v["verdict"], "equivalent", "level {level}: {v}");
|
||
assert!(c.certificate["epsilon"]["bound"].as_f64().unwrap() < 1e-12);
|
||
let after = &c.certificate["output"]["counts"];
|
||
if level >= 1 {
|
||
assert!(after["t_count"].as_u64().unwrap() <= 1, "level {level}: {after}");
|
||
}
|
||
if level == 2 {
|
||
assert!(after["cnots"].as_u64().unwrap() <= 3, "level {level}: {after}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Twirling and decoupling leave the program as it was without noise,
|
||
/// proved by the ladder, at every level and either basis; one seed is
|
||
/// one instance, the same bytes each time, another seed another.
|
||
#[test]
|
||
fn twirling_and_decoupling_keep_the_program() {
|
||
let src = "OPENQASM 3.0;\nqubit[4] q;\nh q[0];\ncx q[0], q[1];\nrz(0.3) q[1];\ncz q[1], q[2];\nswap q[2], q[3];\nh q[1];\nt q[1];\nh q[1];\ncx q[1], q[2];\nx q[0];\ncx q[2], q[3];\ncx q[3], q[2];\n";
|
||
let p = crate::qasm::parse(src).unwrap();
|
||
for basis in [Basis::Standard, Basis::CliffordT] {
|
||
for level in 0..=MAX_LEVEL {
|
||
let o = Options { epsilon: 1e-3, level, basis, twirl: Some(5), dd: Some(Dd::Xy4), ..Default::default() };
|
||
let c = compile(&p, &o).unwrap();
|
||
// The same compile without the two passes: the same circuit
|
||
// (a Clifford+T compile approximates its rotations, so it is
|
||
// within ε of the program itself, and equal to that).
|
||
let plain = compile(&p, &Options { twirl: None, dd: None, ..o.clone() }).unwrap();
|
||
let v = equiv::equivalent_programs(&plain.program, &c.program);
|
||
assert_eq!(v["verdict"], "equivalent", "{basis:?} level {level}: {v}");
|
||
if basis == Basis::Standard {
|
||
assert_eq!(equiv::equivalent_programs(&p, &c.program)["verdict"], "equivalent");
|
||
}
|
||
let passes = c.certificate["passes"].as_array().unwrap();
|
||
let tw = passes.iter().find(|r| r["pass"] == "twirl").unwrap();
|
||
let dd = passes.iter().find(|r| r["pass"] == "dd").unwrap();
|
||
assert_eq!(tw["check"]["verdict"], "proof", "{tw}");
|
||
assert!(tw["check"]["twirled"].as_u64().unwrap() >= 1, "{tw}");
|
||
assert!(dd["check"]["verdict"] == "proof" || dd["check"]["verdict"] == "skipped", "{dd}");
|
||
assert_eq!(compile(&p, &o).unwrap().qasm, c.qasm, "one seed, one instance");
|
||
if basis == Basis::Standard && level == 0 {
|
||
let other = compile(&p, &Options { twirl: Some(6), ..o.clone() }).unwrap();
|
||
assert_ne!(other.qasm, c.qasm, "another seed, another instance");
|
||
assert_eq!(dd["check"]["verdict"], "proof", "the program leaves qubits idle: {dd}");
|
||
assert!(dd["check"]["pulses"].as_u64().unwrap() >= 4);
|
||
}
|
||
}
|
||
}
|
||
// Without the options, the pipeline is as it was.
|
||
assert!(!pipeline(&Options::default()).contains(&"twirl"));
|
||
assert_eq!(Options::read(&json!({ "twirl": true, "dd": "xx" })).unwrap().dd, Some(Dd::Xx));
|
||
assert_eq!(Options::read(&json!({ "twirl": { "seed": 9 } })).unwrap().twirl, Some(9));
|
||
assert!(Options::read(&json!({ "dd": "cpmg" })).is_err());
|
||
assert!(Options::read(&json!({ "twirl": "yes" })).is_err());
|
||
assert_eq!(Options::read(&json!({ "twirl": { "seed": 2 }, "dd": "xy4" })).unwrap().to_json()["dd"], "xy4");
|
||
}
|
||
|
||
/// The frame through each two-qubit Clifford is the one the gate makes:
|
||
/// every one of the sixteen Paulis on either side, checked on dense
|
||
/// matrices.
|
||
#[test]
|
||
fn every_frame_is_carried_through_its_gate() {
|
||
for kind in [Kind::Cx, Kind::Cz, Kind::Swap] {
|
||
for r in 0..16u64 {
|
||
let p = (r & 8 != 0, r & 4 != 0, r & 2 != 0, r & 1 != 0);
|
||
let q = through(kind, p);
|
||
let one = |k: Option<Kind>, at: u32| k.map(|kind| Op { kind, qubits: vec![at], params: vec![] });
|
||
let mut w: Vec<Op> = [one(pauli_kind(p.0, p.1), 0), one(pauli_kind(p.2, p.3), 1)].into_iter().flatten().collect();
|
||
let g = Op { kind, qubits: vec![0, 1], params: vec![] };
|
||
w.push(g.clone());
|
||
w.extend([one(pauli_kind(q.0, q.1), 0), one(pauli_kind(q.2, q.3), 1)].into_iter().flatten());
|
||
let d = mat::distance(&mat::Dense::of(2, &w), &mat::Dense::of(2, std::slice::from_ref(&g)));
|
||
assert!(d < 1e-12, "{kind:?} frame {r:04b}: {d}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn refusals_say_why() {
|
||
let o = Options::default();
|
||
let dynamic = crate::qasm::parse("OPENQASM 3.0;\nqubit[1] q;\nbit[1] c;\nh q[0];\nc[0] = measure q[0];\nx q[0];\n").unwrap();
|
||
assert!(compile(&dynamic, &o).unwrap_err().contains("measures in the middle"));
|
||
let many = format!("OPENQASM 3.0;\nqubit[1] q;\n{}", (0..40).map(|i| format!("rz({}) q[0];\nh q[0];\n", 0.1 + f64::from(i) * 0.01)).collect::<String>());
|
||
let tight = Options { epsilon: 1e-14, level: 0, basis: Basis::CliffordT, ..Default::default() };
|
||
assert!(compile(&crate::qasm::parse(&many).unwrap(), &tight).unwrap_err().contains("below"));
|
||
assert!(Options::read(&json!({ "basis": "native" })).is_err());
|
||
assert!(Options::read(&json!({ "target": "standard" })).unwrap_err().contains("\"basis\""), "the old spelling is refused, never read as the default");
|
||
assert!(Options::read(&json!({ "epsilon": -1 })).is_err());
|
||
assert!(Options::read(&json!({ "level": 7 })).is_err());
|
||
assert_eq!(Options::read(&json!({})).unwrap(), Options::default());
|
||
assert!(Options::read(&json!({ "stages": "yes" })).is_err());
|
||
// With stages, the circuit after every pass, the last the output.
|
||
let r = call(&json!({ "qasm": "OPENQASM 3.0;\nqubit[1] q;\nrx(0.3) q[0];\n", "stages": true, "level": 1 })).unwrap();
|
||
let st = r["stages"].as_array().unwrap();
|
||
assert_eq!(st.len(), pipeline(&Options::default()).len());
|
||
assert_eq!(st.last().unwrap()["qasm"], r["qasm"]);
|
||
assert!(call(&json!({ "qasm": "OPENQASM 3.0;\nqubit[1] q;\n" })).unwrap().get("stages").is_none());
|
||
}
|
||
|
||
#[cfg(feature = "quantum_receipt")]
|
||
#[test]
|
||
fn a_job_runs_its_program_compiled_on_the_bit_exact_method_and_says_how_far_it_is() {
|
||
let src = "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nrz(0.3) q[0];\nry(0.8) q[1];\ncx q[0], q[1];\n";
|
||
let bits = ["00", "01", "10", "11"];
|
||
let job = json!({ "qasm": src, "amplitudes": bits, "compile": { "epsilon": 1e-3 } });
|
||
let (plan, result) = super::super::job::plan_and_run(&job, 1 << 30).unwrap();
|
||
assert_eq!(plan.method, "exact");
|
||
assert_eq!(result["accuracy"]["class"], "bit-exact");
|
||
let c = &result["compiled"];
|
||
let d = c["distance"].as_f64().unwrap();
|
||
assert!(d > 0.0 && d <= 1e-3, "{c}");
|
||
assert_eq!(c["basis"], "clifford_t");
|
||
// Against the program run as given: every amplitude's modulus within
|
||
// the distance (and the 30-bit fixed point's rounding).
|
||
let (_, plain) = super::super::job::plan_and_run(&json!({ "qasm": src, "amplitudes": bits, "method": "statevector" }), 1 << 30).unwrap();
|
||
for i in 0..4 {
|
||
let m = |r: &Value| r["amplitudes"][i]["re"].as_f64().unwrap().hypot(r["amplitudes"][i]["im"].as_f64().unwrap());
|
||
assert!((m(&result) - m(&plain)).abs() <= d + 1e-8, "{i}: {} against {}", m(&result), m(&plain));
|
||
}
|
||
assert!(super::super::job::plan_and_run(&json!({ "qasm": src, "shots": 10, "compile": { "basis": "native" } }), 1 << 30).unwrap_err().starts_with("compile:"));
|
||
}
|
||
|
||
#[cfg(feature = "quantum_receipt")]
|
||
#[test]
|
||
fn a_sealed_compile_verifies_and_a_changed_one_does_not() {
|
||
let req = json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nrz(0.25) q[0];\ncx q[0], q[1];\n", "epsilon": 1e-3 });
|
||
let key = ed25519_dalek::SigningKey::from_bytes(&[7; 32]);
|
||
let body = seal(&req, call(&req).unwrap(), &key);
|
||
assert!(super::super::job::verify(&body).unwrap().0);
|
||
let mut forged = body.clone();
|
||
forged["result"]["certificate"]["epsilon"]["bound"] = json!(0.0);
|
||
assert!(!super::super::job::verify(&forged).unwrap().0);
|
||
let out = body["result"]["qasm"].as_str().unwrap();
|
||
assert!(!out.contains("rz"), "{out}");
|
||
}
|
||
}
|