Skip to content

Commit 29a6733

Browse files
committed
Replacing Python implementation of CoinToss with Rust implementation
1 parent 9cfcbfe commit 29a6733

15 files changed

Lines changed: 872 additions & 253 deletions

File tree

crates/pecos-qsim/src/coin_toss.rs

Lines changed: 430 additions & 0 deletions
Large diffs are not rendered by default.

crates/pecos-qsim/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// the License.
1212

1313
pub mod clifford_gateable;
14+
pub mod coin_toss;
1415
pub mod gens;
1516
pub mod pauli_prop;
1617
// pub mod paulis;
@@ -23,6 +24,7 @@ pub mod state_vec;
2324

2425
pub use arbitrary_rotation_gateable::ArbitraryRotationGateable;
2526
pub use clifford_gateable::{CliffordGateable, MeasurementResult};
27+
pub use coin_toss::CoinToss;
2628
pub use gens::Gens;
2729
// pub use paulis::Paulis;
2830
pub use pauli_prop::{PauliProp, StdPauliProp};
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright 2025 The PECOS Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4+
// in compliance with the License.You may obtain a copy of the License at
5+
//
6+
// https://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software distributed under the License
9+
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10+
// or implied. See the License for the specific language governing permissions and limitations under
11+
// the License.
12+
13+
use pecos::prelude::*;
14+
use pecos_qsim::CoinToss;
15+
use pyo3::prelude::*;
16+
use pyo3::types::PyDict;
17+
18+
/// The struct represents the coin toss simulator exposed to Python
19+
///
20+
/// This simulator ignores all quantum gates and returns random measurement results
21+
/// based on a configurable probability. It's useful for debugging classical logic
22+
/// paths and testing error correction protocols with random noise.
23+
#[pyclass(name = "CoinToss")]
24+
pub struct RsCoinToss {
25+
inner: CoinToss,
26+
}
27+
28+
#[pymethods]
29+
impl RsCoinToss {
30+
/// Creates a new coin toss simulator with the specified number of qubits
31+
///
32+
/// # Arguments
33+
/// * `num_qubits` - Number of qubits in the system
34+
/// * `prob` - Probability of measuring |1⟩ (default: 0.5)
35+
/// * `seed` - Optional seed for the random number generator
36+
#[new]
37+
#[pyo3(signature = (num_qubits, prob=0.5, seed=None))]
38+
pub fn new(num_qubits: usize, prob: f64, seed: Option<u64>) -> PyResult<Self> {
39+
if !(0.0..=1.0).contains(&prob) {
40+
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
41+
"Probability must be between 0.0 and 1.0, got {prob}"
42+
)));
43+
}
44+
45+
let inner = match seed {
46+
Some(s) => CoinToss::with_prob_and_seed(num_qubits, prob, Some(s)),
47+
None => CoinToss::with_prob(num_qubits, prob),
48+
};
49+
50+
Ok(RsCoinToss { inner })
51+
}
52+
53+
/// Resets the simulator (no-op for coin toss, but maintains interface compatibility)
54+
fn reset(&mut self) {
55+
self.inner.reset();
56+
}
57+
58+
/// Returns the number of qubits in the system
59+
#[getter]
60+
fn num_qubits(&self) -> usize {
61+
self.inner.num_qubits()
62+
}
63+
64+
/// Gets the current measurement probability
65+
#[getter]
66+
fn prob(&self) -> f64 {
67+
self.inner.prob()
68+
}
69+
70+
/// Sets the measurement probability
71+
///
72+
/// # Arguments
73+
/// * `prob` - New probability (must be between 0.0 and 1.0)
74+
#[setter]
75+
fn set_prob(&mut self, prob: f64) -> PyResult<()> {
76+
if !(0.0..=1.0).contains(&prob) {
77+
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
78+
"Probability must be between 0.0 and 1.0, got {prob}"
79+
)));
80+
}
81+
self.inner.set_prob(prob);
82+
Ok(())
83+
}
84+
85+
/// Sets the seed for reproducible randomness
86+
///
87+
/// # Arguments
88+
/// * `seed` - Seed value for the random number generator
89+
fn set_seed(&mut self, seed: u64) -> PyResult<()> {
90+
self.inner.set_seed(seed).map_err(|e| {
91+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Failed to set seed: {e}"))
92+
})
93+
}
94+
95+
/// Executes a single-qubit gate based on the provided symbol and location
96+
///
97+
/// All gates are no-ops in the coin toss simulator.
98+
///
99+
/// # Arguments
100+
/// * `symbol` - The gate symbol (e.g., "X", "H", "Z") - ignored
101+
/// * `location` - The qubit index to apply the gate to - ignored
102+
/// * `params` - Optional parameters for parameterized gates - ignored
103+
///
104+
/// # Returns
105+
/// Always returns an empty dictionary since all gates are no-ops
106+
#[allow(clippy::unused_self)]
107+
fn run_gate_1(
108+
&mut self,
109+
_symbol: &str,
110+
_location: usize,
111+
_params: Option<PyObject>,
112+
) -> PyResult<PyObject> {
113+
// All gates are no-ops in coin toss simulator
114+
Python::with_gil(|py| Ok(PyDict::new(py).into()))
115+
}
116+
117+
/// Executes a two-qubit gate based on the provided symbol and locations
118+
///
119+
/// All gates are no-ops in the coin toss simulator.
120+
///
121+
/// # Arguments
122+
/// * `symbol` - The gate symbol (e.g., "CX", "CZ", "SWAP") - ignored
123+
/// * `location_1` - First qubit index - ignored
124+
/// * `location_2` - Second qubit index - ignored
125+
/// * `params` - Optional parameters for parameterized gates - ignored
126+
///
127+
/// # Returns
128+
/// Always returns an empty dictionary since all gates are no-ops
129+
#[allow(clippy::unused_self)]
130+
fn run_gate_2(
131+
&mut self,
132+
_symbol: &str,
133+
_location_1: usize,
134+
_location_2: usize,
135+
_params: Option<PyObject>,
136+
) -> PyResult<PyObject> {
137+
// All gates are no-ops in coin toss simulator
138+
Python::with_gil(|py| Ok(PyDict::new(py).into()))
139+
}
140+
141+
/// Performs a measurement in the Z basis
142+
///
143+
/// Returns a random result (0 or 1) based on the configured probability.
144+
///
145+
/// # Arguments
146+
/// * `location` - The qubit index to measure (ignored - result is always random)
147+
///
148+
/// # Returns
149+
/// Dictionary containing the measurement result: {location: outcome}
150+
/// where outcome is 0 or 1 based on the probability
151+
fn run_measure(&mut self, location: usize) -> PyResult<PyObject> {
152+
let result = self.inner.mz(location);
153+
let outcome = i32::from(result.outcome);
154+
155+
Python::with_gil(|py| {
156+
let dict = PyDict::new(py);
157+
dict.set_item(location, outcome)?;
158+
Ok(dict.into())
159+
})
160+
}
161+
162+
/// String representation of the simulator
163+
fn __repr__(&self) -> String {
164+
format!(
165+
"CoinToss(num_qubits={}, prob={})",
166+
self.inner.num_qubits(),
167+
self.inner.prob()
168+
)
169+
}
170+
171+
/// String representation of the simulator
172+
fn __str__(&self) -> String {
173+
format!(
174+
"CoinToss simulator with {} qubits, P(|1⟩) = {:.3}",
175+
self.inner.num_qubits(),
176+
self.inner.prob()
177+
)
178+
}
179+
}

python/pecos-rslib/rust/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// the License.
1818

1919
mod byte_message_bindings;
20+
mod coin_toss_bindings;
2021
mod cpp_sparse_sim_bindings;
2122
mod engine_bindings;
2223
mod noise_helpers;
@@ -31,6 +32,7 @@ mod state_vec_bindings;
3132
mod state_vec_engine_bindings;
3233

3334
use byte_message_bindings::{PyByteMessage, PyByteMessageBuilder};
35+
use coin_toss_bindings::RsCoinToss;
3436
use cpp_sparse_sim_bindings::CppSparseSim;
3537
use pecos_rng_bindings::RngPcg;
3638
use pyo3::prelude::*;
@@ -46,6 +48,7 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
4648
m.add_class::<CppSparseSim>()?;
4749
m.add_class::<phir_bridge::PHIREngine>()?;
4850
m.add_class::<RsStateVec>()?;
51+
m.add_class::<RsCoinToss>()?;
4952
m.add_class::<PyByteMessage>()?;
5053
m.add_class::<PyByteMessageBuilder>()?;
5154
m.add_class::<PyStateVecEngine>()?;

python/pecos-rslib/rust/src/state_vec_bindings.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub struct RsStateVec {
2323
#[pymethods]
2424
impl RsStateVec {
2525
/// Creates a new state-vector simulator with the specified number of qubits
26-
///
26+
///
2727
/// # Arguments
2828
/// * `num_qubits` - Number of qubits in the system
2929
/// * `seed` - Optional seed for the random number generator

python/pecos-rslib/src/pecos_rslib/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from pecos_rslib.rssparse_sim import SparseSimRs
2222
from pecos_rslib.cppsparse_sim import CppSparseSimRs
2323
from pecos_rslib.rsstate_vec import StateVec
24+
from pecos_rslib.rscoin_toss import CoinToss
2425
from pecos_rslib._pecos_rslib import ByteMessage
2526
from pecos_rslib._pecos_rslib import ByteMessageBuilder
2627
from pecos_rslib._pecos_rslib import StateVecEngineRs
@@ -63,6 +64,7 @@
6364
"SparseSimRs",
6465
"CppSparseSimRs",
6566
"StateVec",
67+
"CoinToss",
6668
"ByteMessage",
6769
"ByteMessageBuilder",
6870
"StateVecEngineRs",

0 commit comments

Comments
 (0)