Skip to content

Commit db663bf

Browse files
committed
Adding biased noise model
1 parent 978cd5f commit db663bf

File tree

19 files changed

+1806
-308
lines changed

19 files changed

+1806
-308
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ bitflags = "2"
4040
syn = { version = "2", features = ["full"] }
4141
quote = "1"
4242
dyn-clone = "1"
43-
once_cell = "1"
4443
regex = "1"
4544

4645
pecos-derive = { version = "0.1.1", path = "crates/pecos-derive"}

crates/pecos-engines/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ bytemuck.workspace = true
2626
bitflags.workspace = true
2727
dyn-clone.workspace = true
2828
libloading.workspace = true
29-
once_cell.workspace = true
3029
regex.workspace = true
3130

3231
pecos-core.workspace = true
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use pecos_engines::Engine;
2+
use pecos_engines::byte_message::ByteMessage;
3+
use pecos_engines::engines::noise::BiasedMeasurementNoise;
4+
use pecos_engines::engines::quantum::StateVecEngine;
5+
use pecos_engines::{EngineSystem, QuantumSystem};
6+
use std::collections::HashMap;
7+
8+
fn main() {
9+
// Create a simple quantum circuit that prepares a superposition and measures it
10+
// We expect a roughly 50/50 distribution of 0s and 1s in the ideal case
11+
let circ = ByteMessage::quantum_operations_builder()
12+
.add_h(&[0])
13+
.add_measurements(&[0], &[0])
14+
.build();
15+
16+
// Create a quantum engine with 1 qubit
17+
let quantum = Box::new(StateVecEngine::new(1));
18+
19+
example1_different_bias_levels(&circ, quantum.clone());
20+
example2_with_seed(&circ);
21+
example3_bell_state();
22+
}
23+
24+
fn example1_different_bias_levels(circ: &ByteMessage, quantum: Box<StateVecEngine>) {
25+
// === EXAMPLE 1: Different bias levels ===
26+
println!("Example 1: Testing different bias levels with 10,000 shots");
27+
println!("{:-^80}", "");
28+
println!(
29+
"{:<30} | {:<10} | {:<10} | {:<10} | {:<10}",
30+
"Configuration", "Expected 0", "Actual 0", "Expected 1", "Actual 1"
31+
);
32+
println!("{:-^80}", "");
33+
34+
// Try different bias configurations
35+
let configs = [
36+
(0.0, 0.0, "No bias (ideal)"),
37+
(0.2, 0.0, "20% flip 0->1, never 1->0"),
38+
(0.0, 0.2, "Never flip 0->1, 20% flip 1->0"),
39+
(0.3, 0.3, "30% flip both ways"),
40+
(0.5, 0.0, "50% flip 0->1, never 1->0"),
41+
(0.0, 0.5, "Never flip 0->1, 50% flip 1->0"),
42+
(1.0, 0.0, "Always flip 0->1, never 1->0"),
43+
(0.0, 1.0, "Never flip 0->1, always 1->0"),
44+
];
45+
46+
// Increased number of shots for more stable statistics
47+
let num_shots = 10000;
48+
49+
for (p_flip_0, p_flip_1, desc) in configs {
50+
// Create the biased measurement noise model
51+
let noise = Box::new(BiasedMeasurementNoise::new(p_flip_0, p_flip_1));
52+
let mut system = QuantumSystem::new(noise, quantum.clone());
53+
54+
// For deterministic testing, set a fixed seed
55+
system.set_seed(42).expect("Failed to set seed");
56+
57+
// Run the circuit multiple times and collect statistics
58+
let mut counts = HashMap::new();
59+
60+
for _ in 0..num_shots {
61+
system.reset().expect("Failed to reset");
62+
let results = system
63+
.process_as_system(circ.clone())
64+
.expect("Failed to process circuit");
65+
let measurements = results
66+
.parse_measurements()
67+
.expect("Failed to parse measurements");
68+
69+
// Each measurement result is a tuple of (qubit_index, value)
70+
let result = measurements
71+
.first()
72+
.map_or("?", |&(_, value)| if value == 1 { "1" } else { "0" });
73+
*counts.entry(result.to_string()).or_insert(0) += 1;
74+
}
75+
76+
// Calculate percentages
77+
let pct_0 = counts.get("0").unwrap_or(&0) * 100 / num_shots;
78+
let pct_1 = counts.get("1").unwrap_or(&0) * 100 / num_shots;
79+
80+
// Calculate expected results based on probabilities
81+
// For a 50/50 input with H gate:
82+
// Expected 0s = 50% * (1-p_flip_0) + 50% * p_flip_1
83+
// Expected 1s = 50% * p_flip_0 + 50% * (1-p_flip_1)
84+
let expected_0 = ((50.0 * (1.0 - p_flip_0) + 50.0 * p_flip_1) as f64).round() as usize;
85+
let expected_1 = ((50.0 * p_flip_0 + 50.0 * (1.0 - p_flip_1)) as f64).round() as usize;
86+
87+
println!(
88+
"{:<30} | {:<10} | {:<10} | {:<10} | {:<10}",
89+
desc,
90+
format!("{}%", expected_0),
91+
format!("{}%", pct_0),
92+
format!("{}%", expected_1),
93+
format!("{}%", pct_1)
94+
);
95+
}
96+
println!("{:-^80}", "");
97+
println!();
98+
}
99+
100+
fn example2_with_seed(circ: &ByteMessage) {
101+
// === EXAMPLE 2: Using direct constructor with seed ===
102+
println!("Example 2: Using direct constructor with seed");
103+
104+
let noise = Box::new(BiasedMeasurementNoise::with_seed(0.4, 0.1, 123));
105+
let quantum = Box::new(StateVecEngine::new(1));
106+
let mut system = QuantumSystem::new(noise, quantum);
107+
108+
// Run the circuit multiple times and collect statistics
109+
let num_shots = 1000;
110+
let mut counts = HashMap::new();
111+
112+
for _ in 0..num_shots {
113+
system.reset().expect("Failed to reset");
114+
let results = system
115+
.process_as_system(circ.clone())
116+
.expect("Failed to process circuit");
117+
let measurements = results
118+
.parse_measurements()
119+
.expect("Failed to parse measurements");
120+
121+
let result = measurements
122+
.first()
123+
.map_or("?", |&(_, value)| if value == 1 { "1" } else { "0" });
124+
*counts.entry(result.to_string()).or_insert(0) += 1;
125+
}
126+
127+
// Calculate percentages
128+
let pct_0 = counts.get("0").unwrap_or(&0) * 100 / num_shots;
129+
let pct_1 = counts.get("1").unwrap_or(&0) * 100 / num_shots;
130+
131+
// Calculate expected results for this configuration
132+
let p_flip_0 = 0.4;
133+
let p_flip_1 = 0.1;
134+
let expected_0 = ((50.0 * (1.0 - p_flip_0) + 50.0 * p_flip_1) as f64).round() as usize;
135+
let expected_1 = ((50.0 * p_flip_0 + 50.0 * (1.0 - p_flip_1)) as f64).round() as usize;
136+
137+
println!("Builder pattern with p_flip_0 = 0.4, p_flip_1 = 0.1");
138+
println!(" Expected results: 0: {expected_0}%, 1: {expected_1}%");
139+
println!(" Actual results: 0: {pct_0}%, 1: {pct_1}%");
140+
println!();
141+
}
142+
143+
fn example3_bell_state() {
144+
// === EXAMPLE 3: Bell state with biased measurement ===
145+
println!("Example 3: Bell state with biased measurement");
146+
147+
// Create a Bell state circuit
148+
let bell_circ = ByteMessage::quantum_operations_builder()
149+
.add_h(&[0])
150+
.add_cx(&[0], &[1])
151+
.add_measurements(&[0], &[0])
152+
.add_measurements(&[1], &[1])
153+
.build();
154+
155+
// Create a new quantum system with 2 qubits
156+
let quantum2 = Box::new(StateVecEngine::new(2));
157+
let noise2 = Box::new(BiasedMeasurementNoise::new(0.2, 0.3));
158+
let mut system2 = QuantumSystem::new(noise2, quantum2);
159+
160+
// Set a fixed seed for deterministic results
161+
system2.set_seed(42).expect("Failed to set seed");
162+
163+
// Run the bell circuit multiple times and collect statistics
164+
let num_shots = 1000;
165+
let mut bell_counts = HashMap::new();
166+
167+
for _ in 0..num_shots {
168+
system2.reset().expect("Failed to reset");
169+
let results = system2
170+
.process_as_system(bell_circ.clone())
171+
.expect("Failed to process Bell circuit");
172+
let measurements = results
173+
.parse_measurements()
174+
.expect("Failed to parse measurements");
175+
176+
// Combine the measurement results into a string
177+
let mut result = String::new();
178+
for &(_, value) in &measurements {
179+
result.push(if value == 1 { '1' } else { '0' });
180+
}
181+
182+
*bell_counts.entry(result).or_insert(0) += 1;
183+
}
184+
185+
println!("Bell state with biased measurement noise:");
186+
println!(" p_flip_0 = 0.2, p_flip_1 = 0.3");
187+
188+
// Calculate expected probabilities for Bell state results with biased measurement
189+
// For Bell state (|00⟩ + |11⟩)/√2, with no bias we expect 50% |00⟩ and 50% |11⟩
190+
let p_flip_0 = 0.2;
191+
let p_flip_1 = 0.3;
192+
193+
// Calculate theoretical distributions
194+
let mut expected_probs = HashMap::new();
195+
expected_probs.insert(
196+
"00".to_string(),
197+
((1.0 - p_flip_0) * (1.0 - p_flip_0) + p_flip_1 * p_flip_1) * 50.0,
198+
);
199+
expected_probs.insert(
200+
"01".to_string(),
201+
((1.0 - p_flip_0) * p_flip_0 + p_flip_1 * (1.0 - p_flip_1)) * 50.0,
202+
);
203+
expected_probs.insert(
204+
"10".to_string(),
205+
(p_flip_0 * (1.0 - p_flip_0) + (1.0 - p_flip_1) * p_flip_1) * 50.0,
206+
);
207+
expected_probs.insert(
208+
"11".to_string(),
209+
(p_flip_0 * p_flip_0 + (1.0 - p_flip_1) * (1.0 - p_flip_1)) * 50.0,
210+
);
211+
212+
// Sort both expected and actual by the pattern (00, 01, 10, 11) for easier comparison
213+
let patterns = ["00", "01", "10", "11"];
214+
215+
println!("{:-^60}", "");
216+
println!("{:<10} | {:<20} | {:<20}", "Pattern", "Expected", "Actual");
217+
println!("{:-^60}", "");
218+
219+
for pattern in &patterns {
220+
let expected = expected_probs.get(*pattern).unwrap_or(&0.0);
221+
let actual_count = bell_counts.get(*pattern).unwrap_or(&0);
222+
let actual_pct = actual_count * 100 / num_shots;
223+
224+
println!(
225+
"{:<10} | {:<20} | {:<20}",
226+
pattern,
227+
format!("{:.2}%", expected),
228+
format!("{} ({}%)", actual_count, actual_pct)
229+
);
230+
}
231+
println!("{:-^60}", "");
232+
}

crates/pecos-engines/examples/run_noisy_circ.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use pecos_engines::byte_message::ByteMessage;
22
use pecos_engines::engines::quantum::StateVecEngine;
3-
use pecos_engines::errors::QueueError;
43
use pecos_engines::{Engine, GeneralDepolarizingNoise};
54
use pecos_engines::{EngineSystem, QuantumSystem};
65

7-
fn main() -> () {
6+
fn main() {
87
let circ = ByteMessage::quantum_operations_builder()
98
.add_h(&[0])
109
.add_cx(&[0], &[1])

crates/pecos-engines/src/byte_message/gate_type.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,14 @@ impl QuantumGate {
173173
self
174174
}
175175

176-
#[must_use] pub fn set_noisy(mut self) -> Self {
176+
#[must_use]
177+
pub fn set_noisy(mut self) -> Self {
177178
self.noiseless = false;
178179
self
179180
}
180181

181-
#[must_use] pub fn is_noiseless(&self) -> bool {
182+
#[must_use]
183+
pub fn is_noiseless(&self) -> bool {
182184
self.noiseless
183185
}
184186
}

crates/pecos-engines/src/byte_message/message.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::byte_message::builder::ByteMessageBuilder;
22
use crate::byte_message::gate_type::{GateType, QuantumGate};
33
use crate::byte_message::protocol::{
4-
calc_padding, BatchHeader, MeasurementHeader, MeasurementResultHeader, MessageHeader,
5-
MessageType, QuantumGateHeader,
4+
BatchHeader, MeasurementHeader, MeasurementResultHeader, MessageHeader, MessageType,
5+
QuantumGateHeader, calc_padding,
66
};
77
use crate::errors::QueueError;
88
use bytemuck::from_bytes;
@@ -802,7 +802,7 @@ impl ByteMessage {
802802
#[cfg(test)]
803803
mod tests {
804804
use super::*;
805-
use crate::engines::{quantum::StateVecEngine, Engine};
805+
use crate::engines::{Engine, quantum::StateVecEngine};
806806

807807
#[test]
808808
fn test_bytemap_builder() {

0 commit comments

Comments
 (0)