1use crate::circuit::QuantumCircuit;
9use crate::gate::Gate;
10use crate::simulator::{SimConfig, Simulator};
11use crate::types::{Complex, NoiseModel};
12
13use std::collections::hash_map::DefaultHasher;
14use std::hash::{Hash, Hasher};
15use std::time::{SystemTime, UNIX_EPOCH};
16
17#[derive(Debug, Clone, PartialEq)]
23pub struct NoiseConfig {
24 pub depolarizing_rate: f64,
25 pub bit_flip_rate: f64,
26 pub phase_flip_rate: f64,
27}
28
29impl NoiseConfig {
30 pub fn from_noise_model(m: &NoiseModel) -> Self {
32 Self {
33 depolarizing_rate: m.depolarizing_rate,
34 bit_flip_rate: m.bit_flip_rate,
35 phase_flip_rate: m.phase_flip_rate,
36 }
37 }
38
39 pub fn to_noise_model(&self) -> NoiseModel {
41 NoiseModel {
42 depolarizing_rate: self.depolarizing_rate,
43 bit_flip_rate: self.bit_flip_rate,
44 phase_flip_rate: self.phase_flip_rate,
45 }
46 }
47}
48
49#[derive(Debug, Clone)]
58pub struct ExecutionRecord {
59 pub circuit_hash: [u8; 32],
62 pub seed: u64,
64 pub backend: String,
66 pub noise_config: Option<NoiseConfig>,
68 pub shots: u32,
70 pub software_version: String,
72 pub timestamp_utc: u64,
74}
75
76pub struct ReplayEngine {
83 version: String,
85}
86
87impl ReplayEngine {
88 pub fn new() -> Self {
90 Self {
91 version: env!("CARGO_PKG_VERSION").to_string(),
92 }
93 }
94
95 pub fn record_execution(
101 &self,
102 circuit: &QuantumCircuit,
103 config: &SimConfig,
104 shots: u32,
105 ) -> ExecutionRecord {
106 let seed = config.seed.unwrap_or(0);
107 let noise_config = config.noise.as_ref().map(NoiseConfig::from_noise_model);
108
109 let timestamp_utc = SystemTime::now()
110 .duration_since(UNIX_EPOCH)
111 .map(|d| d.as_secs())
112 .unwrap_or(0);
113
114 ExecutionRecord {
115 circuit_hash: Self::circuit_hash(circuit),
116 seed,
117 backend: "state_vector".to_string(),
118 noise_config,
119 shots,
120 software_version: self.version.clone(),
121 timestamp_utc,
122 }
123 }
124
125 pub fn replay(&self, record: &ExecutionRecord, circuit: &QuantumCircuit) -> bool {
132 let current_hash = Self::circuit_hash(circuit);
134 if current_hash != record.circuit_hash {
135 return false;
136 }
137
138 let noise = record.noise_config.as_ref().map(NoiseConfig::to_noise_model);
139
140 let config = SimConfig {
141 seed: Some(record.seed),
142 noise: noise.clone(),
143 shots: None,
144 };
145
146 let run_a = Simulator::run_with_config(circuit, &config);
148 let config_b = SimConfig {
149 seed: Some(record.seed),
150 noise,
151 shots: None,
152 };
153 let run_b = Simulator::run_with_config(circuit, &config_b);
154
155 match (run_a, run_b) {
156 (Ok(a), Ok(b)) => {
157 if a.measurements.len() != b.measurements.len() {
158 return false;
159 }
160 a.measurements
161 .iter()
162 .zip(b.measurements.iter())
163 .all(|(ma, mb)| {
164 ma.qubit == mb.qubit
165 && ma.result == mb.result
166 && (ma.probability - mb.probability).abs() < 1e-12
167 })
168 }
169 _ => false,
170 }
171 }
172
173 pub fn circuit_hash(circuit: &QuantumCircuit) -> [u8; 32] {
182 let canonical = Self::circuit_canonical_bytes(circuit);
184
185 let mut result = [0u8; 32];
186
187 let h0 = hash_bytes_with_seed(&canonical, 0);
189 result[0..8].copy_from_slice(&h0.to_le_bytes());
190
191 let h1 = hash_bytes_with_seed(&canonical, 1);
193 result[8..16].copy_from_slice(&h1.to_le_bytes());
194
195 let h2 = hash_bytes_with_seed(&canonical, 2);
197 result[16..24].copy_from_slice(&h2.to_le_bytes());
198
199 let h3 = hash_bytes_with_seed(&canonical, 3);
201 result[24..32].copy_from_slice(&h3.to_le_bytes());
202
203 result
204 }
205
206 fn circuit_canonical_bytes(circuit: &QuantumCircuit) -> Vec<u8> {
211 let mut buf = Vec::new();
212
213 buf.extend_from_slice(&circuit.num_qubits().to_le_bytes());
215
216 for gate in circuit.gates() {
217 let (disc, qubits, params) = gate_components(gate);
219 buf.push(disc);
220
221 for q in &qubits {
222 buf.extend_from_slice(&q.to_le_bytes());
223 }
224 for p in ¶ms {
225 buf.extend_from_slice(&p.to_le_bytes());
226 }
227 }
228
229 buf
230 }
231}
232
233impl Default for ReplayEngine {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239#[derive(Debug, Clone)]
249pub struct StateCheckpoint {
250 data: Vec<u8>,
251 num_amplitudes: usize,
252}
253
254impl StateCheckpoint {
255 pub fn capture(amplitudes: &[Complex]) -> Self {
257 let mut data = Vec::with_capacity(amplitudes.len() * 16);
258 for amp in amplitudes {
259 data.extend_from_slice(&.re.to_le_bytes());
260 data.extend_from_slice(&.im.to_le_bytes());
261 }
262 Self {
263 data,
264 num_amplitudes: amplitudes.len(),
265 }
266 }
267
268 pub fn restore(&self) -> Vec<Complex> {
270 let mut amps = Vec::with_capacity(self.num_amplitudes);
271 for i in 0..self.num_amplitudes {
272 let offset = i * 16;
273 let re = f64::from_le_bytes(
274 self.data[offset..offset + 8]
275 .try_into()
276 .expect("checkpoint data corrupted"),
277 );
278 let im = f64::from_le_bytes(
279 self.data[offset + 8..offset + 16]
280 .try_into()
281 .expect("checkpoint data corrupted"),
282 );
283 amps.push(Complex::new(re, im));
284 }
285 amps
286 }
287
288 pub fn size_bytes(&self) -> usize {
290 self.data.len()
291 }
292}
293
294fn hash_bytes_with_seed(data: &[u8], seed: u64) -> u64 {
303 let mut hasher = DefaultHasher::new();
304 seed.hash(&mut hasher);
305 data.hash(&mut hasher);
306 hasher.finish()
307}
308
309fn gate_components(gate: &Gate) -> (u8, Vec<u32>, Vec<f64>) {
312 match gate {
313 Gate::H(q) => (0, vec![*q], vec![]),
314 Gate::X(q) => (1, vec![*q], vec![]),
315 Gate::Y(q) => (2, vec![*q], vec![]),
316 Gate::Z(q) => (3, vec![*q], vec![]),
317 Gate::S(q) => (4, vec![*q], vec![]),
318 Gate::Sdg(q) => (5, vec![*q], vec![]),
319 Gate::T(q) => (6, vec![*q], vec![]),
320 Gate::Tdg(q) => (7, vec![*q], vec![]),
321 Gate::Rx(q, angle) => (8, vec![*q], vec![*angle]),
322 Gate::Ry(q, angle) => (9, vec![*q], vec![*angle]),
323 Gate::Rz(q, angle) => (10, vec![*q], vec![*angle]),
324 Gate::Phase(q, angle) => (11, vec![*q], vec![*angle]),
325 Gate::CNOT(c, t) => (12, vec![*c, *t], vec![]),
326 Gate::CZ(a, b) => (13, vec![*a, *b], vec![]),
327 Gate::SWAP(a, b) => (14, vec![*a, *b], vec![]),
328 Gate::Rzz(a, b, angle) => (15, vec![*a, *b], vec![*angle]),
329 Gate::Measure(q) => (16, vec![*q], vec![]),
330 Gate::Reset(q) => (17, vec![*q], vec![]),
331 Gate::Barrier => (18, vec![], vec![]),
332 Gate::Unitary1Q(q, m) => {
333 let params = vec![
335 m[0][0].re, m[0][0].im, m[0][1].re, m[0][1].im,
336 m[1][0].re, m[1][0].im, m[1][1].re, m[1][1].im,
337 ];
338 (19, vec![*q], params)
339 }
340 }
341}
342
343#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::circuit::QuantumCircuit;
351 use crate::simulator::SimConfig;
352 use crate::types::Complex;
353
354 #[test]
356 fn same_seed_identical_results() {
357 let mut circuit = QuantumCircuit::new(2);
358 circuit.h(0).cnot(0, 1).measure(0).measure(1);
359
360 let config = SimConfig {
361 seed: Some(42),
362 noise: None,
363 shots: None,
364 };
365
366 let r1 = Simulator::run_with_config(&circuit, &config).unwrap();
367 let r2 = Simulator::run_with_config(&circuit, &config).unwrap();
368
369 assert_eq!(r1.measurements.len(), r2.measurements.len());
370 for (a, b) in r1.measurements.iter().zip(r2.measurements.iter()) {
371 assert_eq!(a.qubit, b.qubit);
372 assert_eq!(a.result, b.result);
373 assert!((a.probability - b.probability).abs() < 1e-12);
374 }
375 }
376
377 #[test]
381 fn different_seed_different_results() {
382 let mut circuit = QuantumCircuit::new(2);
383 circuit.h(0).cnot(0, 1).measure(0).measure(1);
384
385 let mut any_differ = false;
386 for offset in 0..20 {
388 let c1 = SimConfig {
389 seed: Some(100 + offset),
390 noise: None,
391 shots: None,
392 };
393 let c2 = SimConfig {
394 seed: Some(200 + offset),
395 noise: None,
396 shots: None,
397 };
398 let r1 = Simulator::run_with_config(&circuit, &c1).unwrap();
399 let r2 = Simulator::run_with_config(&circuit, &c2).unwrap();
400 if r1.measurements.iter().zip(r2.measurements.iter()).any(|(a, b)| a.result != b.result)
401 {
402 any_differ = true;
403 break;
404 }
405 }
406 assert!(any_differ, "expected at least one pair of seeds to disagree");
407 }
408
409 #[test]
411 fn record_replay_roundtrip() {
412 let mut circuit = QuantumCircuit::new(2);
413 circuit.h(0).cnot(0, 1).measure(0).measure(1);
414
415 let config = SimConfig {
416 seed: Some(99),
417 noise: None,
418 shots: None,
419 };
420
421 let engine = ReplayEngine::new();
422 let record = engine.record_execution(&circuit, &config, 1);
423
424 assert!(engine.replay(&record, &circuit));
425 }
426
427 #[test]
429 fn circuit_hash_deterministic() {
430 let mut circuit = QuantumCircuit::new(3);
431 circuit.h(0).rx(1, 1.234).cnot(0, 2).measure(0);
432
433 let h1 = ReplayEngine::circuit_hash(&circuit);
434 let h2 = ReplayEngine::circuit_hash(&circuit);
435 assert_eq!(h1, h2);
436 }
437
438 #[test]
440 fn circuit_hash_differs_for_different_circuits() {
441 let mut c1 = QuantumCircuit::new(2);
442 c1.h(0).cnot(0, 1);
443
444 let mut c2 = QuantumCircuit::new(2);
445 c2.x(0).cnot(0, 1);
446
447 let h1 = ReplayEngine::circuit_hash(&c1);
448 let h2 = ReplayEngine::circuit_hash(&c2);
449 assert_ne!(h1, h2);
450 }
451
452 #[test]
454 fn checkpoint_capture_restore() {
455 let amplitudes = vec![
456 Complex::new(0.5, 0.5),
457 Complex::new(-0.3, 0.1),
458 Complex::new(0.0, -0.7),
459 Complex::new(0.2, 0.0),
460 ];
461
462 let checkpoint = StateCheckpoint::capture(&litudes);
463 let restored = checkpoint.restore();
464
465 assert_eq!(amplitudes.len(), restored.len());
466 for (orig, rest) in amplitudes.iter().zip(restored.iter()) {
467 assert_eq!(orig.re, rest.re);
468 assert_eq!(orig.im, rest.im);
469 }
470 }
471
472 #[test]
474 fn checkpoint_size_bytes() {
475 let amplitudes = vec![Complex::ZERO; 8];
476 let checkpoint = StateCheckpoint::capture(&litudes);
477 assert_eq!(checkpoint.size_bytes(), 8 * 16);
478 }
479
480 #[test]
482 fn replay_fails_on_modified_circuit() {
483 let mut circuit = QuantumCircuit::new(2);
484 circuit.h(0).cnot(0, 1).measure(0).measure(1);
485
486 let config = SimConfig {
487 seed: Some(42),
488 noise: None,
489 shots: None,
490 };
491
492 let engine = ReplayEngine::new();
493 let record = engine.record_execution(&circuit, &config, 1);
494
495 let mut modified = QuantumCircuit::new(2);
497 modified.x(0).cnot(0, 1).measure(0).measure(1);
498
499 assert!(!engine.replay(&record, &modified));
500 }
501
502 #[test]
504 fn record_captures_noise() {
505 let circuit = QuantumCircuit::new(1);
506 let config = SimConfig {
507 seed: Some(7),
508 noise: Some(NoiseModel {
509 depolarizing_rate: 0.01,
510 bit_flip_rate: 0.005,
511 phase_flip_rate: 0.002,
512 }),
513 shots: None,
514 };
515
516 let engine = ReplayEngine::new();
517 let record = engine.record_execution(&circuit, &config, 100);
518
519 let nc = record.noise_config.as_ref().unwrap();
520 assert!((nc.depolarizing_rate - 0.01).abs() < 1e-15);
521 assert!((nc.bit_flip_rate - 0.005).abs() < 1e-15);
522 assert!((nc.phase_flip_rate - 0.002).abs() < 1e-15);
523 assert_eq!(record.shots, 100);
524 assert_eq!(record.seed, 7);
525 }
526
527 #[test]
529 fn empty_circuit_hash() {
530 let empty = QuantumCircuit::new(2);
531 let mut non_empty = QuantumCircuit::new(2);
532 non_empty.h(0);
533
534 let h1 = ReplayEngine::circuit_hash(&empty);
535 let h2 = ReplayEngine::circuit_hash(&non_empty);
536 assert_ne!(h1, h2);
537
538 assert_eq!(h1, ReplayEngine::circuit_hash(&empty));
540 }
541
542 #[test]
544 fn rotation_angle_changes_hash() {
545 let mut c1 = QuantumCircuit::new(1);
546 c1.rx(0, 1.0);
547
548 let mut c2 = QuantumCircuit::new(1);
549 c2.rx(0, 1.0001);
550
551 assert_ne!(
552 ReplayEngine::circuit_hash(&c1),
553 ReplayEngine::circuit_hash(&c2)
554 );
555 }
556}