diff --git a/python/pecos-rslib/pecos_rslib.pyi b/python/pecos-rslib/pecos_rslib.pyi index 98f02a403..8c5790f15 100644 --- a/python/pecos-rslib/pecos_rslib.pyi +++ b/python/pecos-rslib/pecos_rslib.pyi @@ -771,8 +771,9 @@ class GateBindingsDict: class SparseSim: """Sparse stabilizer simulator.""" - def __init__(self, num_qubits: int) -> None: ... + def __init__(self, num_qubits: int, seed: int | None = None) -> None: ... def reset(self) -> SparseSim: ... + def set_seed(self, seed: int) -> None: ... @property def num_qubits(self) -> int: ... @property @@ -788,8 +789,9 @@ class SparseSim: class Stab: """Generic stabilizer simulator (recommended).""" - def __init__(self, num_qubits: int) -> None: ... + def __init__(self, num_qubits: int, seed: int | None = None) -> None: ... def reset(self) -> Stab: ... + def set_seed(self, seed: int) -> None: ... @property def num_qubits(self) -> int: ... def stab_tableau(self) -> str: ... diff --git a/python/pecos-rslib/src/sparse_stab_bindings.rs b/python/pecos-rslib/src/sparse_stab_bindings.rs index 799967994..a7c382fa2 100644 --- a/python/pecos-rslib/src/sparse_stab_bindings.rs +++ b/python/pecos-rslib/src/sparse_stab_bindings.rs @@ -24,9 +24,13 @@ pub struct PySparseSim { #[pymethods] impl PySparseSim { #[new] - fn new(num_qubits: usize) -> Self { + #[pyo3(signature = (num_qubits, seed=None))] + fn new(num_qubits: usize, seed: Option) -> Self { PySparseSim { - inner: SparseStab::new(num_qubits), + inner: match seed { + Some(s) => SparseStab::with_seed(num_qubits, s), + None => SparseStab::new(num_qubits), + }, } } @@ -40,6 +44,10 @@ impl PySparseSim { self.inner.num_qubits() } + fn set_seed(&mut self, seed: u64) { + self.inner.set_seed(seed); + } + #[allow(clippy::too_many_lines)] #[pyo3(signature = (symbol, location, params=None))] fn run_1q_gate( diff --git a/python/pecos-rslib/src/stab_bindings.rs b/python/pecos-rslib/src/stab_bindings.rs index f08eddb5c..b65e2b300 100644 --- a/python/pecos-rslib/src/stab_bindings.rs +++ b/python/pecos-rslib/src/stab_bindings.rs @@ -24,9 +24,13 @@ pub struct PyStab { #[pymethods] impl PyStab { #[new] - fn new(num_qubits: usize) -> Self { + #[pyo3(signature = (num_qubits, seed=None))] + fn new(num_qubits: usize, seed: Option) -> Self { PyStab { - inner: Stab::new(num_qubits), + inner: match seed { + Some(s) => Stab::with_seed(num_qubits, s), + None => Stab::new(num_qubits), + }, } } @@ -40,6 +44,10 @@ impl PyStab { self.inner.num_qubits() } + fn set_seed(&mut self, seed: u64) { + self.inner.set_seed(seed); + } + #[allow(clippy::too_many_lines)] #[pyo3(signature = (symbol, location, params=None))] fn run_1q_gate( diff --git a/python/pecos-rslib/tests/test_simulator_seeding.py b/python/pecos-rslib/tests/test_simulator_seeding.py new file mode 100644 index 000000000..a538d1526 --- /dev/null +++ b/python/pecos-rslib/tests/test_simulator_seeding.py @@ -0,0 +1,45 @@ +# Copyright 2026 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. + +"""Tests for exposing simulator seeding on stabilizer backends.""" + +import pytest + +from pecos_rslib import SparseSim, Stab + + +def _measurement_sequence(sim_cls, *, seed=None, reseed=None, rounds=32): + sim = sim_cls(1, seed=seed) if seed is not None else sim_cls(1) + if reseed is not None: + sim.set_seed(reseed) + + outcomes = [] + for _ in range(rounds): + sim.reset() + sim.run_1q_gate("H", 0) + outcomes.append(sim.run_1q_gate("MZ", 0)) + return outcomes + + +@pytest.mark.parametrize("sim_cls", [SparseSim, Stab]) +def test_seeded_constructor_repeats_measurement_sequence(sim_cls) -> None: + assert _measurement_sequence(sim_cls, seed=42) == _measurement_sequence(sim_cls, seed=42) + + +@pytest.mark.parametrize("sim_cls", [SparseSim, Stab]) +def test_set_seed_repeats_measurement_sequence(sim_cls) -> None: + assert _measurement_sequence(sim_cls, reseed=42) == _measurement_sequence(sim_cls, reseed=42) + + +@pytest.mark.parametrize("sim_cls", [SparseSim, Stab]) +def test_different_seeds_change_measurement_sequence(sim_cls) -> None: + assert _measurement_sequence(sim_cls, seed=42) != _measurement_sequence(sim_cls, seed=43) diff --git a/python/quantum-pecos/tests/pecos/test_stabilizer_simulator_seeding.py b/python/quantum-pecos/tests/pecos/test_stabilizer_simulator_seeding.py new file mode 100644 index 000000000..35519b5a7 --- /dev/null +++ b/python/quantum-pecos/tests/pecos/test_stabilizer_simulator_seeding.py @@ -0,0 +1,36 @@ +# Copyright 2026 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. + +"""High-level tests for seeded stabilizer simulator re-exports.""" + +import pytest + +from pecos.simulators import SparseSim, Stab + + +def _measurement_sequence(sim_cls, *, seed=None, reseed=None, rounds=16): + sim = sim_cls(1, seed=seed) if seed is not None else sim_cls(1) + if reseed is not None: + sim.set_seed(reseed) + + outcomes = [] + for _ in range(rounds): + sim.reset() + sim.run_1q_gate("H", 0) + outcomes.append(sim.run_1q_gate("MZ", 0)) + return outcomes + + +@pytest.mark.parametrize("sim_cls", [SparseSim, Stab]) +def test_high_level_simulators_accept_seed_and_set_seed(sim_cls) -> None: + assert _measurement_sequence(sim_cls, seed=42) == _measurement_sequence(sim_cls, seed=42) + assert _measurement_sequence(sim_cls, reseed=42) == _measurement_sequence(sim_cls, reseed=42)