From 1ab3b2a26e2e4851b3ee968b1035e887aa9f1e71 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 28 Aug 2025 12:37:53 -0600 Subject: [PATCH 1/5] Moving the C++ implementation of stabilizer sims from being wrapped in Cython to Rust/PyO3 --- Cargo.lock | 15 + crates/pecos-cppsparsesim/Cargo.toml | 26 + crates/pecos-cppsparsesim/README.md | 19 + crates/pecos-cppsparsesim/build.rs | 23 + crates/pecos-cppsparsesim/src/cxx_shim.cpp | 207 ++ crates/pecos-cppsparsesim/src/cxx_shim.h | 75 + crates/pecos-cppsparsesim/src/lib.rs | 475 +++++ .../pecos-cppsparsesim}/src/sparsesim.cpp | 19 +- .../pecos-cppsparsesim}/src/sparsesim.h | 10 +- .../pecos-cppsparsesim/src/sparsesim_col.cpp | 0 .../pecos-cppsparsesim/src/sparsesim_col.h | 0 .../pecos-cppsparsesim/src/sparsesim_row.cpp | 0 .../pecos-cppsparsesim/src/sparsesim_row.h | 0 crates/pecos-cppsparsesim/tests/basic_test.rs | 312 +++ .../tests/test_tableau_trait.rs | 49 + .../examples/stabilizer_tableau_trait.rs | 39 + crates/pecos-qsim/src/lib.rs | 2 + crates/pecos-qsim/src/prelude.rs | 1 + crates/pecos-qsim/src/sparse_stab.rs | 22 + crates/pecos-qsim/src/stabilizer_tableau.rs | 112 ++ python/pecos-rslib/rust/Cargo.toml | 2 + .../rust/src/cpp_sparse_sim_bindings.rs | 300 +++ python/pecos-rslib/rust/src/lib.rs | 3 + .../pecos-rslib/src/pecos_rslib/__init__.py | 2 + .../src/pecos_rslib/cppsparse_sim.py | 523 +++++ .../src/pecos_rslib/rssparse_sim.py | 2 +- .../src/pecos/simulators/__init__.py | 10 +- .../src/pecos/simulators/compile_cython.py | 75 - .../pecos/simulators/cysparsesim/.gitignore | 1 - .../pecos/simulators/cysparsesim/__init__.py | 19 - .../src/pecos/simulators/cysparsesim/setup.py | 73 - .../simulators/cysparsesim/src/__init__.py | 16 - .../cysparsesim/src/cysparsesim.pyx | 619 ------ .../cysparsesim/src/cysparsesim_header.pxd | 70 - .../cysparsesim/src/logical_sign.py | 186 -- .../simulators/cysparsesim_col/.gitignore | 3 - .../simulators/cysparsesim_col/__init__.py | 19 - .../pecos/simulators/cysparsesim_col/setup.py | 63 - .../cysparsesim_col/src/__init__.py | 16 - .../cysparsesim_col/src/cysparsesim.pyx | 460 ----- .../src/cysparsesim_header.pxd | 65 - .../cysparsesim_col/src/logical_sign.py | 177 -- .../cysparsesim_col/src/sparsesim - Copy.cpp | 1489 --------------- .../cysparsesim_col/src/sparsesim.cpp | 1453 -------------- .../simulators/cysparsesim_row/.gitignore | 3 - .../simulators/cysparsesim_row/__init__.py | 19 - .../pecos/simulators/cysparsesim_row/setup.py | 63 - .../cysparsesim_row/src/__init__.py | 16 - .../cysparsesim_row/src/cysparsesim.pyx | 461 ----- .../src/cysparsesim_header.pxd | 65 - .../cysparsesim_row/src/logical_sign.py | 177 -- .../cysparsesim_row/src/sparsesim - Copy.cpp | 1698 ----------------- .../pecos/simulators/sparsesim/bindings.py | 12 + .../test_stab_sims/test_gate_init.py | 3 +- .../test_stab_sims/test_gate_one_qubit.py | 3 +- .../test_stab_sims/test_gate_two_qubit.py | 3 +- .../pecos/integration/test_cppsparse_sim.py | 151 ++ .../pecos/integration/test_random_circuits.py | 46 +- 58 files changed, 2437 insertions(+), 7335 deletions(-) create mode 100644 crates/pecos-cppsparsesim/Cargo.toml create mode 100644 crates/pecos-cppsparsesim/README.md create mode 100644 crates/pecos-cppsparsesim/build.rs create mode 100644 crates/pecos-cppsparsesim/src/cxx_shim.cpp create mode 100644 crates/pecos-cppsparsesim/src/cxx_shim.h create mode 100644 crates/pecos-cppsparsesim/src/lib.rs rename {python/quantum-pecos/src/pecos/simulators/cysparsesim => crates/pecos-cppsparsesim}/src/sparsesim.cpp (98%) rename {python/quantum-pecos/src/pecos/simulators/cysparsesim => crates/pecos-cppsparsesim}/src/sparsesim.h (92%) rename python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim_works.cpp => crates/pecos-cppsparsesim/src/sparsesim_col.cpp (100%) rename python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.h => crates/pecos-cppsparsesim/src/sparsesim_col.h (100%) rename python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim.cpp => crates/pecos-cppsparsesim/src/sparsesim_row.cpp (100%) rename python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim.h => crates/pecos-cppsparsesim/src/sparsesim_row.h (100%) create mode 100644 crates/pecos-cppsparsesim/tests/basic_test.rs create mode 100644 crates/pecos-cppsparsesim/tests/test_tableau_trait.rs create mode 100644 crates/pecos-qsim/examples/stabilizer_tableau_trait.rs create mode 100644 crates/pecos-qsim/src/stabilizer_tableau.rs create mode 100644 python/pecos-rslib/rust/src/cpp_sparse_sim_bindings.rs create mode 100644 python/pecos-rslib/src/pecos_rslib/cppsparse_sim.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/compile_cython.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/.gitignore delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/__init__.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/setup.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/src/__init__.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim.pyx delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim_header.pxd delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim/src/logical_sign.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/.gitignore delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/__init__.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/setup.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/__init__.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim.pyx delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim_header.pxd delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/logical_sign.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim - Copy.cpp delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.cpp delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/.gitignore delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/__init__.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/setup.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/__init__.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim.pyx delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim_header.pxd delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/logical_sign.py delete mode 100644 python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim - Copy.cpp create mode 100644 python/tests/pecos/integration/test_cppsparse_sim.py diff --git a/Cargo.lock b/Cargo.lock index 5f387c2c7..416e8bfe7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1811,6 +1811,19 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "pecos-cppsparsesim" +version = "0.1.1" +dependencies = [ + "cc", + "cxx", + "cxx-build", + "pecos-core", + "pecos-qsim", + "rand", + "rand_chacha", +] + [[package]] name = "pecos-decoder-core" version = "0.1.1" @@ -1939,8 +1952,10 @@ dependencies = [ "parking_lot", "pecos", "pecos-core", + "pecos-cppsparsesim", "pecos-engines", "pecos-qasm", + "pecos-qsim", "pyo3", "pyo3-build-config", "serde_json", diff --git a/crates/pecos-cppsparsesim/Cargo.toml b/crates/pecos-cppsparsesim/Cargo.toml new file mode 100644 index 000000000..4c2f17419 --- /dev/null +++ b/crates/pecos-cppsparsesim/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pecos-cppsparsesim" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true +description = "C++ sparse stabilizer simulator bindings for PECOS" +readme = "README.md" + +[dependencies] +cxx.workspace = true +pecos-core.workspace = true +pecos-qsim.workspace = true +rand.workspace = true +rand_chacha.workspace = true + +[build-dependencies] +cxx-build.workspace = true +cc.workspace = true + +[lints] +workspace = true diff --git a/crates/pecos-cppsparsesim/README.md b/crates/pecos-cppsparsesim/README.md new file mode 100644 index 000000000..3076ba307 --- /dev/null +++ b/crates/pecos-cppsparsesim/README.md @@ -0,0 +1,19 @@ +# pecos-cppsparsesim + +C++ sparse stabilizer simulator bindings for PECOS. + +This crate provides Rust FFI bindings to a C++ implementation of a sparse stabilizer tableau simulator. It implements the same interface as the pure Rust sparse stabilizer simulator but uses an optimized C++ backend. + +## Features + +- Efficient sparse representation of stabilizer tableaux +- Support for all Clifford gates +- Compatible with PECOS quantum simulation framework + +## Usage + +This crate is primarily used as a backend for PECOS simulations and is not intended for direct use. See the main PECOS documentation for usage examples. + +## License + +Licensed under the Apache License, Version 2.0. See LICENSE for details. diff --git a/crates/pecos-cppsparsesim/build.rs b/crates/pecos-cppsparsesim/build.rs new file mode 100644 index 000000000..914ddb48b --- /dev/null +++ b/crates/pecos-cppsparsesim/build.rs @@ -0,0 +1,23 @@ +fn main() { + // Build C++ source files + cc::Build::new() + .cpp(true) + .file("src/sparsesim.cpp") + .file("src/cxx_shim.cpp") + .include("src") + .std("c++14") + .compile("sparsesim"); + + // Generate cxx bridge code + cxx_build::bridge("src/lib.rs") + .file("src/cxx_shim.cpp") + .std("c++14") + .compile("cppsparsesim-bridge"); + + // Tell cargo to rerun if source files change + println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-changed=src/sparsesim.cpp"); + println!("cargo:rerun-if-changed=src/sparsesim.h"); + println!("cargo:rerun-if-changed=src/cxx_shim.cpp"); + println!("cargo:rerun-if-changed=src/cxx_shim.h"); +} diff --git a/crates/pecos-cppsparsesim/src/cxx_shim.cpp b/crates/pecos-cppsparsesim/src/cxx_shim.cpp new file mode 100644 index 000000000..e5e87a269 --- /dev/null +++ b/crates/pecos-cppsparsesim/src/cxx_shim.cpp @@ -0,0 +1,207 @@ +// 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. + +#include "cxx_shim.h" + +StateWrapper::StateWrapper(std::uint64_t num_qubits, std::int32_t reserve_buckets) + : state(static_cast(num_qubits), static_cast(reserve_buckets)) {} + +void StateWrapper::set_seed(std::uint32_t seed) { + // Set the instance RNG seed + state.set_seed(static_cast(seed)); +} + +void StateWrapper::clear() { + state.clear(); +} + +void StateWrapper::hadamard(std::uint64_t qubit) { + state.hadamard(static_cast(qubit)); +} + +void StateWrapper::bitflip(std::uint64_t qubit) { + state.bitflip(static_cast(qubit)); +} + +void StateWrapper::phaseflip(std::uint64_t qubit) { + state.phaseflip(static_cast(qubit)); +} + +void StateWrapper::Y(std::uint64_t qubit) { + state.Y(static_cast(qubit)); +} + +void StateWrapper::phaserot(std::uint64_t qubit) { + state.phaserot(static_cast(qubit)); +} + +void StateWrapper::SZdg(std::uint64_t qubit) { + state.SZdg(static_cast(qubit)); +} + +void StateWrapper::SY(std::uint64_t qubit) { + state.SY(static_cast(qubit)); +} + +void StateWrapper::SYdg(std::uint64_t qubit) { + state.SYdg(static_cast(qubit)); +} + +void StateWrapper::SX(std::uint64_t qubit) { + state.SX(static_cast(qubit)); +} + +void StateWrapper::SXdg(std::uint64_t qubit) { + state.SXdg(static_cast(qubit)); +} + +void StateWrapper::H2(std::uint64_t qubit) { + state.H2(static_cast(qubit)); +} + +void StateWrapper::H3(std::uint64_t qubit) { + state.H3(static_cast(qubit)); +} + +void StateWrapper::H4(std::uint64_t qubit) { + state.H4(static_cast(qubit)); +} + +void StateWrapper::H5(std::uint64_t qubit) { + state.H5(static_cast(qubit)); +} + +void StateWrapper::H6(std::uint64_t qubit) { + state.H6(static_cast(qubit)); +} + +void StateWrapper::F(std::uint64_t qubit) { + state.F(static_cast(qubit)); +} + +void StateWrapper::F2(std::uint64_t qubit) { + state.F2(static_cast(qubit)); +} + +void StateWrapper::F3(std::uint64_t qubit) { + state.F3(static_cast(qubit)); +} + +void StateWrapper::F4(std::uint64_t qubit) { + state.F4(static_cast(qubit)); +} + +void StateWrapper::Fdg(std::uint64_t qubit) { + state.Fdg(static_cast(qubit)); +} + +void StateWrapper::F2dg(std::uint64_t qubit) { + state.F2dg(static_cast(qubit)); +} + +void StateWrapper::F3dg(std::uint64_t qubit) { + state.F3dg(static_cast(qubit)); +} + +void StateWrapper::F4dg(std::uint64_t qubit) { + state.F4dg(static_cast(qubit)); +} + +void StateWrapper::cx(std::uint64_t control, std::uint64_t target) { + // The C++ cx function uses confusing parameter names but actually expects (control, target) + state.cx(static_cast(control), static_cast(target)); +} + +void StateWrapper::cy(std::uint64_t control, std::uint64_t target) { + // CY = (I ⊗ SYdg) CX (I ⊗ SY) + state.SYdg(static_cast(target)); + state.cx(static_cast(control), static_cast(target)); + state.SY(static_cast(target)); +} + +void StateWrapper::cz(std::uint64_t qubit1, std::uint64_t qubit2) { + // CZ = H(qubit2) CX(qubit1, qubit2) H(qubit2) + state.hadamard(static_cast(qubit2)); + state.cx(static_cast(qubit1), static_cast(qubit2)); + state.hadamard(static_cast(qubit2)); +} + +void StateWrapper::swap(std::uint64_t qubit1, std::uint64_t qubit2) { + state.swap(static_cast(qubit1), static_cast(qubit2)); +} + +void StateWrapper::g2(std::uint64_t qubit1, std::uint64_t qubit2) { + // G2 gate decomposition: H(q1), CX(q2, q1), CX(q1, q2), H(q2) + state.hadamard(static_cast(qubit1)); + state.cx(static_cast(qubit2), static_cast(qubit1)); + state.cx(static_cast(qubit1), static_cast(qubit2)); + state.hadamard(static_cast(qubit2)); +} + +void StateWrapper::sxx(std::uint64_t qubit1, std::uint64_t qubit2) { + // SXX = SX(q1).SX(q2).SYdg(q1).CX(q1, q2).SY(q1) + state.SX(static_cast(qubit1)); + state.SX(static_cast(qubit2)); + state.SYdg(static_cast(qubit1)); + state.cx(static_cast(qubit1), static_cast(qubit2)); + state.SY(static_cast(qubit1)); +} + +void StateWrapper::sxxdg(std::uint64_t qubit1, std::uint64_t qubit2) { + // SXXdg = X(q1).X(q2).SXX(q1, q2) + state.bitflip(static_cast(qubit1)); + state.bitflip(static_cast(qubit2)); + sxx(qubit1, qubit2); // Call the wrapper's sxx implementation +} + +std::uint32_t StateWrapper::measure(std::uint64_t qubit, std::int32_t forced_outcome, bool collapse) { + // Simple wrapper - just return the measurement outcome + unsigned int outcome = state.measure(static_cast(qubit), static_cast(forced_outcome), collapse); + return static_cast(outcome); +} + +std::uint64_t StateWrapper::get_num_qubits() const { + return static_cast(state.num_qubits); +} + +bool StateWrapper::has_stab_x(std::uint64_t gen_id, std::uint64_t qubit) const { + const auto& row_set = state.stabs.row_x[static_cast(gen_id)]; + return row_set.count(static_cast(qubit)) > 0; +} + +bool StateWrapper::has_stab_z(std::uint64_t gen_id, std::uint64_t qubit) const { + const auto& row_set = state.stabs.row_z[static_cast(gen_id)]; + return row_set.count(static_cast(qubit)) > 0; +} + +bool StateWrapper::has_destab_x(std::uint64_t gen_id, std::uint64_t qubit) const { + const auto& row_set = state.destabs.row_x[static_cast(gen_id)]; + return row_set.count(static_cast(qubit)) > 0; +} + +bool StateWrapper::has_destab_z(std::uint64_t gen_id, std::uint64_t qubit) const { + const auto& row_set = state.destabs.row_z[static_cast(gen_id)]; + return row_set.count(static_cast(qubit)) > 0; +} + +bool StateWrapper::get_sign_minus(std::uint64_t gen_id) const { + return state.signs_minus.count(static_cast(gen_id)) > 0; +} + +bool StateWrapper::get_sign_i(std::uint64_t gen_id) const { + return state.signs_i.count(static_cast(gen_id)) > 0; +} + +// Factory function +std::unique_ptr create_state_wrapper(std::uint64_t num_qubits, std::int32_t reserve_buckets) { + return std::make_unique(num_qubits, reserve_buckets); +} diff --git a/crates/pecos-cppsparsesim/src/cxx_shim.h b/crates/pecos-cppsparsesim/src/cxx_shim.h new file mode 100644 index 000000000..6b61f354a --- /dev/null +++ b/crates/pecos-cppsparsesim/src/cxx_shim.h @@ -0,0 +1,75 @@ +// 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. + +#pragma once + +#include "sparsesim.h" +#include +#include + +// Wrapper class that cxx can understand +class StateWrapper { +private: + State state; + +public: + StateWrapper(std::uint64_t num_qubits, std::int32_t reserve_buckets); + void set_seed(std::uint32_t seed); + + void clear(); + void hadamard(std::uint64_t qubit); + void bitflip(std::uint64_t qubit); + void phaseflip(std::uint64_t qubit); + void Y(std::uint64_t qubit); + void phaserot(std::uint64_t qubit); + void SZdg(std::uint64_t qubit); + void SY(std::uint64_t qubit); + void SYdg(std::uint64_t qubit); + void SX(std::uint64_t qubit); + void SXdg(std::uint64_t qubit); + void H2(std::uint64_t qubit); + void H3(std::uint64_t qubit); + void H4(std::uint64_t qubit); + void H5(std::uint64_t qubit); + void H6(std::uint64_t qubit); + void F(std::uint64_t qubit); + void F2(std::uint64_t qubit); + void F3(std::uint64_t qubit); + void F4(std::uint64_t qubit); + void Fdg(std::uint64_t qubit); + void F2dg(std::uint64_t qubit); + void F3dg(std::uint64_t qubit); + void F4dg(std::uint64_t qubit); + void cx(std::uint64_t control, std::uint64_t target); + void cy(std::uint64_t control, std::uint64_t target); + void cz(std::uint64_t qubit1, std::uint64_t qubit2); + void swap(std::uint64_t qubit1, std::uint64_t qubit2); + void g2(std::uint64_t qubit1, std::uint64_t qubit2); + void sxx(std::uint64_t qubit1, std::uint64_t qubit2); + void sxxdg(std::uint64_t qubit1, std::uint64_t qubit2); + std::uint32_t measure(std::uint64_t qubit, std::int32_t forced_outcome, bool collapse); + + // Tableau access methods + std::uint64_t get_num_qubits() const; + bool has_stab_x(std::uint64_t gen_id, std::uint64_t qubit) const; + bool has_stab_z(std::uint64_t gen_id, std::uint64_t qubit) const; + bool has_destab_x(std::uint64_t gen_id, std::uint64_t qubit) const; + bool has_destab_z(std::uint64_t gen_id, std::uint64_t qubit) const; + bool get_sign_minus(std::uint64_t gen_id) const; + bool get_sign_i(std::uint64_t gen_id) const; + + // Get access to internal state for checking deterministic measurement + const State& get_state() const { return state; } +}; + +// Factory function +std::unique_ptr create_state_wrapper(std::uint64_t num_qubits, std::int32_t reserve_buckets); diff --git a/crates/pecos-cppsparsesim/src/lib.rs b/crates/pecos-cppsparsesim/src/lib.rs new file mode 100644 index 000000000..7b4d27009 --- /dev/null +++ b/crates/pecos-cppsparsesim/src/lib.rs @@ -0,0 +1,475 @@ +// 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_qsim::{CliffordGateable, MeasurementResult, QuantumSimulator}; + +// Include the cxx-generated bindings +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("pecos-cppsparsesim/src/cxx_shim.h"); + + type StateWrapper; + + // Factory function + fn create_state_wrapper(num_qubits: u64, reserve_buckets: i32) -> UniquePtr; + + // Member functions of StateWrapper - no prefix needed! + fn set_seed(self: Pin<&mut StateWrapper>, seed: u32); + fn clear(self: Pin<&mut StateWrapper>); + fn hadamard(self: Pin<&mut StateWrapper>, qubit: u64); + fn bitflip(self: Pin<&mut StateWrapper>, qubit: u64); + fn phaseflip(self: Pin<&mut StateWrapper>, qubit: u64); + fn Y(self: Pin<&mut StateWrapper>, qubit: u64); + fn phaserot(self: Pin<&mut StateWrapper>, qubit: u64); + fn SZdg(self: Pin<&mut StateWrapper>, qubit: u64); + fn SY(self: Pin<&mut StateWrapper>, qubit: u64); + fn SYdg(self: Pin<&mut StateWrapper>, qubit: u64); + fn SX(self: Pin<&mut StateWrapper>, qubit: u64); + fn SXdg(self: Pin<&mut StateWrapper>, qubit: u64); + fn H2(self: Pin<&mut StateWrapper>, qubit: u64); + fn H3(self: Pin<&mut StateWrapper>, qubit: u64); + fn H4(self: Pin<&mut StateWrapper>, qubit: u64); + fn H5(self: Pin<&mut StateWrapper>, qubit: u64); + fn H6(self: Pin<&mut StateWrapper>, qubit: u64); + fn F(self: Pin<&mut StateWrapper>, qubit: u64); + fn F2(self: Pin<&mut StateWrapper>, qubit: u64); + fn F3(self: Pin<&mut StateWrapper>, qubit: u64); + fn F4(self: Pin<&mut StateWrapper>, qubit: u64); + fn Fdg(self: Pin<&mut StateWrapper>, qubit: u64); + fn F2dg(self: Pin<&mut StateWrapper>, qubit: u64); + fn F3dg(self: Pin<&mut StateWrapper>, qubit: u64); + fn F4dg(self: Pin<&mut StateWrapper>, qubit: u64); + fn cx(self: Pin<&mut StateWrapper>, control: u64, target: u64); + fn cy(self: Pin<&mut StateWrapper>, control: u64, target: u64); + fn cz(self: Pin<&mut StateWrapper>, qubit1: u64, qubit2: u64); + fn swap(self: Pin<&mut StateWrapper>, qubit1: u64, qubit2: u64); + fn g2(self: Pin<&mut StateWrapper>, qubit1: u64, qubit2: u64); + fn sxx(self: Pin<&mut StateWrapper>, qubit1: u64, qubit2: u64); + fn sxxdg(self: Pin<&mut StateWrapper>, qubit1: u64, qubit2: u64); + fn measure( + self: Pin<&mut StateWrapper>, + qubit: u64, + forced_outcome: i32, + collapse: bool, + ) -> u32; + + // Tableau access methods + fn get_num_qubits(self: &StateWrapper) -> u64; + fn has_stab_x(self: &StateWrapper, gen_id: u64, qubit: u64) -> bool; + fn has_stab_z(self: &StateWrapper, gen_id: u64, qubit: u64) -> bool; + fn has_destab_x(self: &StateWrapper, gen_id: u64, qubit: u64) -> bool; + fn has_destab_z(self: &StateWrapper, gen_id: u64, qubit: u64) -> bool; + fn get_sign_minus(self: &StateWrapper, gen_id: u64) -> bool; + fn get_sign_i(self: &StateWrapper, gen_id: u64) -> bool; + } +} + +/// A C++ sparse stabilizer state simulator wrapped for Rust +/// +/// This is a wrapper around the C++ sparse simulator implementation, +/// providing the same interface as `StdSparseStab` but using the C++ backend. +pub struct CppSparseStab { + state: cxx::UniquePtr, + num_qubits: usize, +} + +// SAFETY: CppSparseStab can be safely sent between threads because: +// 1. The C++ StateWrapper manages its own memory properly +// 2. Each instance is used by only one thread at a time +// 3. The underlying C++ code has no shared state between instances +// 4. cxx::UniquePtr provides exclusive ownership +unsafe impl Send for CppSparseStab {} + +// SAFETY: CppSparseStab can be safely shared between threads because: +// 1. The underlying C++ StateWrapper is thread-safe for concurrent read access +// 2. Each instance maintains its own independent state +// 3. No global/shared mutable state is accessed +// 4. cxx::UniquePtr ensures exclusive ownership semantics +unsafe impl Sync for CppSparseStab {} + +impl CppSparseStab { + /// Create a new C++ sparse stabilizer simulator + #[must_use] + pub fn new(num_qubits: usize) -> Self { + let state = ffi::create_state_wrapper(num_qubits as u64, 0); + // C++ constructor already initializes with random_device seed + Self { state, num_qubits } + } + + /// Create a new simulator with a specific seed + /// + /// # Panics + /// + /// Panics if the C++ state wrapper creation fails (should never happen in normal usage) + #[must_use] + pub fn with_seed(num_qubits: usize, seed: u64) -> Self { + let mut state = ffi::create_state_wrapper(num_qubits as u64, 0); + // Use the provided seed for C++ RNG, truncating to 32-bit as C++ expects + #[allow(clippy::cast_possible_truncation)] + let seed_u32 = seed as u32; + state.as_mut().unwrap().set_seed(seed_u32); + Self { state, num_qubits } + } + + /// Create a new simulator with a specific seed (alias for `with_seed`) + #[must_use] + pub fn new_with_seed(num_qubits: usize, seed: u32) -> Self { + Self::with_seed(num_qubits, u64::from(seed)) + } + + /// Get the number of qubits + #[must_use] + pub fn num_qubits(&self) -> usize { + self.num_qubits + } + + /// Set the RNG seed for this simulator instance + /// + /// # Panics + /// + /// Panics if the C++ state wrapper is not initialized (should never happen in normal usage) + pub fn set_seed(&mut self, seed: u64) { + // Truncate to 32-bit as C++ expects + #[allow(clippy::cast_possible_truncation)] + let seed_u32 = seed as u32; + self.state.as_mut().unwrap().set_seed(seed_u32); + } + + /// Internal helper for measurement + fn internal_measure( + &mut self, + qubit: usize, + forced_outcome: Option, + collapse: bool, + ) -> MeasurementResult { + let forced = match forced_outcome { + None => -1, + Some(false) => 0, + Some(true) => 1, + }; + + let outcome_raw = self + .state + .as_mut() + .unwrap() + .measure(qubit as u64, forced, collapse); + let outcome = outcome_raw != 0; + + // Wrapper doesn't care about determinism - always return false + MeasurementResult { + outcome, + is_deterministic: false, + } + } +} + +impl QuantumSimulator for CppSparseStab { + fn reset(&mut self) -> &mut Self { + self.state.as_mut().unwrap().clear(); + // Don't reset the RNG - just reset the quantum state + // This matches the behavior of the pure Rust simulator + self + } +} + +impl CliffordGateable for CppSparseStab { + fn x(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().bitflip(q as u64); + self + } + + fn y(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().Y(q as u64); + self + } + + fn z(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().phaseflip(q as u64); + self + } + + fn h(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().hadamard(q as u64); + self + } + + fn sz(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().phaserot(q as u64); + self + } + + fn szdg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().SZdg(q as u64); + self + } + + fn sy(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().SY(q as u64); + self + } + + fn sydg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().SYdg(q as u64); + self + } + + fn sx(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().SX(q as u64); + self + } + + fn sxdg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().SXdg(q as u64); + self + } + + fn h2(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().H2(q as u64); + self + } + + fn h3(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().H3(q as u64); + self + } + + fn h4(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().H4(q as u64); + self + } + + fn h5(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().H5(q as u64); + self + } + + fn h6(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().H6(q as u64); + self + } + + fn f(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F(q as u64); + self + } + + fn fdg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().Fdg(q as u64); + self + } + + fn f2(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F2(q as u64); + self + } + + fn f2dg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F2dg(q as u64); + self + } + + fn f3(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F3(q as u64); + self + } + + fn f3dg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F3dg(q as u64); + self + } + + fn f4(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F4(q as u64); + self + } + + fn f4dg(&mut self, q: usize) -> &mut Self { + self.state.as_mut().unwrap().F4dg(q as u64); + self + } + + fn cx(&mut self, q1: usize, q2: usize) -> &mut Self { + // CliffordGateable uses cx(control, target) + // C++ also uses cx(control, target) despite confusing parameter names + self.state.as_mut().unwrap().cx(q1 as u64, q2 as u64); + self + } + + fn cy(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().cy(q1 as u64, q2 as u64); + self + } + + fn cz(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().cz(q1 as u64, q2 as u64); + self + } + + fn swap(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().swap(q1 as u64, q2 as u64); + self + } + + fn mz(&mut self, q: usize) -> MeasurementResult { + self.internal_measure(q, None, true) + } + + fn mx(&mut self, q: usize) -> MeasurementResult { + self.h(q); + let result = self.mz(q); + self.h(q); + result + } + + fn my(&mut self, q: usize) -> MeasurementResult { + self.sxdg(q); + let result = self.mz(q); + self.sx(q); + result + } +} + +// Additional convenience methods +impl CppSparseStab { + /// Force a specific measurement outcome + pub fn force_measure(&mut self, q: usize, outcome: bool) -> MeasurementResult { + self.internal_measure(q, Some(outcome), true) + } + + /// Measure without collapsing the state + pub fn peek_measure(&mut self, q: usize) -> MeasurementResult { + self.internal_measure(q, None, false) + } + + /// Swap two qubits + /// + /// # Panics + /// + /// Panics if the C++ state wrapper is not initialized (should never happen in normal usage) + pub fn swap(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().swap(q1 as u64, q2 as u64); + self + } + + /// Apply G2 gate (CZ.H(1).H(2).CZ) + /// + /// # Panics + /// + /// Panics if the C++ state wrapper is not initialized (should never happen in normal usage) + pub fn g2(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().g2(q1 as u64, q2 as u64); + self + } + + /// Apply SXX gate (sqrt(XX)) + /// + /// # Panics + /// + /// Panics if the C++ state wrapper is not initialized (should never happen in normal usage) + pub fn sxx(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().sxx(q1 as u64, q2 as u64); + self + } + + /// Apply `SXXdg` gate (sqrt(XX)†) + /// + /// # Panics + /// + /// Panics if the C++ state wrapper is not initialized (should never happen in normal usage) + pub fn sxxdg(&mut self, q1: usize, q2: usize) -> &mut Self { + self.state.as_mut().unwrap().sxxdg(q1 as u64, q2 as u64); + self + } + + /// Get the stabilizer tableau as a string + pub fn stab_tableau(&mut self) -> String { + self.format_generators(true) + } + + /// Get the destabilizer tableau as a string + pub fn destab_tableau(&mut self) -> String { + self.format_generators(false) + } + + /// Format generators into tableau string + fn format_generators(&self, is_stab: bool) -> String { + let mut result = String::new(); + let state_ref = self.state.as_ref().unwrap(); + // Safe to cast as num_qubits should never exceed usize::MAX + #[allow(clippy::cast_possible_truncation)] + let num_qubits = state_ref.get_num_qubits() as usize; + + for gen_id in 0..num_qubits { + // Determine the sign of this generator + let has_minus = state_ref.get_sign_minus(gen_id as u64); + let has_i = state_ref.get_sign_i(gen_id as u64); + + let sign = match (has_minus, has_i) { + (false, false) => "+", // +1 + (true, false) => "-", // -1 + (false, true) => "i", // +i + (true, true) => "-i", // -i + }; + + result.push_str(sign); + + // Build the Pauli string for this generator + for qubit in 0..num_qubits { + let (has_x, has_z) = if is_stab { + ( + state_ref.has_stab_x(gen_id as u64, qubit as u64), + state_ref.has_stab_z(gen_id as u64, qubit as u64), + ) + } else { + ( + state_ref.has_destab_x(gen_id as u64, qubit as u64), + state_ref.has_destab_z(gen_id as u64, qubit as u64), + ) + }; + + let pauli = match (has_x, has_z) { + (true, true) => 'Y', // Both X and Z -> Y + (true, false) => 'X', // Only X -> X + (false, true) => 'Z', // Only Z -> Z + (false, false) => 'I', // Neither -> I + }; + + result.push(pauli); + } + + result.push('\n'); + } + + result + } +} + +// Re-export for convenience +pub use crate::CppSparseStab as CppStdSparseStab; + +// Implement StabilizerTableauSimulator trait +use pecos_qsim::StabilizerTableauSimulator; + +impl StabilizerTableauSimulator for CppSparseStab { + fn stab_tableau(&self) -> String { + self.format_generators(true) + } + + fn destab_tableau(&self) -> String { + self.format_generators(false) + } + + fn num_qubits(&self) -> usize { + self.num_qubits + } +} diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/sparsesim.cpp b/crates/pecos-cppsparsesim/src/sparsesim.cpp similarity index 98% rename from python/quantum-pecos/src/pecos/simulators/cysparsesim/src/sparsesim.cpp rename to crates/pecos-cppsparsesim/src/sparsesim.cpp index 01390a87b..948742308 100644 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/sparsesim.cpp +++ b/crates/pecos-cppsparsesim/src/sparsesim.cpp @@ -13,11 +13,6 @@ #include "sparsesim.h" -// Function to create random outcomes -unsigned int random_outcome(void) { - // return random() > RAND_MAX/2;: - return rand()%2; -} int_set_vec build_empty(int_num size, int reserve_buckets) { @@ -56,12 +51,24 @@ int_set_vec build_ones(int_num size, int reserve_buckets){ State::State(const int_num& num_qubits, const int& reserve_buckets) : num_qubits(num_qubits), - reserve_buckets(reserve_buckets) + reserve_buckets(reserve_buckets), + rng(std::random_device{}()), // Initialize with random seed by default + dist(0, 1) // Distribution for 0 or 1 { //num_qubits = num_qubits; clear(); } +void State::set_seed(unsigned int seed) { + rng.seed(seed); + // Also set global rand() for backward compatibility with existing code + srand(seed); +} + +unsigned int State::random_outcome() { + return dist(rng); +} + void State::clear() { // Allows state to reinitialize. // Initialize stabilizers. diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/sparsesim.h b/crates/pecos-cppsparsesim/src/sparsesim.h similarity index 92% rename from python/quantum-pecos/src/pecos/simulators/cysparsesim/src/sparsesim.h rename to crates/pecos-cppsparsesim/src/sparsesim.h index abed91f49..98077ed09 100644 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/sparsesim.h +++ b/crates/pecos-cppsparsesim/src/sparsesim.h @@ -15,6 +15,7 @@ #include #include #include +#include using namespace std; @@ -59,14 +60,19 @@ class State { const int reserve_buckets; // Whether to reserve buckets. Generators stabs, destabs; // Stabilizers and destabilizer generator matrices. int_set signs_minus, signs_i; // A column that stores minuses and is. + + // Per-instance random number generator + std::mt19937 rng; + std::uniform_int_distribution dist; // Methods + void set_seed(unsigned int seed); // Set RNG seed void clear(); void hadamard(const int_num& qubit); // H void bitflip(const int_num& qubit); // X void phaseflip(const int_num& qubit); // Z void Y(const int_num& qubit); // Y void phaserot(const int_num& qubit); // S - void SZd(const int_num& qubit); // Sd + void SZdg(const int_num& qubit); // Sd void SY(const int_num& qubit); // R void SYdg(const int_num& qubit); // Rd void SX(const int_num& qubit); // Q @@ -89,6 +95,7 @@ class State { unsigned int measure(const int_num& qubit, int forced_outcome, bool collapse); private: + unsigned int random_outcome(); // Instance method for random outcomes unsigned int deterministic_measure(const int_num& qubit); unsigned int nondeterministic_measure(const int_num& qubit, int forced_outcome); }; @@ -100,4 +107,3 @@ void cnot_gen_mod(Generators& gen, const int_num& tqubit, const int_num& cqubit) void swap_gen_mod(Generators& gen, const int_num& qubit1, const int_num& qubit2); void F1_gen_mod(Generators& gen, const int_num& qubit); void F2_gen_mod(Generators& gen, const int_num& qubit); -unsigned int random_outcome(void); diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim_works.cpp b/crates/pecos-cppsparsesim/src/sparsesim_col.cpp similarity index 100% rename from python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim_works.cpp rename to crates/pecos-cppsparsesim/src/sparsesim_col.cpp diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.h b/crates/pecos-cppsparsesim/src/sparsesim_col.h similarity index 100% rename from python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.h rename to crates/pecos-cppsparsesim/src/sparsesim_col.h diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim.cpp b/crates/pecos-cppsparsesim/src/sparsesim_row.cpp similarity index 100% rename from python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim.cpp rename to crates/pecos-cppsparsesim/src/sparsesim_row.cpp diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim.h b/crates/pecos-cppsparsesim/src/sparsesim_row.h similarity index 100% rename from python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim.h rename to crates/pecos-cppsparsesim/src/sparsesim_row.h diff --git a/crates/pecos-cppsparsesim/tests/basic_test.rs b/crates/pecos-cppsparsesim/tests/basic_test.rs new file mode 100644 index 000000000..492af5abe --- /dev/null +++ b/crates/pecos-cppsparsesim/tests/basic_test.rs @@ -0,0 +1,312 @@ +// 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_cppsparsesim::CppSparseStab; +use pecos_qsim::{CliffordGateable, QuantumSimulator}; + +#[test] +fn test_basic_gates() { + let mut sim = CppSparseStab::new(2); + + // Test basic single-qubit gates + sim.h(0); + sim.x(1); + sim.z(0); + sim.y(1); +} + +#[test] +fn test_bell_state() { + let mut sim = CppSparseStab::new(2); + + // Create Bell state |Φ+⟩ = (|00⟩ + |11⟩)/√2 + sim.h(0); + sim.cx(0, 1); + + // Measure both qubits + let r0 = sim.mz(0); + let r1 = sim.mz(1); + + // Both measurements should be equal (entangled) + assert_eq!(r0.outcome, r1.outcome); +} + +#[test] +fn test_reset() { + let mut sim = CppSparseStab::new(3); + + // Apply some gates + sim.h(0).cx(0, 1).h(2); + + // Reset the simulator + sim.reset(); + + // After reset, all qubits should be in |0⟩ state + // Measuring in Z basis should give 0 + let r0 = sim.mz(0); + let r1 = sim.mz(1); + let r2 = sim.mz(2); + + assert!(!r0.outcome); + assert!(!r1.outcome); + assert!(!r2.outcome); + // Note: Determinism tracking has been removed from the wrapper + // per design decision - the wrapper does not track determinism +} + +#[test] +fn test_phase_gates() { + let mut sim = CppSparseStab::new(1); + + // Test S and S† gates + sim.sz(0); + sim.szdg(0); + + // Test SX and SX† gates + sim.sx(0); + sim.sxdg(0); + + // Test SY and SY† gates + sim.sy(0); + sim.sydg(0); +} + +#[test] +fn test_deterministic_with_seed() { + // Test that simulators with the same seed produce identical results + // Now that we have per-instance RNG, this should work correctly + + let seed = 42; + + // Create two simulators with the same seed + let mut sim1 = CppSparseStab::new_with_seed(3, seed); + let mut sim2 = CppSparseStab::new_with_seed(3, seed); + + // Apply same operations to both + sim1.h(0); + sim1.h(1); + sim1.cx(0, 2); + sim1.h(2); + + sim2.h(0); + sim2.h(1); + sim2.cx(0, 2); + sim2.h(2); + + // Collect measurements from both simulators + let results1 = vec![sim1.mz(0).outcome, sim1.mz(1).outcome, sim1.mz(2).outcome]; + + let results2 = vec![sim2.mz(0).outcome, sim2.mz(1).outcome, sim2.mz(2).outcome]; + + // Results should be identical with same seed + assert_eq!( + results1, results2, + "Simulators with same seed should produce identical results" + ); + + // Test with different seeds to ensure they differ + let mut sim3 = CppSparseStab::new_with_seed(3, seed + 1); + sim3.h(0); + sim3.h(1); + sim3.cx(0, 2); + sim3.h(2); + + let _results3 = [sim3.mz(0).outcome, sim3.mz(1).outcome, sim3.mz(2).outcome]; + + // Very unlikely to be the same with different seed + // (1/8 chance, but we accept this small possibility) + // The other tests verify statistical properties more thoroughly +} + +#[test] +fn test_different_seeds_different_results() { + // Test that different seeds produce different results (with high probability) + // We'll run multiple trials to account for the small chance of getting same results + + let mut same_count = 0; + let trials = 10; + + for trial in 0..trials { + let seed1 = trial * 100 + 1; + let seed2 = trial * 100 + 2; + + let mut sim1 = CppSparseStab::new_with_seed(5, seed1); + let mut sim2 = CppSparseStab::new_with_seed(5, seed2); + + // Create superposition on all qubits + for i in 0..5 { + sim1.h(i); + sim2.h(i); + } + + // Measure all qubits + let mut results1 = vec![]; + let mut results2 = vec![]; + for i in 0..5 { + results1.push(sim1.mz(i).outcome); + results2.push(sim2.mz(i).outcome); + } + + if results1 == results2 { + same_count += 1; + } + } + + // With 5 qubits, probability of getting same results is 1/2^5 = 1/32 + // Over 10 trials, we expect 0-1 matches, definitely not all 10 + assert!( + same_count < trials / 2, + "Different seeds should produce different results most of the time. Got {same_count} same out of {trials} trials" + ); +} + +#[test] +fn test_forced_measurements() { + // Test that forced measurements work correctly + let mut sim = CppSparseStab::new_with_seed(3, 123); + + // Put qubits in superposition + sim.h(0); + sim.h(1); + sim.h(2); + + // Force measurements to specific values + let r0 = sim.force_measure(0, false); // Force to 0 + let r1 = sim.force_measure(1, true); // Force to 1 + let r2 = sim.force_measure(2, false); // Force to 0 + + assert!(!r0.outcome, "Forced measurement to 0 should return false"); + assert!(r1.outcome, "Forced measurement to 1 should return true"); + assert!(!r2.outcome, "Forced measurement to 0 should return false"); + + // After forcing, qubits should be in deterministic states + // Measuring again should give same results + let r0_again = sim.mz(0); + let r1_again = sim.mz(1); + let r2_again = sim.mz(2); + + assert!(!r0_again.outcome); + assert!(r1_again.outcome); + assert!(!r2_again.outcome); +} + +#[test] +fn test_forced_measurement_on_deterministic_state() { + // Test that forcing a deterministic state returns the deterministic value + // (not the forced value) + let mut sim = CppSparseStab::new(2); + + // Qubit 0 is deterministically |0⟩ + // Try to force it to 1 + let r0 = sim.force_measure(0, true); + assert!( + !r0.outcome, + "Forcing deterministic |0⟩ to 1 should still return 0" + ); + + // Put qubit 1 in |1⟩ + sim.x(1); + // Try to force it to 0 + let r1 = sim.force_measure(1, false); + assert!( + r1.outcome, + "Forcing deterministic |1⟩ to 0 should still return 1" + ); +} + +#[test] +fn test_measurement_statistics() { + // Test that non-deterministic measurements produce roughly 50/50 results + let seed = 999; + let num_trials = 1000; + let mut zeros = 0; + let mut ones = 0; + + for i in 0..num_trials { + // Use different seeds but deterministic sequence + let mut sim = CppSparseStab::new_with_seed(1, seed + i); + sim.h(0); // Put in superposition + + let result = sim.mz(0); + if result.outcome { + ones += 1; + } else { + zeros += 1; + } + } + + // Check that we get roughly 50/50 distribution (with some tolerance) + let ratio = f64::from(zeros) / f64::from(num_trials); + assert!( + ratio > 0.4 && ratio < 0.6, + "Expected roughly 50/50 distribution, got {zeros} zeros and {ones} ones (ratio: {ratio})" + ); +} + +#[test] +fn test_complex_circuit_determinism() { + // Test a more complex circuit with multiple gates and measurements + let seed = 12345; + + // Run the same complex circuit twice with same seed + let results1 = run_complex_circuit(seed); + let results2 = run_complex_circuit(seed); + + assert_eq!( + results1, results2, + "Complex circuit with same seed should produce identical results" + ); + + // Run with different seed + let results3 = run_complex_circuit(seed + 1); + + // Very unlikely to get same results with different seed + assert_ne!( + results1, results3, + "Complex circuit with different seed should produce different results" + ); +} + +fn run_complex_circuit(seed: u32) -> Vec { + let mut sim = CppSparseStab::new_with_seed(6, seed); + let mut results = vec![]; + + // Create a complex entangled state + sim.h(0); + sim.cx(0, 1); + sim.h(2); + sim.cx(2, 3); + sim.cx(1, 4); + sim.h(5); + sim.cz(3, 5); + + // Apply some single-qubit gates + sim.sz(0); + sim.sx(2); + sim.sy(4); + + // Measure some qubits + results.push(sim.mz(0).outcome); + results.push(sim.mz(2).outcome); + + // Apply more gates + sim.h(1); + sim.cx(4, 5); + + // Measure remaining qubits + results.push(sim.mz(1).outcome); + results.push(sim.mz(3).outcome); + results.push(sim.mz(4).outcome); + results.push(sim.mz(5).outcome); + + results +} diff --git a/crates/pecos-cppsparsesim/tests/test_tableau_trait.rs b/crates/pecos-cppsparsesim/tests/test_tableau_trait.rs new file mode 100644 index 000000000..0ca56f29e --- /dev/null +++ b/crates/pecos-cppsparsesim/tests/test_tableau_trait.rs @@ -0,0 +1,49 @@ +// Test the StabilizerTableauSimulator trait implementation for CppSparseStab + +use pecos_cppsparsesim::CppSparseStab; +use pecos_qsim::{CliffordGateable, StabilizerTableauSimulator}; + +#[test] +fn test_cpp_sparse_stab_tableau_trait() { + let mut sim = CppSparseStab::new(2); + + // Apply Bell state preparation + sim.h(0); + sim.cx(0, 1); + + // Test that we can access tableaux through the trait + let stab = sim.stab_tableau(); + let destab = sim.destab_tableau(); + let full = sim.full_tableau(); + + // Verify the stabilizers contain expected patterns + assert!(stab.contains("XX")); // Bell state stabilizer + assert!(stab.contains("ZZ")); // Bell state stabilizer + + // Verify destabilizers + assert!(destab.contains('X')); + assert!(destab.contains('Z')); + + // Verify full tableau contains both sections + assert!(full.contains("Stabilizers:")); + assert!(full.contains("Destabilizers:")); + + // Test num_qubits through the trait + assert_eq!(sim.num_qubits(), 2); +} + +#[test] +fn test_tableau_trait_generic_function() { + fn generic_tableau_test(mut sim: T) + where + T: StabilizerTableauSimulator + CliffordGateable, + { + sim.x(0); + let stab = sim.stab_tableau(); + assert!(stab.contains('Z')); // X gate changes Z stabilizer + assert_eq!(sim.num_qubits(), 1); + } + + let sim = CppSparseStab::new(1); + generic_tableau_test(sim); +} diff --git a/crates/pecos-qsim/examples/stabilizer_tableau_trait.rs b/crates/pecos-qsim/examples/stabilizer_tableau_trait.rs new file mode 100644 index 000000000..c27f35ef1 --- /dev/null +++ b/crates/pecos-qsim/examples/stabilizer_tableau_trait.rs @@ -0,0 +1,39 @@ +// Example demonstrating the StabilizerTableauSimulator trait +// +// This example shows how different stabilizer simulators can implement +// the same trait interface for accessing tableau information. + +use pecos_qsim::{CliffordGateable, StabilizerTableauSimulator, StdSparseStab}; + +/// Generic function that works with any stabilizer tableau simulator +fn print_bell_state_tableaux(name: &str, mut sim: T) +where + T: StabilizerTableauSimulator + CliffordGateable, +{ + println!("=== {name} ==="); + + // Create Bell state |00> + |11> + sim.h(0); + sim.cx(0, 1); + + println!("Number of qubits: {}", sim.num_qubits()); + println!("\nStabilizers:"); + println!("{}", sim.stab_tableau()); + println!("Destabilizers:"); + println!("{}", sim.destab_tableau()); + + // The full tableau method combines both + println!("\nFull tableau:"); + println!("{}", sim.full_tableau()); + println!(); +} + +fn main() { + // The trait allows us to work with different implementations uniformly + let sim = StdSparseStab::new(2); + print_bell_state_tableaux("Pure Rust Stabilizer Simulator", sim); + + // Future implementations can be added here: + // let other_sim = OtherStabilizerImpl::new(2); + // print_bell_state_tableaux("Other Implementation", other_sim); +} diff --git a/crates/pecos-qsim/src/lib.rs b/crates/pecos-qsim/src/lib.rs index 9816f063d..ed2768acb 100644 --- a/crates/pecos-qsim/src/lib.rs +++ b/crates/pecos-qsim/src/lib.rs @@ -18,6 +18,7 @@ pub mod arbitrary_rotation_gateable; pub mod prelude; pub mod quantum_simulator; pub mod sparse_stab; +pub mod stabilizer_tableau; pub mod state_vec; pub use arbitrary_rotation_gateable::ArbitraryRotationGateable; @@ -28,4 +29,5 @@ pub use pauli_prop::{PauliProp, StdPauliProp}; pub use pecos_core::VecSet; pub use quantum_simulator::QuantumSimulator; pub use sparse_stab::{SparseStab, StdSparseStab}; +pub use stabilizer_tableau::StabilizerTableauSimulator; pub use state_vec::StateVec; diff --git a/crates/pecos-qsim/src/prelude.rs b/crates/pecos-qsim/src/prelude.rs index 3ca41682a..eb3884799 100644 --- a/crates/pecos-qsim/src/prelude.rs +++ b/crates/pecos-qsim/src/prelude.rs @@ -18,5 +18,6 @@ pub use crate::{ pauli_prop::{PauliProp, StdPauliProp}, quantum_simulator::QuantumSimulator, sparse_stab::{SparseStab, StdSparseStab}, + stabilizer_tableau::StabilizerTableauSimulator, state_vec::StateVec, }; diff --git a/crates/pecos-qsim/src/sparse_stab.rs b/crates/pecos-qsim/src/sparse_stab.rs index 4c3fcbf34..3b17caebc 100644 --- a/crates/pecos-qsim/src/sparse_stab.rs +++ b/crates/pecos-qsim/src/sparse_stab.rs @@ -772,6 +772,28 @@ where } } +// Implement StabilizerTableauSimulator trait for SparseStab +use crate::stabilizer_tableau::StabilizerTableauSimulator; + +impl StabilizerTableauSimulator for SparseStab +where + T: for<'a> Set<'a, Element = E>, + E: IndexableElement, + R: RngCore + SeedableRng + Rng + Debug, +{ + fn stab_tableau(&self) -> String { + Self::tableau_string(self.num_qubits, &self.stabs) + } + + fn destab_tableau(&self) -> String { + Self::tableau_string(self.num_qubits, &self.destabs) + } + + fn num_qubits(&self) -> usize { + self.num_qubits + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/pecos-qsim/src/stabilizer_tableau.rs b/crates/pecos-qsim/src/stabilizer_tableau.rs new file mode 100644 index 000000000..ef25324e1 --- /dev/null +++ b/crates/pecos-qsim/src/stabilizer_tableau.rs @@ -0,0 +1,112 @@ +// 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. + +//! Trait for stabilizer tableau-based quantum simulators. +//! +//! This trait provides a common interface for simulators that use the stabilizer/destabilizer +//! tableau formalism, allowing them to share functionality like tableau printing and manipulation. + +use crate::QuantumSimulator; + +/// A trait for quantum simulators that use the stabilizer tableau formalism. +/// +/// This trait extends `QuantumSimulator` with methods specific to stabilizer-based +/// simulators, including tableau access and manipulation. +/// +/// # Examples +/// ```rust +/// use pecos_qsim::{StabilizerTableauSimulator, CliffordGateable, StdSparseStab}; +/// +/// let mut sim = StdSparseStab::new(2); +/// sim.h(0).cx(0, 1); // Create Bell state +/// +/// // Print the stabilizer tableau +/// println!("{}", sim.stab_tableau()); +/// ``` +pub trait StabilizerTableauSimulator: QuantumSimulator { + /// Returns a string representation of the stabilizer tableau. + /// + /// The tableau format shows each stabilizer generator as a Pauli string + /// with its phase (+, -, i, or -i). + /// + /// # Examples + /// ```rust + /// use pecos_qsim::{StabilizerTableauSimulator, StdSparseStab}; + /// + /// let sim = StdSparseStab::new(2); + /// let tableau = sim.stab_tableau(); + /// assert!(tableau.contains("+ZI")); + /// assert!(tableau.contains("+IZ")); + /// ``` + fn stab_tableau(&self) -> String; + + /// Returns a string representation of the destabilizer tableau. + /// + /// The tableau format shows each destabilizer generator as a Pauli string + /// with its phase (+, -, i, or -i). + /// + /// # Examples + /// ```rust + /// use pecos_qsim::{StabilizerTableauSimulator, StdSparseStab}; + /// + /// let sim = StdSparseStab::new(2); + /// let tableau = sim.destab_tableau(); + /// assert!(tableau.contains("+XI")); + /// assert!(tableau.contains("+IX")); + /// ``` + fn destab_tableau(&self) -> String; + + /// Returns the combined stabilizer and destabilizer tableaux. + /// + /// This shows the full canonical form of the stabilizer state with both + /// stabilizers and destabilizers. + /// + /// # Examples + /// ```rust + /// use pecos_qsim::{StabilizerTableauSimulator, StdSparseStab}; + /// + /// let sim = StdSparseStab::new(1); + /// let full = sim.full_tableau(); + /// assert!(full.contains("Destabilizers:")); + /// assert!(full.contains("Stabilizers:")); + /// ``` + fn full_tableau(&self) -> String { + format!( + "Destabilizers:\n{}\nStabilizers:\n{}", + self.destab_tableau(), + self.stab_tableau() + ) + } + + /// Checks if a given Pauli operator commutes with all stabilizers. + /// + /// This can be used to verify if an operator is in the stabilizer group. + /// + /// # Arguments + /// * `pauli_string` - A string representation of a Pauli operator (e.g., "XIZ") + /// + /// # Returns + /// `true` if the operator commutes with all stabilizers, `false` otherwise. + fn commutes_with_stabilizers(&self, pauli_string: &str) -> bool { + // Default implementation - derived types should override for efficiency + let _ = pauli_string; + unimplemented!("commutes_with_stabilizers not yet implemented") + } + + /// Returns the number of qubits in the simulator. + /// + /// This method should be implemented by each simulator type to return + /// the number of qubits being simulated. + fn num_qubits(&self) -> usize; +} diff --git a/python/pecos-rslib/rust/Cargo.toml b/python/pecos-rslib/rust/Cargo.toml index fda60e2dc..1e09dd34e 100644 --- a/python/pecos-rslib/rust/Cargo.toml +++ b/python/pecos-rslib/rust/Cargo.toml @@ -30,6 +30,8 @@ pecos = { workspace = true } pecos-core = { workspace = true } pecos-qasm = { workspace = true, features = ["wasm"] } pecos-engines = { workspace = true } +pecos-qsim = { workspace = true } +pecos-cppsparsesim = { path = "../../../crates/pecos-cppsparsesim" } parking_lot = { workspace = true} serde_json = { workspace = true } diff --git a/python/pecos-rslib/rust/src/cpp_sparse_sim_bindings.rs b/python/pecos-rslib/rust/src/cpp_sparse_sim_bindings.rs new file mode 100644 index 000000000..bbcd2bc89 --- /dev/null +++ b/python/pecos-rslib/rust/src/cpp_sparse_sim_bindings.rs @@ -0,0 +1,300 @@ +// 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_cppsparsesim::CppSparseStab; +use pecos_qsim::{CliffordGateable, QuantumSimulator}; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyTuple}; + +// Monte Carlo engines create independent simulator copies for each thread. +// CppSparseStab implements Send, so each thread gets exclusive access to its own instance. +#[pyclass(name = "CppSparseSim")] +pub struct CppSparseSim { + inner: CppSparseStab, +} + +#[pymethods] +impl CppSparseSim { + #[new] + #[pyo3(signature = (num_qubits, seed=None))] + fn new(num_qubits: usize, seed: Option) -> Self { + let inner = match seed { + Some(s) => CppSparseStab::with_seed(num_qubits, s), + None => CppSparseStab::new(num_qubits), + }; + CppSparseSim { inner } + } + + fn set_seed(&mut self, seed: u64) { + self.inner.set_seed(seed); + } + + fn reset(&mut self) { + self.inner.reset(); + } + + fn __repr__(&self) -> String { + format!("CppSparseSim(num_qubits={})", self.inner.num_qubits()) + } + + #[getter] + fn num_qubits(&self) -> usize { + self.inner.num_qubits() + } + + #[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) + } + "MZ" => { + let result = self.inner.mz(location); + Ok(Some(u8::from(result.outcome))) + } + "MX" => { + let result = self.inner.mx(location); + Ok(Some(u8::from(result.outcome))) + } + "MY" => { + let result = self.inner.my(location); + Ok(Some(u8::from(result.outcome))) + } + "MZForced" => { + if let Some(params) = params { + // Extract forced_outcome as integer first, then convert to bool + let forced_int = params + .get_item("forced_outcome")? + .ok_or_else(|| { + PyErr::new::( + "MZForced requires a 'forced_outcome' parameter", + ) + })? + .extract::()?; + let forced_value = forced_int != 0; + let result = self.inner.force_measure(location, forced_value); + Ok(Some(u8::from(result.outcome))) + } else { + Err(PyErr::new::( + "MZForced requires a 'forced_outcome' parameter", + )) + } + } + _ => Err(PyErr::new::(format!( + "Unsupported single-qubit gate: {symbol}" + ))), + } + } + + 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) + } + "SWAP" => { + self.inner.swap(q1, q2); + Ok(None) + } + "G2" => { + self.inner.g2(q1, q2); + Ok(None) + } + "SXX" => { + self.inner.sxx(q1, q2); + Ok(None) + } + "SXXdg" => { + self.inner.sxxdg(q1, q2); + Ok(None) + } + _ => Err(PyErr::new::(format!( + "Unsupported two-qubit gate: {symbol}" + ))), + } + } + + 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::( + "Gates must have either 1 or 2 qubit locations", + )), + } + } + + // Additional methods that mirror SparseSim's API + fn h(&mut self, qubit: usize) { + self.inner.h(qubit); + } + + fn x(&mut self, qubit: usize) { + self.inner.x(qubit); + } + + fn y(&mut self, qubit: usize) { + self.inner.y(qubit); + } + + fn z(&mut self, qubit: usize) { + self.inner.z(qubit); + } + + fn cx(&mut self, control: usize, target: usize) { + self.inner.cx(control, target); + } + + fn mz(&mut self, qubit: usize) -> bool { + self.inner.mz(qubit).outcome + } + + fn mx(&mut self, qubit: usize) -> bool { + self.inner.mx(qubit).outcome + } + + fn my(&mut self, qubit: usize) -> bool { + self.inner.my(qubit).outcome + } + + fn stab_tableau(&mut self) -> String { + self.inner.stab_tableau() + } + + fn destab_tableau(&mut self) -> String { + self.inner.destab_tableau() + } +} diff --git a/python/pecos-rslib/rust/src/lib.rs b/python/pecos-rslib/rust/src/lib.rs index 61fc6b912..1a28582ea 100644 --- a/python/pecos-rslib/rust/src/lib.rs +++ b/python/pecos-rslib/rust/src/lib.rs @@ -17,6 +17,7 @@ // the License. mod byte_message_bindings; +mod cpp_sparse_sim_bindings; mod engine_bindings; mod noise_helpers; // mod pcg_bindings; @@ -30,6 +31,7 @@ mod state_vec_bindings; mod state_vec_engine_bindings; use byte_message_bindings::{PyByteMessage, PyByteMessageBuilder}; +use cpp_sparse_sim_bindings::CppSparseSim; use pecos_rng_bindings::RngPcg; use pyo3::prelude::*; use sparse_stab_bindings::SparseSim; @@ -41,6 +43,7 @@ use state_vec_engine_bindings::PyStateVecEngine; #[pymodule] fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/python/pecos-rslib/src/pecos_rslib/__init__.py b/python/pecos-rslib/src/pecos_rslib/__init__.py index d7ab3de07..a586061ee 100644 --- a/python/pecos-rslib/src/pecos_rslib/__init__.py +++ b/python/pecos-rslib/src/pecos_rslib/__init__.py @@ -19,6 +19,7 @@ from importlib.metadata import PackageNotFoundError, version from pecos_rslib.rssparse_sim import SparseSimRs +from pecos_rslib.cppsparse_sim import CppSparseSimRs from pecos_rslib.rsstate_vec import StateVecRs from pecos_rslib._pecos_rslib import ByteMessage from pecos_rslib._pecos_rslib import ByteMessageBuilder @@ -60,6 +61,7 @@ __all__ = [ "SparseSimRs", + "CppSparseSimRs", "StateVecRs", "ByteMessage", "ByteMessageBuilder", diff --git a/python/pecos-rslib/src/pecos_rslib/cppsparse_sim.py b/python/pecos-rslib/src/pecos_rslib/cppsparse_sim.py new file mode 100644 index 000000000..8a78dd521 --- /dev/null +++ b/python/pecos-rslib/src/pecos_rslib/cppsparse_sim.py @@ -0,0 +1,523 @@ +# 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. + +"""C++-based sparse stabilizer simulator for PECOS. + +This module provides a Python interface to the high-performance C++ implementation of sparse stabilizer simulation, +enabling efficient quantum circuit simulation for stabilizer circuits with reduced memory overhead and improved +performance compared to dense state vector representations. +""" + +from __future__ import annotations + +# ruff: noqa: SLF001 + +from typing import TYPE_CHECKING, NoReturn + +from pecos_rslib._pecos_rslib import CppSparseSim as CppRustSparseSim + +if TYPE_CHECKING: + from pecos.typing import SimulatorGateParams + + +class CppSparseSimRs: + """C++-based sparse stabilizer simulator wrapped via Rust. + + A high-performance sparse stabilizer simulator implemented in C++, exposed through Rust bindings, + providing efficient simulation of quantum circuits that can be represented using the stabilizer + formalism with reduced memory requirements. + """ + + def __init__(self, num_qubits: int, seed: int | None = None): + """Initialize the C++-based sparse simulator. + + Args: + num_qubits: Number of qubits to simulate. + seed: Optional seed for the RNG. If None, uses hardware random. + """ + if seed is not None: + self._sim = CppRustSparseSim(num_qubits, seed) + else: + self._sim = CppRustSparseSim(num_qubits) + self.num_qubits = num_qubits + self.bindings = dict(gate_dict) + + def reset(self) -> CppSparseSimRs: + """Reset the simulator to its initial state. + + Returns: + Self for method chaining. + """ + self._sim.reset() + return self + + def set_seed(self, seed: int) -> None: + """Set the RNG seed for this simulator instance. + + Args: + seed: The seed value for the random number generator. + """ + self._sim.set_seed(seed) + + def run_gate( + self, + symbol: str, + locations: set[int] | set[tuple[int, ...]], + **params: SimulatorGateParams, + ) -> dict[int, int]: + """Execute a quantum gate on specified locations. + + Args: + symbol: Gate symbol/name to execute. + locations: Set of qubit locations to apply the gate to. + **params: Additional gate parameters. + + 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"],) + + 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 is not None: + output[location] = results + + return output + + def run_circuit( + self, + circuit, + 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 + + def add_faults(self, circuit, removed_locations: set[int] | None = None) -> None: + """Add faults to the simulator by running a circuit. + + Args: + circuit: Circuit containing fault operations. + removed_locations: Optional set of locations to exclude. + """ + self.run_circuit(circuit, removed_locations) + + @property + def stabs(self) -> TableauWrapper: + """Get stabilizers tableau wrapper. + + Returns: + Wrapper for accessing stabilizer tableau. + """ + return TableauWrapper(self._sim, is_stab=True) + + @property + def destabs(self) -> TableauWrapper: + """Get destabilizers tableau wrapper. + + Returns: + Wrapper for accessing destabilizer tableau. + """ + return TableauWrapper(self._sim, is_stab=False) + + def print_stabs( + self, + *, + verbose: bool = True, + print_y: bool = True, + print_destabs: bool = False, + ) -> str | tuple[str, str]: + """Print stabilizer tableau(s). + + Args: + verbose: Whether to print to stdout. + print_y: Whether to print Y operators as Y (True) or W (False). + print_destabs: Whether to also print destabilizers. + + Returns: + String representation of stabilizers, or tuple if destabs included. + """ + stabs_raw = self._sim.stab_tableau() + stabs_lines = stabs_raw.strip().split("\n") + stabs_formatted = [ + adjust_tableau_string(line, is_stab=True, print_y=print_y) + for line in stabs_lines + ] + + if print_destabs: + destabs_raw = self._sim.destab_tableau() + destabs_lines = destabs_raw.strip().split("\n") + destabs_formatted = [ + adjust_tableau_string(line, is_stab=False, print_y=print_y) + for line in destabs_lines + ] + + if verbose: + print("Stabilizers:") + for line in stabs_formatted: + print(line) + print("Destabilizers:") + for line in destabs_formatted: + print(line) + return stabs_formatted, destabs_formatted + else: + if verbose: + print("Stabilizers:") + for line in stabs_formatted: + print(line) + return stabs_formatted + + def logical_sign(self, logical_op) -> NoReturn: # noqa: ARG002 + """Calculate logical sign (not implemented). + + Args: + logical_op: Logical operator to analyze. + + Raises: + NotImplementedError: This method is not yet implemented. + """ + msg = "logical_sign method not implemented yet" + raise NotImplementedError(msg) + + def refactor( + self, xs, zs, choose=None, prefer=None, protected=None + ) -> NoReturn: # noqa: ARG002 + """Refactor stabilizer tableau (not implemented). + + Args: + xs: X component. + zs: Z component. + choose: Choice parameter. + prefer: Preference parameter. + protected: Protection parameter. + + Raises: + NotImplementedError: This method is not yet implemented. + """ + msg = "refactor method not implemented yet" + raise NotImplementedError(msg) + + def find_stab(self, xs, zs) -> NoReturn: # noqa: ARG002 + """Find stabilizer (not implemented). + + Args: + xs: X component. + zs: Z component. + + Raises: + NotImplementedError: This method is not yet implemented. + """ + msg = "find_stab method not implemented yet" + raise NotImplementedError(msg) + + def copy(self) -> NoReturn: + """Create a copy of the simulator (not implemented). + + Raises: + NotImplementedError: This method is not yet implemented. + """ + msg = "copy method not implemented yet" + raise NotImplementedError(msg) + + +class TableauWrapper: + def __init__(self, sim, *, is_stab: bool): + self._sim = sim + self._is_stab = is_stab + + def print_tableau( + self, *, verbose: bool = False, print_y: bool = False + ) -> list[str]: + if self._is_stab: + tableau = self._sim.stab_tableau() + else: + tableau = self._sim.destab_tableau() + + lines = tableau.strip().split("\n") + adjusted_lines = [ + adjust_tableau_string(line, is_stab=self._is_stab, print_y=print_y) + for line in lines + ] + + if verbose: + for line in adjusted_lines: + print(line) + + return adjusted_lines + + +def _measure_z_forced(sim, qubit: int, params: dict) -> int | None: + """Perform forced Z measurement, returning None (omitted) when result is 0.""" + params.get("forced_outcome", 0) + # Debug output + # print(f"[Python] _measure_z_forced: qubit={qubit}, forced_outcome={forced}") + result = sim.run_1q_gate("MZForced", qubit, params) + # print(f"[Python] _measure_z_forced: result={result}") + # For compatibility with Python simulators, return None when measurement is 0 + # This causes the result to be omitted from the output dict + if result == 0: + return None + return result + + +def _init_to_zero(sim, qubit: int, forced_outcome: int = -1) -> None: + """Initialize qubit to |0> by measuring and correcting. + + Args: + sim: The simulator instance + qubit: The qubit to initialize + forced_outcome: The forced measurement outcome (-1 for random, 0 or 1 for forced) + """ + # Measure the qubit with optional forcing + if forced_outcome == -1: + result = sim.mz(qubit) + else: + # Use forced measurement - this matches Python's behavior + result = sim.run_1q_gate("MZForced", qubit, {"forced_outcome": forced_outcome}) + result = result if result is not None else 0 + # If it's |1>, flip it to |0> + if result: + sim.x(qubit) + return None + + +def _init_to_one(sim, qubit: int, forced_outcome: int = -1) -> None: + """Initialize qubit to |1> by measuring and correcting. + + Args: + sim: The simulator instance + qubit: The qubit to initialize + forced_outcome: The forced measurement outcome (-1 for random, 0 or 1 for forced) + """ + # Measure the qubit with optional forcing + if forced_outcome == -1: + result = sim.mz(qubit) + else: + # Use forced measurement + result = sim.run_1q_gate("MZForced", qubit, {"forced_outcome": forced_outcome}) + result = result if result is not None else 0 + # If it's |0>, flip it to |1> + if not result: + sim.x(qubit) + return None + + +def _init_to_plus(sim, qubit: int) -> None: + """Initialize qubit to |+>.""" + # First ensure |0> (no forcing since we want deterministic init) + _init_to_zero(sim, qubit, forced_outcome=-1) + # Apply H to get |+> + sim.h(qubit) + return None + + +def _init_to_minus(sim, qubit: int) -> None: + """Initialize qubit to |->.""" + # First ensure |1> + _init_to_one(sim, qubit) + # Apply H to get |-> + sim.h(qubit) + return None + + +def _init_to_plus_i(sim, qubit: int) -> None: + """Initialize qubit to |+i> using H5 gate.""" + # C++ H5 on |0> produces iY which is iW (what we need for |+i>) + _init_to_zero(sim, qubit, forced_outcome=-1) + sim.run_1q_gate("H5", qubit, {}) + return None + + +def _init_to_minus_i(sim, qubit: int) -> None: + """Initialize qubit to |-i> using H6 gate.""" + # C++ H6 on |0> produces -iY which is -iW (what we need for |-i>) + _init_to_zero(sim, qubit, forced_outcome=-1) + sim.run_1q_gate("H6", qubit, {}) + return None + + +def adjust_tableau_string(line: str, *, is_stab: bool, print_y: bool = True) -> str: + """ + Adjust the tableau string to ensure the sign part always takes up two spaces + and handle Y vs W display based on print_y parameter. + + Args: + line (str): A single line from the tableau string. + is_stab (bool): True if this is a stabilizer, False if destabilizer. + print_y (bool): If True, show Y operators as Y. If False, show as W with proper phase. + + Returns: + str: The adjusted line with proper spacing and Y/W formatting. + """ + # First handle the sign formatting + if is_stab: + if line.startswith("+i"): + adjusted = " i" + line[2:] + elif line.startswith("-i"): + adjusted = "-i" + line[2:] + elif line.startswith("i"): + adjusted = " i" + line[1:] # Handle bare imaginary (no + or -) + elif line.startswith("+"): + adjusted = " " + line[1:] + elif line.startswith("-"): + adjusted = " -" + line[1:] + else: + adjusted = " " + line # Default case, shouldn't happen with correct input + else: + # For destabilizers, strip all signs (no phases shown) + # Remove any sign prefix (+, -, +i, -i, i) and add two spaces + if line.startswith("+i") or line.startswith("-i"): + adjusted = " " + line[2:] # Strip 2 chars for imaginary signs + elif line.startswith("i"): + adjusted = " " + line[1:] # Strip 1 char for bare imaginary + elif line.startswith("+") or line.startswith("-"): + adjusted = " " + line[1:] # Strip 1 char for real signs + else: + adjusted = " " + line # No sign to strip + + # Handle Y vs W conversion based on print_y parameter + if not print_y: + # Simply replace Y with W - the phase is already correct from C++ + adjusted = adjusted.replace("Y", "W") + + return adjusted + + +# Define the gate dictionary - reuse the same mappings as SparseSim +gate_dict = { + "I": lambda sim, q, **params: None, # noqa: ARG005 + "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), + # Alternative names for square root gates + "Q": lambda sim, q, **params: sim._sim.run_1q_gate( + "SX", q, params + ), # Q = sqrt(X) = SX + "Qd": lambda sim, q, **params: sim._sim.run_1q_gate("SXdg", q, params), # Q† = SXdg + "R": lambda sim, q, **params: sim._sim.run_1q_gate( + "SY", q, params + ), # R = sqrt(Y) = SY + "Rd": lambda sim, q, **params: sim._sim.run_1q_gate("SYdg", q, params), # R† = SYdg + "S": lambda sim, q, **params: sim._sim.run_1q_gate("SZ", q, params), # S gate is SZ + "Sd": lambda sim, q, **params: sim._sim.run_1q_gate("SZdg", q, params), # S dagger + "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), + "F1": lambda sim, q, **params: sim._sim.run_1q_gate( + "F", q, params + ), # Alternative name for F + "F1d": lambda sim, q, **params: sim._sim.run_1q_gate( + "Fdg", q, params + ), # Alternative name for Fdg + "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), + "F2d": lambda sim, q, **params: sim._sim.run_1q_gate( + "F2dg", q, params + ), # Alternative name for F2dg + "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), + "F3d": lambda sim, q, **params: sim._sim.run_1q_gate( + "F3dg", q, params + ), # Alternative name for F3dg + "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), + "F4d": lambda sim, q, **params: sim._sim.run_1q_gate( + "F4dg", q, params + ), # Alternative name for F4dg + "II": lambda sim, qs, **params: None, # noqa: ARG005 + "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), + "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 + ), # G is an alias for G2 + "G2": lambda sim, qs, **params: sim._sim.run_2q_gate("G2", 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), + "SqrtXX": lambda sim, qs, **params: sim._sim.run_2q_gate( + "SXX", qs, params + ), # SqrtXX is an alias for SXX + "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), + "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), + "Measure": lambda sim, q, **params: sim._sim.run_1q_gate("MZ", q, params), + "measure Z": lambda sim, q, **params: _measure_z_forced(sim._sim, q, params), + "MZForced": lambda sim, q, **params: _measure_z_forced(sim._sim, q, params), + # PZForced - for the forced projection gate, we still support forced_outcome + "PZForced": lambda sim, q, **params: ( + _init_to_zero(sim._sim, q, forced_outcome=params.get("forced_outcome", 0)) + if params.get("forced_outcome", 0) == 0 + else _init_to_one(sim._sim, q, forced_outcome=params.get("forced_outcome", 1)) + ), + # Init gates - always initialize to the specified state, ignore forced_outcome + # CppSparseStab doesn't have PZ/PX/PY projection gates, so we measure and correct + "Init": lambda sim, q, **params: _init_to_zero(sim._sim, q), # Init to |0> + "init |0>": lambda sim, q, **params: _init_to_zero( + sim._sim, q, forced_outcome=params.get("forced_outcome", -1) + ), + "init |1>": lambda sim, q, **params: _init_to_one(sim._sim, q), + "init |+>": lambda sim, q, **params: _init_to_plus(sim._sim, q), + "init |->": lambda sim, q, **params: _init_to_minus(sim._sim, q), + "init |+i>": lambda sim, q, **params: _init_to_plus_i(sim._sim, q), + "init |-i>": lambda sim, q, **params: _init_to_minus_i(sim._sim, q), +} + +__all__ = ["CppSparseSimRs", "gate_dict"] diff --git a/python/pecos-rslib/src/pecos_rslib/rssparse_sim.py b/python/pecos-rslib/src/pecos_rslib/rssparse_sim.py index e32b04dc7..976d67138 100644 --- a/python/pecos-rslib/src/pecos_rslib/rssparse_sim.py +++ b/python/pecos-rslib/src/pecos_rslib/rssparse_sim.py @@ -85,7 +85,7 @@ def run_gate( msg = f"Gate {symbol} is not supported in this simulator." raise Exception(msg) - if results: + if results is not None: output[location] = results return output diff --git a/python/quantum-pecos/src/pecos/simulators/__init__.py b/python/quantum-pecos/src/pecos/simulators/__init__.py index 8df11087b..8d9c0d6db 100644 --- a/python/quantum-pecos/src/pecos/simulators/__init__.py +++ b/python/quantum-pecos/src/pecos/simulators/__init__.py @@ -17,7 +17,7 @@ # specific language governing permissions and limitations under the License. # Rust version of stabilizer sim -from pecos_rslib import SparseSimRs, StateVecRs +from pecos_rslib import CppSparseSimRs, SparseSimRs, StateVecRs from pecos_rslib import SparseSimRs as SparseSim from pecos.simulators import sim_class_types @@ -37,14 +37,6 @@ SparseSim as SparseSimPy, ) -# C++ version of SparseStabSim wrapper -try: - from pecos.simulators.cysparsesim import ( - SparseSim as SparseSimCy, - ) -except ImportError: - SparseSimCy = None - # Attempt to import optional ProjectQ package try: import projectq diff --git a/python/quantum-pecos/src/pecos/simulators/compile_cython.py b/python/quantum-pecos/src/pecos/simulators/compile_cython.py deleted file mode 100644 index 6e6ad35be..000000000 --- a/python/quantum-pecos/src/pecos/simulators/compile_cython.py +++ /dev/null @@ -1,75 +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. - -"""Cython compilation utilities for PECOS simulators. - -This module provides functionality to compile Cython extensions for -high-performance PECOS quantum simulators. -""" - -import subprocess -import sys -from pathlib import Path - - -def main() -> None: - """Compile Cython extensions for PECOS simulators.""" - # See if Cython has been installed... - - current_location = Path.parent(Path.resolve(__file__)) - - cython_dirs = [ - "cysparsesim", - ] - failed = {} - - for d in cython_dirs: - path = Path(current_location / d) - - p = subprocess.Popen( # noqa: S603 - Running trusted setup.py for Cython compilation - [sys.executable, "setup.py", "build_ext", "--inplace"], - cwd=path, - stderr=subprocess.PIPE, - ) - p.wait() - _, error = p.communicate() - - if p.returncode: - failed[d] = error - - return failed, cython_dirs - - -if __name__ == "__main__": - failed, cython_dirs = main() - - successful = set(cython_dirs) - set(failed.keys()) - - if successful: - print(f"\nSUCCESSFUL COMPILATION ({len(successful)}/{len(cython_dirs)}):") - for c in cython_dirs: - if c in successful: - print(c) - - if failed: - print(f"\nFAILED ({len(failed)}/{len(cython_dirs)}):") - - for f, error in failed.items(): - print("--------------") - print(f'Cython package "{f}" failed to compile!') - - print("\nError:\n") - print(error.decode()) - print("--------------") - - print("\nRecommend compiling separately those that failed.") diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/.gitignore b/python/quantum-pecos/src/pecos/simulators/cysparsesim/.gitignore deleted file mode 100644 index d5c4a1fd0..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyd diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/__init__.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim/__init__.py deleted file mode 100644 index 714346905..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Cython sparse stabilizer simulator. - -This package provides a Cython-based sparse stabilizer simulator for efficient simulation. -""" - -# 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. - -from pecos.simulators.cysparsesim.cysparsesim import SparseSim diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/setup.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim/setup.py deleted file mode 100644 index 7953fec2c..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/setup.py +++ /dev/null @@ -1,73 +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. - -"""Setup. - -===== - -The setup file for the Cython wrapped C++ version of SparseSim. - -Notes: ------ - Use the following to compile from command line: - python setup.py build_ext --inplace - -""" - -import contextlib -import shutil -from distutils.core import setup -from distutils.extension import Extension -from pathlib import Path - -from Cython.Build import cythonize - -# Delete previous build folder -current_location = Path.parent(Path.resolve(__file__)) -with contextlib.suppress(FileNotFoundError): - shutil.rmtree(Path(current_location / "build")) - -# compiler_flags = ["-std=c++11", "-Wall", "-fPIC", "-O2", "-O3", "-c", ] -compiler_flags = ["-std=c++11", "-W3", "-fPIC", "-O2", "-O3", "-c"] - -ext_modules = [ - Extension( - "cysparsesim", - sources=[ - "src/cysparsesim.pyx", - "src/sparsesim.cpp", - "src/logical_sign.py", - ], - language="c++", - extra_compile_args=compiler_flags, - include_dirs=["./src"], - # include_dirs=[np.get_include()], - ), -] - - -for e in ext_modules: - e.cython_directives = { - "boundscheck": False, - "wraparound": False, - } - - -setup( - name="state", - ext_modules=cythonize(ext_modules, build_dir="build", language_level=3), - script_args=["build_ext"], - options={ - "build_ext": {"inplace": True}, - }, -) diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/__init__.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/__init__.py deleted file mode 100644 index 7ec8e503e..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Source implementation for Cython sparse simulator. - -This package contains the source implementation for the Cython sparse stabilizer 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. diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim.pyx b/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim.pyx deleted file mode 100644 index 44ea7d77a..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim.pyx +++ /dev/null @@ -1,619 +0,0 @@ -# distutils: language = c++ -#cython: language_level=3, boundscheck=False, nonecheck=False, wraparound=False - -# 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. - -cimport src.cysparsesim_header as s -from src.cysparsesim_header cimport int_num, bool - -# from .logical_sign import find_logical_signs -from .src.logical_sign import find_logical_signs - -from ..gate_syms import alt_symbols - -cdef dict bindings = { - - 'init |0>': SparseSim.initzero, - 'init |1>': SparseSim.initone, - 'init |+>': SparseSim.initplus, - 'init |->': SparseSim.initminus, - 'init |+i>': SparseSim.initplusi, - 'init |-i>': SparseSim.initminusi, - - 'I': SparseSim.I, - 'X': SparseSim.X, - 'Y': SparseSim.Y, - 'Z': SparseSim.Z, - - 'SX': SparseSim.SX, - 'SXd': SparseSim.SXdg, - 'SY': SparseSim.SY, - 'SYdg': SparseSim.SYdg, - 'SZ': SparseSim.SZ, - 'SZdg': SparseSim.SZdg, - - 'H': SparseSim.hadamard, - 'H2': SparseSim.H2, - 'H3': SparseSim.H3, - 'H4': SparseSim.H4, - 'H5': SparseSim.H5, - 'H6': SparseSim.H6, - - 'F': SparseSim.F, - 'F2': SparseSim.F2, - 'F3': SparseSim.F3, - 'F4': SparseSim.F4, - - 'F1dg': SparseSim.Fdg, - 'F2dg': SparseSim.F2dg, - 'F3dg': SparseSim.F3dg, - 'F4dg': SparseSim.F4dg, - - 'II': SparseSim.II, - 'CX': SparseSim.cx, - 'CZ': SparseSim.cz, - 'CY': SparseSim.cy, - 'SWAP': SparseSim.swap, - 'G': SparseSim.g2, - - 'SXX': SparseSim.SXX, # \equiv e^{+i (\pi /4)} * e^{-i (\pi /4) XX } == R(XX, pi/2) - 'MS': SparseSim.SXX, - 'MSXX': SparseSim.SXX, - - 'measure X': SparseSim.measureX, - 'measure Y': SparseSim.measureY, - 'measure Z': SparseSim.measure, - 'force output': SparseSim.force_output, - - } - -cdef class SparseSim: - - cdef s.State* _c_state - - cdef public: - int_num num_qubits - int reserve_buckets - dict bindings - - def __cinit__(self, int_num num_qubits, int reserve_buckets=0): - self._c_state = new s.State(num_qubits, reserve_buckets) - - self.num_qubits = self._c_state.num_qubits - self.reserve_buckets = self._c_state.reserve_buckets - self.bindings = dict(bindings) - for k, v in alt_symbols.items(): - if v in self.bindings: - self.bindings[k] = self.bindings[v] - - cdef void hadamard(self, int_num qubit): - self._c_state.hadamard(qubit) - - cdef void H2(self, int_num qubit): - self._c_state.H2(qubit) - - cdef void H3(self, int_num qubit): - self._c_state.H3(qubit) - - cdef void H4(self, int_num qubit): - self._c_state.H4(qubit) - - cdef void H5(self, int_num qubit): - self._c_state.H5(qubit) - - cdef void H6(self, int_num qubit): - self._c_state.H6(qubit) - - cdef void F(self, int_num qubit): - self._c_state.F(qubit) - - cdef void F2(self, int_num qubit): - self._c_state.F2(qubit) - - cdef void F3(self, int_num qubit): - self._c_state.F3(qubit) - - cdef void F4(self, int_num qubit): - self._c_state.F4(qubit) - - cdef void Fdg(self, int_num qubit): - self._c_state.Fdg(qubit) - - cdef void F2dg(self, int_num qubit): - self._c_state.F2dg(qubit) - - cdef void F3dg(self, int_num qubit): - self._c_state.F3dg(qubit) - - cdef void F4dg(self, int_num qubit): - self._c_state.F4dg(qubit) - - cdef void I(self, int_num qubit): - pass - - cdef void X(self, int_num qubit): - self._c_state.bitflip(qubit) - - cdef void Y(self, int_num qubit): - self._c_state.Y(qubit) - - cdef void Z(self, int_num qubit): - self._c_state.phaseflip(qubit) - - cdef void SZ(self, int_num qubit): - self._c_state.phaserot(qubit) - - cdef void SZdg(self, int_num qubit): - self._c_state.SZdg(qubit) - - cdef void SY(self, int_num qubit): - self._c_state.SY(qubit) - - cdef void SYdg(self, int_num qubit): - self._c_state.SYdg(qubit) - - cdef void SX(self, int_num qubit): - self._c_state.SX(qubit) - - cdef void SXdg(self, int_num qubit): - self._c_state.SXdg(qubit) - - cdef void cx(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.cx(cqubit, tqubit) - - cdef void cy(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.phaserot(tqubit) - self._c_state.cx(cqubit, tqubit) - self._c_state.SZdg(tqubit) - - cdef void cz(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.hadamard(tqubit) - self._c_state.cx(cqubit, tqubit) - self._c_state.hadamard(tqubit) - - cdef void g2(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.hadamard(cqubit) - self._c_state.cx(tqubit, cqubit) - self._c_state.cx(cqubit, tqubit) - self._c_state.hadamard(tqubit) - - cdef void swap(self, tuple qubits): - cdef int_num qubit1 = qubits[0] - cdef int_num qubit2 = qubits[1] - - self._c_state.swap(qubit1, qubit2) - - cdef void II(self, tuple qubits): - pass - - cdef void SXX(self, tuple qubits): - cdef int_num qubit1 = qubits[0] - cdef int_num qubit2 = qubits[1] - - self._c_state.SX(qubit1) # Sqrt X - self._c_state.SX(qubit2) # Sqrt X - self._c_state.SYdg(qubit1) # (Sqrt Y)^\dagger - self._c_state.cx(qubit1, qubit2) # CNOT - self._c_state.SY(qubit1) # Sqrt Y - - # cpdef unsigned int measure(self, const s.int_num qubit, - def measure(self, const int_num qubit, int forced_outcome=-1, bool collapse=True): - - return self._c_state.measure(qubit, forced_outcome, collapse) - - - def measureX(self, const int_num qubit, int forced_outcome=-1, bool collapse=True): - - cdef unsigned int result - - self._c_state.hadamard(qubit) - result = self._c_state.measure(qubit, forced_outcome, collapse) - self._c_state.hadamard(qubit) - return result - - def measureY(self, const int_num qubit, int forced_outcome=-1, bool collapse=True): - - cdef unsigned int result - - self._c_state.H5(qubit) - result = self._c_state.measure(qubit, forced_outcome, collapse) - self._c_state.H5(qubit) - return result - - cdef void initzero(self, const int_num qubit, int forced_outcome = -1): - cdef unsigned int result - - result = self._c_state.measure(qubit, forced_outcome, True) - - if result == 1: - self._c_state.bitflip(qubit) - - cdef void initone(self, const int_num qubit, int forced_outcome = -1): - cdef unsigned int result - - result = self._c_state.measure(qubit, forced_outcome, True) - - if result == 0: - self._c_state.bitflip(qubit) - - cdef void initplus(self, const int_num qubit, int forced_outcome = -1): - self.initzero(qubit, forced_outcome) - self._c_state.hadamard(qubit) - - cdef void initminus(self, const int_num qubit, int forced_outcome = -1): - self.initone(qubit, forced_outcome) - self._c_state.hadamard(qubit) - - cdef void initplusi(self, const int_num qubit, int forced_outcome = -1): - self.initzero(qubit, forced_outcome) - self._c_state.H5(qubit) - - cdef void initminusi(self, const int_num qubit, int forced_outcome = -1): - self.initone(qubit, forced_outcome) - self._c_state.H6(qubit) - - def logical_sign(self, logical_op): - """ - - Args: - logical_op: - - Returns: - - """ - return find_logical_signs(self, logical_op) - - def run_gate(self, symbol, locations, **params): - """ - - Args: - symbol: - locations: - **params: - - Returns: - - """ - - output = {} - for location in locations: - results = self.bindings[symbol](self, location, **params) - - if results: - output[location] = results - - return output - - def run_circuit(self, circuit, removed_locations=None): - """ - - Args: - circuit (QuantumCircuit): A circuit instance or object with an appropriate items() generator. - removed_locations: - - Returns (list): If output is True then the circuit output is returned. Note that this output format may differ - from what a ``circuit_runner`` will return for the same method named ``run_circuit``. - - """ - - # TODO: removed_locations doesn't make sense except if circuit is tick_circuit - # because can't say not to do gates for particular ticks.... - - 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 - - @property - def signs_minus(self): - return self._c_state.signs_minus - - @property - def signs_i(self): - return self._c_state.signs_i - - @property - def stabs(self): - return self._c_state.stabs - - @property - def destabs(self): - return self._c_state.destabs - - def _pauli_sign(self, gen, i_gen): - - if i_gen in self.signs_minus: - if i_gen in self.signs_i: - sign = '-i' - else: - sign = ' -' - else: - - if i_gen in self.signs_i: - sign = ' i' - else: - sign = ' ' - - return sign - - def force_output(self, int_num qubit, forced_output=-1): - """ - Outputs value. - - Used for error generators to generate outputs when replacing measurements. - - Args: - state: - qubit: - output: - - Returns: - - """ - return forced_output - - def col_string(self, gen, print_signs=True): - """ - Prints out the stabilizers for the column-wise sparse representation. - - :param num_qubits: - :return: - """ - - col_x = gen['col_x'] - col_z = gen['col_z'] - - result = [] - - num_qubits = self.num_qubits - - for i_gen in range(num_qubits): - - stab_letters = [] - - # ---- Signs ---- # - if print_signs: - sign = self._pauli_sign(gen, i_gen) - else: - sign = ' ' - - stab_letters.append(sign) - - # ---- Paulis ---- # - for qubit in range(num_qubits): - - letter = 'U' - - if i_gen in col_x[qubit] and i_gen not in col_z[qubit]: - letter = 'X' - - elif i_gen not in col_x[qubit] and i_gen in col_z[qubit]: - letter = 'Z' - - elif i_gen in col_x[qubit] and i_gen in col_z[qubit]: - letter = 'W' - - elif i_gen not in col_x[qubit] and i_gen not in col_z[qubit]: - letter = 'I' - - stab_letters.append(letter) - - # print(''.join(stab_letters)) - result.append(''.join(stab_letters)) - - return result - - def get_largest_degree(self): - - cdef: - list size_stabs_col_x = [] - list size_stabs_col_z = [] - list size_destabs_col_x = [] - list size_destabs_col_z = [] - list size_stabs_row_x = [] - list size_stabs_row_z = [] - list size_destabs_row_x = [] - list size_destabs_row_z = [] - dict output_dict = {} - - for col in self._c_state.stabs.col_x: - size_stabs_col_x.append(len(col)) - - for col in self._c_state.stabs.col_z: - size_stabs_col_z.append(len(col)) - - for col in self._c_state.destabs.col_x: - size_destabs_col_x.append(len(col)) - - for col in self._c_state.destabs.col_z: - size_destabs_col_z.append(len(col)) - - for row in self._c_state.stabs.row_x: - size_stabs_row_x.append(len(row)) - - for row in self._c_state.stabs.row_z: - size_stabs_row_z.append(len(row)) - - for row in self._c_state.destabs.row_x: - size_destabs_row_x.append(len(row)) - - for row in self._c_state.destabs.row_z: - size_destabs_row_z.append(len(row)) - - output_dict = { - 'size_stabs_col_x': size_stabs_col_x, - 'size_stabs_col_z': size_stabs_col_z, - 'size_destabs_col_x': size_destabs_col_x, - 'size_destabs_col_z': size_destabs_col_z, - 'size_stabs_row_x': size_stabs_row_x, - 'size_stabs_row_z': size_stabs_row_z, - 'size_destabs_row_x': size_destabs_row_x, - 'size_destabs_row_z': size_destabs_row_z, - 'size_signs_minus': len(self._c_state.signs_minus), - 'size_signs_i': len(self._c_state.signs_i), - } - - return output_dict - - def set_stabs_destabs(self, stabs_row_x, stabs_row_z, destabs_row_x, destabs_row_z): - - if len(stabs_row_x) != self.num_qubits: - raise Exception('Size of `stabs_row_x` must equal `num_qubits`.') - - if len(stabs_row_z) != self.num_qubits: - raise Exception('Size of `stabs_row_z` must equal `num_qubits`.') - - if len(destabs_row_x) != self.num_qubits: - raise Exception('Size of `destabs_row_x` must equal `num_qubits`.') - - if len(destabs_row_z) != self.num_qubits: - raise Exception('Size of `destabs_row_z` must equal `num_qubits`.') - - self._c_state.stabs.row_x = stabs_row_x - self._c_state.stabs.row_z = stabs_row_z - self._c_state.destabs.row_x = destabs_row_x - self._c_state.destabs.row_z = destabs_row_z - - stabs_col_x = [set() for i in range(self.num_qubits)] - stabs_col_z = [set() for i in range(self.num_qubits)] - destabs_col_x = [set() for i in range(self.num_qubits)] - destabs_col_z = [set() for i in range(self.num_qubits)] - - for s, q_set in enumerate(stabs_row_x): - for q in q_set: - # stabs_row_x[q].add(s) - stabs_col_x[q].add(s) - - for s, q_set in enumerate(stabs_row_z): - for q in q_set: - # stabs_row_z[q].add(s) - stabs_col_z[q].add(s) - - for s, q_set in enumerate(destabs_row_x): - for q in q_set: - # destabs_row_x[q].add(s) - destabs_col_x[q].add(s) - - for s, q_set in enumerate(destabs_row_z): - for q in q_set: - # destabs_row_z[q].add(s) - destabs_col_z[q].add(s) - - self._c_state.stabs.col_x = stabs_col_x - self._c_state.stabs.col_z = stabs_col_z - self._c_state.destabs.col_x = destabs_col_x - self._c_state.destabs.col_z = destabs_col_z - - def print_tableau(self, gen, verbose=True, print_signs=True): - """ - Prints out the stabilizers. - :return: - """ - - col_str = self.col_string(gen, print_signs=print_signs) - row_str = self.row_string(gen, print_signs=print_signs) - - if col_str != row_str: - print('col') - for line in col_str: - print(line) - - print('\nrow') - for line in row_str: - print(line) - - raise Exception('Something bad happened! String representation of the row-wise vs column-wile ' - 'spare stabilizers do not match!') - - if verbose: - for line in col_str: - print(line) - - return col_str - - def print_stabs(self): - str_s = self.print_tableau(self.stabs, verbose=True) - print('-------------------------------') - str_d = self.print_tableau(self.destabs, verbose=True, - print_signs=False) - - return str_s, str_d - - def row_string(self, gen, print_signs=True): - """ - Prints out the stabilizers for the row-wise sparse representation. - - """ - - row_x = gen['row_x'] - row_z = gen['row_z'] - - result = [] - - num_qubits = self.num_qubits - - for i_gen in range(num_qubits): - - stab_letters = [] - - # ---- Signs ---- # - if print_signs: - sign = self._pauli_sign(gen, i_gen) - else: - sign = ' ' - stab_letters.append(sign) - - # ---- Paulis ---- # - for qubit in range(num_qubits): - - letter = 'U' - - if qubit in row_x[i_gen] and qubit not in row_z[i_gen]: - letter = 'X' - - elif qubit not in row_x[i_gen] and qubit in row_z[i_gen]: - letter = 'Z' - - elif qubit in row_x[i_gen] and qubit in row_z[i_gen]: - letter = 'W' - - elif qubit not in row_x[i_gen] and qubit not in row_z[i_gen]: - letter = 'I' - - stab_letters.append(letter) - - # print(''.join(stab_letters)) - result.append(''.join(stab_letters)) - - return result - - def __dealloc__(self): - del self._c_state diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim_header.pxd b/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim_header.pxd deleted file mode 100644 index 41da089e3..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/cysparsesim_header.pxd +++ /dev/null @@ -1,70 +0,0 @@ -# distutils: language = c++ -#cython: language_level=3, boundscheck=False, nonecheck=False, wraparound=False - -# 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. - -from libcpp cimport bool -from libcpp.vector cimport vector -from libcpp.unordered_set cimport unordered_set - -cdef extern from "sparsesim.h": - - ctypedef unsigned long long int_num - ctypedef unordered_set[int_num] int_set - ctypedef vector[int_set] int_set_vec - - unsigned int random_outcome() - - struct Generators: - int_set_vec col_x - int_set_vec col_z - int_set_vec row_x - int_set_vec row_z - - cdef cppclass State: - # State() except + - State(int_num, bool) except + - State(int_num) except + - int_num num_qubits - int reserve_buckets - Generators stabs, destabs - int_set signs_minus, signs_i - - # Methods - void hadamard(const int_num &qubit) - void bitflip(const int_num &qubit) # X - void phaseflip(const int_num& qubit) # Z - void Y(const int_num& qubit) # Y - void phaserot(const int_num& qubit) # S - void SZdg(const int_num& qubit) # S - void SY(const int_num& qubit) # R - void SYdg(const int_num& qubit) # Rd - void SX(const int_num& qubit) # Q - void SXdg(const int_num& qubit) # Qd - void H2(const int_num &qubit) # H2 - void H3(const int_num &qubit) # H3 - void H4(const int_num &qubit) # H4 - void H5(const int_num &qubit) # H5 - void H6(const int_num &qubit) # H6 - void F(const int_num &qubit) # F1 - void F2(const int_num &qubit) # F2 - void F3(const int_num &qubit) # F3 - void F4(const int_num &qubit) # F4 - void Fdg(const int_num &qubit) # F1d - void F2dg(const int_num &qubit) # F2d - void F3dg(const int_num &qubit) # F3d - void F4dg(const int_num &qubit) # F4d - void cx(const int_num& tqubit, const int_num& cqubit) - void swap(const int_num& qubit1, const int_num& qubit2) - unsigned int measure(const int_num& qubit, int forced_outcome, bool collapse) diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/logical_sign.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/logical_sign.py deleted file mode 100644 index 7cf9d5d1e..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim/src/logical_sign.py +++ /dev/null @@ -1,186 +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. - -"""Functions for logical sign computation. - -find_logical_signs -logical_flip -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.circuits import QuantumCircuit - from pecos.protocols import SimulatorProtocol - - -def find_logical_signs( - state: SimulatorProtocol, - logical_circuit: QuantumCircuit, - delogical_circuit: QuantumCircuit | None = None, -) -> int: - """Find the sign of the logical operator. - - Args: - ---- - state: The quantum state instance - logical_circuit: The logical circuit to find the sign of - delogical_circuit: Optional delogical circuit to check anti-commutation with - """ - if len(logical_circuit) != 1: - msg = "Logical operators are expected to only have one tick." - raise Exception(msg) - - stabs = state.stabs - destabs = state.destabs - - logical_xs = set() - logical_zs = set() - - for symbol, gate_locations, _ in logical_circuit.items(): - if symbol == "X": - logical_xs.update(gate_locations) - elif symbol == "Z": - logical_zs.update(gate_locations) - elif symbol == "Y": - logical_xs.update(gate_locations) - logical_zs.update(gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - if ( - delogical_circuit - ): # Check the relationship between logical operator and delogical operator. - if len(delogical_circuit) != 1: - msg = "Delogical operators are expected to only have one tick." - raise Exception(msg) - - delogical_xs = set() - delogical_zs = set() - - for symbol, gate_locations, _ in delogical_circuit.items(): - if symbol == "X": - delogical_xs.update(gate_locations) - elif symbol == "Z": - delogical_zs.update(gate_locations) - elif symbol == "Y": - delogical_xs.update(gate_locations) - delogical_zs.update(gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - # Make sure the logical and delogical anti-commute - - anticom_x = ( - len(logical_xs & delogical_zs) % 2 - ) # Number of common elements modulo 2 - anticom_z = ( - len(logical_zs & delogical_xs) % 2 - ) # Number of common elements modulo 2 - - if not ((anticom_x + anticom_z) % 2): - print(f"logical Xs: {logical_xs} logical Zs: {logical_zs}") - print(f"delogical Xs: {delogical_xs} delogical Zs: {delogical_zs}") - msg = "Logical and delogical operators supplied do not anti-commute!" - raise Exception(msg) - - # We want the supplied logical operator to be in the stabilizer group and - # the supplied delogical to not be in the stabilizers (we want it to end up being the logical op's destabilizer) - - # The following two function calls are wasteful because we will need some of what they discover... such as all the - # stabilizers that have destabilizers that anti-commute with the logical operator... - # But it is assumed that the user is not calling this function that often... so we can be wasteful... - - # Check logical is a stabilizer (we want to remove it from the stabilizers) - - # Find the anti-commuting destabilizers => stabilizers to give the logical operator - # -------------------------- - build_stabs = set() - - for q in logical_xs: # For qubits that have Xs in for the logical operator... - build_stabs ^= destabs["col_z"][ - q - ] # Add in stabilizers that anti-commute for the logical operator's Xs - - for q in logical_zs: - build_stabs ^= destabs["col_x"][ - q - ] # Add in stabilizers that anti-commute for the logical operator's Zs - - # If a stabilizer anticommutes an even number of times for the X and/or Z Paulis... it will not appear due to ^= - - # Confirm that the stabilizers chosen give the logical operator. If not... return with a failure = 1 - # -------------------------- - test_x = set() - test_z = set() - - for stab in build_stabs: - test_x ^= stabs["row_x"][stab] - test_z ^= stabs["row_z"][stab] - - # Compare with logical operator - test_x ^= logical_xs - test_z ^= logical_zs - - if len(test_x) != 0 or len(test_z) != 0: - # for stab in build_stabs: - - print(f"Logical op: xs - {logical_xs} and zs - {logical_zs}") - msg = f"Failure due to not finding logical op! x... {test_x ^ logical_xs!s} z... {test_z ^ logical_zs!s}" - raise Exception(msg) - - # Get the sign of the logical operator - # -------------------------- - - # First, the minus sign - logical_minus = len(build_stabs & stabs.signs_minus) - - # Second, the number of imaginary numbers - logical_i = len(build_stabs & stabs.signs_i) - - # Translate the Ws to Ys... W = -i(iW) = -iY => For each Y add another -1 and +i. - logical_ws = logical_xs & logical_zs - num_ys = len(logical_ws) - - logical_minus += num_ys - logical_i += num_ys - - # Do (-1)^even = 1 -> 0, (-1)^odd = -1 -> 1 - logical_minus %= 2 - - # Reinterpret number of is - logical_i %= 4 - - # num_is %4 = 0 => +1 => logical_i = 0, logical_minus += 0 - # num_is %4 = 1 => +i => logical_i = 1, logical_minus += 0 - - if logical_i == 2: # num_is %4 = 2 => -1 => logical_i = 0, logical_minus += 1 - logical_i = 0 - logical_minus += 1 - elif logical_i == 3: # num_is %4 = 3 => -i => logical_i = 1, logical_minus += 1 - logical_i = 1 - logical_minus += 1 - - if logical_i != 0: - msg = "Logical operator has an imaginary sign... Not allowed if logical state is stabilized by logical op!" - raise Exception(msg) - - return logical_minus diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/.gitignore b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/.gitignore deleted file mode 100644 index 8213b52e5..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build -*.pyd -cysparsesim.cpp diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/__init__.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/__init__.py deleted file mode 100644 index 4d1f9344e..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Column-wise Cython sparse stabilizer simulator. - -This package provides a column-wise implementation of the Cython sparse stabilizer simulator. -""" - -# 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. - -from pecos.simulators.cysparsesim_col.cysparsesim import State diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/setup.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/setup.py deleted file mode 100644 index cb665df4c..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/setup.py +++ /dev/null @@ -1,63 +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. - -"""Setup. - -===== - -The setup file for the Cython wrapped C++ version of SparseSim. - -Notes: ------ - Use the following to compile from command line: - python setup.py build_ext --inplace - -""" - -import contextlib -import shutil -from distutils.core import setup -from distutils.extension import Extension -from pathlib import Path - -from Cython.Build import cythonize - -# Delete previous build folder -current_location = Path.parent(Path.resolve(__file__)) -with contextlib.suppress(FileNotFoundError): - shutil.rmtree(Path(current_location / "build")) - -compiler_flags = ["-std=c++11", "-Wall", "-fPIC", "-O2", "-O3", "-c"] - -ext_modules = [ - Extension( - "cysparsesim", - sources=["src/cysparsesim.pyx", "src/sparsesim.cpp"], - language="c++", - extra_compile_args=compiler_flags, - include_dirs=["./src"], - # include_dirs=[np.get_include()], - ), -] - - -for e in ext_modules: - e.cython_directives = {"boundscheck": False, "wraparound": False} - - -setup( - name="state", - ext_modules=cythonize(ext_modules, build_dir="build"), - script_args=["build_ext"], - options={"build_ext": {"inplace": True}}, -) diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/__init__.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/__init__.py deleted file mode 100644 index fbb0019f9..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Source implementation for column-wirse Cython sparse simulator. - -This package contains the source implementation for the column-wise Cython sparse stabilizer 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. diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim.pyx b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim.pyx deleted file mode 100644 index e2e3a4339..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim.pyx +++ /dev/null @@ -1,460 +0,0 @@ -# distutils: language = c++ -#cython: language_level=3, boundscheck=False, nonecheck=False, wraparound=False - -# 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. - -cimport cysparsesim_header as s -from cysparsesim_header cimport int_num - -from .src.logical_sign import find_logical_signs - -cdef dict gate_dict = { - 'I': State.I, - 'X': State.X, - 'Y': State.Y, - 'Z': State.Z, - - 'II': State.II, - 'CNOT': State.cnot, - 'CZ': State.cz, - 'SWAP': State.swap, - 'G': State.g2, - - 'H': State.hadamard, - 'H1': State.hadamard, - 'H2': State.H2, - 'H3': State.H3, - 'H4': State.H4, - 'H5': State.H5, - 'H6': State.H6, - - 'H+z+x': State.hadamard, - 'H-z-x': State.H2, - 'H+y-z': State.H3, - 'H-y-z': State.H4, - 'H-x+y': State.H5, - 'H-x-y': State.H6, - - 'F1': State.F1, - 'F2': State.F2, - 'F3': State.F3, - 'F4': State.F4, - - 'F1d': State.F1d, - 'F2d': State.F2d, - 'F3d': State.F3d, - 'F4d': State.F4d, - - 'R': State.R, - 'Rd': State.Rd, - 'Q': State.Q, - 'Qd': State.Qd, - 'S': State.S, - 'Sd': State.Sd, - - 'measure X': State.measureX, - 'measure Y': State.measureY, - 'measure Z': State.measure, - 'force output': State.force_output, - - 'init |0>': State.initzero, - 'init |1>': State.initone, - 'init |+>': State.initplus, - 'init |->': State.initminus, - 'init |+i>': State.initplusi, - 'init |-i>': State.initminusi, - - } - -cdef class State: - - cdef s.State* _c_state - - cdef public: - int_num num_qubits - dict gate_dict - - def __cinit__(self, int_num num_qubits): - self._c_state = new s.State(num_qubits) - - self.num_qubits = self._c_state.num_qubits - self.gate_dict = dict(gate_dict) - - cdef void hadamard(self, int_num qubit): - self._c_state.hadamard(qubit) - - cdef void H2(self, int_num qubit): - self._c_state.H2(qubit) - - cdef void H3(self, int_num qubit): - self._c_state.H3(qubit) - - cdef void H4(self, int_num qubit): - self._c_state.H4(qubit) - - cdef void H5(self, int_num qubit): - self._c_state.H5(qubit) - - cdef void H6(self, int_num qubit): - self._c_state.H6(qubit) - - cdef void F1(self, int_num qubit): - self._c_state.F1(qubit) - - cdef void F2(self, int_num qubit): - self._c_state.F2(qubit) - - cdef void F3(self, int_num qubit): - self._c_state.F3(qubit) - - cdef void F4(self, int_num qubit): - self._c_state.F4(qubit) - - cdef void F1d(self, int_num qubit): - self._c_state.F1d(qubit) - - cdef void F2d(self, int_num qubit): - self._c_state.F2d(qubit) - - cdef void F3d(self, int_num qubit): - self._c_state.F3d(qubit) - - cdef void F4d(self, int_num qubit): - self._c_state.F4d(qubit) - - cdef void I(self, int_num qubit): - pass - - cdef void X(self, int_num qubit): - self._c_state.bitflip(qubit) - - cdef void Y(self, int_num qubit): - self._c_state.Y(qubit) - - cdef void Z(self, int_num qubit): - self._c_state.phaseflip(qubit) - - cdef void S(self, int_num qubit): - self._c_state.phaserot(qubit) - - cdef void Sd(self, int_num qubit): - self._c_state.Sd(qubit) - - cdef void R(self, int_num qubit): - self._c_state.R(qubit) - - cdef void Rd(self, int_num qubit): - self._c_state.Rd(qubit) - - cdef void Q(self, int_num qubit): - self._c_state.Q(qubit) - - cdef void Qd(self, int_num qubit): - self._c_state.Qd(qubit) - - cdef void cnot(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.cnot(cqubit, tqubit) - - cdef void cz(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.hadamard(tqubit) - self._c_state.cnot(cqubit, tqubit) - self._c_state.hadamard(tqubit) - - cdef void g2(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.hadamard(cqubit) - self._c_state.cnot(tqubit, cqubit) - self._c_state.cnot(cqubit, tqubit) - self._c_state.hadamard(tqubit) - - cdef void swap(self, tuple qubits): - cdef int_num qubit1 = qubits[0] - cdef int_num qubit2 = qubits[1] - - self._c_state.swap(qubit1, qubit2) - - cdef void II(self, tuple qubits): - pass - - # cpdef unsigned int measure(self, const s.int_num qubit, - def measure(self, const int_num qubit, int forced_outcome = -1): - return self._c_state.measure(qubit, forced_outcome) - - def measureX(self, const int_num qubit, int forced_outcome = -1): - - cdef unsigned int result - - self._c_state.hadamard(qubit) - result = self._c_state.measure(qubit, forced_outcome) - self._c_state.hadamard(qubit) - return result - - def measureY(self, const int_num qubit, int forced_outcome = -1): - - cdef unsigned int result - - self._c_state.H5(qubit) - result = self._c_state.measure(qubit, forced_outcome) - self._c_state.H5(qubit) - return result - - cdef void initzero(self, const int_num qubit, int forced_outcome = -1): - cdef unsigned int result - result = self._c_state.measure(qubit, forced_outcome) - - if result: - self._c_state.bitflip(qubit) - - cdef void initone(self, const int_num qubit, int forced_outcome = -1): - cdef unsigned int result - result = self._c_state.measure(qubit, forced_outcome) - - if not result: - self._c_state.bitflip(qubit) - - cdef void initplus(self, const int_num qubit, int forced_outcome = -1): - self.initzero(qubit, forced_outcome) - self._c_state.hadamard(qubit) - - cdef void initminus(self, const int_num qubit, int forced_outcome = -1): - self.initone(qubit, forced_outcome) - self._c_state.hadamard(qubit) - - cdef void initplusi(self, const int_num qubit, int forced_outcome = -1): - self.initzero(qubit, forced_outcome) - self._c_state.H5(qubit) - - cdef void initminusi(self, const int_num qubit, int forced_outcome = -1): - self.initone(qubit, forced_outcome) - self._c_state.H5(qubit) - - def logical_sign(self, logical_op, delogical_op): - """ - - Args: - logical_op: - delogical_op: - - Returns: - - """ - return find_logical_signs(self, logical_op, delogical_op) - - def run_gate(self, symbol, locations, output=True, **gate_kwargs): - """ - - Args: - symbol: - locations: - output: - **gate_kwargs: - - Returns: - - """ - - if output: - output = {} - for location in locations: - results = self.gate_dict[symbol](self, location, **gate_kwargs) - if results: - output[location] = results - - return output - - else: - for location in locations: - self.gate_dict[symbol](self, location, **gate_kwargs) - - return {} - - @property - def signs_minus(self): - return self._c_state.signs_minus - - @property - def signs_i(self): - return self._c_state.signs_i - - @property - def stabs(self): - return self._c_state.stabs - - @property - def destabs(self): - return self._c_state.destabs - - def _pauli_sign(self, gen, i_gen): - - if i_gen in self.signs_minus: - if i_gen in self.signs_i: - sign = '-i' - else: - sign = ' -' - else: - - if i_gen in self.signs_i: - sign = ' i' - else: - sign = ' ' - - return sign - - def force_output(self, int_num qubit, output=-1): - """ - Outputs value. - - Used for error generators to generate outputs when replacing measurements. - - Args: - state: - qubit: - output: - - Returns: - - """ - return output - - def col_string(self, gen): - """ - Prints out the stabilizers for the column-wise sparse representation. - - :param num_qubits: - :return: - """ - - col_x = gen['col_x'] - col_z = gen['col_z'] - - result = [] - - num_qubits = self.num_qubits - - for i_gen in range(num_qubits): - - stab_letters = [] - - # ---- Signs ---- # - sign = self._pauli_sign(gen, i_gen) - stab_letters.append(sign) - - # ---- Paulis ---- # - for qubit in range(num_qubits): - - letter = 'U' - - if i_gen in col_x[qubit] and i_gen not in col_z[qubit]: - letter = 'X' - - elif i_gen not in col_x[qubit] and i_gen in col_z[qubit]: - letter = 'Z' - - elif i_gen in col_x[qubit] and i_gen in col_z[qubit]: - letter = 'W' - - elif i_gen not in col_x[qubit] and i_gen not in col_z[qubit]: - letter = 'I' - - stab_letters.append(letter) - - # print(''.join(stab_letters)) - result.append(''.join(stab_letters)) - - return result - - def print_tableau(self, gen, verbose=True): - """ - Prints out the stabilizers. - :return: - """ - - col_str = self.col_string(gen) - row_str = self.row_string(gen) - - if col_str != row_str: - print('col') - for line in col_str: - print(line) - - print('\nrow') - for line in row_str: - print(line) - - raise Exception('Something bad happened! String representation of the row-wise vs column-wile ' - 'spare stabilizers do not match!') - - if verbose: - for line in col_str: - print(line) - - return col_str - - def row_string(self, gen): - """ - Prints out the stabilizers for the row-wise sparse representation. - - :param num_qubits: - :return: - """ - - row_x = gen['row_x'] - row_z = gen['row_z'] - - result = [] - - num_qubits = self.num_qubits - - for i_gen in range(num_qubits): - - stab_letters = [] - - # ---- Signs ---- # - sign = self._pauli_sign(gen, i_gen) - stab_letters.append(sign) - - # ---- Paulis ---- # - for qubit in range(num_qubits): - - letter = 'U' - - if qubit in row_x[i_gen] and qubit not in row_z[i_gen]: - letter = 'X' - - elif qubit not in row_x[i_gen] and qubit in row_z[i_gen]: - letter = 'Z' - - elif qubit in row_x[i_gen] and qubit in row_z[i_gen]: - letter = 'W' - - elif qubit not in row_x[i_gen] and qubit not in row_z[i_gen]: - letter = 'I' - - stab_letters.append(letter) - - # print(''.join(stab_letters)) - result.append(''.join(stab_letters)) - - return result - - def __dealloc__(self): - del self._c_state diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim_header.pxd b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim_header.pxd deleted file mode 100644 index 7f79ee5c0..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/cysparsesim_header.pxd +++ /dev/null @@ -1,65 +0,0 @@ -# distutils: language = c++ -#cython: language_level=3, boundscheck=False, nonecheck=False, wraparound=False - -# 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. - -from libcpp.vector cimport vector -from libcpp.unordered_set cimport unordered_set - -cdef extern from "sparsesim.h": - - ctypedef unsigned long long int_num - ctypedef unordered_set[int_num] int_set - ctypedef vector[int_set] int_set_vec - - struct Generators: - int_set_vec col_x - int_set_vec col_z - int_set_vec row_x - int_set_vec row_z - - cdef cppclass State: - # State() except + - State(int_num) except + - int_num num_qubits - Generators stabs, destabs - int_set signs_minus, signs_i - - # Methods - void hadamard(const int_num &qubit) - void bitflip(const int_num &qubit) # X - void phaseflip(const int_num& qubit) # Z - void Y(const int_num& qubit) # Y - void phaserot(const int_num& qubit) # S - void Sd(const int_num& qubit) # S - void R(const int_num& qubit) # R - void Rd(const int_num& qubit) # Rd - void Q(const int_num& qubit) # Q - void Qd(const int_num& qubit) # Qd - void H2(const int_num &qubit) # H2 - void H3(const int_num &qubit) # H3 - void H4(const int_num &qubit) # H4 - void H5(const int_num &qubit) # H5 - void H6(const int_num &qubit) # H6 - void F1(const int_num &qubit) # F1 - void F2(const int_num &qubit) # F2 - void F3(const int_num &qubit) # F3 - void F4(const int_num &qubit) # F4 - void F1d(const int_num &qubit) # F1d - void F2d(const int_num &qubit) # F2d - void F3d(const int_num &qubit) # F3d - void F4d(const int_num &qubit) # F4d - void cnot(const int_num& tqubit, const int_num& cqubit) - void swap(const int_num& qubit1, const int_num& qubit2) - unsigned int measure(const int_num& qubit, int force) diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/logical_sign.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/logical_sign.py deleted file mode 100644 index 28a9c9fd6..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/logical_sign.py +++ /dev/null @@ -1,177 +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. - -"""Functions for logical sign computation. - -find_logical_signs -logical_flip -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.circuits import QuantumCircuit - from pecos.protocols import SimulatorProtocol - - -def find_logical_signs( - state: SimulatorProtocol, - logical_circuit: QuantumCircuit, - delogical_circuit: QuantumCircuit, -) -> int: - """Find the sign of the logical operator. - - Args: - ---- - state: The quantum state instance - logical_circuit: The logical circuit to find the sign of - delogical_circuit: Optional delogical circuit to check anti-commutation with - """ - if len(logical_circuit) != 1 or len(delogical_circuit) != 1: - msg = "Logical operators are expected to only have one tick." - raise Exception(msg) - - stabs = state.stabs - destabs = state.destabs - signs_minus = state.signs_minus - signs_i = state.signs_i - - logical_xs = set() - logical_zs = set() - - delogical_xs = set() - delogical_zs = set() - - for symbol, gate_locations in logical_circuit.items(params=False): - if symbol == "X": - logical_xs.update(gate_locations) - elif symbol == "Z": - logical_zs.update(gate_locations) - elif symbol == "Y": - logical_xs.update(gate_locations) - logical_zs.update(gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - for symbol, gate_locations in delogical_circuit.items(params=False): - if symbol == "X": - delogical_xs.update(gate_locations) - elif symbol == "Z": - delogical_zs.update(gate_locations) - elif symbol == "Y": - delogical_xs.update(gate_locations) - delogical_zs.update(gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - # Make sure the logical and delogical anti-commute - - anticom_x = len(logical_xs & delogical_zs) % 2 # Number of common elements modulo 2 - anticom_z = len(logical_zs & delogical_xs) % 2 # Number of common elements modulo 2 - - if not ((anticom_x + anticom_z) % 2): - print(f"logical Xs: {logical_xs} logical Zs: {logical_zs}") - print(f"delogical Xs: {delogical_xs} delogical Zs: {delogical_zs}") - msg = "Logical and delogical operators supplied do not anti-commute!" - raise Exception(msg) - - # We want the supplied logical operator to be in the stabilizer group and - # the supplied delogical to not be in the stabilizers (we want it to end up being the logical op's destabilizer) - - # The following two function calls are wasteful because we will need some of what they discover... such as all the - # stabilizers that have destabilizers that anti-commute with the logical operator... - # But it is assumed that the user is not calling this function that often... so we can be wasteful... - - # Check logical is a stabilizer (we want to remove it from the stabilizers) - - # Find the anti-commuting destabilizers => stabilizers to give the logical operator - # -------------------------- - build_stabs = set() - - for q in logical_xs: # For qubits that have Xs in for the logical operator... - build_stabs ^= destabs["col_z"][ - q - ] # Add in stabilizers that anti-commute for the logical operator's Xs - - for q in logical_zs: - build_stabs ^= destabs["col_x"][ - q - ] # Add in stabilizers that anti-commute for the logical operator's Zs - - # If a stabilizer anticommutes an even number of times for the X and/or Z Paulis... it will not appear due to ^= - - # Confirm that the stabilizers chosen give the logical operator. If not... return with a failure = 1 - # -------------------------- - test_x = set() - test_z = set() - - for stab in build_stabs: - test_x ^= stabs["row_x"][stab] - test_z ^= stabs["row_z"][stab] - - # Compare with logical operator - test_x ^= logical_xs - test_z ^= logical_zs - - if len(test_x) != 0 or len(test_z) != 0: - # for stab in build_stabs: - - print(f"Logical op: xs - {logical_xs} and zs - {logical_zs}") - msg = f"Failure due to not finding logical op! x... {test_x ^ logical_xs!s} z... {test_z ^ logical_zs!s}" - raise Exception(msg) - - # Get the sign of the logical operator - # -------------------------- - - # First, the minus sign - logical_minus = len(build_stabs & signs_minus) - - # Second, the number of imaginary numbers - logical_i = len(build_stabs & signs_i) - - # Translate the Ws to Ys... W = -i(iW) = -iY => For each Y add another -1 and +i. - logical_ws = logical_xs & logical_zs - num_ys = len(logical_ws) - - logical_minus += num_ys - logical_i += num_ys - - # Do (-1)^even = 1 -> 0, (-1)^odd = -1 -> 1 - logical_minus %= 2 - - # Reinterpret number of is - logical_i %= 4 - - # num_is %4 = 0 => +1 => logical_i = 0, logical_minus += 0 - # num_is %4 = 1 => +i => logical_i = 1, logical_minus += 0 - - if logical_i == 2: # num_is %4 = 2 => -1 => logical_i = 0, logical_minus += 1 - logical_i = 0 - logical_minus += 1 - elif logical_i == 3: # num_is %4 = 3 => -i => logical_i = 1, logical_minus += 1 - logical_i = 1 - logical_minus += 1 - - if logical_i != 0: - msg = "Logical operator has an imaginary sign... Not allowed if logical state is stabilized by logical op!" - raise Exception(msg) - - return logical_minus diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim - Copy.cpp b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim - Copy.cpp deleted file mode 100644 index 818f7a12a..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim - Copy.cpp +++ /dev/null @@ -1,1489 +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. - -#include "sparsesim.h" - -int_set_vec build_empty(int_num size) { - int_set_vec matrix(size); - - return matrix; -} - -int_set_vec build_ones(int_num size){ - - int_set_vec matrix = build_empty(size); - - for( int_num i = 0; i < size; i++) { - matrix[i].insert(i); - } - - return matrix; -} - -State::State(const int_num& num_qubits) - :num_qubits(num_qubits) -{ - //num_qubits = num_qubits; - clear(); -} - -void State::clear() { // Allows state to reinitialize. - - // Initialize stabilizers. - stabs.col_x = build_empty(num_qubits); - stabs.col_z = build_ones(num_qubits); - - // Initialize destabilizers. - destabs.col_x = build_ones(num_qubits); - destabs.col_z = build_empty(num_qubits); - - //Inilialize signs columns - signs_minus.clear(); - signs_i.clear(); -} - -void State::hadamard(const int_num& qubit) { - /* - X -> Z - Z -> X - W -> -W - Y -> -Y - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - - -} - -void hadamard_gen_mod(Generators& gen, const int_num& qubit) { - // X <-> Z - - // Swap for columns - gen.col_x[qubit].swap(gen.col_z[qubit]); - -} - -void State::bitflip(const int_num& qubit) { - - // Z -> -1 - for (const int_num& elem: stabs.col_z[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - - } - - } // end for -} - -void State::phaseflip(const int_num& qubit) { - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - - } - - } // end for -} - -void State::Y(const int_num& qubit) { - - // Z -> -1 - for (const int_num& elem: stabs.col_z[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - } - - } // end for - - // X -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - } - - } // end for -} - -void State::phaserot(const int_num& qubit) { - /* - X -> iW = Y - Z -> Z - W -> iX - Y -> -X - */ - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void phaserot_gen_mod(Generators& gen, const int_num& qubit) { - /* - X -> iW - Z -> Z - W -> iX - */ - // X -> Z - - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - - // Update column - if ( gen.col_z[qubit].count(x_gen_id)) { - gen.col_z[qubit].erase(x_gen_id); - } else { - gen.col_z[qubit].insert(x_gen_id); - } - } - -} - -void State::cnot(const int_num& tqubit, const int_num& cqubit) { - cnot_gen_mod(stabs, tqubit, cqubit); - cnot_gen_mod(destabs, tqubit, cqubit); -} - -void cnot_gen_mod(Generators& gen, const int_num& tqubit, - const int_num& cqubit) { - - - // Xt ^= Xc X propagates from control to target - for (const int_num& x_gen_id: gen.col_x[tqubit]) { - - // column update - if (gen.col_x[cqubit].count(x_gen_id)) { - gen.col_x[cqubit].erase(x_gen_id); - } else { - gen.col_x[cqubit].insert(x_gen_id); - } - } - - // Zt ^= Zc Z propagates from target to control - for (const int_num& z_gen_id: gen.col_z[cqubit]) { - - // column update - if (gen.col_z[tqubit].count(z_gen_id)) { - gen.col_z[tqubit].erase(z_gen_id); - } else { - gen.col_z[tqubit].insert(z_gen_id); - } - } - -} - - - -void State::swap(const int_num& qubit1, const int_num& qubit2) { - swap_gen_mod(stabs, qubit1, qubit2); - swap_gen_mod(destabs, qubit1, qubit2); -} - -void swap_gen_mod(Generators& gen, const int_num& qubit1, - const int_num& qubit2) { - - // Swap for columns - gen.col_x[qubit1].swap(gen.col_x[qubit2]); - gen.col_z[qubit1].swap(gen.col_z[qubit2]); - -} - -unsigned int State::measure(const int_num& qubit, int force=-1) { - - if (stabs.col_x[qubit].size() == 0) { // There are no anticommuting stabilizers - return deterministic_measure(qubit); - } else { - return nondeterministic_measure(qubit, force); - // return 1; - } - -} - - -unsigned int State::deterministic_measure(const int_num& qubit) { - - int_set cumulative_x; - int_num num_minuses = 0; - int_num num_is = 0; - bool has_x, has_minus; - int_set mul_stabs = destabs.col_x[qubit]; - - // When no stabilizers anti-commute with the measurement, this means that - // we are measuring a stabilizer of the state. The task then is to - // determine the sign of the measured stabilizer. The generators that have - // destabilizers that anticommute with the measurement multiply to give - // the measured stabilzer. Therefore, we loop through these generators. - - - has_minus = false; - for (int_num q = 0; q < num_qubits; q++){ - has_x = false; - for (const int_num& stab_id: mul_stabs) { - - if (has_x){ - - if (stabs.col_z[q].count(stab_id)) { - has_minus = !has_minus; - } - - } - - if (stabs.col_x[q].count(stab_id)) { - has_x = !has_x; - } - - } - } - - if (has_minus) { - num_minuses = 1; - } - - - - // Count the is and -1s out front of the stabilizers being multiplied - // together. - for (const int_num& gen_id: mul_stabs) { - - // Determine the overall minus sign of the generators. - if (signs_minus.count(gen_id)) { - num_minuses += 1; - } - - // Determine the sign contribution due to is. - if (signs_i.count(gen_id)) { - num_is += 1; - } - } - - - - if (num_is % 4) { // Can only be 0 or 2 (otherwise => an overall i or -i) - num_minuses += 1; - } - - return num_minuses % 2; - -} - -unsigned int State::nondeterministic_measure(const int_num& qubit, - int force=-1) { - // Here at least one stabilizer anticommutes with the measured stabilizer. - // Therefore, we will have to update the stabilizers and destabilizers. - - int meas_outcome; - int_num num_minuses; - int_set anticom_stabs, anticom_destabs; - - int_set removed_row_x, removed_row_z; - - anticom_stabs = int_set(stabs.col_x[qubit]); - anticom_destabs = int_set(destabs.col_x[qubit]); - - // Choose an anti-commuting stabilizer to remove. - // int_num removed_id = *(stabs.col_x[qubit].begin()); - - // Choosing the smallest - int_num removed_id = *(stabs.col_x[qubit].begin()); - - /* - int_num smallest_wt = 2*num_qubits + 2; - int_num temp_wt; - - for (const int_num& stab_id: stabs.col_x[qubit]) { - - temp_wt = 0; - - for (int_num q = 0; q < num_qubits; q++){ - if (stabs.col_z[q].count(stab_id)) { - temp_wt += 1; - } - if (stabs.col_x[q].count(stab_id)) { - temp_wt += 1; - } - } - - if (temp_wt < smallest_wt) { - removed_id = stab_id; - smallest_wt = temp_wt; - } - - }*/ - - // End choosing the smallest - - anticom_stabs.erase(removed_id); - anticom_destabs.erase(removed_id); - - // Create removed row_x and row_z - for (int_num q = 0; q < num_qubits; q++){ - - if (stabs.col_z[q].count(removed_id)) { // loop over removed_row_z - removed_row_z.insert(q); - - ////// - stabs.col_z[q].erase(removed_id); - destabs.col_z[q].insert(removed_id); - - for (const int_num& gen_id: anticom_destabs) { - if (destabs.col_z[q].count(gen_id)) { - destabs.col_z[q].erase(gen_id); - } else { - destabs.col_z[q].insert(gen_id); - } - - } - ///// - - } - if (stabs.col_x[q].count(removed_id)) { // loop over removed_row_x - removed_row_x.insert(q); - - ///// - stabs.col_x[q].erase(removed_id); - destabs.col_x[q].insert(removed_id); - - for (const int_num& gen_id: anticom_destabs) { - if (destabs.col_x[q].count(gen_id)) { - destabs.col_x[q].erase(gen_id); - } else { - destabs.col_x[q].insert(gen_id); - } - - } - ///// - } - - - if (destabs.col_z[q].count(removed_id)) { // loop over removed_row_z_destab - destabs.col_z[q].erase(removed_id); - } - - if (destabs.col_x[q].count(removed_id)) { // loop over removed_row_x_destab - destabs.col_x[q].erase(removed_id); - } - - - } - - - if (signs_minus.count(removed_id)) { - - // Update all the anti-commuting stabs signs with that of the removed stab. - for (const int_num& gen_id: anticom_stabs) { - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - } - - } - - if (signs_i.count(removed_id)) { - - signs_i.erase(removed_id); // Throw away the sign for the removed stab. - - for (const int_num& gen_id: anticom_stabs) { - // i*i = -1 - if(signs_i.count(gen_id)) { - signs_i.erase(gen_id); - - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - - } else { - - signs_i.insert(gen_id); - } - } - } - - - // for (const int_num& gen_id: stabs.col_x[qubit]) { - for (const int_num& gen_id: anticom_stabs) { - - - num_minuses = 0; - // Correct signs due to ZX -> -XZ - // Count the number of minuses due to this - - - for (const int_num& q: removed_row_z) { - - if (stabs.col_x[q].count(gen_id)) { - num_minuses += 1; - } - - if (stabs.col_z[q].count(gen_id)) { - stabs.col_z[q].erase(gen_id); - } else { - stabs.col_z[q].insert(gen_id); - } - - } - - if (num_minuses % 2) { - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - } - - for (const int_num& q: removed_row_x) { - if (stabs.col_x[q].count(gen_id)) { - stabs.col_x[q].erase(gen_id); - } else { - stabs.col_x[q].insert(gen_id); - } - - } - - } // end big for loop - - - // ------------------------------------------------------------------------ - // Update destabilziers - - - - // Add in/Multiply by the new destabilizer - // This makes all destabilizers commute with the new stabilizer. - /* - for (const int_num& q: removed_row_x) { - - stabs.col_x[q].erase(removed_id); - destabs.col_x[q].insert(removed_id); - - for (const int_num& gen_id: anticom_destabs) { - if (destabs.col_x[q].count(gen_id)) { - destabs.col_x[q].erase(gen_id); - } else { - destabs.col_x[q].insert(gen_id); - } - - } - }*/ - - - /* - for (const int_num& q: removed_row_z) { - - stabs.col_z[q].erase(removed_id); - destabs.col_z[q].insert(removed_id); - - for (const int_num& gen_id: anticom_destabs) { - if (destabs.col_z[q].count(gen_id)) { - destabs.col_z[q].erase(gen_id); - } else { - destabs.col_z[q].insert(gen_id); - } - - } - }*/ - - - // Remove replaced stabilizer with the measured stabilizer - stabs.col_z[qubit].insert(removed_id); - - meas_outcome = force; - - if (meas_outcome) { - signs_minus.insert(removed_id); - } else { - signs_minus.erase(removed_id); - } - - return meas_outcome; - -} - -// ---------------------------------------------------------------------------- - -void State::R(const int_num& qubit) { - /* - Applies a R rotation to stabilizers/destabilizers - - R = \sqrt{XZ} = SQS^{\dagger} - - R = R - XZ = R^2 - R^{\dagger} = R^3 - I = R^4 - - X -> -Z - Z -> X - W -> W - Y -> Y - */ - - // Change the sign appropriately - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Swap X <-> Z - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - - - -void State::Rd(const int_num& qubit) { - /* - Applies a R rotation to stabilizers/destabilizers - - R^{\dagger} = \sqrt{XZ} = SQ^{\dagger}S^{\dagger} - - R = R - XZ = R^2 - R^{\dagger} = R^3 - I = R^4 - - X -> Z - Z -> -X - W -> W - Y -> Y - */ - - // Change the sign appropriately - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Swap X <-> Z - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - -void State::Sd(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> Z - W -> -iX - Y -> X - */ - - - - for (const int_num& i: stabs.col_x[qubit]) { - - - // X -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - - -void State::Q(const int_num& qubit) { - /* - X -> X - Z -> -iW = -Y - W -> -iZ - Y -> Z - */ - - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void Q_gen_mod(Generators& gen, const int_num& qubit) { - /* - X -> X - Z -> Y - */ - - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - - if (gen.col_x[qubit].count(z_gen_id)) { - gen.col_x[qubit].erase(z_gen_id); - } else { - gen.col_x[qubit].insert(z_gen_id); - } - - } - -} - -void State::Qd(const int_num& qubit) { - /* - X -> X - Z -> iW = Y - W -> iZ - Y -> -Z - */ - - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void State::H2(const int_num& qubit) { - /* - X -> -Z - Z -> -X - W -> -W - Y -> -Y - */ - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - -void State::H3(const int_num& qubit) { - /* - X -> iW = Y - Z -> -Z - W -> -iX - Y -> X - */ - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - } - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void State::H4(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> -Z - W -> iX - Y -> -X - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void State::H5(const int_num& qubit) { - /* - X -> -X - Z -> iW = Y - W -> -iZ - Y -> Z - */ - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void State::H6(const int_num& qubit) { - /* - X -> -X - Z -> -iW = -Y - W -> iZ - Y -> -Z - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - - -void State::F1(const int_num& qubit) { - /* - X -> Z - Z -> X - W -> -W - Y -> -Y - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void F1_gen_mod(Generators& gen, const int_num& qubit) { - // X -> W - // Z -> X - - - // Swap for columns - gen.col_x[qubit].swap(gen.col_z[qubit]); - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - if (gen.col_x[qubit].count(z_gen_id)) { - gen.col_x[qubit].erase(z_gen_id); - } else { - gen.col_x[qubit].insert(z_gen_id); - } - } - -} - - -void State::F2(const int_num& qubit) { - /* - X -> -Z - Z -> iW = Y - W -> iX - Y -> -X - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void F2_gen_mod(Generators& gen, const int_num& qubit) { - // X -> W - // Z -> X - - // X -> Z - // Z -> W - - // Swap for columns - gen.col_z[qubit].swap(gen.col_x[qubit]); - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - if (gen.col_z[qubit].count(x_gen_id)) { - gen.col_z[qubit].erase(x_gen_id); - } else { - gen.col_z[qubit].insert(x_gen_id); - } - } - -} - -void State::F3(const int_num& qubit) { - /* - X -> iW = Y - Z -> -X - W -> iZ - Y -> -Z - */ - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void State::F4(const int_num& qubit) { - /* - X -> Z - Z -> -iW = -Y - W -> iX - Y -> -X - */ - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F1d(const int_num& qubit) { - /* - X -> Z - Z -> iW = Y - W -> -iX - Y -> X - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F2d(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> -X - W -> -iZ - Y -> Z - */ - - - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void State::F3d(const int_num& qubit) { - /* - X -> -Z - Z -> -iW = -Y - W -> -iX - Y -> X - */ - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F4d(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> X - W -> iZ - Y -> -Z - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.cpp b/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.cpp deleted file mode 100644 index e021ab387..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_col/src/sparsesim.cpp +++ /dev/null @@ -1,1453 +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. - -#include "sparsesim.h" - -int_set_vec build_empty(int_num size) { - int_set_vec matrix(size); - - return matrix; -} - -int_set_vec build_ones(int_num size){ - - int_set_vec matrix = build_empty(size); - - for( int_num i = 0; i < size; i++) { - matrix[i].insert(i); - } - - return matrix; -} - -State::State(const int_num& num_qubits) - :num_qubits(num_qubits) -{ - //num_qubits = num_qubits; - clear(); -} - -void State::clear() { // Allows state to reinitialize. - - // Initialize stabilizers. - stabs.col_x = build_empty(num_qubits); - stabs.col_z = build_ones(num_qubits); - - // Initialize destabilizers. - destabs.col_x = build_ones(num_qubits); - destabs.col_z = build_empty(num_qubits); - - //Inilialize signs columns - signs_minus.clear(); - signs_i.clear(); -} - -void State::hadamard(const int_num& qubit) { - /* - X -> Z - Z -> X - W -> -W - Y -> -Y - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - - -} - -void hadamard_gen_mod(Generators& gen, const int_num& qubit) { - // X <-> Z - - // Swap for columns - gen.col_x[qubit].swap(gen.col_z[qubit]); - -} - -void State::bitflip(const int_num& qubit) { - - // Z -> -1 - for (const int_num& elem: stabs.col_z[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - - } - - } // end for -} - -void State::phaseflip(const int_num& qubit) { - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - - } - - } // end for -} - -void State::Y(const int_num& qubit) { - - // Z -> -1 - for (const int_num& elem: stabs.col_z[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - } - - } // end for - - // X -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - } - - } // end for -} - -void State::phaserot(const int_num& qubit) { - /* - X -> iW = Y - Z -> Z - W -> iX - Y -> -X - */ - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void phaserot_gen_mod(Generators& gen, const int_num& qubit) { - /* - X -> iW - Z -> Z - W -> iX - */ - // X -> Z - - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - - // Update column - if ( gen.col_z[qubit].count(x_gen_id)) { - gen.col_z[qubit].erase(x_gen_id); - } else { - gen.col_z[qubit].insert(x_gen_id); - } - } - -} - -void State::cnot(const int_num& tqubit, const int_num& cqubit) { - cnot_gen_mod(stabs, tqubit, cqubit); - cnot_gen_mod(destabs, tqubit, cqubit); -} - -void cnot_gen_mod(Generators& gen, const int_num& tqubit, - const int_num& cqubit) { - - - // Xt ^= Xc X propagates from control to target - for (const int_num& x_gen_id: gen.col_x[tqubit]) { - - // column update - if (gen.col_x[cqubit].count(x_gen_id)) { - gen.col_x[cqubit].erase(x_gen_id); - } else { - gen.col_x[cqubit].insert(x_gen_id); - } - } - - // Zt ^= Zc Z propagates from target to control - for (const int_num& z_gen_id: gen.col_z[cqubit]) { - - // column update - if (gen.col_z[tqubit].count(z_gen_id)) { - gen.col_z[tqubit].erase(z_gen_id); - } else { - gen.col_z[tqubit].insert(z_gen_id); - } - } - -} - - - -void State::swap(const int_num& qubit1, const int_num& qubit2) { - swap_gen_mod(stabs, qubit1, qubit2); - swap_gen_mod(destabs, qubit1, qubit2); -} - -void swap_gen_mod(Generators& gen, const int_num& qubit1, - const int_num& qubit2) { - - // Swap for columns - gen.col_x[qubit1].swap(gen.col_x[qubit2]); - gen.col_z[qubit1].swap(gen.col_z[qubit2]); - -} - -unsigned int State::measure(const int_num& qubit, int force=-1) { - - if (stabs.col_x[qubit].size() == 0) { // There are no anticommuting stabilizers - return deterministic_measure(qubit); - } else { - return nondeterministic_measure(qubit, force); - // return 1; - } - -} - - -unsigned int State::deterministic_measure(const int_num& qubit) { - - int_set cumulative_x; - int_num num_minuses = 0; - int_num num_is = 0; - bool has_x, has_minus; - int_set mul_stabs = destabs.col_x[qubit]; - - // When no stabilizers anti-commute with the measurement, this means that - // we are measuring a stabilizer of the state. The task then is to - // determine the sign of the measured stabilizer. The generators that have - // destabilizers that anticommute with the measurement multiply to give - // the measured stabilzer. Therefore, we loop through these generators. - - - has_minus = false; - for (int_num q = 0; q < num_qubits; q++){ - has_x = false; - for (const int_num& stab_id: mul_stabs) { - - if (has_x){ - - if (stabs.col_z[q].count(stab_id)) { - has_minus = !has_minus; - } - - } - - if (stabs.col_x[q].count(stab_id)) { - has_x = !has_x; - } - - } - } - - if (has_minus) { - num_minuses = 1; - } - - - - // Count the is and -1s out front of the stabilizers being multiplied - // together. - for (const int_num& gen_id: mul_stabs) { - - // Determine the overall minus sign of the generators. - if (signs_minus.count(gen_id)) { - num_minuses += 1; - } - - // Determine the sign contribution due to is. - if (signs_i.count(gen_id)) { - num_is += 1; - } - } - - - - if (num_is % 4) { // Can only be 0 or 2 (otherwise => an overall i or -i) - num_minuses += 1; - } - - return num_minuses % 2; - -} - -unsigned int State::nondeterministic_measure(const int_num& qubit, - int force=-1) { - // Here at least one stabilizer anticommutes with the measured stabilizer. - // Therefore, we will have to update the stabilizers and destabilizers. - - int meas_outcome; - int_num num_minuses; - - int_set removed_row_x, removed_row_z; - // int_set destab_removed_row_x, destab_removed_row_z; - int_set anticom_stabs, anticom_destabs; - - anticom_stabs = int_set(stabs.col_x[qubit]); - anticom_destabs = int_set(destabs.col_x[qubit]); - - // Choose an anti-commuting stabilizer to remove. - // int_num removed_id = *(stabs.col_x[qubit].begin()); - - int_num removed_id = *(stabs.col_x[qubit].begin()); - - /*int_num smallest_wt = 2*num_qubits + 2; - int_num temp_wt; - - for (const int_num& stab_id: stabs.col_x[qubit]) { - - temp_wt = 0; - - for (int_num q = 0; q < num_qubits; q++){ - if (stabs.col_z[q].count(stab_id)) { - temp_wt += 1; - } - if (stabs.col_x[q].count(stab_id)) { - temp_wt += 1; - } - } - - if (temp_wt < smallest_wt) { - removed_id = stab_id; - smallest_wt = temp_wt; - } - - }*/ - - anticom_stabs.erase(removed_id); - anticom_destabs.erase(removed_id); - - for (int_num q = 0; q < num_qubits; q++){ - if (stabs.col_z[q].count(removed_id)) { - removed_row_z.insert(q); - } - if (stabs.col_x[q].count(removed_id)) { - removed_row_x.insert(q); - } - if (destabs.col_z[q].count(removed_id)) { - destabs.col_z[q].erase(removed_id); - } - if (destabs.col_x[q].count(removed_id)) { - destabs.col_x[q].erase(removed_id); - } - - } - - // const int_set removed_row_x = int_set(stabs.row_x[removed_id]); - // const int_set removed_row_z = int_set(stabs.row_z[removed_id]); - - - if (signs_minus.count(removed_id)) { - - // Update all the anti-commuting stabs signs with that of the removed stab. - for (const int_num& gen_id: anticom_stabs) { - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - } - - } - - if (signs_i.count(removed_id)) { - - signs_i.erase(removed_id); // Throw away the sign for the removed stab. - - for (const int_num& gen_id: anticom_stabs) { - // i*i = -1 - if(signs_i.count(gen_id)) { - signs_i.erase(gen_id); - - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - - } else { - - signs_i.insert(gen_id); - } - } - } - - - - // for (const int_num& gen_id: stabs.col_x[qubit]) { - for (const int_num& gen_id: anticom_stabs) { - - - num_minuses = 0; - // Correct signs due to ZX -> -XZ - // Count the number of minuses due to this - - - for (const int_num& q: removed_row_z) { - - if (stabs.col_x[q].count(gen_id)) { - num_minuses += 1; - } - - if (stabs.col_z[q].count(gen_id)) { - stabs.col_z[q].erase(gen_id); - } else { - stabs.col_z[q].insert(gen_id); - } - - } - - if (num_minuses % 2) { - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - } - - for (const int_num& q: removed_row_x) { - if (stabs.col_x[q].count(gen_id)) { - stabs.col_x[q].erase(gen_id); - } else { - stabs.col_x[q].insert(gen_id); - } - - } - - } // end big for loop - - - // ------------------------------------------------------------------------ - // Update destabilziers - - - - // Add in/Multiply by the new destabilizer - // This makes all destabilizers commute with the new stabilizer. - for (const int_num& q: removed_row_x) { - - stabs.col_x[q].erase(removed_id); - destabs.col_x[q].insert(removed_id); - - for (const int_num& row: anticom_destabs) { - if (destabs.col_x[q].count(row)) { - destabs.col_x[q].erase(row); - } else { - destabs.col_x[q].insert(row); - } - - } - } - - - for (const int_num& q: removed_row_z) { - - stabs.col_z[q].erase(removed_id); - destabs.col_z[q].insert(removed_id); - - for (const int_num& row: anticom_destabs) { - if (destabs.col_z[q].count(row)) { - destabs.col_z[q].erase(row); - } else { - destabs.col_z[q].insert(row); - } - - } - } - - - // Remove replaced stabilizer with the measured stabilizer - stabs.col_z[qubit].insert(removed_id); - - meas_outcome = force; - - if (meas_outcome) { - signs_minus.insert(removed_id); - } else { - signs_minus.erase(removed_id); - } - - return meas_outcome; - -} - -// ---------------------------------------------------------------------------- - -void State::R(const int_num& qubit) { - /* - Applies a R rotation to stabilizers/destabilizers - - R = \sqrt{XZ} = SQS^{\dagger} - - R = R - XZ = R^2 - R^{\dagger} = R^3 - I = R^4 - - X -> -Z - Z -> X - W -> W - Y -> Y - */ - - // Change the sign appropriately - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Swap X <-> Z - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - - - -void State::Rd(const int_num& qubit) { - /* - Applies a R rotation to stabilizers/destabilizers - - R^{\dagger} = \sqrt{XZ} = SQ^{\dagger}S^{\dagger} - - R = R - XZ = R^2 - R^{\dagger} = R^3 - I = R^4 - - X -> Z - Z -> -X - W -> W - Y -> Y - */ - - // Change the sign appropriately - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Swap X <-> Z - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - -void State::Sd(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> Z - W -> -iX - Y -> X - */ - - - - for (const int_num& i: stabs.col_x[qubit]) { - - - // X -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - - -void State::Q(const int_num& qubit) { - /* - X -> X - Z -> -iW = -Y - W -> -iZ - Y -> Z - */ - - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void Q_gen_mod(Generators& gen, const int_num& qubit) { - /* - X -> X - Z -> Y - */ - - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - - if (gen.col_x[qubit].count(z_gen_id)) { - gen.col_x[qubit].erase(z_gen_id); - } else { - gen.col_x[qubit].insert(z_gen_id); - } - - } - -} - -void State::Qd(const int_num& qubit) { - /* - X -> X - Z -> iW = Y - W -> iZ - Y -> -Z - */ - - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void State::H2(const int_num& qubit) { - /* - X -> -Z - Z -> -X - W -> -W - Y -> -Y - */ - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - -void State::H3(const int_num& qubit) { - /* - X -> iW = Y - Z -> -Z - W -> -iX - Y -> X - */ - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - } - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void State::H4(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> -Z - W -> iX - Y -> -X - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void State::H5(const int_num& qubit) { - /* - X -> -X - Z -> iW = Y - W -> -iZ - Y -> Z - */ - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void State::H6(const int_num& qubit) { - /* - X -> -X - Z -> -iW = -Y - W -> iZ - Y -> -Z - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - - -void State::F1(const int_num& qubit) { - /* - X -> Z - Z -> X - W -> -W - Y -> -Y - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void F1_gen_mod(Generators& gen, const int_num& qubit) { - // X -> W - // Z -> X - - - // Swap for columns - gen.col_x[qubit].swap(gen.col_z[qubit]); - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - if (gen.col_x[qubit].count(z_gen_id)) { - gen.col_x[qubit].erase(z_gen_id); - } else { - gen.col_x[qubit].insert(z_gen_id); - } - } - -} - - -void State::F2(const int_num& qubit) { - /* - X -> -Z - Z -> iW = Y - W -> iX - Y -> -X - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void F2_gen_mod(Generators& gen, const int_num& qubit) { - // X -> W - // Z -> X - - // X -> Z - // Z -> W - - // Swap for columns - gen.col_z[qubit].swap(gen.col_x[qubit]); - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - if (gen.col_z[qubit].count(x_gen_id)) { - gen.col_z[qubit].erase(x_gen_id); - } else { - gen.col_z[qubit].insert(x_gen_id); - } - } - -} - -void State::F3(const int_num& qubit) { - /* - X -> iW = Y - Z -> -X - W -> iZ - Y -> -Z - */ - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void State::F4(const int_num& qubit) { - /* - X -> Z - Z -> -iW = -Y - W -> iX - Y -> -X - */ - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F1d(const int_num& qubit) { - /* - X -> Z - Z -> iW = Y - W -> -iX - Y -> X - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F2d(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> -X - W -> -iZ - Y -> Z - */ - - - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void State::F3d(const int_num& qubit) { - /* - X -> -Z - Z -> -iW = -Y - W -> -iX - Y -> X - */ - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F4d(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> X - W -> iZ - Y -> -Z - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/.gitignore b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/.gitignore deleted file mode 100644 index 8213b52e5..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build -*.pyd -cysparsesim.cpp diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/__init__.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/__init__.py deleted file mode 100644 index 306bdf6f9..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Row-wise Cython sparse stabilizer simulator. - -This package provides a row-wise implementation of the Cython sparse stabilizer simulator. -""" - -# 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. - -from pecos.simulators.cysparsesim_row.cysparsesim import State diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/setup.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/setup.py deleted file mode 100644 index cb665df4c..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/setup.py +++ /dev/null @@ -1,63 +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. - -"""Setup. - -===== - -The setup file for the Cython wrapped C++ version of SparseSim. - -Notes: ------ - Use the following to compile from command line: - python setup.py build_ext --inplace - -""" - -import contextlib -import shutil -from distutils.core import setup -from distutils.extension import Extension -from pathlib import Path - -from Cython.Build import cythonize - -# Delete previous build folder -current_location = Path.parent(Path.resolve(__file__)) -with contextlib.suppress(FileNotFoundError): - shutil.rmtree(Path(current_location / "build")) - -compiler_flags = ["-std=c++11", "-Wall", "-fPIC", "-O2", "-O3", "-c"] - -ext_modules = [ - Extension( - "cysparsesim", - sources=["src/cysparsesim.pyx", "src/sparsesim.cpp"], - language="c++", - extra_compile_args=compiler_flags, - include_dirs=["./src"], - # include_dirs=[np.get_include()], - ), -] - - -for e in ext_modules: - e.cython_directives = {"boundscheck": False, "wraparound": False} - - -setup( - name="state", - ext_modules=cythonize(ext_modules, build_dir="build"), - script_args=["build_ext"], - options={"build_ext": {"inplace": True}}, -) diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/__init__.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/__init__.py deleted file mode 100644 index 66c997a37..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Source implementation for row-wise Cython sparse simulator. - -This package contains the source implementation for the row-wise Cython sparse stabilizer 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. diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim.pyx b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim.pyx deleted file mode 100644 index 36fc64e15..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim.pyx +++ /dev/null @@ -1,461 +0,0 @@ -# distutils: language = c++ -#cython: language_level=3, boundscheck=False, nonecheck=False, wraparound=False - - -# 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. - -cimport cysparsesim_header as s -from cysparsesim_header cimport int_num - -from .src.logical_sign import find_logical_signs - -cdef dict gate_dict = { - 'I': State.I, - 'X': State.X, - 'Y': State.Y, - 'Z': State.Z, - - 'II': State.II, - 'CNOT': State.cnot, - 'CZ': State.cz, - 'SWAP': State.swap, - 'G': State.g2, - - 'H': State.hadamard, - 'H1': State.hadamard, - 'H2': State.H2, - 'H3': State.H3, - 'H4': State.H4, - 'H5': State.H5, - 'H6': State.H6, - - 'H+z+x': State.hadamard, - 'H-z-x': State.H2, - 'H+y-z': State.H3, - 'H-y-z': State.H4, - 'H-x+y': State.H5, - 'H-x-y': State.H6, - - 'F1': State.F1, - 'F2': State.F2, - 'F3': State.F3, - 'F4': State.F4, - - 'F1d': State.F1d, - 'F2d': State.F2d, - 'F3d': State.F3d, - 'F4d': State.F4d, - - 'R': State.R, - 'Rd': State.Rd, - 'Q': State.Q, - 'Qd': State.Qd, - 'S': State.S, - 'Sd': State.Sd, - - 'measure X': State.measureX, - 'measure Y': State.measureY, - 'measure Z': State.measure, - 'force output': State.force_output, - - 'init |0>': State.initzero, - 'init |1>': State.initone, - 'init |+>': State.initplus, - 'init |->': State.initminus, - 'init |+i>': State.initplusi, - 'init |-i>': State.initminusi, - - } - -cdef class State: - - cdef s.State* _c_state - - cdef public: - int_num num_qubits - dict gate_dict - - def __cinit__(self, int_num num_qubits): - self._c_state = new s.State(num_qubits) - - self.num_qubits = self._c_state.num_qubits - self.gate_dict = dict(gate_dict) - - cdef void hadamard(self, int_num qubit): - self._c_state.hadamard(qubit) - - cdef void H2(self, int_num qubit): - self._c_state.H2(qubit) - - cdef void H3(self, int_num qubit): - self._c_state.H3(qubit) - - cdef void H4(self, int_num qubit): - self._c_state.H4(qubit) - - cdef void H5(self, int_num qubit): - self._c_state.H5(qubit) - - cdef void H6(self, int_num qubit): - self._c_state.H6(qubit) - - cdef void F1(self, int_num qubit): - self._c_state.F1(qubit) - - cdef void F2(self, int_num qubit): - self._c_state.F2(qubit) - - cdef void F3(self, int_num qubit): - self._c_state.F3(qubit) - - cdef void F4(self, int_num qubit): - self._c_state.F4(qubit) - - cdef void F1d(self, int_num qubit): - self._c_state.F1d(qubit) - - cdef void F2d(self, int_num qubit): - self._c_state.F2d(qubit) - - cdef void F3d(self, int_num qubit): - self._c_state.F3d(qubit) - - cdef void F4d(self, int_num qubit): - self._c_state.F4d(qubit) - - cdef void I(self, int_num qubit): - pass - - cdef void X(self, int_num qubit): - self._c_state.bitflip(qubit) - - cdef void Y(self, int_num qubit): - self._c_state.Y(qubit) - - cdef void Z(self, int_num qubit): - self._c_state.phaseflip(qubit) - - cdef void S(self, int_num qubit): - self._c_state.phaserot(qubit) - - cdef void Sd(self, int_num qubit): - self._c_state.Sd(qubit) - - cdef void R(self, int_num qubit): - self._c_state.R(qubit) - - cdef void Rd(self, int_num qubit): - self._c_state.Rd(qubit) - - cdef void Q(self, int_num qubit): - self._c_state.Q(qubit) - - cdef void Qd(self, int_num qubit): - self._c_state.Qd(qubit) - - cdef void cnot(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.cnot(cqubit, tqubit) - - cdef void cz(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.hadamard(tqubit) - self._c_state.cnot(cqubit, tqubit) - self._c_state.hadamard(tqubit) - - cdef void g2(self, tuple qubits): - cdef int_num cqubit = qubits[0] - cdef int_num tqubit = qubits[1] - - self._c_state.hadamard(cqubit) - self._c_state.cnot(tqubit, cqubit) - self._c_state.cnot(cqubit, tqubit) - self._c_state.hadamard(tqubit) - - cdef void swap(self, tuple qubits): - cdef int_num qubit1 = qubits[0] - cdef int_num qubit2 = qubits[1] - - self._c_state.swap(qubit1, qubit2) - - cdef void II(self, tuple qubits): - pass - - # cpdef unsigned int measure(self, const s.int_num qubit, - def measure(self, const int_num qubit, int forced_outcome = -1): - return self._c_state.measure(qubit, forced_outcome) - - def measureX(self, const int_num qubit, int forced_outcome = -1): - - cdef unsigned int result - - self._c_state.hadamard(qubit) - result = self._c_state.measure(qubit, forced_outcome) - self._c_state.hadamard(qubit) - return result - - def measureY(self, const int_num qubit, int forced_outcome = -1): - - cdef unsigned int result - - self._c_state.H5(qubit) - result = self._c_state.measure(qubit, forced_outcome) - self._c_state.H5(qubit) - return result - - cdef void initzero(self, const int_num qubit, int forced_outcome = -1): - cdef unsigned int result - result = self._c_state.measure(qubit, forced_outcome) - - if result: - self._c_state.bitflip(qubit) - - cdef void initone(self, const int_num qubit, int forced_outcome = -1): - cdef unsigned int result - result = self._c_state.measure(qubit, forced_outcome) - - if not result: - self._c_state.bitflip(qubit) - - cdef void initplus(self, const int_num qubit, int forced_outcome = -1): - self.initzero(qubit) - self._c_state.hadamard(qubit, forced_outcome) - - cdef void initminus(self, const int_num qubit, int forced_outcome = -1): - self.initone(qubit) - self._c_state.hadamard(qubit, forced_outcome) - - cdef void initplusi(self, const int_num qubit, int forced_outcome = -1): - self.initzero(qubit, forced_outcome) - self._c_state.H5(qubit) - - cdef void initminusi(self, const int_num qubit, int forced_outcome = -1): - self.initone(qubit, forced_outcome) - self._c_state.H5(qubit) - - def logical_sign(self, logical_op, delogical_op): - """ - - Args: - logical_op: - delogical_op: - - Returns: - - """ - return find_logical_signs(self, logical_op, delogical_op) - - def run_gate(self, symbol, locations, output=True, **gate_kwargs): - """ - - Args: - symbol: - locations: - output: - **gate_kwargs: - - Returns: - - """ - - if output: - output = {} - for location in locations: - results = self.gate_dict[symbol](self, location, **gate_kwargs) - if results: - output[location] = results - - return output - - else: - for location in locations: - self.gate_dict[symbol](self, location, **gate_kwargs) - - return {} - - @property - def signs_minus(self): - return self._c_state.signs_minus - - @property - def signs_i(self): - return self._c_state.signs_i - - @property - def stabs(self): - return self._c_state.stabs - - @property - def destabs(self): - return self._c_state.destabs - - def _pauli_sign(self, gen, i_gen): - - if i_gen in self.signs_minus: - if i_gen in self.signs_i: - sign = '-i' - else: - sign = ' -' - else: - - if i_gen in self.signs_i: - sign = ' i' - else: - sign = ' ' - - return sign - - def force_output(self, int_num qubit, output=-1): - """ - Outputs value. - - Used for error generators to generate outputs when replacing measurements. - - Args: - state: - qubit: - output: - - Returns: - - """ - return output - - def col_string(self, gen): - """ - Prints out the stabilizers for the column-wise sparse representation. - - :param num_qubits: - :return: - """ - - col_x = gen['col_x'] - col_z = gen['col_z'] - - result = [] - - num_qubits = self.num_qubits - - for i_gen in range(num_qubits): - - stab_letters = [] - - # ---- Signs ---- # - sign = self._pauli_sign(gen, i_gen) - stab_letters.append(sign) - - # ---- Paulis ---- # - for qubit in range(num_qubits): - - letter = 'U' - - if i_gen in col_x[qubit] and i_gen not in col_z[qubit]: - letter = 'X' - - elif i_gen not in col_x[qubit] and i_gen in col_z[qubit]: - letter = 'Z' - - elif i_gen in col_x[qubit] and i_gen in col_z[qubit]: - letter = 'W' - - elif i_gen not in col_x[qubit] and i_gen not in col_z[qubit]: - letter = 'I' - - stab_letters.append(letter) - - # print(''.join(stab_letters)) - result.append(''.join(stab_letters)) - - return result - - def print_tableau(self, gen, verbose=True): - """ - Prints out the stabilizers. - :return: - """ - - col_str = self.col_string(gen) - row_str = self.row_string(gen) - - if col_str != row_str: - print('col') - for line in col_str: - print(line) - - print('\nrow') - for line in row_str: - print(line) - - raise Exception('Something bad happened! String representation of the row-wise vs column-wile ' - 'spare stabilizers do not match!') - - if verbose: - for line in col_str: - print(line) - - return col_str - - def row_string(self, gen): - """ - Prints out the stabilizers for the row-wise sparse representation. - - :param num_qubits: - :return: - """ - - row_x = gen['row_x'] - row_z = gen['row_z'] - - result = [] - - num_qubits = self.num_qubits - - for i_gen in range(num_qubits): - - stab_letters = [] - - # ---- Signs ---- # - sign = self._pauli_sign(gen, i_gen) - stab_letters.append(sign) - - # ---- Paulis ---- # - for qubit in range(num_qubits): - - letter = 'U' - - if qubit in row_x[i_gen] and qubit not in row_z[i_gen]: - letter = 'X' - - elif qubit not in row_x[i_gen] and qubit in row_z[i_gen]: - letter = 'Z' - - elif qubit in row_x[i_gen] and qubit in row_z[i_gen]: - letter = 'W' - - elif qubit not in row_x[i_gen] and qubit not in row_z[i_gen]: - letter = 'I' - - stab_letters.append(letter) - - # print(''.join(stab_letters)) - result.append(''.join(stab_letters)) - - return result - - def __dealloc__(self): - del self._c_state diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim_header.pxd b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim_header.pxd deleted file mode 100644 index 7f79ee5c0..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/cysparsesim_header.pxd +++ /dev/null @@ -1,65 +0,0 @@ -# distutils: language = c++ -#cython: language_level=3, boundscheck=False, nonecheck=False, wraparound=False - -# 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. - -from libcpp.vector cimport vector -from libcpp.unordered_set cimport unordered_set - -cdef extern from "sparsesim.h": - - ctypedef unsigned long long int_num - ctypedef unordered_set[int_num] int_set - ctypedef vector[int_set] int_set_vec - - struct Generators: - int_set_vec col_x - int_set_vec col_z - int_set_vec row_x - int_set_vec row_z - - cdef cppclass State: - # State() except + - State(int_num) except + - int_num num_qubits - Generators stabs, destabs - int_set signs_minus, signs_i - - # Methods - void hadamard(const int_num &qubit) - void bitflip(const int_num &qubit) # X - void phaseflip(const int_num& qubit) # Z - void Y(const int_num& qubit) # Y - void phaserot(const int_num& qubit) # S - void Sd(const int_num& qubit) # S - void R(const int_num& qubit) # R - void Rd(const int_num& qubit) # Rd - void Q(const int_num& qubit) # Q - void Qd(const int_num& qubit) # Qd - void H2(const int_num &qubit) # H2 - void H3(const int_num &qubit) # H3 - void H4(const int_num &qubit) # H4 - void H5(const int_num &qubit) # H5 - void H6(const int_num &qubit) # H6 - void F1(const int_num &qubit) # F1 - void F2(const int_num &qubit) # F2 - void F3(const int_num &qubit) # F3 - void F4(const int_num &qubit) # F4 - void F1d(const int_num &qubit) # F1d - void F2d(const int_num &qubit) # F2d - void F3d(const int_num &qubit) # F3d - void F4d(const int_num &qubit) # F4d - void cnot(const int_num& tqubit, const int_num& cqubit) - void swap(const int_num& qubit1, const int_num& qubit2) - unsigned int measure(const int_num& qubit, int force) diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/logical_sign.py b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/logical_sign.py deleted file mode 100644 index 28a9c9fd6..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/logical_sign.py +++ /dev/null @@ -1,177 +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. - -"""Functions for logical sign computation. - -find_logical_signs -logical_flip -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pecos.circuits import QuantumCircuit - from pecos.protocols import SimulatorProtocol - - -def find_logical_signs( - state: SimulatorProtocol, - logical_circuit: QuantumCircuit, - delogical_circuit: QuantumCircuit, -) -> int: - """Find the sign of the logical operator. - - Args: - ---- - state: The quantum state instance - logical_circuit: The logical circuit to find the sign of - delogical_circuit: Optional delogical circuit to check anti-commutation with - """ - if len(logical_circuit) != 1 or len(delogical_circuit) != 1: - msg = "Logical operators are expected to only have one tick." - raise Exception(msg) - - stabs = state.stabs - destabs = state.destabs - signs_minus = state.signs_minus - signs_i = state.signs_i - - logical_xs = set() - logical_zs = set() - - delogical_xs = set() - delogical_zs = set() - - for symbol, gate_locations in logical_circuit.items(params=False): - if symbol == "X": - logical_xs.update(gate_locations) - elif symbol == "Z": - logical_zs.update(gate_locations) - elif symbol == "Y": - logical_xs.update(gate_locations) - logical_zs.update(gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - for symbol, gate_locations in delogical_circuit.items(params=False): - if symbol == "X": - delogical_xs.update(gate_locations) - elif symbol == "Z": - delogical_zs.update(gate_locations) - elif symbol == "Y": - delogical_xs.update(gate_locations) - delogical_zs.update(gate_locations) - else: - msg = f'Can not currently handle logical operator with operator "{symbol}"!' - raise Exception( - msg, - ) - - # Make sure the logical and delogical anti-commute - - anticom_x = len(logical_xs & delogical_zs) % 2 # Number of common elements modulo 2 - anticom_z = len(logical_zs & delogical_xs) % 2 # Number of common elements modulo 2 - - if not ((anticom_x + anticom_z) % 2): - print(f"logical Xs: {logical_xs} logical Zs: {logical_zs}") - print(f"delogical Xs: {delogical_xs} delogical Zs: {delogical_zs}") - msg = "Logical and delogical operators supplied do not anti-commute!" - raise Exception(msg) - - # We want the supplied logical operator to be in the stabilizer group and - # the supplied delogical to not be in the stabilizers (we want it to end up being the logical op's destabilizer) - - # The following two function calls are wasteful because we will need some of what they discover... such as all the - # stabilizers that have destabilizers that anti-commute with the logical operator... - # But it is assumed that the user is not calling this function that often... so we can be wasteful... - - # Check logical is a stabilizer (we want to remove it from the stabilizers) - - # Find the anti-commuting destabilizers => stabilizers to give the logical operator - # -------------------------- - build_stabs = set() - - for q in logical_xs: # For qubits that have Xs in for the logical operator... - build_stabs ^= destabs["col_z"][ - q - ] # Add in stabilizers that anti-commute for the logical operator's Xs - - for q in logical_zs: - build_stabs ^= destabs["col_x"][ - q - ] # Add in stabilizers that anti-commute for the logical operator's Zs - - # If a stabilizer anticommutes an even number of times for the X and/or Z Paulis... it will not appear due to ^= - - # Confirm that the stabilizers chosen give the logical operator. If not... return with a failure = 1 - # -------------------------- - test_x = set() - test_z = set() - - for stab in build_stabs: - test_x ^= stabs["row_x"][stab] - test_z ^= stabs["row_z"][stab] - - # Compare with logical operator - test_x ^= logical_xs - test_z ^= logical_zs - - if len(test_x) != 0 or len(test_z) != 0: - # for stab in build_stabs: - - print(f"Logical op: xs - {logical_xs} and zs - {logical_zs}") - msg = f"Failure due to not finding logical op! x... {test_x ^ logical_xs!s} z... {test_z ^ logical_zs!s}" - raise Exception(msg) - - # Get the sign of the logical operator - # -------------------------- - - # First, the minus sign - logical_minus = len(build_stabs & signs_minus) - - # Second, the number of imaginary numbers - logical_i = len(build_stabs & signs_i) - - # Translate the Ws to Ys... W = -i(iW) = -iY => For each Y add another -1 and +i. - logical_ws = logical_xs & logical_zs - num_ys = len(logical_ws) - - logical_minus += num_ys - logical_i += num_ys - - # Do (-1)^even = 1 -> 0, (-1)^odd = -1 -> 1 - logical_minus %= 2 - - # Reinterpret number of is - logical_i %= 4 - - # num_is %4 = 0 => +1 => logical_i = 0, logical_minus += 0 - # num_is %4 = 1 => +i => logical_i = 1, logical_minus += 0 - - if logical_i == 2: # num_is %4 = 2 => -1 => logical_i = 0, logical_minus += 1 - logical_i = 0 - logical_minus += 1 - elif logical_i == 3: # num_is %4 = 3 => -i => logical_i = 1, logical_minus += 1 - logical_i = 1 - logical_minus += 1 - - if logical_i != 0: - msg = "Logical operator has an imaginary sign... Not allowed if logical state is stabilized by logical op!" - raise Exception(msg) - - return logical_minus diff --git a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim - Copy.cpp b/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim - Copy.cpp deleted file mode 100644 index bff725d78..000000000 --- a/python/quantum-pecos/src/pecos/simulators/cysparsesim_row/src/sparsesim - Copy.cpp +++ /dev/null @@ -1,1698 +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. - -#include "sparsesim.h" - -int_set_vec build_empty(int_num size) { - int_set_vec matrix(size); - - return matrix; -} - -int_set_vec build_ones(int_num size){ - - int_set_vec matrix = build_empty(size); - - for( int_num i = 0; i < size; i++) { - matrix[i].insert(i); - } - - return matrix; -} - -State::State(const int_num& num_qubits) - :num_qubits(num_qubits) -{ - //num_qubits = num_qubits; - clear(); -} - -void State::clear() { // Allows state to reinitialize. - - // Initialize stabilizers. - stabs.col_x = build_empty(num_qubits); - stabs.col_z = build_ones(num_qubits); - stabs.row_x = build_empty(num_qubits); - stabs.row_z = build_ones(num_qubits); - - // Initialize destabilizers. - destabs.col_x = build_ones(num_qubits); - destabs.col_z = build_empty(num_qubits); - destabs.row_x = build_ones(num_qubits); - destabs.row_z = build_empty(num_qubits); - - //Inilialize signs columns - signs_minus.clear(); - signs_i.clear(); -} - -void State::hadamard(const int_num& qubit) { - /* - X -> Z - Z -> X - W -> -W - Y -> -Y - */ - - // X and Z -> -1 - for (int_num s = 0; s < num_qubits; s++){ - - if(stabs.row_z[s].count(qubit) & stabs.row_x[s].count(qubit)) { - if (signs_minus.count(s)) { - signs_minus.erase(s); - - } else { - signs_minus.insert(s); - - } - - } else { // Only X or Z - - if(stabs.row_z[s].count(qubit)) { - stabs.row_x[s].insert(qubit); - stabs.row_z[s].erase(qubit); - - } else { - - if(stabs.row_x[s].count(qubit)) { - stabs.row_z[s].insert(qubit); - stabs.row_x[s].erase(qubit); - - } - } - - } - - if(!(destabs.row_z[s].count(qubit) & destabs.row_x[s].count(qubit))) { // Only X or Z - - if(destabs.row_z[s].count(qubit)) { - destabs.row_x[s].insert(qubit); - destabs.row_z[s].erase(qubit); - - } else { - - if(destabs.row_x[s].count(qubit)) { - destabs.row_z[s].insert(qubit); - destabs.row_x[s].erase(qubit); - - } - } - - } - - - } - - stabs.col_x[qubit].swap(stabs.col_z[qubit]); - destabs.col_x[qubit].swap(destabs.col_z[qubit]); - - -} - -void hadamard_gen_mod(Generators& gen, const int_num& qubit) { - // X <-> Z - - - // Swap for rows - for (const int_num& x_gen_id: gen.col_x[qubit]) { - if (gen.col_z[qubit].count(x_gen_id) == 0) { - gen.row_x[x_gen_id].erase(qubit); - gen.row_z[x_gen_id].insert(qubit); - } - } - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - if (gen.col_x[qubit].count(z_gen_id) == 0) { - gen.row_z[z_gen_id].erase(qubit); - gen.row_x[z_gen_id].insert(qubit); - } - } - - // Swap for columns - gen.col_x[qubit].swap(gen.col_z[qubit]); - -} - -void State::bitflip(const int_num& qubit) { - - /* - for (const int_num& elem: stabs.col_z[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - } else { - signs_minus.insert(elem); - - } - - } // end for - */ - - // Z -> -1 - for (int_num s = 0; s < num_qubits; s++){ - - if(stabs.row_z[s].count(qubit)) { - if (signs_minus.count(s)) { - signs_minus.erase(s); - - } else { - signs_minus.insert(s); - - } - } - } - -} - -void State::phaseflip(const int_num& qubit) { - - // X -> -1 - // Z -> -1 - for (int_num s = 0; s < num_qubits; s++){ - - if(stabs.row_x[s].count(qubit)) { - if (signs_minus.count(s)) { - signs_minus.erase(s); - - } else { - signs_minus.insert(s); - - } - } - } -} - -void State::Y(const int_num& qubit) { - - // Z -> -1 - for (int_num s = 0; s < num_qubits; s++){ - - if(stabs.row_x[s].count(qubit)) { - if (signs_minus.count(s)) { - signs_minus.erase(s); - - } else { - signs_minus.insert(s); - - } - } - - if(stabs.row_z[s].count(qubit)) { - if (signs_minus.count(s)) { - signs_minus.erase(s); - - } else { - signs_minus.insert(s); - - } - } - } - -} - -void State::phaserot(const int_num& qubit) { - /* - X -> iW = Y - Z -> Z - W -> iX - Y -> -X - */ - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (int_num s = 0; s < num_qubits; s++){ - - if(stabs.row_x[s].count(qubit)) { - if (signs_i.count(s)) { - signs_i.erase(s); - - // Now add it to signs_minus - if(signs_minus.count(s)) { - signs_minus.erase(s); - } else { - signs_minus.insert(s); - } - - } else { - signs_i.insert(s); - } - - } - - } - - - /* - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - }*/ - - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void phaserot_gen_mod(Generators& gen, const int_num& qubit) { - /* - X -> iW - Z -> Z - W -> iX - */ - // X -> Z - - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - - // Update row - if (gen.row_z[x_gen_id].count(qubit)) { - gen.row_z[x_gen_id].erase(qubit); - } else { - gen.row_z[x_gen_id].insert(qubit); - } - - // Update column - if ( gen.col_z[qubit].count(x_gen_id)) { - gen.col_z[qubit].erase(x_gen_id); - } else { - gen.col_z[qubit].insert(x_gen_id); - } - } - -} - -void State::cnot(const int_num& tqubit, const int_num& cqubit) { - // Xt ^= Xc X propagates from control to target - - /* - for (const int_num& x_gen_id: stabs.col_x[tqubit]) { - if (stabs.row_x[x_gen_id].count(cqubit)) { - stabs.row_x[x_gen_id].erase(cqubit); - stabs.col_x[cqubit].erase(x_gen_id); - } else { - stabs.row_x[x_gen_id].insert(cqubit); - stabs.col_x[cqubit].insert(x_gen_id); - } - }*/ - for (int_num s = 0; s < num_qubits; s++){ - - if (stabs.row_x[s].count(tqubit)) { - - if (stabs.row_x[s].count(cqubit)) { - stabs.row_x[s].erase(cqubit); - stabs.col_x[cqubit].erase(s); - } else { - stabs.row_x[s].insert(cqubit); - stabs.col_x[cqubit].insert(s); - } - } - - - - // Zt ^= Zc Z propagates from target to control - if (stabs.row_z[s].count(cqubit)) { - if (stabs.row_z[s].count(tqubit)) { - stabs.row_z[s].erase(tqubit); - stabs.col_z[tqubit].erase(s); - } else { - stabs.row_z[s].insert(tqubit); - stabs.col_z[tqubit].insert(s); - } - } - - if (destabs.row_x[s].count(tqubit)) { - if (destabs.row_x[s].count(cqubit)) { - destabs.row_x[s].erase(cqubit); - destabs.col_x[cqubit].erase(s); - } else { - destabs.row_x[s].insert(cqubit); - destabs.col_x[cqubit].insert(s); - } - } - - // Zt ^= Zc Z propagates from target to control - if (destabs.row_z[s].count(cqubit)) { - if (destabs.row_z[s].count(tqubit)) { - destabs.row_z[s].erase(tqubit); - destabs.col_z[tqubit].erase(s); - } else { - destabs.row_z[s].insert(tqubit); - destabs.col_z[tqubit].insert(s); - } - } - } -} - -void cnot_gen_mod(Generators& gen, const int_num& tqubit, - const int_num& cqubit) { - - // Xt ^= Xc X propagates from control to target - for (const int_num& x_gen_id: gen.col_x[tqubit]) { - if (gen.row_x[x_gen_id].count(cqubit)) { - gen.row_x[x_gen_id].erase(cqubit); - gen.col_x[cqubit].erase(x_gen_id); - } else { - gen.row_x[x_gen_id].insert(cqubit); - gen.col_x[cqubit].insert(x_gen_id); - } - } - - // Zt ^= Zc Z propagates from target to control - for (const int_num& z_gen_id: gen.col_z[cqubit]) { - if (gen.row_z[z_gen_id].count(tqubit)) { - gen.row_z[z_gen_id].erase(tqubit); - gen.col_z[tqubit].erase(z_gen_id); - } else { - gen.row_z[z_gen_id].insert(tqubit); - gen.col_z[tqubit].insert(z_gen_id); - } - } - -} - - - -void State::swap(const int_num& qubit1, const int_num& qubit2) { - swap_gen_mod(stabs, qubit1, qubit2); - swap_gen_mod(destabs, qubit1, qubit2); -} - -void swap_gen_mod(Generators& gen, const int_num& qubit1, - const int_num& qubit2) { - - // Xs - for (const int_num& x_gen_id: gen.col_x[qubit1]) { - if (gen.col_x[qubit2].count(x_gen_id) == 0){ - gen.row_x[x_gen_id].erase(qubit1); - gen.row_x[x_gen_id].insert(qubit2); - } - - } - - for (const int_num& x_gen_id: gen.col_x[qubit2]) { - if (gen.col_x[qubit1].count(x_gen_id) == 0){ - gen.row_x[x_gen_id].erase(qubit2); - gen.row_x[x_gen_id].insert(qubit1); - } - - } - - // Zs - for (const int_num& z_gen_id: gen.col_z[qubit1]) { - if (gen.col_z[qubit2].count(z_gen_id) == 0){ - gen.row_z[z_gen_id].erase(qubit1); - gen.row_z[z_gen_id].insert(qubit2); - } - - } - - for (const int_num& z_gen_id: gen.col_z[qubit2]) { - if (gen.col_z[qubit1].count(z_gen_id) == 0){ - gen.row_z[z_gen_id].erase(qubit2); - gen.row_z[z_gen_id].insert(qubit1); - } - - } - - // Swap for columns - gen.col_x[qubit1].swap(gen.col_x[qubit2]); - gen.col_z[qubit1].swap(gen.col_z[qubit2]); - -} - -unsigned int State::measure(const int_num& qubit, int force=-1) { - - if (stabs.col_x[qubit].size() == 0) { // There are no anticommuting stabilizers - return deterministic_measure(qubit); - } else { - return nondeterministic_measure(qubit, force); - } - -} - -unsigned int State::deterministic_measure(const int_num& qubit) { - - int_set cumulative_x; - int_num num_minuses = 0; - int_num num_is = 0; - - - // When no stabilizers anti-commute with the measurement, this means that - // we are measuring a stabilizer of the state. The task then is to - // determine the sign of the measured stabilizer. The generators that have - // destabilizers that anticommute with the measurement multiply to give - // the measured stabilzer. Therefore, we loop through these generators. - - - // Count the is and -1s out front of the stabilizers being multiplied - // together. - - for (int_num gen_id = 0; gen_id < num_qubits; gen_id++){ - - if (destabs.row_x[gen_id].count(qubit)) { - - - - // Determine the overall minus sign of the generators. - if (signs_minus.count(gen_id)) { - num_minuses += 1; - } - - // Determine the sign contribution due to is. - if (signs_i.count(gen_id)) { - num_is += 1; - } - - // When determine the sign of the measured stabilizer we are - // effectively multiplying generators together to get the measured - // stabilzier. We therefore have to correct the sign due to ZX -> -ZX. - for (const int_num& q: stabs.row_z[gen_id]) { - if (cumulative_x.count(q)) { - num_minuses += 1; - } - } - - // We only need to know the Xs contribution of the generator - // multiplications. => cumulative_x - for (const int_num& q: stabs.row_x[gen_id]) { - if (cumulative_x.count(q)) { - cumulative_x.erase(q); - } else { - cumulative_x.insert(q); - } - } - - - - } - - } - - - if (num_is % 4) { // Can only be 0 or 2 (otherwise => an overall i or -i) - num_minuses += 1; - } - - return num_minuses % 2; - -} - -unsigned int State::nondeterministic_measure(const int_num& qubit, - int force=-1) { - // Here at least one stabilizer anticommutes with the measured stabilizer. - // Therefore, we will have to update the stabilizers and destabilizers. - - int meas_outcome; - int_num num_minuses; - // int_set removed_row_x, removed_row_z; - int_set anticom_stabs, anticom_destabs; - - // anticom_stabs = int_set(stabs.col_x[qubit]); - // anticom_destabs = int_set(destabs.col_x[qubit]); - - // Choose an anti-commuting stabilizer to remove. - // int_num removed_id = *(stabs.col_x[qubit].begin()); - - // int_num removed_id = *(stabs.col_x[qubit].begin()); - int_num removed_id = 2*num_qubits + 1; - int_num smallest_wt = 2*num_qubits; - int_num temp_wt; - - for (int_num gen_id = 0; gen_id < num_qubits; gen_id++){ - if (stabs.row_x[gen_id].count(qubit)) { - anticom_stabs.insert(gen_id); - } - - if (destabs.row_x[gen_id].count(qubit)) { - anticom_destabs.insert(gen_id); - } - - - if (stabs.row_x[gen_id].count(qubit)) { - - temp_wt = stabs.row_x[gen_id].size() + stabs.row_z[gen_id].size(); - - if (temp_wt < smallest_wt) { - removed_id = gen_id; - smallest_wt = temp_wt; - } - } - - - } - - anticom_stabs.erase(removed_id); - anticom_destabs.erase(removed_id); - - const int_set removed_row_x = int_set(stabs.row_x[removed_id]); - const int_set removed_row_z = int_set(stabs.row_z[removed_id]); - - - if (signs_minus.count(removed_id)) { - - // Update all the anti-commuting stabs signs with that of the removed stab. - for (const int_num& gen_id: anticom_stabs) { - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - } - - } - - if (signs_i.count(removed_id)) { - - signs_i.erase(removed_id); // Throw away the sign for the removed stab. - - for (const int_num& gen_id: anticom_stabs) { - // i*i = -1 - if(signs_i.count(gen_id)) { - signs_i.erase(gen_id); - - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - - } else { - - signs_i.insert(gen_id); - } - } - } - - for (const int_num& q: destabs.row_x[removed_id]) { - destabs.col_x[q].erase(removed_id); - } - - for (const int_num& q : destabs.row_z[removed_id]) { - destabs.col_z[q].erase(removed_id); - } - - - // for (const int_num& gen_id: stabs.col_x[qubit]) { - for (const int_num& gen_id: anticom_stabs) { - - - num_minuses = 0; - // Correct signs due to ZX -> -XZ - // Count the number of minuses due to this - - - for (const int_num& q: removed_row_z) { - - if (stabs.row_x[gen_id].count(q)) { - num_minuses += 1; - } - - if (stabs.row_z[gen_id].count(q)) { - stabs.row_z[gen_id].erase(q); - stabs.col_z[q].erase(gen_id); - } else { - stabs.row_z[gen_id].insert(q); - stabs.col_z[q].insert(gen_id); - } - - } - - if (num_minuses % 2) { - if (signs_minus.count(gen_id)) { - signs_minus.erase(gen_id); - } else { - signs_minus.insert(gen_id); - } - } - - for (const int_num& q: removed_row_x) { - if (stabs.row_x[gen_id].count(q)) { - stabs.row_x[gen_id].erase(q); - stabs.col_x[q].erase(gen_id); - } else { - stabs.row_x[gen_id].insert(q); - stabs.col_x[q].insert(gen_id); - } - - } - - } // end big for loop - - - // ------------------------------------------------------------------------ - // Update destabilziers - - - - // Add in/Multiply by the new destabilizer - // This makes all destabilizers commute with the new stabilizer. - for (const int_num& q: removed_row_x) { - - stabs.col_x[q].erase(removed_id); - destabs.col_x[q].insert(removed_id); - - for (const int_num& row: anticom_destabs) { - if (destabs.col_x[q].count(row)) { - destabs.col_x[q].erase(row); - destabs.row_x[row].erase(q); - } else { - destabs.col_x[q].insert(row); - destabs.row_x[row].insert(q); - } - - } - } - - - for (const int_num& q: removed_row_z) { - - stabs.col_z[q].erase(removed_id); - destabs.col_z[q].insert(removed_id); - - for (const int_num& row: anticom_destabs) { - if (destabs.col_z[q].count(row)) { - destabs.col_z[q].erase(row); - destabs.row_z[row].erase(q); - } else { - destabs.col_z[q].insert(row); - destabs.row_z[row].insert(q); - } - - } - } - - - // Remove replaced stabilizer with the measured stabilizer - stabs.col_z[qubit].insert(removed_id); - - // Row update - stabs.row_x[removed_id].clear(); - stabs.row_z[removed_id].clear(); - stabs.row_z[removed_id].insert(qubit); - - destabs.row_x[removed_id] = removed_row_x; - destabs.row_z[removed_id] = removed_row_z; - - meas_outcome = force; - - if (meas_outcome) { - signs_minus.insert(removed_id); - } else { - signs_minus.erase(removed_id); - } - - return meas_outcome; - -} - -// ---------------------------------------------------------------------------- - -void State::R(const int_num& qubit) { - /* - Applies a R rotation to stabilizers/destabilizers - - R = \sqrt{XZ} = SQS^{\dagger} - - R = R - XZ = R^2 - R^{\dagger} = R^3 - I = R^4 - - X -> -Z - Z -> X - W -> W - Y -> Y - */ - - // Change the sign appropriately - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Swap X <-> Z - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - - - -void State::Rd(const int_num& qubit) { - /* - Applies a R rotation to stabilizers/destabilizers - - R^{\dagger} = \sqrt{XZ} = SQ^{\dagger}S^{\dagger} - - R = R - XZ = R^2 - R^{\dagger} = R^3 - I = R^4 - - X -> Z - Z -> -X - W -> W - Y -> Y - */ - - // Change the sign appropriately - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Swap X <-> Z - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - -void State::Sd(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> Z - W -> -iX - Y -> X - */ - - - - for (const int_num& i: stabs.col_x[qubit]) { - - - // X -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - - -void State::Q(const int_num& qubit) { - /* - X -> X - Z -> -iW = -Y - W -> -iZ - Y -> Z - */ - - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void Q_gen_mod(Generators& gen, const int_num& qubit) { - /* - X -> X - Z -> Y - */ - - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - - // Update row - if (gen.row_x[z_gen_id].count(qubit)) { - gen.row_x[z_gen_id].erase(qubit); - gen.col_x[qubit].erase(z_gen_id); - } else { - gen.row_x[z_gen_id].insert(qubit); - gen.col_x[qubit].insert(z_gen_id); - } - - } - -} - -void State::Qd(const int_num& qubit) { - /* - X -> X - Z -> iW = Y - W -> iZ - Y -> -Z - */ - - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void State::H2(const int_num& qubit) { - /* - X -> -Z - Z -> -X - W -> -W - Y -> -Y - */ - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - hadamard_gen_mod(stabs, qubit); - hadamard_gen_mod(destabs, qubit); - -} - -void State::H3(const int_num& qubit) { - /* - X -> iW = Y - Z -> -Z - W -> -iX - Y -> X - */ - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - } - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void State::H4(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> -Z - W -> iX - Y -> -X - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - phaserot_gen_mod(stabs, qubit); - phaserot_gen_mod(destabs, qubit); - -} - -void State::H5(const int_num& qubit) { - /* - X -> -X - Z -> iW = Y - W -> -iZ - Y -> Z - */ - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> -1 - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - -void State::H6(const int_num& qubit) { - /* - X -> -X - Z -> -iW = -Y - W -> iZ - Y -> -Z - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - Q_gen_mod(stabs, qubit); - Q_gen_mod(destabs, qubit); - -} - - -void State::F1(const int_num& qubit) { - /* - X -> Z - Z -> X - W -> -W - Y -> -Y - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - for (const int_num& i: stabs.col_x[qubit]) { - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void F1_gen_mod(Generators& gen, const int_num& qubit) { - // X -> W - // Z -> X - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - - if (gen.col_z[qubit].count(x_gen_id)) { - gen.row_x[x_gen_id].erase(qubit); - } else { - gen.row_z[x_gen_id].insert(qubit); - } - } - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - if (gen.col_x[qubit].count(z_gen_id) == 0) { - gen.row_z[z_gen_id].erase(qubit); - gen.row_x[z_gen_id].insert(qubit); - } - - } - - // Swap for columns - gen.col_x[qubit].swap(gen.col_z[qubit]); - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - if (gen.col_x[qubit].count(z_gen_id)) { - gen.col_x[qubit].erase(z_gen_id); - } else { - gen.col_x[qubit].insert(z_gen_id); - } - } - -} - - -void State::F2(const int_num& qubit) { - /* - X -> -Z - Z -> iW = Y - W -> iX - Y -> -X - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - for (const int_num& i: stabs.col_z[qubit]) { - - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void F2_gen_mod(Generators& gen, const int_num& qubit) { - // X -> W - // Z -> X - - // X -> Z - // Z -> W - - for (const int_num& z_gen_id: gen.col_z[qubit]) { - - if (gen.col_x[qubit].count(z_gen_id)) { - gen.row_z[z_gen_id].erase(qubit); - } else { - gen.row_x[z_gen_id].insert(qubit); - } - } - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - if (gen.col_z[qubit].count(x_gen_id) == 0) { - gen.row_x[x_gen_id].erase(qubit); - gen.row_z[x_gen_id].insert(qubit); - } - - } - - // Swap for columns - gen.col_z[qubit].swap(gen.col_x[qubit]); - - for (const int_num& x_gen_id: gen.col_x[qubit]) { - if (gen.col_z[qubit].count(x_gen_id)) { - gen.col_z[qubit].erase(x_gen_id); - } else { - gen.col_z[qubit].insert(x_gen_id); - } - } - -} - -void State::F3(const int_num& qubit) { - /* - X -> iW = Y - Z -> -X - W -> iZ - Y -> -Z - */ - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void State::F4(const int_num& qubit) { - /* - X -> Z - Z -> -iW = -Y - W -> iX - Y -> -X - */ - - // Z not X -> -1 - // ------------------- - for (const int_num& i: stabs.col_z[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_x[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F1d(const int_num& qubit) { - /* - X -> Z - Z -> iW = Y - W -> -iX - Y -> X - */ - - // X and Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - - if (stabs.col_z[qubit].count(elem)) { - - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F2d(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> -X - W -> -iZ - Y -> Z - */ - - - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} - -void State::F3d(const int_num& qubit) { - /* - X -> -Z - Z -> -iW = -Y - W -> -iX - Y -> X - */ - - // X or Z -> -1 - for (const int_num& elem: stabs.col_x[qubit]) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } // end for - - - for (const int_num& elem: stabs.col_z[qubit]) { - - if(stabs.col_x[qubit].count(elem) == 0) { - if (signs_minus.count(elem)) { - signs_minus.erase(elem); - - } else { - signs_minus.insert(elem); - - } - } - } // end for - - - for (const int_num& i: stabs.col_z[qubit]) { - - // Z -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F2_gen_mod(stabs, qubit); - F2_gen_mod(destabs, qubit); - -} - -void State::F4d(const int_num& qubit) { - /* - X -> -iW = -Y - Z -> X - W -> iZ - Y -> -Z - */ - - // X not Z -> -1 - // ------------------- - for (const int_num& i: stabs.col_x[qubit]) { - if(signs_minus.count(i)) { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.erase(i); - } - } else { - if(stabs.col_z[qubit].count(i) == 0) { - signs_minus.insert(i); - } - } - } - - // X -> i - // signs_i ^= stabs.col_x[qubit] - // plus: i * i = -1 - for (const int_num& i: stabs.col_x[qubit]) { - if (signs_i.count(i)) { - signs_i.erase(i); - - // Now add it to signs_minus - if(signs_minus.count(i)) { - signs_minus.erase(i); - } else { - signs_minus.insert(i); - } - - } else { - signs_i.insert(i); - } - - } - - F1_gen_mod(stabs, qubit); - F1_gen_mod(destabs, qubit); - -} diff --git a/python/quantum-pecos/src/pecos/simulators/sparsesim/bindings.py b/python/quantum-pecos/src/pecos/simulators/sparsesim/bindings.py index 872f18d69..f7de77127 100644 --- a/python/quantum-pecos/src/pecos/simulators/sparsesim/bindings.py +++ b/python/quantum-pecos/src/pecos/simulators/sparsesim/bindings.py @@ -51,6 +51,13 @@ "SYdg": q1.SYdg, # +z-x == R(Y, -pi/2) "SZ": q1.SZ, # +y+z == R(Z, pi/2) "SZdg": q1.SZdg, # -y+z == R(Z, -pi/2) + # Alternative names for square roots + "Q": q1.SX, # sqrt(X) + "Qd": q1.SXdg, # sqrt(X)† + "R": q1.SY, # sqrt(Y) + "Rd": q1.SYdg, # sqrt(Y)† + "S": q1.SZ, # sqrt(Z) + "Sd": q1.SZdg, # sqrt(Z)† # Hadamard-like "H": q1.H, "H2": q1.H2, @@ -61,12 +68,17 @@ # Face rotations "F": q1.F, # +y+x "Fdg": q1.Fdg, # +z+y + "F1": q1.F, # Alternative name for F + "F1d": q1.Fdg, # Alternative name for Fdg "F2": q1.F2, # -z+y "F2dg": q1.F2dg, # -y-x + "F2d": q1.F2dg, # Alternative name for F2dg "F3": q1.F3, # +y-x "F3dg": q1.F3dg, # -z-y + "F3d": q1.F3dg, # Alternative name for F3dg "F4": q1.F4, # +z-y "F4dg": q1.F4dg, # -y-z + "F4d": q1.F4dg, # Alternative name for F4dg # two-qubit operations # ==================== "CX": q2.CX, diff --git a/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_init.py b/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_init.py index 2e1cfa4f2..49b7e059f 100644 --- a/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_init.py +++ b/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_init.py @@ -10,11 +10,12 @@ # specific language governing permissions and limitations under the License. """Integration tests for stabilizer simulator gate initialization.""" -from pecos.simulators import SparseSimPy, SparseSimRs +from pecos.simulators import CppSparseSimRs, SparseSimPy, SparseSimRs states = [ SparseSimPy, SparseSimRs, + CppSparseSimRs, ] diff --git a/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_one_qubit.py b/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_one_qubit.py index 08ebeab3f..bbdbf515a 100644 --- a/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_one_qubit.py +++ b/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_one_qubit.py @@ -11,11 +11,12 @@ """Test all one-qubit gates.""" -from pecos.simulators import SparseSimPy, SparseSimRs +from pecos.simulators import CppSparseSimRs, SparseSimPy, SparseSimRs states = [ SparseSimPy, SparseSimRs, + CppSparseSimRs, ] diff --git a/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_two_qubit.py b/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_two_qubit.py index 2ad708a76..9d8ed0366 100644 --- a/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_two_qubit.py +++ b/python/tests/pecos/integration/state_sim_tests/test_stab_sims/test_gate_two_qubit.py @@ -11,11 +11,12 @@ """Test all one-qubit gates.""" -from pecos.simulators import SparseSimPy, SparseSimRs +from pecos.simulators import CppSparseSimRs, SparseSimPy, SparseSimRs states = [ SparseSimPy, SparseSimRs, + CppSparseSimRs, ] diff --git a/python/tests/pecos/integration/test_cppsparse_sim.py b/python/tests/pecos/integration/test_cppsparse_sim.py new file mode 100644 index 000000000..ebfa52e59 --- /dev/null +++ b/python/tests/pecos/integration/test_cppsparse_sim.py @@ -0,0 +1,151 @@ +# 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. + +"""Integration tests for C++ sparse simulator via Rust bindings.""" + +import pytest +from pecos.simulators import CppSparseSimRs + + +def test_basic_gates() -> None: + """Test basic gate operations without checking tableaus.""" + sim = CppSparseSimRs(2) + + # Test single qubit gates + sim.run_gate("X", {0}) + sim.run_gate("Y", {1}) + sim.run_gate("Z", {0}) + sim.run_gate("H", {0}) + + # Test two qubit gates + sim.run_gate("CX", {(0, 1)}) + sim.run_gate("CZ", {(0, 1)}) + + # Reset and test again + sim.reset() + sim.run_gate("H", {0}) + sim.run_gate("CX", {(0, 1)}) + + +def test_measurements() -> None: + """Test measurement operations.""" + sim = CppSparseSimRs(3) + + # Measure in computational basis (should get 0) + result = sim.run_gate("MZ", {0}) + assert result[0] == 0 + + # Apply X then measure (should get 1) + sim.run_gate("X", {1}) + result = sim.run_gate("MZ", {1}) + assert result[1] == 1 + + # Test measurement after H (random but deterministic with fixed seed) + sim.reset() + sim.run_gate("H", {0}) + result = sim.run_gate("MZ", {0}) + assert result[0] in [0, 1] + + +def test_bell_state() -> None: + """Test creating and measuring Bell states.""" + sim = CppSparseSimRs(2) + + # Create |00> + |11> Bell state + sim.run_gate("H", {0}) + sim.run_gate("CX", {(0, 1)}) + + # Measure both qubits - they should be correlated + result0 = sim.run_gate("MZ", {0}) + result1 = sim.run_gate("MZ", {1}) + assert result0[0] == result1[1] + + +def test_ghz_state() -> None: + """Test creating and measuring GHZ states.""" + sim = CppSparseSimRs(3) + + # Create |000> + |111> GHZ state + sim.run_gate("H", {0}) + sim.run_gate("CX", {(0, 1)}) + sim.run_gate("CX", {(0, 2)}) + + # Measure all qubits - they should all be the same + result0 = sim.run_gate("MZ", {0}) + result1 = sim.run_gate("MZ", {1}) + result2 = sim.run_gate("MZ", {2}) + assert result0[0] == result1[1] == result2[2] + + +def test_circuit_execution() -> None: + """Test running a simple circuit.""" + sim = CppSparseSimRs(4) + + # Define a simple circuit + circuit = [ + ("H", {0, 1}), + ("CX", {(0, 2)}), + ("CX", {(1, 3)}), + ("H", {2, 3}), + ] + + # Run the circuit + for gate, qubits in circuit: + sim.run_gate(gate, qubits) + + # The circuit should execute without errors + # We're not checking the state, just that it runs + + +def test_reset() -> None: + """Test reset functionality.""" + sim = CppSparseSimRs(2) + + # Apply some gates + sim.run_gate("X", {0}) + sim.run_gate("H", {1}) + sim.run_gate("CX", {(0, 1)}) + + # Reset + sim.reset() + + # After reset, measuring should give |00> + result0 = sim.run_gate("MZ", {0}) + result1 = sim.run_gate("MZ", {1}) + assert result0[0] == 0 + assert result1[1] == 0 + + +@pytest.mark.parametrize("num_qubits", [1, 2, 3, 5, 10]) +def test_various_sizes(num_qubits: int) -> None: + """Test simulator with various numbers of qubits.""" + sim = CppSparseSimRs(num_qubits) + + # Apply some gates + for i in range(num_qubits): + sim.run_gate("H", {i}) + + # Apply CNOT chain + for i in range(num_qubits - 1): + sim.run_gate("CX", {(i, i + 1)}) + + # Measure all qubits + results = {} + for i in range(num_qubits): + result = sim.run_gate("MZ", {i}) + results.update(result) + + # Check all were measured + assert len(results) == num_qubits + + # Check that all results are valid (0 or 1) + for i in range(num_qubits): + assert results[i] in [0, 1] diff --git a/python/tests/pecos/integration/test_random_circuits.py b/python/tests/pecos/integration/test_random_circuits.py index 47bff3579..ef6023dfd 100644 --- a/python/tests/pecos/integration/test_random_circuits.py +++ b/python/tests/pecos/integration/test_random_circuits.py @@ -17,7 +17,7 @@ from typing import Any import numpy as np -from pecos.simulators import SparseSimPy, SparseSimRs +from pecos.simulators import CppSparseSimRs, SparseSimPy, SparseSimRs def test_random_circuits() -> None: @@ -61,6 +61,7 @@ def test_random_circuits() -> None: state_sims.append(SparseSimPy) state_sims.append(SparseSimRs) + state_sims.append(CppSparseSimRs) assert run_circuit_test(state_sims, num_qubits=10, circuit_depth=50) @@ -81,16 +82,31 @@ def run_circuit_test( circuit = generate_circuit(gates, num_qubits, circuit_depth) measurements = [] - for state_sim in state_sims: + for i, state_sim in enumerate(state_sims): np.random.seed(seed) - meas = run_a_circuit(num_qubits, state_sim, circuit) - + verbose = ( + seed == 32 and state_sim.__name__ == "CppSparseSimRs" + ) # Debug failing case + meas = run_a_circuit( + num_qubits, + state_sim, + circuit, + _test_seed=seed, + verbose=verbose, + ) + if seed == 32: + print( + f"Simulator {i} ({state_sim.__name__}): {meas[:20]}...", + ) # Show first 20 measurements measurements.append(meas) meas0 = measurements[0] - for meas in measurements[1:]: + for i, meas in enumerate(measurements[1:], 1): if meas0 != meas: print("seed=", seed) + print("Simulator 0 measurements:", meas0) + print(f"Simulator {i} measurements:", meas) + print(f"Simulator types: {[type(s).__name__ for s in state_sims]}") print(circuit) return False @@ -130,20 +146,34 @@ def run_a_circuit( circuit: list[tuple[str, int | np.ndarray]], *, verbose: bool = False, + _test_seed: int | None = None, # Unused - kept for API compatibility ) -> list[int]: """Run a quantum circuit on a specific simulator and return measurements.""" state = state_rep(num_qubits) measurements = [] - if isinstance(state, SparseSimRs): + if isinstance(state, SparseSimRs | CppSparseSimRs): state.bindings["measure Z"] = state.bindings["MZForced"] - state.bindings["init |0>"] = state.bindings["PZForced"] + state.bindings["init |0>"] = state.bindings.get( + "PZForced", + state.bindings.get("init |0>"), + ) + # Don't set seed for C++ simulator - use numpy random for forced outcomes instead + # if isinstance(state, CppSparseSimRs) and hasattr(state, 'set_seed') and test_seed is not None: + # # Use the test seed directly for C++ RNG + # state.set_seed(test_seed) - for element, q in circuit: + for i, (element, q) in enumerate(circuit): m = -1 if element == "measure Z": + if ( + verbose and isinstance(state, CppSparseSimRs) and i == 26 + ): # Debug the 27th operation + print(f"\n[DEBUG] Op {i}: {element} on qubit {q}, forcing outcome to 0") m = state.run_gate(element, {q}, forced_outcome=0) m = m.get(q, 0) + if verbose and isinstance(state, CppSparseSimRs) and i == 26: + print(f"[DEBUG] Result: {m}\n") measurements.append(m) elif element == "init |0>": From 1165e801b078a5e8e8e388267cacb8fe51daf003 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 28 Aug 2025 13:25:14 -0600 Subject: [PATCH 2/5] fix for cpp version compatibility --- .github/workflows/python-release.yml | 15 +++++++- crates/pecos-cppsparsesim/build.rs | 47 +++++++++++++++++++----- crates/pecos-ldpc-decoders/build_ldpc.rs | 16 +++++++- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index 0bf89393e..df42fd5e6 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -91,6 +91,11 @@ jobs: - name: Build wheel uses: PyO3/maturin-action@v1 + env: + # Set C++14 standard for cross-compilation compatibility + CXXFLAGS: "-std=c++14" + CXX_aarch64_unknown_linux_gnu: "aarch64-linux-gnu-g++" + CXXFLAGS_aarch64_unknown_linux_gnu: "-std=c++14" with: command: build args: --release --out dist --interpreter python3.10 @@ -98,10 +103,16 @@ jobs: target: ${{ matrix.architecture == 'aarch64' && (matrix.os == 'macos-latest' && 'aarch64-apple-darwin' || 'aarch64-unknown-linux-gnu') || (matrix.os == 'macos-latest' && 'x86_64-apple-darwin' || '') }} manylinux: auto before-script-linux: | + # Install dependencies and ensure modern C++ compiler if command -v yum &> /dev/null; then - yum install -y openssl-devel + yum install -y openssl-devel gcc-c++ libstdc++-devel + # For manylinux, install newer gcc if needed + if [ "${{ matrix.architecture }}" = "aarch64" ]; then + yum install -y devtoolset-10-gcc-c++ || true + source /opt/rh/devtoolset-10/enable 2>/dev/null || true + fi elif command -v apt-get &> /dev/null; then - apt-get update && apt-get install -y libssl-dev pkg-config + apt-get update && apt-get install -y libssl-dev pkg-config g++ libstdc++-11-dev else echo "No supported package manager found" exit 1 diff --git a/crates/pecos-cppsparsesim/build.rs b/crates/pecos-cppsparsesim/build.rs index 914ddb48b..4da15a203 100644 --- a/crates/pecos-cppsparsesim/build.rs +++ b/crates/pecos-cppsparsesim/build.rs @@ -1,18 +1,47 @@ fn main() { // Build C++ source files - cc::Build::new() + let mut build = cc::Build::new(); + build .cpp(true) .file("src/sparsesim.cpp") .file("src/cxx_shim.cpp") - .include("src") - .std("c++14") - .compile("sparsesim"); + .include("src"); + + // Use C++14 or newer to avoid issues with older cross-compilers + // that don't fully support C++11 type traits like is_trivially_move_constructible + let target = std::env::var("TARGET").unwrap_or_default(); + + // For cross-compilation (especially aarch64), we need at least C++14 + // to ensure type traits are available + if target.contains("aarch64") || target.contains("arm") { + // Try C++17 first, fall back to C++14 + if !build.is_flag_supported("-std=c++17").unwrap_or(false) { + build.std("c++14"); + } else { + build.std("c++17"); + } + } else { + build.std("c++14"); + } + + build.compile("sparsesim"); - // Generate cxx bridge code - cxx_build::bridge("src/lib.rs") - .file("src/cxx_shim.cpp") - .std("c++14") - .compile("cppsparsesim-bridge"); + // Generate cxx bridge code with same C++ standard + let mut bridge = cxx_build::bridge("src/lib.rs"); + bridge.file("src/cxx_shim.cpp"); + + // Match the same C++ standard for cxx bridge + if target.contains("aarch64") || target.contains("arm") { + if !bridge.is_flag_supported("-std=c++17").unwrap_or(false) { + bridge.std("c++14"); + } else { + bridge.std("c++17"); + } + } else { + bridge.std("c++14"); + } + + bridge.compile("cppsparsesim-bridge"); // Tell cargo to rerun if source files change println!("cargo:rerun-if-changed=src/lib.rs"); diff --git a/crates/pecos-ldpc-decoders/build_ldpc.rs b/crates/pecos-ldpc-decoders/build_ldpc.rs index 0fa659507..6ce247169 100644 --- a/crates/pecos-ldpc-decoders/build_ldpc.rs +++ b/crates/pecos-ldpc-decoders/build_ldpc.rs @@ -191,12 +191,26 @@ fn build_cxx_bridge(ldpc_dir: &Path) -> Result<()> { let mut build = cxx_build::bridge("src/bridge.rs"); build .file("src/bridge.cpp") - .std("c++17") .include(&src_cpp_dir) .include(&include_dir) .include(include_dir.join("robin_map")) .include(include_dir.join("rapidcsv")) .include("include"); + + // Use C++17 when available, fall back to C++14 for older compilers + // This helps with cross-compilation where older toolchains may not fully support C++17 + let target = env::var("TARGET").unwrap_or_default(); + if target.contains("aarch64") || target.contains("arm") { + // For ARM targets, check what's supported + if !build.is_flag_supported("-std=c++17").unwrap_or(false) { + build.std("c++14"); + } else { + build.std("c++17"); + } + } else { + // For other targets, use C++17 + build.std("c++17"); + } // Report ccache/sccache configuration report_cache_config(); From 754b5f88cc2a3e5c97d727e595f6f368c8e6bcc2 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 28 Aug 2025 13:28:00 -0600 Subject: [PATCH 3/5] lint --- crates/pecos-cppsparsesim/build.rs | 22 +++++++++++----------- crates/pecos-ldpc-decoders/build_ldpc.rs | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/pecos-cppsparsesim/build.rs b/crates/pecos-cppsparsesim/build.rs index 4da15a203..e7c05b67b 100644 --- a/crates/pecos-cppsparsesim/build.rs +++ b/crates/pecos-cppsparsesim/build.rs @@ -6,41 +6,41 @@ fn main() { .file("src/sparsesim.cpp") .file("src/cxx_shim.cpp") .include("src"); - + // Use C++14 or newer to avoid issues with older cross-compilers // that don't fully support C++11 type traits like is_trivially_move_constructible let target = std::env::var("TARGET").unwrap_or_default(); - + // For cross-compilation (especially aarch64), we need at least C++14 // to ensure type traits are available if target.contains("aarch64") || target.contains("arm") { // Try C++17 first, fall back to C++14 - if !build.is_flag_supported("-std=c++17").unwrap_or(false) { - build.std("c++14"); - } else { + if build.is_flag_supported("-std=c++17").unwrap_or(false) { build.std("c++17"); + } else { + build.std("c++14"); } } else { build.std("c++14"); } - + build.compile("sparsesim"); // Generate cxx bridge code with same C++ standard let mut bridge = cxx_build::bridge("src/lib.rs"); bridge.file("src/cxx_shim.cpp"); - + // Match the same C++ standard for cxx bridge if target.contains("aarch64") || target.contains("arm") { - if !bridge.is_flag_supported("-std=c++17").unwrap_or(false) { - bridge.std("c++14"); - } else { + if bridge.is_flag_supported("-std=c++17").unwrap_or(false) { bridge.std("c++17"); + } else { + bridge.std("c++14"); } } else { bridge.std("c++14"); } - + bridge.compile("cppsparsesim-bridge"); // Tell cargo to rerun if source files change diff --git a/crates/pecos-ldpc-decoders/build_ldpc.rs b/crates/pecos-ldpc-decoders/build_ldpc.rs index 6ce247169..d7c8c29b5 100644 --- a/crates/pecos-ldpc-decoders/build_ldpc.rs +++ b/crates/pecos-ldpc-decoders/build_ldpc.rs @@ -196,16 +196,16 @@ fn build_cxx_bridge(ldpc_dir: &Path) -> Result<()> { .include(include_dir.join("robin_map")) .include(include_dir.join("rapidcsv")) .include("include"); - + // Use C++17 when available, fall back to C++14 for older compilers // This helps with cross-compilation where older toolchains may not fully support C++17 let target = env::var("TARGET").unwrap_or_default(); if target.contains("aarch64") || target.contains("arm") { // For ARM targets, check what's supported - if !build.is_flag_supported("-std=c++17").unwrap_or(false) { - build.std("c++14"); - } else { + if build.is_flag_supported("-std=c++17").unwrap_or(false) { build.std("c++17"); + } else { + build.std("c++14"); } } else { // For other targets, use C++17 From 329ddaa5fd1c986b9355d1efebf217de614d418e Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 28 Aug 2025 13:47:06 -0600 Subject: [PATCH 4/5] fix workflow? --- .github/workflows/python-release.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index df42fd5e6..eba3c6c21 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -91,28 +91,18 @@ jobs: - name: Build wheel uses: PyO3/maturin-action@v1 - env: - # Set C++14 standard for cross-compilation compatibility - CXXFLAGS: "-std=c++14" - CXX_aarch64_unknown_linux_gnu: "aarch64-linux-gnu-g++" - CXXFLAGS_aarch64_unknown_linux_gnu: "-std=c++14" with: command: build - args: --release --out dist --interpreter python3.10 + args: --release --out dist --interpreter python3.10 ${{ matrix.architecture == 'aarch64' && '--zig' || '' }} working-directory: python/pecos-rslib target: ${{ matrix.architecture == 'aarch64' && (matrix.os == 'macos-latest' && 'aarch64-apple-darwin' || 'aarch64-unknown-linux-gnu') || (matrix.os == 'macos-latest' && 'x86_64-apple-darwin' || '') }} manylinux: auto before-script-linux: | - # Install dependencies and ensure modern C++ compiler + # Install basic dependencies if command -v yum &> /dev/null; then - yum install -y openssl-devel gcc-c++ libstdc++-devel - # For manylinux, install newer gcc if needed - if [ "${{ matrix.architecture }}" = "aarch64" ]; then - yum install -y devtoolset-10-gcc-c++ || true - source /opt/rh/devtoolset-10/enable 2>/dev/null || true - fi + yum install -y openssl-devel elif command -v apt-get &> /dev/null; then - apt-get update && apt-get install -y libssl-dev pkg-config g++ libstdc++-11-dev + apt-get update && apt-get install -y libssl-dev pkg-config else echo "No supported package manager found" exit 1 From 1f0cd08e6d3a61e56ecb30f893a4e372d88b2662 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 28 Aug 2025 14:09:41 -0600 Subject: [PATCH 5/5] fix --- .github/workflows/python-release.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index eba3c6c21..18d30fcf6 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -97,16 +97,6 @@ jobs: working-directory: python/pecos-rslib target: ${{ matrix.architecture == 'aarch64' && (matrix.os == 'macos-latest' && 'aarch64-apple-darwin' || 'aarch64-unknown-linux-gnu') || (matrix.os == 'macos-latest' && 'x86_64-apple-darwin' || '') }} manylinux: auto - before-script-linux: | - # Install basic dependencies - if command -v yum &> /dev/null; then - yum install -y openssl-devel - elif command -v apt-get &> /dev/null; then - apt-get update && apt-get install -y libssl-dev pkg-config - else - echo "No supported package manager found" - exit 1 - fi - name: Restore README.md if: always()