diff --git a/Cargo.lock b/Cargo.lock index 480cbf9a..62ee3837 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,8 +1181,10 @@ dependencies = [ name = "pecos-python" version = "0.1.1" dependencies = [ + "num-complex", "pecos", "pyo3", + "rand", ] [[package]] diff --git a/crates/pecos-python/Cargo.toml b/crates/pecos-python/Cargo.toml index 9df76358..533578e9 100644 --- a/crates/pecos-python/Cargo.toml +++ b/crates/pecos-python/Cargo.toml @@ -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 diff --git a/crates/pecos-python/src/lib.rs b/crates/pecos-python/src/lib.rs index 8cb94545..cee848b3 100644 --- a/crates/pecos-python/src/lib.rs +++ b/crates/pecos-python/src/lib.rs @@ -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 @@ -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::()?; + m.add_class::()?; Ok(()) } diff --git a/crates/pecos-python/src/state_vec_bindings.rs b/crates/pecos-python/src/state_vec_bindings.rs new file mode 100644 index 00000000..f49c6f18 --- /dev/null +++ b/crates/pecos-python/src/state_vec_bindings.rs @@ -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> { + 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::( + "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> { + if location.len() != 2 { + return Err(PyErr::new::( + "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::( + "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> { + 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::( + "Gate location must be specified for either 1 or 2 qubits", + )), + } + } +} diff --git a/crates/pecos-qsim/src/state_vec.rs b/crates/pecos-qsim/src/state_vec.rs index a3b3577b..ad1e001d 100644 --- a/crates/pecos-qsim/src/state_vec.rs +++ b/crates/pecos-qsim/src/state_vec.rs @@ -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] @@ -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 diff --git a/crates/pecos/src/prelude.rs b/crates/pecos/src/prelude.rs index 7b3c5847..566b23e8 100644 --- a/crates/pecos/src/prelude.rs +++ b/crates/pecos/src/prelude.rs @@ -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; diff --git a/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py b/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py new file mode 100644 index 00000000..185a9fdc --- /dev/null +++ b/python/pecos-rslib/src/pecos_rslib/rsstate_vec.py @@ -0,0 +1,194 @@ +# 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. + +# ruff: noqa: SLF001 + +from __future__ import annotations + +from typing import Any + +from pecos_rslib._pecos_rslib import RsStateVec as RustStateVec + + +class StateVecRs: + def __init__(self, num_qubits: int): + """ + Initializes the Rust-backed state vector simulator. + + Args: + num_qubits (int): The number of qubits in the quantum system. + """ + self._sim = RustStateVec(num_qubits) + self.num_qubits = num_qubits + self.bindings = dict(gate_dict) + + def reset(self): + """Resets the quantum state to the all-zero state.""" + self._sim.reset() + return self + + def run_gate( + self, + symbol: str, + locations: set[int] | set[tuple[int, ...]], + **params: Any, + ) -> dict[int, int]: + """ + Applies a gate to the quantum state. + + Args: + symbol (str): The gate symbol (e.g., "X", "H", "CX"). + location (tuple[int, ...]): The qubit(s) to which the gate is applied. + params (dict, optional): Parameters for the gate (e.g., rotation angles). + + Returns: + None + """ + # self._sim.run_gate(symbol, location, params) + 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"],) + + 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, + removed_locations: set[int] | None = None, + ) -> dict[int, int]: + 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 + + +# Define the gate dictionary +gate_dict = { + "I": lambda sim, q, **params: None, + "X": lambda sim, q, **params: sim._sim.run_1q_gate("X", q, params), + "Y": lambda sim, q, **params: sim._sim.run_1q_gate("Y", q, params), + "Z": lambda sim, q, **params: sim._sim.run_1q_gate("Z", q, params), + "SX": lambda sim, q, **params: sim._sim.run_1q_gate("SX", q, params), + "SXdg": lambda sim, q, **params: sim._sim.run_1q_gate("SXdg", q, params), + "SY": lambda sim, q, **params: sim._sim.run_1q_gate("SY", q, params), + "SYdg": lambda sim, q, **params: sim._sim.run_1q_gate("SYdg", q, params), + "SZ": lambda sim, q, **params: sim._sim.run_1q_gate("SZ", q, params), + "SZdg": lambda sim, q, **params: sim._sim.run_1q_gate("SZdg", q, params), + "H": lambda sim, q, **params: sim._sim.run_1q_gate("H", q, params), + "H2": lambda sim, q, **params: sim._sim.run_1q_gate("H2", q, params), + "H3": lambda sim, q, **params: sim._sim.run_1q_gate("H3", q, params), + "H4": lambda sim, q, **params: sim._sim.run_1q_gate("H4", q, params), + "H5": lambda sim, q, **params: sim._sim.run_1q_gate("H5", q, params), + "H6": lambda sim, q, **params: sim._sim.run_1q_gate("H6", q, params), + "F": lambda sim, q, **params: sim._sim.run_1q_gate("F", q, params), + "Fdg": lambda sim, q, **params: sim._sim.run_1q_gate("Fdg", q, params), + "F2": lambda sim, q, **params: sim._sim.run_1q_gate("F2", q, params), + "F2dg": lambda sim, q, **params: sim._sim.run_1q_gate("F2dg", q, params), + "F3": lambda sim, q, **params: sim._sim.run_1q_gate("F3", q, params), + "F3dg": lambda sim, q, **params: sim._sim.run_1q_gate("F3dg", q, params), + "F4": lambda sim, q, **params: sim._sim.run_1q_gate("F4", q, params), + "F4dg": lambda sim, q, **params: sim._sim.run_1q_gate("F4dg", q, params), + "II": lambda sim, qs, **params: None, + "CX": lambda sim, qs, **params: sim._sim.run_2q_gate("CX", qs, params), + "CNOT": lambda sim, qs, **params: sim._sim.run_2q_gate("CX", qs, params), + "CY": lambda sim, qs, **params: sim._sim.run_2q_gate("CY", qs, params), + "CZ": lambda sim, qs, **params: sim._sim.run_2q_gate("CZ", qs, params), + "SXX": lambda sim, qs, **params: sim._sim.run_2q_gate("SXX", qs, params), + "SXXdg": lambda sim, qs, **params: sim._sim.run_2q_gate("SXXdg", qs, params), + "SYY": lambda sim, qs, **params: sim._sim.run_2q_gate("SYY", qs, params), + "SYYdg": lambda sim, qs, **params: sim._sim.run_2q_gate("SYYdg", qs, params), + "SZZ": lambda sim, qs, **params: sim._sim.run_2q_gate("SZZ", qs, params), + "SZZdg": lambda sim, qs, **params: sim._sim.run_2q_gate("SZZdg", qs, params), + "SWAP": lambda sim, qs, **params: sim._sim.run_2q_gate("SWAP", qs, params), + "G": lambda sim, qs, **params: sim._sim.run_2q_gate("G2", qs, params), + "G2": lambda sim, qs, **params: sim._sim.run_2q_gate("G2", qs, params), + "MZ": lambda sim, q, **params: sim._sim.run_1q_gate("MZ", q, params), + "MX": lambda sim, q, **params: sim._sim.run_1q_gate("MX", q, params), + "MY": lambda sim, q, **params: sim._sim.run_1q_gate("MY", q, params), + "PZ": lambda sim, q, **params: sim._sim.run_1q_gate("PZ", q, params), + "PX": lambda sim, q, **params: sim._sim.run_1q_gate("PX", q, params), + "PY": lambda sim, q, **params: sim._sim.run_1q_gate("PY", q, params), + "PnZ": lambda sim, q, **params: sim._sim.run_1q_gate("PnZ", q, params), + "Init +Z": lambda sim, q, **params: sim._sim.run_1q_gate("PZ", q, params), + "Init -Z": lambda sim, q, **params: sim._sim.run_1q_gate("PnZ", q, params), + "Init +X": lambda sim, q, **params: sim._sim.run_1q_gate("PX", q, params), + "Init -X": lambda sim, q, **params: sim._sim.run_1q_gate("PnX", q, params), + "Init +Y": lambda sim, q, **params: sim._sim.run_1q_gate("PY", q, params), + "Init -Y": lambda sim, q, **params: sim._sim.run_1q_gate("PnY", q, params), + "init |0>": lambda sim, q, **params: sim._sim.run_1q_gate("PZ", q, params), + "init |1>": lambda sim, q, **params: sim._sim.run_1q_gate("PnZ", q, params), + "init |+>": lambda sim, q, **params: sim._sim.run_1q_gate("PX", q, params), + "init |->": lambda sim, q, **params: sim._sim.run_1q_gate("PnX", q, params), + "init |+i>": lambda sim, q, **params: sim._sim.run_1q_gate("PY", q, params), + "init |-i>": lambda sim, q, **params: sim._sim.run_1q_gate("PnY", q, params), + "leak": lambda sim, q, **params: sim._sim.run_1q_gate("PZ", q, params), + "leak |0>": lambda sim, q, **params: sim._sim.run_1q_gate("PZ", q, params), + "leak |1>": lambda sim, q, **params: sim._sim.run_1q_gate("PnZ", q, params), + "unleak |0>": lambda sim, q, **params: sim._sim.run_1q_gate("PZ", q, params), + "unleak |1>": lambda sim, q, **params: sim._sim.run_1q_gate("PnZ", q, params), + "Measure +X": lambda sim, q, **params: sim._sim.run_1q_gate("MX", q, params), + "Measure +Y": lambda sim, q, **params: sim._sim.run_1q_gate("MY", q, params), + "Measure +Z": lambda sim, q, **params: sim._sim.run_1q_gate("MZ", q, params), + "Q": lambda sim, q, **params: sim._sim.run_1q_gate("SX", q, params), + "Qd": lambda sim, q, **params: sim._sim.run_1q_gate("SXdg", q, params), + "R": lambda sim, q, **params: sim._sim.run_1q_gate("SY", q, params), + "Rd": lambda sim, q, **params: sim._sim.run_1q_gate("SYdg", q, params), + "S": lambda sim, q, **params: sim._sim.run_1q_gate("SZ", q, params), + "Sd": lambda sim, q, **params: sim._sim.run_1q_gate("SZdg", q, params), + "H1": lambda sim, q, **params: sim._sim.run_1q_gate("H1", q, params), + "F1": lambda sim, q, **params: sim._sim.run_1q_gate("F", q, params), + "F1d": lambda sim, q, **params: sim._sim.run_1q_gate("Fdg", q, params), + "F2d": lambda sim, q, **params: sim._sim.run_1q_gate("F2dg", q, params), + "F3d": lambda sim, q, **params: sim._sim.run_1q_gate("F3dg", q, params), + "F4d": lambda sim, q, **params: sim._sim.run_1q_gate("F4dg", q, params), + "SqrtXX": lambda sim, qs, **params: sim._sim.run_2q_gate("SXX", qs, params), + "SqrtYY": lambda sim, qs, **params: sim._sim.run_2q_gate("SYY", qs, params), + "SqrtZZ": lambda sim, qs, **params: sim._sim.run_2q_gate("SZZ", qs, params), + "measure Z": lambda sim, q, **params: sim._sim.run_1q_gate("MZ", q, params), + # "MZForced": lambda sim, q, **params: sim._sim.run_1q_gate("MZForced", q, params), + # "PZForced": lambda sim, q, **params: sim._sim.run_1q_gate("PZForced", q, params), + "SqrtXXd": lambda sim, qs, **params: sim._sim.run_2q_gate("SXXdg", qs, params), + "SqrtYYd": lambda sim, qs, **params: sim._sim.run_2q_gate("SYYdg", qs, params), + "SqrtZZd": lambda sim, qs, **params: sim._sim.run_2q_gate("SZZdg", qs, params), + "SqrtX": lambda sim, q, **params: sim._sim.run_1q_gate("SX", q, params), + "SqrtXd": lambda sim, q, **params: sim._sim.run_1q_gate("SXdg", q, params), + "SqrtY": lambda sim, q, **params: sim._sim.run_1q_gate("SY", q, params), + "SqrtYd": lambda sim, q, **params: sim._sim.run_1q_gate("SYdg", q, params), + "SqrtZ": lambda sim, q, **params: sim._sim.run_1q_gate("SZ", q, params), + "SqrtZd": lambda sim, q, **params: sim._sim.run_1q_gate("SZdg", q, params), +} + +# "force output": qmeas.force_output, + +__all__ = ["StateVecRs", "gate_dict"]