prism-q 0.35.0

Fast Rust quantum circuit simulator. OpenQASM 3.0, multiple backends, AVX2 SIMD kernels, optional CUDA and MPI, QEC tooling, Python bindings.
Documentation
use super::*;
use crate::gates::Gate;

pub(super) fn rx_cx_chain(circuit: &mut Circuit, qubits: std::ops::Range<usize>) {
    for q in qubits.clone() {
        circuit.add_gate(Gate::Rx(0.3), &[q]);
    }
    for q in qubits.start..qubits.end - 1 {
        circuit.add_gate(Gate::Cx, &[q, q + 1]);
    }
}

#[test]
fn dense_entangled_circuit_is_candidate() {
    let mut circuit = Circuit::new(8, 0);
    rx_cx_chain(&mut circuit, 0..8);
    assert!(auto_terminal_statevector_candidate(&circuit));
}

#[test]
fn decomposable_circuit_is_not_candidate() {
    let mut circuit = Circuit::new(12, 0);
    rx_cx_chain(&mut circuit, 0..6);
    rx_cx_chain(&mut circuit, 6..12);
    assert!(!auto_terminal_statevector_candidate(&circuit));
}

#[test]
fn partial_independent_circuit_is_not_candidate() {
    let mut circuit = Circuit::new(10, 0);
    rx_cx_chain(&mut circuit, 0..8);
    rx_cx_chain(&mut circuit, 8..10);
    assert!(!auto_terminal_statevector_candidate(&circuit));
}

// A Clifford+T circuit inside the stabilizer-rank budget stays a statevector
// candidate: the probability route no longer diverts it to the rank engine.
#[test]
fn clifford_t_inside_the_rank_budget_is_a_candidate() {
    let n = 10;
    let budget = stabilizer_rank_budget(n);
    assert_eq!(budget, 2);

    let mut within = Circuit::new(n, 0);
    within.add_gate(Gate::H, &[0]);
    for q in 0..n - 1 {
        within.add_gate(Gate::Cx, &[q, q + 1]);
    }
    for q in 0..budget {
        within.add_gate(Gate::T, &[q]);
    }
    assert!(auto_terminal_statevector_candidate(&within));
}

#[test]
fn temporal_prefix_circuit_is_not_candidate() {
    let n = 8;
    let mut circuit = Circuit::new(n, 0);
    circuit.add_gate(Gate::H, &[0]);
    for _ in 0..3 {
        for q in 0..n - 1 {
            circuit.add_gate(Gate::Cx, &[q, q + 1]);
        }
    }
    for q in 0..n {
        circuit.add_gate(Gate::Rx(0.3), &[q]);
    }
    assert!(has_temporal_clifford_opportunity(
        &BackendKind::Auto,
        &circuit
    ));
    assert!(!auto_terminal_statevector_candidate(&circuit));
}

// A Clifford-only circuit has no dense tail to accelerate, so exporting the
// tableau to run its measurements and guards densely is a loss. Deleting the
// `is_clifford_only` guards in `dispatch.rs` makes both assertions fail.
#[test]
fn clifford_only_circuit_is_not_a_temporal_prefix_candidate() {
    use crate::circuit::{ClassicalCondition, guarded};
    use crate::circuit::{Instruction, SmallVec};

    let n = 8;
    let mut circuit = Circuit::new(n, n);
    circuit.add_gate(Gate::H, &[0]);
    for _ in 0..4 {
        for q in 0..n - 1 {
            circuit.add_gate(Gate::Cx, &[q, q + 1]);
        }
    }
    circuit.add_measure(0, 0);
    circuit.instructions.extend(guarded(
        ClassicalCondition::BitIsOne(0),
        vec![
            Instruction::Gate {
                gate: Gate::X,
                targets: SmallVec::from_slice(&[1]),
            },
            Instruction::Gate {
                gate: Gate::Z,
                targets: SmallVec::from_slice(&[2]),
            },
        ],
    ));
    assert!(circuit.is_clifford_only());
    assert!(!has_temporal_clifford_opportunity(
        &BackendKind::Auto,
        &circuit
    ));
    assert!(plan_temporal_clifford(&BackendKind::Auto, &circuit).is_none());

    // The same prefix with a non-Clifford tail still qualifies.
    let mut mixed = circuit.clone();
    mixed.add_gate(Gate::Rx(0.3), &[0]);
    assert!(has_temporal_clifford_opportunity(
        &BackendKind::Auto,
        &mixed
    ));
}