Skip to content

Commit

Permalink
Adding initial Rust state-vec Python bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
ciaranra committed Dec 21, 2024
1 parent c727061 commit c989843
Show file tree
Hide file tree
Showing 7 changed files with 479 additions and 21 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/pecos-python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ crate-type = ["cdylib"]
[dependencies]
pyo3 = { workspace=true, features = ["extension-module"] }
pecos = { workspace = true }
num-complex = { workspace = true }
rand = { workspace = true }

[lints]
workspace = true
6 changes: 5 additions & 1 deletion crates/pecos-python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2024 The PECOS Developers
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use thispub(crate)pub(crate) file except
// in compliance with the License.You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
Expand All @@ -11,12 +11,16 @@
// the License.

mod sparse_sim;
mod state_vec_bindings;

use sparse_sim::SparseSim;
use state_vec_bindings::RsStateVec;

use pyo3::prelude::*;

#[pymodule]
fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<SparseSim>()?;
m.add_class::<RsStateVec>()?;
Ok(())
}
270 changes: 270 additions & 0 deletions crates/pecos-python/src/state_vec_bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
use pecos::prelude::*;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};

/// The struct represents the state-vector simulator exposed to Python
#[pyclass]
pub struct RsStateVec {
inner: StateVec,
}

#[pymethods]
impl RsStateVec {
/// Creates a new state-vector simulator with the specified number of qubits
#[new]
pub fn new(num_qubits: usize) -> Self {
RsStateVec {
inner: StateVec::new(num_qubits),
}
}

/// Resets the quantum state to the all-zero state
fn reset(&mut self) {
self.inner.reset();
}

/// Executes a single-qubit gate based on the provided symbol and location
///
/// `symbol`: The gate symbol (e.g., "X", "H", "Z")
/// `location`: The qubit index to apply the gate to
/// `params`: Optional parameters for parameterized gates (currently unused here)
///
/// Returns an optional result, usually `None` unless a measurement is performed
#[allow(clippy::too_many_lines)]
#[pyo3(signature = (symbol, location, _params=None))]
fn run_1q_gate(
&mut self,
symbol: &str,
location: usize,
_params: Option<&Bound<'_, PyDict>>,
) -> PyResult<Option<u8>> {
match symbol {
"X" => {
self.inner.x(location);
Ok(None)
}
"Y" => {
self.inner.y(location);
Ok(None)
}
"Z" => {
self.inner.z(location);
Ok(None)
}
"H" => {
self.inner.h(location);
Ok(None)
}
"H2" => {
self.inner.h2(location);
Ok(None)
}
"H3" => {
self.inner.h3(location);
Ok(None)
}
"H4" => {
self.inner.h4(location);
Ok(None)
}
"H5" => {
self.inner.h5(location);
Ok(None)
}
"H6" => {
self.inner.h6(location);
Ok(None)
}
"F" => {
self.inner.f(location);
Ok(None)
}
"Fdg" => {
self.inner.fdg(location);
Ok(None)
}
"F2" => {
self.inner.f2(location);
Ok(None)
}
"F2dg" => {
self.inner.f2dg(location);
Ok(None)
}
"F3" => {
self.inner.f3(location);
Ok(None)
}
"F3dg" => {
self.inner.f3dg(location);
Ok(None)
}
"F4" => {
self.inner.f4(location);
Ok(None)
}
"F4dg" => {
self.inner.f4dg(location);
Ok(None)
}
"SX" => {
self.inner.sx(location);
Ok(None)
}
"SXdg" => {
self.inner.sxdg(location);
Ok(None)
}
"SY" => {
self.inner.sy(location);
Ok(None)
}
"SYdg" => {
self.inner.sydg(location);
Ok(None)
}
"SZ" => {
self.inner.sz(location);
Ok(None)
}
"SZdg" => {
self.inner.szdg(location);
Ok(None)
}
"PZ" => {
self.inner.pz(location);
Ok(None)
}
"PX" => {
self.inner.px(location);
Ok(None)
}
"PY" => {
self.inner.py(location);
Ok(None)
}
"PnZ" => {
self.inner.pnz(location);
Ok(None)
}
"PnX" => {
self.inner.pnx(location);
Ok(None)
}
"PnY" => {
self.inner.pny(location);
Ok(None)
}
"MZ" | "MX" | "MY" => {
let result = match symbol {
"MZ" => self.inner.mz(location),
"MX" => self.inner.mx(location),
"MY" => self.inner.my(location),
_ => unreachable!(),
};
Ok(Some(u8::from(result.outcome)))
}
_ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Unsupported single-qubit gate",
)),
}
}

/// Executes a two-qubit gate based on the provided symbol and locations
///
/// `symbol`: The gate symbol (e.g., "CX", "CZ")
/// `location`: A tuple specifying the two qubits to apply the gate to
/// `params`: Optional parameters for parameterized gates (currently unused here)
///
/// Returns an optional result, usually `None` unless a measurement is performed
#[pyo3(signature = (symbol, location, _params))]
fn run_2q_gate(
&mut self,
symbol: &str,
location: &Bound<'_, PyTuple>,
_params: Option<&Bound<'_, PyDict>>,
) -> PyResult<Option<u8>> {
if location.len() != 2 {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Two-qubit gate requires exactly 2 qubit locations",
));
}

let q1: usize = location.get_item(0)?.extract()?;
let q2: usize = location.get_item(1)?.extract()?;

match symbol {
"CX" => {
self.inner.cx(q1, q2);
Ok(None)
}
"CY" => {
self.inner.cy(q1, q2);
Ok(None)
}
"CZ" => {
self.inner.cz(q1, q2);
Ok(None)
}
"SXX" => {
self.inner.sxx(q1, q2);
Ok(None)
}
"SXXdg" => {
self.inner.sxxdg(q1, q2);
Ok(None)
}
"SYY" => {
self.inner.syy(q1, q2);
Ok(None)
}
"SYYdg" => {
self.inner.syydg(q1, q2);
Ok(None)
}
"SZZ" => {
self.inner.szz(q1, q2);
Ok(None)
}
"SZZdg" => {
self.inner.szzdg(q1, q2);
Ok(None)
}
"SWAP" => {
self.inner.swap(q1, q2);
Ok(None)
}
"G2" => {
self.inner.g2(q1, q2);
Ok(None)
}
_ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Unsupported two-qubit gate",
)),
}
}

/// Dispatches a gate to the appropriate handler based on the number of qubits specified
///
/// `symbol`: The gate symbol
/// `location`: A tuple specifying the qubits to apply the gate to
/// `params`: Optional parameters for parameterized gates
#[pyo3(signature = (symbol, location, params=None))]
fn run_gate(
&mut self,
symbol: &str,
location: &Bound<'_, PyTuple>,
params: Option<&Bound<'_, PyDict>>,
) -> PyResult<Option<u8>> {
match location.len() {
1 => {
let qubit: usize = location.get_item(0)?.extract()?;
self.run_1q_gate(symbol, qubit, params)
}
2 => self.run_2q_gate(symbol, location, params),
_ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Gate location must be specified for either 1 or 2 qubits",
)),
}
}
}
17 changes: 1 addition & 16 deletions crates/pecos-qsim/src/state_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1348,10 +1348,7 @@ mod tests {
q1.cz(0, 1);
q2.cz(1, 0);

// Results should be identical
for (a, b) in q1.state.iter().zip(q2.state.iter()) {
assert!((a - b).norm() < 1e-10);
}
assert_states_equal(&q1.state, &q2.state);
}

#[test]
Expand Down Expand Up @@ -1697,18 +1694,6 @@ mod tests {
}
}

#[test]
fn test_measure2() {
let mut q = StateVec::new(1);
q.h(0);
let _result = q.mz(0);

// Check collapse to |0⟩ or |1⟩
// assert!(result == 0 || result == 1);
let norm: f64 = q.state.iter().map(num_complex::Complex::norm_sqr).sum();
assert!((norm - 1.0).abs() < 1e-10);
}

#[test]
fn test_mz() {
// Test 1: Measuring |0> state
Expand Down
9 changes: 5 additions & 4 deletions crates/pecos/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
// the License.

// re-exporting pecos-core
pub use pecos_core::VecSet;
pub use pecos_core::{IndexableElement, VecSet};

// re-exporting pecos-qsim
pub use pecos_qsim::ArbitraryRotationGateable;
pub use pecos_qsim::CliffordGateable;
pub use pecos_qsim::QuantumSimulatorState;
pub use pecos_qsim::SparseStab;
pub use pecos_qsim::StateVec;

// TODO: add the following in the future as makes sense...
// pub use pecos_qsim::clifford_simulator::CliffordSimulator;
// pub use pecos_qsim::gens::Gens;
// pub use pecos_qsim::measurement::{MeasBitValue, MeasValue, Measurement}; // TODO: Distinguish between trait and struct/enum
// pub use pecos_qsim::nonclifford_simulator::NonCliffordSimulator;
// pub use pecos_qsim::pauli_prop::{PauliProp, StdPauliProp};
// pub use pecos_qsim::paulis::Paulis;
// pub use pecos_qsim::quantum_simulator::QuantumSimulator;
// pub use pecos_qsim::sparse_stab::SparseStab;
Loading

0 comments on commit c989843

Please sign in to comment.