wai-quantum 0.3.24: fault-tolerant resource estimation, reproducible and signed #141

Merged
dcharlot merged 1 commit from wai-quantum-resource into main 2026-10-04 14:04:22 -04:00
15 changed files with 2108 additions and 132 deletions

View file

@ -589,7 +589,7 @@ dependencies = [
[[package]]
name = "wai-quantum"
version = "0.3.23"
version = "0.3.24"
dependencies = [
"blake3",
"ed25519-dalek",

View file

@ -415,7 +415,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wai-quantum"
version = "0.3.23"
version = "0.3.24"
dependencies = [
"blake3",
"ed25519-dalek",

View file

@ -1,6 +1,6 @@
[package]
name = "wai-quantum"
version = "0.3.23"
version = "0.3.24"
edition = "2024"
rust-version = "1.97"
license = "Apache-2.0"
@ -48,6 +48,11 @@ quantum_spd_receipt = ["quantum_spd", "_signing"]
# Tensor-network states on the hardware graph: simple-update evolution, belief-
# propagation environments; exact on trees, an independent check on loopy graphs.
quantum_bptn = ["quantum_spd"]
# Fault-tolerant resource estimation: logical layout, code distance, exhaustive
# T-factory search and the space-time frontier, reproducible bit for bit.
quantum_resource = []
# Signed, energy-accounted receipts for it, re-checkable bit for bit anywhere.
quantum_resource_receipt = ["quantum_resource", "_signing"]
# Mixed states and the observables that distinguish quantum theory from a classical
# account: density matrices, the partial trace, exact Pauli expectations, CHSH.
quantum_info = ["quantum"]
@ -87,7 +92,7 @@ quantum_phasor_meter = ["_signing"]
# Everything.
full = [
"quantum", "quantum_toolchain", "quantum_mps", "quantum_stabilizer", "quantum_pauli", "quantum_spd", "quantum_spd_receipt", "quantum_bptn", "quantum_qir", "quantum_info", "quantum_qudit", "quantum_comm", "quantum_source", "quantum_channel",
"quantum", "quantum_toolchain", "quantum_mps", "quantum_stabilizer", "quantum_pauli", "quantum_spd", "quantum_spd_receipt", "quantum_bptn", "quantum_resource", "quantum_resource_receipt", "quantum_qir", "quantum_info", "quantum_qudit", "quantum_comm", "quantum_source", "quantum_channel",
"quantum_receipt", "quantum_ops", "quantum_cal", "quantum_mitigate", "quantum_qec",
"quantum_control", "quantum_noise", "quantum_compile", "quantum_atom", "quantum_qbom",
"quantum_vml", "quantum_phasor", "quantum_qfhrr", "quantum_kernel", "quantum_qdata",

View file

@ -62,6 +62,8 @@ never creates information.
| `quantum_spd` | **sparse Pauli dynamics at utility scale**: arbitrary-angle Pauli rotations on up to 1024 qubits, exact Clifford and quarter-turn rotations, a rigorous bound on what truncation can move a result, and the 127-qubit heavy-hex kicked-Ising circuits — whose exactly computed 5-step magnetization it reproduces on all 127 qubits |
| `quantum_spd_receipt` | a signed, energy-accounted receipt over a sparse-Pauli-dynamics run, re-checkable bit for bit on any machine |
| `quantum_bptn` | **tensor-network states on the hardware graph**: simple-update evolution with belief-propagation environments and re-gauging, the device's couplings applied a layer at a time in parallel (bit-identical to one at a time). Exact on trees; on the 127-qubit lattice it gives the exactly computed 5-step magnetization to 4e-7 at bond dimension 32 — an independent check on sparse Pauli dynamics, whose errors it does not share |
| `quantum_resource` | **fault-tolerant resource estimation**: from a program's logical counts (qubits, T, Toffoli and rotation gates, measurements) to code distance, T-state factories found by exhaustive search, physical qubits and runtime, on six reference qubits and three codes, with the qubits-versus-time frontier. Reproduces the published worked examples to their printed precision |
| `quantum_resource_receipt` | a signed, energy-accounted receipt over an estimate, re-checkable bit for bit on any machine |
| `quantum_receipt` | signed, energy-accounted receipt over a reconstruction |
| `quantum_ops` | the operations/attestation layer |
| `quantum_cal`, `quantum_control`, `quantum_noise` | calibration, filter-function robust control, DD noise spectroscopy, Cycle-Benchmarking noise learning |
@ -113,6 +115,39 @@ published references. `quantum_bptn` computes the same circuits as tensor
networks (`--example utility_bptn`), so a result can be checked by a second
method that does not share the first one's approximation.
## Fault-tolerant resources
What a program would need on a machine that does not exist yet is a resource
estimate, and claims about that machine stand on one. `quantum_resource` makes
the estimate a deterministic function of its inputs:
```sh
wai-quantum estimate factoring # 2048-bit factoring, six reference qubits
wai-quantum estimate chemistry --qubit ns-e4 --frontier
wai-quantum estimate factoring --qubit ns-e4 --receipt | wai-quantum verify
```
The model is the planar-ISA one of arXiv:2211.07629: surface and Floquet-code
patches, lattice surgery, and 15-to-1 distillation factories chosen by
exhaustive search over unit types, distances and copy counts. The tests
reproduce that source's own tables:
- every code distance and factory of its per-qubit factoring table, down to the
copy counts;
- the distance, factory count, physical qubits and runtime of all twelve
factoring and chemistry rows of its summary table;
- all twelve factory counts of its dynamics rows. Factoring on the `(ns, 10⁻⁴)`
qubit comes out at `d = 13`, 18 factories, 8,716,258 physical qubits and
17 h 43 min.
Holding the model to those tables exposed places where the source's prose and
its numbers disagree, and one factory that is not the minimum of its own model.
Each is documented in the module and settled by the tables.
A sealed estimate names its whole job (counts, qubit, code, budget, slowdown and
search limits, to the bit). Re-running it anywhere must reproduce every figure,
down to each factory round, and the bytes are the same natively and as a WASI
component.
## Determinism
Simulation is **byte-exact**: amplitudes are dyadic fixed-point, so a circuit

View file

@ -26,7 +26,8 @@ mod linalg;
feature = "quantum_mps",
feature = "quantum_vml",
feature = "quantum_phasor",
feature = "quantum_kernel"
feature = "quantum_kernel",
feature = "quantum_resource"
))]
mod repro;
@ -84,6 +85,12 @@ pub mod quantum_spd_receipt;
#[cfg(feature = "quantum_bptn")]
pub mod quantum_bptn;
#[cfg(feature = "quantum_resource")]
pub mod quantum_resource;
#[cfg(feature = "quantum_resource_receipt")]
pub mod quantum_resource_receipt;
#[cfg(feature = "quantum_info")]
pub mod quantum_info;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,584 @@
//! Signed, re-checkable resource estimates (`wai.quantum.resource`).
//!
//! A resource estimate is the number a claim stands on: so many physical
//! qubits, so much time. The receipt binds that number to everything it came
//! from:
//!
//! - **the job**: the program's counts, the physical qubit, the code (or the
//! rule "cheapest code this qubit supports"), the error budget, the slowdown
//! and the search limits. Rates and times are hashed as their exact IEEE-754
//! bits.
//! - **the estimate**: every figure in it, down to each factory round's copy
//! count and output error.
//! - **the energy** the sink spent producing it (`joules_micro`, `0` if
//! unmetered).
//!
//! The estimate is a pure function of the job, computed with `+ − × ÷` and
//! in-crate logarithms. [`ResourceReceipt::verify_computation`] therefore
//! re-runs the job anywhere and demands the identical estimate, bit for bit.
//! Change one input (an error rate by one ulp, the budget, the code) and the
//! check fails.
//!
//! The receipt is a JWP profile like the crate's others (Merkle root over leaf
//! hashes, Ed25519). It can carry an energy-class label
//! (`quantum_energy::Labelled`).
use crate::merkle::merkle_root;
use crate::quantum_resource::{
estimate_best, estimate_best_isa, estimate, estimate_isa, Budget, Estimate, EstimateConfig, EstimateError,
Factory, InstructionSet, IsaCounts, LogicalCounts, PhysicalQubit, QecModel, Round, SearchLimits, UnitKind,
};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde_json::{json, Value};
const DOMAIN_RECEIPT: &[u8] = b"wai:resource-receipt\x01";
const DOMAIN_RECEIPT_ID: &[u8] = b"wai:resource-receipt-id\x01";
const DOMAIN_JOB: &[u8] = b"wai:resource-job\x01";
const DOMAIN_RESULT: &[u8] = b"wai:resource-estimate\x01";
/// The program being estimated, at the level it is known.
#[derive(Clone, Debug, PartialEq)]
pub enum Program {
/// Algorithm-level counts, laid out by the estimator.
Logical(LogicalCounts),
/// Layout-level counts, given directly.
Isa(IsaCounts),
}
/// Everything that determines an estimate.
#[derive(Clone, Debug, PartialEq)]
pub struct ResourceJob {
pub program: Program,
pub qubit: PhysicalQubit,
/// The code to use, or `None` for the cheapest the qubit supports.
pub code: Option<QecModel>,
pub config: EstimateConfig,
}
impl ResourceJob {
pub fn run(&self) -> Result<Estimate, EstimateError> {
match (&self.program, &self.code) {
(Program::Logical(c), Some(q)) => estimate(c, &self.qubit, q, &self.config),
(Program::Logical(c), None) => estimate_best(c, &self.qubit, &self.config),
(Program::Isa(i), Some(q)) => estimate_isa(i, &self.qubit, q, &self.config),
(Program::Isa(i), None) => estimate_best_isa(i, &self.qubit, &self.config),
}
}
/// BLAKE3 over the job's canonical encoding.
pub fn job_hash(&self) -> [u8; 32] {
let mut o = DOMAIN_JOB.to_vec();
match &self.program {
Program::Logical(c) => {
o.push(1);
for x in [c.qubits, c.measurements, c.rotations, c.rotation_depth, c.t_gates, c.ccz] {
u(x, &mut o);
}
}
Program::Isa(i) => {
o.push(2);
isa(i, &mut o);
}
}
qubit(&self.qubit, &mut o);
match &self.code {
Some(q) => {
o.push(1);
code(q, &mut o);
}
None => o.push(0),
}
f(self.config.error_budget, &mut o);
u(self.config.slowdown, &mut o);
u(self.config.limits.max_distance as u64, &mut o);
u(self.config.limits.max_rounds as u64, &mut o);
*blake3::hash(&o).as_bytes()
}
}
fn u(x: u64, o: &mut Vec<u8>) {
o.extend_from_slice(&x.to_be_bytes());
}
fn f(x: f64, o: &mut Vec<u8>) {
o.extend_from_slice(&x.to_bits().to_be_bytes());
}
fn text(s: &str, o: &mut Vec<u8>) {
u(s.len() as u64, o);
o.extend_from_slice(s.as_bytes());
}
fn set(s: InstructionSet) -> u8 {
match s {
InstructionSet::GateBased => 1,
InstructionSet::Measurement => 2,
}
}
fn isa(i: &IsaCounts, o: &mut Vec<u8>) {
for x in [i.tiles, i.min_steps, i.t_states, i.t_per_rotation] {
u(x, o);
}
o.push(i.synthesizes as u8);
}
fn qubit(q: &PhysicalQubit, o: &mut Vec<u8>) {
text(&q.name, o);
o.push(set(q.instruction_set));
for x in [q.gate_ns, q.meas_ns, q.clifford_error, q.t_error] {
f(x, o);
}
}
fn code(q: &QecModel, o: &mut Vec<u8>) {
text(&q.name, o);
o.push(set(q.instruction_set));
f(q.prefactor, o);
f(q.threshold, o);
u(q.qubits.0, o);
u(q.qubits.1, o);
u(q.qubits.2 as u64, o);
f(q.cycle.0, o);
f(q.cycle.1, o);
}
fn kind_tag(k: UnitKind) -> u8 {
match k {
UnitKind::SpaceEfficient => 1,
UnitKind::ReedMuller => 2,
}
}
fn result_hash(e: &Estimate) -> [u8; 32] {
let mut o = DOMAIN_RESULT.to_vec();
text(&e.qubit, &mut o);
text(&e.code, &mut o);
isa(&e.isa, &mut o);
for x in [e.budget.logical, e.budget.distillation, e.budget.synthesis] {
f(x, &mut o);
}
u(e.distance as u64, &mut o);
u(e.tile_qubits, &mut o);
f(e.step_ns, &mut o);
u(e.steps, &mut o);
f(e.runtime_ns, &mut o);
f(e.logical_error, &mut o);
f(e.t_target, &mut o);
match &e.factory {
Some(fa) => {
o.push(1);
u(fa.rounds.len() as u64, &mut o);
for r in &fa.rounds {
o.push(kind_tag(r.kind));
u(r.distance as u64, &mut o);
u(r.copies, &mut o);
u(r.unit_qubits, &mut o);
f(r.duration_ns, &mut o);
f(r.failure, &mut o);
f(r.output_error, &mut o);
}
u(fa.qubits, &mut o);
f(fa.duration_ns, &mut o);
f(fa.output_error, &mut o);
u(fa.outputs, &mut o);
}
None => o.push(0),
}
for x in [e.factories, e.algorithm_qubits, e.factory_qubits, e.physical_qubits] {
u(x, &mut o);
}
*blake3::hash(&o).as_bytes()
}
fn hx(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn unhex<const N: usize>(s: &str) -> Option<[u8; N]> {
if s.len() != 2 * N || !s.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut out = [0u8; N];
for (i, c) in s.as_bytes().chunks(2).enumerate() {
out[i] = u8::from_str_radix(std::str::from_utf8(c).ok()?, 16).ok()?;
}
Some(out)
}
fn bits(x: f64) -> String {
format!("{:016x}", x.to_bits())
}
fn unbits(v: &Value) -> Option<f64> {
u64::from_str_radix(v.as_str()?, 16).ok().map(f64::from_bits)
}
/// A signed record of one resource estimate and the energy spent on it.
#[derive(Clone, Debug, PartialEq)]
pub struct ResourceReceipt {
pub job_hash: [u8; 32],
pub estimate: Estimate,
pub result_hash: [u8; 32],
/// Merkle root over (job, estimate) hashes.
pub merkle_root: [u8; 32],
/// Measured microjoules the sink spent (`0` = unmetered). Attested by the
/// signature, not verifiable by anyone else.
pub joules_micro: u64,
pub parent_receipt_hash: Option<[u8; 32]>,
pub signer_pubkey: [u8; 32],
pub signer_id: String,
pub sig: [u8; 64],
}
impl ResourceReceipt {
fn leaves(&self) -> Vec<[u8; 32]> {
vec![self.job_hash, self.result_hash]
}
fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
let mut o = crate::quantum_energy::labelled_domain(DOMAIN_RECEIPT, label);
o.extend_from_slice(&self.job_hash);
o.extend_from_slice(&self.result_hash);
o.extend_from_slice(&self.merkle_root);
o.extend_from_slice(&self.joules_micro.to_be_bytes());
crate::quantum_energy::write_label(label, &mut o);
match self.parent_receipt_hash {
Some(h) => {
o.push(1);
o.extend_from_slice(&h);
}
None => o.push(0),
}
o.extend_from_slice(&self.signer_pubkey);
o.extend_from_slice(self.signer_id.as_bytes());
o
}
/// Run `job` under `meter` and seal what it computed. `meter` is handed a
/// closure that performs the run; it must call it once, and returns the
/// microjoules it measured (`|run| { run(); 0 }` for an unmetered seal).
pub fn seal(
signer: &SigningKey,
signer_id: impl Into<String>,
job: &ResourceJob,
parent_receipt_hash: Option<[u8; 32]>,
meter: impl FnOnce(&mut dyn FnMut()) -> u64,
) -> Result<ResourceReceipt, EstimateError> {
let mut out = None;
let joules_micro = meter(&mut || out = Some(job.run()));
let estimate = out.ok_or(EstimateError::NotRun)??;
let mut r = ResourceReceipt {
job_hash: job.job_hash(),
result_hash: result_hash(&estimate),
estimate,
merkle_root: [0; 32],
joules_micro,
parent_receipt_hash,
signer_pubkey: signer.verifying_key().to_bytes(),
signer_id: signer_id.into(),
sig: [0; 64],
};
r.merkle_root = merkle_root(&r.leaves());
r.sig = signer.sign(&r.payload_with(None)).to_bytes();
Ok(r)
}
fn body_verifies(&self) -> bool {
result_hash(&self.estimate) == self.result_hash && merkle_root(&self.leaves()) == self.merkle_root
}
/// The receipt is internally consistent and its signature is valid. Does
/// not re-run the job: see [`Self::verify_computation`].
pub fn verify(&self) -> bool {
if !self.body_verifies() {
return false;
}
let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
return false;
};
k.verify(&self.payload_with(None), &Signature::from_bytes(&self.sig)).is_ok()
}
/// The re-run half of [`Self::verify_computation`].
pub(crate) fn computation_matches(&self, job: &ResourceJob) -> bool {
job.job_hash() == self.job_hash && job.run().is_ok_and(|e| result_hash(&e) == self.result_hash)
}
/// The strong check: `job` is the job this receipt names, re-running it
/// here reproduces the sealed estimate bit for bit, and the receipt
/// verifies.
pub fn verify_computation(&self, job: &ResourceJob) -> bool {
self.computation_matches(job) && self.verify()
}
/// Stable id, for chaining a derived receipt via `parent_receipt_hash`.
pub fn receipt_hash(&self) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(DOMAIN_RECEIPT_ID);
h.update(&self.payload_with(None));
h.update(&self.sig);
*h.finalize().as_bytes()
}
/// Canonical JSON, `kind` first and then keys sorted. Every floating-point
/// figure appears as its exact bits (`*_bits`), which are what a verifier
/// reads back; the headline runtime also appears as a readable number.
pub fn to_json(&self) -> String {
let e = &self.estimate;
let factory = e.factory.as_ref().map(|fa| {
json!({
"duration_ns_bits": bits(fa.duration_ns),
"output_error_bits": bits(fa.output_error),
"outputs": fa.outputs,
"qubits": fa.qubits,
"rounds": fa.rounds.iter().map(|r| json!({
"copies": r.copies,
"distance": r.distance,
"duration_ns_bits": bits(r.duration_ns),
"failure_bits": bits(r.failure),
"kind": match r.kind { UnitKind::SpaceEfficient => "space-efficient", UnitKind::ReedMuller => "reed-muller" },
"output_error_bits": bits(r.output_error),
"unit_qubits": r.unit_qubits,
})).collect::<Vec<_>>(),
})
});
let v = json!({
"kind": "quantum-resource",
"estimate": {
"algorithm_qubits": e.algorithm_qubits,
"budget_bits": [bits(e.budget.logical), bits(e.budget.distillation), bits(e.budget.synthesis)],
"code": e.code,
"distance": e.distance,
"factories": e.factories,
"factory": factory,
"factory_qubits": e.factory_qubits,
"isa": {
"min_steps": e.isa.min_steps,
"synthesizes": e.isa.synthesizes,
"t_per_rotation": e.isa.t_per_rotation,
"t_states": e.isa.t_states,
"tiles": e.isa.tiles,
},
"logical_error_bits": bits(e.logical_error),
"physical_qubits": e.physical_qubits,
"qubit": e.qubit,
"runtime_ns_bits": bits(e.runtime_ns),
"runtime_s": e.runtime_ns / 1e9,
"step_ns_bits": bits(e.step_ns),
"steps": e.steps,
"t_target_bits": bits(e.t_target),
"tile_qubits": e.tile_qubits,
},
"job_hash": hx(&self.job_hash),
"joules_micro": self.joules_micro,
"parent_receipt_hash": self.parent_receipt_hash.map(|h| hx(&h)),
"receipt_hash": hx(&self.receipt_hash()),
"result_hash": hx(&self.result_hash),
"root_hash": hx(&self.merkle_root),
"sig": hx(&self.sig),
"signer_id": self.signer_id,
"signer_pubkey": hx(&self.signer_pubkey),
});
// The crate's serde_json keeps insertion order, and keys are written in
// sorted order after `kind`, so this is canonical.
v.to_string()
}
pub fn from_json(s: &str) -> Option<ResourceReceipt> {
let v: Value = serde_json::from_str(s).ok()?;
if v.get("kind")?.as_str()? != "quantum-resource" {
return None;
}
let e = v.get("estimate")?;
let n = |o: &Value, k: &str| o.get(k).and_then(Value::as_u64);
let b = |o: &Value, k: &str| o.get(k).and_then(unbits);
let i = e.get("isa")?;
let budget = e.get("budget_bits")?.as_array()?;
let factory = match e.get("factory")? {
Value::Null => None,
fa => {
let mut rounds = Vec::new();
for r in fa.get("rounds")?.as_array()? {
rounds.push(Round {
kind: match r.get("kind")?.as_str()? {
"space-efficient" => UnitKind::SpaceEfficient,
"reed-muller" => UnitKind::ReedMuller,
_ => return None,
},
distance: u32::try_from(n(r, "distance")?).ok()?,
copies: n(r, "copies")?,
unit_qubits: n(r, "unit_qubits")?,
duration_ns: b(r, "duration_ns_bits")?,
failure: b(r, "failure_bits")?,
output_error: b(r, "output_error_bits")?,
});
}
Some(Factory {
rounds,
qubits: n(fa, "qubits")?,
duration_ns: b(fa, "duration_ns_bits")?,
output_error: b(fa, "output_error_bits")?,
outputs: n(fa, "outputs")?,
})
}
};
let estimate = Estimate {
qubit: e.get("qubit")?.as_str()?.into(),
code: e.get("code")?.as_str()?.into(),
isa: IsaCounts {
tiles: n(i, "tiles")?,
min_steps: n(i, "min_steps")?,
t_states: n(i, "t_states")?,
t_per_rotation: n(i, "t_per_rotation")?,
synthesizes: i.get("synthesizes")?.as_bool()?,
},
budget: Budget {
logical: unbits(budget.first()?)?,
distillation: unbits(budget.get(1)?)?,
synthesis: unbits(budget.get(2)?)?,
},
distance: u32::try_from(n(e, "distance")?).ok()?,
tile_qubits: n(e, "tile_qubits")?,
step_ns: b(e, "step_ns_bits")?,
steps: n(e, "steps")?,
runtime_ns: b(e, "runtime_ns_bits")?,
logical_error: b(e, "logical_error_bits")?,
t_target: b(e, "t_target_bits")?,
factory,
factories: n(e, "factories")?,
algorithm_qubits: n(e, "algorithm_qubits")?,
factory_qubits: n(e, "factory_qubits")?,
physical_qubits: n(e, "physical_qubits")?,
};
let s = |k: &str| v.get(k).and_then(Value::as_str);
let parent = match v.get("parent_receipt_hash") {
Some(Value::String(h)) => Some(unhex::<32>(h)?),
_ => None,
};
Some(ResourceReceipt {
job_hash: unhex(s("job_hash")?)?,
estimate,
result_hash: unhex(s("result_hash")?)?,
merkle_root: unhex(s("root_hash")?)?,
joules_micro: v.get("joules_micro")?.as_u64()?,
parent_receipt_hash: parent,
signer_pubkey: unhex(s("signer_pubkey")?)?,
signer_id: s("signer_id")?.to_string(),
sig: unhex(s("sig")?)?,
})
}
}
impl Default for ResourceJob {
/// 2048-bit factoring on the `(ns, 10⁻⁴)` gate-based qubit, `ε = 1/3`.
fn default() -> Self {
ResourceJob {
program: Program::Logical(LogicalCounts {
qubits: 12_581,
measurements: 1_080_000_000,
rotations: 12,
rotation_depth: 12,
t_gates: 12,
ccz: 3_730_000_000,
}),
qubit: PhysicalQubit::ns_e4(),
code: None,
config: EstimateConfig { error_budget: 1.0 / 3.0, slowdown: 1, limits: SearchLimits::default() },
}
}
}
crate::quantum_energy::labellable!(ResourceReceipt, DOMAIN_RECEIPT_ID);
#[cfg(test)]
mod tests {
use super::*;
fn key(b: u8) -> SigningKey {
SigningKey::from_bytes(&[b; 32])
}
fn unmetered(run: &mut dyn FnMut()) -> u64 {
run();
0
}
#[test]
fn a_sealed_estimate_verifies_and_reproduces() {
let j = ResourceJob::default();
let r = ResourceReceipt::seal(&key(1), "sink-a", &j, None, |run| {
run();
1_700
})
.unwrap();
assert!(r.verify());
assert!(r.verify_computation(&j));
assert_eq!(r.estimate, j.run().unwrap());
assert_eq!((r.estimate.distance, r.estimate.factories), (13, 18));
assert_eq!(r.joules_micro, 1_700);
}
#[test]
fn the_json_round_trips_to_the_same_bytes() {
// With a factory, and a Clifford-only program without one.
let clifford = ResourceJob {
program: Program::Logical(LogicalCounts { qubits: 50, measurements: 1_000, ..Default::default() }),
..ResourceJob::default()
};
for job in [ResourceJob::default(), clifford] {
let r = ResourceReceipt::seal(&key(2), "sink \"b\"", &job, Some([7; 32]), unmetered).unwrap();
let back = ResourceReceipt::from_json(&r.to_json()).unwrap();
assert_eq!(back, r);
assert_eq!(back.to_json(), r.to_json());
assert!(back.verify());
assert!(back.verify_computation(&job));
}
}
#[test]
fn tampering_is_caught() {
let j = ResourceJob::default();
let r = ResourceReceipt::seal(&key(3), "sink", &j, None, unmetered).unwrap();
let mut a = r.clone();
a.joules_micro = 1;
assert!(!a.verify(), "the energy is signed");
let mut b = r.clone();
b.estimate.physical_qubits -= 1;
assert!(!b.verify(), "the headline is hashed");
let mut c = r.clone();
c.estimate.factory.as_mut().unwrap().rounds[0].copies = 15;
assert!(!c.verify(), "every factory round is hashed");
// One ulp on the physical error rate is a different job.
let mut other = j.clone();
other.qubit.clifford_error = f64::from_bits(other.qubit.clifford_error.to_bits() + 1);
assert!(!r.verify_computation(&other));
let mut fixed = j.clone();
fixed.code = Some(QecModel::surface_gate());
assert!(!r.verify_computation(&fixed), "choosing the code is part of the job");
let mut slower = j.clone();
slower.config.slowdown = 2;
assert!(!r.verify_computation(&slower));
}
#[test]
fn a_meter_that_never_runs_the_job_cannot_seal() {
assert_eq!(
ResourceReceipt::seal(&key(4), "sink", &ResourceJob::default(), None, |_run| 99),
Err(EstimateError::NotRun)
);
}
#[test]
fn an_energy_class_label_seals_over_it() {
use crate::quantum_energy::{EnergyClass, Labelled};
let r = ResourceReceipt::seal(&key(5), "sink", &ResourceJob::default(), None, |run| {
run();
1_000
})
.unwrap();
let labelled = Labelled::seal(r, EnergyClass::ModelBased, &key(5)).unwrap();
assert!(labelled.verify());
assert!(!labelled.receipt.verify(), "the inner signature now covers the label");
}
}

View file

@ -7,146 +7,51 @@
//! fdlibm's algorithm, as musl carries it, using only `+ − × ÷`, which Rust
//! never fuses.
/// `sin` and `cos` of `x`, bit-identical on every IEEE-754 platform. The
/// algorithm is fdlibm's (as musl carries it): Cody–Waite reduction by π/2 in
/// three parts, then minimax polynomials on [−π/4, π/4]. Only `+ − × ÷` are
/// used, and Rust never fuses them, so the bits do not depend on the target.
/// Accurate to within an ulp of the true value.
///
/// For `|x| > 2²⁰` (and for non-finite `x`) both are NaN: the reduction used
/// here is exact only below that, and a NaN is better than a value that
/// depends on the platform.
pub fn sin_cos(x: f64) -> (f64, f64) {
if !(x.abs() <= 1_048_576.0) {
return (f64::NAN, f64::NAN);
}
let ix = (x.to_bits() >> 32) as u32 & 0x7fff_ffff;
if ix <= 0x3fe9_21fb {
// |x| ≤ π/4
if ix < 0x3e40_0000 {
// |x| < 2⁻²⁷: sin x = x and cos x = 1 to the last bit.
return (x, 1.0);
}
return (k_sin(x, 0.0, false), k_cos(x, 0.0));
}
let (n, y0, y1) = rem_pio2(x, ix);
let (s, c) = (k_sin(y0, y1, true), k_cos(y0, y1));
match n & 3 {
0 => (s, c),
1 => (c, -s),
2 => (-s, -c),
_ => (-c, s),
}
}
const S1: f64 = -1.666_666_666_666_663_243_48e-1;
const S2: f64 = 8.333_333_333_322_489_461_24e-3;
const S3: f64 = -1.984_126_982_985_794_931_34e-4;
const S4: f64 = 2.755_731_370_707_006_767_89e-6;
const S5: f64 = -2.505_076_025_340_686_341_95e-8;
const S6: f64 = 1.589_690_995_211_550_102_21e-10;
fn k_sin(x: f64, y: f64, tail: bool) -> f64 {
let z = x * x;
let w = z * z;
let r = S2 + z * (S3 + z * S4) + z * w * (S5 + z * S6);
let v = z * x;
if !tail {
x + v * (S1 + z * r)
} else {
x - ((z * (0.5 * y - v * r) - y) - v * S1)
}
}
const C1: f64 = 4.166_666_666_666_660_190_37e-2;
const C2: f64 = -1.388_888_888_887_410_957_49e-3;
const C3: f64 = 2.480_158_728_947_672_941_78e-5;
const C4: f64 = -2.755_731_435_139_066_330_35e-7;
const C5: f64 = 2.087_572_321_298_174_827_90e-9;
const C6: f64 = -1.135_964_755_778_819_482_65e-11;
fn k_cos(x: f64, y: f64) -> f64 {
let z = x * x;
let w = z * z;
let r = z * (C1 + z * (C2 + z * C3)) + w * w * (C4 + z * (C5 + z * C6));
let hz = 0.5 * z;
let w = 1.0 - hz;
w + (((1.0 - w) - hz) + (z * r - x * y))
}
const TOINT: f64 = 1.5 / f64::EPSILON;
const PIO4: f64 = 7.853_981_633_974_482_789_99e-1;
const INVPIO2: f64 = 6.366_197_723_675_813_824_33e-1;
const PIO2_1: f64 = 1.570_796_326_734_125_614_17;
const PIO2_1T: f64 = 6.077_100_506_506_192_249_32e-11;
const PIO2_2: f64 = 6.077_100_506_303_965_976_60e-11;
const PIO2_2T: f64 = 2.022_266_248_795_950_631_54e-21;
const PIO2_3: f64 = 2.022_266_248_711_166_455_80e-21;
const PIO2_3T: f64 = 8.478_427_660_368_899_569_97e-32;
/// `x = n·π/2 + (y0 + y1)` with `|y0 + y1| ≤ π/4`, for `|x| ≤ 2²⁰`.
fn rem_pio2(x: f64, ix: u32) -> (i64, f64, f64) {
let mut f = (x * INVPIO2 + TOINT) - TOINT;
let mut r = x - f * PIO2_1;
let mut w = f * PIO2_1T;
// Matters only under directed rounding; kept so the port is exact.
if r - w < -PIO4 {
f -= 1.0;
r = x - f * PIO2_1;
w = f * PIO2_1T;
} else if r - w > PIO4 {
f += 1.0;
r = x - f * PIO2_1;
w = f * PIO2_1T;
}
let mut y0 = r - w;
let ex = (ix >> 20) as i32;
let ey = ((y0.to_bits() >> 52) & 0x7ff) as i32;
if ex - ey > 16 {
let t = r;
w = f * PIO2_2;
r = t - w;
w = f * PIO2_2T - ((t - r) - w);
y0 = r - w;
let ey = ((y0.to_bits() >> 52) & 0x7ff) as i32;
if ex - ey > 49 {
let t = r;
w = f * PIO2_3;
r = t - w;
w = f * PIO2_3T - ((t - r) - w);
y0 = r - w;
}
}
let y1 = (r - y0) - w;
(f as i64, y0, y1)
}
#[cfg(any(
feature = "quantum_spd",
feature = "quantum_pauli",
feature = "quantum_mps",
feature = "quantum_vml",
feature = "quantum_phasor",
feature = "quantum_kernel"
))]
mod trig;
#[cfg(any(
feature = "quantum_spd",
feature = "quantum_pauli",
feature = "quantum_mps",
feature = "quantum_vml",
feature = "quantum_phasor",
feature = "quantum_kernel"
))]
pub use trig::sin_cos;
// ---------------------------------------------------------------------------
// Natural logarithm
// ---------------------------------------------------------------------------
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LN2_HI: f64 = 6.931_471_803_691_238_164_90e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LN2_LO: f64 = 1.908_214_929_270_587_700_02e-10;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG1: f64 = 6.666_666_666_666_735_130e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG2: f64 = 3.999_999_999_940_941_908e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG3: f64 = 2.857_142_874_366_239_149e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG4: f64 = 2.222_219_843_214_978_396e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG5: f64 = 1.818_357_216_161_805_012e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG6: f64 = 1.531_383_769_920_937_332e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
const LG7: f64 = 1.479_819_860_511_658_591e-1;
/// The natural logarithm, within an ulp of the true value: `−∞` at zero, NaN
/// below it.
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
pub fn ln(x: f64) -> f64 {
let mut bits = x.to_bits();
let mut hx = (bits >> 32) as u32;
@ -357,7 +262,7 @@ mod tests {
})
}
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel"))]
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource"))]
#[test]
fn ln_is_within_an_ulp_of_the_platform() {
for x in inputs().filter(|&x| x > 0.0) {

View file

@ -0,0 +1,116 @@
//! `sin` and `cos`, for the layers that rotate: fdlibm's algorithm as musl
//! carries it, using only `+ − × ÷`.
/// `sin` and `cos` of `x`, bit-identical on every IEEE-754 platform. The
/// algorithm is fdlibm's (as musl carries it): Cody–Waite reduction by π/2 in
/// three parts, then minimax polynomials on [−π/4, π/4]. Only `+ − × ÷` are
/// used, and Rust never fuses them, so the bits do not depend on the target.
/// Accurate to within an ulp of the true value.
///
/// For `|x| > 2²⁰` (and for non-finite `x`) both are NaN: the reduction used
/// here is exact only below that, and a NaN is better than a value that
/// depends on the platform.
pub fn sin_cos(x: f64) -> (f64, f64) {
if !(x.abs() <= 1_048_576.0) {
return (f64::NAN, f64::NAN);
}
let ix = (x.to_bits() >> 32) as u32 & 0x7fff_ffff;
if ix <= 0x3fe9_21fb {
// |x| ≤ π/4
if ix < 0x3e40_0000 {
// |x| < 2⁻²⁷: sin x = x and cos x = 1 to the last bit.
return (x, 1.0);
}
return (k_sin(x, 0.0, false), k_cos(x, 0.0));
}
let (n, y0, y1) = rem_pio2(x, ix);
let (s, c) = (k_sin(y0, y1, true), k_cos(y0, y1));
match n & 3 {
0 => (s, c),
1 => (c, -s),
2 => (-s, -c),
_ => (-c, s),
}
}
const S1: f64 = -1.666_666_666_666_663_243_48e-1;
const S2: f64 = 8.333_333_333_322_489_461_24e-3;
const S3: f64 = -1.984_126_982_985_794_931_34e-4;
const S4: f64 = 2.755_731_370_707_006_767_89e-6;
const S5: f64 = -2.505_076_025_340_686_341_95e-8;
const S6: f64 = 1.589_690_995_211_550_102_21e-10;
fn k_sin(x: f64, y: f64, tail: bool) -> f64 {
let z = x * x;
let w = z * z;
let r = S2 + z * (S3 + z * S4) + z * w * (S5 + z * S6);
let v = z * x;
if !tail {
x + v * (S1 + z * r)
} else {
x - ((z * (0.5 * y - v * r) - y) - v * S1)
}
}
const C1: f64 = 4.166_666_666_666_660_190_37e-2;
const C2: f64 = -1.388_888_888_887_410_957_49e-3;
const C3: f64 = 2.480_158_728_947_672_941_78e-5;
const C4: f64 = -2.755_731_435_139_066_330_35e-7;
const C5: f64 = 2.087_572_321_298_174_827_90e-9;
const C6: f64 = -1.135_964_755_778_819_482_65e-11;
fn k_cos(x: f64, y: f64) -> f64 {
let z = x * x;
let w = z * z;
let r = z * (C1 + z * (C2 + z * C3)) + w * w * (C4 + z * (C5 + z * C6));
let hz = 0.5 * z;
let w = 1.0 - hz;
w + (((1.0 - w) - hz) + (z * r - x * y))
}
const TOINT: f64 = 1.5 / f64::EPSILON;
const PIO4: f64 = 7.853_981_633_974_482_789_99e-1;
const INVPIO2: f64 = 6.366_197_723_675_813_824_33e-1;
const PIO2_1: f64 = 1.570_796_326_734_125_614_17;
const PIO2_1T: f64 = 6.077_100_506_506_192_249_32e-11;
const PIO2_2: f64 = 6.077_100_506_303_965_976_60e-11;
const PIO2_2T: f64 = 2.022_266_248_795_950_631_54e-21;
const PIO2_3: f64 = 2.022_266_248_711_166_455_80e-21;
const PIO2_3T: f64 = 8.478_427_660_368_899_569_97e-32;
/// `x = n·π/2 + (y0 + y1)` with `|y0 + y1| ≤ π/4`, for `|x| ≤ 2²⁰`.
fn rem_pio2(x: f64, ix: u32) -> (i64, f64, f64) {
let mut f = (x * INVPIO2 + TOINT) - TOINT;
let mut r = x - f * PIO2_1;
let mut w = f * PIO2_1T;
// Matters only under directed rounding; kept so the port is exact.
if r - w < -PIO4 {
f -= 1.0;
r = x - f * PIO2_1;
w = f * PIO2_1T;
} else if r - w > PIO4 {
f += 1.0;
r = x - f * PIO2_1;
w = f * PIO2_1T;
}
let mut y0 = r - w;
let ex = (ix >> 20) as i32;
let ey = ((y0.to_bits() >> 52) & 0x7ff) as i32;
if ex - ey > 16 {
let t = r;
w = f * PIO2_2;
r = t - w;
w = f * PIO2_2T - ((t - r) - w);
y0 = r - w;
let ey = ((y0.to_bits() >> 52) & 0x7ff) as i32;
if ex - ey > 49 {
let t = r;
w = f * PIO2_3;
r = t - w;
w = f * PIO2_3T - ((t - r) - w);
y0 = r - w;
}
}
let y1 = (r - y0) - w;
(f as i64, y0, y1)
}

View file

@ -14,7 +14,8 @@
feature = "quantum_mps",
feature = "quantum_pauli",
feature = "quantum_spd",
feature = "quantum_bptn"
feature = "quantum_bptn",
feature = "quantum_resource"
))]
use wai_quantum::*;
@ -121,3 +122,28 @@ fn tensor_network_evolution() {
v.push(t.discarded());
assert_eq!(digest(&v), 0x0e51_33ff_7987_aa26);
}
/// Every figure of the factoring and chemistry estimates on all six reference
/// qubits: distances, factory rounds, qubit counts, times and error rates.
/// The rotation T-count goes through the in-crate logarithm.
#[test]
fn resource_estimates() {
use quantum_resource::*;
let programs = [
(LogicalCounts { qubits: 12_581, measurements: 1_080_000_000, rotations: 12, rotation_depth: 12, t_gates: 12, ccz: 3_730_000_000 }, 1.0 / 3.0),
(LogicalCounts { qubits: 1318, measurements: 1_370_000_000, rotations: 206_000_000, rotation_depth: 205_000_000, t_gates: 55_300_000, ccz: 135_000_000_000 }, 0.01),
];
let mut v = Vec::new();
for (c, eps) in programs {
for q in PhysicalQubit::presets() {
let e = estimate_best(&c, &q, &EstimateConfig::new(eps)).unwrap();
v.extend([e.distance as f64, e.steps as f64, e.runtime_ns, e.logical_error, e.t_target]);
v.extend([e.factories as f64, e.physical_qubits as f64, e.isa.t_per_rotation as f64]);
for r in &e.factory.unwrap().rounds {
v.extend([r.distance as f64, r.copies as f64, r.duration_ns, r.failure, r.output_error]);
}
}
}
assert_eq!(digest(&v), 0xce9d_43b2_d498_f379);
}

2
wai/wai-rs/Cargo.lock generated
View file

@ -3900,7 +3900,7 @@ dependencies = [
[[package]]
name = "wai-quantum"
version = "0.3.23"
version = "0.3.24"
dependencies = [
"blake3",
"ed25519-dalek",

View file

@ -341,6 +341,10 @@ quantum_spd = ["quantum", "wai-quantum/quantum_spd"]
quantum_spd_receipt = ["quantum_spd", "wai-quantum/quantum_spd_receipt"]
# Tensor-network states on the hardware graph (simple update, belief propagation).
quantum_bptn = ["quantum_spd", "wai-quantum/quantum_bptn"]
# Fault-tolerant resource estimation (layout, distance, T factories, frontier),
# and its signed receipts.
quantum_resource = ["dep:wai-quantum", "wai-quantum/quantum_resource"]
quantum_resource_receipt = ["quantum_resource", "wai-quantum/quantum_resource_receipt"]
quantum_qir = ["quantum_toolchain", "wai-quantum/quantum_qir"]
# Variational quantum machine learning (extensions/quantum-sim § VML,
# `wai.quantum.vml`): the interference-based ML the thermodynamic-computing pivot
@ -468,6 +472,7 @@ required-features = [
"quantum_qudit",
"quantum_spd_receipt",
"quantum_bptn",
"quantum_resource_receipt",
]
[[bin]]

View file

@ -36,6 +36,17 @@
//! wai-quantum utility --receipt | wai-quantum verify
//! ```
//!
//! And for fault-tolerant machines that do not exist yet, what a program would
//! need — physical qubits and time — on six reference qubits, as a re-checkable
//! signed estimate:
//!
//! ```text
//! wai-quantum estimate factoring # 2048-bit factoring, all six qubits
//! wai-quantum estimate chemistry --qubit ns-e4 --frontier
//! wai-quantum estimate --qubits 100 --ccz 1e6 --budget 0.01
//! wai-quantum estimate factoring --qubit ns-e4 --receipt | wai-quantum verify
//! ```
//!
//! The `channel` command prints the Holevo bound before and after, which can only
//! fall: noise never creates information. Two results there are easy to miss —
//! fully damping `|1⟩` yields a *pure* output that still carries nothing (both
@ -51,6 +62,8 @@ use wai::quantum_info::{chsh, DensityMatrix};
use wai::quantum_qudit::QuditState;
use wai::quantum_energy::{EnergyClass, MaybeLabelled};
use wai::quantum_receipt::QuantumReceipt;
use wai::quantum_resource::{self as qr, EstimateConfig, LogicalCounts, PhysicalQubit, QecModel};
use wai::quantum_resource_receipt::{Program, ResourceJob, ResourceReceipt};
use wai::quantum_spd_receipt::{SpdJob, SpdReceipt};
use wai::quantum_source::{holevo_bound, schumacher_limit, von_neumann_entropy};
use wai::quantum_toolchain as qt;
@ -128,6 +141,11 @@ fn usage() -> ! {
UTILITY SCALE:\n \
wai-quantum utility [--theta T] [--steps N] [--threshold D] [-q QUBIT] [--json] [--receipt]\n \
wai-quantum utility --method bptn [--chi X] [--theta T] [--steps N] [-q QUBIT] [--json]\n\n\
FAULT-TOLERANT RESOURCES:\n \
wai-quantum estimate [factoring|chemistry] [--qubit NAME|all] [--code surface|surface-meas|floquet]\n \
[--budget E] [--slowdown N] [--frontier] [--json] [--receipt]\n \
wai-quantum estimate --qubits N [--ccz N] [--t N] [--rotations N] [--depth N] [--measurements N] ...\n \
Qubits: us-e3, us-e4, ns-e3, ns-e4, meas-e4, meas-e6.\n\n\
Channels: identity, bitflip, dephasing, depolarizing, damping.\n\
Run `wai-quantum list` for the algorithm catalog."
);
@ -185,6 +203,126 @@ fn describe(c: &Circuit) -> (String, String, u64, String) {
)
}
/// The six reference qubits by command-line name.
fn reference_qubit(name: &str) -> PhysicalQubit {
match name {
"us-e3" => PhysicalQubit::us_e3(),
"us-e4" => PhysicalQubit::us_e4(),
"ns-e3" => PhysicalQubit::ns_e3(),
"ns-e4" => PhysicalQubit::ns_e4(),
"meas-e4" => PhysicalQubit::meas_ns_e4(),
"meas-e6" => PhysicalQubit::meas_ns_e6(),
other => die(&format!("unknown qubit '{other}' (us-e3, us-e4, ns-e3, ns-e4, meas-e4, meas-e6, all)")),
}
}
/// A duration in the unit a person would say it in.
fn human_time(ns: f64) -> String {
let s = ns / 1e9;
let (m, h, d, y) = (60.0, 3600.0, 86_400.0, 365.25 * 86_400.0);
if s < 1e-3 {
format!("{:.1} us", ns / 1e3)
} else if s < 1.0 {
format!("{:.1} ms", s * 1e3)
} else if s < m {
format!("{s:.2} s")
} else if s < h {
format!("{:.1} min", s / m)
} else if s < d {
format!("{} h {} min", (s / h) as u64, ((s % h) / m) as u64)
} else if s < y {
format!("{:.1} days", s / d)
} else {
format!("{:.1} years", s / y)
}
}
/// A program for `estimate`: a named example, or counts from flags.
/// Returns the counts and the error budget the example is stated with.
fn estimate_program(args: &[String], name: Option<&String>) -> (LogicalCounts, f64) {
let counted = ["--qubits", "--ccz", "--t", "--rotations", "--depth", "--measurements"]
.iter()
.any(|f| opt(args, f, "").is_some());
if counted {
// Counts may be written as 1e9; they must still be whole numbers.
let count = |f: &str| -> u64 {
let v: f64 = num(args, f, "", 0.0);
if !(v >= 0.0 && v.fract() == 0.0 && v < 1.8e19) {
die(&format!("{f} must be a whole number"));
}
v as u64
};
let c = LogicalCounts {
qubits: count("--qubits"),
measurements: count("--measurements"),
rotations: count("--rotations"),
rotation_depth: count("--depth"),
t_gates: count("--t"),
ccz: count("--ccz"),
};
if c.rotations > 0 && c.rotation_depth == 0 {
die("--rotations needs --depth (non-Clifford layers containing a rotation)");
}
return (c, 0.001);
}
match name.map(String::as_str).unwrap_or("factoring") {
// 2048-bit factoring (windowed, coset representation), ε = 1/3: the
// answer is checkable, so a failed run is simply repeated.
"factoring" => (
LogicalCounts { qubits: 12_581, measurements: 1_080_000_000, rotations: 12, rotation_depth: 12, t_gates: 12, ccz: 3_730_000_000 },
1.0 / 3.0,
),
// Ground-state energy of a 76-orbital catalyst model by double-
// factorized qubitization, ε = 0.01.
"chemistry" => (
LogicalCounts {
qubits: 1318,
measurements: 1_370_000_000,
rotations: 206_000_000,
rotation_depth: 205_000_000,
t_gates: 55_300_000,
ccz: 135_000_000_000,
},
0.01,
),
other => die(&format!("unknown program '{other}' (factoring, chemistry, or counts via --qubits ...)")),
}
}
fn estimate_json(e: &qr::Estimate) -> String {
let rounds: Vec<String> = e
.factory
.iter()
.flat_map(|f| f.rounds.iter())
.map(|r| {
format!(
"{{\"copies\":{},\"distance\":{},\"unit\":\"{}\"}}",
r.copies,
r.distance,
match r.kind {
qr::UnitKind::SpaceEfficient => "space-efficient",
qr::UnitKind::ReedMuller => "reed-muller",
}
)
})
.collect();
format!(
"{{\"code\":{},\"distance\":{},\"factories\":{},\"factory_fraction\":{},\"factory_rounds\":[{}],\
\"logical_steps\":{},\"physical_qubits\":{},\"qubit\":{},\"runtime_s\":{},\"t_states\":{},\"tiles\":{}}}",
serde_json::to_string(&e.code).unwrap(),
e.distance,
e.factories,
e.factory_fraction(),
rounds.join(","),
e.steps,
e.physical_qubits,
serde_json::to_string(&e.qubit).unwrap(),
e.runtime_ns / 1e9,
e.isa.t_states,
e.isa.tiles,
)
}
fn main() {
let argv: Vec<String> = std::env::args().collect();
if argv.len() < 2 {
@ -270,6 +408,25 @@ fn main() {
// carries its acquisition class inside the signature
// (`quantum_energy::Labelled`, as `wai_quantum_meter` emits).
let src = read_stdin();
if let Some(any) = MaybeLabelled::<ResourceReceipt>::from_json(&src) {
let ok = any.verify();
let r = any.receipt();
let e = &r.estimate;
println!("signature {}", if ok { "VALID" } else { "INVALID" });
println!("kind fault-tolerant resource estimate");
println!("job-hash {}", hx(&r.job_hash));
println!("qubit {} ({}, d = {})", e.qubit, e.code, e.distance);
println!("physical {} qubits", e.physical_qubits);
println!("runtime {}", human_time(e.runtime_ns));
println!("factories {}", e.factories);
println!("joules {} uJ", r.joules_micro);
println!("energy class {}", energy_class_line(any.energy_class(), r.joules_micro));
println!("signer {}", r.signer_id);
if !ok {
std::process::exit(1);
}
return;
}
if let Some(any) = MaybeLabelled::<SpdReceipt>::from_json(&src) {
let ok = any.verify();
let r = any.receipt();
@ -388,6 +545,112 @@ fn main() {
}
}
// What a fault-tolerant machine would need to run a program: code
// distance, T factories, physical qubits and wall-clock time, on the
// six reference qubits. `--receipt` seals one estimate, re-checkable
// bit for bit by anyone who re-runs it.
"estimate" => {
let (counts, default_budget) = estimate_program(args, algo);
let budget: f64 = num(args, "--budget", "", default_budget);
let slowdown: u64 = num(args, "--slowdown", "", 1);
let cfg = EstimateConfig { error_budget: budget, slowdown, ..EstimateConfig::new(budget) };
let code = opt(args, "--code", "").map(|c| match c.as_str() {
"surface" => QecModel::surface_gate(),
"surface-meas" => QecModel::surface_measurement(),
"floquet" => QecModel::floquet(),
other => die(&format!("unknown code '{other}' (surface, surface-meas, floquet)")),
});
let which = opt(args, "--qubit", "").unwrap_or_else(|| {
if flag(args, "--receipt") || flag(args, "--frontier") { "ns-e4".into() } else { "all".into() }
});
let qubits: Vec<PhysicalQubit> =
if which == "all" { PhysicalQubit::presets() } else { vec![reference_qubit(&which)] };
if flag(args, "--receipt") {
if qubits.len() != 1 {
die("--receipt seals one estimate: pick a --qubit");
}
let job = ResourceJob { program: Program::Logical(counts), qubit: qubits[0].clone(), code, config: cfg };
// The same deterministic demo key as `receipt`: the estimate is
// bit-identical everywhere, so is its receipt.
let key = SigningKey::from_bytes(&[42u8; 32]);
let r = ResourceReceipt::seal(&key, "did:key:wai-quantum-cli", &job, None, |run| {
run();
0
})
.unwrap_or_else(|e| die(&format!("estimate: {e:?}")));
println!("{}", r.to_json());
return;
}
let run = |q: &PhysicalQubit, cfg: &EstimateConfig| match &code {
Some(c) => qr::estimate(&counts, q, c, cfg),
None => qr::estimate_best(&counts, q, cfg),
};
if flag(args, "--frontier") {
// Slow the program down to need fewer factories: each point
// uses fewer qubits than every faster one.
let q = &qubits[0];
let c = code.clone().unwrap_or_else(|| {
run(q, &cfg).map(|e| {
QecModel::for_qubit(q).into_iter().find(|m| m.name == e.code).expect("code of the estimate")
})
.unwrap_or_else(|e| die(&format!("estimate: {e:?}")))
});
let isa = qr::layout(&counts, budget);
let json = flag(args, "--json");
let points = qr::frontier(&isa, q, &c, &cfg, 1024);
if json {
let rows: Vec<String> = points.iter().map(estimate_json).collect();
println!("[{}]", rows.join(","));
return;
}
println!("{:>8} {:>3} {:>9} {:>14} {}", "slowdown", "d", "factories", "physical", "runtime");
for e in &points {
println!(
"{:>8} {:>3} {:>9} {:>14} {}",
e.steps / isa.min_steps,
e.distance,
e.factories,
e.physical_qubits,
human_time(e.runtime_ns)
);
}
return;
}
let estimates: Vec<_> = qubits.iter().map(|q| (q, run(q, &cfg))).collect();
if flag(args, "--json") {
let rows: Vec<String> = estimates
.iter()
.map(|(q, e)| match e {
Ok(e) => estimate_json(e),
Err(err) => format!("{{\"error\":\"{err:?}\",\"qubit\":{}}}", serde_json::to_string(&q.name).unwrap()),
})
.collect();
println!("[{}]", rows.join(","));
return;
}
let isa = qr::layout(&counts, budget);
println!("program {} tiles, {} logical steps, {} T states (budget {budget})", isa.tiles, isa.min_steps, isa.t_states);
println!(
"{:<12} {:<13} {:>3} {:>9} {:>8} {:>14} {}",
"qubit", "code", "d", "factories", "share", "physical", "runtime"
);
for (q, e) in &estimates {
match e {
Ok(e) => println!(
"{:<12} {:<13} {:>3} {:>9} {:>7.1}% {:>14} {}",
e.qubit,
e.code,
e.distance,
e.factories,
100.0 * e.factory_fraction(),
e.physical_qubits,
human_time(e.runtime_ns)
),
Err(err) => println!("{:<12} {err:?}", q.name),
}
}
}
"entropy" => {
let c = circuit_from(args, algo);
let sv = c.simulate().unwrap_or_else(|e| die(&format!("simulate: {e:?}")));

View file

@ -590,6 +590,12 @@ pub use wai_quantum::quantum_spd_receipt;
#[cfg(feature = "quantum_bptn")]
pub use wai_quantum::quantum_bptn;
#[cfg(feature = "quantum_resource")]
pub use wai_quantum::quantum_resource;
#[cfg(feature = "quantum_resource_receipt")]
pub use wai_quantum::quantum_resource_receipt;
#[cfg(feature = "quantum_qir")]
pub use wai_quantum::quantum_qir;

View file

@ -734,7 +734,7 @@ dependencies = [
[[package]]
name = "wai-quantum"
version = "0.3.23"
version = "0.3.24"
dependencies = [
"blake3",
"ed25519-dalek",