use std::fmt::{self, Display, Formatter};
use std::ops::{Add, AddAssign, Mul, MulAssign};
use std::str::FromStr;
pub enum CliffordGateValue<'a> {
Name(&'a str),
Gate(crate::Gate),
}
impl<'a> From<&'a str> for CliffordGateValue<'a> {
fn from(value: &'a str) -> Self {
Self::Name(value)
}
}
impl<'a> From<crate::Gate> for CliffordGateValue<'a> {
fn from(value: crate::Gate) -> Self {
Self::Gate(value)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct CliffordString {
pub(crate) inner: stim_cxx::CliffordString,
}
impl CliffordString {
#[must_use]
pub fn new(num_qubits: usize) -> Self {
Self {
inner: stim_cxx::CliffordString::new(num_qubits),
}
}
fn parse_text(text: &str) -> crate::Result<Self> {
stim_cxx::CliffordString::from_text(text)
.map(|inner| Self { inner })
.map_err(crate::StimError::from)
}
#[must_use]
pub fn from_pauli_string(pauli_string: &crate::PauliString) -> Self {
Self {
inner: stim_cxx::CliffordString::from_pauli_string(&pauli_string.inner),
}
}
pub fn from_circuit(circuit: &crate::Circuit) -> crate::Result<Self> {
stim_cxx::CliffordString::from_circuit(&circuit.inner)
.map(|inner| Self { inner })
.map_err(crate::StimError::from)
}
#[must_use]
pub fn random(num_qubits: usize) -> Self {
Self {
inner: stim_cxx::CliffordString::random(num_qubits),
}
}
#[must_use]
pub fn all_cliffords_string() -> Self {
Self {
inner: stim_cxx::CliffordString::all_cliffords_string(),
}
}
#[must_use]
pub fn num_qubits(&self) -> usize {
self.inner.num_qubits()
}
#[must_use]
pub fn len(&self) -> usize {
self.num_qubits()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: isize) -> crate::Result<crate::Gate> {
self.inner
.get_item_name(index as i64)
.map_err(crate::StimError::from)
.and_then(|name| crate::Gate::new(&name))
}
pub fn set<'a>(
&mut self,
index: isize,
value: impl Into<CliffordGateValue<'a>>,
) -> crate::Result<()> {
let normalized = crate::normalize_index(index, self.len())
.ok_or_else(|| crate::StimError::new(format!("index {index} out of range")))?;
let replacement = match value.into() {
CliffordGateValue::Name(name) => crate::Gate::new(name)?.name().to_string(),
CliffordGateValue::Gate(gate) => gate.name().to_string(),
};
let mut parts: Vec<String> = if self.is_empty() {
Vec::new()
} else {
self.to_string().split(',').map(str::to_owned).collect()
};
parts[normalized] = replacement;
*self = parts.join(",").parse::<Self>()?;
Ok(())
}
pub fn slice(
&self,
start: Option<isize>,
stop: Option<isize>,
step: isize,
) -> crate::Result<Self> {
if step == 0 {
return Err(crate::StimError::new("slice step cannot be zero"));
}
let indices = crate::compute_slice_indices(self.len() as isize, start, stop, step);
let slice_length = i64::try_from(indices.len())
.map_err(|_| crate::StimError::new("slice result length overflow"))?;
let start = indices.first().copied().unwrap_or(0) as i64;
Ok(Self {
inner: self.inner.get_slice(start, step as i64, slice_length),
})
}
pub fn x_outputs(&self) -> (crate::PauliString, Vec<bool>) {
let signs = self.inner.x_signs_bit_packed();
(
crate::PauliString {
inner: self.inner.x_outputs(),
imag: false,
},
crate::unpack_bits(&signs, self.len()),
)
}
pub fn x_outputs_bit_packed(&self) -> (crate::PauliString, Vec<u8>) {
let signs = self.inner.x_signs_bit_packed();
(
crate::PauliString {
inner: self.inner.x_outputs(),
imag: false,
},
signs,
)
}
pub fn y_outputs(&self) -> (crate::PauliString, Vec<bool>) {
let signs = self.inner.y_signs_bit_packed();
(
crate::PauliString {
inner: self.inner.y_outputs(),
imag: false,
},
crate::unpack_bits(&signs, self.len()),
)
}
pub fn y_outputs_bit_packed(&self) -> (crate::PauliString, Vec<u8>) {
let signs = self.inner.y_signs_bit_packed();
(
crate::PauliString {
inner: self.inner.y_outputs(),
imag: false,
},
signs,
)
}
pub fn z_outputs(&self) -> (crate::PauliString, Vec<bool>) {
let signs = self.inner.z_signs_bit_packed();
(
crate::PauliString {
inner: self.inner.z_outputs(),
imag: false,
},
crate::unpack_bits(&signs, self.len()),
)
}
pub fn z_outputs_bit_packed(&self) -> (crate::PauliString, Vec<u8>) {
let signs = self.inner.z_signs_bit_packed();
(
crate::PauliString {
inner: self.inner.z_outputs(),
imag: false,
},
signs,
)
}
#[must_use]
pub fn pow(&self, exponent: i64) -> Self {
Self {
inner: self.inner.pow(exponent),
}
}
pub fn ipow(&mut self, exponent: i64) {
self.inner.ipow(exponent);
}
}
impl Default for CliffordString {
fn default() -> Self {
Self::new(0)
}
}
impl Display for CliffordString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner.to_text())
}
}
impl fmt::Debug for CliffordString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner.to_repr_text())
}
}
impl FromStr for CliffordString {
type Err = crate::StimError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_text(s)
}
}
impl Add for CliffordString {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
inner: self.inner.add(&rhs.inner),
}
}
}
impl AddAssign for CliffordString {
fn add_assign(&mut self, rhs: Self) {
self.inner.add_assign(&rhs.inner);
}
}
impl Mul<CliffordString> for CliffordString {
type Output = Self;
fn mul(self, rhs: CliffordString) -> Self::Output {
Self {
inner: self.inner.mul_clifford(&rhs.inner),
}
}
}
impl MulAssign<CliffordString> for CliffordString {
fn mul_assign(&mut self, rhs: CliffordString) {
self.inner.mul_assign_clifford(&rhs.inner);
}
}
impl Mul<u64> for CliffordString {
type Output = Self;
fn mul(self, rhs: u64) -> Self::Output {
Self {
inner: self
.inner
.repeat(rhs)
.expect("CliffordString repetition should fit platform size"),
}
}
}
impl MulAssign<u64> for CliffordString {
fn mul_assign(&mut self, rhs: u64) {
self.inner
.repeat_assign(rhs)
.expect("CliffordString repetition should fit platform size");
}
}
impl Mul<CliffordString> for u64 {
type Output = CliffordString;
fn mul(self, rhs: CliffordString) -> Self::Output {
rhs * self
}
}
#[cfg(test)]
mod tests {
use super::CliffordString;
use crate::{Circuit, PauliString};
use std::str::FromStr;
#[test]
fn clifford_string_core_examples_match_documented_behavior() {
let c = CliffordString::new(3);
assert_eq!(c.to_string(), "I,I,I");
assert_eq!(format!("{c:?}"), "stim.CliffordString(\"I,I,I\")");
assert_eq!(c.len(), 3);
let s = " X , Y , Z , H_XZ , SQRT_X,C_XYZ, "
.parse::<CliffordString>()
.unwrap();
assert_eq!(s.to_string(), "X,Y,Z,H,SQRT_X,C_XYZ");
assert_eq!(s.get(2).unwrap().name(), "Z");
assert_eq!(s.get(-1).unwrap().name(), "C_XYZ");
assert_eq!(
s.slice(None, Some(-1), 1).unwrap().to_string(),
"X,Y,Z,H,SQRT_X"
);
assert_eq!(s.slice(None, None, 2).unwrap().to_string(), "X,Z,SQRT_X");
let from_pauli = CliffordString::from_pauli_string(&"-XYZ".parse::<PauliString>().unwrap());
assert_eq!(from_pauli.to_string(), "X,Y,Z");
let from_circuit = CliffordString::from_circuit(
&Circuit::from_str(
"
H 0 1 2
S 2 3
TICK
S 3
I 6
",
)
.unwrap(),
)
.unwrap();
assert_eq!(from_circuit.to_string(), "H,H,C_ZYX,Z,I,I,I");
}
#[test]
fn clifford_string_all_cliffords_and_outputs_match_documented_examples() {
let cliffords = CliffordString::all_cliffords_string();
assert_eq!(cliffords.len(), 24);
assert_eq!(
cliffords.slice(Some(0), Some(8), 1).unwrap().to_string(),
"I,X,Y,Z,H_XY,S,S_DAG,H_NXY"
);
assert_eq!(
cliffords.slice(Some(8), Some(16), 1).unwrap().to_string(),
"H,SQRT_Y_DAG,H_NXZ,SQRT_Y,H_YZ,H_NYZ,SQRT_X,SQRT_X_DAG"
);
assert_eq!(
cliffords.slice(Some(16), None, 1).unwrap().to_string(),
"C_XYZ,C_XYNZ,C_NXYZ,C_XNYZ,C_ZYX,C_ZNYX,C_NZYX,C_ZYNX"
);
let (x_paulis, x_signs) = "I,Y,H,S".parse::<CliffordString>().unwrap().x_outputs();
assert_eq!(x_paulis, "+XXZY".parse::<PauliString>().unwrap());
assert_eq!(x_signs, vec![false, true, false, false]);
assert_eq!(
"I,Y,H,S"
.parse::<CliffordString>()
.unwrap()
.x_outputs_bit_packed()
.1,
vec![2]
);
let (y_paulis, y_signs) = "I,X,H,S".parse::<CliffordString>().unwrap().y_outputs();
assert_eq!(y_paulis, "+YYYX".parse::<PauliString>().unwrap());
assert_eq!(y_signs, vec![false, true, true, true]);
assert_eq!(
"I,X,H,S"
.parse::<CliffordString>()
.unwrap()
.y_outputs_bit_packed()
.1,
vec![14]
);
let (z_paulis, z_signs) = "I,Y,H,S".parse::<CliffordString>().unwrap().z_outputs();
assert_eq!(z_paulis, "+ZZXZ".parse::<PauliString>().unwrap());
assert_eq!(z_signs, vec![false, true, false, false]);
assert_eq!(
"I,Y,H,S"
.parse::<CliffordString>()
.unwrap()
.z_outputs_bit_packed()
.1,
vec![2]
);
}
#[test]
fn clifford_string_arithmetic_and_powers_match_documented_examples() {
assert_eq!(
"I,X,H".parse::<CliffordString>().unwrap() + "Y,S".parse::<CliffordString>().unwrap(),
"I,X,H,Y,S".parse::<CliffordString>().unwrap()
);
let mut concat = "I,X,H".parse::<CliffordString>().unwrap();
concat += "Y,S".parse::<CliffordString>().unwrap();
assert_eq!(concat, "I,X,H,Y,S".parse::<CliffordString>().unwrap());
assert_eq!(
"S,X,X".parse::<CliffordString>().unwrap()
* "S,Z,H,Z".parse::<CliffordString>().unwrap(),
"Z,Y,SQRT_Y,Z".parse::<CliffordString>().unwrap()
);
let mut mul_assign = "S,X,X".parse::<CliffordString>().unwrap();
mul_assign *= "S,Z,H,Z".parse::<CliffordString>().unwrap();
assert_eq!(
mul_assign,
"Z,Y,SQRT_Y,Z".parse::<CliffordString>().unwrap()
);
assert_eq!(
"I,X,H".parse::<CliffordString>().unwrap() * 3,
"I,X,H,I,X,H,I,X,H".parse::<CliffordString>().unwrap()
);
assert_eq!(
2 * "I,X,H".parse::<CliffordString>().unwrap(),
"I,X,H,I,X,H".parse::<CliffordString>().unwrap()
);
let mut repeated = "I,X,H".parse::<CliffordString>().unwrap();
repeated *= 2;
assert_eq!(repeated, "I,X,H,I,X,H".parse::<CliffordString>().unwrap());
let mut p = "I,X,H,S,C_XYZ".parse::<CliffordString>().unwrap();
p.ipow(3);
assert_eq!(p, "I,X,H,S_DAG,I".parse::<CliffordString>().unwrap());
p.ipow(2);
assert_eq!(p, "I,I,I,Z,I".parse::<CliffordString>().unwrap());
assert_eq!(p.pow(2), "I,I,I,I,I".parse::<CliffordString>().unwrap());
p.ipow(2);
assert_eq!(p, "I,I,I,I,I".parse::<CliffordString>().unwrap());
}
#[test]
fn clifford_string_set_updates_single_gate_entries() {
let mut s = "I,I,I,I,I".parse::<CliffordString>().unwrap();
s.set(1, "H").unwrap();
assert_eq!(s, "I,H,I,I,I".parse::<CliffordString>().unwrap());
s.set(-1, crate::Gate::S).unwrap();
assert_eq!(s, "I,H,I,I,S".parse::<CliffordString>().unwrap());
}
#[test]
fn clifford_string_remaining_convenience_paths_are_covered() {
let default = CliffordString::default();
assert!(default.is_empty());
let random = CliffordString::random(3);
assert_eq!(random.clone().len(), 3);
let step_error = "I,X"
.parse::<CliffordString>()
.unwrap()
.slice(None, None, 0)
.unwrap_err();
assert!(step_error.message().contains("slice step cannot be zero"));
}
}