use crate::circuit::CircuitError;
use crate::circuit::error::ParameterError;
use crate::circuit::parameter::Parameter;
use crate::circuit::symbolic_matrix::PARALLEL_THRESHOLD_OPS;
use ndarray::Array2;
use ndarray::parallel::prelude::*;
use num_complex::Complex64;
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Add, Mul, Neg, Sub};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SymbolicComplex {
pub re: Parameter,
pub im: Parameter,
}
pub type SymbolicMatrix = Array2<SymbolicComplex>;
impl Default for SymbolicComplex {
fn default() -> Self {
Self::zero()
}
}
impl SymbolicComplex {
pub fn new(re: impl Into<Parameter>, im: impl Into<Parameter>) -> Self {
Self {
re: re.into(),
im: im.into(),
}
}
pub fn zero() -> Self {
Self::new(0.0, 0.0)
}
pub fn one() -> Self {
Self::new(1.0, 0.0)
}
pub fn i() -> Self {
Self::new(0.0, 1.0)
}
pub fn from_real(value: impl Into<Parameter>) -> Self {
Self::new(value, 0.0)
}
pub fn from_complex(value: Complex64) -> Self {
Self::new(value.re, value.im)
}
pub fn exp_i(theta: impl Into<Parameter>) -> Self {
let theta = theta.into();
Self::new(theta.cos(), theta.sin())
}
pub fn evaluate(
&self,
bindings: &Option<HashMap<&str, f64>>,
) -> Result<Complex64, ParameterError> {
Ok(Complex64::new(
self.re.evaluate(bindings)?,
self.im.evaluate(bindings)?,
))
}
pub fn simplify(&self) -> Result<Self, ParameterError> {
Ok(Self::new(self.re.simplify()?, self.im.simplify()?))
}
pub fn replace(&self, symbol: &str, value: impl Into<Parameter>) -> Self {
let value = value.into();
Self::new(
self.re.replace(symbol, value.clone()),
self.im.replace(symbol, value),
)
}
pub fn is_zero_exact(&self) -> bool {
self.re.is_zero() && self.im.is_zero()
}
pub fn is_one_exact(&self) -> bool {
self.re.is_one() && self.im.is_zero()
}
pub fn simplifies_to_zero(&self) -> Result<bool, ParameterError> {
if self.is_zero_exact() {
return Ok(true);
}
let simplified = self.simplify()?;
Ok(simplified.is_zero_exact())
}
}
impl Add for SymbolicComplex {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(self.re + rhs.re, self.im + rhs.im)
}
}
impl Add<&SymbolicComplex> for SymbolicComplex {
type Output = SymbolicComplex;
fn add(self, rhs: &SymbolicComplex) -> Self::Output {
SymbolicComplex::new(self.re + rhs.re.clone(), self.im + rhs.im.clone())
}
}
impl Add<SymbolicComplex> for &SymbolicComplex {
type Output = SymbolicComplex;
fn add(self, rhs: SymbolicComplex) -> Self::Output {
SymbolicComplex::new(self.re.clone() + rhs.re, self.im.clone() + rhs.im)
}
}
impl Add<&SymbolicComplex> for &SymbolicComplex {
type Output = SymbolicComplex;
fn add(self, rhs: &SymbolicComplex) -> Self::Output {
SymbolicComplex::new(
self.re.clone() + rhs.re.clone(),
self.im.clone() + rhs.im.clone(),
)
}
}
impl Sub for SymbolicComplex {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self::new(self.re - rhs.re, self.im - rhs.im)
}
}
impl Sub<&SymbolicComplex> for SymbolicComplex {
type Output = SymbolicComplex;
fn sub(self, rhs: &SymbolicComplex) -> Self::Output {
SymbolicComplex::new(self.re - rhs.re.clone(), self.im - rhs.im.clone())
}
}
impl Sub<SymbolicComplex> for &SymbolicComplex {
type Output = SymbolicComplex;
fn sub(self, rhs: SymbolicComplex) -> Self::Output {
SymbolicComplex::new(self.re.clone() - rhs.re, self.im.clone() - rhs.im)
}
}
impl Sub<&SymbolicComplex> for &SymbolicComplex {
type Output = SymbolicComplex;
fn sub(self, rhs: &SymbolicComplex) -> Self::Output {
SymbolicComplex::new(
self.re.clone() - rhs.re.clone(),
self.im.clone() - rhs.im.clone(),
)
}
}
impl Mul for SymbolicComplex {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let re = self.re.clone() * rhs.re.clone() - self.im.clone() * rhs.im.clone();
let im = self.re * rhs.im + self.im * rhs.re;
Self::new(re, im)
}
}
impl Mul<&SymbolicComplex> for SymbolicComplex {
type Output = SymbolicComplex;
fn mul(self, rhs: &SymbolicComplex) -> Self::Output {
let re = self.re.clone() * rhs.re.clone() - self.im.clone() * rhs.im.clone();
let im = self.re * rhs.im.clone() + self.im * rhs.re.clone();
SymbolicComplex::new(re, im)
}
}
impl Mul<SymbolicComplex> for &SymbolicComplex {
type Output = SymbolicComplex;
fn mul(self, rhs: SymbolicComplex) -> Self::Output {
let re = self.re.clone() * rhs.re.clone() - self.im.clone() * rhs.im.clone();
let im = self.re.clone() * rhs.im + self.im.clone() * rhs.re;
SymbolicComplex::new(re, im)
}
}
impl Mul<&SymbolicComplex> for &SymbolicComplex {
type Output = SymbolicComplex;
fn mul(self, rhs: &SymbolicComplex) -> Self::Output {
let re = self.re.clone() * rhs.re.clone() - self.im.clone() * rhs.im.clone();
let im = self.re.clone() * rhs.im.clone() + self.im.clone() * rhs.re.clone();
SymbolicComplex::new(re, im)
}
}
impl Neg for SymbolicComplex {
type Output = Self;
fn neg(self) -> Self::Output {
Self::new(-self.re, -self.im)
}
}
impl Neg for &SymbolicComplex {
type Output = SymbolicComplex;
fn neg(self) -> Self::Output {
SymbolicComplex::new(-self.re.clone(), -self.im.clone())
}
}
impl Mul<Complex64> for SymbolicComplex {
type Output = Self;
fn mul(self, rhs: Complex64) -> Self::Output {
Self::new(
self.re.clone() * rhs.re - self.im.clone() * rhs.im,
self.re * rhs.im + self.im * rhs.re,
)
}
}
impl Mul<Complex64> for &SymbolicComplex {
type Output = SymbolicComplex;
fn mul(self, rhs: Complex64) -> Self::Output {
SymbolicComplex::new(
self.re.clone() * rhs.re - self.im.clone() * rhs.im,
self.re.clone() * rhs.im + self.im.clone() * rhs.re,
)
}
}
impl Mul<SymbolicComplex> for Complex64 {
type Output = SymbolicComplex;
fn mul(self, rhs: SymbolicComplex) -> Self::Output {
rhs * self
}
}
impl Mul<&SymbolicComplex> for Complex64 {
type Output = SymbolicComplex;
fn mul(self, rhs: &SymbolicComplex) -> Self::Output {
rhs * self
}
}
impl fmt::Display for SymbolicComplex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.im.is_zero() {
write!(f, "{}", self.re)
} else if self.re.is_zero() {
write!(f, "{}i", self.im)
} else {
let im_str = self.im.to_string();
if im_str.starts_with('-') {
write!(f, "{} - {}i", self.re, im_str.trim_start_matches('-'))
} else {
write!(f, "{} + {}i", self.re, im_str)
}
}
}
}
const INTERNAL_SUB_PREFIX: &str = "__cqlib_internal_sub";
pub fn substitute_symbolic_matrix(
matrix: SymbolicMatrix,
replacements: &HashMap<String, Parameter>,
) -> Result<SymbolicMatrix, CircuitError> {
if replacements.is_empty() {
return Ok(matrix);
}
for value in matrix.iter() {
for sym in value
.re
.get_symbols()
.into_iter()
.chain(value.im.get_symbols())
{
if sym.contains(INTERNAL_SUB_PREFIX) {
return Err(CircuitError::InvalidOperation(format!(
"symbol name '{}' in input matrix collides with internal substitution prefix '{}'",
sym, INTERNAL_SUB_PREFIX
)));
}
}
}
for key in replacements.keys() {
if key.contains(INTERNAL_SUB_PREFIX) {
return Err(CircuitError::InvalidOperation(format!(
"symbol name '{}' collides with internal substitution prefix '{}'",
key, INTERNAL_SUB_PREFIX
)));
}
}
for param in replacements.values() {
for sym in param.get_symbols() {
if sym.contains(INTERNAL_SUB_PREFIX) {
return Err(CircuitError::InvalidOperation(format!(
"symbol name '{}' in replacement value collides with internal substitution prefix '{}'",
sym, INTERNAL_SUB_PREFIX
)));
}
}
}
let dim = matrix.raw_dim();
let (mut raw, _) = matrix.into_raw_vec_and_offset();
if replacements.len() == 1 {
let (symbol, replacement) = replacements.iter().next().unwrap();
raw.par_iter_mut().for_each(|v| {
*v = v.replace(symbol, replacement.clone());
});
return Ok(Array2::from_shape_vec(dim, raw).expect("valid shape for owned matrix"));
}
let keys: HashSet<&str> = replacements.keys().map(|s| s.as_str()).collect();
let has_overlap = replacements.values().any(|param| {
param
.get_symbols()
.iter()
.any(|sym| keys.contains(sym.as_str()))
});
if has_overlap {
let temp_replacements: Vec<(String, String, Parameter)> = replacements
.iter()
.map(|(k, v)| {
(
k.clone(),
format!("{}{}", INTERNAL_SUB_PREFIX, k),
v.clone(),
)
})
.collect();
raw.par_iter_mut().for_each(|v| {
for (symbol, temp_key, _) in &temp_replacements {
*v = v.replace(symbol, Parameter::symbol(temp_key));
}
for (_, temp_key, replacement) in &temp_replacements {
*v = v.replace(temp_key, replacement.clone());
}
});
} else {
let reps: Vec<(String, Parameter)> = replacements
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
raw.par_iter_mut().for_each(|v| {
for (symbol, replacement) in &reps {
*v = v.replace(symbol, replacement.clone());
}
});
}
Ok(Array2::from_shape_vec(dim, raw).expect("valid shape for owned matrix"))
}
pub fn simplify_matrix(m: &SymbolicMatrix) -> Result<SymbolicMatrix, ParameterError> {
let mut out = m.clone();
out.as_slice_mut()
.expect("symbolic matrix must be contiguous")
.par_iter_mut()
.try_for_each(|elem| -> Result<(), ParameterError> {
*elem = elem.simplify()?;
Ok(())
})?;
Ok(out)
}
pub fn symbolic_eye(dim: usize) -> SymbolicMatrix {
let mut matrix = Array2::from_elem((dim, dim), SymbolicComplex::zero());
for i in 0..dim {
matrix[[i, i]] = SymbolicComplex::one();
}
matrix
}
pub fn apply_symbolic_permutation_gate(
matrix: &mut SymbolicMatrix,
permutation: &[(usize, SymbolicComplex)],
bits: &[usize],
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let gate_dim = 1usize << bits.len();
let sorted_bits = sorted_bits(bits);
let offsets = gate_offsets(bits);
let loop_limit = dim >> bits.len();
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, input: &mut Vec<SymbolicComplex>| {
let base = expand_base_index(i, &sorted_bits);
unsafe {
input.resize(gate_dim * cols, SymbolicComplex::zero());
for local_row in 0..gate_dim {
let row_ptr = unsafe_slice.row_ptr(base | offsets[local_row], cols);
for col in 0..cols {
input[local_row * cols + col] = (*row_ptr.add(col)).clone();
}
}
for (local_row, (source_row, factor)) in permutation.iter().enumerate() {
let row_ptr = unsafe_slice.row_ptr(base | offsets[local_row], cols);
let source_start = source_row * cols;
for col in 0..cols {
*row_ptr.add(col) = factor * &input[source_start + col];
}
}
}
};
if parallel {
(0..loop_limit)
.into_par_iter()
.for_each_init(Vec::new, |input, i| process_idx(i, input));
} else {
let mut input = Vec::new();
for i in 0..loop_limit {
process_idx(i, &mut input);
}
}
}
pub fn apply_symbolic_diagonal_gate(
matrix: &mut SymbolicMatrix,
diagonal: &[SymbolicComplex],
bits: &[usize],
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let sorted_bits = sorted_bits(bits);
let offsets = gate_offsets(bits);
let loop_limit = dim >> bits.len();
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| {
let base = expand_base_index(i, &sorted_bits);
unsafe {
for (local_row, scale) in diagonal.iter().enumerate() {
if scale.is_one_exact() {
continue;
}
let row_ptr = unsafe_slice.row_ptr(base | offsets[local_row], cols);
for col in 0..cols {
let old = std::mem::take(&mut *row_ptr.add(col));
*row_ptr.add(col) = scale * old;
}
}
}
};
if parallel {
(0..loop_limit).into_par_iter().for_each(process_idx);
} else {
(0..loop_limit).for_each(process_idx);
}
}
pub fn apply_numeric_permutation_gate(
matrix: &mut SymbolicMatrix,
permutation: &[(usize, Complex64)],
bits: &[usize],
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let gate_dim = 1usize << bits.len();
let sorted_bits = sorted_bits(bits);
let offsets = gate_offsets(bits);
let loop_limit = dim >> bits.len();
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, input: &mut Vec<SymbolicComplex>| {
let base = expand_base_index(i, &sorted_bits);
unsafe {
input.resize(gate_dim * cols, SymbolicComplex::zero());
for local_row in 0..gate_dim {
let row_ptr = unsafe_slice.row_ptr(base | offsets[local_row], cols);
for col in 0..cols {
input[local_row * cols + col] = (*row_ptr.add(col)).clone();
}
}
for (local_row, (source_row, factor)) in permutation.iter().copied().enumerate() {
let row_ptr = unsafe_slice.row_ptr(base | offsets[local_row], cols);
let source_start = source_row * cols;
for col in 0..cols {
*row_ptr.add(col) = factor * &input[source_start + col];
}
}
}
};
if parallel {
(0..loop_limit)
.into_par_iter()
.for_each_init(Vec::new, |input, i| process_idx(i, input));
} else {
let mut input = Vec::new();
for i in 0..loop_limit {
process_idx(i, &mut input);
}
}
}
pub fn apply_numeric_diagonal_gate(
matrix: &mut SymbolicMatrix,
diagonal: &[Complex64],
bits: &[usize],
) {
let dim = matrix.nrows();
let cols = matrix.ncols();
let sorted_bits = sorted_bits(bits);
let offsets = gate_offsets(bits);
let loop_limit = dim >> bits.len();
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| {
let base = expand_base_index(i, &sorted_bits);
unsafe {
for (local_row, scale) in diagonal.iter().copied().enumerate() {
if numeric_is_one(scale) {
continue;
}
let row_ptr = unsafe_slice.row_ptr(base | offsets[local_row], cols);
for col in 0..cols {
let old = std::mem::take(&mut *row_ptr.add(col));
*row_ptr.add(col) = scale * old;
}
}
}
};
if parallel {
(0..loop_limit).into_par_iter().for_each(process_idx);
} else {
(0..loop_limit).for_each(process_idx);
}
}
pub fn evaluate_symbolic_matrix(
matrix: &SymbolicMatrix,
bindings: &Option<HashMap<&str, f64>>,
) -> Result<Array2<Complex64>, ParameterError> {
let mut out = Array2::from_elem(matrix.raw_dim(), Complex64::new(0.0, 0.0));
if let (Some(m), Some(o)) = (matrix.as_slice(), out.as_slice_mut()) {
m.par_iter().zip(o.par_iter_mut()).try_for_each(
|(v, o)| -> Result<(), ParameterError> {
*o = v.evaluate(bindings)?;
Ok(())
},
)?;
} else {
for ((row, col), value) in matrix.indexed_iter() {
out[[row, col]] = value.evaluate(bindings)?;
}
}
Ok(out)
}
pub(crate) struct UnsafeSymbolicSlice<'a> {
ptr: *mut SymbolicComplex,
_marker: PhantomData<&'a mut [SymbolicComplex]>,
}
unsafe impl<'a> Sync for UnsafeSymbolicSlice<'a> {}
unsafe impl<'a> Send for UnsafeSymbolicSlice<'a> {}
impl<'a> UnsafeSymbolicSlice<'a> {
pub fn new(slice: &'a mut [SymbolicComplex]) -> Self {
Self {
ptr: slice.as_mut_ptr(),
_marker: PhantomData,
}
}
pub unsafe fn row_ptr(&self, row_idx: usize, cols: usize) -> *mut SymbolicComplex {
unsafe { self.ptr.add(row_idx * cols) }
}
}
fn expand_base_index(mut compact: usize, sorted_bits: &[usize]) -> usize {
for &q in sorted_bits {
let mask = (1usize << q) - 1;
let left = (compact & !mask) << 1;
let right = compact & mask;
compact = left | right;
}
compact
}
fn sorted_bits(bits: &[usize]) -> SmallVec<[usize; 8]> {
let mut sorted: SmallVec<[usize; 8]> = bits.iter().copied().collect();
sorted.sort();
sorted
}
fn gate_offsets(bits: &[usize]) -> Vec<usize> {
let gate_dim = 1usize << bits.len();
let mut offsets = vec![0usize; gate_dim];
for (k, offset_ref) in 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;
}
offsets
}
pub(crate) fn numeric_is_zero(value: Complex64) -> bool {
value.re == 0.0 && value.im == 0.0
}
pub(crate) fn numeric_is_one(value: Complex64) -> bool {
value.re == 1.0 && value.im == 0.0
}
#[cfg(test)]
#[path = "./matrix_test.rs"]
mod matrix_test;