Skip to content

Commit

Permalink
When initializing state, set the next rng based on the current rng (#184
Browse files Browse the repository at this point in the history
)

Co-authored-by: Stefan J. Wernli <[email protected]>
  • Loading branch information
idavis and swernli authored Jun 12, 2024
1 parent 6f239c7 commit 1185f30
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 15 deletions.
4 changes: 3 additions & 1 deletion backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ pub fn set_rng_seed(seed: u64) {
pub extern "C" fn __quantum__rt__initialize(_: *mut c_char) {
SIM_STATE.with(|sim_state| {
let state = &mut *sim_state.borrow_mut();
state.sim = QuantumSim::default();
// in order to continue using the same RNG, we need to reset the simulator
// and keep the same RNG
state.sim = QuantumSim::new(Some(state.sim.take_rng()));
state.res = bitvec![];
state.max_qubit_id = 0;
});
Expand Down
Binary file added runner/tests/resources/random-bit.bc
Binary file not shown.
42 changes: 41 additions & 1 deletion runner/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use runner::{run_bitcode, run_file};
use runner::{run_bitcode, run_file, OUTPUT};

// This group of tests verifies the behavior of QIR execution with a series of quantum gate checks based on the Choi–Jamiołkowski Isomorphism.
// They will verify the behavior of body, adjoint, controlled, and controlled adjoint specializations of each gate against decompositions thereof,
Expand Down Expand Up @@ -278,6 +278,46 @@ fn run_file_errors_on_missing_file() {
);
}

#[test]
fn run_file_random_seed_is_applied_and_persists_across_shots() {
OUTPUT.with(|output| {
let mut output = output.borrow_mut();
output.use_std_out(false);
});

let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("resources")
.join("random-bit.bc");

// running 100 random shots with the same seed should be enough to ensure
// that the output is deterministic
let shots = 100;
let rngseed = 42;

let mut first_output = vec![];
let result = run_file(path.clone(), None, shots, Some(rngseed), &mut first_output);
assert!(result.is_ok());

let mut second_output = vec![];
let result = run_file(path, None, shots, Some(rngseed), &mut second_output);
assert!(result.is_ok());

let first_result = String::from_utf8(first_output).expect("output should be valid utf8");
let second_result = String::from_utf8(second_output).expect("output should be valid utf8");

// sanity check that the output has been captured
// if the output recording is accidentally set to stdout,
// this will fail.
assert!(
first_result.contains("OUTPUT RESULT 1"),
"Ensure global output has use_std_out set to false"
);

// the output should be the same for each set of shots
assert_eq!(first_result, second_result);
}

#[test]
fn run_file_errors_on_missing_binding() {
let bitcode = include_bytes!("resources/missing-intrinsic.bc");
Expand Down
30 changes: 17 additions & 13 deletions sparsesim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,22 @@ pub(crate) enum FlushLevel {

impl Default for QuantumSim {
fn default() -> Self {
Self::new()
Self::new(None)
}
}

/// Provides the common set of functionality across all quantum simulation types.
impl QuantumSim {
/// Creates a new sparse state quantum simulator object with empty initial state (no qubits allocated, no operations buffered).
#[must_use]
pub fn new() -> Self {
pub fn new(rng: Option<StdRng>) -> Self {
let mut initial_state = SparseState::default();
initial_state.insert(BigUint::zero(), Complex64::one());

QuantumSim {
state: initial_state,
id_map: FxHashMap::default(),
rng: RefCell::new(StdRng::from_entropy()),
rng: RefCell::new(rng.unwrap_or_else(StdRng::from_entropy)),
h_flag: BigUint::zero(),
rx_queue: FxHashMap::default(),
ry_queue: FxHashMap::default(),
Expand All @@ -84,6 +84,10 @@ impl QuantumSim {
self.rng.replace(StdRng::seed_from_u64(seed));
}

pub fn take_rng(&mut self) -> StdRng {
self.rng.replace(StdRng::from_entropy())
}

/// Returns a sorted copy of the current sparse state as a vector of pairs of indices and complex numbers, along with
/// the total number of currently allocated qubits to help in interpreting the sparse state.
#[allow(clippy::missing_panics_doc)] // reason="Panics can only occur if the keys are not present in the map, which should not happen."
Expand Down Expand Up @@ -1288,7 +1292,7 @@ mod tests {
#[test]
#[should_panic(expected = "Duplicate qubit id '0' found in application.")]
fn test_duplicate_target() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let q = sim.allocate();
sim.mcx(&[q], q);
}
Expand All @@ -1297,7 +1301,7 @@ mod tests {
#[test]
#[should_panic(expected = "Duplicate qubit id '1' found in application.")]
fn test_duplicate_control() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let q = sim.allocate();
let c = sim.allocate();
sim.mcx(&[c, c], q);
Expand All @@ -1307,7 +1311,7 @@ mod tests {
#[test]
#[should_panic(expected = "Duplicate qubit id '0' found in application.")]
fn test_target_in_control() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let q = sim.allocate();
let c = sim.allocate();
sim.mcx(&[c, q], q);
Expand All @@ -1316,7 +1320,7 @@ mod tests {
/// Large, entangled state handling.
#[test]
fn test_large_state() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let ctl = sim.allocate();
sim.h(ctl);
for _ in 0..4999 {
Expand All @@ -1332,7 +1336,7 @@ mod tests {
/// Verify seeded RNG is predictable.
#[test]
fn test_seeded_rng() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
sim.set_rng_seed(42);
let q = sim.allocate();
let mut val1 = 0_u64;
Expand All @@ -1342,7 +1346,7 @@ mod tests {
val1 += 1 << i;
}
}
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
sim.set_rng_seed(42);
let q = sim.allocate();
let mut val2 = 0_u64;
Expand All @@ -1358,7 +1362,7 @@ mod tests {
/// Verify that dump after swap on released qubits doesn't crash.
#[test]
fn test_swap_dump() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let q = sim.allocate();
let inner_q = sim.allocate();
sim.swap_qubit_ids(q, inner_q);
Expand All @@ -1369,7 +1373,7 @@ mod tests {
/// Verify that swap preserves queued rotations.
#[test]
fn test_swap_rotations() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let (q1, q2) = (sim.allocate(), sim.allocate());
sim.rx(PI / 7.0, q1);
sim.ry(PI / 7.0, q2);
Expand All @@ -1384,7 +1388,7 @@ mod tests {
/// a no-op.
#[test]
fn test_rx_queue_nearly_zero() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let q = sim.allocate();
sim.rx(PI / 4.0, q);
assert_eq!(sim.state.len(), 1);
Expand All @@ -1397,7 +1401,7 @@ mod tests {
/// a no-op.
#[test]
fn test_ry_queue_nearly_zero() {
let mut sim = QuantumSim::new();
let mut sim = QuantumSim::new(None);
let q = sim.allocate();
sim.ry(PI / 4.0, q);
assert_eq!(sim.state.len(), 1);
Expand Down

0 comments on commit 1185f30

Please sign in to comment.