Skip to content

Commit 1efe606

Browse files
committed
Add shot argument to PECOS CLI and ensure deterministic output
1 parent 0ff544d commit 1efe606

File tree

5 files changed

+274
-10
lines changed

5 files changed

+274
-10
lines changed

Cargo.lock

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

crates/pecos-cli/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,10 @@ pecos.workspace = true
2121
clap.workspace = true
2222
env_logger.workspace = true
2323

24+
[dev-dependencies]
25+
assert_cmd = "2.0"
26+
predicates = "3.0"
27+
tempfile = "3.8"
28+
2429
[lints]
2530
workspace = true

crates/pecos-cli/src/main.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ struct RunArgs {
4545
/// Depolarizing noise probability (between 0 and 1)
4646
#[arg(short = 'p', long = "noise", value_parser = parse_noise_probability)]
4747
noise_probability: Option<f64>,
48+
49+
/// Seed for random number generation (for reproducible results)
50+
#[arg(short = 'd', long)]
51+
seed: Option<u64>,
4852
}
4953

5054
fn parse_noise_probability(arg: &str) -> Result<f64, String> {
@@ -68,7 +72,7 @@ fn run_program(args: &RunArgs) -> Result<(), Box<dyn Error>> {
6872
prob,
6973
args.shots,
7074
args.workers,
71-
None,
75+
args.seed,
7276
)?;
7377

7478
results.print();
@@ -100,3 +104,46 @@ fn main() -> Result<(), Box<dyn Error>> {
100104

101105
Ok(())
102106
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
112+
#[test]
113+
fn verify_cli_seed_argument() {
114+
let cmd = Cli::parse_from([
115+
"pecos",
116+
"run",
117+
"program.json",
118+
"-d",
119+
"42",
120+
"-s",
121+
"100",
122+
"-w",
123+
"2",
124+
]);
125+
126+
match cmd.command {
127+
Commands::Run(args) => {
128+
assert_eq!(args.seed, Some(42));
129+
assert_eq!(args.shots, 100);
130+
assert_eq!(args.workers, 2);
131+
}
132+
Commands::Compile(_) => panic!("Expected Run command"),
133+
}
134+
}
135+
136+
#[test]
137+
fn verify_cli_no_seed_argument() {
138+
let cmd = Cli::parse_from(["pecos", "run", "program.json", "-s", "100", "-w", "2"]);
139+
140+
match cmd.command {
141+
Commands::Run(args) => {
142+
assert_eq!(args.seed, None);
143+
assert_eq!(args.shots, 100);
144+
assert_eq!(args.workers, 2);
145+
}
146+
Commands::Compile(_) => panic!("Expected Run command"),
147+
}
148+
}
149+
}

crates/pecos-cli/tests/seed.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use assert_cmd::prelude::*;
2+
use std::path::PathBuf;
3+
use std::process::Command;
4+
5+
#[test]
6+
fn test_seed_produces_consistent_results() -> Result<(), Box<dyn std::error::Error>> {
7+
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
8+
let test_file = manifest_dir.join("../../examples/phir/bell.json");
9+
10+
// Run multiple times with seed 42
11+
let seed_42_run1 = Command::cargo_bin("pecos")?
12+
.env("RUST_LOG", "info")
13+
.arg("run")
14+
.arg(&test_file)
15+
.arg("-s")
16+
.arg("10") // Fewer shots for faster tests
17+
.arg("-w")
18+
.arg("1") // Single worker to avoid thread scheduling differences
19+
.arg("-p")
20+
.arg("0.1")
21+
.arg("-d")
22+
.arg("42")
23+
.output()?;
24+
25+
let seed_42_run2 = Command::cargo_bin("pecos")?
26+
.env("RUST_LOG", "info")
27+
.arg("run")
28+
.arg(&test_file)
29+
.arg("-s")
30+
.arg("10")
31+
.arg("-w")
32+
.arg("1")
33+
.arg("-p")
34+
.arg("0.1")
35+
.arg("-d")
36+
.arg("42")
37+
.output()?;
38+
39+
// Run multiple times with seed 43
40+
let seed_43_run1 = Command::cargo_bin("pecos")?
41+
.env("RUST_LOG", "info")
42+
.arg("run")
43+
.arg(&test_file)
44+
.arg("-s")
45+
.arg("10")
46+
.arg("-w")
47+
.arg("1")
48+
.arg("-p")
49+
.arg("0.1")
50+
.arg("-d")
51+
.arg("43")
52+
.output()?;
53+
54+
let seed_43_run2 = Command::cargo_bin("pecos")?
55+
.env("RUST_LOG", "info")
56+
.arg("run")
57+
.arg(&test_file)
58+
.arg("-s")
59+
.arg("10")
60+
.arg("-w")
61+
.arg("1")
62+
.arg("-p")
63+
.arg("0.1")
64+
.arg("-d")
65+
.arg("43")
66+
.output()?;
67+
68+
// Check that all commands ran successfully
69+
assert!(seed_42_run1.status.success(), "First seed 42 run failed");
70+
assert!(seed_42_run2.status.success(), "Second seed 42 run failed");
71+
assert!(seed_43_run1.status.success(), "First seed 43 run failed");
72+
assert!(seed_43_run2.status.success(), "Second seed 43 run failed");
73+
74+
// Convert outputs to strings
75+
let seed_42_output1 = String::from_utf8(seed_42_run1.stdout)?;
76+
let seed_42_output2 = String::from_utf8(seed_42_run2.stdout)?;
77+
let seed_43_output1 = String::from_utf8(seed_43_run1.stdout)?;
78+
let seed_43_output2 = String::from_utf8(seed_43_run2.stdout)?;
79+
80+
// Verify consistency within each seed group
81+
assert_eq!(
82+
seed_42_output1, seed_42_output2,
83+
"Results with seed 42 should be identical across runs"
84+
);
85+
86+
assert_eq!(
87+
seed_43_output1, seed_43_output2,
88+
"Results with seed 43 should be identical across runs"
89+
);
90+
91+
// Verify that different seeds produce different results
92+
assert_ne!(
93+
seed_42_output1, seed_43_output1,
94+
"Results with different seeds (42 vs 43) should differ"
95+
);
96+
97+
Ok(())
98+
}

0 commit comments

Comments
 (0)