use crate::circuit::circuit_to_matrix::{
apply_gate_to_matrix as apply_numeric_gate_to_numeric_matrix, circuit_to_matrix,
};
use crate::circuit::symbolic_matrix::PARALLEL_THRESHOLD_OPS;
use crate::circuit::symbolic_matrix::matrix::{
SymbolicComplex, SymbolicMatrix, UnsafeSymbolicSlice, apply_numeric_diagonal_gate,
apply_numeric_permutation_gate, apply_symbolic_diagonal_gate, apply_symbolic_permutation_gate,
numeric_is_zero, substitute_symbolic_matrix, symbolic_eye,
};
use crate::circuit::{Circuit, CircuitError, CircuitParam, Instruction, Parameter, StandardGate};
use ndarray::Array2;
use ndarray::parallel::prelude::*;
use num_complex::Complex64;
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet};
enum SymbolicGateKind {
Diagonal(Vec<SymbolicComplex>),
Permutation(Vec<(usize, SymbolicComplex)>),
Dense,
}
enum NumericGateKind {
Diagonal(Vec<Complex64>),
Permutation(Vec<(usize, Complex64)>),
Dense,
}
const NUMERIC_RUN_DENSE_FLUSH_MAX_DIM: usize = 64;
struct NumericRun {
matrix: Array2<Complex64>,
len: usize,
}
impl NumericRun {
fn new(dim: usize) -> Self {
Self {
matrix: Array2::eye(dim),
len: 0,
}
}
fn push(&mut self, gate: &Array2<Complex64>, bits: &[usize]) -> Result<(), CircuitError> {
validate_target_bits(self.matrix.nrows(), bits)?;
apply_numeric_gate_to_numeric_matrix(&mut self.matrix, gate, bits)?;
self.len += 1;
Ok(())
}
fn reset(&mut self) {
self.matrix.fill(Complex64::new(0.0, 0.0));
for idx in 0..self.matrix.nrows() {
self.matrix[[idx, idx]] = Complex64::new(1.0, 0.0);
}
self.len = 0;
}
fn is_empty(&self) -> bool {
self.len == 0
}
}
pub fn standard_gate_symbolic_matrix(
gate: StandardGate,
params: &[Parameter],
) -> Result<SymbolicMatrix, CircuitError> {
validate_params(gate, params)?;
let z = SymbolicComplex::zero();
let o = SymbolicComplex::one();
let i = SymbolicComplex::i();
let neg_i = -i.clone();
let h = SymbolicComplex::from_real(1.0 / std::f64::consts::SQRT_2);
Ok(match gate {
StandardGate::H
| StandardGate::I
| StandardGate::S
| StandardGate::SDG
| StandardGate::T
| StandardGate::TDG
| StandardGate::X
| StandardGate::Y
| StandardGate::Z
| StandardGate::X2P
| StandardGate::X2M
| StandardGate::Y2P
| StandardGate::Y2M
| StandardGate::SWAP
| StandardGate::CX
| StandardGate::CY
| StandardGate::CZ
| StandardGate::CCX => {
let numeric = gate
.matrix(&[])
.map_err(|_| CircuitError::NoMatrixRepresentation)?;
symbolic_matrix_from_numeric(numeric.as_ref())
}
StandardGate::RX => {
let c = cos_half(¶ms[0]);
let s = neg_i_times(sin_half(¶ms[0]));
ndarray::array![[c.clone(), s.clone()], [s, c]]
}
StandardGate::RY => {
let c = cos_half(¶ms[0]);
let s = SymbolicComplex::from_real(sin_half(¶ms[0]));
ndarray::array![[c.clone(), -s.clone()], [s, c]]
}
StandardGate::RZ => {
let h = half(¶ms[0]);
ndarray::array![
[exp_neg_i(h.clone()), z.clone()],
[z, SymbolicComplex::exp_i(h)]
]
}
StandardGate::Phase => ndarray::array![
[o.clone(), z.clone()],
[z, SymbolicComplex::exp_i(params[0].clone())]
],
StandardGate::GPhase => {
let phase = SymbolicComplex::exp_i(params[0].clone());
ndarray::array![[phase.clone(), z.clone()], [z, phase]]
}
StandardGate::RXX => {
let c = cos_half(¶ms[0]);
let s = neg_i_times(sin_half(¶ms[0]));
ndarray::array![
[c.clone(), z.clone(), z.clone(), s.clone()],
[z.clone(), c.clone(), s.clone(), z.clone()],
[z.clone(), s.clone(), c.clone(), z.clone()],
[s, z.clone(), z, c]
]
}
StandardGate::RYY => {
let c = cos_half(¶ms[0]);
let s = i_times(sin_half(¶ms[0]));
let ns = -s.clone();
ndarray::array![
[c.clone(), z.clone(), z.clone(), s.clone()],
[z.clone(), c.clone(), ns.clone(), z.clone()],
[z.clone(), ns, c.clone(), z.clone()],
[s, z.clone(), z, c]
]
}
StandardGate::RZZ => {
let h = half(¶ms[0]);
let exp_neg = exp_neg_i(h.clone());
let exp_pos = SymbolicComplex::exp_i(h);
ndarray::array![
[exp_neg.clone(), z.clone(), z.clone(), z.clone()],
[z.clone(), exp_pos.clone(), z.clone(), z.clone()],
[z.clone(), z.clone(), exp_pos, z.clone()],
[z.clone(), z.clone(), z, exp_neg]
]
}
StandardGate::RZX => {
let c = cos_half(¶ms[0]);
let s = i_times(sin_half(¶ms[0]));
let ns = -s.clone();
ndarray::array![
[c.clone(), ns.clone(), z.clone(), z.clone()],
[ns, c.clone(), z.clone(), z.clone()],
[z.clone(), z.clone(), c.clone(), s.clone()],
[z.clone(), z.clone(), s, c]
]
}
StandardGate::CRX => {
let c = cos_half(¶ms[0]);
let s = neg_i_times(sin_half(¶ms[0]));
ndarray::array![
[o.clone(), z.clone(), z.clone(), z.clone()],
[z.clone(), o.clone(), z.clone(), z.clone()],
[z.clone(), z.clone(), c.clone(), s.clone()],
[z.clone(), z.clone(), s, c]
]
}
StandardGate::CRY => {
let c = cos_half(¶ms[0]);
let s = SymbolicComplex::from_real(sin_half(¶ms[0]));
ndarray::array![
[o.clone(), z.clone(), z.clone(), z.clone()],
[z.clone(), o.clone(), z.clone(), z.clone()],
[z.clone(), z.clone(), c.clone(), -s.clone()],
[z.clone(), z.clone(), s, c]
]
}
StandardGate::CRZ => {
let h = half(¶ms[0]);
ndarray::array![
[o.clone(), z.clone(), z.clone(), z.clone()],
[z.clone(), o.clone(), z.clone(), z.clone()],
[z.clone(), z.clone(), exp_neg_i(h.clone()), z.clone()],
[z.clone(), z.clone(), z, SymbolicComplex::exp_i(h)]
]
}
StandardGate::RXY => {
let c = cos_half(¶ms[0]);
let s = SymbolicComplex::from_real(sin_half(¶ms[0]));
let upper = neg_i.clone() * exp_neg_i(params[1].clone()) * s.clone();
let lower = neg_i * SymbolicComplex::exp_i(params[1].clone()) * s;
ndarray::array![[c.clone(), upper], [lower, c]]
}
StandardGate::U => {
let c = cos_half(¶ms[0]);
let s = SymbolicComplex::from_real(sin_half(¶ms[0]));
let exp_phi = SymbolicComplex::exp_i(params[1].clone());
let exp_lambda = SymbolicComplex::exp_i(params[2].clone());
let exp_phi_lambda = SymbolicComplex::exp_i(params[1].clone() + params[2].clone());
ndarray::array![
[c.clone(), -(exp_lambda * s.clone())],
[exp_phi * s, exp_phi_lambda * c]
]
}
StandardGate::XY => {
let upper = neg_i.clone() * exp_neg_i(params[0].clone());
let lower = neg_i * SymbolicComplex::exp_i(params[0].clone());
ndarray::array![[z, upper], [lower, SymbolicComplex::zero()]]
}
StandardGate::XY2P => {
let upper = neg_i.clone() * exp_neg_i(params[0].clone()) * h.clone();
let lower = neg_i * SymbolicComplex::exp_i(params[0].clone()) * h.clone();
ndarray::array![[h.clone(), upper], [lower, h]]
}
StandardGate::XY2M => {
let upper = i.clone() * exp_neg_i(params[0].clone()) * h.clone();
let lower = i * SymbolicComplex::exp_i(params[0].clone()) * h.clone();
ndarray::array![[h.clone(), upper], [lower, h]]
}
StandardGate::FSIM => {
let c = SymbolicComplex::from_real(params[0].cos());
let s = neg_i_times(params[0].sin());
let phase = exp_neg_i(params[1].clone());
ndarray::array![
[o.clone(), z.clone(), z.clone(), z.clone()],
[z.clone(), c.clone(), s.clone(), z.clone()],
[z.clone(), s, c, z.clone()],
[z.clone(), z.clone(), z, phase]
]
}
})
}
pub fn control_matrix(base: &SymbolicMatrix, num_ctrls: usize) -> SymbolicMatrix {
if num_ctrls == 0 {
return base.clone();
}
let base_dim = base.nrows();
let total_dim = base_dim << num_ctrls;
let mut matrix = symbolic_eye(total_dim);
let start = total_dim - base_dim;
for row in 0..base_dim {
for col in 0..base_dim {
matrix[[start + row, start + col]] = base[[row, col]].clone();
}
}
matrix
}
pub fn circuit_to_symbolic_matrix(
circuit: &Circuit,
qubits_order: Option<&[usize]>,
) -> Result<SymbolicMatrix, CircuitError> {
let circuit_qubits: Vec<usize> = circuit.qubits().iter().map(|q| q.index()).collect();
let num_qubits = circuit_qubits.len();
let dim = 1usize.checked_shl(num_qubits as u32).ok_or_else(|| {
CircuitError::InvalidOperation(format!(
"cannot build matrix for {num_qubits} qubits: dimension overflows usize"
))
})?;
dim.checked_mul(dim).ok_or_else(|| {
CircuitError::InvalidOperation(format!(
"cannot build matrix for {num_qubits} qubits: matrix element count overflows usize"
))
})?;
let target_order: Vec<usize> = match qubits_order {
Some(order) => {
let circuit_set: HashSet<usize> = circuit_qubits.iter().copied().collect();
let order_set: HashSet<usize> = order.iter().copied().collect();
if circuit_set != order_set || circuit_set.len() != order.len() {
return Err(CircuitError::InvalidOperation(format!(
"qubits_order mismatch! Circuit has {:?}, but order provided is {:?}",
circuit_qubits, order
)));
}
order.to_vec()
}
None => {
let mut sorted = circuit_qubits.clone();
sorted.sort();
sorted
}
};
let qubit_bit_map: HashMap<usize, usize> = target_order
.iter()
.enumerate()
.map(|(i, &q_id)| (q_id, i))
.collect();
let full_bits: Vec<usize> = (0..num_qubits).collect();
let mut matrix: Option<SymbolicMatrix> = None;
let mut numeric_run = NumericRun::new(dim);
for op in circuit.operations() {
let bits: SmallVec<[usize; 3]> = op
.qubits
.iter()
.map(|q| {
qubit_bit_map
.get(&q.index())
.copied()
.ok_or(CircuitError::QubitNotFound(q.id()))
})
.collect::<Result<_, _>>()?;
let params = resolve_params(circuit, &op.params)?;
match &op.instruction {
Instruction::Standard(gate) => {
let reversed_bits: SmallVec<[usize; 3]> = bits.iter().copied().rev().collect();
if let Some(numeric_params) = constant_params(¶ms)? {
let gate_matrix = gate
.matrix(&numeric_params)
.map_err(|_| CircuitError::NoMatrixRepresentation)?;
push_numeric_gate(
&mut matrix,
&mut numeric_run,
gate_matrix.as_ref(),
&reversed_bits,
&full_bits,
)?;
} else {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let matrix = matrix.get_or_insert_with(|| symbolic_eye(dim));
apply_standard_gate_to_matrix(matrix, *gate, &reversed_bits, ¶ms)?;
}
}
Instruction::McGate(mc_gate) => {
let reversed_bits: SmallVec<[usize; 3]> = bits.iter().copied().rev().collect();
if let Some(numeric_params) = constant_params(¶ms)? {
let gate_matrix = mc_gate
.matrix(&numeric_params)
.map_err(|_| CircuitError::NoMatrixRepresentation)?;
push_numeric_gate(
&mut matrix,
&mut numeric_run,
gate_matrix.as_ref(),
&reversed_bits,
&full_bits,
)?;
} else {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let matrix = matrix.get_or_insert_with(|| symbolic_eye(dim));
let base = standard_gate_symbolic_matrix(*mc_gate.base_gate(), ¶ms)?;
let gate_matrix = control_matrix(&base, mc_gate.num_ctrl_qubits());
apply_gate_to_matrix(matrix, &gate_matrix, &reversed_bits)?;
}
}
Instruction::UnitaryGate(u_gate) => {
let reversed_bits: SmallVec<[usize; 3]> = bits.iter().copied().rev().collect();
if let Some(unitary_matrix) = u_gate.symbolic_matrix() {
if let Some(concrete_params) = constant_params(¶ms)? {
let gate_matrix = u_gate.matrix_for_params(&concrete_params)?;
push_numeric_gate(
&mut matrix,
&mut numeric_run,
gate_matrix.as_ref(),
&reversed_bits,
&full_bits,
)?;
} else {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let matrix_params = u_gate
.matrix_params()
.ok_or(CircuitError::SymbolicParameterError)?;
let expected = matrix_params.len();
let actual = params.len();
if actual != expected {
return Err(CircuitError::ParameterCountMismatch { expected, actual });
}
let replacements: HashMap<String, Parameter> = matrix_params
.iter()
.cloned()
.zip(params.iter().cloned())
.collect();
let sub_matrix =
substitute_symbolic_matrix(unitary_matrix.clone(), &replacements)?;
let matrix = matrix.get_or_insert_with(|| symbolic_eye(dim));
apply_gate_to_matrix(matrix, &sub_matrix, &reversed_bits)?;
}
} else if u_gate.circuit().is_some() && u_gate.matrix().is_none() {
if let Some(numeric_params) = constant_params(¶ms)? {
let gate_matrix = u_gate.matrix_for_params(&numeric_params)?;
push_numeric_gate(
&mut matrix,
&mut numeric_run,
gate_matrix.as_ref(),
&bits,
&full_bits,
)?;
} else {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let matrix = matrix.get_or_insert_with(|| symbolic_eye(dim));
let sub_circuit = u_gate
.circuit()
.as_ref()
.ok_or(CircuitError::NoMatrixRepresentation)?;
let symbols = sub_circuit.used_symbols();
let expected = symbols.len();
let actual = params.len();
if actual != expected {
return Err(CircuitError::ParameterCountMismatch { expected, actual });
}
let replacements: HashMap<String, Parameter> = symbols
.iter()
.cloned()
.zip(params.iter().cloned())
.collect();
let sub_matrix = sub_circuit.symbolic_matrix()?;
let sub_matrix =
substitute_symbolic_matrix((*sub_matrix).clone(), &replacements)?;
apply_gate_to_matrix(matrix, &sub_matrix, &bits)?;
}
} else if let Some(concrete_params) = constant_params(¶ms)? {
let gate_matrix = u_gate.matrix_for_params(&concrete_params)?;
push_numeric_gate(
&mut matrix,
&mut numeric_run,
gate_matrix.as_ref(),
&reversed_bits,
&full_bits,
)?;
} else {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let concrete_params: Vec<f64> = params
.iter()
.map(|param| {
param
.evaluate(&None)
.map_err(|_| CircuitError::SymbolicParameterError)
})
.collect::<Result<_, _>>()?;
let gate_matrix = u_gate.matrix_for_params(&concrete_params)?;
let sub_matrix = symbolic_matrix_from_numeric(gate_matrix.as_ref());
let matrix = matrix.get_or_insert_with(|| symbolic_eye(dim));
apply_gate_to_matrix(matrix, &sub_matrix, &reversed_bits)?;
}
}
Instruction::CircuitGate(circuit_gate) => {
let symbols = circuit_gate.signature_params();
let expected = symbols.len();
let actual = params.len();
if actual != expected {
return Err(CircuitError::ParameterCountMismatch { expected, actual });
}
let replacements: HashMap<String, Parameter> = symbols
.iter()
.cloned()
.zip(params.iter().cloned())
.collect();
if let Some(numeric_params) = constant_params(¶ms)? {
let mut bindings = HashMap::new();
for (symbol, value) in symbols.iter().zip(numeric_params.iter()) {
bindings.insert(symbol.as_str(), *value);
}
let sub_circuit = circuit_gate
.circuit()
.circuit()
.assign_parameters(&Some(bindings))
.map_err(|_| CircuitError::SymbolicParameterError)?;
let sub_matrix = circuit_to_matrix(&sub_circuit, None)?;
push_numeric_gate(
&mut matrix,
&mut numeric_run,
&sub_matrix,
&bits,
&full_bits,
)?;
} else {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let matrix = matrix.get_or_insert_with(|| symbolic_eye(dim));
let sub_matrix = circuit_gate.symbolic_matrix()?;
let sub_matrix =
substitute_symbolic_matrix((*sub_matrix).clone(), &replacements)?;
apply_gate_to_matrix(matrix, &sub_matrix, &bits)?;
}
}
Instruction::ClassicalControl(_) => {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
return Err(CircuitError::InvalidOperation(
"control-flow operations do not have an unconditional matrix representation"
.to_string(),
));
}
Instruction::ClassicalData(_) => {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
return Err(CircuitError::NoMatrixRepresentation);
}
Instruction::Directive(directive) => match directive {
crate::circuit::Directive::Barrier => continue,
crate::circuit::Directive::Measure | crate::circuit::Directive::Reset => {
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
return Err(CircuitError::NoMatrixRepresentation);
}
},
Instruction::Delay => continue,
}
}
flush_numeric_run(&mut matrix, &mut numeric_run, &full_bits)?;
let mut matrix = matrix.unwrap_or_else(|| symbolic_eye(dim));
let global_phase = circuit.global_phase();
if !global_phase.is_zero() {
let phase = SymbolicComplex::exp_i(global_phase);
matrix
.as_slice_mut()
.expect("symbolic matrix must be contiguous")
.par_iter_mut()
.for_each(|value| {
let old = std::mem::take(value);
*value = &phase * old;
});
}
Ok(matrix)
}
fn constant_params(params: &[Parameter]) -> Result<Option<Vec<f64>>, CircuitError> {
if params.iter().any(|param| !param.is_constant()) {
return Ok(None);
}
params
.iter()
.map(|param| {
param
.evaluate(&None)
.map_err(|_| CircuitError::SymbolicParameterError)
})
.collect::<Result<Vec<_>, _>>()
.map(Some)
}
fn push_numeric_gate(
matrix: &mut Option<SymbolicMatrix>,
numeric_run: &mut NumericRun,
gate: &Array2<Complex64>,
bits: &[usize],
full_bits: &[usize],
) -> Result<(), CircuitError> {
if matrix.is_none() || numeric_run.matrix.nrows() <= NUMERIC_RUN_DENSE_FLUSH_MAX_DIM {
numeric_run.push(gate, bits)
} else {
flush_numeric_run(matrix, numeric_run, full_bits)?;
apply_gate_to_matrix_num(
matrix
.as_mut()
.expect("symbolic matrix must exist after numeric-run flush"),
gate,
bits,
)
}
}
fn flush_numeric_run(
matrix: &mut Option<SymbolicMatrix>,
numeric_run: &mut NumericRun,
full_bits: &[usize],
) -> Result<(), CircuitError> {
if numeric_run.is_empty() {
return Ok(());
}
if let Some(matrix) = matrix.as_mut() {
apply_gate_to_matrix_num(matrix, &numeric_run.matrix, full_bits)?;
} else {
*matrix = Some(symbolic_matrix_from_numeric(&numeric_run.matrix));
}
numeric_run.reset();
Ok(())
}
pub fn apply_standard_gate_to_matrix(
matrix: &mut SymbolicMatrix,
gate: StandardGate,
bits: &[usize],
params: &[Parameter],
) -> Result<(), CircuitError> {
let has_symbols = params.iter().any(|p| !p.is_constant());
if has_symbols {
let gate_matrix = standard_gate_symbolic_matrix(gate, params)?;
apply_gate_to_matrix(matrix, &gate_matrix, bits)?;
} else {
let numeric_params: Vec<f64> = params
.iter()
.map(|p| {
p.evaluate(&None)
.map_err(|_| CircuitError::SymbolicParameterError)
})
.collect::<Result<_, _>>()?;
let gate_matrix = gate
.matrix(&numeric_params)
.map_err(|_| CircuitError::NoMatrixRepresentation)?;
apply_gate_to_matrix_num(matrix, gate_matrix.as_ref(), bits)?;
}
Ok(())
}
pub fn apply_gate_to_matrix(
matrix: &mut SymbolicMatrix,
gate: &SymbolicMatrix,
bits: &[usize],
) -> Result<(), CircuitError> {
validate_target_bits(matrix.nrows(), bits)?;
let expected_dim = 1usize << bits.len();
if gate.nrows() != expected_dim || gate.ncols() != expected_dim {
return Err(CircuitError::QubitCountMismatch {
expected: gate.nrows().trailing_zeros() as usize,
actual: bits.len(),
});
}
match classify_symbolic_gate(gate) {
SymbolicGateKind::Diagonal(diagonal) => {
apply_symbolic_diagonal_gate(matrix, &diagonal, bits);
}
SymbolicGateKind::Permutation(permutation) => {
apply_symbolic_permutation_gate(matrix, &permutation, bits);
}
SymbolicGateKind::Dense => match bits.len() {
1 => apply_single_qubit_gate(matrix, gate, bits[0]),
2 => apply_two_qubit_gate(matrix, gate, bits[0], bits[1]),
_ => apply_general_gate(matrix, gate, bits),
},
}
Ok(())
}
pub fn apply_single_qubit_gate(matrix: &mut SymbolicMatrix, gate: &SymbolicMatrix, bit: usize) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let step = 1usize << bit;
let total_ops = dim.saturating_mul(cols);
let parallel = total_ops >= PARALLEL_THRESHOLD_OPS;
let u00 = &gate[[0, 0]];
let u01 = &gate[[0, 1]];
let u10 = &gate[[1, 0]];
let u11 = &gate[[1, 1]];
let slice = matrix
.as_slice_mut()
.expect("Symbolic matrix must be contiguous");
let unsafe_slice = UnsafeSymbolicSlice::new(slice);
let process_block = |i: usize| {
unsafe {
for j in 0..step {
let r0_idx = i + j;
let r1_idx = r0_idx + step;
let r0_ptr = unsafe_slice.row_ptr(r0_idx, cols);
let r1_ptr = unsafe_slice.row_ptr(r1_idx, cols);
for col in 0..cols {
let v0 = (*r0_ptr.add(col)).clone();
let v1 = (*r1_ptr.add(col)).clone();
*r0_ptr.add(col) = u00 * &v0 + u01 * &v1;
*r1_ptr.add(col) = u10 * &v0 + u11 * &v1;
}
}
}
};
if parallel {
(0..dim)
.into_par_iter()
.step_by(step * 2)
.for_each(process_block);
} else {
(0..dim).step_by(step * 2).for_each(process_block);
}
}
pub fn apply_two_qubit_gate(
matrix: &mut SymbolicMatrix,
gate: &SymbolicMatrix,
b0: usize,
b1: usize,
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let (low, high) = if b0 < b1 { (b0, b1) } else { (b1, b0) };
let mask_low = (1usize << low) - 1;
let mask_high = (1usize << high) - 1;
let off0 = 1usize << b0;
let off1 = 1usize << b1;
let loop_limit = dim >> 2;
let total_ops = dim.saturating_mul(cols);
let parallel = total_ops >= PARALLEL_THRESHOLD_OPS;
let g00 = &gate[[0, 0]];
let g01 = &gate[[0, 1]];
let g02 = &gate[[0, 2]];
let g03 = &gate[[0, 3]];
let g10 = &gate[[1, 0]];
let g11 = &gate[[1, 1]];
let g12 = &gate[[1, 2]];
let g13 = &gate[[1, 3]];
let g20 = &gate[[2, 0]];
let g21 = &gate[[2, 1]];
let g22 = &gate[[2, 2]];
let g23 = &gate[[2, 3]];
let g30 = &gate[[3, 0]];
let g31 = &gate[[3, 1]];
let g32 = &gate[[3, 2]];
let g33 = &gate[[3, 3]];
let slice = matrix
.as_slice_mut()
.expect("Symbolic matrix must be contiguous");
let unsafe_slice = UnsafeSymbolicSlice::new(slice);
let process_idx = |i: usize| {
let left_part = (i & !mask_low) << 1;
let right_part = i & mask_low;
let tmp = left_part | right_part;
let left_final = (tmp & !mask_high) << 1;
let right_final = tmp & mask_high;
let base = left_final | right_final;
let r0_idx = base;
let r1_idx = base | off0;
let r2_idx = base | off1;
let r3_idx = base | off0 | off1;
unsafe {
let p0 = unsafe_slice.row_ptr(r0_idx, cols);
let p1 = unsafe_slice.row_ptr(r1_idx, cols);
let p2 = unsafe_slice.row_ptr(r2_idx, cols);
let p3 = unsafe_slice.row_ptr(r3_idx, cols);
for col in 0..cols {
let v0 = (*p0.add(col)).clone();
let v1 = (*p1.add(col)).clone();
let v2 = (*p2.add(col)).clone();
let v3 = (*p3.add(col)).clone();
*p0.add(col) = g00 * &v0 + g01 * &v1 + g02 * &v2 + g03 * &v3;
*p1.add(col) = g10 * &v0 + g11 * &v1 + g12 * &v2 + g13 * &v3;
*p2.add(col) = g20 * &v0 + g21 * &v1 + g22 * &v2 + g23 * &v3;
*p3.add(col) = g30 * &v0 + g31 * &v1 + g32 * &v2 + g33 * &v3;
}
}
};
if parallel {
(0..loop_limit).into_par_iter().for_each(process_idx);
} else {
(0..loop_limit).for_each(process_idx);
}
}
pub fn apply_general_gate(matrix: &mut SymbolicMatrix, gate: &SymbolicMatrix, bits: &[usize]) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let num_targets = bits.len();
let gate_dim = 1usize << num_targets;
let mut sorted_bits: SmallVec<[usize; 8]> = bits.iter().copied().collect();
sorted_bits.sort();
let mut gate_offsets = vec![0usize; gate_dim];
for (k, offset_ref) in gate_offsets.iter_mut().enumerate() {
let mut offset = 0usize;
for (j, &physical_bit) in bits.iter().enumerate() {
if (k >> j) & 1 == 1 {
offset |= 1usize << physical_bit;
}
}
*offset_ref = offset;
}
let loop_limit = dim >> num_targets;
let total_ops = dim.saturating_mul(cols);
let parallel = total_ops >= PARALLEL_THRESHOLD_OPS;
let slice = matrix
.as_slice_mut()
.expect("Symbolic matrix must be contiguous");
let unsafe_slice = UnsafeSymbolicSlice::new(slice);
let process_idx =
|i: usize, row_ptrs: &mut Vec<*mut SymbolicComplex>, input: &mut Vec<SymbolicComplex>| {
let mut base = i;
for &q in &sorted_bits {
let mask = (1usize << q) - 1;
let left = (base & !mask) << 1;
let right = base & mask;
base = left | right;
}
unsafe {
row_ptrs.clear();
for offset in gate_offsets.iter().take(gate_dim) {
row_ptrs.push(unsafe_slice.row_ptr(base | offset, cols));
}
for col in 0..cols {
for g in 0..gate_dim {
input[g] = (*row_ptrs[g].add(col)).clone();
}
for row in 0..gate_dim {
let mut sum = SymbolicComplex::zero();
for col_gate in 0..gate_dim {
sum = sum + &gate[[row, col_gate]] * &input[col_gate];
}
*row_ptrs[row].add(col) = sum;
}
}
}
};
if parallel {
(0..loop_limit).into_par_iter().for_each_init(
|| {
(
Vec::<*mut SymbolicComplex>::with_capacity(gate_dim),
vec![SymbolicComplex::zero(); gate_dim],
)
},
|(row_ptrs, input), i| process_idx(i, row_ptrs, input),
);
} else {
let mut row_ptrs = Vec::<*mut SymbolicComplex>::with_capacity(gate_dim);
let mut input = vec![SymbolicComplex::zero(); gate_dim];
for i in 0..loop_limit {
process_idx(i, &mut row_ptrs, &mut input);
}
}
}
pub fn apply_gate_to_matrix_num(
matrix: &mut SymbolicMatrix,
gate: &Array2<Complex64>,
bits: &[usize],
) -> Result<(), CircuitError> {
validate_target_bits(matrix.nrows(), bits)?;
let expected_dim = 1usize << bits.len();
if gate.nrows() != expected_dim || gate.ncols() != expected_dim {
return Err(CircuitError::QubitCountMismatch {
expected: gate.nrows().trailing_zeros() as usize,
actual: bits.len(),
});
}
match classify_numeric_gate(gate) {
NumericGateKind::Diagonal(diagonal) => {
apply_numeric_diagonal_gate(matrix, &diagonal, bits);
}
NumericGateKind::Permutation(permutation) => {
apply_numeric_permutation_gate(matrix, &permutation, bits);
}
NumericGateKind::Dense => match bits.len() {
1 => apply_single_qubit_gate_num(matrix, gate, bits[0]),
2 => apply_two_qubit_gate_num(matrix, gate, bits[0], bits[1]),
_ => apply_general_gate_num(matrix, gate, bits),
},
}
Ok(())
}
pub fn apply_single_qubit_gate_num(
matrix: &mut SymbolicMatrix,
gate: &Array2<Complex64>,
bit: usize,
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let step = 1usize << bit;
let total_ops = dim.saturating_mul(cols);
let parallel = total_ops >= PARALLEL_THRESHOLD_OPS;
let u00 = gate[[0, 0]];
let u01 = gate[[0, 1]];
let u10 = gate[[1, 0]];
let u11 = gate[[1, 1]];
let slice = matrix
.as_slice_mut()
.expect("Symbolic matrix must be contiguous");
let unsafe_slice = UnsafeSymbolicSlice::new(slice);
let process_block = |i: usize| {
unsafe {
for j in 0..step {
let r0_idx = i + j;
let r1_idx = r0_idx + step;
let r0_ptr = unsafe_slice.row_ptr(r0_idx, cols);
let r1_ptr = unsafe_slice.row_ptr(r1_idx, cols);
for col in 0..cols {
let v0 = (*r0_ptr.add(col)).clone();
let v1 = (*r1_ptr.add(col)).clone();
*r0_ptr.add(col) = u00 * &v0 + u01 * &v1;
*r1_ptr.add(col) = u10 * &v0 + u11 * &v1;
}
}
}
};
if parallel {
(0..dim)
.into_par_iter()
.step_by(step * 2)
.for_each(process_block);
} else {
(0..dim).step_by(step * 2).for_each(process_block);
}
}
pub fn apply_two_qubit_gate_num(
matrix: &mut SymbolicMatrix,
gate: &Array2<Complex64>,
b0: usize,
b1: usize,
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let (low, high) = if b0 < b1 { (b0, b1) } else { (b1, b0) };
let mask_low = (1usize << low) - 1;
let mask_high = (1usize << high) - 1;
let off0 = 1usize << b0;
let off1 = 1usize << b1;
let loop_limit = dim >> 2;
let total_ops = dim.saturating_mul(cols);
let parallel = total_ops >= PARALLEL_THRESHOLD_OPS;
let g00 = gate[[0, 0]];
let g01 = gate[[0, 1]];
let g02 = gate[[0, 2]];
let g03 = gate[[0, 3]];
let g10 = gate[[1, 0]];
let g11 = gate[[1, 1]];
let g12 = gate[[1, 2]];
let g13 = gate[[1, 3]];
let g20 = gate[[2, 0]];
let g21 = gate[[2, 1]];
let g22 = gate[[2, 2]];
let g23 = gate[[2, 3]];
let g30 = gate[[3, 0]];
let g31 = gate[[3, 1]];
let g32 = gate[[3, 2]];
let g33 = gate[[3, 3]];
let slice = matrix
.as_slice_mut()
.expect("Symbolic matrix must be contiguous");
let unsafe_slice = UnsafeSymbolicSlice::new(slice);
let process_idx = |i: usize| {
let left_part = (i & !mask_low) << 1;
let right_part = i & mask_low;
let tmp = left_part | right_part;
let left_final = (tmp & !mask_high) << 1;
let right_final = tmp & mask_high;
let base = left_final | right_final;
let r0_idx = base;
let r1_idx = base | off0;
let r2_idx = base | off1;
let r3_idx = base | off0 | off1;
unsafe {
let p0 = unsafe_slice.row_ptr(r0_idx, cols);
let p1 = unsafe_slice.row_ptr(r1_idx, cols);
let p2 = unsafe_slice.row_ptr(r2_idx, cols);
let p3 = unsafe_slice.row_ptr(r3_idx, cols);
for col in 0..cols {
let v0 = (*p0.add(col)).clone();
let v1 = (*p1.add(col)).clone();
let v2 = (*p2.add(col)).clone();
let v3 = (*p3.add(col)).clone();
*p0.add(col) = g00 * &v0 + g01 * &v1 + g02 * &v2 + g03 * &v3;
*p1.add(col) = g10 * &v0 + g11 * &v1 + g12 * &v2 + g13 * &v3;
*p2.add(col) = g20 * &v0 + g21 * &v1 + g22 * &v2 + g23 * &v3;
*p3.add(col) = g30 * &v0 + g31 * &v1 + g32 * &v2 + g33 * &v3;
}
}
};
if parallel {
(0..loop_limit).into_par_iter().for_each(process_idx);
} else {
(0..loop_limit).for_each(process_idx);
}
}
pub fn apply_general_gate_num(
matrix: &mut SymbolicMatrix,
gate: &Array2<Complex64>,
bits: &[usize],
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let num_targets = bits.len();
let gate_dim = 1usize << num_targets;
let mut sorted_bits: SmallVec<[usize; 8]> = bits.iter().copied().collect();
sorted_bits.sort();
let mut gate_offsets = vec![0usize; gate_dim];
for (k, offset_ref) in gate_offsets.iter_mut().enumerate() {
let mut offset = 0usize;
for (j, &physical_bit) in bits.iter().enumerate() {
if (k >> j) & 1 == 1 {
offset |= 1usize << physical_bit;
}
}
*offset_ref = offset;
}
let loop_limit = dim >> num_targets;
let total_ops = dim.saturating_mul(cols);
let parallel = total_ops >= PARALLEL_THRESHOLD_OPS;
let slice = matrix
.as_slice_mut()
.expect("Symbolic matrix must be contiguous");
let unsafe_slice = UnsafeSymbolicSlice::new(slice);
let process_idx =
|i: usize, row_ptrs: &mut Vec<*mut SymbolicComplex>, input: &mut Vec<SymbolicComplex>| {
let mut base = i;
for &q in &sorted_bits {
let mask = (1usize << q) - 1;
let left = (base & !mask) << 1;
let right = base & mask;
base = left | right;
}
unsafe {
row_ptrs.clear();
for offset in gate_offsets.iter().take(gate_dim) {
row_ptrs.push(unsafe_slice.row_ptr(base | offset, cols));
}
for col in 0..cols {
for g in 0..gate_dim {
input[g] = (*row_ptrs[g].add(col)).clone();
}
for row in 0..gate_dim {
let mut sum = SymbolicComplex::zero();
for col_gate in 0..gate_dim {
sum = sum + gate[[row, col_gate]] * &input[col_gate];
}
*row_ptrs[row].add(col) = sum;
}
}
}
};
if parallel {
(0..loop_limit).into_par_iter().for_each_init(
|| {
(
Vec::<*mut SymbolicComplex>::with_capacity(gate_dim),
vec![SymbolicComplex::zero(); gate_dim],
)
},
|(row_ptrs, input), i| process_idx(i, row_ptrs, input),
);
} else {
let mut row_ptrs = Vec::<*mut SymbolicComplex>::with_capacity(gate_dim);
let mut input = vec![SymbolicComplex::zero(); gate_dim];
for i in 0..loop_limit {
process_idx(i, &mut row_ptrs, &mut input);
}
}
}
fn validate_target_bits(matrix_dim: usize, bits: &[usize]) -> Result<(), CircuitError> {
if !matrix_dim.is_power_of_two() {
return Err(CircuitError::InvalidOperation(format!(
"matrix dimension {matrix_dim} is not a power of two"
)));
}
let num_qubits = matrix_dim.trailing_zeros() as usize;
let mut seen = HashSet::with_capacity(bits.len());
for &bit in bits {
if bit >= num_qubits {
return Err(CircuitError::InvalidOperation(format!(
"gate bit {bit} is out of range for {num_qubits} qubits"
)));
}
if !seen.insert(bit) {
return Err(CircuitError::DuplicateQubits);
}
}
Ok(())
}
fn validate_params(gate: StandardGate, params: &[Parameter]) -> Result<(), CircuitError> {
let expected = gate.num_params();
if params.len() != expected {
return Err(CircuitError::ParameterCountMismatch {
expected,
actual: params.len(),
});
}
Ok(())
}
fn symbolic_matrix_from_numeric(matrix: &Array2<Complex64>) -> SymbolicMatrix {
matrix.mapv(SymbolicComplex::from_complex)
}
fn half(theta: &Parameter) -> Parameter {
theta.clone() / 2.0
}
fn cos_half(theta: &Parameter) -> SymbolicComplex {
SymbolicComplex::from_real(half(theta).cos())
}
fn sin_half(theta: &Parameter) -> Parameter {
half(theta).sin()
}
fn neg_i_times(value: Parameter) -> SymbolicComplex {
SymbolicComplex::new(0.0, -1.0 * value)
}
fn i_times(value: Parameter) -> SymbolicComplex {
SymbolicComplex::new(0.0, value)
}
fn exp_neg_i(theta: Parameter) -> SymbolicComplex {
SymbolicComplex::exp_i(-theta)
}
fn classify_symbolic_gate(gate: &SymbolicMatrix) -> SymbolicGateKind {
let dim = gate.nrows();
if (0..dim).all(|row| (0..dim).all(|col| row == col || gate[[row, col]].is_zero_exact())) {
return SymbolicGateKind::Diagonal((0..dim).map(|idx| gate[[idx, idx]].clone()).collect());
}
let mut seen_cols = vec![false; dim];
let mut permutation = Vec::with_capacity(dim);
for row in 0..dim {
let mut nonzero = None;
for col in 0..dim {
if !gate[[row, col]].is_zero_exact() {
if nonzero.is_some() || seen_cols[col] {
return SymbolicGateKind::Dense;
}
nonzero = Some((col, gate[[row, col]].clone()));
}
}
let Some((col, factor)) = nonzero else {
return SymbolicGateKind::Dense;
};
seen_cols[col] = true;
permutation.push((col, factor));
}
SymbolicGateKind::Permutation(permutation)
}
fn classify_numeric_gate(gate: &Array2<Complex64>) -> NumericGateKind {
let dim = gate.nrows();
if (0..dim).all(|row| (0..dim).all(|col| row == col || numeric_is_zero(gate[[row, col]]))) {
return NumericGateKind::Diagonal((0..dim).map(|idx| gate[[idx, idx]]).collect());
}
let mut seen_cols = vec![false; dim];
let mut permutation = Vec::with_capacity(dim);
for row in 0..dim {
let mut nonzero = None;
for col in 0..dim {
let factor = gate[[row, col]];
if !numeric_is_zero(factor) {
if nonzero.is_some() || seen_cols[col] {
return NumericGateKind::Dense;
}
nonzero = Some((col, factor));
}
}
let Some((col, factor)) = nonzero else {
return NumericGateKind::Dense;
};
seen_cols[col] = true;
permutation.push((col, factor));
}
NumericGateKind::Permutation(permutation)
}
fn resolve_params(
circuit: &Circuit,
params: &[CircuitParam],
) -> Result<Vec<Parameter>, CircuitError> {
params
.iter()
.map(|param| match param {
CircuitParam::Fixed(value) => Ok(Parameter::from(*value)),
CircuitParam::Index(idx) => circuit
.parameters()
.get_index(*idx as usize)
.cloned()
.ok_or(CircuitError::InvalidParameterIndex(*idx)),
})
.collect()
}
#[cfg(test)]
#[path = "./gate_test.rs"]
mod gate_test;