Skip to content

Commit e721f8a

Browse files
authored
Moving the C++ implementation of stabilizer sims from being wrapped i… (#183)
* Moving the C++ implementation of stabilizer sims from being wrapped in Cython to Rust/PyO3 * fix for cpp version compatibility
1 parent a5a7d90 commit e721f8a

60 files changed

Lines changed: 2482 additions & 7346 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/python-release.yml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,10 @@ jobs:
9393
uses: PyO3/maturin-action@v1
9494
with:
9595
command: build
96-
args: --release --out dist --interpreter python3.10
96+
args: --release --out dist --interpreter python3.10 ${{ matrix.architecture == 'aarch64' && '--zig' || '' }}
9797
working-directory: python/pecos-rslib
9898
target: ${{ matrix.architecture == 'aarch64' && (matrix.os == 'macos-latest' && 'aarch64-apple-darwin' || 'aarch64-unknown-linux-gnu') || (matrix.os == 'macos-latest' && 'x86_64-apple-darwin' || '') }}
9999
manylinux: auto
100-
before-script-linux: |
101-
if command -v yum &> /dev/null; then
102-
yum install -y openssl-devel
103-
elif command -v apt-get &> /dev/null; then
104-
apt-get update && apt-get install -y libssl-dev pkg-config
105-
else
106-
echo "No supported package manager found"
107-
exit 1
108-
fi
109100

110101
- name: Restore README.md
111102
if: always()

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "pecos-cppsparsesim"
3+
version.workspace = true
4+
edition.workspace = true
5+
authors.workspace = true
6+
homepage.workspace = true
7+
repository.workspace = true
8+
license.workspace = true
9+
keywords.workspace = true
10+
categories.workspace = true
11+
description = "C++ sparse stabilizer simulator bindings for PECOS"
12+
readme = "README.md"
13+
14+
[dependencies]
15+
cxx.workspace = true
16+
pecos-core.workspace = true
17+
pecos-qsim.workspace = true
18+
rand.workspace = true
19+
rand_chacha.workspace = true
20+
21+
[build-dependencies]
22+
cxx-build.workspace = true
23+
cc.workspace = true
24+
25+
[lints]
26+
workspace = true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# pecos-cppsparsesim
2+
3+
C++ sparse stabilizer simulator bindings for PECOS.
4+
5+
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.
6+
7+
## Features
8+
9+
- Efficient sparse representation of stabilizer tableaux
10+
- Support for all Clifford gates
11+
- Compatible with PECOS quantum simulation framework
12+
13+
## Usage
14+
15+
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.
16+
17+
## License
18+
19+
Licensed under the Apache License, Version 2.0. See LICENSE for details.

crates/pecos-cppsparsesim/build.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
fn main() {
2+
// Build C++ source files
3+
let mut build = cc::Build::new();
4+
build
5+
.cpp(true)
6+
.file("src/sparsesim.cpp")
7+
.file("src/cxx_shim.cpp")
8+
.include("src");
9+
10+
// Use C++14 or newer to avoid issues with older cross-compilers
11+
// that don't fully support C++11 type traits like is_trivially_move_constructible
12+
let target = std::env::var("TARGET").unwrap_or_default();
13+
14+
// For cross-compilation (especially aarch64), we need at least C++14
15+
// to ensure type traits are available
16+
if target.contains("aarch64") || target.contains("arm") {
17+
// Try C++17 first, fall back to C++14
18+
if build.is_flag_supported("-std=c++17").unwrap_or(false) {
19+
build.std("c++17");
20+
} else {
21+
build.std("c++14");
22+
}
23+
} else {
24+
build.std("c++14");
25+
}
26+
27+
build.compile("sparsesim");
28+
29+
// Generate cxx bridge code with same C++ standard
30+
let mut bridge = cxx_build::bridge("src/lib.rs");
31+
bridge.file("src/cxx_shim.cpp");
32+
33+
// Match the same C++ standard for cxx bridge
34+
if target.contains("aarch64") || target.contains("arm") {
35+
if bridge.is_flag_supported("-std=c++17").unwrap_or(false) {
36+
bridge.std("c++17");
37+
} else {
38+
bridge.std("c++14");
39+
}
40+
} else {
41+
bridge.std("c++14");
42+
}
43+
44+
bridge.compile("cppsparsesim-bridge");
45+
46+
// Tell cargo to rerun if source files change
47+
println!("cargo:rerun-if-changed=src/lib.rs");
48+
println!("cargo:rerun-if-changed=src/sparsesim.cpp");
49+
println!("cargo:rerun-if-changed=src/sparsesim.h");
50+
println!("cargo:rerun-if-changed=src/cxx_shim.cpp");
51+
println!("cargo:rerun-if-changed=src/cxx_shim.h");
52+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright 2025 The PECOS Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4+
// in compliance with the License.You may obtain a copy of the License at
5+
//
6+
// https://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software distributed under the License
9+
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10+
// or implied. See the License for the specific language governing permissions and limitations under
11+
// the License.
12+
13+
#include "cxx_shim.h"
14+
15+
StateWrapper::StateWrapper(std::uint64_t num_qubits, std::int32_t reserve_buckets)
16+
: state(static_cast<int_num>(num_qubits), static_cast<int>(reserve_buckets)) {}
17+
18+
void StateWrapper::set_seed(std::uint32_t seed) {
19+
// Set the instance RNG seed
20+
state.set_seed(static_cast<unsigned int>(seed));
21+
}
22+
23+
void StateWrapper::clear() {
24+
state.clear();
25+
}
26+
27+
void StateWrapper::hadamard(std::uint64_t qubit) {
28+
state.hadamard(static_cast<int_num>(qubit));
29+
}
30+
31+
void StateWrapper::bitflip(std::uint64_t qubit) {
32+
state.bitflip(static_cast<int_num>(qubit));
33+
}
34+
35+
void StateWrapper::phaseflip(std::uint64_t qubit) {
36+
state.phaseflip(static_cast<int_num>(qubit));
37+
}
38+
39+
void StateWrapper::Y(std::uint64_t qubit) {
40+
state.Y(static_cast<int_num>(qubit));
41+
}
42+
43+
void StateWrapper::phaserot(std::uint64_t qubit) {
44+
state.phaserot(static_cast<int_num>(qubit));
45+
}
46+
47+
void StateWrapper::SZdg(std::uint64_t qubit) {
48+
state.SZdg(static_cast<int_num>(qubit));
49+
}
50+
51+
void StateWrapper::SY(std::uint64_t qubit) {
52+
state.SY(static_cast<int_num>(qubit));
53+
}
54+
55+
void StateWrapper::SYdg(std::uint64_t qubit) {
56+
state.SYdg(static_cast<int_num>(qubit));
57+
}
58+
59+
void StateWrapper::SX(std::uint64_t qubit) {
60+
state.SX(static_cast<int_num>(qubit));
61+
}
62+
63+
void StateWrapper::SXdg(std::uint64_t qubit) {
64+
state.SXdg(static_cast<int_num>(qubit));
65+
}
66+
67+
void StateWrapper::H2(std::uint64_t qubit) {
68+
state.H2(static_cast<int_num>(qubit));
69+
}
70+
71+
void StateWrapper::H3(std::uint64_t qubit) {
72+
state.H3(static_cast<int_num>(qubit));
73+
}
74+
75+
void StateWrapper::H4(std::uint64_t qubit) {
76+
state.H4(static_cast<int_num>(qubit));
77+
}
78+
79+
void StateWrapper::H5(std::uint64_t qubit) {
80+
state.H5(static_cast<int_num>(qubit));
81+
}
82+
83+
void StateWrapper::H6(std::uint64_t qubit) {
84+
state.H6(static_cast<int_num>(qubit));
85+
}
86+
87+
void StateWrapper::F(std::uint64_t qubit) {
88+
state.F(static_cast<int_num>(qubit));
89+
}
90+
91+
void StateWrapper::F2(std::uint64_t qubit) {
92+
state.F2(static_cast<int_num>(qubit));
93+
}
94+
95+
void StateWrapper::F3(std::uint64_t qubit) {
96+
state.F3(static_cast<int_num>(qubit));
97+
}
98+
99+
void StateWrapper::F4(std::uint64_t qubit) {
100+
state.F4(static_cast<int_num>(qubit));
101+
}
102+
103+
void StateWrapper::Fdg(std::uint64_t qubit) {
104+
state.Fdg(static_cast<int_num>(qubit));
105+
}
106+
107+
void StateWrapper::F2dg(std::uint64_t qubit) {
108+
state.F2dg(static_cast<int_num>(qubit));
109+
}
110+
111+
void StateWrapper::F3dg(std::uint64_t qubit) {
112+
state.F3dg(static_cast<int_num>(qubit));
113+
}
114+
115+
void StateWrapper::F4dg(std::uint64_t qubit) {
116+
state.F4dg(static_cast<int_num>(qubit));
117+
}
118+
119+
void StateWrapper::cx(std::uint64_t control, std::uint64_t target) {
120+
// The C++ cx function uses confusing parameter names but actually expects (control, target)
121+
state.cx(static_cast<int_num>(control), static_cast<int_num>(target));
122+
}
123+
124+
void StateWrapper::cy(std::uint64_t control, std::uint64_t target) {
125+
// CY = (I ⊗ SYdg) CX (I ⊗ SY)
126+
state.SYdg(static_cast<int_num>(target));
127+
state.cx(static_cast<int_num>(control), static_cast<int_num>(target));
128+
state.SY(static_cast<int_num>(target));
129+
}
130+
131+
void StateWrapper::cz(std::uint64_t qubit1, std::uint64_t qubit2) {
132+
// CZ = H(qubit2) CX(qubit1, qubit2) H(qubit2)
133+
state.hadamard(static_cast<int_num>(qubit2));
134+
state.cx(static_cast<int_num>(qubit1), static_cast<int_num>(qubit2));
135+
state.hadamard(static_cast<int_num>(qubit2));
136+
}
137+
138+
void StateWrapper::swap(std::uint64_t qubit1, std::uint64_t qubit2) {
139+
state.swap(static_cast<int_num>(qubit1), static_cast<int_num>(qubit2));
140+
}
141+
142+
void StateWrapper::g2(std::uint64_t qubit1, std::uint64_t qubit2) {
143+
// G2 gate decomposition: H(q1), CX(q2, q1), CX(q1, q2), H(q2)
144+
state.hadamard(static_cast<int_num>(qubit1));
145+
state.cx(static_cast<int_num>(qubit2), static_cast<int_num>(qubit1));
146+
state.cx(static_cast<int_num>(qubit1), static_cast<int_num>(qubit2));
147+
state.hadamard(static_cast<int_num>(qubit2));
148+
}
149+
150+
void StateWrapper::sxx(std::uint64_t qubit1, std::uint64_t qubit2) {
151+
// SXX = SX(q1).SX(q2).SYdg(q1).CX(q1, q2).SY(q1)
152+
state.SX(static_cast<int_num>(qubit1));
153+
state.SX(static_cast<int_num>(qubit2));
154+
state.SYdg(static_cast<int_num>(qubit1));
155+
state.cx(static_cast<int_num>(qubit1), static_cast<int_num>(qubit2));
156+
state.SY(static_cast<int_num>(qubit1));
157+
}
158+
159+
void StateWrapper::sxxdg(std::uint64_t qubit1, std::uint64_t qubit2) {
160+
// SXXdg = X(q1).X(q2).SXX(q1, q2)
161+
state.bitflip(static_cast<int_num>(qubit1));
162+
state.bitflip(static_cast<int_num>(qubit2));
163+
sxx(qubit1, qubit2); // Call the wrapper's sxx implementation
164+
}
165+
166+
std::uint32_t StateWrapper::measure(std::uint64_t qubit, std::int32_t forced_outcome, bool collapse) {
167+
// Simple wrapper - just return the measurement outcome
168+
unsigned int outcome = state.measure(static_cast<int_num>(qubit), static_cast<int>(forced_outcome), collapse);
169+
return static_cast<std::uint32_t>(outcome);
170+
}
171+
172+
std::uint64_t StateWrapper::get_num_qubits() const {
173+
return static_cast<std::uint64_t>(state.num_qubits);
174+
}
175+
176+
bool StateWrapper::has_stab_x(std::uint64_t gen_id, std::uint64_t qubit) const {
177+
const auto& row_set = state.stabs.row_x[static_cast<int_num>(gen_id)];
178+
return row_set.count(static_cast<int_num>(qubit)) > 0;
179+
}
180+
181+
bool StateWrapper::has_stab_z(std::uint64_t gen_id, std::uint64_t qubit) const {
182+
const auto& row_set = state.stabs.row_z[static_cast<int_num>(gen_id)];
183+
return row_set.count(static_cast<int_num>(qubit)) > 0;
184+
}
185+
186+
bool StateWrapper::has_destab_x(std::uint64_t gen_id, std::uint64_t qubit) const {
187+
const auto& row_set = state.destabs.row_x[static_cast<int_num>(gen_id)];
188+
return row_set.count(static_cast<int_num>(qubit)) > 0;
189+
}
190+
191+
bool StateWrapper::has_destab_z(std::uint64_t gen_id, std::uint64_t qubit) const {
192+
const auto& row_set = state.destabs.row_z[static_cast<int_num>(gen_id)];
193+
return row_set.count(static_cast<int_num>(qubit)) > 0;
194+
}
195+
196+
bool StateWrapper::get_sign_minus(std::uint64_t gen_id) const {
197+
return state.signs_minus.count(static_cast<int_num>(gen_id)) > 0;
198+
}
199+
200+
bool StateWrapper::get_sign_i(std::uint64_t gen_id) const {
201+
return state.signs_i.count(static_cast<int_num>(gen_id)) > 0;
202+
}
203+
204+
// Factory function
205+
std::unique_ptr<StateWrapper> create_state_wrapper(std::uint64_t num_qubits, std::int32_t reserve_buckets) {
206+
return std::make_unique<StateWrapper>(num_qubits, reserve_buckets);
207+
}

0 commit comments

Comments
 (0)