use crate::circuit::circuit_to_matrix;
use crate::circuit::error::CircuitError;
use crate::circuit::gate::circuit_gate::FrozenCircuit;
use crate::circuit::symbolic_matrix::{SymbolicMatrix, evaluate_symbolic_matrix};
use alloc::borrow::Cow;
use ndarray::Array2;
use num_complex::Complex;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq)]
pub enum UnitaryMatrix {
Numeric(Array2<Complex<f64>>),
Symbolic(SymbolicMatrix),
}
#[derive(Clone)]
pub struct UnitaryGate
where
Self: Send + Sync,
{
id: Uuid,
label: Arc<String>,
matrix: Option<Arc<UnitaryMatrix>>,
matrix_params: Option<Arc<[String]>>,
num_qubits: u16,
num_params: u16,
circuit: Option<Arc<FrozenCircuit>>,
}
impl UnitaryGate {
pub fn new(label: &str, num_qubits: u16, num_params: u16) -> Self {
Self {
id: Uuid::new_v4(),
label: Arc::new(label.to_string()),
matrix: None,
matrix_params: None,
num_qubits,
num_params,
circuit: None,
}
}
pub fn label(&self) -> &str {
&self.label
}
pub fn num_qubits(&self) -> u16 {
self.num_qubits
}
pub fn num_params(&self) -> u16 {
self.num_params
}
pub fn matrix(&self) -> Option<&Array2<Complex<f64>>> {
match self.matrix.as_deref() {
Some(UnitaryMatrix::Numeric(matrix)) => Some(matrix),
_ => None,
}
}
pub fn symbolic_matrix(&self) -> Option<&SymbolicMatrix> {
match self.matrix.as_deref() {
Some(UnitaryMatrix::Symbolic(matrix)) => Some(matrix),
_ => None,
}
}
pub fn matrix_params(&self) -> Option<&[String]> {
self.matrix_params.as_deref()
}
pub fn matrix_repr(&self) -> Option<&UnitaryMatrix> {
self.matrix.as_deref()
}
pub fn circuit(&self) -> &Option<Arc<FrozenCircuit>> {
&self.circuit
}
pub fn with_matrix(mut self, mat: Array2<Complex<f64>>) -> Result<Self, CircuitError> {
if self.num_params != 0 {
return Err(CircuitError::ParameterCountMismatch {
expected: 0,
actual: self.num_params as usize,
});
}
self.validate_matrix_shape(&mat)?;
self.matrix = Some(Arc::new(UnitaryMatrix::Numeric(mat)));
self.matrix_params = None;
Ok(self)
}
pub fn with_symbolic_matrix<I, S>(
mut self,
params: I,
matrix: SymbolicMatrix,
) -> Result<Self, CircuitError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.validate_symbolic_matrix_shape(&matrix)?;
let params: Vec<String> = params.into_iter().map(Into::into).collect();
self.validate_symbolic_matrix_params(¶ms, &matrix)?;
self.matrix = Some(Arc::new(UnitaryMatrix::Symbolic(matrix)));
self.matrix_params = Some(Arc::from(params.into_boxed_slice()));
Ok(self)
}
pub fn matrix_for_params(
&self,
params: &[f64],
) -> Result<Cow<'_, Array2<Complex<f64>>>, CircuitError> {
self.resolve_matrix_for_params(params, false)
}
pub(crate) fn matrix_for_params_in_gate_order(
&self,
params: &[f64],
) -> Result<Cow<'_, Array2<Complex<f64>>>, CircuitError> {
self.resolve_matrix_for_params(params, true)
}
fn resolve_matrix_for_params(
&self,
params: &[f64],
circuit_msb_first: bool,
) -> Result<Cow<'_, Array2<Complex<f64>>>, CircuitError> {
self.validate_param_values(params)?;
if let Some(matrix) = self.matrix.as_deref() {
match matrix {
UnitaryMatrix::Numeric(matrix) => return Ok(Cow::Borrowed(matrix)),
UnitaryMatrix::Symbolic(matrix) => {
let matrix_params = self
.matrix_params
.as_deref()
.ok_or(CircuitError::SymbolicParameterError)?;
let bindings: HashMap<&str, f64> = matrix_params
.iter()
.map(String::as_str)
.zip(params.iter().copied())
.collect();
let matrix = evaluate_symbolic_matrix(matrix, &Some(bindings))
.map_err(|_| CircuitError::SymbolicParameterError)?;
self.validate_matrix_shape(&matrix)?;
return Ok(Cow::Owned(matrix));
}
}
}
if let Some(circuit) = self.circuit.as_ref() {
let inner = circuit.circuit();
let symbols = circuit.used_symbols();
let mut bindings = HashMap::new();
for (symbol, value) in symbols.iter().zip(params.iter()) {
bindings.insert(symbol.as_str(), *value);
}
let assigned = inner
.assign_parameters(&Some(bindings))
.map_err(|_| CircuitError::SymbolicParameterError)?;
let mut order: Vec<usize> = circuit
.qubits_in_numeric_order()
.iter()
.map(|q| q.index())
.collect();
if circuit_msb_first {
order.reverse();
}
return circuit_to_matrix(&assigned, Some(&order)).map(Cow::Owned);
}
Err(CircuitError::NoMatrixRepresentation)
}
fn validate_param_values(&self, params: &[f64]) -> Result<(), CircuitError> {
if params.len() != self.num_params as usize {
return Err(CircuitError::ParameterCountMismatch {
expected: self.num_params as usize,
actual: params.len(),
});
}
for (idx, value) in params.iter().copied().enumerate() {
if !value.is_finite() {
return Err(CircuitError::InvalidParameterValue(idx, value));
}
}
Ok(())
}
fn validate_symbolic_matrix_params(
&self,
params: &[String],
matrix: &SymbolicMatrix,
) -> Result<(), CircuitError> {
if params.len() != self.num_params as usize {
return Err(CircuitError::ParameterCountMismatch {
expected: self.num_params as usize,
actual: params.len(),
});
}
let mut seen = HashSet::new();
for param in params {
if !seen.insert(param.as_str()) {
return Err(CircuitError::InvalidOperation(format!(
"duplicate symbolic matrix parameter '{param}'"
)));
}
}
for value in matrix.iter() {
for symbol in value
.re
.get_symbols()
.into_iter()
.chain(value.im.get_symbols())
{
if !seen.contains(symbol.as_str()) {
return Err(CircuitError::InvalidOperation(format!(
"symbolic matrix contains undeclared parameter '{symbol}'"
)));
}
}
}
Ok(())
}
fn validate_circuit_signature(&self, circuit: &FrozenCircuit) -> Result<(), CircuitError> {
let actual_qubits = circuit.circuit().qubits().len();
if actual_qubits != self.num_qubits as usize {
return Err(CircuitError::QubitCountMismatch {
expected: self.num_qubits as usize,
actual: actual_qubits,
});
}
let actual_params = circuit.used_symbols().len();
if actual_params != self.num_params as usize {
return Err(CircuitError::ParameterCountMismatch {
expected: self.num_params as usize,
actual: actual_params,
});
}
Ok(())
}
pub fn with_circuit(mut self, circuit: Arc<FrozenCircuit>) -> Result<Self, CircuitError> {
self.validate_circuit_signature(&circuit)?;
self.circuit = Some(circuit);
Ok(self)
}
fn validate_matrix_shape(&self, mat: &Array2<Complex<f64>>) -> Result<(), CircuitError> {
self.validate_symbolic_matrix_shape(mat)?;
for ((row, col), val) in mat.indexed_iter() {
if !val.re.is_finite() || !val.im.is_finite() {
return Err(CircuitError::InvalidOperation(format!(
"Matrix contains non-finite element at ({row}, {col}): {val}"
)));
}
}
const UNITARITY_EPS: f64 = 1e-10;
let conj_t = mat.t().mapv(|x| x.conj());
let product = conj_t.dot(mat);
for (i, val) in product.diag().iter().enumerate() {
let diff = (val - Complex::new(1.0, 0.0)).norm();
if diff > UNITARITY_EPS {
return Err(CircuitError::InvalidOperation(format!(
"Matrix is not unitary: (U†U)[{i},{i}] = {val}, expected 1.0"
)));
}
}
for ((i, j), val) in product.indexed_iter() {
if i != j {
let diff = val.norm();
if diff > UNITARITY_EPS {
return Err(CircuitError::InvalidOperation(format!(
"Matrix is not unitary: (U†U)[{i},{j}] = {val}, expected 0.0"
)));
}
}
}
Ok(())
}
fn validate_symbolic_matrix_shape<T>(&self, mat: &Array2<T>) -> Result<(), CircuitError> {
let expected_dim = 1usize.checked_shl(self.num_qubits as u32).ok_or_else(|| {
CircuitError::InvalidOperation(format!(
"cannot build matrix for {} qubits: dimension overflows usize",
self.num_qubits
))
})?;
if mat.shape() != [expected_dim, expected_dim] {
return Err(CircuitError::InvalidOperation(format!(
"Matrix dimension mismatch. Expected {}x{}, got {}x{}",
expected_dim,
expected_dim,
mat.nrows(),
mat.ncols()
)));
}
Ok(())
}
}
impl fmt::Debug for UnitaryGate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnitaryGate")
.field("id", &self.id)
.field("label", &self.label)
.field("matrix", &self.matrix)
.field("matrix_params", &self.matrix_params)
.field("num_qubits", &self.num_qubits)
.field("num_params", &self.num_params)
.field("circuit", &self.circuit)
.finish()
}
}
impl Eq for UnitaryGate {}
impl PartialEq for UnitaryGate {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Hash for UnitaryGate {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl fmt::Display for UnitaryGate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.label().fmt(f)
}
}
#[cfg(test)]
#[path = "./unitary_gate_test.rs"]
mod unitary_gate_test;