diff --git a/crates/pecos-qsim/src/coin_toss.rs b/crates/pecos-qsim/src/coin_toss.rs new file mode 100644 index 000000000..aa12d253e --- /dev/null +++ b/crates/pecos-qsim/src/coin_toss.rs @@ -0,0 +1,430 @@ +// Copyright 2025 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License.You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. + +use super::arbitrary_rotation_gateable::ArbitraryRotationGateable; +use super::clifford_gateable::{CliffordGateable, MeasurementResult}; +use super::quantum_simulator::QuantumSimulator; +use pecos_core::RngManageable; +use pecos_core::errors::PecosError; +use rand_chacha::ChaCha8Rng; + +use core::fmt::Debug; +use rand::{Rng, RngCore, SeedableRng}; + +/// A quantum simulator that ignores all quantum operations and uses coin tosses for measurements +/// +/// `CoinToss` is a minimal simulator that treats all quantum gates as no-ops and returns +/// random measurement results with a configurable probability. This is useful for: +/// - Debugging classical logic paths in quantum algorithms +/// - Testing error correction protocols with random noise +/// - Rapid prototyping where quantum coherence isn't important +/// +/// # Type Parameters +/// * `R` - Random number generator type implementing `RngCore + SeedableRng` traits +/// +/// # Examples +/// ```rust +/// use pecos_qsim::CoinToss; +/// +/// // Create a new 4-qubit coin toss simulator with 50% probability of measuring |1⟩ +/// let mut sim = CoinToss::new(4); +/// +/// // Create with custom probability and seed +/// let mut biased_sim = CoinToss::with_prob_and_seed(4, 0.8, Some(42)); +/// ``` +#[derive(Clone, Debug)] +pub struct CoinToss +where + R: RngCore + SeedableRng + Debug, +{ + num_qubits: usize, + prob: f64, + rng: R, +} + +impl CoinToss { + /// Create a new `CoinToss` simulator with default 50% measurement probability + /// + /// # Arguments + /// * `num_qubits` - Number of qubits in the system + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let mut sim = CoinToss::new(4); + /// ``` + #[must_use] + pub fn new(num_qubits: usize) -> Self { + Self::with_prob_and_seed(num_qubits, 0.5, None) + } + + /// Create a new `CoinToss` simulator with custom probability + /// + /// # Arguments + /// * `num_qubits` - Number of qubits in the system + /// * `prob` - Probability of measuring |1⟩ (must be between 0.0 and 1.0) + /// + /// # Panics + /// Panics if `prob` is not in the range [0.0, 1.0] + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let mut sim = CoinToss::with_prob(4, 0.8); // 80% chance of measuring |1⟩ + /// ``` + #[must_use] + pub fn with_prob(num_qubits: usize, prob: f64) -> Self { + Self::with_prob_and_seed(num_qubits, prob, None) + } + + /// Create a new `CoinToss` simulator with a specific seed + /// + /// # Arguments + /// * `num_qubits` - Number of qubits in the system + /// * `seed` - Optional seed for reproducible randomness + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let mut sim = CoinToss::with_seed(4, Some(42)); + /// ``` + #[must_use] + pub fn with_seed(num_qubits: usize, seed: Option) -> Self { + Self::with_prob_and_seed(num_qubits, 0.5, seed) + } + + /// Create a new `CoinToss` simulator with custom probability and seed + /// + /// # Arguments + /// * `num_qubits` - Number of qubits in the system + /// * `prob` - Probability of measuring |1⟩ (must be between 0.0 and 1.0) + /// * `seed` - Optional seed for reproducible randomness + /// + /// # Panics + /// Panics if `prob` is not in the range [0.0, 1.0] + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let mut sim = CoinToss::with_prob_and_seed(4, 0.3, Some(123)); + /// ``` + #[must_use] + pub fn with_prob_and_seed(num_qubits: usize, prob: f64, seed: Option) -> Self { + assert!( + (0.0..=1.0).contains(&prob), + "Probability must be between 0.0 and 1.0, got {prob}" + ); + + let rng = if let Some(s) = seed { + ChaCha8Rng::seed_from_u64(s) + } else { + // Use a default seed when none provided + let default_seed = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + ChaCha8Rng::seed_from_u64(default_seed) + }; + + Self { + num_qubits, + prob, + rng, + } + } +} + +impl CoinToss +where + R: RngCore + SeedableRng + Debug, +{ + /// Returns the number of qubits in the system + /// + /// # Returns + /// The number of qubits being simulated + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let sim = CoinToss::new(5); + /// assert_eq!(sim.num_qubits(), 5); + /// ``` + #[inline] + #[must_use] + pub fn num_qubits(&self) -> usize { + self.num_qubits + } + + /// Get the current measurement probability + /// + /// # Returns + /// The probability of measuring |1⟩ (between 0.0 and 1.0) + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let sim = CoinToss::with_prob(3, 0.8); + /// assert_eq!(sim.prob(), 0.8); + /// ``` + #[inline] + #[must_use] + pub fn prob(&self) -> f64 { + self.prob + } + + /// Set the measurement probability + /// + /// # Arguments + /// * `prob` - New probability (must be between 0.0 and 1.0) + /// + /// # Panics + /// Panics if `prob` is not in the range [0.0, 1.0] + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let mut sim = CoinToss::new(2); + /// sim.set_prob(0.3); + /// assert_eq!(sim.prob(), 0.3); + /// ``` + pub fn set_prob(&mut self, prob: f64) { + assert!( + (0.0..=1.0).contains(&prob), + "Probability must be between 0.0 and 1.0, got {prob}" + ); + self.prob = prob; + } + + /// Set seed for reproducible randomness + /// + /// This is similar to the Python `CoinToss` interface and `StateVec`'s seed functionality. + /// + /// # Arguments + /// * `seed` - Seed value for the random number generator + /// + /// # Errors + /// + /// Returns an error if the seed cannot be set (currently never fails). + /// + /// # Examples + /// ```rust + /// use pecos_qsim::CoinToss; + /// let mut sim = CoinToss::new(2); + /// sim.set_seed(42); + /// ``` + pub fn set_seed(&mut self, seed: u64) -> Result<(), PecosError> { + let new_rng = R::seed_from_u64(seed); + self.set_rng(new_rng) + } +} + +impl QuantumSimulator for CoinToss +where + R: RngCore + SeedableRng + Debug, +{ + fn reset(&mut self) -> &mut Self { + // CoinToss is stateless, so reset is a no-op + self + } +} + +impl RngManageable for CoinToss +where + R: RngCore + SeedableRng + Debug, +{ + type Rng = R; + + fn set_rng(&mut self, rng: Self::Rng) -> Result<(), PecosError> { + self.rng = rng; + Ok(()) + } + + fn rng(&self) -> &Self::Rng { + &self.rng + } + + fn rng_mut(&mut self) -> &mut Self::Rng { + &mut self.rng + } +} + +impl CliffordGateable for CoinToss +where + R: RngCore + SeedableRng + Debug, +{ + // All quantum gates are no-ops in CoinToss - they all return self for chaining + fn h(&mut self, _qubit: usize) -> &mut Self { + self + } + fn sz(&mut self, _qubit: usize) -> &mut Self { + self + } + fn sx(&mut self, _qubit: usize) -> &mut Self { + self + } + fn sy(&mut self, _qubit: usize) -> &mut Self { + self + } + fn cx(&mut self, _control: usize, _target: usize) -> &mut Self { + self + } + fn cy(&mut self, _control: usize, _target: usize) -> &mut Self { + self + } + fn cz(&mut self, _control: usize, _target: usize) -> &mut Self { + self + } + fn swap(&mut self, _qubit1: usize, _qubit2: usize) -> &mut Self { + self + } + + // Measurement returns random results based on the configured probability + fn mz(&mut self, _qubit: usize) -> MeasurementResult { + MeasurementResult { + outcome: self.rng.random::() < self.prob, + is_deterministic: false, + } + } +} + +impl ArbitraryRotationGateable for CoinToss +where + R: RngCore + SeedableRng + Debug, +{ + // All rotation gates are no-ops in CoinToss - they all return self for chaining + fn rx(&mut self, _theta: f64, _q: usize) -> &mut Self { + self + } + fn rz(&mut self, _theta: f64, _q: usize) -> &mut Self { + self + } + fn rzz(&mut self, _theta: f64, _q1: usize, _q2: usize) -> &mut Self { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_coin_toss() { + let sim = CoinToss::new(4); + assert_eq!(sim.num_qubits(), 4); + assert!((sim.prob() - 0.5).abs() < f64::EPSILON); + } + + #[test] + fn test_with_prob() { + let sim = CoinToss::with_prob(3, 0.8); + assert_eq!(sim.num_qubits(), 3); + assert!((sim.prob() - 0.8).abs() < f64::EPSILON); + } + + #[test] + #[should_panic(expected = "Probability must be between 0.0 and 1.0")] + fn test_invalid_prob_high() { + let _ = CoinToss::with_prob(2, 1.5); + } + + #[test] + #[should_panic(expected = "Probability must be between 0.0 and 1.0")] + fn test_invalid_prob_low() { + let _ = CoinToss::with_prob(2, -0.1); + } + + #[test] + fn test_with_seed_reproducible() { + let mut sim1 = CoinToss::with_seed(2, Some(42)); + let mut sim2 = CoinToss::with_seed(2, Some(42)); + + // Should produce identical sequences with same seed + for _ in 0..10 { + let result1 = sim1.mz(0); + let result2 = sim2.mz(0); + assert_eq!(result1.outcome, result2.outcome); + } + } + + #[test] + fn test_prob_setter() { + let mut sim = CoinToss::new(2); + assert!((sim.prob() - 0.5).abs() < f64::EPSILON); + + sim.set_prob(0.9); + assert!((sim.prob() - 0.9).abs() < f64::EPSILON); + } + + #[test] + fn test_gates_are_noop() { + let mut sim = CoinToss::new(2); + + // All gates should succeed and return self for chaining + sim.h(0).sz(0).cx(0, 1); + // If we get here without panic, gates work as expected + } + + #[test] + fn test_measurements_distribution() { + let mut sim = CoinToss::with_prob_and_seed(1, 0.0, Some(42)); + + // With prob=0.0, should always measure |0⟩ + for _ in 0..100 { + assert!(!sim.mz(0).outcome); + } + + sim.set_prob(1.0); + // With prob=1.0, should always measure |1⟩ + for _ in 0..100 { + assert!(sim.mz(0).outcome); + } + } + + #[test] + fn test_reset_is_noop() { + let mut sim = CoinToss::new(3); + let prob_before = sim.prob(); + sim.reset(); + assert!((sim.prob() - prob_before).abs() < f64::EPSILON); + } + + #[test] + fn test_rotation_gates_are_noop() { + let mut sim = CoinToss::new(2); + + // All rotation gates should succeed and return self for chaining + sim.rx(1.5, 0).ry(0.5, 1).rz(2.1, 0).rzz(0.8, 0, 1); + // If we get here without panic, rotation gates work as expected + } + + #[test] + fn test_num_qubits() { + let sim = CoinToss::new(5); + assert_eq!(sim.num_qubits(), 5); + } + + #[test] + fn test_set_seed() { + let mut sim1 = CoinToss::new(2); + let mut sim2 = CoinToss::new(2); + + sim1.set_seed(123).unwrap(); + sim2.set_seed(123).unwrap(); + + // Should produce identical sequences with same seed + for _ in 0..10 { + let result1 = sim1.mz(0); + let result2 = sim2.mz(0); + assert_eq!(result1.outcome, result2.outcome); + } + } +} diff --git a/crates/pecos-qsim/src/lib.rs b/crates/pecos-qsim/src/lib.rs index ed2768acb..e41883eae 100644 --- a/crates/pecos-qsim/src/lib.rs +++ b/crates/pecos-qsim/src/lib.rs @@ -11,6 +11,7 @@ // the License. pub mod clifford_gateable; +pub mod coin_toss; pub mod gens; pub mod pauli_prop; // pub mod paulis; @@ -23,6 +24,7 @@ pub mod state_vec; pub use arbitrary_rotation_gateable::ArbitraryRotationGateable; pub use clifford_gateable::{CliffordGateable, MeasurementResult}; +pub use coin_toss::CoinToss; pub use gens::Gens; // pub use paulis::Paulis; pub use pauli_prop::{PauliProp, StdPauliProp}; diff --git a/crates/pecos-qsim/src/pauli_prop.rs b/crates/pecos-qsim/src/pauli_prop.rs index aaba97b9b..1c2338dc9 100644 --- a/crates/pecos-qsim/src/pauli_prop.rs +++ b/crates/pecos-qsim/src/pauli_prop.rs @@ -14,6 +14,8 @@ use super::clifford_gateable::{CliffordGateable, MeasurementResult}; use crate::quantum_simulator::QuantumSimulator; use core::marker::PhantomData; use pecos_core::{IndexableElement, Set, VecSet}; +use std::collections::BTreeMap; +use std::fmt; // TODO: Allow for the use of sets of elements of types other than usize @@ -38,8 +40,8 @@ pub type StdPauliProp = PauliProp, usize>; /// - `zs`: Records qubits with Z Pauli operators /// /// Y operators are implicitly represented by qubits present in both sets since Y = iXZ. -/// The sign/phase of the operators is not tracked as it's often not relevant for the -/// intended use cases. +/// +/// Optionally, the sign and phase can be tracked for full Pauli string representation. /// /// # Type Parameters /// - `T`: The set type used to store qubit indices (e.g., `VecSet`\) @@ -70,11 +72,15 @@ where { xs: T, zs: T, + /// Optional tracking of the sign (false = +1, true = -1) + sign: Option, + /// Optional tracking of imaginary phase (0 = 1, 1 = i, 2 = -1, 3 = -i) + img: Option, + /// Maximum qubit index for string representation (optional) + num_qubits: Option, _marker: PhantomData, } -// TODO: Optionally track the sign of the operator - impl Default for PauliProp where E: IndexableElement, @@ -90,21 +96,40 @@ where E: IndexableElement, T: for<'a> Set<'a, Element = E>, { - /// Creates a new `PauliProp` simulator for the specified number of qubits. + /// Creates a new `PauliProp` simulator. /// /// The simulator is initialized with no Pauli operators as the user needs to specify what /// observables to track. /// + /// # Returns + /// A new `PauliProp` instance + #[must_use] + pub fn new() -> Self { + PauliProp { + xs: T::new(), + zs: T::new(), + sign: None, + img: None, + num_qubits: None, + _marker: PhantomData, + } + } + + /// Creates a new `PauliProp` simulator with sign tracking enabled. + /// /// # Arguments - /// * `num_qubits` - The total number of qubits to simulate + /// * `num_qubits` - The total number of qubits (for string representation) /// /// # Returns - /// A new `PauliProp` instance configured for the specified number of qubits + /// A new `PauliProp` instance with sign tracking #[must_use] - pub fn new() -> Self { + pub fn with_sign_tracking(num_qubits: usize) -> Self { PauliProp { xs: T::new(), zs: T::new(), + sign: Some(false), // Start with +1 + img: Some(0), // Start with no imaginary component + num_qubits: Some(num_qubits), _marker: PhantomData, } } @@ -123,6 +148,12 @@ where fn reset(&mut self) -> &mut Self { self.xs.clear(); self.zs.clear(); + if self.sign.is_some() { + self.sign = Some(false); + } + if self.img.is_some() { + self.img = Some(0); + } self } } @@ -217,6 +248,335 @@ where self.add_x(item); self.add_z(item); } + + /// Flips the sign of the Pauli string (if sign tracking is enabled). + #[inline] + pub fn flip_sign(&mut self) { + if let Some(ref mut sign) = self.sign { + *sign = !*sign; + } + } + + /// Adds imaginary factors to the phase (if phase tracking is enabled). + /// + /// # Arguments + /// * `num_is` - Number of i factors to add + pub fn flip_img(&mut self, num_is: usize) { + if let Some(img) = self.img.as_mut() { + // Use modulo 4 on num_is first to ensure it fits in u8 + // Safe to cast since modulo 4 guarantees result is 0-3 + #[allow(clippy::cast_possible_truncation)] + let num_is_mod = (num_is % 4) as u8; + *img = (*img + num_is_mod) % 4; + + // If we've accumulated 2 or 3 i's, flip the sign + let should_flip = *img == 2 || *img == 3; + + *img %= 2; // Keep only 0 or 1 for the imaginary part + + if should_flip { + self.flip_sign(); + } + } + } + + /// Adds Pauli operators from a `BTreeMap` representation. + /// + /// The map should have keys "X", "Y", and "Z" with sets of qubit indices. + /// This method properly handles operator composition with phase tracking if enabled. + /// + /// # Arguments + /// * `paulis` - `BTreeMap` with "X", "Y", "Z" keys mapping to sets of qubit indices + /// + /// # Example + /// ```rust + /// use std::collections::BTreeMap; + /// use pecos_qsim::StdPauliProp; + /// use pecos_core::{VecSet, Set}; + /// + /// let mut sim = StdPauliProp::with_sign_tracking(4); + /// let mut paulis = BTreeMap::new(); + /// let mut x_set = VecSet::new(); + /// x_set.insert(0); + /// x_set.insert(1); + /// paulis.insert("X".to_string(), x_set); + /// sim.add_paulis(&paulis); + /// ``` + pub fn add_paulis(&mut self, paulis: &BTreeMap) + where + T: Clone, + E: Copy, + { + // Handle X operators + if let Some(x_set) = paulis.get("X") { + for &item in x_set.iter() { + let was_y = self.contains_y(item); + let was_z = self.contains_z(item) && !was_y; + + self.add_x(item); + + if self.sign.is_some() { + if was_y { + // Y·X = -iZ (applying X after Y) + self.flip_img(1); + self.flip_sign(); + } else if was_z { + // Z·X = iY (applying X after Z) + self.flip_img(1); + } + } + } + } + + // Handle Z operators + if let Some(z_set) = paulis.get("Z") { + for &item in z_set.iter() { + let was_y = self.contains_y(item); + let was_x = self.contains_x(item) && !was_y; + + self.add_z(item); + + if self.sign.is_some() { + if was_x { + // X·Z = -iY (applying Z after X) + self.flip_img(1); + self.flip_sign(); + } else if was_y { + // Y·Z = iX (applying Z after Y) + self.flip_img(1); + } + } + } + } + + // Handle Y operators + if let Some(y_set) = paulis.get("Y") { + for &item in y_set.iter() { + let was_x = self.contains_x(item) && !self.contains_z(item); + let was_z = self.contains_z(item) && !self.contains_x(item); + + self.add_y(item); + + if self.sign.is_some() { + if was_z { + // Z·Y = -iX (applying Y after Z) + self.flip_img(1); + self.flip_sign(); + } else if was_x { + // X·Y = iZ (applying Y after X) + self.flip_img(1); + } + } + } + } + } + + /// Calculates the weight of the Pauli string (number of non-identity operators). + /// + /// # Returns + /// The total number of qubits with non-identity Pauli operators + pub fn weight(&self) -> usize { + // Count X-only qubits + let mut count = 0; + for item in self.xs.iter() { + if !self.zs.contains(item) { + count += 1; + } + } + + // Count Z-only qubits + for item in self.zs.iter() { + if !self.xs.contains(item) { + count += 1; + } + } + + // Count Y qubits (both X and Z) + for item in self.xs.iter() { + if self.zs.contains(item) { + count += 1; + } + } + + count + } + + /// Checks if this is the identity operator (no Pauli operators on any qubit). + /// + /// # Returns + /// true if there are no X, Y, or Z operators on any qubit + pub fn is_identity(&self) -> bool { + self.xs.is_empty() && self.zs.is_empty() + } + + /// Gets the sign as a boolean (false for +, true for -). + /// + /// # Returns + /// false for positive sign, true for negative sign + pub fn get_sign(&self) -> bool { + self.sign.unwrap_or(false) + } + + /// Gets the imaginary component (0 for real, 1 for imaginary). + /// + /// # Returns + /// 0 for real, 1 for imaginary + pub fn get_img(&self) -> u8 { + self.img.unwrap_or(0) + } + + /// Returns the sign string representation. + /// + /// # Returns + /// A string like "+", "-", "+i", or "-i" depending on the phase + pub fn sign_string(&self) -> String { + match (self.sign, self.img) { + (Some(false), Some(0) | None) => "+".to_string(), + (Some(true), Some(0) | None) => "-".to_string(), + (Some(false), Some(1)) => "+i".to_string(), + (Some(true), Some(1)) => "-i".to_string(), + _ => String::new(), + } + } + + /// Returns the operator string representation for sparse format. + /// + /// # Returns + /// A string like "`X_0` `Z_2` `Y_3`" representing non-identity operators + pub fn sparse_string(&self) -> String + where + E: Copy, + { + let mut entries = Vec::new(); + + // Collect all qubit indices with operators + for &item in self.xs.iter() { + if self.contains_y(item) { + entries.push((item, 'Y')); + } else { + entries.push((item, 'X')); + } + } + + for &item in self.zs.iter() { + if !self.xs.contains(&item) { + entries.push((item, 'Z')); + } + } + + if entries.is_empty() { + "I".to_string() + } else { + // Format as sparse representation + entries + .iter() + .map(|(idx, op)| format!("{op}{idx:?}")) + .collect::>() + .join(" ") + } + } + + /// Returns the full Pauli string representation with sign and operators. + /// + /// # Returns + /// A string like "+`X_0` `Z_2`" in sparse format + pub fn to_pauli_string(&self) -> String + where + E: Copy, + { + format!("{}{}", self.sign_string(), self.sparse_string()) + } +} + +// Specialized implementation for StdPauliProp (usize indices) +impl StdPauliProp { + /// Get all qubits with X operators (including those with Y) + #[must_use] + pub fn get_x_qubits(&self) -> Vec { + self.xs.iter().copied().collect() + } + + /// Get all qubits with Z operators (including those with Y) + #[must_use] + pub fn get_z_qubits(&self) -> Vec { + self.zs.iter().copied().collect() + } + + /// Get all qubits with only X operators (not Y) + #[must_use] + pub fn get_x_only_qubits(&self) -> Vec { + self.xs + .iter() + .filter(|&q| !self.contains_z(*q)) + .copied() + .collect() + } + + /// Get all qubits with only Z operators (not Y) + #[must_use] + pub fn get_z_only_qubits(&self) -> Vec { + self.zs + .iter() + .filter(|&q| !self.contains_x(*q)) + .copied() + .collect() + } + + /// Get all qubits with Y operators (both X and Z) + #[must_use] + pub fn get_y_qubits(&self) -> Vec { + self.xs + .iter() + .filter(|&q| self.contains_z(*q)) + .copied() + .collect() + } + + /// Returns the operator string as a dense representation. + /// + /// Requires `num_qubits` to be set. + /// + /// # Returns + /// A string like "IXYZ" representing the Pauli operators on each qubit + #[must_use] + pub fn dense_string(&self) -> String { + if let Some(n) = self.num_qubits { + let mut result = String::with_capacity(n); + for i in 0..n { + if self.contains_y(i) { + result.push('Y'); + } else if self.contains_x(i) { + result.push('X'); + } else if self.contains_z(i) { + result.push('Z'); + } else { + result.push('I'); + } + } + result + } else { + self.sparse_string() + } + } + + /// Returns the full dense Pauli string with sign. + /// + /// # Returns + /// A string like "+IXYZ" or "-iXYZ" + #[must_use] + pub fn to_dense_string(&self) -> String { + format!("{}{}", self.sign_string(), self.dense_string()) + } +} + +impl fmt::Display for PauliProp +where + T: for<'a> Set<'a, Element = E>, + E: IndexableElement + Copy, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_pauli_string()) + } } impl CliffordGateable for PauliProp @@ -340,3 +700,113 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + #[test] + fn test_sign_tracking() { + let mut sim = StdPauliProp::with_sign_tracking(4); + + // Initially should be + + assert_eq!(sim.sign_string(), "+"); + + // Flip sign + sim.flip_sign(); + assert_eq!(sim.sign_string(), "-"); + + // Add imaginary phase + sim.flip_sign(); // Back to + + sim.flip_img(1); + assert_eq!(sim.sign_string(), "+i"); + + // Two i's should give -1 + sim.flip_img(1); + assert_eq!(sim.sign_string(), "-"); + } + + #[test] + fn test_weight() { + let mut sim = StdPauliProp::new(); + + // Empty should have weight 0 + assert_eq!(sim.weight(), 0); + + // Add X on qubit 0 + sim.add_x(0); + assert_eq!(sim.weight(), 1); + + // Add Z on qubit 1 + sim.add_z(1); + assert_eq!(sim.weight(), 2); + + // Add Y on qubit 2 (both X and Z) + sim.add_y(2); + assert_eq!(sim.weight(), 3); + + // Adding X to qubit with Z makes Y + sim.add_x(1); + assert_eq!(sim.weight(), 3); // Still 3 operators + } + + #[test] + fn test_dense_string() { + let mut sim = StdPauliProp::with_sign_tracking(4); + + sim.add_x(0); + sim.add_z(2); + sim.add_y(3); + + assert_eq!(sim.dense_string(), "XIZY"); + assert_eq!(sim.to_dense_string(), "+XIZY"); + + sim.flip_sign(); + assert_eq!(sim.to_dense_string(), "-XIZY"); + } + + #[test] + fn test_add_paulis() { + let mut sim = StdPauliProp::with_sign_tracking(4); + + let mut paulis = BTreeMap::new(); + let mut x_set = VecSet::new(); + x_set.insert(0); + x_set.insert(1); + + let mut z_set = VecSet::new(); + z_set.insert(2); + + paulis.insert("X".to_string(), x_set); + paulis.insert("Z".to_string(), z_set); + + sim.add_paulis(&paulis); + + assert!(sim.contains_x(0)); + assert!(sim.contains_x(1)); + assert!(sim.contains_z(2)); + assert_eq!(sim.weight(), 3); + } + + #[test] + fn test_pauli_composition_with_phase() { + let mut sim = StdPauliProp::with_sign_tracking(2); + + // Start with X on qubit 0 + sim.add_x(0); + + // Add Z to same qubit: X·Z = -iY (applying Z after X) + let mut paulis = BTreeMap::new(); + let mut z_set = VecSet::new(); + z_set.insert(0); + paulis.insert("Z".to_string(), z_set); + + sim.add_paulis(&paulis); + + // Should now have Y on qubit 0 + assert!(sim.contains_y(0)); + // Phase should be -i (X·Z = -iY) + assert_eq!(sim.sign_string(), "-i"); + } +} diff --git a/python/docs/install.rst b/python/docs/install.rst index 1b241c83f..346621f62 100644 --- a/python/docs/install.rst +++ b/python/docs/install.rst @@ -20,7 +20,6 @@ Optional packages include: * Cython (for compiling C and C++ extensions) * PyTest (for running tests) -* ProjectQ (to take advantage of ProjectQ's simulators via PECOS) Note on Python Distribution/Environment diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.bindings.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.bindings.rst deleted file mode 100644 index e878daf54..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.bindings.rst +++ /dev/null @@ -1,23 +0,0 @@ -pecos.simulators.basic\_sv.bindings -=================================== - -.. automodule:: pecos.simulators.basic_sv.bindings - - - - - - - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_init.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_init.rst deleted file mode 100644 index 2968846ca..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_init.rst +++ /dev/null @@ -1,30 +0,0 @@ -pecos.simulators.basic\_sv.gates\_init -====================================== - -.. automodule:: pecos.simulators.basic_sv.gates_init - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - init_one - init_zero - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_meas.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_meas.rst deleted file mode 100644 index 7c528f93b..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_meas.rst +++ /dev/null @@ -1,29 +0,0 @@ -pecos.simulators.basic\_sv.gates\_meas -====================================== - -.. automodule:: pecos.simulators.basic_sv.gates_meas - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - meas_z - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_one_qubit.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_one_qubit.rst deleted file mode 100644 index 7ebe841fa..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_one_qubit.rst +++ /dev/null @@ -1,58 +0,0 @@ -pecos.simulators.basic\_sv.gates\_one\_qubit -============================================ - -.. automodule:: pecos.simulators.basic_sv.gates_one_qubit - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - F - F2 - F2d - F3 - F3d - F4 - F4d - Fdg - H - H2 - H3 - H4 - H5 - H6 - R1XY - RX - RY - RZ - SX - SXdg - SY - SYdg - SZ - SZdg - T - Tdg - X - Y - Z - identity - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_two_qubit.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_two_qubit.rst deleted file mode 100644 index 7320b63fa..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.gates_two_qubit.rst +++ /dev/null @@ -1,43 +0,0 @@ -pecos.simulators.basic\_sv.gates\_two\_qubit -============================================ - -.. automodule:: pecos.simulators.basic_sv.gates_two_qubit - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - CX - CY - CZ - G - R2XXYYZZ - RXX - RYY - RZZ - SWAP - SXX - SXXdg - SYY - SYYdg - SZZ - SZZdg - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.rst deleted file mode 100644 index 23181e07f..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.rst +++ /dev/null @@ -1,36 +0,0 @@ -pecos.simulators.basic\_sv -========================== - -.. automodule:: pecos.simulators.basic_sv - - - - - - - - - - - - - - - - - - - -.. rubric:: Modules - -.. autosummary:: - :toctree: - :recursive: - - pecos.simulators.basic_sv.bindings - pecos.simulators.basic_sv.gates_init - pecos.simulators.basic_sv.gates_meas - pecos.simulators.basic_sv.gates_one_qubit - pecos.simulators.basic_sv.gates_two_qubit - pecos.simulators.basic_sv.state - diff --git a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.state.rst b/python/docs/reference/_autosummary/pecos.simulators.basic_sv.state.rst deleted file mode 100644 index d57062441..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.basic_sv.state.rst +++ /dev/null @@ -1,29 +0,0 @@ -pecos.simulators.basic\_sv.state -================================ - -.. automodule:: pecos.simulators.basic_sv.state - - - - - - - - - - - - .. rubric:: Classes - - .. autosummary:: - - BasicSV - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.bindings.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.bindings.rst deleted file mode 100644 index cc83cedfe..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.bindings.rst +++ /dev/null @@ -1,23 +0,0 @@ -pecos.simulators.projectq.bindings -================================== - -.. automodule:: pecos.simulators.projectq.bindings - - - - - - - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_init.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_init.rst deleted file mode 100644 index 4d5f6f885..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_init.rst +++ /dev/null @@ -1,34 +0,0 @@ -pecos.simulators.projectq.gates\_init -===================================== - -.. automodule:: pecos.simulators.projectq.gates_init - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - init_minus - init_minusi - init_one - init_plus - init_plusi - init_zero - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_meas.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_meas.rst deleted file mode 100644 index d18db2f6b..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_meas.rst +++ /dev/null @@ -1,32 +0,0 @@ -pecos.simulators.projectq.gates\_meas -===================================== - -.. automodule:: pecos.simulators.projectq.gates_meas - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - force_output - meas_x - meas_y - meas_z - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_one_qubit.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_one_qubit.rst deleted file mode 100644 index cedf45198..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_one_qubit.rst +++ /dev/null @@ -1,53 +0,0 @@ -pecos.simulators.projectq.gates\_one\_qubit -=========================================== - -.. automodule:: pecos.simulators.projectq.gates_one_qubit - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - F - F2 - F2dg - F3 - F3dg - F4 - F4dg - Fdg - H - H2 - H3 - H4 - H5 - H6 - Identity - R1XY - SX - SXdg - SY - SYdg - SZ - SZdg - X - Y - Z - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_two_qubit.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_two_qubit.rst deleted file mode 100644 index b15ee4854..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.gates_two_qubit.rst +++ /dev/null @@ -1,44 +0,0 @@ -pecos.simulators.projectq.gates\_two\_qubit -=========================================== - -.. automodule:: pecos.simulators.projectq.gates_two_qubit - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - CNOT - CY - CZ - G2 - II - R2XXYYZZ - RXX - RYY - RZZ - SWAP - SXX - SXXdg - SYY - SYYdg - SZZ - SZZdg - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.helper.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.helper.rst deleted file mode 100644 index 948f8baf8..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.helper.rst +++ /dev/null @@ -1,29 +0,0 @@ -pecos.simulators.projectq.helper -================================ - -.. automodule:: pecos.simulators.projectq.helper - - - - - - - - - - - - .. rubric:: Classes - - .. autosummary:: - - MakeFunc - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.logical_sign.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.logical_sign.rst deleted file mode 100644 index 0e43c3cab..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.logical_sign.rst +++ /dev/null @@ -1,29 +0,0 @@ -pecos.simulators.projectq.logical\_sign -======================================= - -.. automodule:: pecos.simulators.projectq.logical_sign - - - - - - - - .. rubric:: Functions - - .. autosummary:: - - find_logical_signs - - - - - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.rst deleted file mode 100644 index ed4cce0fb..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.rst +++ /dev/null @@ -1,38 +0,0 @@ -pecos.simulators.projectq -========================= - -.. automodule:: pecos.simulators.projectq - - - - - - - - - - - - - - - - - - - -.. rubric:: Modules - -.. autosummary:: - :toctree: - :recursive: - - pecos.simulators.projectq.bindings - pecos.simulators.projectq.gates_init - pecos.simulators.projectq.gates_meas - pecos.simulators.projectq.gates_one_qubit - pecos.simulators.projectq.gates_two_qubit - pecos.simulators.projectq.helper - pecos.simulators.projectq.logical_sign - pecos.simulators.projectq.state - diff --git a/python/docs/reference/_autosummary/pecos.simulators.projectq.state.rst b/python/docs/reference/_autosummary/pecos.simulators.projectq.state.rst deleted file mode 100644 index 1300fe233..000000000 --- a/python/docs/reference/_autosummary/pecos.simulators.projectq.state.rst +++ /dev/null @@ -1,29 +0,0 @@ -pecos.simulators.projectq.state -=============================== - -.. automodule:: pecos.simulators.projectq.state - - - - - - - - - - - - .. rubric:: Classes - - .. autosummary:: - - ProjectQSim - - - - - - - - - diff --git a/python/docs/reference/_autosummary/pecos.simulators.rst b/python/docs/reference/_autosummary/pecos.simulators.rst index b33b3ab09..503d2415e 100644 --- a/python/docs/reference/_autosummary/pecos.simulators.rst +++ b/python/docs/reference/_autosummary/pecos.simulators.rst @@ -27,14 +27,12 @@ :toctree: :recursive: - pecos.simulators.basic_sv pecos.simulators.cointoss pecos.simulators.compile_cython pecos.simulators.gate_syms pecos.simulators.mps_pytket pecos.simulators.parent_sim_classes pecos.simulators.paulifaultprop - pecos.simulators.projectq pecos.simulators.quantum_simulator pecos.simulators.qulacs pecos.simulators.sim_class_types diff --git a/python/pecos-rslib/rust/src/coin_toss_bindings.rs b/python/pecos-rslib/rust/src/coin_toss_bindings.rs new file mode 100644 index 000000000..1d3e0c1b1 --- /dev/null +++ b/python/pecos-rslib/rust/src/coin_toss_bindings.rs @@ -0,0 +1,179 @@ +// Copyright 2025 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License.You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. + +use pecos::prelude::*; +use pecos_qsim::CoinToss; +use pyo3::prelude::*; +use pyo3::types::PyDict; + +/// The struct represents the coin toss simulator exposed to Python +/// +/// This simulator ignores all quantum gates and returns random measurement results +/// based on a configurable probability. It's useful for debugging classical logic +/// paths and testing error correction protocols with random noise. +#[pyclass(name = "CoinToss")] +pub struct RsCoinToss { + inner: CoinToss, +} + +#[pymethods] +impl RsCoinToss { + /// Creates a new coin toss simulator with the specified number of qubits + /// + /// # Arguments + /// * `num_qubits` - Number of qubits in the system + /// * `prob` - Probability of measuring |1⟩ (default: 0.5) + /// * `seed` - Optional seed for the random number generator + #[new] + #[pyo3(signature = (num_qubits, prob=0.5, seed=None))] + pub fn new(num_qubits: usize, prob: f64, seed: Option) -> PyResult { + if !(0.0..=1.0).contains(&prob) { + return Err(PyErr::new::(format!( + "Probability must be between 0.0 and 1.0, got {prob}" + ))); + } + + let inner = match seed { + Some(s) => CoinToss::with_prob_and_seed(num_qubits, prob, Some(s)), + None => CoinToss::with_prob(num_qubits, prob), + }; + + Ok(RsCoinToss { inner }) + } + + /// Resets the simulator (no-op for coin toss, but maintains interface compatibility) + fn reset(&mut self) { + self.inner.reset(); + } + + /// Returns the number of qubits in the system + #[getter] + fn num_qubits(&self) -> usize { + self.inner.num_qubits() + } + + /// Gets the current measurement probability + #[getter] + fn prob(&self) -> f64 { + self.inner.prob() + } + + /// Sets the measurement probability + /// + /// # Arguments + /// * `prob` - New probability (must be between 0.0 and 1.0) + #[setter] + fn set_prob(&mut self, prob: f64) -> PyResult<()> { + if !(0.0..=1.0).contains(&prob) { + return Err(PyErr::new::(format!( + "Probability must be between 0.0 and 1.0, got {prob}" + ))); + } + self.inner.set_prob(prob); + Ok(()) + } + + /// Sets the seed for reproducible randomness + /// + /// # Arguments + /// * `seed` - Seed value for the random number generator + fn set_seed(&mut self, seed: u64) -> PyResult<()> { + self.inner.set_seed(seed).map_err(|e| { + PyErr::new::(format!("Failed to set seed: {e}")) + }) + } + + /// Executes a single-qubit gate based on the provided symbol and location + /// + /// All gates are no-ops in the coin toss simulator. + /// + /// # Arguments + /// * `symbol` - The gate symbol (e.g., "X", "H", "Z") - ignored + /// * `location` - The qubit index to apply the gate to - ignored + /// * `params` - Optional parameters for parameterized gates - ignored + /// + /// # Returns + /// Always returns an empty dictionary since all gates are no-ops + #[allow(clippy::unused_self)] + fn run_gate_1( + &mut self, + _symbol: &str, + _location: usize, + _params: Option, + ) -> PyResult { + // All gates are no-ops in coin toss simulator + Python::with_gil(|py| Ok(PyDict::new(py).into())) + } + + /// Executes a two-qubit gate based on the provided symbol and locations + /// + /// All gates are no-ops in the coin toss simulator. + /// + /// # Arguments + /// * `symbol` - The gate symbol (e.g., "CX", "CZ", "SWAP") - ignored + /// * `location_1` - First qubit index - ignored + /// * `location_2` - Second qubit index - ignored + /// * `params` - Optional parameters for parameterized gates - ignored + /// + /// # Returns + /// Always returns an empty dictionary since all gates are no-ops + #[allow(clippy::unused_self)] + fn run_gate_2( + &mut self, + _symbol: &str, + _location_1: usize, + _location_2: usize, + _params: Option, + ) -> PyResult { + // All gates are no-ops in coin toss simulator + Python::with_gil(|py| Ok(PyDict::new(py).into())) + } + + /// Performs a measurement in the Z basis + /// + /// Returns a random result (0 or 1) based on the configured probability. + /// + /// # Arguments + /// * `location` - The qubit index to measure (ignored - result is always random) + /// + /// # Returns + /// Dictionary containing the measurement result: {location: outcome} + /// where outcome is 0 or 1 based on the probability + fn run_measure(&mut self, location: usize) -> PyResult { + let result = self.inner.mz(location); + let outcome = i32::from(result.outcome); + + Python::with_gil(|py| { + let dict = PyDict::new(py); + dict.set_item(location, outcome)?; + Ok(dict.into()) + }) + } + + /// String representation of the simulator + fn __repr__(&self) -> String { + format!( + "CoinToss(num_qubits={}, prob={})", + self.inner.num_qubits(), + self.inner.prob() + ) + } + + /// String representation of the simulator + fn __str__(&self) -> String { + format!( + "CoinToss simulator with {} qubits, P(|1⟩) = {:.3}", + self.inner.num_qubits(), + self.inner.prob() + ) + } +} diff --git a/python/pecos-rslib/rust/src/lib.rs b/python/pecos-rslib/rust/src/lib.rs index 1a28582ea..20693cb47 100644 --- a/python/pecos-rslib/rust/src/lib.rs +++ b/python/pecos-rslib/rust/src/lib.rs @@ -17,9 +17,11 @@ // the License. mod byte_message_bindings; +mod coin_toss_bindings; mod cpp_sparse_sim_bindings; mod engine_bindings; mod noise_helpers; +mod pauli_prop_bindings; // mod pcg_bindings; mod pecos_rng_bindings; pub mod phir_bridge; @@ -31,7 +33,9 @@ mod state_vec_bindings; mod state_vec_engine_bindings; use byte_message_bindings::{PyByteMessage, PyByteMessageBuilder}; +use coin_toss_bindings::RsCoinToss; use cpp_sparse_sim_bindings::CppSparseSim; +use pauli_prop_bindings::PyPauliProp; use pecos_rng_bindings::RngPcg; use pyo3::prelude::*; use sparse_stab_bindings::SparseSim; @@ -46,6 +50,8 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/python/pecos-rslib/rust/src/pauli_prop_bindings.rs b/python/pecos-rslib/rust/src/pauli_prop_bindings.rs new file mode 100644 index 000000000..d940c5983 --- /dev/null +++ b/python/pecos-rslib/rust/src/pauli_prop_bindings.rs @@ -0,0 +1,260 @@ +// Copyright 2025 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License.You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. + +use pecos_core::{Set, VecSet}; +use pecos_qsim::{CliffordGateable, QuantumSimulator, StdPauliProp}; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PySet}; +use std::collections::BTreeMap; + +/// Python wrapper for the Rust `PauliProp` simulator +/// +/// This simulator tracks how Pauli operators propagate through Clifford circuits. +/// It's particularly useful for fault propagation and stabilizer simulations. +#[pyclass(name = "PauliProp")] +pub struct PyPauliProp { + inner: StdPauliProp, +} + +#[pymethods] +impl PyPauliProp { + /// Create a new `PauliProp` simulator + /// + /// Args: + /// `num_qubits`: Optional number of qubits (for string representation) + /// `track_sign`: Whether to track sign and phase + #[new] + #[pyo3(signature = (num_qubits=None, track_sign=false))] + pub fn new(num_qubits: Option, track_sign: bool) -> Self { + let inner = if track_sign { + if let Some(n) = num_qubits { + StdPauliProp::with_sign_tracking(n) + } else { + // Default to tracking with 0 qubits if not specified + StdPauliProp::with_sign_tracking(0) + } + } else { + StdPauliProp::new() + }; + + PyPauliProp { inner } + } + + /// Reset the simulator state + pub fn reset(&mut self) { + self.inner.reset(); + } + + /// Check if a qubit has an X operator + pub fn contains_x(&self, qubit: usize) -> bool { + self.inner.contains_x(qubit) + } + + /// Check if a qubit has a Z operator + pub fn contains_z(&self, qubit: usize) -> bool { + self.inner.contains_z(qubit) + } + + /// Check if a qubit has a Y operator + pub fn contains_y(&self, qubit: usize) -> bool { + self.inner.contains_y(qubit) + } + + /// Add an X operator to a qubit + pub fn add_x(&mut self, qubit: usize) { + self.inner.add_x(qubit); + } + + /// Add a Z operator to a qubit + pub fn add_z(&mut self, qubit: usize) { + self.inner.add_z(qubit); + } + + /// Add a Y operator to a qubit + pub fn add_y(&mut self, qubit: usize) { + self.inner.add_y(qubit); + } + + /// Flip the sign of the Pauli string + pub fn flip_sign(&mut self) { + self.inner.flip_sign(); + } + + /// Add imaginary factors + pub fn flip_img(&mut self, num_is: usize) { + self.inner.flip_img(num_is); + } + + /// Add Pauli operators from a dictionary + /// + /// Args: + /// paulis: Dictionary with keys "X", "Y", "Z" mapping to sets of qubit indices + pub fn add_paulis(&mut self, paulis: &Bound<'_, PyDict>) -> PyResult<()> { + let mut btree_map = BTreeMap::new(); + + // Convert Python dict to BTreeMap> + for (key, value) in paulis.iter() { + let key_str: String = key.extract()?; + + if let Ok(py_set) = value.downcast::() { + let mut vec_set = VecSet::new(); + for item in py_set.iter() { + let qubit: usize = item.extract()?; + vec_set.insert(qubit); + } + btree_map.insert(key_str, vec_set); + } else { + // Try to handle it as a Python set-like object + let iter = value.call_method0("__iter__")?; + let mut vec_set = VecSet::new(); + while let Ok(item) = iter.call_method0("__next__") { + let qubit: usize = item.extract()?; + vec_set.insert(qubit); + } + btree_map.insert(key_str, vec_set); + } + } + + self.inner.add_paulis(&btree_map); + Ok(()) + } + + /// Get the weight of the Pauli string (number of non-identity operators) + pub fn weight(&self) -> usize { + self.inner.weight() + } + + /// Get the sign string representation + pub fn sign_string(&self) -> String { + self.inner.sign_string() + } + + /// Get the sparse string representation + pub fn sparse_string(&self) -> String { + self.inner.sparse_string() + } + + /// Get the dense string representation (for `StdPauliProp`) + pub fn dense_string(&self) -> String { + self.inner.dense_string() + } + + /// Get the full Pauli string with sign + pub fn to_pauli_string(&self) -> String { + self.inner.to_pauli_string() + } + + /// Get the full dense Pauli string with sign + pub fn to_dense_string(&self) -> String { + self.inner.to_dense_string() + } + + // Clifford gates + + /// Apply Hadamard gate + pub fn h(&mut self, qubit: usize) { + self.inner.h(qubit); + } + + /// Apply S gate (sqrt(Z)) + pub fn sz(&mut self, qubit: usize) { + self.inner.sz(qubit); + } + + /// Apply sqrt(X) gate + pub fn sx(&mut self, qubit: usize) { + self.inner.sx(qubit); + } + + /// Apply sqrt(Y) gate + pub fn sy(&mut self, qubit: usize) { + self.inner.sy(qubit); + } + + /// Apply CNOT/CX gate + pub fn cx(&mut self, control: usize, target: usize) { + self.inner.cx(control, target); + } + + /// Apply CY gate + pub fn cy(&mut self, control: usize, target: usize) { + self.inner.cy(control, target); + } + + /// Apply CZ gate + pub fn cz(&mut self, control: usize, target: usize) { + self.inner.cz(control, target); + } + + /// Apply SWAP gate + pub fn swap(&mut self, q1: usize, q2: usize) { + self.inner.swap(q1, q2); + } + + /// Measure in Z basis + pub fn mz(&mut self, qubit: usize) -> bool { + self.inner.mz(qubit).outcome + } + + /// Check if this is the identity operator + pub fn is_identity(&self) -> bool { + self.inner.is_identity() + } + + /// Get the sign as a boolean (false for +, true for -) + pub fn get_sign(&self) -> bool { + self.inner.get_sign() + } + + /// Get the imaginary component (0 for real, 1 for imaginary) + pub fn get_img(&self) -> u8 { + self.inner.get_img() + } + + /// Get all faults as a dictionary (compatible with Python `PauliFaultProp`) + pub fn get_faults(&self, py: Python<'_>) -> PyResult { + let dict = PyDict::new(py); + + // Get X-only qubits + let x_set = PySet::empty(py)?; + for qubit in self.inner.get_x_only_qubits() { + x_set.add(qubit)?; + } + dict.set_item("X", x_set)?; + + // Get Y qubits + let y_set = PySet::empty(py)?; + for qubit in self.inner.get_y_qubits() { + y_set.add(qubit)?; + } + dict.set_item("Y", y_set)?; + + // Get Z-only qubits + let z_set = PySet::empty(py)?; + for qubit in self.inner.get_z_only_qubits() { + z_set.add(qubit)?; + } + dict.set_item("Z", z_set)?; + + Ok(dict.into()) + } + + /// String representation + fn __repr__(&self) -> String { + format!("PauliProp({})", self.inner.to_pauli_string()) + } + + /// String representation + fn __str__(&self) -> String { + self.inner.to_pauli_string() + } +} diff --git a/python/pecos-rslib/rust/src/state_vec_bindings.rs b/python/pecos-rslib/rust/src/state_vec_bindings.rs index adddb04c9..ff395d083 100644 --- a/python/pecos-rslib/rust/src/state_vec_bindings.rs +++ b/python/pecos-rslib/rust/src/state_vec_bindings.rs @@ -23,10 +23,18 @@ pub struct RsStateVec { #[pymethods] impl RsStateVec { /// Creates a new state-vector simulator with the specified number of qubits + /// + /// # Arguments + /// * `num_qubits` - Number of qubits in the system + /// * `seed` - Optional seed for the random number generator #[new] - pub fn new(num_qubits: usize) -> Self { + #[pyo3(signature = (num_qubits, seed=None))] + pub fn new(num_qubits: usize, seed: Option) -> Self { RsStateVec { - inner: StateVec::new(num_qubits), + inner: match seed { + Some(s) => StateVec::with_seed(num_qubits, s), + None => StateVec::new(num_qubits), + }, } } diff --git a/python/pecos-rslib/src/pecos_rslib/__init__.py b/python/pecos-rslib/src/pecos_rslib/__init__.py index a586061ee..e61c39487 100644 --- a/python/pecos-rslib/src/pecos_rslib/__init__.py +++ b/python/pecos-rslib/src/pecos_rslib/__init__.py @@ -21,6 +21,8 @@ from pecos_rslib.rssparse_sim import SparseSimRs from pecos_rslib.cppsparse_sim import CppSparseSimRs from pecos_rslib.rsstate_vec import StateVecRs +from pecos_rslib.rscoin_toss import CoinToss +from pecos_rslib.rspauli_prop import PauliPropRs from pecos_rslib._pecos_rslib import ByteMessage from pecos_rslib._pecos_rslib import ByteMessageBuilder from pecos_rslib._pecos_rslib import StateVecEngineRs @@ -63,6 +65,8 @@ "SparseSimRs", "CppSparseSimRs", "StateVecRs", + "CoinToss", + "PauliPropRs", "ByteMessage", "ByteMessageBuilder", "StateVecEngineRs", diff --git a/python/pecos-rslib/src/pecos_rslib/rscoin_toss.py b/python/pecos-rslib/src/pecos_rslib/rscoin_toss.py new file mode 100644 index 000000000..7b357266f --- /dev/null +++ b/python/pecos-rslib/src/pecos_rslib/rscoin_toss.py @@ -0,0 +1,250 @@ +# Copyright 2025 The PECOS Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License.You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +"""Rust-based coin toss simulator for PECOS. + +This module provides a Python interface to the high-performance Rust implementation of a coin toss quantum simulator. +The simulator ignores all quantum gates and returns random measurement results based on a configurable probability, +making it useful for debugging classical logic paths and testing error correction protocols with random noise. +""" + +# ruff: noqa: SLF001 + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pecos_rslib._pecos_rslib import CoinToss as RustCoinToss + +if TYPE_CHECKING: + from pecos.typing import SimulatorGateParams + + +class CoinToss: + """Rust-based coin toss quantum simulator. + + A high-performance coin toss simulator implemented in Rust that ignores all quantum operations and uses + coin tosses for measurements. This is useful for debugging classical branches in quantum algorithms + and testing error correction protocols with random noise. + """ + + def __init__(self, num_qubits: int, prob: float = 0.5, seed: int | None = None): + """ + Initializes the Rust-backed coin toss simulator. + + Args: + num_qubits (int): The number of qubits in the quantum system. + prob (float): Probability of measuring |1⟩ (default: 0.5). + seed (int | None): Optional seed for the random number generator. + """ + self._sim = RustCoinToss(num_qubits, prob, seed) + self.num_qubits = num_qubits + self.bindings = dict(gate_dict) + + @property + def prob(self) -> float: + """Get the current measurement probability.""" + return self._sim.prob + + @prob.setter + def prob(self, value: float) -> None: + """Set the measurement probability.""" + self._sim.prob = value + + def reset(self) -> CoinToss: + """ + Reset the simulator (no-op for coin toss, but maintains interface compatibility). + + Returns: + CoinToss: Returns self for method chaining. + """ + self._sim.reset() + return self + + def set_seed(self, seed: int) -> None: + """ + Set the seed for reproducible randomness. + + Args: + seed (int): Seed value for the random number generator. + """ + self._sim.set_seed(seed) + + def run_gate( + self, symbol: str, location: int | set[int], **params: SimulatorGateParams + ) -> dict: + """ + Execute a quantum gate (all gates are no-ops in coin toss simulator). + + Args: + symbol (str): The gate symbol (ignored). + location (int | set[int]): The qubit(s) to apply the gate to (ignored). + **params: Gate parameters (ignored). + + Returns: + dict: Always returns an empty dictionary since all gates are no-ops. + """ + # All gates are no-ops - return empty dict + return {} + + def run_circuit(self, circuit) -> dict[int, int]: + """ + Execute a complete quantum circuit (all gates are no-ops). + + Args: + circuit: The quantum circuit to execute (gates are ignored). + + Returns: + dict[int, int]: Dictionary mapping qubit indices to measurement results (1 for |1⟩). + """ + measurement_results = {} + + # Process circuit but ignore all gates - only measurements matter + for gate_data in circuit.items(): + # Gate data is a tuple: (gate_symbol, locations, params) + if isinstance(gate_data, tuple) and len(gate_data) >= 2: + gate_symbol, locations = gate_data[0], gate_data[1] + + if gate_symbol.lower().startswith("measure"): + # Handle measurements + if isinstance(locations, set): + for loc in locations: + result = self._sim.run_measure(loc) + # Only store results that measured |1⟩ + if result and list(result.values())[0] == 1: + measurement_results[loc] = 1 + elif isinstance(locations, int): + result = self._sim.run_measure(locations) + # Only store results that measured |1⟩ + if result and list(result.values())[0] == 1: + measurement_results[locations] = 1 + # All other gates are ignored + + return measurement_results + + +# Gate dictionary mapping gate symbols to no-op functions +# This maintains compatibility with the expected gate bindings interface +def _noop_gate(*args, **kwargs) -> None: + """No-operation function for all gates.""" + pass + + +def _measure_gate(state: CoinToss, qubit: int, **_params: SimulatorGateParams) -> int: + """Return |1⟩ with probability state.prob or |0⟩ otherwise.""" + result = state._sim.run_measure(qubit) + return list(result.values())[0] if result else 0 + + +gate_dict = { + # All gates are no-ops except measurements + # Single-qubit gates + "I": _noop_gate, + "X": _noop_gate, + "Y": _noop_gate, + "Z": _noop_gate, + "H": _noop_gate, + "S": _noop_gate, + "Sd": _noop_gate, + "SX": _noop_gate, + "SY": _noop_gate, + "SZ": _noop_gate, + "SXdg": _noop_gate, + "SYdg": _noop_gate, + "SZdg": _noop_gate, + "T": _noop_gate, + "Tdg": _noop_gate, + # Face gates + "F": _noop_gate, + "Fdg": _noop_gate, + "F1": _noop_gate, + "F1d": _noop_gate, + "F2": _noop_gate, + "F2d": _noop_gate, + "F3": _noop_gate, + "F3d": _noop_gate, + "F4": _noop_gate, + "F4d": _noop_gate, + # Hadamard variants + "H1": _noop_gate, + "H2": _noop_gate, + "H3": _noop_gate, + "H4": _noop_gate, + "H5": _noop_gate, + "H6": _noop_gate, + "H+z+x": _noop_gate, + "H-z-x": _noop_gate, + "H+y-z": _noop_gate, + "H-y-z": _noop_gate, + "H-x+y": _noop_gate, + "H-x-y": _noop_gate, + # Rotation gates + "RX": _noop_gate, + "RY": _noop_gate, + "RZ": _noop_gate, + "R1XY": _noop_gate, + "RXY1Q": _noop_gate, + # Other gates + "Q": _noop_gate, + "Qd": _noop_gate, + "R": _noop_gate, + "Rd": _noop_gate, + # Two-qubit gates + "CX": _noop_gate, + "CY": _noop_gate, + "CZ": _noop_gate, + "SWAP": _noop_gate, + "CNOT": _noop_gate, + # Entangling gates + "SXX": _noop_gate, + "SYY": _noop_gate, + "SZZ": _noop_gate, + "SXXdg": _noop_gate, + "SYYdg": _noop_gate, + "SZZdg": _noop_gate, + "SqrtZZ": _noop_gate, + # Rotation gates + "RXX": _noop_gate, + "RYY": _noop_gate, + "RZZ": _noop_gate, + "R2XXYYZZ": _noop_gate, + # Other gates + "G": _noop_gate, + "II": _noop_gate, + # Initialization gates + "Init": _noop_gate, + "Init +Z": _noop_gate, + "Init -Z": _noop_gate, + "Init +X": _noop_gate, + "Init -X": _noop_gate, + "Init +Y": _noop_gate, + "Init -Y": _noop_gate, + "init |0>": _noop_gate, + "init |1>": _noop_gate, + # Leak gates + "leak": _noop_gate, + "leak |0>": _noop_gate, + "leak |1>": _noop_gate, + "unleak |0>": _noop_gate, + "unleak |1>": _noop_gate, + # Measurements - these actually return random results + "Measure": _measure_gate, + "measure Z": _measure_gate, + "Measure +Z": _measure_gate, + "Measure -Z": _measure_gate, + "Measure +X": _measure_gate, + "Measure -X": _measure_gate, + "Measure +Y": _measure_gate, + "Measure -Y": _measure_gate, + "MX": _measure_gate, + "MY": _measure_gate, + "MZ": _measure_gate, +} diff --git a/python/pecos-rslib/src/pecos_rslib/rspauli_prop.py b/python/pecos-rslib/src/pecos_rslib/rspauli_prop.py new file mode 100644 index 000000000..706ee3953 --- /dev/null +++ b/python/pecos-rslib/src/pecos_rslib/rspauli_prop.py @@ -0,0 +1,214 @@ +# Copyright 2025 The PECOS Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License.You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +"""Rust-based Pauli propagation simulator for PECOS. + +This module provides a Python interface to the high-performance Rust implementation of a Pauli +propagation simulator. The simulator efficiently tracks how Pauli operators transform under +Clifford operations. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pecos_rslib._pecos_rslib import PauliProp as RustPauliProp + +if TYPE_CHECKING: + pass + + +class PauliPropRs: + """Rust-based Pauli propagation simulator. + + A high-performance simulator for tracking Pauli operator propagation through + Clifford circuits. Useful for fault propagation and stabilizer simulations. + """ + + def __init__(self, num_qubits: int | None = None, track_sign: bool = False): + """Initialize the Rust-backed Pauli propagation simulator. + + Args: + num_qubits: Optional number of qubits (for string representation). + track_sign: Whether to track sign and phase. + """ + self._sim = RustPauliProp(num_qubits, track_sign) + self.num_qubits = num_qubits + self.track_sign = track_sign + + def reset(self) -> PauliPropRs: + """Reset the simulator state. + + Returns: + Self for method chaining. + """ + self._sim.reset() + return self + + @property + def faults(self) -> dict[str, set[int]]: + """Get the current faults as a dictionary. + + Returns: + Dictionary with keys "X", "Y", "Z" mapping to sets of qubit indices. + """ + return self._sim.get_faults() + + def contains_x(self, qubit: int) -> bool: + """Check if a qubit has an X operator.""" + return self._sim.contains_x(qubit) + + def contains_y(self, qubit: int) -> bool: + """Check if a qubit has a Y operator.""" + return self._sim.contains_y(qubit) + + def contains_z(self, qubit: int) -> bool: + """Check if a qubit has a Z operator.""" + return self._sim.contains_z(qubit) + + def add_x(self, qubit: int) -> None: + """Add an X operator to a qubit.""" + self._sim.add_x(qubit) + + def add_y(self, qubit: int) -> None: + """Add a Y operator to a qubit.""" + self._sim.add_y(qubit) + + def add_z(self, qubit: int) -> None: + """Add a Z operator to a qubit.""" + self._sim.add_z(qubit) + + def flip_sign(self) -> None: + """Flip the sign of the Pauli string.""" + self._sim.flip_sign() + + def flip_img(self, num_is: int) -> None: + """Add imaginary factors to the phase. + + Args: + num_is: Number of i factors to add. + """ + self._sim.flip_img(num_is) + + def add_paulis(self, paulis: dict[str, set[int] | list[int]]) -> None: + """Add Pauli operators from a dictionary. + + Args: + paulis: Dictionary with keys "X", "Y", "Z" mapping to sets/lists of qubit indices. + """ + # Convert lists to sets if needed + paulis_dict = {} + for key, value in paulis.items(): + if isinstance(value, list): + paulis_dict[key] = set(value) + else: + paulis_dict[key] = value + self._sim.add_paulis(paulis_dict) + + def set_faults(self, paulis: dict[str, set[int] | list[int]]) -> None: + """Set the faults by clearing and then adding new ones. + + Args: + paulis: Dictionary with keys "X", "Y", "Z" mapping to sets/lists of qubit indices. + """ + self.reset() + if paulis: + self.add_paulis(paulis) + + def weight(self) -> int: + """Get the weight of the Pauli string (number of non-identity operators).""" + return self._sim.weight() + + def sign_string(self) -> str: + """Get the sign string representation.""" + return self._sim.sign_string() + + def sparse_string(self) -> str: + """Get the sparse string representation.""" + return self._sim.sparse_string() + + def dense_string(self) -> str: + """Get the dense string representation.""" + return self._sim.dense_string() + + def to_pauli_string(self) -> str: + """Get the full Pauli string with sign.""" + return self._sim.to_pauli_string() + + def to_dense_string(self) -> str: + """Get the full dense Pauli string with sign.""" + return self._sim.to_dense_string() + + def fault_string(self) -> str: + """Get the fault string representation (for compatibility with PauliFaultProp).""" + return self.to_pauli_string() + + def fault_wt(self) -> int: + """Get the fault weight (for compatibility with PauliFaultProp).""" + return self.weight() + + # Clifford gates + + def h(self, qubit: int) -> None: + """Apply Hadamard gate.""" + self._sim.h(qubit) + + def sz(self, qubit: int) -> None: + """Apply S gate (sqrt(Z)).""" + self._sim.sz(qubit) + + def sx(self, qubit: int) -> None: + """Apply sqrt(X) gate.""" + self._sim.sx(qubit) + + def sy(self, qubit: int) -> None: + """Apply sqrt(Y) gate.""" + self._sim.sy(qubit) + + def cx(self, control: int, target: int) -> None: + """Apply CNOT/CX gate.""" + self._sim.cx(control, target) + + def cy(self, control: int, target: int) -> None: + """Apply CY gate.""" + self._sim.cy(control, target) + + def cz(self, control: int, target: int) -> None: + """Apply CZ gate.""" + self._sim.cz(control, target) + + def swap(self, q1: int, q2: int) -> None: + """Apply SWAP gate.""" + self._sim.swap(q1, q2) + + def mz(self, qubit: int) -> bool: + """Measure in Z basis.""" + return self._sim.mz(qubit) + + def is_identity(self) -> bool: + """Check if this is the identity operator.""" + return self._sim.is_identity() + + def get_sign_bool(self) -> bool: + """Get the sign as a boolean (False for +, True for -).""" + return self._sim.get_sign() + + def get_img_value(self) -> int: + """Get the imaginary component (0 for real, 1 for imaginary).""" + return self._sim.get_img() + + def __str__(self) -> str: + """String representation.""" + return str(self._sim) + + def __repr__(self) -> str: + """Representation string.""" + return repr(self._sim) diff --git a/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py b/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py index 514c084a4..6e653b09e 100644 --- a/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py +++ b/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, List -from pecos_rslib._pecos_rslib import RsStateVec as RustStateVec +from pecos_rslib._pecos_rslib import RsStateVec if TYPE_CHECKING: from pecos.typing import SimulatorGateParams @@ -35,14 +35,15 @@ class StateVecRs: quantum circuits with full quantum state representation and support for complex quantum operations. """ - def __init__(self, num_qubits: int): + def __init__(self, num_qubits: int, seed: int | None = None): """ Initializes the Rust-backed state vector simulator. Args: num_qubits (int): The number of qubits in the quantum system. + seed (int | None): Optional seed for the random number generator. """ - self._sim = RustStateVec(num_qubits) + self._sim = RsStateVec(num_qubits, seed) self.num_qubits = num_qubits self.bindings = dict(gate_dict) @@ -60,7 +61,7 @@ def vector(self) -> List[complex]: else: vector = list(raw_vector) - # Convert vector from little-endian to big-endian ordering to match BasicSV + # Convert vector from little-endian to big-endian ordering to match PECOS convention num_qubits = self.num_qubits # Create indices mapping using pure Python diff --git a/python/quantum-pecos/pyproject.toml b/python/quantum-pecos/pyproject.toml index d9308becb..e7ce7a18f 100644 --- a/python/quantum-pecos/pyproject.toml +++ b/python/quantum-pecos/pyproject.toml @@ -63,10 +63,6 @@ guppy = [ "selene-sim~=0.2.0", # Then selene-sim (dependency of guppylang) "hugr>=0.13.0,<0.14", # Use stable version compatible with guppylang ] -projectq = [ # State-vector sims using ProjectQ - "pybind11>=2.2.3", - "projectq>=0.5", -] wasmtime = [ "wasmtime>=13.0" ] @@ -74,7 +70,6 @@ visualization = [ "plotly~=5.9.0", ] simulators = [ - "quantum-pecos[projectq]", "quantum-pecos[qulacs]; python_version < '3.13'", ] wasm-all = [ diff --git a/python/quantum-pecos/src/pecos/simulators/__init__.py b/python/quantum-pecos/src/pecos/simulators/__init__.py index 8d9c0d6db..eda771892 100644 --- a/python/quantum-pecos/src/pecos/simulators/__init__.py +++ b/python/quantum-pecos/src/pecos/simulators/__init__.py @@ -16,34 +16,24 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -# Rust version of stabilizer sim -from pecos_rslib import CppSparseSimRs, SparseSimRs, StateVecRs +# Rust version of simulators +from pecos_rslib import CoinToss, CppSparseSimRs, SparseSimRs from pecos_rslib import SparseSimRs as SparseSim from pecos.simulators import sim_class_types -from pecos.simulators.basic_sv.state import BasicSV # Basic numpy statevector simulator -from pecos.simulators.cointoss import ( - CoinToss, -) # Ignores quantum gates, coin toss for measurements from pecos.simulators.default_simulator import DefaultSimulator -from pecos.simulators.paulifaultprop import ( - PauliFaultProp, +from pecos.simulators.pauliprop import ( + PauliFaultProp, # Backward compatibility + PauliProp, ) # Pauli fault propagation sim from pecos.simulators.sparsesim import ( SparseSim as SparseSimPy, ) - -# Attempt to import optional ProjectQ package -try: - import projectq - - from pecos.simulators.projectq.state import ProjectQSim # wrapper for ProjectQ sim -except ImportError: - ProjectQSim = None +from pecos.simulators.statevec import StateVec # Attempt to import optional Qulacs package try: diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/bindings.py b/python/quantum-pecos/src/pecos/simulators/basic_sv/bindings.py deleted file mode 100644 index 8fe8caa6b..000000000 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/bindings.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2024 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Gate bindings for basic state vector simulator. - -This module provides gate operation bindings for the basic state vector quantum simulator, organizing and exposing -the quantum gate implementations for initialization, measurement, single-qubit, and two-qubit operations. -""" - -import pecos.simulators.basic_sv.gates_one_qubit as one_q -import pecos.simulators.basic_sv.gates_two_qubit as two_q -from pecos.simulators.basic_sv.gates_init import init_one, init_zero -from pecos.simulators.basic_sv.gates_meas import meas_z - -# Supporting gates from table: -# https://github.com/CQCL/phir/blob/main/spec.md#table-ii---quantum-operations - -gate_dict = { - "Init": init_zero, - "init |0>": init_zero, - "init |1>": init_one, - "Measure": meas_z, - "measure Z": meas_z, - "leak": init_zero, - "leak |0>": init_zero, - "leak |1>": init_one, - "unleak |0>": init_zero, - "unleak |1>": init_one, - "I": one_q.identity, - "X": one_q.X, - "Y": one_q.Y, - "Z": one_q.Z, - "RX": one_q.RX, - "RY": one_q.RY, - "RZ": one_q.RZ, - "R1XY": one_q.R1XY, - "RXY1Q": one_q.R1XY, - "SX": one_q.SX, - "SXdg": one_q.SXdg, - "SqrtX": one_q.SX, - "SqrtXd": one_q.SXdg, - "SY": one_q.SY, - "SYdg": one_q.SYdg, - "SqrtY": one_q.SY, - "SqrtYd": one_q.SYdg, - "SZ": one_q.SZ, - "SZdg": one_q.SZdg, - "SqrtZ": one_q.SZ, - "SqrtZd": one_q.SZdg, - "H": one_q.H, - "F": one_q.F, - "Fdg": one_q.Fdg, - "T": one_q.T, - "Tdg": one_q.Tdg, - "CX": two_q.CX, - "CY": two_q.CY, - "CZ": two_q.CZ, - "RXX": two_q.RXX, - "RYY": two_q.RYY, - "RZZ": two_q.RZZ, - "R2XXYYZZ": two_q.R2XXYYZZ, - "SXX": two_q.SXX, - "SXXdg": two_q.SXXdg, - "SYY": two_q.SYY, - "SYYdg": two_q.SYYdg, - "SZZ": two_q.SZZ, - "SqrtZZ": two_q.SZZ, - "SZZdg": two_q.SZZdg, - "SWAP": two_q.SWAP, - "Q": one_q.SX, - "Qd": one_q.SXdg, - "R": one_q.SY, - "Rd": one_q.SYdg, - "S": one_q.SZ, - "Sd": one_q.SZdg, - "H1": one_q.H, - "H2": one_q.H2, - "H3": one_q.H3, - "H4": one_q.H4, - "H5": one_q.H5, - "H6": one_q.H6, - "H+z+x": one_q.H, - "H-z-x": one_q.H2, - "H+y-z": one_q.H3, - "H-y-z": one_q.H4, - "H-x+y": one_q.H5, - "H-x-y": one_q.H6, - "F1": one_q.F, - "F1d": one_q.Fdg, - "F2": one_q.F2, - "F2d": one_q.F2d, - "F3": one_q.F3, - "F3d": one_q.F3d, - "F4": one_q.F4, - "F4d": one_q.F4d, - "CNOT": two_q.CX, - "G": two_q.G, - "II": one_q.identity, -} diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_init.py b/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_init.py deleted file mode 100644 index ac92cf174..000000000 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_init.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2024 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Qubit initialization operations for basic state vector simulator. - -This module provides quantum state initialization operations for the basic state vector simulator, including -functions to initialize qubits to the |0⟩ and |1⟩ computational basis states. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from pecos.simulators.basic_sv.gates_meas import meas_z -from pecos.simulators.basic_sv.gates_one_qubit import X - -if TYPE_CHECKING: - from pecos.simulators.basic_sv.state import BasicSV - from pecos.typing import SimulatorGateParams - - -def init_zero(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialise or reset the qubit to state |0>. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit to be initialised - """ - result = meas_z(state, qubit) - - if result: - X(state, qubit) - - -def init_one(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialise or reset the qubit to state |1>. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit to be initialised. - """ - result = meas_z(state, qubit) - - if not result: - X(state, qubit) diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_meas.py b/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_meas.py deleted file mode 100644 index 486b10399..000000000 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_meas.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2024 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Quantum measurement operations for basic state vector simulator. - -This module provides quantum measurement operations for the basic state vector simulator, including projective -measurements in the computational basis and other measurement schemes with proper state collapse. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import numpy as np - -if TYPE_CHECKING: - from pecos.simulators.basic_sv.state import BasicSV - from pecos.typing import SimulatorGateParams - - -def meas_z(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> int: - """Measure in the Z-basis, collapse and normalise. - - Notes: - The number of qubits in the state remains the same. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit to be measured - - Returns: - The outcome of the measurement, either 0 or 1. - """ - if qubit >= state.num_qubits or qubit < 0: - msg = f"Qubit {qubit} out of range." - raise ValueError(msg) - - # Define the Z-basis projectors - proj_0 = np.array( - [ - [1, 0], - [0, 0], - ], - ) - proj_1 = np.array( - [ - [0, 0], - [0, 1], - ], - ) - - # Use np.einsum to apply the projector to `qubit`. - # To do so, we need to assign subscript labels to each array axis. - subscripts = "".join( - [ - state.subscript_string((qubit,), ("q",)), # Current vector - ",", - "Qq", # Subscripts for the projector operator, acting on `qubit` q - "->", - state.subscript_string( - (qubit,), - ("Q",), - ), # Resulting vector, with updated Q - ], - ) - # Obtain the projected state where qubit `qubit` is in state |0> - projected_vector = np.einsum(subscripts, state.internal_vector, proj_0) - - # Obtain the probability of outcome |0> - prob_0 = np.sum(projected_vector * np.conjugate(projected_vector)) - - # Simulate the measurement - result = 0 if np.random.random() < prob_0 else 1 - - # Collapse the state - if result == 0: - state.internal_vector = projected_vector - else: - state.internal_vector = np.einsum(subscripts, state.internal_vector, proj_1) - - # Normalise - if result == 0: - state.internal_vector /= np.sqrt(prob_0) - else: - state.internal_vector /= np.sqrt(1 - prob_0) - - return result diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_one_qubit.py b/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_one_qubit.py deleted file mode 100644 index 681dc3077..000000000 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_one_qubit.py +++ /dev/null @@ -1,416 +0,0 @@ -# Copyright 2024 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Single-qubit gate operations for basic state vector simulator. - -This module provides single-qubit quantum gate operations for the basic state vector simulator, including Pauli -gates, rotation gates, Hadamard gates, and other fundamental single-qubit quantum operations. -""" - -from __future__ import annotations - -import cmath -import math -from typing import TYPE_CHECKING - -import numpy as np - -if TYPE_CHECKING: - from pecos.simulators.basic_sv.state import BasicSV - from pecos.typing import SimulatorGateParams - - -def _apply_one_qubit_matrix(state: BasicSV, qubit: int, matrix: np.ndarray) -> None: - """Apply the matrix to the state. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - matrix: The matrix to be applied - """ - if qubit >= state.num_qubits or qubit < 0: - msg = f"Qubit {qubit} out of range." - raise ValueError(msg) - - # Use np.einsum to apply the gate to `qubit`. - # To do so, we need to assign subscript labels to each array axis. - subscripts = "".join( - [ - state.subscript_string((qubit,), ("q",)), # Current vector - ",", - "Qq", # Subscripts for the gate, acting on `qubit` q - "->", - state.subscript_string( - (qubit,), - ("Q",), - ), # Resulting vector, with updated Q - ], - ) - # Update the state by applying the matrix - state.internal_vector = np.einsum(subscripts, state.internal_vector, matrix) - - -def identity(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Identity gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - - -def X(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Pauli X gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - matrix = np.array( - [ - [0, 1], - [1, 0], - ], - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def Y(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Pauli Y gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - matrix = np.array( - [ - [0, -1j], - [1j, 0], - ], - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def Z(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Pauli Z gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - matrix = np.array( - [ - [1, 0], - [0, -1], - ], - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def RX( - state: BasicSV, - qubit: int, - angles: tuple[float], - **_params: SimulatorGateParams, -) -> None: - """Apply an RX gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - angles: A tuple containing a single angle in radians - """ - if len(angles) != 1: - msg = "Gate must be given 1 angle parameter." - raise ValueError(msg) - theta = angles[0] - - matrix = np.array( - [ - [math.cos(theta / 2), -1j * math.sin(theta / 2)], - [-1j * math.sin(theta / 2), math.cos(theta / 2)], - ], - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def RY( - state: BasicSV, - qubit: int, - angles: tuple[float], - **_params: SimulatorGateParams, -) -> None: - """Apply an RY gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - angles: A tuple containing a single angle in radians - """ - if len(angles) != 1: - msg = "Gate must be given 1 angle parameter." - raise ValueError(msg) - theta = angles[0] - - matrix = np.array( - [ - [math.cos(theta / 2), -math.sin(theta / 2)], - [math.sin(theta / 2), math.cos(theta / 2)], - ], - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def RZ( - state: BasicSV, - qubit: int, - angles: tuple[float], - **_params: SimulatorGateParams, -) -> None: - """Apply an RZ gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - angles: A tuple containing a single angle in radians - """ - if len(angles) != 1: - msg = "Gate must be given 1 angle parameter." - raise ValueError(msg) - theta = angles[0] - - matrix = np.array( - [ - [cmath.exp(-1j * theta / 2), 0], - [0, cmath.exp(1j * theta / 2)], - ], - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def R1XY( - state: BasicSV, - qubit: int, - angles: tuple[float, float], - **_params: SimulatorGateParams, -) -> None: - """Apply an R1XY gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - angles: A tuple containing two angles in radians - """ - if len(angles) != 2: - msg = "Gate must be given 2 angle parameters." - raise ValueError(msg) - theta = angles[0] - phi = angles[1] - - # Gate is equal to RZ(phi-pi/2)*RY(theta)*RZ(-phi+pi/2) - RZ(state, qubit, angles=(-phi + math.pi / 2,)) - RY(state, qubit, angles=(theta,)) - RZ(state, qubit, angles=(phi - math.pi / 2,)) - - -def SX(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply a square-root of X. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RX(state, qubit, angles=(math.pi / 2,)) - - -def SXdg(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply adjoint of the square-root of X. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RX(state, qubit, angles=(-math.pi / 2,)) - - -def SY(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply a square-root of Y. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RY(state, qubit, angles=(math.pi / 2,)) - - -def SYdg(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply adjoint of the square-root of Y. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RY(state, qubit, angles=(-math.pi / 2,)) - - -def SZ(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply a square-root of Z. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RZ(state, qubit, angles=(math.pi / 2,)) - - -def SZdg(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply adjoint of the square-root of Z. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RZ(state, qubit, angles=(-math.pi / 2,)) - - -def H(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply Hadamard gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - matrix = ( - 1 - / np.sqrt(2) - * np.array( - [ - [1, 1], - [1, -1], - ], - ) - ) - _apply_one_qubit_matrix(state, qubit, matrix) - - -def F(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply face rotation of an octahedron #1 (X->Y->Z->X). - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RX(state, qubit, angles=(math.pi / 2,)) - RZ(state, qubit, angles=(math.pi / 2,)) - - -def Fdg(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply adjoint of face rotation of an octahedron #1 (X<-Y<-Z<-X). - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RZ(state, qubit, angles=(-math.pi / 2,)) - RX(state, qubit, angles=(-math.pi / 2,)) - - -def T(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply a T gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RZ(state, qubit, angles=(math.pi / 4,)) - - -def Tdg(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply adjoint of a T gate. - - Args: - state: An instance of BasicSV - qubit: The index of the qubit where the gate is applied - """ - RZ(state, qubit, angles=(-math.pi / 4,)) - - -def H2(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'H2': ('S', 'S', 'H', 'S', 'S').""" - Z(state, qubit) - H(state, qubit) - Z(state, qubit) - - -def H3(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'H3': ('H', 'S', 'S', 'H', 'S',).""" - X(state, qubit) - SZ(state, qubit) - - -def H4(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'H4': ('H', 'S', 'S', 'H', 'S', 'S', 'S',).""" - X(state, qubit) - SZdg(state, qubit) - - -def H5(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'H5': ('S', 'S', 'S', 'H', 'S').""" - SZdg(state, qubit) - H(state, qubit) - SZ(state, qubit) - - -def H6(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'H6': ('S', 'H', 'S', 'S', 'S',).""" - SZ(state, qubit) - H(state, qubit) - SZdg(state, qubit) - - -def F2(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'F2': ('S', 'S', 'H', 'S').""" - Z(state, qubit) - H(state, qubit) - SZ(state, qubit) - - -def F2d(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'F2d': ('S', 'S', 'S', 'H', 'S', 'S').""" - SZdg(state, qubit) - H(state, qubit) - Z(state, qubit) - - -def F3(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'F3': ('S', 'H', 'S', 'S').""" - SZ(state, qubit) - H(state, qubit) - Z(state, qubit) - - -def F3d(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'F3d': ('S', 'S', 'H', 'S', 'S', 'S').""" - Z(state, qubit) - H(state, qubit) - SZdg(state, qubit) - - -def F4(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'F4': ('H', 'S', 'S', 'S').""" - H(state, qubit) - SZdg(state, qubit) - - -def F4d(state: BasicSV, qubit: int, **_params: SimulatorGateParams) -> None: - """'F4d': ('S', 'H').""" - SZ(state, qubit) - H(state, qubit) diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_two_qubit.py b/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_two_qubit.py deleted file mode 100644 index 2611996fd..000000000 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/gates_two_qubit.py +++ /dev/null @@ -1,363 +0,0 @@ -# Copyright 2024 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Two-qubit gate operations for basic state vector simulator. - -This module provides two-qubit quantum gate operations for the basic state vector simulator, including CNOT gates, -controlled gates, and other fundamental two-qubit quantum operations for entangling operations. -""" - -from __future__ import annotations - -import cmath -import math -from typing import TYPE_CHECKING - -import numpy as np - -from pecos.simulators.basic_sv.gates_one_qubit import H - -if TYPE_CHECKING: - from pecos.simulators.basic_sv.state import BasicSV - from pecos.typing import SimulatorGateParams - - -def _apply_two_qubit_matrix( - state: BasicSV, - qubits: tuple[int, int], - matrix: np.array, -) -> None: - """Apply the matrix to the state. - - Args: - state: An instance of BasicSV - qubits: A tuple of two qubit indices where the gate is applied - matrix: The matrix to be applied - """ - if qubits[0] >= state.num_qubits or qubits[0] < 0: - msg = f"Qubit {qubits[0]} out of range." - raise ValueError(msg) - if qubits[1] >= state.num_qubits or qubits[1] < 0: - msg = f"Qubit {qubits[1]} out of range." - raise ValueError(msg) - - # Reshape the matrix into an ndarray of shape (2,2,2,2) - # Use positional argument for backward compatibility with NumPy < 2.0 - reshaped_matrix = np.reshape(matrix, (2, 2, 2, 2)) - - # Use np.einsum to apply the gate to `qubit`. - # To do so, we need to assign subscript labels to each array axis. - if qubits[0] < qubits[1]: - gate_subscripts = "LRlr" - qubit_labels_before = ("l", "r") - qubit_labels_after = ("L", "R") - else: # Implicit swap of gate subscripts - gate_subscripts = "RLrl" - qubit_labels_before = ("r", "l") - qubit_labels_after = ("R", "L") - - subscripts = "".join( - [ - state.subscript_string(qubits, qubit_labels_before), # Current vector - ",", - gate_subscripts, # Subscripts for the gate - "->", - state.subscript_string(qubits, qubit_labels_after), # Resulting vector - ], - ) - # Update the state by applying the matrix - state.internal_vector = np.einsum( - subscripts, - state.internal_vector, - reshaped_matrix, - ) - - -def CX(state: BasicSV, qubits: tuple[int, int], **_params: SimulatorGateParams) -> None: - """Apply controlled X gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - The one at `qubits[0]` is the control qubit. - """ - matrix = np.array( - [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 0, 1], - [0, 0, 1, 0], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def CY(state: BasicSV, qubits: tuple[int, int], **_params: SimulatorGateParams) -> None: - """Apply controlled Y gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - The one at `qubits[0]` is the control qubit. - """ - matrix = np.array( - [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 0, -1j], - [0, 0, 1j, 0], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def CZ(state: BasicSV, qubits: tuple[int, int], **_params: SimulatorGateParams) -> None: - """Apply controlled Z gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - The one at `qubits[0]` is the control qubit. - """ - matrix = np.array( - [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, -1], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def RXX( - state: BasicSV, - qubits: tuple[int, int], - angles: tuple[float], - **_params: SimulatorGateParams, -) -> None: - """Apply a rotation about XX. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - angles: A tuple containing a single angle in radians - """ - if len(angles) != 1: - msg = "Gate must be given 1 angle parameter." - raise ValueError(msg) - theta = angles[0] - - matrix = np.array( - [ - [math.cos(theta / 2), 0, 0, -1j * math.sin(theta / 2)], - [0, math.cos(theta / 2), -1j * math.sin(theta / 2), 0], - [0, -1j * math.sin(theta / 2), math.cos(theta / 2), 0], - [-1j * math.sin(theta / 2), 0, 0, math.cos(theta / 2)], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def RYY( - state: BasicSV, - qubits: tuple[int, int], - angles: tuple[float], - **_params: SimulatorGateParams, -) -> None: - """Apply a rotation about YY. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - angles: A tuple containing a single angle in radians - """ - if len(angles) != 1: - msg = "Gate must be given 1 angle parameter." - raise ValueError(msg) - theta = angles[0] - - matrix = np.array( - [ - [math.cos(theta / 2), 0, 0, 1j * math.sin(theta / 2)], - [0, math.cos(theta / 2), -1j * math.sin(theta / 2), 0], - [0, -1j * math.sin(theta / 2), math.cos(theta / 2), 0], - [1j * math.sin(theta / 2), 0, 0, math.cos(theta / 2)], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def RZZ( - state: BasicSV, - qubits: tuple[int, int], - angles: tuple[float], - **_params: SimulatorGateParams, -) -> None: - """Apply a rotation about ZZ. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - angles: A tuple containing a single angle in radians - """ - if len(angles) != 1: - msg = "Gate must be given 1 angle parameter." - raise ValueError(msg) - theta = angles[0] - - matrix = np.array( - [ - [cmath.exp(-1j * theta / 2), 0, 0, 0], - [0, cmath.exp(1j * theta / 2), 0, 0], - [0, 0, cmath.exp(1j * theta / 2), 0], - [0, 0, 0, cmath.exp(-1j * theta / 2)], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def R2XXYYZZ( - state: BasicSV, - qubits: tuple[int, int], - angles: tuple[float, float, float], - **_params: SimulatorGateParams, -) -> None: - """Apply RXX*RYY*RZZ. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - angles: A tuple containing three angles in radians, for XX, YY and ZZ, in that order - """ - if len(angles) != 3: - msg = "Gate must be given 3 angle parameters." - raise ValueError(msg) - - RXX(state, qubits, (angles[0],)) - RYY(state, qubits, (angles[1],)) - RZZ(state, qubits, (angles[2],)) - - -def SXX( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply a square root of XX gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - RXX(state, qubits, angles=(math.pi / 2,)) - - -def SXXdg( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply adjoint of a square root of XX gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - RXX(state, qubits, angles=(-math.pi / 2,)) - - -def SYY( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply a square root of YY gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - RYY(state, qubits, angles=(math.pi / 2,)) - - -def SYYdg( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply adjoint of a square root of YY gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - RYY(state, qubits, angles=(-math.pi / 2,)) - - -def SZZ( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply a square root of ZZ gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - RZZ(state, qubits, angles=(math.pi / 2,)) - - -def SZZdg( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply adjoint of a square root of ZZ gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - RZZ(state, qubits, angles=(-math.pi / 2,)) - - -def SWAP( - state: BasicSV, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply a SWAP gate. - - Args: - state: An instance of BasicSV - qubits: A tuple with the index of the qubits where the gate is applied - """ - matrix = np.array( - [ - [1, 0, 0, 0], - [0, 0, 1, 0], - [0, 1, 0, 0], - [0, 0, 0, 1], - ], - ) - _apply_two_qubit_matrix(state, qubits, matrix) - - -def G(state: BasicSV, qubits: tuple[int, int], **_params: SimulatorGateParams) -> None: - """'G': (('I', 'H'), 'CNOT', ('H', 'H'), 'CNOT', ('I', 'H')).""" - H(state, qubits[1]) - CX(state, qubits) - H(state, qubits[0]) - H(state, qubits[1]) - CX(state, qubits) - H(state, qubits[1]) diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/state.py b/python/quantum-pecos/src/pecos/simulators/basic_sv/state.py deleted file mode 100644 index 3297f7665..000000000 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/state.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2024 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Quantum state representation for basic state vector simulator. - -This module provides the quantum state representation and management functionality for the basic state vector -simulator, including state vector storage, manipulation, and utility functions for quantum state operations. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import numpy as np - -from pecos.simulators.basic_sv import bindings -from pecos.simulators.sim_class_types import StateVector - -if TYPE_CHECKING: - import sys - - from numpy.typing import ArrayLike - - # Handle Python 3.10 compatibility for Self type - if sys.version_info >= (3, 11): - from typing import Self - else: - from typing import TypeVar - - Self = TypeVar("Self", bound="BasicSV") - - -class BasicSV(StateVector): - """Basic state vector simulator using NumPy. - - Notes: - The purpose of this simulator is to provide a minimum requirement approach - to test other simulators against. Not suitable for users looking for performance. - Maximum number of qubits is restricted to 10. - """ - - def __init__(self, num_qubits: int, seed: int | None = None) -> None: - """Initializes the state vector. - - Args: - num_qubits (int): Number of qubits being represented. - seed (int): Seed for randomness. - - Raises: - ValueError: If `num_qubits` is larger than 10. - """ - if not isinstance(num_qubits, int): - msg = "``num_qubits`` should be of type ``int``." - raise TypeError(msg) - if num_qubits > 10: - msg = "`num_qubits` cannot be larger than 10." - raise ValueError(msg) - - super().__init__() - np.random.seed(seed) - - self.bindings = bindings.gate_dict - self.num_qubits = num_qubits - - self.internal_vector = None - self.reset() - - def subscript_string(self, qubits: tuple[int], labels: tuple[chr]) -> str: - """Returns a string of subscripts to use with `np.einsum`. - - The string returned identifies each of the qubits (ndarray axes) in - `self.internal_vector` with a character. Each of the elements in `qubits` is - assigned the specified character from the `labels` list. - - Args: - qubits (tuple[int]): The list of qubits with special labels. - labels (tuple[chr]): The special labels given to the qubits. - - Returns: - A string of subscripts for `self.internal_vector` to use with `np.einsum`. - - Raises: - ValueError: If an element in `qubits` is larger than self.num_qubits. - ValueError: If a label in `labels` would collide with another label. - ValueError: If the length of `qubits` and `labels` do not match. - """ - if any(q >= self.num_qubits or q < 0 for q in qubits): - msg = "Qubit out of range." - raise ValueError(msg) - if len(qubits) != len(labels): - msg = f"Different number of entries in qubits {qubits} and labels {labels}" - raise ValueError(msg) - - # Each axis in the ndarray corresponds to a qubit. Name each of them with - # a unique lowercase letter, starting from chr(97)=="a". - qubit_ids = [chr(97 + q) for q in range(self.num_qubits)] - - # Since the maximum number of qubits is set to 10 and chr(107)=="k", any - # letter "larger" than "k" is a valid element of `labels`. - if any(lbl in qubit_ids for lbl in labels) or len(set(labels)) < len(labels): - msg = "Label already in use, choose another label." - raise ValueError(msg) - - # Rename the qubits with special labels - for q, lbl in zip(qubits, labels, strict=False): - qubit_ids[q] = lbl - - # Concatenate characters into a string and return - return "".join(qubit_ids) - - def reset(self) -> Self: - """Reset the quantum state for another run without reinitializing.""" - # Initialize state vector to |0> - self.internal_vector = np.zeros(shape=2**self.num_qubits) - self.internal_vector[0] = 1 - # Internally use a ndarray representation so that it's easier to apply gates - # without needing to apply tensor products. - - # Use positional argument for backward compatibility with NumPy < 2.0 - self.internal_vector = np.reshape( - self.internal_vector, - [2] * self.num_qubits, - ) - return self - - @property - def vector(self) -> ArrayLike: - """Get the quantum state vector as a numpy array. - - Returns: - The state vector reshaped to have 2^num_qubits elements. - """ - # Use positional argument for backward compatibility with NumPy < 2.0 - return np.reshape(self.internal_vector, 2**self.num_qubits) diff --git a/python/quantum-pecos/src/pecos/simulators/cointoss/__init__.py b/python/quantum-pecos/src/pecos/simulators/cointoss/__init__.py deleted file mode 100644 index 40d804be5..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cointoss/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Coin toss simulator. - -This package provides a simulator that ignores quantum gates and uses coin tosses for measurements. -""" - -# Copyright 2023 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -from pecos.simulators.cointoss import bindings -from pecos.simulators.cointoss.state import CoinToss diff --git a/python/quantum-pecos/src/pecos/simulators/cointoss/bindings.py b/python/quantum-pecos/src/pecos/simulators/cointoss/bindings.py deleted file mode 100644 index 6a8d2632b..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cointoss/bindings.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Gate bindings for coin toss quantum simulator. - -This module provides gate operation bindings for the coin toss quantum simulator, which provides a simplified -quantum simulation model that treats all quantum operations as classical coin tosses for rapid prototyping. -""" - -from pecos.simulators.cointoss.gates import ignore_gate, measure - -# Supporting gates from table: -# https://github.com/CQCL/phir/blob/main/spec.md#table-ii---quantum-operations - -gate_dict = { - "Init": ignore_gate, - "Init +Z": ignore_gate, - "Init -Z": ignore_gate, - "init |0>": ignore_gate, - "init |1>": ignore_gate, - "leak": ignore_gate, - "leak |0>": ignore_gate, - "leak |1>": ignore_gate, - "unleak |0>": ignore_gate, - "unleak |1>": ignore_gate, - "Measure": measure, - "measure Z": measure, - "I": ignore_gate, - "X": ignore_gate, - "Y": ignore_gate, - "Z": ignore_gate, - "RX": ignore_gate, - "RY": ignore_gate, - "RZ": ignore_gate, - "R1XY": ignore_gate, - "RXY1Q": ignore_gate, - "SX": ignore_gate, - "SXdg": ignore_gate, - "SqrtX": ignore_gate, - "SqrtXd": ignore_gate, - "SY": ignore_gate, - "SYdg": ignore_gate, - "SqrtY": ignore_gate, - "SqrtYd": ignore_gate, - "SZ": ignore_gate, - "SZdg": ignore_gate, - "SqrtZ": ignore_gate, - "SqrtZd": ignore_gate, - "H": ignore_gate, - "F": ignore_gate, - "Fdg": ignore_gate, - "T": ignore_gate, - "Tdg": ignore_gate, - "CX": ignore_gate, - "CY": ignore_gate, - "CZ": ignore_gate, - "RXX": ignore_gate, - "RYY": ignore_gate, - "RZZ": ignore_gate, - "R2XXYYZZ": ignore_gate, - "SXX": ignore_gate, - "SXXdg": ignore_gate, - "SYY": ignore_gate, - "SYYdg": ignore_gate, - "SZZ": ignore_gate, - "SqrtZZ": ignore_gate, - "SZZdg": ignore_gate, - "SWAP": ignore_gate, - "Q": ignore_gate, - "Qd": ignore_gate, - "R": ignore_gate, - "Rd": ignore_gate, - "S": ignore_gate, - "Sd": ignore_gate, - "H1": ignore_gate, - "H2": ignore_gate, - "H3": ignore_gate, - "H4": ignore_gate, - "H5": ignore_gate, - "H6": ignore_gate, - "H+z+x": ignore_gate, - "H-z-x": ignore_gate, - "H+y-z": ignore_gate, - "H-y-z": ignore_gate, - "H-x+y": ignore_gate, - "H-x-y": ignore_gate, - "F1": ignore_gate, - "F1d": ignore_gate, - "F2": ignore_gate, - "F2d": ignore_gate, - "F3": ignore_gate, - "F3d": ignore_gate, - "F4": ignore_gate, - "F4d": ignore_gate, - "CNOT": ignore_gate, - "G": ignore_gate, - "II": ignore_gate, -} diff --git a/python/quantum-pecos/src/pecos/simulators/cointoss/gates.py b/python/quantum-pecos/src/pecos/simulators/cointoss/gates.py deleted file mode 100644 index 694d65656..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cointoss/gates.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2023 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Gate operations for coin toss quantum simulator. - -This module provides gate operations for the coin toss quantum simulator, implementing a simplified quantum model -where all quantum gates are treated as no-ops and measurements return random classical outcomes. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import numpy as np - -if TYPE_CHECKING: - from pecos.simulators.cointoss.state import CoinToss - from pecos.typing import SimulatorGateParams - - -def ignore_gate(state: CoinToss, _qubits: int, **_params: SimulatorGateParams) -> None: - """Ignore the gate. - - Args: - state: An instance of ``CoinToss``. - _qubits: The qubits the gate was applied to. - """ - - -def measure(state: CoinToss, _qubits: int, **_params: SimulatorGateParams) -> int: - """Return |1> with probability ``state.prob`` or |0> otherwise. - - Args: - state: An instance of ``CoinToss``. - _qubits: The qubit the measurement is applied to. - """ - return 1 if np.random.random() < state.prob else 0 diff --git a/python/quantum-pecos/src/pecos/simulators/cointoss/state.py b/python/quantum-pecos/src/pecos/simulators/cointoss/state.py deleted file mode 100644 index 4415b1bf4..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cointoss/state.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2023 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""State representation for coin toss quantum simulator. - -This module provides the quantum state representation for the coin toss simulator, implementing a minimal state -model that tracks qubit count without maintaining actual quantum state information for rapid simulation. -""" - -from __future__ import annotations - -import random -from typing import TYPE_CHECKING - -from pecos.simulators.cointoss import bindings -from pecos.simulators.default_simulator import DefaultSimulator - -if TYPE_CHECKING: - # Handle Python 3.10 compatibility for Self type - import sys - - if sys.version_info >= (3, 11): - from typing import Self - else: - from typing import TypeVar - - Self = TypeVar("Self", bound="CoinToss") - - -class CoinToss(DefaultSimulator): - """Ignore all quantum operations and toss a coin to decide measurement outcomes. - - Meant for stochastical debugging of the classical branches. - """ - - def __init__( - self, - num_qubits: int, - prob: float = 0.5, - seed: int | None = None, - ) -> None: - """Initialization is trivial, since there is no state. - - Args: - num_qubits (int): Number of qubits being represented. - prob (float): Probability of measurements returning |1>. - Default value is 0.5. - seed (int): Seed for randomness. - """ - if not isinstance(num_qubits, int): - msg = "``num_qubits`` should be of type ``int``." - raise TypeError(msg) - if not (prob >= 0 and prob <= 1): - msg = "``prob`` should be a real number in [0,1]." - raise ValueError(msg) - random.seed(seed) - - super().__init__() - - self.bindings = bindings.gate_dict - self.num_qubits = num_qubits - self.prob = prob - - def reset(self) -> Self: - """Reset the quantum state for another run without reinitializing.""" - # Do nothing, this simulator does not keep a state! - return self diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/__init__.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/__init__.py similarity index 72% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/__init__.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/__init__.py index 3cb204813..e3430380f 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/__init__.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/__init__.py @@ -14,5 +14,10 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -from pecos.simulators.paulifaultprop import bindings -from pecos.simulators.paulifaultprop.state import PauliFaultProp +from pecos.simulators.pauliprop import bindings +from pecos.simulators.pauliprop.state import PauliProp +from pecos.simulators.pauliprop.state import ( + PauliProp as PauliFaultProp, +) # Backward compatibility + +__all__ = ["PauliFaultProp", "PauliProp", "bindings"] diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/bindings.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/bindings.py similarity index 98% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/bindings.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/bindings.py index d3fa89853..5a02c543c 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/bindings.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/bindings.py @@ -11,7 +11,7 @@ """Specifies the symbol and function for each gate.""" -from pecos.simulators.paulifaultprop import ( +from pecos.simulators.pauliprop import ( gates_init, gates_meas, gates_one_qubit, diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_init.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_init.py similarity index 86% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_init.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/gates_init.py index bdbba53cf..3e382fd9b 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_init.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_init.py @@ -20,15 +20,15 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from pecos.simulators.paulifaultprop.state import PauliFaultProp + from pecos.simulators.pauliprop.state import PauliProp from pecos.typing import SimulatorGateParams -def init(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def init(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Initialize qubit to zero state. Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. qubit (int): The qubit index to initialize. **_params: Unused additional parameters (kept for interface compatibility). """ diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_meas.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_meas.py similarity index 86% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_meas.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/gates_meas.py index 801dac54d..a4e92da74 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_meas.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_meas.py @@ -20,15 +20,15 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from pecos.simulators.paulifaultprop.state import PauliFaultProp + from pecos.simulators.pauliprop.state import PauliProp from pecos.typing import SimulatorGateParams -def meas_x(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> int: +def meas_x(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> int: """Measurement in the X basis. Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. qubit (int): The qubit index to measure. **_params: Unused additional parameters (kept for interface compatibility). """ @@ -37,11 +37,11 @@ def meas_x(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> return 0 -def meas_z(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> int: +def meas_z(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> int: """Measurement in the Z basis. Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. qubit (int): The qubit index to measure. **_params: Unused additional parameters (kept for interface compatibility). """ @@ -50,11 +50,11 @@ def meas_z(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> return 0 -def meas_y(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> int: +def meas_y(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> int: """Measurement in the Y basis. Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. qubit (int): The qubit index to measure. **_params: Unused additional parameters (kept for interface compatibility). """ @@ -64,7 +64,7 @@ def meas_y(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> def meas_pauli( - state: PauliFaultProp, + state: PauliProp, qubits: int | tuple[int, ...], **params: SimulatorGateParams, ) -> int: @@ -115,7 +115,7 @@ def meas_pauli( return meas % 2 -def force_output(_state: PauliFaultProp, _qubit: int, forced_output: int = -1) -> int: +def force_output(_state: PauliProp, _qubit: int, forced_output: int = -1) -> int: """Outputs value. Used for error generators to generate outputs when replacing measurements. diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_one_qubit.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_one_qubit.py similarity index 72% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_one_qubit.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/gates_one_qubit.py index 0dcfd2074..4d64033c7 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_one_qubit.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_one_qubit.py @@ -20,12 +20,12 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from pecos.simulators.paulifaultprop.state import PauliFaultProp + from pecos.simulators.pauliprop.state import PauliProp from pecos.typing import SimulatorGateParams def switch( - state: PauliFaultProp, + state: PauliProp, switch_list: list[tuple[str, str]], qubit: int, ) -> None: @@ -46,7 +46,7 @@ def switch( break -def Identity(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def Identity(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Identity does nothing. X -> X @@ -54,7 +54,7 @@ def Identity(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) Y -> Y Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -62,7 +62,7 @@ def Identity(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) """ -def X(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def X(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Pauli X. X -> X @@ -70,7 +70,7 @@ def X(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None Y -> -Y Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -80,7 +80,7 @@ def X(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None state.flip_sign() -def Y(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def Y(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Apply Pauli Y gate. X -> -X @@ -88,7 +88,7 @@ def Y(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None Y -> Y. Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -98,7 +98,7 @@ def Y(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None state.flip_sign() -def Z(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def Z(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Apply Pauli Z gate. X -> -X @@ -106,7 +106,7 @@ def Z(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None Y -> -Y. Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -116,7 +116,7 @@ def Z(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None state.flip_sign() -def SX(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def SX(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Square root of X. X -> X @@ -124,7 +124,7 @@ def SX(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -143,7 +143,7 @@ def SX(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def SXdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def SXdg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hermitian conjugate of the square root of X. X -> X @@ -151,7 +151,7 @@ def SXdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N Y -> -Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -170,7 +170,7 @@ def SXdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N ) -def SY(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def SY(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Square root of Y. X -> -Z @@ -178,7 +178,7 @@ def SY(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> Y Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -197,7 +197,7 @@ def SY(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def SYdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def SYdg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hermitian conjugate of the square root of Y. X -> Z @@ -205,7 +205,7 @@ def SYdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N Y -> Y Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -224,7 +224,7 @@ def SYdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N ) -def SZ(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def SZ(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Square root of Z. X -> Y @@ -232,7 +232,7 @@ def SZ(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -251,7 +251,7 @@ def SZ(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def SZdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def SZdg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hermitian conjugate of the square root of Z. X -> -Y @@ -259,7 +259,7 @@ def SZdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N Y -> X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -278,7 +278,7 @@ def SZdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N ) -def H(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def H(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hadamard gate. X -> Z @@ -286,7 +286,7 @@ def H(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None Y -> -Y Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -305,7 +305,7 @@ def H(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None ) -def H2(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def H2(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hadamard-like rotation. X -> -Z @@ -313,7 +313,7 @@ def H2(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -Y Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -332,7 +332,7 @@ def H2(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def H3(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def H3(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hadamard-like rotation. X -> Y @@ -340,7 +340,7 @@ def H3(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -359,7 +359,7 @@ def H3(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def H4(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def H4(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hadamard-like rotation. X -> -Y @@ -367,7 +367,7 @@ def H4(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -386,7 +386,7 @@ def H4(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def H5(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def H5(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hadamard-like rotation. X -> -X @@ -394,7 +394,7 @@ def H5(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -413,7 +413,7 @@ def H5(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def H6(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def H6(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Hadamard-like rotation. X -> -X @@ -421,7 +421,7 @@ def H6(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -440,7 +440,7 @@ def H6(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def F(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> Y @@ -448,7 +448,7 @@ def F(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None Y -> Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -465,7 +465,7 @@ def F(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None ) -def F2(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F2(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> -Z @@ -473,7 +473,7 @@ def F2(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -493,7 +493,7 @@ def F2(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def F3(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F3(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> Y @@ -501,7 +501,7 @@ def F3(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -521,7 +521,7 @@ def F3(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def F4(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F4(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> Z @@ -529,7 +529,7 @@ def F4(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non Y -> -X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -549,7 +549,7 @@ def F4(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> Non ) -def Fdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def Fdg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> Z @@ -557,7 +557,7 @@ def Fdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> No Y -> X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -574,7 +574,7 @@ def Fdg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> No ) -def F2dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F2dg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> -Y @@ -582,7 +582,7 @@ def F2dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N Y -> Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -602,7 +602,7 @@ def F2dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N ) -def F3dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F3dg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> -Z @@ -610,7 +610,7 @@ def F3dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N Y -> X Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None @@ -630,7 +630,7 @@ def F3dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N ) -def F4dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> None: +def F4dg(state: PauliProp, qubit: int, **_params: SimulatorGateParams) -> None: """Face rotation. X -> -Y @@ -638,7 +638,7 @@ def F4dg(state: PauliFaultProp, qubit: int, **_params: SimulatorGateParams) -> N Y -> -Z Args: - state (PauliFaultProp): The class representing the Pauli fault state. + state (PauliProp): The class representing the Pauli fault state. qubit (int): An integer indexing the qubit being operated on. Returns: None diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_two_qubit.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_two_qubit.py similarity index 91% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_two_qubit.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/gates_two_qubit.py index 5661b188b..1e55d1e88 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/gates_two_qubit.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/gates_two_qubit.py @@ -19,14 +19,14 @@ from typing import TYPE_CHECKING -from pecos.simulators.paulifaultprop.gates_one_qubit import SX, SY, SZ, H, SYdg, SZdg, X +from pecos.simulators.pauliprop.gates_one_qubit import SX, SY, SZ, H, SYdg, SZdg, X if TYPE_CHECKING: - from pecos.simulators.paulifaultprop.state import PauliFaultProp + from pecos.simulators.pauliprop.state import PauliProp from pecos.typing import SimulatorGateParams -def CX(state: PauliFaultProp, qubits: tuple[int, int]) -> None: +def CX(state: PauliProp, qubits: tuple[int, int]) -> None: """Applies the controlled-X gate. state (SparseSim): Instance representing the stabilizer state. @@ -119,7 +119,7 @@ def CX(state: PauliFaultProp, qubits: tuple[int, int]) -> None: state.flip_sign() -def CZ(state: PauliFaultProp, qubits: tuple[int, int]) -> None: +def CZ(state: PauliProp, qubits: tuple[int, int]) -> None: """Applies the controlled-Z gate. II -> II @@ -150,7 +150,7 @@ def CZ(state: PauliFaultProp, qubits: tuple[int, int]) -> None: H(state, qubits[1]) -def CY(state: PauliFaultProp, qubits: tuple[int, int]) -> None: +def CY(state: PauliProp, qubits: tuple[int, int]) -> None: """Applies the controlled-Y gate. II -> II @@ -181,7 +181,7 @@ def CY(state: PauliFaultProp, qubits: tuple[int, int]) -> None: SZ(state, qubits[1]) -def SWAP(state: PauliFaultProp, qubits: tuple[int, int]) -> None: +def SWAP(state: PauliProp, qubits: tuple[int, int]) -> None: """Applies a SWAP gate. state (SparseSim): Instance representing the stabilizer state. @@ -197,7 +197,7 @@ def SWAP(state: PauliFaultProp, qubits: tuple[int, int]) -> None: CX(state, (q1, q2)) -def G2(state: PauliFaultProp, qubits: tuple[int, int]) -> None: +def G2(state: PauliProp, qubits: tuple[int, int]) -> None: """Applies a CZ.H(1).H(2).CZ. state (SparseSim): Instance representing the stabilizer state. @@ -213,7 +213,7 @@ def G2(state: PauliFaultProp, qubits: tuple[int, int]) -> None: def II( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: @@ -228,7 +228,7 @@ def II( def SXX( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: @@ -249,7 +249,7 @@ def SXX( def SXXdg( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: @@ -268,7 +268,7 @@ def SXXdg( def SYY( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: @@ -282,7 +282,7 @@ def SYY( TODO: verify implementation! Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. qubits (tuple[int, int]): A tuple of two qubit indices to apply the gate to. """ qubit1, qubit2 = qubits @@ -294,14 +294,14 @@ def SYY( def SYYdg( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: """Adjoint of SYY. Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. qubits (tuple[int, int]): A tuple of two qubit indices to apply the gate to. **_params: Unused additional parameters (kept for interface compatibility). """ @@ -314,7 +314,7 @@ def SYYdg( def SZZ( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: @@ -332,7 +332,7 @@ def SZZ( def SZZdg( - state: PauliFaultProp, + state: PauliProp, qubits: tuple[int, int], **_params: SimulatorGateParams, ) -> None: diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/logical_sign.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/logical_sign.py similarity index 90% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/logical_sign.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/logical_sign.py index 64b057fc4..739444509 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/logical_sign.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/logical_sign.py @@ -21,14 +21,14 @@ if TYPE_CHECKING: from pecos.circuits import QuantumCircuit - from pecos.simulators.paulifaultprop.state import PauliFaultProp + from pecos.simulators.pauliprop.state import PauliProp -def find_logical_signs(state: PauliFaultProp, logical_circuit: QuantumCircuit) -> int: +def find_logical_signs(state: PauliProp, logical_circuit: QuantumCircuit) -> int: """Find the sign of the logical operator. Args: - state: The PauliFaultProp state instance. + state: The PauliProp state instance. logical_circuit (QuantumCircuit): The logical circuit to find the sign of. """ if len(logical_circuit) != 1: diff --git a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/state.py b/python/quantum-pecos/src/pecos/simulators/pauliprop/state.py similarity index 55% rename from python/quantum-pecos/src/pecos/simulators/paulifaultprop/state.py rename to python/quantum-pecos/src/pecos/simulators/pauliprop/state.py index 1568c807a..f6e40143d 100644 --- a/python/quantum-pecos/src/pecos/simulators/paulifaultprop/state.py +++ b/python/quantum-pecos/src/pecos/simulators/pauliprop/state.py @@ -19,9 +19,11 @@ from typing import TYPE_CHECKING +from pecos_rslib import PauliPropRs + from pecos.simulators.gate_syms import alt_symbols -from pecos.simulators.paulifaultprop import bindings -from pecos.simulators.paulifaultprop.logical_sign import find_logical_signs +from pecos.simulators.pauliprop import bindings +from pecos.simulators.pauliprop.logical_sign import find_logical_signs from pecos.simulators.sim_class_types import PauliPropagation if TYPE_CHECKING: @@ -29,7 +31,7 @@ from pecos.circuits.quantum_circuit import ParamGateCollection -class PauliFaultProp(PauliPropagation): +class PauliProp(PauliPropagation): r"""A simulator that evolves Pauli faults through Clifford circuits. The unitary evolution of a Pauli follows :math:`PC = CP' \Leftrightarrow P' = C^{\dagger} P C`, where :math:`P` and @@ -44,7 +46,7 @@ class PauliFaultProp(PauliPropagation): """ def __init__(self, *, num_qubits: int, track_sign: bool = False) -> None: - """Initialize a PauliFaultProp state. + """Initialize a PauliProp state. Args: num_qubits (int): Number of qubits in the system. @@ -56,26 +58,100 @@ def __init__(self, *, num_qubits: int, track_sign: bool = False) -> None: super().__init__() self.num_qubits = num_qubits - self.faults = { - "X": set(), - "Y": set(), - "Z": set(), - } - # Here we will encode Y as the qubit id in faults_x and faults_z - self.track_sign = track_sign - self.sign = 0 - self.img = 0 - self.bindings = bindings.gate_dict + # Use Rust backend + self._backend = PauliPropRs(num_qubits, track_sign) + + # Set up optimized bindings for gates available in Rust backend + self._setup_optimized_bindings() + + # Fall back to Python implementations for gates not in Rust + for gate, func in bindings.gate_dict.items(): + if gate not in self.bindings: + self.bindings[gate] = func + + # Add alternative symbols for k, v in alt_symbols.items(): if v in self.bindings: self.bindings[k] = self.bindings[v] + def _setup_optimized_bindings(self) -> None: + """Set up direct bindings to Rust backend for supported gates.""" + self.bindings = {} + backend = self._backend # Local reference to avoid attribute lookup + + # Single-qubit gates - location is always an int + self.bindings["H"] = lambda s, q, **p: backend.h(q) # noqa: ARG005 + self.bindings["SX"] = lambda s, q, **p: backend.sx(q) # noqa: ARG005 + self.bindings["SY"] = lambda s, q, **p: backend.sy(q) # noqa: ARG005 + self.bindings["SZ"] = lambda s, q, **p: backend.sz(q) # noqa: ARG005 + + # Two-qubit gates - location is always a tuple + self.bindings["CX"] = lambda s, qs, **p: backend.cx( # noqa: ARG005 + qs[0], + qs[1], + ) + self.bindings["CY"] = lambda s, qs, **p: backend.cy( # noqa: ARG005 + qs[0], + qs[1], + ) + self.bindings["CZ"] = lambda s, qs, **p: backend.cz( # noqa: ARG005 + qs[0], + qs[1], + ) + self.bindings["SWAP"] = lambda s, qs, **p: backend.swap( # noqa: ARG005 + qs[0], + qs[1], + ) + + # Note: X, Y, Z are Pauli operators, not gates to apply to the state, + # so they should still use the Python implementations + + @property + def faults(self) -> dict: + """Get the current faults dictionary.""" + return self._backend.faults + + @faults.setter + def faults(self, value: dict) -> None: + """Set the faults dictionary.""" + self._backend.set_faults(value) + + @property + def sign(self) -> int: + """Get the sign (0 for +, 1 for -).""" + return 1 if self._backend.get_sign_bool() else 0 + + @sign.setter + def sign(self, value: int) -> None: + """Set the sign.""" + # Reset sign to 0, then flip if needed + current = self.sign + if current != value: + self._backend.flip_sign() + + @property + def img(self) -> int: + """Get the imaginary component (0 or 1).""" + return self._backend.get_img_value() + + @img.setter + def img(self, value: int) -> None: + """Set the imaginary component.""" + # Determine how many flips needed to get to target value + current = self.img + if current != value: + # If current is 0 and we want 1, flip once + # If current is 1 and we want 0, flip 3 times (or once more to cycle back) + if value == 1 and current == 0: + self._backend.flip_img(1) + elif value == 0 and current == 1: + self._backend.flip_img(3) + def flip_sign(self) -> None: """Flip the sign of the Pauli string.""" - self.sign += 1 - self.sign %= 2 + self._backend.flip_sign() def flip_img(self, num_is: int) -> None: """Flip the imaginary component based on number of i factors. @@ -83,13 +159,7 @@ def flip_img(self, num_is: int) -> None: Args: num_is: Number of imaginary factors to add. """ - self.img += num_is - self.img %= 4 - - if self.img in {2, 3}: - self.flip_sign() - - self.img %= 2 + self._backend.flip_img(num_is) def logical_sign(self, logical_op: QuantumCircuit) -> int: """Find the sign of a logical operator. @@ -131,7 +201,7 @@ def run_circuit( if circuit_type in {"faults", "recovery"}: self.add_faults(circuit) return None - if self.faults["X"] or self.faults["Y"] or self.faults["Z"]: + if not self._backend.is_identity(): # Only apply gates if there are faults to act on return super().run_circuit(circuit, removed_locations) return None @@ -163,94 +233,10 @@ def add_faults( symbol, locations, _ = elem if symbol in {"X", "Y", "Z"}: - if symbol == "X": - # X.I = X - # X.X = I - # X.Y = iZ - # X.Z = -iY - - yoverlap = self.faults["Y"] & locations - zoverlap = self.faults["Z"] & locations - - self.faults["Y"] -= yoverlap - self.faults["Z"] -= zoverlap - - self.faults["Y"] ^= zoverlap - self.faults["Z"] ^= yoverlap - - self.faults["X"] ^= locations - yoverlap - zoverlap - - if self.track_sign: - if yoverlap: - # X.Y = i Z - self.flip_img(len(yoverlap)) - - if zoverlap: - # X.Z = -i Y - self.flip_img(len(zoverlap)) - - if len(zoverlap) % 2: - self.flip_sign() - - elif symbol == "Z": - # Z.I = Z - # Z.X = iY - # Z.Y = -iX - # Z.Z = I - - xoverlap = self.faults["X"] & locations - yoverlap = self.faults["Y"] & locations - - self.faults["X"] -= xoverlap - self.faults["Y"] -= yoverlap - - self.faults["X"] ^= yoverlap - self.faults["Y"] ^= xoverlap - - self.faults["Z"] ^= locations - xoverlap - yoverlap - - if self.track_sign: - if xoverlap: - # Z.X = i Y - self.flip_img(len(xoverlap)) - - if yoverlap: - # Z.Y = -i X - self.flip_img(len(yoverlap)) - - if len(yoverlap) % 2: - self.flip_sign() - - else: - # Y.I = Y - # Y.X = -iZ - # Y.Y = I - # Y.Z = iX - - xoverlap = self.faults["X"] & locations - zoverlap = self.faults["Z"] & locations - - self.faults["X"] -= xoverlap - self.faults["Z"] -= zoverlap - - self.faults["X"] ^= zoverlap - self.faults["Z"] ^= xoverlap - - self.faults["Y"] ^= locations - xoverlap - zoverlap - - if self.track_sign: - if zoverlap: - # Y Z = i X - self.flip_img(len(zoverlap)) - - if xoverlap: - # Y X = -i Z - self.flip_img(len(xoverlap)) - - if len(xoverlap) % 2: - self.flip_sign() - - else: + # Convert locations to a dict for add_paulis + paulis_dict = {symbol: locations} + self._backend.add_paulis(paulis_dict) + elif symbol != "I": msg = f"Got {symbol}. Can only handle Pauli errors." raise Exception(msg) @@ -260,20 +246,7 @@ def get_str(self) -> str: Returns: String representation with sign and Pauli operators. """ - fault_dict = self.faults - - pstr = "-" if self.sign else "+" - - for q in range(self.num_qubits): - if q in fault_dict.get("X", set()): - pstr += "X" - elif q in fault_dict.get("Y", set()): - pstr += "Y" - elif q in fault_dict.get("Z", set()): - pstr += "Z" - else: - pstr += "I" - return pstr + return self._backend.to_dense_string() def fault_str_sign(self, *, strip: bool = False) -> str: """Get the sign component of the fault string. @@ -284,19 +257,19 @@ def fault_str_sign(self, *, strip: bool = False) -> str: Returns: String representation of the sign component. """ - fault_str = [] - - if self.sign: - fault_str.append("-") + sign_str = self._backend.sign_string() + + # Convert to the expected format + if sign_str == "+": + fault_str = "+ " + elif sign_str == "-": + fault_str = "- " + elif sign_str == "+i": + fault_str = "+i" + elif sign_str == "-i": + fault_str = "-i" else: - fault_str.append("+") - - if self.img: - fault_str.append("i") - else: - fault_str.append(" ") - - fault_str = "".join(fault_str) + fault_str = sign_str if strip: fault_str = fault_str.strip() @@ -309,22 +282,14 @@ def fault_str_operator(self) -> str: Returns: String representation of the Pauli operators. """ - fault_str = [] - - for q in range(self.num_qubits): - if q in self.faults["X"]: - fault_str.append("X") - - elif q in self.faults["Y"]: - fault_str.append("Y") - - elif q in self.faults["Z"]: - fault_str.append("Z") - - else: - fault_str.append("I") - - return "".join(fault_str) + # Get the dense string and remove the sign part + full_str = self._backend.to_dense_string() + # Remove the sign prefix (+, -, +i, -i) + if full_str.startswith(("+i", "-i")): + return full_str[2:] + if full_str.startswith(("+", "-")): + return full_str[1:] + return full_str def fault_string(self) -> str: """Get the complete fault string with sign and operators. @@ -332,7 +297,14 @@ def fault_string(self) -> str: Returns: Complete string representation of the fault state. """ - return f"{self.fault_str_sign()}{self.fault_str_operator()}" + # Use the backend's string representation but format it for compatibility + backend_str = self._backend.to_dense_string() + # Ensure there's a space after the sign if no 'i' + if backend_str.startswith("+") and not backend_str.startswith("+i"): + return "+ " + backend_str[1:] + if backend_str.startswith("-") and not backend_str.startswith("-i"): + return "- " + backend_str[1:] + return backend_str def fault_wt(self) -> int: """Get the weight of the fault (number of non-identity operators). @@ -340,16 +312,13 @@ def fault_wt(self) -> int: Returns: Total weight of X, Y, and Z operators. """ - wt = len(self.faults["X"]) - wt += len(self.faults["Y"]) - wt += len(self.faults["Z"]) - - return wt + return self._backend.weight() def __str__(self) -> str: """Return string representation of the Pauli fault state.""" + faults = self.faults return "{{'X': {}, 'Y': {}, 'Z': {}}}".format( - self.faults["X"], - self.faults["Y"], - self.faults["Z"], + faults["X"], + faults["Y"], + faults["Z"], ) diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/__init__.py b/python/quantum-pecos/src/pecos/simulators/projectq/__init__.py deleted file mode 100644 index 10449993d..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""ProjectQ simulator wrapper. - -This package provides a wrapper for the ProjectQ quantum simulator. -""" - -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -from pecos.simulators.projectq import bindings -from pecos.simulators.projectq.state import ProjectQSim diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/bindings.py b/python/quantum-pecos/src/pecos/simulators/projectq/bindings.py deleted file mode 100644 index b93bd6a49..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/bindings.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2018 The PECOS Developers -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Gate bindings for ProjectQ quantum simulator. - -This module provides gate operation bindings for the ProjectQ quantum simulator, organizing and exposing -Quantum gate implementations using the ProjectQ framework for full quantum circuit simulation. -""" - -from projectq import ops - -from pecos.simulators.projectq import ( - gates_init, - gates_meas, - gates_one_qubit, - gates_two_qubit, -) -from pecos.simulators.projectq.helper import MakeFunc - -# Note: More ProjectQ gates can be added by updating the wrapper's `gate_dict` attribute. - -# Note: Multiqubit gates are usually in all caps. - - -gate_dict = { - # ProjectQ specific: - "T": MakeFunc(ops.T).func, # fourth root of Z - "Tdg": MakeFunc(ops.Tdag).func, # fourth root of Z dagger - "SSWAP": MakeFunc(ops.SqrtSwap).func, - "Entangle": MakeFunc( - ops.Entangle, - ).func, # H on first qubit and CNOT to all others... - "RX": gates_one_qubit.RX, # Rotation about X (takes angle arg) - "RY": gates_one_qubit.RY, # Rotation about Y (takes angle arg) - "RZ": gates_one_qubit.RZ, # Rotation about Z (takes angle arg) - "R1XY": gates_one_qubit.R1XY, - "PhaseRot": MakeFunc( - ops.R, - angle=True, - ).func, # Phase-shift: Same as Rz but with a 1 in upper left of matrix. - "TOFFOLI": MakeFunc(ops.Toffoli).func, - "CRZ": MakeFunc(ops.CRz, angle=True).func, # Controlled-Rz gate - "CRX": MakeFunc(ops.C(ops.Rx, 1), angle=True).func, # Controlled-Rx - "CRY": MakeFunc(ops.C(ops.Ry, 1), angle=True).func, # Controlled-Ry - "RXX": gates_two_qubit.RXX, - "RYY": gates_two_qubit.RYY, - "RZZ": gates_two_qubit.RZZ, - "R2XXYYZZ": gates_two_qubit.R2XXYYZZ, - # Initialization - # ============== - "Init +Z": gates_init.init_zero, # Init by measuring (if entangle => random outcome - "Init -Z": gates_init.init_one, # Init by measuring (if entangle => random outcome - "Init +X": gates_init.init_plus, # Init by measuring (if entangle => random outcome - "Init -X": gates_init.init_minus, # Init by measuring (if entangle => random outcome - "Init +Y": gates_init.init_plusi, # Init by measuring (if entangle => random outcome - "Init -Y": gates_init.init_minusi, # Init by measuring (if entangle => random outcome - "leak": gates_init.init_zero, - "leak |0>": gates_init.init_zero, - "leak |1>": gates_init.init_one, - "unleak |0>": gates_init.init_zero, - "unleak |1>": gates_init.init_one, - # one-qubit operations - # ==================== - # Paulis # x->, z-> - "I": gates_one_qubit.Identity, # +x+z - "X": gates_one_qubit.X, # +x-z - "Y": gates_one_qubit.Y, # -x-z - "Z": gates_one_qubit.Z, # -x+z - # Square root of Paulis - "SX": gates_one_qubit.SX, # +x-y sqrt of X - "SXdg": gates_one_qubit.SXdg, # +x+y sqrt of X dagger - "SY": gates_one_qubit.SY, # -z+x sqrt of Y - "SYdg": gates_one_qubit.SYdg, # +z-x sqrt of Y dagger - "SZ": gates_one_qubit.SZ, # +y+z sqrt of Z - "SZdg": gates_one_qubit.SZdg, # -y+z sqrt of Z dagger - # Hadamard-like - "H": gates_one_qubit.H, - "H2": gates_one_qubit.H2, - "H3": gates_one_qubit.H3, - "H4": gates_one_qubit.H4, - "H5": gates_one_qubit.H5, - "H6": gates_one_qubit.H6, - # Face rotations - "F": gates_one_qubit.F, # +y+x - "Fdg": gates_one_qubit.Fdg, # +z+y - "F2": gates_one_qubit.F2, # -z+y - "F2dg": gates_one_qubit.F2dg, # -y-x - "F3": gates_one_qubit.F3, # +y-x - "F3dg": gates_one_qubit.F3dg, # -z-y - "F4": gates_one_qubit.F4, # +z-y - "F4dg": gates_one_qubit.F4dg, # -y-z - # two-qubit operations - # ==================== - "CX": gates_two_qubit.CNOT, - "CY": gates_two_qubit.CY, - "CZ": gates_two_qubit.CZ, - "SWAP": gates_two_qubit.SWAP, - "G": gates_two_qubit.G2, - "G2": gates_two_qubit.G2, - "II": gates_two_qubit.II, - # Mølmer-Sørensen gates - "SXX": gates_two_qubit.SXX, # \equiv e^{+i (\pi /4)} * e^{-i (\pi /4) XX } == R(XX, pi/2) - "SYY": gates_two_qubit.SYY, - "SZZ": gates_two_qubit.SZZ, - # Measurements - # ============ - "Measure +X": gates_meas.meas_x, # no random_output (force outcome) ! - "Measure +Y": gates_meas.meas_y, # no random_output (force outcome) ! - "Measure +Z": gates_meas.meas_z, # no random_output (force outcome) ! - "force output": gates_meas.force_output, -} diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/gates_init.py b/python/quantum-pecos/src/pecos/simulators/projectq/gates_init.py deleted file mode 100644 index a3b6f3d95..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/gates_init.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2019 The PECOS Developers -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Qubit initialization operations for ProjectQ simulator. - -This module provides quantum state initialization operations for the ProjectQ simulator, including functions to -initialize qubits to computational basis states using the ProjectQ quantum computing framework. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.simulators.projectq.state import ProjectQSim - from pecos.typing import SimulatorGateParams - -from pecos.simulators.projectq.gates_meas import meas_z -from pecos.simulators.projectq.gates_one_qubit import H2, H5, H6, H, X - - -def init_zero(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialize qubit to zero state. - - Args: - state: The ProjectQ state instance. - qubit: The qubit index to initialize. - **_params: Unused additional parameters (kept for interface compatibility). - """ - result = meas_z(state, qubit) - - if result: - X(state, qubit) - - -def init_one(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialize qubit in state |1>. - - :param state: - :param qubit: - :return: - """ - init_zero(state, qubit) - X(state, qubit) - - -def init_plus(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialize qubit in state |+>. - - :param gens: - :param qubit: - :return: - """ - init_zero(state, qubit) - H(state, qubit) - - -def init_minus(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialize qubit in state |->. - - :param gens: - :param qubit: - :return: - """ - init_zero(state, qubit) - H2(state, qubit) - - -def init_plusi(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialize qubit in state |+i>. - - :param gens: - :param qubit: - :return: - """ - init_zero(state, qubit) - H5(state, qubit) - - -def init_minusi(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Initialize qubit in state |-i>. - - Args: - ---- - state: The ProjectQ state instance - qubit: The qubit index to initialize - """ - init_zero(state, qubit) - H6(state, qubit) diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/gates_meas.py b/python/quantum-pecos/src/pecos/simulators/projectq/gates_meas.py deleted file mode 100644 index 36168787a..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/gates_meas.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright 2019 The PECOS Developers -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Quantum measurement operations for ProjectQ simulator. - -This module provides quantum measurement operations for the ProjectQ simulator, including projective measurements -with proper state collapse and sampling using the ProjectQ quantum computing framework. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.simulators.projectq.state import ProjectQSim - from pecos.typing import SimulatorGateParams - -from projectq.ops import Measure - -from pecos.simulators.projectq.gates_one_qubit import H5, H - - -def force_output( - _state: ProjectQSim, - _qubit: int, - forced_output: int = -1, - **_params: SimulatorGateParams, -) -> int: - """Outputs value. - - Used for error generators to generate outputs when replacing measurements. - - Args: - ---- - _state: Unused state parameter (kept for interface compatibility) - _qubit: Unused qubit parameter (kept for interface compatibility) - forced_output: The value to output - **_params: Unused additional parameters (kept for interface compatibility) - """ - return forced_output - - -def meas_z( - state: ProjectQSim, - qubit: int, - forced_outcome: int = -1, - **_params: SimulatorGateParams, -) -> int: - """Measurement in the Z-basis. - - Args: - state: The ProjectQ state instance - qubit: The qubit index to measure - forced_outcome: If 0 or 1, forces the measurement outcome to that value when the measurement would - otherwise be non-deterministic - **_params: Unused additional parameters (kept for interface compatibility) - """ - q = state.qids[qubit] - - state.eng.flush() - - if forced_outcome in {0, 1}: - # project the qubit to the desired state ("randomly" chooses the value `forced_outcome`) - state.eng.backend.collapse_wavefunction([q], [forced_outcome]) - # Note: this will raise an error if the probability of collapsing to this state is close to 0.0 - - return forced_outcome - - Measure | q - state.eng.flush() - - return int(q) - - -def meas_y( - state: ProjectQSim, - qubit: int, - forced_outcome: int = -1, - **_params: SimulatorGateParams, -) -> int: - """Measurement in the Y-basis. - - Args: - ---- - state: The ProjectQ state instance - qubit: The qubit index to measure - forced_outcome: If 0 or 1, forces the measurement outcome to that value when the measurement would - otherwise be non-deterministic - **_params: Unused additional parameters (kept for interface compatibility) - """ - H5(state, qubit) - meas_outcome = meas_z(state, qubit, forced_outcome) - H5(state, qubit) - - return meas_outcome - - -def meas_x( - state: ProjectQSim, - qubit: int, - forced_outcome: int = -1, - **_params: SimulatorGateParams, -) -> int: - """Measurement in the X-basis. - - Args: - ---- - state: The ProjectQ state instance - qubit: The qubit index to measure - forced_outcome: If 0 or 1, forces the measurement outcome to that value when the measurement would - otherwise be non-deterministic - **_params: Unused additional parameters (kept for interface compatibility) - """ - H(state, qubit) - meas_outcome = meas_z(state, qubit, forced_outcome) - H(state, qubit) - - return meas_outcome diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/gates_one_qubit.py b/python/quantum-pecos/src/pecos/simulators/projectq/gates_one_qubit.py deleted file mode 100644 index 5f2f0eeb0..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/gates_one_qubit.py +++ /dev/null @@ -1,310 +0,0 @@ -# Copyright 2019 The PECOS Developers -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Single-qubit gate operations for ProjectQ simulator. - -This module provides single-qubit quantum gate operations for the ProjectQ simulator, including Pauli gates, -rotation gates, Hadamard gates, and other fundamental single-qubit operations using the ProjectQ framework. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.simulators.projectq.state import ProjectQSim - from pecos.typing import SimulatorGateParams - -import numpy as np -from projectq import ops - -from pecos.simulators.projectq.helper import MakeFunc - - -def Identity(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Identity does nothing. - - X -> X - - Z -> Z - - Y -> Y - - Args: - state: The ProjectQ state instance. - qubit (int): The qubit index to apply the gate to. - - Returns: None - - """ - - -def X(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Pauli X. - - X -> X - - Z -> -Z - - Y -> -Y - - Args: - state: The ProjectQ state instance. - qubit (int): The qubit index to apply the gate to. - - Returns: None - - """ - ops.X | state.qids[qubit] - - -def Y(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """X -> -X. - - Z -> -Z - - Y -> Y - - Args: - state: The ProjectQ state instance. - qubit (int): The qubit index to apply the gate to. - - Returns: None - - """ - ops.Y | state.qids[qubit] - - -def Z(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """X -> -X. - - Z -> Z - - Y -> -Y - - Args: - state: The ProjectQ state instance. - qubit (int): The qubit index to apply the gate to. - - Returns: None - - """ - ops.Z | state.qids[qubit] - - -RX = MakeFunc(ops.Rx, angle=True).func # Rotation about X (takes angle arg) -RY = MakeFunc(ops.Ry, angle=True).func # Rotation about Y (takes angle arg) -RZ = MakeFunc(ops.Rz, angle=True).func # Rotation about Z (takes angle arg) - - -def R1XY( - state: ProjectQSim, - qubit: int, - angles: tuple[float, float], - **_params: SimulatorGateParams, -) -> None: - """Apply a single-qubit rotation gate composed of Y and Z rotations. - - R1XY(theta, phi) = U1q(theta, phi) = RZ(phi-pi/2)*RY(theta)*RZ(-phi+pi/2). - - Args: - state: The ProjectQ state instance. - qubit (int): The qubit index to apply the gate to. - angles (tuple[float, float]): A tuple of (theta, phi) rotation angles. - **_params: Unused additional parameters (kept for interface compatibility). - """ - theta = angles[0] - phi = angles[1] - - RZ(state, qubit, angle=-phi + np.pi / 2) - RY(state, qubit, angle=theta) - RZ(state, qubit, angle=phi - np.pi / 2) - - -def SX(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Square-root of X gate class.""" - RX(state, qubit, angle=np.pi / 2) - - -def SXdg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of the square-root of X gate class.""" - RX(state, qubit, angle=-np.pi / 2) - - -def SY(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Square-root of Y gate class.""" - RY(state, qubit, angle=np.pi / 2) - - -def SYdg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of the square-root of Y gate class.""" - RY(state, qubit, angle=-np.pi / 2) - - -def SZ(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Square-root of Z gate class.""" - ops.S | state.qids[qubit] - - -def SZdg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of the square-root of Z gate class.""" - ops.Sdag | state.qids[qubit] - - -def H(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Square root of Z. - - X -> Z - - Z -> X - - Y -> -Y - - Args: - state: The ProjectQ state instance. - qubit (int): The qubit index to apply the gate to. - - Returns: None - - """ - ops.H | state.qids[qubit] - - -def H2(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply H2 Hadamard variant gate (Ry(π/2) followed by Z). - - Args: - state: ProjectQ simulator state. - qubit: Target qubit index. - """ - # @property - # def matrix(self): - - ops.Ry(np.pi / 2) | state.qids[qubit] - ops.Z | state.qids[qubit] - - -def H3(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply H3 Hadamard variant gate (S followed by Y). - - Args: - state: ProjectQ simulator state. - qubit: Target qubit index. - """ - # @property - # def matrix(self): - - ops.S | state.qids[qubit] - ops.Y | state.qids[qubit] - - -def H4(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply H4 Hadamard variant gate (S followed by X). - - Args: - state: ProjectQ simulator state. - qubit: Target qubit index. - """ - # @property - # def matrix(self): - - ops.S | state.qids[qubit] - ops.X | state.qids[qubit] - - -def H5(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply H5 Hadamard variant gate (Rx(π/2) followed by Z). - - Args: - state: ProjectQ simulator state. - qubit: Target qubit index. - """ - # @property - # def matrix(self): - - ops.Rx(np.pi / 2) | state.qids[qubit] - ops.Z | state.qids[qubit] - - -def H6(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Apply H6 Hadamard variant gate (Rx(π/2) followed by Y). - - Args: - state: ProjectQ simulator state. - qubit: Target qubit index. - """ - # @property - # def matrix(self): - - ops.Rx(np.pi / 2) | state.qids[qubit] - ops.Y | state.qids[qubit] - - -def F(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Face rotations of an octahedron #1.""" - # @property - # def matrix(self): - - ops.Rx(np.pi / 2) | state.qids[qubit] - ops.Rz(np.pi / 2) | state.qids[qubit] - - -def Fdg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of face rotations of an octahedron #1.""" - ops.Rz(-np.pi / 2) | state.qids[qubit] - ops.Rx(-np.pi / 2) | state.qids[qubit] - - -def F2(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Face rotations of an octahedron #2.""" - # @property - # def matrix(self): - - ops.Rz(np.pi / 2) | state.qids[qubit] - ops.Rx(-np.pi / 2) | state.qids[qubit] - - -def F2dg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of face rotations of an octahedron #2.""" - ops.Rx(np.pi / 2) | state.qids[qubit] - ops.Rz(-np.pi / 2) | state.qids[qubit] - - -def F3(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Face rotations of an octahedron #3.""" - # @property - # def matrix(self): - - ops.Rx(-np.pi / 2) | state.qids[qubit] - ops.Rz(np.pi / 2) | state.qids[qubit] - - -def F3dg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of face rotations of an octahedron #3.""" - ops.Rz(-np.pi / 2) | state.qids[qubit] - ops.Rx(np.pi / 2) | state.qids[qubit] - - -def F4(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Face rotations of an octahedron #4.""" - # @property - # def matrix(self): - - ops.Rz(np.pi / 2) | state.qids[qubit] - ops.Rx(np.pi / 2) | state.qids[qubit] - - -def F4dg(state: ProjectQSim, qubit: int, **_params: SimulatorGateParams) -> None: - """Adjoint of face rotations of an octahedron #4.""" - ops.Rx(-np.pi / 2) | state.qids[qubit] - ops.Rz(-np.pi / 2) | state.qids[qubit] diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/gates_two_qubit.py b/python/quantum-pecos/src/pecos/simulators/projectq/gates_two_qubit.py deleted file mode 100644 index a0b0ba937..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/gates_two_qubit.py +++ /dev/null @@ -1,302 +0,0 @@ -# Copyright 2019 The PECOS Developers -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Two-qubit gate operations for ProjectQ simulator. - -This module provides two-qubit quantum gate operations for the ProjectQ simulator, including CNOT gates, -controlled gates, and other fundamental two-qubit operations using the ProjectQ framework. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.simulators.projectq.state import ProjectQSim - from pecos.typing import SimulatorGateParams - -from numpy import pi -from projectq import ops - -from pecos.simulators.projectq.gates_one_qubit import H - - -def II( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply two-qubit identity gate (no operation). - - Args: - state: ProjectQ simulator state. - qubits: Tuple of two target qubit indices. - """ - - -def G2( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Applies a CZ.H(1).H(2).CZ.""" - CZ(state, qubits) - H(state, qubits[0]) - H(state, qubits[1]) - CZ(state, qubits) - - -def CNOT( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply controlled-NOT (CNOT) gate. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of (control, target) qubit indices. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - - ops.CNOT | (q1, q2) - - -def CZ( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply controlled-Z (CZ) gate. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of (control, target) qubit indices. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - - ops.C(ops.Z) | (q1, q2) - - -def CY( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply controlled-Y (CY) gate. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of (control, target) qubit indices. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - - ops.C(ops.Y) | (q1, q2) - - -def SWAP( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Apply SWAP gate to exchange qubit states. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of two qubit indices to swap. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - - ops.Swap | (q1, q2) - - -def SXX( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Square root of XX rotation to generators. - - state (SparseSim): Instance representing the stabilizer state. - qubit (int): Integer that indexes the qubit being acted on. - - Returns: None - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rxx(pi / 2) | (q1, q2) - - -def SXXdg( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Adjoint of square root of XX rotation. - - state: Instance representing the stabilizer state. - qubit: Integer that indexes the qubit being acted on. - - Returns: None - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rxx(-pi / 2) | (q1, q2) - - -def SYY( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Square root of YY rotation to generators. - - state: Instance representing the stabilizer state. - qubit: Integer that indexes the qubit being acted on. - - Returns: None - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Ryy(pi / 2) | (q1, q2) - - -def SYYdg( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Adjoint of square root of YY rotation to generators. - - state: Instance representing the stabilizer state. - qubit: Integer that indexes the qubit being acted on. - - Returns: None - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Ryy(-pi / 2) | (q1, q2) - - -def SZZ( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Applies a square root of ZZ rotation to generators. - - state: Instance representing the stabilizer state. - qubit: Integer that indexes the qubit being acted on. - - Returns: None - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rzz(pi / 2) | (q1, q2) - - -def SZZdg( - state: ProjectQSim, - qubits: tuple[int, int], - **_params: SimulatorGateParams, -) -> None: - """Applies an adjoint of square root of ZZ rotation to generators. - - state: Instance representing the stabilizer state. - qubit: Integer that indexes the qubit being acted on. - - Returns: None - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rzz(-pi / 2) | (q1, q2) - - -def RXX( - state: ProjectQSim, - qubits: tuple[int, int], - angle: float | None = None, - **_params: SimulatorGateParams, -) -> None: - """Apply RXX rotation gate around XX axis. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of two target qubit indices. - angle: Rotation angle in radians. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rxx(angle) | (q1, q2) - - -def RYY( - state: ProjectQSim, - qubits: tuple[int, int], - angle: float | None = None, - **_params: SimulatorGateParams, -) -> None: - """Apply RYY rotation gate around YY axis. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of two target qubit indices. - angle: Rotation angle in radians. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Ryy(angle) | (q1, q2) - - -def RZZ( - state: ProjectQSim, - qubits: tuple[int, int], - angle: float | None = None, - **_params: SimulatorGateParams, -) -> None: - """Apply RZZ rotation gate around ZZ axis. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of two target qubit indices. - angle: Rotation angle in radians. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rzz(angle) | (q1, q2) - - -def R2XXYYZZ( - state: ProjectQSim, - qubits: tuple[int, int], - angles: tuple[float, float, float] | None = None, - **_params: SimulatorGateParams, -) -> None: - """Apply combined RXX, RYY, RZZ rotation gates. - - Sequentially applies RXX, RYY, and RZZ rotations with given angles. - - Args: - state: ProjectQ simulator state. - qubits: Tuple of two target qubit indices. - angles: Tuple of (RXX angle, RYY angle, RZZ angle) in radians. - """ - q1 = state.qids[qubits[0]] - q2 = state.qids[qubits[1]] - ops.Rxx(angles[0]) | (q1, q2) - ops.Ryy(angles[1]) | (q1, q2) - ops.Rzz(angles[2]) | (q1, q2) diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/helper.py b/python/quantum-pecos/src/pecos/simulators/projectq/helper.py deleted file mode 100644 index 1aee79948..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/helper.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2019 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Helper utilities for ProjectQ simulator. - -This module provides helper utilities and utility functions for the ProjectQ simulator, including common operations -and support functions used across the ProjectQ-based quantum simulation components. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from projectq.ops._basics import BasicGate - - from pecos.simulators.projectq.state import ProjectQSim - from pecos.typing import Location, SimulatorGateParams - - -class MakeFunc: - """Converts ProjectQ gate to a function.""" - - def __init__( - self, - gate: BasicGate | type[BasicGate], - *, - angle: bool = False, - ) -> None: - """Initialize MakeFunc with a gate. - - Args: - gate: The ProjectQ gate to wrap. - angle (bool): Whether the gate takes an angle parameter. - """ - self.gate = gate - self.angle = angle - - def func( - self, - state: ProjectQSim, - qubits: Location, - **params: SimulatorGateParams, - ) -> None: - """Apply the wrapped ProjectQ gate to the quantum state. - - Args: - state: The ProjectQ simulator state. - qubits: Qubit location(s) to apply the gate to. - **params: Additional gate parameters (e.g., angles). - """ - if isinstance(qubits, int): - qs = state.qids[qubits] - else: - qs = [] - for q in qubits: - qs.append(state.qids[q]) - - if self.angle: - self.gate(params["angle"]) | qs - else: - self.gate | qs diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/logical_sign.py b/python/quantum-pecos/src/pecos/simulators/projectq/logical_sign.py deleted file mode 100644 index ba984a8a7..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/logical_sign.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2019 The PECOS Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""Logical sign tracking for ProjectQ simulator. - -This module provides logical sign tracking functionality for the ProjectQ simulator, managing global phase and -logical operator signs that arise during quantum circuit execution using the ProjectQ framework. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from projectq.ops import QubitOperator - -if TYPE_CHECKING: - from pecos.circuits import QuantumCircuit - from pecos.simulators.projectq.state import ProjectQSim - - -def find_logical_signs( - state: ProjectQSim, - logical_circuit: QuantumCircuit, - *, - allow_float: bool = False, -) -> int | float: - """Find the sign of the logical operator. - - Args: - ---- - state: The ProjectQ state instance - logical_circuit: The logical circuit to find the sign of - allow_float: Whether to allow floating point results - """ - if len(logical_circuit) != 1: - msg = "Logical operators are expected to only have one tick." - raise Exception(msg) - - logical_xs = set() - logical_zs = set() - - op_string = [] - - for symbol, gate_locations, _ in logical_circuit.items(): - if symbol == "X": - logical_xs.update(gate_locations) - op_string.extend(f"X{loc}" for loc in gate_locations) - elif symbol == "Z": - logical_zs.update(gate_locations) - op_string.extend(f"Z{loc}" for loc in gate_locations) - elif symbol == "Y": - logical_xs.update(gate_locations) - logical_zs.update(gate_locations) - op_string.extend(f"Y{loc}" for loc in gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - op_string = " ".join(op_string) - state.eng.flush() - result = state.eng.backend.get_expectation_value( - QubitOperator(op_string), - state.qureg, - ) - - if not allow_float: - result = round(result, 5) - if result == -1: - return 1 - if result == 1: - return 0 - print("Operator being measured:", op_string) - print("RESULT FOUND:", result) - msg = "Unexpected result found!" - raise Exception(msg) - - return result diff --git a/python/quantum-pecos/src/pecos/simulators/projectq/state.py b/python/quantum-pecos/src/pecos/simulators/projectq/state.py deleted file mode 100644 index 001b05525..000000000 --- a/python/quantum-pecos/src/pecos/simulators/projectq/state.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright 2018 The PECOS Developers -# Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract -# DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with -# the License.You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -"""A simple wrapper for the ProjectQ simulator. - -Compatibility checked for: ProjectQ version 0.5.1 -""" - -from __future__ import annotations - -import contextlib -from typing import TYPE_CHECKING - -import numpy as np -from projectq import MainEngine -from projectq.ops import All, Measure - -from pecos.simulators.gate_syms import alt_symbols -from pecos.simulators.projectq import bindings -from pecos.simulators.projectq.helper import MakeFunc -from pecos.simulators.projectq.logical_sign import find_logical_signs -from pecos.simulators.sim_class_types import StateVector - -if TYPE_CHECKING: - from collections.abc import Callable - - from projectq.ops._basics import BasicGate - - from pecos.circuits import QuantumCircuit - from pecos.typing import Location, SimulatorGateParams - - -class ProjectQSim(StateVector): - """Initializes the stabilizer state. - - Args: - ---- - num_qubits (int): Number of qubits being represented. - """ - - def __init__(self, num_qubits: int) -> None: - """Initialize the ProjectQ quantum simulator state. - - Args: - num_qubits: Number of qubits to simulate. - - Raises: - TypeError: If num_qubits is not an integer. - """ - if not isinstance(num_qubits, int): - msg = f"`num_qubits` should be of type `int.` but got type: {type(num_qubits)} " - raise TypeError(msg) - - super().__init__() - - self.bindings = bindings.gate_dict - for k, v in alt_symbols.items(): - if v in self.bindings: - self.bindings[k] = self.bindings[v] - - self.num_qubits = num_qubits - self.eng = MainEngine() - - self.qureg = self.eng.allocate_qureg(num_qubits) - self.qs = list(self.qureg) - self.qids = dict(enumerate(self.qs)) - self.gate_dict = {} - - def reset(self) -> ProjectQSim: - """Reset the quantum state to all 0 for another run without reinitializing.""" - self.eng.flush() - amps = [0] * 2**self.num_qubits - amps[0] = 1 - self.eng.backend.set_wavefunction(amps, self.qureg) - return self - - def logical_sign(self, logical_op: QuantumCircuit) -> int: - """Find the sign of a logical operator. - - Args: - logical_op (QuantumCircuit): The logical operator circuit. - """ - return find_logical_signs(self, logical_op) - - def add_gate( - self, - symbol: str, - gate_obj: ( - BasicGate - | type[BasicGate] - | Callable[[ProjectQSim, Location, SimulatorGateParams], None] - ), - *, - make_func: bool = True, - ) -> None: - """Adds a new gate on the fly to this Simulator. - - Args: - ---- - symbol: The symbol/name for the gate - gate_obj: The gate object to add - make_func: Whether to wrap the gate object with MakeFunc - """ - if symbol in self.gate_dict: - print("WARNING: Can not add gate as the symbol has already been taken.") - elif make_func: - self.gate_dict[symbol] = MakeFunc(gate_obj).func - else: - self.gate_dict[symbol] = gate_obj - - def get_probs(self, key_basis: list[str] | None = None) -> dict[str, float]: - """Get measurement probabilities for computational basis states. - - Args: - key_basis: Optional list of basis states to get probabilities for. - If None, returns probabilities for all 2^n basis states. - - Returns: - Dictionary mapping basis state strings to their probabilities. - """ - self.eng.flush() - - if key_basis: - probs_dict = {} - for b in key_basis: - # ProjectQ uses reversed bit order - b_reversed = b[::-1] - p = self.eng.backend.get_probability(b_reversed, self.qureg) - probs_dict[b] = p - return probs_dict - - probs_dict = {} - for i in range(np.power(2, self.num_qubits)): - b_str = format(i, f"0{self.num_qubits}b") - p = self.eng.backend.get_probability(b_str, self.qureg) - # Store with reversed bit order for consistent output - b_key = b_str[::-1] - probs_dict[b_key] = p - - return probs_dict - - def get_amps(self, key_basis: list[str] | None = None) -> dict[str, complex]: - """Get probability amplitudes for computational basis states. - - Args: - key_basis: Optional list of basis states to get amplitudes for. - If None, returns amplitudes for all 2^n basis states. - - Returns: - Dictionary mapping basis state strings to their complex amplitudes. - """ - self.eng.flush() - - if key_basis: - amps_dict = {} - for b in key_basis: - # ProjectQ uses reversed bit order - b_reversed = b[::-1] - p = self.eng.backend.get_amplitude(b_reversed, self.qureg) - amps_dict[b] = p - return amps_dict - - amp_dict = {} - for i in range(np.power(2, self.num_qubits)): - b_str = format(i, f"0{self.num_qubits}b") - a = self.eng.backend.get_amplitude(b_str, self.qureg) - # Store with reversed bit order for consistent output - b_key = b_str[::-1] - amp_dict[b_key] = a - - return amp_dict - - def __del__(self) -> None: - """Clean up ProjectQ engine and deallocate qubits when the object is destroyed.""" - self.eng.flush() - All(Measure) | self.qureg # Requirement by ProjectQ... - - with contextlib.suppress(KeyError): - self.eng.flush(deallocate_qubits=True) diff --git a/python/quantum-pecos/src/pecos/simulators/quantum_simulator.py b/python/quantum-pecos/src/pecos/simulators/quantum_simulator.py index 12e4b05a7..b03de7465 100644 --- a/python/quantum-pecos/src/pecos/simulators/quantum_simulator.py +++ b/python/quantum-pecos/src/pecos/simulators/quantum_simulator.py @@ -21,15 +21,11 @@ from typing import Any from pecos.reps.pypmir.op_types import QOp -from pecos.simulators import StateVecRs +from pecos.simulators import StateVec from pecos.simulators.sparsesim.state import SparseSim JSONType = dict[str, Any] | list[Any] | str | int | float | bool | None -try: - from pecos.simulators.projectq.state import ProjectQSim -except ImportError: - ProjectQSim = None try: from pecos.simulators import MPS @@ -92,9 +88,9 @@ def init(self, num_qubits: int) -> None: if Qulacs is not None: self.state = Qulacs else: - self.state = StateVecRs - elif "ProjectQSim": - self.state = ProjectQSim + self.state = StateVec + elif self.backend == "StateVec": + self.state = StateVec elif self.backend in {"MPS", "mps"}: self.state = MPS elif self.backend == "Qulacs": @@ -102,7 +98,7 @@ def init(self, num_qubits: int) -> None: elif self.backend == "CuStateVec": self.state = CuStateVec else: - msg = f"simulator `{self.state}` not currently implemented!" + msg = f"simulator `{self.backend}` not currently implemented!" raise NotImplementedError(msg) if self.backend is None: diff --git a/python/quantum-pecos/src/pecos/simulators/basic_sv/__init__.py b/python/quantum-pecos/src/pecos/simulators/statevec/__init__.py similarity index 60% rename from python/quantum-pecos/src/pecos/simulators/basic_sv/__init__.py rename to python/quantum-pecos/src/pecos/simulators/statevec/__init__.py index 8d004faca..a955d6046 100644 --- a/python/quantum-pecos/src/pecos/simulators/basic_sv/__init__.py +++ b/python/quantum-pecos/src/pecos/simulators/statevec/__init__.py @@ -1,9 +1,4 @@ -"""Basic state vector simulator. - -This package provides a basic NumPy-based state vector quantum simulator. -""" - -# Copyright 2024 The PECOS Developers +# Copyright 2025 The PECOS Developers # # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with # the License.You may obtain a copy of the License at @@ -14,5 +9,12 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -from pecos.simulators.basic_sv import bindings -from pecos.simulators.basic_sv.state import BasicSV +"""Quantum state vector simulator for PECOS. + +This module provides a quantum state vector simulator with a high-performance Rust backend, enabling efficient +simulation of arbitrary quantum circuits with full quantum state representation. +""" + +from pecos.simulators.statevec.state import StateVec + +__all__ = ["StateVec"] diff --git a/python/quantum-pecos/src/pecos/simulators/statevec/bindings.py b/python/quantum-pecos/src/pecos/simulators/statevec/bindings.py new file mode 100644 index 000000000..62651248f --- /dev/null +++ b/python/quantum-pecos/src/pecos/simulators/statevec/bindings.py @@ -0,0 +1,264 @@ +# Copyright 2025 The PECOS Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License.You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +"""Gate bindings for the state vector simulator. + +This module provides the gate bindings that map gate symbols to their corresponding implementations +in the Rust backend for the state vector simulator. +""" + +# ruff: noqa: ARG005 + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pecos.simulators.statevec.state import StateVec + + +def get_bindings(state: StateVec) -> dict: + """Get gate bindings for the state vector simulator. + + Args: + state: The StateVec instance to bind gates to. + + Returns: + Dictionary mapping gate symbols to their implementations. + """ + # Get reference to backend's internal simulator for efficiency + sim = state.backend._sim # noqa: SLF001 + + return { + # Single-qubit gates + "I": lambda s, q, **p: None, + "X": lambda s, q, **p: sim.run_1q_gate("X", q, p), + "Y": lambda s, q, **p: sim.run_1q_gate("Y", q, p), + "Z": lambda s, q, **p: sim.run_1q_gate("Z", q, p), + "SX": lambda s, q, **p: sim.run_1q_gate("SX", q, p), + "SXdg": lambda s, q, **p: sim.run_1q_gate("SXdg", q, p), + "SY": lambda s, q, **p: sim.run_1q_gate("SY", q, p), + "SYdg": lambda s, q, **p: sim.run_1q_gate("SYdg", q, p), + "SZ": lambda s, q, **p: sim.run_1q_gate("SZ", q, p), + "SZdg": lambda s, q, **p: sim.run_1q_gate("SZdg", q, p), + "H": lambda s, q, **p: sim.run_1q_gate("H", q, p), + "H1": lambda s, q, **p: sim.run_1q_gate("H", q, p), + "H2": lambda s, q, **p: sim.run_1q_gate("H2", q, p), + "H3": lambda s, q, **p: sim.run_1q_gate("H3", q, p), + "H4": lambda s, q, **p: sim.run_1q_gate("H4", q, p), + "H5": lambda s, q, **p: sim.run_1q_gate("H5", q, p), + "H6": lambda s, q, **p: sim.run_1q_gate("H6", q, p), + "H+z+x": lambda s, q, **p: sim.run_1q_gate("H", q, p), + "H-z-x": lambda s, q, **p: sim.run_1q_gate("H2", q, p), + "H+y-z": lambda s, q, **p: sim.run_1q_gate("H3", q, p), + "H-y-z": lambda s, q, **p: sim.run_1q_gate("H4", q, p), + "H-x+y": lambda s, q, **p: sim.run_1q_gate("H5", q, p), + "H-x-y": lambda s, q, **p: sim.run_1q_gate("H6", q, p), + "F": lambda s, q, **p: sim.run_1q_gate("F", q, p), + "Fdg": lambda s, q, **p: sim.run_1q_gate("Fdg", q, p), + "F2": lambda s, q, **p: sim.run_1q_gate("F2", q, p), + "F2dg": lambda s, q, **p: sim.run_1q_gate("F2dg", q, p), + "F3": lambda s, q, **p: sim.run_1q_gate("F3", q, p), + "F3dg": lambda s, q, **p: sim.run_1q_gate("F3dg", q, p), + "F4": lambda s, q, **p: sim.run_1q_gate("F4", q, p), + "F4dg": lambda s, q, **p: sim.run_1q_gate("F4dg", q, p), + "T": lambda s, q, **p: sim.run_1q_gate("T", q, p), + "Tdg": lambda s, q, **p: sim.run_1q_gate("Tdg", q, p), + # Two-qubit gates + "II": lambda s, qs, **p: None, + "CX": lambda s, qs, **p: sim.run_2q_gate( + "CX", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "CNOT": lambda s, qs, **p: sim.run_2q_gate( + "CX", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "CY": lambda s, qs, **p: sim.run_2q_gate( + "CY", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "CZ": lambda s, qs, **p: sim.run_2q_gate( + "CZ", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SXX": lambda s, qs, **p: sim.run_2q_gate( + "SXX", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SXXdg": lambda s, qs, **p: sim.run_2q_gate( + "SXXdg", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SYY": lambda s, qs, **p: sim.run_2q_gate( + "SYY", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SYYdg": lambda s, qs, **p: sim.run_2q_gate( + "SYYdg", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SZZ": lambda s, qs, **p: sim.run_2q_gate( + "SZZ", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SZZdg": lambda s, qs, **p: sim.run_2q_gate( + "SZZdg", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SWAP": lambda s, qs, **p: sim.run_2q_gate( + "SWAP", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "G": lambda s, qs, **p: sim.run_2q_gate( + "G2", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "G2": lambda s, qs, **p: sim.run_2q_gate( + "G2", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + # Measurements + "MZ": lambda s, q, **p: sim.run_1q_gate("MZ", q, p), + "MX": lambda s, q, **p: sim.run_1q_gate("MX", q, p), + "MY": lambda s, q, **p: sim.run_1q_gate("MY", q, p), + "Measure +X": lambda s, q, **p: sim.run_1q_gate("MX", q, p), + "Measure +Y": lambda s, q, **p: sim.run_1q_gate("MY", q, p), + "Measure +Z": lambda s, q, **p: sim.run_1q_gate("MZ", q, p), + "Measure": lambda s, q, **p: sim.run_1q_gate("MZ", q, p), + "measure Z": lambda s, q, **p: sim.run_1q_gate("MZ", q, p), + # Projections/Initializations + "PZ": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "PX": lambda s, q, **p: sim.run_1q_gate("PX", q, p), + "PY": lambda s, q, **p: sim.run_1q_gate("PY", q, p), + "PnZ": lambda s, q, **p: sim.run_1q_gate("PnZ", q, p), + "Init": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "Init +Z": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "Init -Z": lambda s, q, **p: sim.run_1q_gate("PnZ", q, p), + "Init +X": lambda s, q, **p: sim.run_1q_gate("PX", q, p), + "Init -X": lambda s, q, **p: sim.run_1q_gate("PnX", q, p), + "Init +Y": lambda s, q, **p: sim.run_1q_gate("PY", q, p), + "Init -Y": lambda s, q, **p: sim.run_1q_gate("PnY", q, p), + "init |0>": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "init |1>": lambda s, q, **p: sim.run_1q_gate("PnZ", q, p), + "init |+>": lambda s, q, **p: sim.run_1q_gate("PX", q, p), + "init |->": lambda s, q, **p: sim.run_1q_gate("PnX", q, p), + "init |+i>": lambda s, q, **p: sim.run_1q_gate("PY", q, p), + "init |-i>": lambda s, q, **p: sim.run_1q_gate("PnY", q, p), + "leak": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "leak |0>": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "leak |1>": lambda s, q, **p: sim.run_1q_gate("PnZ", q, p), + "unleak |0>": lambda s, q, **p: sim.run_1q_gate("PZ", q, p), + "unleak |1>": lambda s, q, **p: sim.run_1q_gate("PnZ", q, p), + # Aliases + "Q": lambda s, q, **p: sim.run_1q_gate("SX", q, p), + "Qd": lambda s, q, **p: sim.run_1q_gate("SXdg", q, p), + "R": lambda s, q, **p: sim.run_1q_gate("SY", q, p), + "Rd": lambda s, q, **p: sim.run_1q_gate("SYdg", q, p), + "S": lambda s, q, **p: sim.run_1q_gate("SZ", q, p), + "Sd": lambda s, q, **p: sim.run_1q_gate("SZdg", q, p), + "F1": lambda s, q, **p: sim.run_1q_gate("F", q, p), + "F1d": lambda s, q, **p: sim.run_1q_gate("Fdg", q, p), + "F2d": lambda s, q, **p: sim.run_1q_gate("F2dg", q, p), + "F3d": lambda s, q, **p: sim.run_1q_gate("F3dg", q, p), + "F4d": lambda s, q, **p: sim.run_1q_gate("F4dg", q, p), + "SqrtX": lambda s, q, **p: sim.run_1q_gate("SX", q, p), + "SqrtXd": lambda s, q, **p: sim.run_1q_gate("SXdg", q, p), + "SqrtY": lambda s, q, **p: sim.run_1q_gate("SY", q, p), + "SqrtYd": lambda s, q, **p: sim.run_1q_gate("SYdg", q, p), + "SqrtZ": lambda s, q, **p: sim.run_1q_gate("SZ", q, p), + "SqrtZd": lambda s, q, **p: sim.run_1q_gate("SZdg", q, p), + "SqrtXX": lambda s, qs, **p: sim.run_2q_gate( + "SXX", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SqrtYY": lambda s, qs, **p: sim.run_2q_gate( + "SYY", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SqrtZZ": lambda s, qs, **p: sim.run_2q_gate( + "SZZ", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SqrtXXd": lambda s, qs, **p: sim.run_2q_gate( + "SXXdg", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SqrtYYd": lambda s, qs, **p: sim.run_2q_gate( + "SYYdg", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + "SqrtZZd": lambda s, qs, **p: sim.run_2q_gate( + "SZZdg", + tuple(qs) if isinstance(qs, list) else qs, + p, + ), + # Rotation gates + "RX": lambda s, q, **p: sim.run_1q_gate( + "RX", + q, + {"angle": p["angles"][0]} if "angles" in p else {"angle": 0}, + ), + "RY": lambda s, q, **p: sim.run_1q_gate( + "RY", + q, + {"angle": p["angles"][0]} if "angles" in p else {"angle": 0}, + ), + "RZ": lambda s, q, **p: sim.run_1q_gate( + "RZ", + q, + {"angle": p["angles"][0]} if "angles" in p else {"angle": 0}, + ), + "R1XY": lambda s, q, **p: sim.run_1q_gate("R1XY", q, {"angles": p["angles"]}), + "RXX": lambda s, qs, **p: sim.run_2q_gate( + "RXX", + tuple(qs) if isinstance(qs, list) else qs, + {"angle": p["angles"][0]} if "angles" in p else {"angle": 0}, + ), + "RYY": lambda s, qs, **p: sim.run_2q_gate( + "RYY", + tuple(qs) if isinstance(qs, list) else qs, + {"angle": p["angles"][0]} if "angles" in p else {"angle": 0}, + ), + "RZZ": lambda s, qs, **p: sim.run_2q_gate( + "RZZ", + tuple(qs) if isinstance(qs, list) else qs, + {"angle": p["angles"][0]} if "angles" in p else {"angle": 0}, + ), + "RZZRYYRXX": lambda s, qs, **p: sim.run_2q_gate( + "RZZRYYRXX", + tuple(qs) if isinstance(qs, list) else qs, + {"angles": p["angles"]} if "angles" in p else {"angles": [0, 0, 0]}, + ), + "R2XXYYZZ": lambda s, qs, **p: sim.run_2q_gate( + "RZZRYYRXX", + tuple(qs) if isinstance(qs, list) else qs, + {"angles": p["angles"]} if "angles" in p else {"angles": [0, 0, 0]}, + ), + } diff --git a/python/quantum-pecos/src/pecos/simulators/statevec/state.py b/python/quantum-pecos/src/pecos/simulators/statevec/state.py new file mode 100644 index 000000000..a09de5f9b --- /dev/null +++ b/python/quantum-pecos/src/pecos/simulators/statevec/state.py @@ -0,0 +1,130 @@ +# Copyright 2025 The PECOS Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License.You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +"""State vector simulator implementation. + +This module provides the StateVec class, a quantum state vector simulator that uses a high-performance Rust backend +for efficient quantum circuit simulation with full quantum state representation. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pecos_rslib import StateVecRs + +from pecos.simulators.statevec.bindings import get_bindings + +if TYPE_CHECKING: + from pecos.circuits import QuantumCircuit + from pecos.circuits.quantum_circuit import ParamGateCollection + from pecos.typing import SimulatorGateParams + + +class StateVec: + """Quantum state vector simulator. + + A quantum state vector simulator that uses a high-performance Rust backend (StateVecRs) for efficient + simulation of arbitrary quantum circuits with full quantum state representation. + """ + + def __init__(self, num_qubits: int, seed: int | None = None) -> None: + """Initializes the state vector simulator. + + Args: + num_qubits (int): The number of qubits in the quantum system. + seed (int | None): Optional seed for the random number generator. + """ + self.backend = StateVecRs(num_qubits, seed) + self.num_qubits = num_qubits + self.bindings = get_bindings(self) + + @property + def vector(self) -> list[complex]: + """Get the state vector as a list of complex numbers. + + Returns: + List of complex amplitudes representing the quantum state. + """ + return self.backend.vector + + def reset(self) -> StateVec: + """Resets the quantum state to the all-zero state.""" + self.backend.reset() + return self + + def run_gate( + self, + symbol: str, + locations: set[int] | set[tuple[int, ...]], + **params: SimulatorGateParams, + ) -> dict[int, int]: + """Applies a gate to the quantum state. + + Args: + symbol (str): The gate symbol (e.g., "X", "H", "CX"). + locations (set): The qubit(s) to which the gate is applied. + params (dict, optional): Parameters for the gate (e.g., rotation angles). + + Returns: + Dictionary mapping locations to measurement results. + """ + output = {} + + if params.get("simulate_gate", True) and locations: + for location in locations: + if params.get("angles") and len(params["angles"]) == 1: + params.update({"angle": params["angles"][0]}) + elif "angle" in params and "angles" not in params: + params["angles"] = (params["angle"],) + + # Convert list to tuple if needed (for Rust bindings compatibility) + if isinstance(location, list): + location = tuple(location) # noqa: PLW2901 + + if symbol in self.bindings: + results = self.bindings[symbol](self, location, **params) + else: + msg = f"Gate {symbol} is not supported in this simulator." + raise Exception(msg) + + if results: + output[location] = results + + return output + + def run_circuit( + self, + circuit: QuantumCircuit | ParamGateCollection, + removed_locations: set[int] | None = None, + ) -> dict[int, int]: + """Execute a quantum circuit. + + Args: + circuit: Quantum circuit to execute. + removed_locations: Optional set of locations to exclude. + + Returns: + Dictionary mapping locations to measurement results. + """ + if removed_locations is None: + removed_locations = set() + + results = {} + for symbol, locations, params in circuit.items(): + gate_results = self.run_gate( + symbol, + locations - removed_locations, + **params, + ) + results.update(gate_results) + + return results diff --git a/python/slr-tests/pytest.ini b/python/slr-tests/pytest.ini index 2b202317d..8002642ab 100644 --- a/python/slr-tests/pytest.ini +++ b/python/slr-tests/pytest.ini @@ -15,8 +15,6 @@ markers = wasmer: mark test as using the "wasmer" option. wasmtime: mark test as using the "wasmtime" option. -# ProjectQ has a bunch of deprecation warnings from NumPy because they still use np.matrix instead of np.array -# TODO: comment this to deal with ProjectQ gate warnings +# Ignore various deprecation warnings filterwarnings = - ignore::PendingDeprecationWarning:projectq.ops._gates ignore::DeprecationWarning:dateutil.tz.tz.* diff --git a/python/tests/pecos/integration/state_sim_tests/test_statevec.py b/python/tests/pecos/integration/state_sim_tests/test_statevec.py index 1d93e067f..31dbbe2b6 100644 --- a/python/tests/pecos/integration/state_sim_tests/test_statevec.py +++ b/python/tests/pecos/integration/state_sim_tests/test_statevec.py @@ -29,15 +29,13 @@ from pecos.error_models.generic_error_model import GenericErrorModel from pecos.simulators import ( MPS, - BasicSV, CuStateVec, Qulacs, - StateVecRs, + StateVec, ) str_to_sim = { - "StateVecRS": StateVecRs, - "BasicSV": BasicSV, + "StateVec": StateVec, "Qulacs": Qulacs, "CuStateVec": CuStateVec, "MPS": MPS, @@ -107,20 +105,20 @@ def check_measurement( assert np.allclose(abs_values_vector, final_vector) -def compare_against_basicsv(simulator: str, qc: QuantumCircuit) -> None: - """Compare simulator results against BasicSV reference implementation.""" - basicsv = BasicSV(len(qc.qudits)) - basicsv.run_circuit(qc) +def compare_against_statevec(simulator: str, qc: QuantumCircuit) -> None: + """Compare simulator results against StateVec reference implementation.""" + statevec = StateVec(len(qc.qudits)) + statevec.run_circuit(qc) sim = check_dependencies(simulator)(len(qc.qudits)) sim.run_circuit(qc) print(f"[COMPARE] Simulator: {simulator}") - print(f"[COMPARE] BasicSV vector: {basicsv.vector}") + print(f"[COMPARE] StateVec vector: {statevec.vector}") print(f"[COMPARE] sim.vector: {sim.vector}") # Use updated verify function - verify(simulator, qc, basicsv.vector) + verify(simulator, qc, statevec.vector) def generate_random_state(seed: int | None = None) -> QuantumCircuit: @@ -153,8 +151,7 @@ def generate_random_state(seed: int | None = None) -> QuantumCircuit: @pytest.mark.parametrize( "simulator", [ - "StateVecRS", - "BasicSV", + "StateVec", "Qulacs", "CuStateVec", "MPS", @@ -174,8 +171,7 @@ def test_init(simulator: str) -> None: @pytest.mark.parametrize( "simulator", [ - "StateVecRS", - "BasicSV", + "StateVec", "Qulacs", "CuStateVec", "MPS", @@ -193,8 +189,7 @@ def test_H_measure(simulator: str) -> None: @pytest.mark.parametrize( "simulator", [ - "StateVecRS", - "BasicSV", + "StateVec", "Qulacs", "CuStateVec", "MPS", @@ -240,7 +235,7 @@ def test_comp_basis_circ_and_measure(simulator: str) -> None: @pytest.mark.parametrize( "simulator", [ - "StateVecRS", + "StateVec", "Qulacs", "CuStateVec", "MPS", @@ -254,136 +249,136 @@ def test_all_gate_circ(simulator: str) -> None: qcs.append(generate_random_state(seed=5555)) qcs.append(generate_random_state(seed=42)) - # Verify that each of these states matches with BasicSV + # Verify that each of these states matches with StateVec for qc in qcs: - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) # Apply each gate on randomly generated states and compare again for qc in qcs: qc.append({"SZZ": {(4, 2)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"RX": {0, 2}}, angles=(np.pi / 4,)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SXXdg": {(0, 3)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"RY": {0, 3}}, angles=(np.pi / 8,)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"RZZ": {(0, 3)}}, angles=(np.pi / 16,)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"RZ": {1, 4}}, angles=(np.pi / 16,)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"R1XY": {2}}, angles=(np.pi / 16, np.pi / 2)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"I": {0, 1, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"X": {1, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Y": {3, 4}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"CY": {(2, 3), (4, 1)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SYY": {(1, 4)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Z": {2, 0}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H": {3, 1}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"RYY": {(2, 1)}}, angles=(np.pi / 8,)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SZZdg": {(3, 1)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F": {0, 2, 4}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"CX": {(0, 1), (4, 2)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Fdg": {3, 1}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SYYdg": {(1, 3)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SX": {1, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"R2XXYYZZ": {(0, 4)}}, angles=(np.pi / 4, np.pi / 16, np.pi / 2)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SY": {3, 4}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SZ": {2, 0}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SZdg": {1, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"CZ": {(1, 3)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SXdg": {3, 4}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SYdg": {2, 0}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"T": {0, 2, 4}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SXX": {(0, 2)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"SWAP": {(4, 0)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Tdg": {3, 1}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"RXX": {(1, 3)}}, angles=(np.pi / 4,)) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Q": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Qd": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"R": {0}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Rd": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"S": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"Sd": {0}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H1": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H2": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H3": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H4": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H5": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H6": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H+z+x": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H-z-x": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H+y-z": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H-y-z": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H-x+y": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"H-x-y": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F1": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F1d": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F2": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F2d": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F3": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F3d": {1, 4, 2}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F4": {2, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"F4d": {0, 3}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"CNOT": {(0, 1)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"G": {(1, 3)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) qc.append({"II": {(4, 2)}}) - compare_against_basicsv(simulator, qc) + compare_against_statevec(simulator, qc) # Measure qc.append({"Measure": {0, 1, 2, 3, 4}}) @@ -393,7 +388,7 @@ def test_all_gate_circ(simulator: str) -> None: @pytest.mark.parametrize( "simulator", [ - "StateVecRs", + "StateVec", "MPS", "Qulacs", "CuStateVec", @@ -424,7 +419,7 @@ def test_hybrid_engine_no_noise(simulator: str) -> None: @pytest.mark.parametrize( "simulator", [ - "StateVecRs", + "StateVec", "MPS", "Qulacs", "CuStateVec", diff --git a/python/tests/pecos/integration/test_phir.py b/python/tests/pecos/integration/test_phir.py index 89f74e886..59ff19135 100644 --- a/python/tests/pecos/integration/test_phir.py +++ b/python/tests/pecos/integration/test_phir.py @@ -251,7 +251,6 @@ def test_qparallel() -> None: assert m.count("1111") == len(m) -@pytest.mark.optional_dependency # uses projectq / state-vector def test_bell_qparallel() -> None: """Testing a program creating and measuring a Bell state and using qparallel blocks returns expected results.""" results = HybridEngine(qsim="state-vector").run( diff --git a/python/tests/pytest.ini b/python/tests/pytest.ini index a32cbbce2..9a9242b32 100644 --- a/python/tests/pytest.ini +++ b/python/tests/pytest.ini @@ -15,8 +15,6 @@ markers = wasmer: mark test as using the "wasmer" option. wasmtime: mark test as using the "wasmtime" option. -# ProjectQ has a bunch of deprecation warnings from NumPy because they still use np.matrix instead of np.array -# TODO: comment this to deal with ProjectQ gate warnings +# Ignore cuQuantum deprecation warnings filterwarnings = - ignore::PendingDeprecationWarning:projectq.* ignore::DeprecationWarning:cuquantum diff --git a/python/tests/test_pauli_prop_rust_backend.py b/python/tests/test_pauli_prop_rust_backend.py new file mode 100644 index 000000000..d43fb2e46 --- /dev/null +++ b/python/tests/test_pauli_prop_rust_backend.py @@ -0,0 +1,172 @@ +# Copyright 2025 The PECOS Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License.You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +"""Test the PauliProp with Rust backend.""" + +import pytest +from pecos.circuits import QuantumCircuit +from pecos.simulators import PauliProp + + +def test_pauli_fault_prop_basic() -> None: + """Test basic functionality of PauliProp with Rust backend.""" + state = PauliProp(num_qubits=4, track_sign=True) + + # Initially empty + assert state.faults == {"X": set(), "Y": set(), "Z": set()} + assert state.fault_wt() == 0 + assert state.sign == 0 + assert state.img == 0 + + # Add faults via circuit + qc = QuantumCircuit() + qc.append({"X": {0, 1}, "Z": {2}, "Y": {3}}) + state.add_faults(qc) + + # Check faults were added + assert 0 in state.faults["X"] + assert 1 in state.faults["X"] + assert 2 in state.faults["Z"] + assert 3 in state.faults["Y"] + assert state.fault_wt() == 4 + + # Check string representations + assert state.get_str() == "+XXZY" + assert state.fault_str_operator() == "XXZY" + + +def test_pauli_fault_prop_composition() -> None: + """Test Pauli composition with the Rust backend.""" + state = PauliProp(num_qubits=3, track_sign=True) + + # Add X to qubit 0 + qc1 = QuantumCircuit() + qc1.append({"X": {0}}) + state.add_faults(qc1) + + # Add Z to qubit 0 (X * Z = -iY) + qc2 = QuantumCircuit() + qc2.append({"Z": {0}}) + state.add_faults(qc2) + + # Should now have Y on qubit 0 + assert 0 in state.faults["Y"] + assert 0 not in state.faults["X"] + assert 0 not in state.faults["Z"] + + # Check phase tracking + assert state.img == 1 # Should have i factor + assert state.sign == 1 # Should have negative sign + + +def test_pauli_fault_prop_sign_tracking() -> None: + """Test sign and phase tracking.""" + state = PauliProp(num_qubits=2, track_sign=True) + + # Test sign flipping + assert state.sign == 0 + state.flip_sign() + assert state.sign == 1 + state.flip_sign() + assert state.sign == 0 + + # Test imaginary component + assert state.img == 0 + state.flip_img(1) + assert state.img == 1 + state.flip_img(2) + assert state.img == 1 # 1 + 2 = 3 % 2 = 1, with sign flip + assert state.sign == 1 # Should have flipped sign + + +def test_pauli_fault_prop_setters() -> None: + """Test property setters.""" + state = PauliProp(num_qubits=3, track_sign=True) + + # Set faults directly + state.faults = {"X": {0}, "Y": {1}, "Z": {2}} + assert state.faults["X"] == {0} + assert state.faults["Y"] == {1} + assert state.faults["Z"] == {2} + + # Set sign directly + state.sign = 1 + assert state.sign == 1 + state.sign = 0 + assert state.sign == 0 + + # Set img directly + state.img = 1 + assert state.img == 1 + state.img = 0 + assert state.img == 0 + + +def test_pauli_fault_prop_string_methods() -> None: + """Test various string representation methods.""" + state = PauliProp(num_qubits=4, track_sign=True) + + # Add some faults + qc = QuantumCircuit() + qc.append({"X": {0}, "Y": {1}, "Z": {2}}) + state.add_faults(qc) + + # Test string representations + assert state.get_str() == "+XYZI" + assert state.fault_str_operator() == "XYZI" + assert state.fault_str_sign(strip=False) in ["+ ", "+", "+ "] + assert state.fault_str_sign(strip=True) == "+" + assert state.fault_string() in ["+ XYZI", "+XYZI"] + + # Test with negative sign + state.flip_sign() + assert state.get_str() == "-XYZI" + assert state.fault_str_sign(strip=True) == "-" + + # Test __str__ method + str_repr = str(state) + assert "{'X': {0}, 'Y': {1}, 'Z': {2}}" in str_repr + + +def test_pauli_fault_prop_with_minus() -> None: + """Test add_faults with minus parameter.""" + state = PauliProp(num_qubits=2, track_sign=True) + + # Add faults with minus=True + qc = QuantumCircuit() + qc.append({"X": {0}}) + state.add_faults(qc, minus=True) + + assert state.sign == 1 # Should have negative sign + assert 0 in state.faults["X"] + + +def test_pauli_fault_prop_invalid_operations() -> None: + """Test error handling for invalid operations.""" + state = PauliProp(num_qubits=2, track_sign=False) + + # Try to add non-Pauli operation + qc = QuantumCircuit() + qc.append({"H": {0}}) # Hadamard is not a Pauli + + with pytest.raises(Exception, match="Can only handle Pauli errors"): + state.add_faults(qc) + + +if __name__ == "__main__": + test_pauli_fault_prop_basic() + test_pauli_fault_prop_composition() + test_pauli_fault_prop_sign_tracking() + test_pauli_fault_prop_setters() + test_pauli_fault_prop_string_methods() + test_pauli_fault_prop_with_minus() + test_pauli_fault_prop_invalid_operations() + print("All tests passed!") diff --git a/python/tests/test_rust_pauli_prop.py b/python/tests/test_rust_pauli_prop.py new file mode 100644 index 000000000..212bc5344 --- /dev/null +++ b/python/tests/test_rust_pauli_prop.py @@ -0,0 +1,142 @@ +# Copyright 2025 The PECOS Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License.You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +"""Test the Rust PauliProp integration.""" + +from pecos.circuits import QuantumCircuit +from pecos.simulators import PauliProp +from pecos_rslib import PauliPropRs + + +def test_rust_pauli_prop_basic() -> None: + """Test basic functionality of Rust PauliProp.""" + sim = PauliPropRs(num_qubits=4, track_sign=True) + + # Add Pauli operators + sim.add_x(0) + sim.add_z(2) + sim.add_y(3) + + assert sim.weight() == 3 + assert sim.contains_x(0) + assert sim.contains_z(2) + assert sim.contains_y(3) + + # Test string representations + assert sim.to_dense_string() == "+XIZY" + + # Test Hadamard gate (X -> Z) + sim.h(0) + assert sim.contains_z(0) + assert not sim.contains_x(0) + + +def test_rust_pauli_prop_composition() -> None: + """Test Pauli composition with phase tracking.""" + sim = PauliPropRs(num_qubits=3, track_sign=True) + + # X * Z = -iY (applying Z after X) + sim.add_x(0) + sim.add_paulis({"Z": {0}}) + + assert sim.contains_y(0) + assert sim.sign_string() == "-i" # X*Z = -iY + + # Y * Y = I + sim.add_y(0) + assert not sim.contains_x(0) + assert not sim.contains_z(0) + assert not sim.contains_y(0) + + +def test_rust_pauli_prop_gates() -> None: + """Test Clifford gates.""" + sim = PauliPropRs(num_qubits=3, track_sign=False) + + # Test CX propagation + sim.add_x(0) # X on control + sim.cx(0, 1) # Should propagate to target + assert sim.contains_x(0) + assert sim.contains_x(1) + + # Test CZ propagation + sim2 = PauliPropRs(num_qubits=3, track_sign=False) + sim2.add_z(1) # Z on target + sim2.cx(0, 1) # Should propagate to control + assert sim2.contains_z(0) + assert sim2.contains_z(1) + + +def test_rust_vs_python_consistency() -> None: + """Test that Rust and Python implementations give same results.""" + # Create both simulators + rust_sim = PauliPropRs(num_qubits=4, track_sign=True) + py_sim = PauliProp(num_qubits=4, track_sign=True) + + # Add same faults using appropriate APIs + qc = QuantumCircuit() + qc.append({"X": {0, 1}, "Z": {2}, "Y": {3}}) + py_sim.add_faults(qc) + + rust_sim.add_x(0) + rust_sim.add_x(1) + rust_sim.add_z(2) + rust_sim.add_y(3) + + # Check weights match + assert rust_sim.weight() == py_sim.fault_wt() + + # Check string representations match + assert rust_sim.to_dense_string() == py_sim.get_str() + + # Test composition + qc2 = QuantumCircuit() + qc2.append({"Z": {0}}) # Add Z to qubit with X -> Y + py_sim.add_faults(qc2) + + rust_sim.add_paulis({"Z": {0}}) + + # Both should now have Y on qubit 0 + assert 0 in py_sim.faults["Y"] + assert rust_sim.contains_y(0) + + # Check phase tracking + assert rust_sim.sign_string() == py_sim.fault_str_sign().strip() + + +def test_rust_pauli_prop_weight() -> None: + """Test weight calculation.""" + sim = PauliPropRs(num_qubits=5, track_sign=False) + + assert sim.weight() == 0 + + sim.add_x(0) + assert sim.weight() == 1 + + sim.add_z(1) + assert sim.weight() == 2 + + sim.add_y(2) + assert sim.weight() == 3 + + # Adding X to qubit with Z makes Y (still weight 3) + sim.add_x(1) + assert sim.weight() == 3 + assert sim.contains_y(1) + + +if __name__ == "__main__": + test_rust_pauli_prop_basic() + test_rust_pauli_prop_composition() + test_rust_pauli_prop_gates() + test_rust_vs_python_consistency() + test_rust_pauli_prop_weight() + print("All tests passed!") diff --git a/uv.lock b/uv.lock index 43c576300..480e5c009 100644 --- a/uv.lock +++ b/uv.lock @@ -736,7 +736,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -2549,22 +2549,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, ] -[[package]] -name = "projectq" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "requests" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/88/48c8347cadf6afb7f329e7c06d30880159e2a50b6e8a643c7667c26ffaf0/projectq-0.8.0.tar.gz", hash = "sha256:0bcd242afabe947ac4737dffab1de62628b84125ee6ccb3ec23bd4f1d082ec60", size = 433667, upload-time = "2022-10-18T16:03:17.779Z" } - [[package]] name = "prometheus-client" version = "0.22.1" @@ -2619,15 +2603,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] -[[package]] -name = "pybind11" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/83/698d120e257a116f2472c710932023ad779409adf2734d2e940f34eea2c5/pybind11-3.0.0.tar.gz", hash = "sha256:c3f07bce3ada51c3e4b76badfa85df11688d12c46111f9d242bc5c9415af7862", size = 544819, upload-time = "2025-07-10T16:52:09.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/9c/85f50a5476832c3efc67b6d7997808388236ae4754bf53e1749b3bc27577/pybind11-3.0.0-py3-none-any.whl", hash = "sha256:7c5cac504da5a701b5163f0e6a7ba736c713a096a5378383c5b4b064b753f607", size = 292118, upload-time = "2025-07-10T16:52:07.828Z" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -3127,8 +3102,6 @@ all = [ { name = "hugr" }, { name = "llvmlite", marker = "python_full_version < '3.13'" }, { name = "plotly" }, - { name = "projectq" }, - { name = "pybind11" }, { name = "qulacs", marker = "python_full_version < '3.13'" }, { name = "selene-sim" }, { name = "wasmer", marker = "python_full_version < '3.13'" }, @@ -3140,10 +3113,6 @@ guppy = [ { name = "hugr" }, { name = "selene-sim" }, ] -projectq = [ - { name = "projectq" }, - { name = "pybind11" }, -] qir = [ { name = "llvmlite", marker = "python_full_version < '3.13'" }, ] @@ -3151,8 +3120,6 @@ qulacs = [ { name = "qulacs" }, ] simulators = [ - { name = "projectq" }, - { name = "pybind11" }, { name = "qulacs", marker = "python_full_version < '3.13'" }, ] visualization = [ @@ -3182,10 +3149,7 @@ requires-dist = [ { name = "pecos-rslib", editable = "python/pecos-rslib" }, { name = "phir", specifier = ">=0.3.3" }, { name = "plotly", marker = "extra == 'visualization'", specifier = "~=5.9.0" }, - { name = "projectq", marker = "extra == 'projectq'", specifier = ">=0.5" }, - { name = "pybind11", marker = "extra == 'projectq'", specifier = ">=2.2.3" }, { name = "quantum-pecos", extras = ["guppy"], marker = "extra == 'all'" }, - { name = "quantum-pecos", extras = ["projectq"], marker = "extra == 'simulators'" }, { name = "quantum-pecos", extras = ["qir"], marker = "extra == 'all'" }, { name = "quantum-pecos", extras = ["qulacs"], marker = "python_full_version < '3.13' and extra == 'simulators'" }, { name = "quantum-pecos", extras = ["simulators"], marker = "extra == 'all'" }, @@ -3200,7 +3164,7 @@ requires-dist = [ { name = "wasmer-compiler-cranelift", marker = "extra == 'wasmer'", specifier = "~=1.1.0" }, { name = "wasmtime", marker = "extra == 'wasmtime'", specifier = ">=13.0" }, ] -provides-extras = ["qir", "guppy", "projectq", "wasmtime", "visualization", "simulators", "wasm-all", "all", "qulacs", "wasmer"] +provides-extras = ["qir", "guppy", "wasmtime", "visualization", "simulators", "wasm-all", "all", "qulacs", "wasmer"] [[package]] name = "qulacs"