wai-quantum 0.3.26: tensor-network contraction, hyper-optimised, sliced, bit-identical #144
15 changed files with 1949 additions and 23 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.25"
|
||||
version = "0.3.26"
|
||||
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.25"
|
||||
version = "0.3.26"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "wai-quantum"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
edition = "2024"
|
||||
rust-version = "1.97"
|
||||
license = "Apache-2.0"
|
||||
|
|
@ -56,6 +56,9 @@ quantum_resource_receipt = ["quantum_resource", "_signing"]
|
|||
# Noisy Clifford circuits at QEC scale: bit-packed Pauli-frame sampling, detector
|
||||
# error models by backward sensitivity, and exact minimum-weight matching.
|
||||
quantum_frame = []
|
||||
# Tensor-network contraction: hyper-optimised contraction trees, slicing, and
|
||||
# amplitudes of circuits far past the state vector, reproducible bit for bit.
|
||||
quantum_tn = []
|
||||
# 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"]
|
||||
|
|
@ -95,13 +98,17 @@ quantum_phasor_meter = ["_signing"]
|
|||
|
||||
# Everything.
|
||||
full = [
|
||||
"quantum", "quantum_toolchain", "quantum_mps", "quantum_stabilizer", "quantum_pauli", "quantum_spd", "quantum_spd_receipt", "quantum_bptn", "quantum_resource", "quantum_resource_receipt", "quantum_frame", "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_frame", "quantum_tn", "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",
|
||||
"quantum_phasor_meter",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
name = "tn_path"
|
||||
required-features = ["quantum_tn"]
|
||||
|
||||
[[example]]
|
||||
name = "qec_threshold"
|
||||
required-features = ["quantum_frame"]
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ never creates information.
|
|||
| `quantum_mitigate` | zero-noise extrapolation, readout M3, classical shadows |
|
||||
| `quantum_qec` | CSS codes (distance verified by exhaustive search, not asserted) — **rotated surface** `[[d²,1,d]]`, toric, bivariate-bicycle, and the **`[[7,1,3]]` colour code**, whose self-duality buys a *transversal* logical Hadamard — one round, no ancillas, no surgery — verified on the simulator, with the surface code shown failing the same move — with **two decoders**: union-find (weighted matching, single-shot, space-time with faulty measurement, **and circuit-level with hook errors**; **imports and exports** Stim-syntax detector error models, so other tools' noise models decode here and ours decode there) and Relay-BP (for qLDPC) |
|
||||
| `quantum_frame` | **noisy Clifford circuits at QEC scale**: reads the ecosystem's circuit text; samples detection events by bit-packed Pauli-frame simulation (64 shots per word); derives the detector error model by backward sensitivity, refusing non-deterministic detectors; generates the rotated surface-code memory experiment byte for byte as the reference layout; and decodes by **exact minimum-weight perfect matching** (Edmonds' blossom on integer weights). Measures logical error rates and fits the code model resource estimates assume |
|
||||
| `quantum_tn` | **tensor-network contraction**: circuits (any unitary on any qubits) become networks; a seeded hyper-optimiser finds contraction trees from randomised-greedy and multilevel-partition families, reconfigures them optimally in subtrees, and slices them to a memory cap with a second search around the slices; slices contract in a fixed order, threaded, to the same bits everywhere. Single amplitudes or open-output batches |
|
||||
| `quantum_compile` | Clifford routing + stabilizer-tableau equivalence proof |
|
||||
| `quantum_atom` | neutral-atom register preparation (Hungarian / LSAP) |
|
||||
| `quantum_qir` | **QIR export + ingest** — emit and parse QIR (the QIR Alliance's LLVM-based IR); emitted modules validated with `llvm-as`, and 9 of 10 catalog algorithms re-ingest to a bit-identical statevector |
|
||||
|
|
@ -175,6 +176,29 @@ fitted code model comes out at `a ≈ 0.023`, `p* ≈ 0.0100`. That is a measure
|
|||
value for the constants a resource estimate (`quantum_resource`) otherwise takes
|
||||
on trust, and it can be fed straight into one.
|
||||
|
||||
## Tensor networks
|
||||
|
||||
An amplitude of a circuit is a tensor network, and the order it is contracted
|
||||
in decides whether it costs `2³⁰` operations or `2⁶⁰`. `quantum_tn` searches for
|
||||
that order, slices it to fit memory, and contracts it:
|
||||
|
||||
```sh
|
||||
wai-quantum tn --file circuit.jsonl --bits 0110… # one amplitude
|
||||
wai-quantum tn --file circuit.jsonl --open 0,5,9 # a batch over three open outputs
|
||||
wai-quantum tn --file circuit.jsonl --plan-only --cap-log2 30
|
||||
```
|
||||
|
||||
Circuits are JSON lines, one gate per line: `{"q":[a,b],"m":[re,im, …]}`.
|
||||
|
||||
On single amplitudes of random circuits on the 54-qubit advantage-experiment
|
||||
grid, the search finds trees of `10¹⁰·⁵` operations at 10 cycles and `10¹⁸·⁸` at
|
||||
20, in seconds. An independently developed hyper-optimiser, given the same
|
||||
networks, lands within a factor of 2.5 of these at every depth, in either
|
||||
direction. The 54-qubit, 10-cycle amplitude, contracted here in 16 slices,
|
||||
agrees with an independent contraction to 12 significant figures. The module
|
||||
documentation states what slicing costs on deep circuits and what the scalar
|
||||
executor can and cannot contract.
|
||||
|
||||
## Determinism
|
||||
|
||||
Simulation is **byte-exact**: amplitudes are dyadic fixed-point, so a circuit
|
||||
|
|
|
|||
63
wai/wai-quantum/examples/tn_path.rs
Normal file
63
wai/wai-quantum/examples/tn_path.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
//! Contraction trees for a circuit's amplitude, from the command line.
|
||||
//!
|
||||
//! Reads a circuit as JSON lines, one gate per line:
|
||||
//! `{"q":[0,1],"m":[re,im, re,im, …]}` (row-major, rows = outputs, the first
|
||||
//! qubit most significant). Writes the simplified network's shape (for any
|
||||
//! other tool to search the same network) and the best plan found.
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo run --release --example tn_path --features quantum_tn -- CIRCUIT N TRIALS SEED [LOG2_CAP] [SHAPE_OUT] [--contract]
|
||||
//! ```
|
||||
use wai_quantum::quantum_tn::{contract_plan, find_path, Families, Gate, Network, PathOptions};
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let text = std::fs::read_to_string(&a[1]).expect("read circuit");
|
||||
let n: u32 = a[2].parse().unwrap();
|
||||
let trials: usize = a[3].parse().unwrap();
|
||||
let seed: u64 = a[4].parse().unwrap();
|
||||
let cap: Option<f64> = a.get(5).filter(|s| *s != "-").map(|s| 2f64.powf(s.parse().unwrap()));
|
||||
let shape_out = a.get(6).filter(|s| *s != "-");
|
||||
let gates: Vec<Gate> = text
|
||||
.lines()
|
||||
.filter(|l| !l.trim().is_empty())
|
||||
.map(|l| {
|
||||
let v: serde_json::Value = serde_json::from_str(l).expect("json gate");
|
||||
let qubits: Vec<u32> = v["q"].as_array().unwrap().iter().map(|x| x.as_u64().unwrap() as u32).collect();
|
||||
let m: Vec<f64> = v["m"].as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect();
|
||||
let matrix = m.chunks(2).map(|c| wai_quantum::quantum_tn::complex(c[0], c[1])).collect();
|
||||
Gate { qubits, matrix }
|
||||
})
|
||||
.collect();
|
||||
let mut net = Network::amplitude(n, &gates, &vec![0; n as usize], &[]).expect("network");
|
||||
let raw = net.tensors.len();
|
||||
net.simplify();
|
||||
let shape = net.shape();
|
||||
if let Some(path) = shape_out {
|
||||
let inputs: Vec<String> = shape.iter().map(|s| format!("{s:?}")).collect();
|
||||
let dims: Vec<String> = net.dims.iter().map(|d| d.to_string()).collect();
|
||||
std::fs::write(path, format!("{{\"inputs\":[{}],\"output\":{:?},\"dims\":[{}]}}\n", inputs.join(","), net.open, dims.join(","))).unwrap();
|
||||
}
|
||||
let t0 = std::time::Instant::now();
|
||||
let families = match std::env::var("TN_FAMILIES").as_deref() {
|
||||
Ok("greedy") => Families::Greedy,
|
||||
Ok("partition") => Families::Partition,
|
||||
_ => Families::Both,
|
||||
};
|
||||
let reconfigure = std::env::var("TN_RECONF").ok().and_then(|v| v.parse().ok()).unwrap_or(8);
|
||||
let plan = find_path(&shape, &net.dims, &net.open, &PathOptions { trials, seed, max_size: cap, reconfigure, families });
|
||||
let dt = t0.elapsed().as_secs_f64();
|
||||
println!(
|
||||
"tensors {raw} -> {} log10(flops) {:.3} log2(max size) {:.1} slices {} search {:.1}s",
|
||||
shape.len(),
|
||||
plan.total_flops().log10(),
|
||||
plan.per_slice.max_size.log2(),
|
||||
plan.slices,
|
||||
dt
|
||||
);
|
||||
if a.iter().any(|x| x == "--contract") {
|
||||
let t0 = std::time::Instant::now();
|
||||
let t = contract_plan(&net, &plan).expect("contract");
|
||||
println!("amplitude {:.12e} {:+.12e}i contract {:.1}s", t.data[0].re, t.data[0].im, t0.elapsed().as_secs_f64());
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
#[cfg(feature = "_signing")]
|
||||
mod merkle;
|
||||
|
||||
#[cfg(any(feature = "quantum_mps", feature = "quantum_bptn"))]
|
||||
#[cfg(any(feature = "quantum_mps", feature = "quantum_bptn", feature = "quantum_tn"))]
|
||||
mod linalg;
|
||||
|
||||
#[cfg(any(
|
||||
|
|
@ -28,7 +28,8 @@ mod linalg;
|
|||
feature = "quantum_phasor",
|
||||
feature = "quantum_kernel",
|
||||
feature = "quantum_resource",
|
||||
feature = "quantum_frame"
|
||||
feature = "quantum_frame",
|
||||
feature = "quantum_tn"
|
||||
))]
|
||||
mod repro;
|
||||
|
||||
|
|
@ -98,6 +99,9 @@ pub mod quantum_frame;
|
|||
#[cfg(feature = "quantum_frame")]
|
||||
pub mod quantum_match;
|
||||
|
||||
#[cfg(feature = "quantum_tn")]
|
||||
pub mod quantum_tn;
|
||||
|
||||
#[cfg(feature = "quantum_info")]
|
||||
pub mod quantum_info;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ pub struct C {
|
|||
pub re: f64,
|
||||
pub im: f64,
|
||||
}
|
||||
// The tensor-network layer uses only the type and its constants; the
|
||||
// arithmetic helpers serve the decomposition backends.
|
||||
#[cfg_attr(not(any(feature = "quantum_mps", feature = "quantum_bptn")), allow(dead_code))]
|
||||
impl C {
|
||||
pub(crate) const ZERO: C = C { re: 0.0, im: 0.0 };
|
||||
pub(crate) const ONE: C = C { re: 1.0, im: 0.0 };
|
||||
|
|
|
|||
1676
wai/wai-quantum/src/quantum_tn.rs
Normal file
1676
wai/wai-quantum/src/quantum_tn.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -30,28 +30,28 @@ pub use trig::sin_cos;
|
|||
// Natural logarithm
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LN2_HI: f64 = 6.931_471_803_691_238_164_90e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LN2_LO: f64 = 1.908_214_929_270_587_700_02e-10;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LG1: f64 = 6.666_666_666_666_735_130e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LG2: f64 = 3.999_999_999_940_941_908e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LG3: f64 = 2.857_142_874_366_239_149e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LG4: f64 = 2.222_219_843_214_978_396e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LG5: f64 = 1.818_357_216_161_805_012e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const LG6: f64 = 1.531_383_769_920_937_332e-1;
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
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", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
pub fn ln(x: f64) -> f64 {
|
||||
let mut bits = x.to_bits();
|
||||
let mut hx = (bits >> 32) as u32;
|
||||
|
|
@ -95,7 +95,7 @@ pub fn ln(x: f64) -> f64 {
|
|||
// Exponential
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "quantum_frame")]
|
||||
#[cfg(any(feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
const EXP_P: [f64; 5] = [
|
||||
1.666_666_666_666_660_190_37e-1,
|
||||
-2.777_777_777_701_559_338_42e-3,
|
||||
|
|
@ -107,7 +107,7 @@ const EXP_P: [f64; 5] = [
|
|||
/// `eˣ`, bit-identical on every IEEE-754 platform: fdlibm's algorithm
|
||||
/// (reduction by `ln 2` in two parts, a rational approximation on
|
||||
/// `[−ln2/2, ln2/2]`), using only `+ − × ÷`. Within an ulp of the true value.
|
||||
#[cfg(feature = "quantum_frame")]
|
||||
#[cfg(any(feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
pub fn exp(x: f64) -> f64 {
|
||||
const LN2_HI_E: f64 = 6.931_471_803_691_238_164_90e-1;
|
||||
const LN2_LO_E: f64 = 1.908_214_929_270_587_700_02e-10;
|
||||
|
|
@ -308,7 +308,7 @@ mod tests {
|
|||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "quantum_frame")]
|
||||
#[cfg(any(feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
#[test]
|
||||
fn exp_is_within_an_ulp_of_the_platform() {
|
||||
let mut s = 0x9e37_79b9_7f4a_7c15u64;
|
||||
|
|
@ -351,7 +351,7 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame"))]
|
||||
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn"))]
|
||||
#[test]
|
||||
fn ln_is_within_an_ulp_of_the_platform() {
|
||||
for x in inputs().filter(|&x| x > 0.0) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
feature = "quantum_spd",
|
||||
feature = "quantum_bptn",
|
||||
feature = "quantum_resource",
|
||||
feature = "quantum_frame"
|
||||
feature = "quantum_frame",
|
||||
feature = "quantum_tn"
|
||||
))]
|
||||
|
||||
use wai_quantum::*;
|
||||
|
|
@ -172,3 +173,50 @@ fn frame_sampling_and_matching() {
|
|||
assert_eq!(digest(&v), 0xcbb3_99f9_6d5c_e8f0);
|
||||
}
|
||||
|
||||
/// A contraction plan found by the seeded hyper-optimiser, and the amplitude
|
||||
/// its sliced, threaded contraction gives: the search's random stream and
|
||||
/// logarithms, and the fixed-order complex arithmetic.
|
||||
#[test]
|
||||
fn tensor_network_plan_and_amplitude() {
|
||||
use quantum_tn::{complex, contract_plan, find_path, Families, Gate, Network, PathOptions};
|
||||
// A 3 × 4 grid, ten layers of fixed two-qubit gates and phased rotations.
|
||||
let (w, h) = (4u32, 3u32);
|
||||
let mut gates = Vec::new();
|
||||
let mut k = 0.0f64;
|
||||
for layer in 0..10u32 {
|
||||
for q in 0..w * h {
|
||||
k += 0.37;
|
||||
let (c, s) = (1.0 / (1.0 + k * k).sqrt(), k / (1.0 + k * k).sqrt());
|
||||
gates.push(Gate { qubits: vec![q], matrix: vec![complex(c, 0.0), complex(0.0, -s), complex(0.0, -s), complex(c, 0.0)] });
|
||||
}
|
||||
let horizontal = layer % 2 == 0;
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let (a, b) = if horizontal { (y * w + x, y * w + x + 1) } else { (y * w + x, (y + 1) * w + x) };
|
||||
let ok = if horizontal { x + 1 < w && (x + layer / 2) % 2 == 0 } else { y + 1 < h && (y + layer / 2) % 2 == 0 };
|
||||
if ok {
|
||||
// An iSWAP-like gate with a controlled phase.
|
||||
let z = complex(0.0, 0.0);
|
||||
let one = complex(1.0, 0.0);
|
||||
let mi = complex(0.0, -1.0);
|
||||
let ph = complex(0.5, -0.866_025_403_784_438_6);
|
||||
gates.push(Gate { qubits: vec![a, b], matrix: vec![one, z, z, z, z, z, mi, z, z, mi, z, z, z, z, z, ph] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let n = w * h;
|
||||
let bits: Vec<u8> = (0..n).map(|q| (q % 3 == 1) as u8).collect();
|
||||
let mut net = Network::amplitude(n, &gates, &bits, &[]).unwrap();
|
||||
net.simplify();
|
||||
let shape = net.shape();
|
||||
let free = find_path(&shape, &net.dims, &net.open, &PathOptions { trials: 12, seed: 5, max_size: None, reconfigure: 6, families: Families::Both });
|
||||
let cap = free.per_slice.max_size / 8.0;
|
||||
let plan = find_path(&shape, &net.dims, &net.open, &PathOptions { trials: 12, seed: 5, max_size: Some(cap), reconfigure: 6, families: Families::Both });
|
||||
let amp = contract_plan(&net, &plan).unwrap().data[0];
|
||||
let steps: Vec<f64> = plan.path.steps.iter().flat_map(|&(a, b)| [a as f64, b as f64]).collect();
|
||||
let mut v = vec![amp.re, amp.im, plan.per_slice.flops, plan.per_slice.max_size, plan.slices];
|
||||
v.extend(steps);
|
||||
assert_eq!(digest(&v), 0x8954_0c3a_d1fc_a04d);
|
||||
}
|
||||
|
||||
|
|
|
|||
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.25"
|
||||
version = "0.3.26"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
|
|
@ -351,6 +351,8 @@ quantum_resource_receipt = ["quantum_resource", "wai-quantum/quantum_resource_re
|
|||
# Noisy Clifford circuits at QEC scale: frame sampling, detector error models,
|
||||
# exact matching, measured code models.
|
||||
quantum_frame = ["dep:wai-quantum", "wai-quantum/quantum_frame"]
|
||||
# Tensor-network contraction: hyper-optimised trees, slicing, circuit amplitudes.
|
||||
quantum_tn = ["dep:wai-quantum", "wai-quantum/quantum_tn"]
|
||||
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
|
||||
|
|
@ -480,6 +482,7 @@ required-features = [
|
|||
"quantum_bptn",
|
||||
"quantum_resource_receipt",
|
||||
"quantum_frame",
|
||||
"quantum_tn",
|
||||
]
|
||||
|
||||
[[bin]]
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ fn usage() -> ! {
|
|||
wai-quantum qec --file CIRCUIT [--shots N] (the ecosystem's circuit text)\n \
|
||||
wai-quantum qec [--circuit | --dem] [-d D | --file CIRCUIT]\n \
|
||||
wai-quantum qec --fit [--shots N] (measure a and p* of the code model)\n\n\
|
||||
TENSOR NETWORKS:\n \
|
||||
wai-quantum tn --file CIRCUIT.jsonl [-n N] [--bits B] [--open q,q] [--trials T] [--seed S] [--cap-log2 L] [--plan-only] [--json]\n\n\
|
||||
Channels: identity, bitflip, dephasing, depolarizing, damping.\n\
|
||||
Run `wai-quantum list` for the algorithm catalog."
|
||||
);
|
||||
|
|
@ -759,6 +761,99 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
// A circuit's amplitudes by tensor-network contraction: a seeded
|
||||
// hyper-optimised contraction tree, sliced to a memory cap, contracted
|
||||
// in a fixed order. Circuits are JSON lines, one gate per line:
|
||||
// {"q":[0,1],"m":[re,im, …]} (rows are outputs, first qubit most
|
||||
// significant).
|
||||
"tn" => {
|
||||
use wai::quantum_tn::{complex, contract_plan, find_path, Families, Gate, Network, PathOptions};
|
||||
let path = opt(args, "--file", "").unwrap_or_else(|| die("--file CIRCUIT.jsonl is required"));
|
||||
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| die(&format!("{path}: {e}")));
|
||||
let mut gates = Vec::new();
|
||||
let mut n: u32 = 0;
|
||||
for (i, line) in text.lines().enumerate().filter(|(_, l)| !l.trim().is_empty()) {
|
||||
let v: serde_json::Value = serde_json::from_str(line).unwrap_or_else(|_| die(&format!("line {}: not JSON", i + 1)));
|
||||
let qubits: Vec<u32> = v["q"]
|
||||
.as_array()
|
||||
.unwrap_or_else(|| die(&format!("line {}: missing \"q\"", i + 1)))
|
||||
.iter()
|
||||
.map(|x| x.as_u64().unwrap_or_else(|| die("qubits are integers")) as u32)
|
||||
.collect();
|
||||
let m: Vec<f64> = v["m"]
|
||||
.as_array()
|
||||
.unwrap_or_else(|| die(&format!("line {}: missing \"m\"", i + 1)))
|
||||
.iter()
|
||||
.map(|x| x.as_f64().unwrap_or_else(|| die("matrix entries are numbers")))
|
||||
.collect();
|
||||
n = n.max(qubits.iter().max().map_or(0, |q| q + 1));
|
||||
gates.push(Gate { qubits, matrix: m.chunks(2).map(|c| complex(c[0], *c.get(1).unwrap_or(&0.0))).collect() });
|
||||
}
|
||||
let n: u32 = num(args, "--qubits", "-n", n);
|
||||
let bits: Vec<u8> = match opt(args, "--bits", "") {
|
||||
Some(b) => b.chars().map(|c| if c == '1' { 1 } else if c == '0' { 0 } else { die("--bits is a 0/1 string") }).collect(),
|
||||
None => vec![0; n as usize],
|
||||
};
|
||||
let open: Vec<u32> = opt(args, "--open", "")
|
||||
.map(|l| l.split(',').map(|q| q.trim().parse().unwrap_or_else(|_| die("bad --open qubit"))).collect())
|
||||
.unwrap_or_default();
|
||||
if open.len() > 20 {
|
||||
die("at most 20 open qubits (2^20 amplitudes)");
|
||||
}
|
||||
let mut net = Network::amplitude(n, &gates, &bits, &open).unwrap_or_else(|e| die(&format!("{e:?}")));
|
||||
let raw = net.tensors.len();
|
||||
net.simplify();
|
||||
let shape = net.shape();
|
||||
let cap: Option<f64> = opt(args, "--cap-log2", "").map(|v| 2f64.powf(v.parse().unwrap_or_else(|_| die("bad --cap-log2"))));
|
||||
let options = PathOptions {
|
||||
trials: num(args, "--trials", "", 64),
|
||||
seed: num(args, "--seed", "", 0),
|
||||
max_size: cap,
|
||||
reconfigure: 8,
|
||||
families: Families::Both,
|
||||
};
|
||||
let plan = find_path(&shape, &net.dims, &net.open, &options);
|
||||
let flops = plan.total_flops();
|
||||
let plan_line = if flops > 0.0 {
|
||||
format!("10^{:.3} operations, largest intermediate 2^{:.1}, {} slices", flops.log10(), plan.per_slice.max_size.log2(), plan.slices)
|
||||
} else {
|
||||
"no contractions needed".to_string()
|
||||
};
|
||||
if flag(args, "--plan-only") || flops > 1e13 {
|
||||
if !flag(args, "--plan-only") {
|
||||
eprintln!("wai-quantum: the plan costs 10^{:.1} operations; printing the plan only (pass a smaller circuit, or --plan-only)", flops.log10());
|
||||
}
|
||||
if flag(args, "--json") {
|
||||
println!(
|
||||
"{{\"flops\":{flops},\"log2_max_size\":{},\"slices\":{},\"tensors\":{}}}",
|
||||
plan.per_slice.max_size.log2(),
|
||||
plan.slices,
|
||||
shape.len()
|
||||
);
|
||||
} else {
|
||||
println!("network {raw} tensors, {} after simplification", shape.len());
|
||||
println!("plan {plan_line}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
let t = contract_plan(&net, &plan).unwrap_or_else(|e| die(&format!("{e:?}")));
|
||||
if flag(args, "--json") {
|
||||
let amps: Vec<String> = t.data.iter().map(|c| format!("[{},{}]", c.re, c.im)).collect();
|
||||
println!(
|
||||
"{{\"amplitudes\":[{}],\"flops\":{flops},\"log2_max_size\":{},\"slices\":{}}}",
|
||||
amps.join(","),
|
||||
plan.per_slice.max_size.log2(),
|
||||
plan.slices
|
||||
);
|
||||
} else {
|
||||
println!("network {raw} tensors, {} after simplification", shape.len());
|
||||
println!("plan {plan_line}");
|
||||
for (i, c) in t.data.iter().enumerate() {
|
||||
println!("amp[{i}] {:+.12e} {:+.12e}i", c.re, c.im);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"entropy" => {
|
||||
let c = circuit_from(args, algo);
|
||||
let sv = c.simulate().unwrap_or_else(|e| die(&format!("simulate: {e:?}")));
|
||||
|
|
|
|||
|
|
@ -624,6 +624,9 @@ pub use wai_quantum::quantum_frame;
|
|||
|
||||
#[cfg(feature = "quantum_frame")]
|
||||
pub use wai_quantum::quantum_match;
|
||||
|
||||
#[cfg(feature = "quantum_tn")]
|
||||
pub use wai_quantum::quantum_tn;
|
||||
#[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.25"
|
||||
version = "0.3.26"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
Loading…
Reference in a new issue