wai-quantum 0.3.26: tensor-network contraction, hyper-optimised, sliced, bit-identical #144

Merged
dcharlot merged 1 commit from wai-quantum-tn into main 2026-10-04 20:40:53 -04:00
15 changed files with 1949 additions and 23 deletions

View file

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

View file

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

View file

@ -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"]

View file

@ -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

View 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());
}
}

View file

@ -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;

View file

@ -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 };

File diff suppressed because it is too large Load diff

View file

@ -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) {

View file

@ -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
View file

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

View file

@ -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]]

View file

@ -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:?}")));

View file

@ -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;

View file

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