open-standards/wai/wai-quantum/examples/tn_cost.rs
Transaction Science 382cf672a2 wai-quantum tn: Network::reduce fixes the indices a tensor allows one value of
Where a tensor is zero unless index i takes v, every tensor holding i keeps
its entries at v and drops i, repeated until nothing changes; then simplify.
Only zeros are read and entries selected. Projections run through diagonal
gates and the deltas of split ones, so a CZ beside a projected output becomes
scalars. Open indices are never fixed. simplify itself is unchanged, so
networks that do not call reduce keep their bits.

On the same 61-qubit CZ circuits (depth 8/12/16/20, 64 trials): log10
multiply-adds 5.35/8.25/11.22/14.26 split and reduced, against 6.58/9.23/
12.09/15.16 split alone and 8.03/12.46/16.09/19.64 whole and simplified.

tn_cost, the CLI's tn subcommand and the service's tensor_network method
reduce their split networks. Tests: split networks reduced, and whole-gate
networks reduced, give the state vector's amplitudes; open outputs too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 20:29:22 -04:00

75 lines
3.4 KiB
Rust

//! The cost of one amplitude of a circuit by tensor-network contraction, as
//! the hyper-optimised path search finds it.
//!
//! ```text
//! cargo run --release --example tn_cost --features quantum_tn -- CIRCUIT.qasm TRIALS SEED [LOG2_MAX_SIZE]
//! ```
//!
//! `CIRCUIT.qasm`: OpenQASM 3 with `rz`, `rx`, `ry`, `h`, `x`, `sx`, `cz`,
//! `cx` (barriers and measurements skipped). Prints log10 of the complex
//! multiply-adds for one amplitude (every slice), log2 of the largest
//! intermediate, and the number of slices. The network splits each `cz` and
//! `cx` at the qubits it is block-diagonal on ([`Network::amplitude_split`]);
//! `TN_DENSE=1` keeps every gate whole.
use std::time::Instant;
use wai_quantum::gates::Gate;
use wai_quantum::quantum_tn::{Network, PathOptions, find_path};
fn main() {
let args: Vec<String> = std::env::args().collect();
let src = std::fs::read_to_string(&args[1]).expect("the circuit reads");
let trials: usize = args[2].parse().unwrap();
let seed: u64 = args[3].parse().unwrap();
let max_size = args.get(4).map(|s| s.parse::<f64>().unwrap().exp2());
let mut n = 0u32;
let mut gates = Vec::new();
for line in src.lines().map(str::trim) {
let line = line.trim_end_matches(';');
if let Some(rest) = line.strip_prefix("qubit[") {
n = rest.split(']').next().unwrap().parse().unwrap();
continue;
}
if line.is_empty() || ["OPENQASM", "include", "bit", "barrier", "c["].iter().any(|p| line.starts_with(p)) || line.contains("measure") {
continue;
}
let (head, args) = line.split_once(' ').expect("a gate and its qubits");
let (name, theta) = match head.split_once('(') {
Some((n, p)) => (n, p.trim_end_matches(')').parse::<f64>().expect("one angle")),
None => (head, 0.0),
};
let qs: Vec<u32> = args.split(',').map(|a| a.trim().trim_start_matches("q[").trim_end_matches(']').parse().unwrap()).collect();
gates.push(match (name, qs.as_slice()) {
("rz", [a]) => Gate::rz(*a, theta),
("rx", [a]) => Gate::rx(*a, theta),
("ry", [a]) => Gate::ry(*a, theta),
("h", [a]) => Gate::h(*a),
("x", [a]) => Gate::x(*a),
("sx", [a]) => Gate::sqrt_x(*a),
("cz", [a, b]) => Gate::cz(*a, *b),
("cx", [a, b]) => Gate::cx(*a, *b),
_ => panic!("gate {name} on {qs:?}"),
});
}
let bits = vec![0u8; n as usize];
// TN_DENSE=1: every gate one tensor, as before splitting.
let dense = std::env::var("TN_DENSE").as_deref() == Ok("1");
let mut net = if dense { Network::amplitude(n, &gates, &bits, &[]) } else { Network::amplitude_split(n, &gates, &bits, &[]) }.expect("the network builds");
// Split networks are reduced (projections fixed through diagonal gates);
// TN_DENSE=1 is the pipeline before both.
if dense {
net.simplify();
} else {
net.reduce();
}
let t = Instant::now();
let plan = find_path(&net.shape(), &net.dims, &net.open, &PathOptions { trials, seed, max_size, ..PathOptions::default() });
println!(
"{n} qubits, {} gates: log10 flops {:.3}, log2 largest {:.1}, slices {:.3e}, searched in {:?}",
gates.len(),
plan.total_flops().log10(),
plan.per_slice.max_size.log2(),
plan.slices,
t.elapsed()
);
}