use crate::circuit::circuit_impl::Circuit;
use crate::circuit::error::CircuitError;
use crate::circuit::symbolic_matrix::{SymbolicMatrix, circuit_to_symbolic_matrix};
use indexmap::IndexSet;
use std::sync::{Arc, OnceLock};
#[derive(Debug, Clone)]
pub struct FrozenCircuit {
pub(crate) circuit: Circuit,
used_symbols: Arc<IndexSet<String>>,
symbolic_matrix_cache: Arc<OnceLock<Arc<SymbolicMatrix>>>,
}
impl FrozenCircuit {
pub fn new(circuit: Circuit) -> Self {
let used_symbols = Arc::new(circuit.used_symbols());
Self {
circuit,
used_symbols,
symbolic_matrix_cache: Arc::new(OnceLock::new()),
}
}
pub fn circuit(&self) -> &Circuit {
&self.circuit
}
pub fn used_symbols(&self) -> &IndexSet<String> {
&self.used_symbols
}
pub fn symbolic_matrix(&self) -> Result<Arc<SymbolicMatrix>, CircuitError> {
if let Some(matrix) = self.symbolic_matrix_cache.get() {
return Ok(matrix.clone());
}
let matrix = Arc::new(circuit_to_symbolic_matrix(&self.circuit, None)?);
let _ = self.symbolic_matrix_cache.set(matrix.clone());
Ok(self.symbolic_matrix_cache.get().cloned().unwrap_or(matrix))
}
}
impl PartialEq for FrozenCircuit {
fn eq(&self, other: &Self) -> bool {
self.circuit == other.circuit && self.used_symbols == other.used_symbols
}
}
#[derive(Debug, Clone)]
pub struct CircuitGate {
pub name: Arc<String>,
pub(crate) num_qubits: usize,
pub(crate) num_params: usize,
pub(crate) signature_params: IndexSet<String>,
pub(crate) circuit: Arc<FrozenCircuit>,
}
impl PartialEq for CircuitGate {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.num_qubits == other.num_qubits
&& self.num_params == other.num_params
&& self
.signature_params
.iter()
.eq(other.signature_params.iter())
&& self.circuit.circuit() == other.circuit.circuit()
}
}
impl CircuitGate {
pub fn new(name: impl Into<String>, circuit: FrozenCircuit) -> Result<Self, CircuitError> {
let num_qubits = circuit.circuit.qubits().len();
let signature_params = circuit.used_symbols().clone();
let num_params = signature_params.len();
Ok(Self {
name: Arc::new(name.into()),
num_qubits,
num_params,
signature_params,
circuit: Arc::new(circuit),
})
}
pub fn with_signature(
name: impl Into<String>,
circuit: FrozenCircuit,
params: impl IntoIterator<Item = String>,
) -> Result<Self, CircuitError> {
let mut signature_params = IndexSet::new();
for param in params {
if !signature_params.insert(param.clone()) {
return Err(CircuitError::InvalidOperation(format!(
"duplicate circuit gate parameter '{param}'"
)));
}
}
for symbol in circuit.used_symbols() {
if !signature_params.contains(symbol) {
return Err(CircuitError::InvalidOperation(format!(
"circuit gate implementation references undeclared parameter '{symbol}'"
)));
}
}
let num_qubits = circuit.circuit.qubits().len();
let num_params = signature_params.len();
Ok(Self {
name: Arc::new(name.into()),
num_qubits,
num_params,
signature_params,
circuit: Arc::new(circuit),
})
}
pub fn signature_params(&self) -> &IndexSet<String> {
&self.signature_params
}
pub fn used_symbols(&self) -> &IndexSet<String> {
self.circuit.used_symbols()
}
pub fn symbols(&self) -> IndexSet<String> {
self.used_symbols().clone()
}
pub fn num_qubits(&self) -> usize {
self.num_qubits
}
pub fn num_params(&self) -> usize {
self.num_params
}
pub fn circuit(&self) -> Arc<FrozenCircuit> {
self.circuit.clone()
}
pub fn symbolic_matrix(&self) -> Result<Arc<SymbolicMatrix>, CircuitError> {
self.circuit.symbolic_matrix()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn inverse(&self) -> Result<Self, CircuitError> {
let inverted_circuit = self.circuit.circuit.inverse()?;
let frozen_inverted = FrozenCircuit::new(inverted_circuit);
CircuitGate::with_signature(
format!("{}_dg", self.name),
frozen_inverted,
self.signature_params.iter().cloned(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::{Circuit, Parameter, Qubit};
#[test]
fn signature_params_are_distinct_from_used_symbols() {
let mut circuit = Circuit::new(1);
circuit
.rx(Qubit::new(0), Parameter::symbol("used"))
.unwrap();
let gate = CircuitGate::with_signature(
"declared",
FrozenCircuit::new(circuit),
["unused".to_string(), "used".to_string()],
)
.unwrap();
assert_eq!(gate.num_params(), 2);
assert_eq!(
gate.signature_params().iter().cloned().collect::<Vec<_>>(),
vec!["unused".to_string(), "used".to_string()]
);
assert!(gate.symbols().contains("used"));
assert!(!gate.symbols().contains("unused"));
assert_eq!(gate.used_symbols(), &gate.symbols());
}
#[test]
fn explicit_signature_ignores_interned_but_unreferenced_symbols() {
let mut circuit = Circuit::new(1);
circuit.add_parameter(Parameter::symbol("stale"));
circuit
.rx(Qubit::new(0), Parameter::symbol("used"))
.unwrap();
let gate = CircuitGate::with_signature(
"declared",
FrozenCircuit::new(circuit),
["declared_unused".to_string(), "used".to_string()],
)
.unwrap();
assert_eq!(
gate.signature_params()
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
vec!["declared_unused", "used"]
);
assert_eq!(
gate.used_symbols()
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
vec!["used"]
);
}
#[test]
fn explicit_signature_still_rejects_actually_used_undeclared_symbols() {
let mut circuit = Circuit::new(1);
circuit
.rx(Qubit::new(0), Parameter::symbol("used"))
.unwrap();
let error = CircuitGate::with_signature(
"missing",
FrozenCircuit::new(circuit),
["different".to_string()],
)
.unwrap_err();
assert!(
matches!(error, CircuitError::InvalidOperation(message) if message.contains("used"))
);
}
#[test]
fn inverse_preserves_explicit_signature_and_live_dependencies() {
let mut circuit = Circuit::new(1);
circuit.add_parameter(Parameter::symbol("stale"));
circuit
.rx(Qubit::new(0), Parameter::symbol("used"))
.unwrap();
let gate = CircuitGate::with_signature(
"forward",
FrozenCircuit::new(circuit),
["declared_unused".to_string(), "used".to_string()],
)
.unwrap();
let inverse = gate.inverse().unwrap();
assert_eq!(inverse.signature_params(), gate.signature_params());
assert_eq!(inverse.used_symbols(), gate.used_symbols());
}
#[test]
fn frozen_circuits_compare_structurally_ignoring_matrix_cache() {
let make_frozen = || {
let mut circuit = Circuit::new(1);
circuit.h(Qubit::new(0)).unwrap();
FrozenCircuit::new(circuit)
};
let a = make_frozen();
let b = make_frozen();
assert_eq!(a, b);
a.symbolic_matrix().unwrap();
assert_eq!(a, b);
let mut other = Circuit::new(1);
other.x(Qubit::new(0)).unwrap();
assert_ne!(a, FrozenCircuit::new(other));
}
}