|
| 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 | +} |
0 commit comments