Skip to content

Commit 4b1915d

Browse files
authored
exposing stabilizer seeding for the Python wrappers (#275)
1 parent 8736c84 commit 4b1915d

8 files changed

Lines changed: 190 additions & 30 deletions

File tree

.github/dependabot.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
version: 2
2+
3+
updates:
4+
# Rust (Cargo) - security updates only
5+
- package-ecosystem: "cargo"
6+
directory: "/"
7+
schedule:
8+
interval: "weekly"
9+
open-pull-requests-limit: 5
10+
groups:
11+
rust-security:
12+
applies-to: security-updates
13+
patterns: ["*"]
14+
# Only open PRs for security advisories
15+
allow:
16+
- dependency-type: "all"
17+
# Ignore all version updates (non-security)
18+
ignore:
19+
- dependency-name: "*"
20+
update-types: ["version-update:semver-patch", "version-update:semver-minor", "version-update:semver-major"]
21+
22+
# Python (pip) - security updates only
23+
- package-ecosystem: "pip"
24+
directory: "/"
25+
schedule:
26+
interval: "weekly"
27+
open-pull-requests-limit: 5
28+
groups:
29+
python-security:
30+
applies-to: security-updates
31+
patterns: ["*"]
32+
ignore:
33+
- dependency-name: "*"
34+
update-types: ["version-update:semver-patch", "version-update:semver-minor", "version-update:semver-major"]
35+
36+
# GitHub Actions - security updates only
37+
- package-ecosystem: "github-actions"
38+
directory: "/"
39+
schedule:
40+
interval: "weekly"
41+
open-pull-requests-limit: 5
42+
groups:
43+
actions-security:
44+
applies-to: security-updates
45+
patterns: ["*"]
46+
ignore:
47+
- dependency-name: "*"
48+
update-types: ["version-update:semver-patch", "version-update:semver-minor", "version-update:semver-major"]

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/pecos-rslib/pecos_rslib.pyi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,8 @@ class GateBindingsDict:
771771
class SparseSim:
772772
"""Sparse stabilizer simulator."""
773773

774-
def __init__(self, num_qubits: int) -> None: ...
774+
def __init__(self, num_qubits: int, seed: int | None = None) -> None: ...
775+
def set_seed(self, seed: int) -> None: ...
775776
def reset(self) -> SparseSim: ...
776777
@property
777778
def num_qubits(self) -> int: ...
@@ -788,7 +789,8 @@ class SparseSim:
788789
class Stab:
789790
"""Generic stabilizer simulator (recommended)."""
790791

791-
def __init__(self, num_qubits: int) -> None: ...
792+
def __init__(self, num_qubits: int, seed: int | None = None) -> None: ...
793+
def set_seed(self, seed: int) -> None: ...
792794
def reset(self) -> Stab: ...
793795
@property
794796
def num_qubits(self) -> int: ...

python/pecos-rslib/src/sparse_stab_bindings.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,20 @@ pub struct PySparseSim {
2424
#[pymethods]
2525
impl PySparseSim {
2626
#[new]
27-
fn new(num_qubits: usize) -> Self {
27+
#[pyo3(signature = (num_qubits, seed=None))]
28+
fn new(num_qubits: usize, seed: Option<u64>) -> Self {
2829
PySparseSim {
29-
inner: SparseStab::new(num_qubits),
30+
inner: match seed {
31+
Some(s) => SparseStab::with_seed(num_qubits, s),
32+
None => SparseStab::new(num_qubits),
33+
},
3034
}
3135
}
3236

37+
fn set_seed(&mut self, seed: u64) {
38+
self.inner.set_seed(seed);
39+
}
40+
3341
fn reset(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
3442
slf.inner.reset();
3543
slf

python/pecos-rslib/src/stab_bindings.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,20 @@ pub struct PyStab {
2424
#[pymethods]
2525
impl PyStab {
2626
#[new]
27-
fn new(num_qubits: usize) -> Self {
27+
#[pyo3(signature = (num_qubits, seed=None))]
28+
fn new(num_qubits: usize, seed: Option<u64>) -> Self {
2829
PyStab {
29-
inner: Stab::new(num_qubits),
30+
inner: match seed {
31+
Some(s) => Stab::with_seed(num_qubits, s),
32+
None => Stab::new(num_qubits),
33+
},
3034
}
3135
}
3236

37+
fn set_seed(&mut self, seed: u64) {
38+
self.inner.set_seed(seed);
39+
}
40+
3341
fn reset(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
3442
slf.inner.reset();
3543
slf
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2026 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+
"""Tests for simulator seeding via the pecos_rslib bindings."""
14+
15+
import pytest
16+
from pecos_rslib.simulators import SparseSim, Stab
17+
18+
19+
def _measure_sequence(sim_cls: type, *, seed: int, rounds: int = 16) -> list:
20+
"""Create a seeded simulator, apply H then MZ repeatedly, return outcomes."""
21+
sim = sim_cls(1, seed=seed)
22+
outcomes = []
23+
for _ in range(rounds):
24+
sim.reset()
25+
sim.run_1q_gate("H", 0)
26+
outcomes.append(sim.run_1q_gate("MZ", 0))
27+
return outcomes
28+
29+
30+
@pytest.mark.parametrize("sim_cls", [SparseSim, Stab])
31+
class TestSimulatorSeeding:
32+
"""Verify that seeded stabilizer simulators produce reproducible results."""
33+
34+
def test_constructor_seed_is_reproducible(self, sim_cls: type) -> None:
35+
"""Same seed in constructor gives same measurement sequence."""
36+
assert _measure_sequence(sim_cls, seed=42) == _measure_sequence(sim_cls, seed=42)
37+
38+
def test_different_seeds_differ(self, sim_cls: type) -> None:
39+
"""Different seeds give different measurement sequences."""
40+
assert _measure_sequence(sim_cls, seed=42) != _measure_sequence(sim_cls, seed=99)
41+
42+
def test_set_seed_is_reproducible(self, sim_cls: type) -> None:
43+
"""Calling set_seed after construction gives reproducible results."""
44+
sim_a = sim_cls(1)
45+
sim_a.set_seed(42)
46+
outcomes_a = [sim_a.run_1q_gate("H", 0) for _ in range(8)]
47+
48+
sim_b = sim_cls(1)
49+
sim_b.set_seed(42)
50+
outcomes_b = [sim_b.run_1q_gate("H", 0) for _ in range(8)]
51+
52+
assert outcomes_a == outcomes_b
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2026 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+
"""High-level tests for seeded stabilizer simulator re-exports."""
14+
15+
import pytest
16+
from pecos.simulators import SparseSim, Stab
17+
18+
19+
def _measurement_sequence(
20+
sim_cls: type,
21+
*,
22+
seed: int | None = None,
23+
reseed: int | None = None,
24+
rounds: int = 16,
25+
) -> list:
26+
sim = sim_cls(1, seed=seed) if seed is not None else sim_cls(1)
27+
if reseed is not None:
28+
sim.set_seed(reseed)
29+
30+
outcomes = []
31+
for _ in range(rounds):
32+
sim.reset()
33+
sim.run_1q_gate("H", 0)
34+
outcomes.append(sim.run_1q_gate("MZ", 0))
35+
return outcomes
36+
37+
38+
@pytest.mark.parametrize("sim_cls", [SparseSim, Stab])
39+
def test_high_level_simulators_accept_seed_and_set_seed(sim_cls: type) -> None:
40+
"""Verify that seeded stabilizer simulators produce reproducible results."""
41+
assert _measurement_sequence(sim_cls, seed=42) == _measurement_sequence(sim_cls, seed=42)
42+
assert _measurement_sequence(sim_cls, reseed=42) == _measurement_sequence(sim_cls, reseed=42)

uv.lock

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)