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]>
75 lines
3.4 KiB
Rust
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()
|
|
);
|
|
}
|