wai-quantum 0.3.24: fault-tolerant resource estimation, reproducible and signed #141
15 changed files with 2108 additions and 132 deletions
2
wai/wai-quantum-serve/Cargo.lock
generated
2
wai/wai-quantum-serve/Cargo.lock
generated
|
|
@ -589,7 +589,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wai-quantum"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
2
wai/wai-quantum/Cargo.lock
generated
2
wai/wai-quantum/Cargo.lock
generated
|
|
@ -415,7 +415,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|||
|
||||
[[package]]
|
||||
name = "wai-quantum"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
1024
wai/wai-quantum/src/quantum_resource.rs
Normal file
1024
wai/wai-quantum/src/quantum_resource.rs
Normal file
File diff suppressed because it is too large
Load diff
584
wai/wai-quantum/src/quantum_resource_receipt.rs
Normal file
584
wai/wai-quantum/src/quantum_resource_receipt.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
116
wai/wai-quantum/src/repro/trig.rs
Normal file
116
wai/wai-quantum/src/repro/trig.rs
Normal 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)
|
||||
}
|
||||
|
|
@ -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
2
wai/wai-rs/Cargo.lock
generated
|
|
@ -3900,7 +3900,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wai-quantum"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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:?}")));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
2
wai/wai-web/Cargo.lock
generated
2
wai/wai-web/Cargo.lock
generated
|
|
@ -734,7 +734,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wai-quantum"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
Loading…
Reference in a new issue