1use super::wire::WireMap;
34use crate::dynamic::DynamicCircuit;
35use crate::statevector::StateVectorSimulator;
36use quantrs2_core::qubit::QubitId;
37use serde::{Deserialize, Serialize};
38use std::collections::BTreeSet;
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct PennyLaneOperation {
45 pub name: String,
47 pub wires: Vec<usize>,
49 #[serde(default)]
51 pub params: Vec<f64>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct PennyLaneObservable {
57 pub name: String,
59 pub wires: Vec<usize>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct PennyLaneCircuit {
66 pub num_wires: usize,
68 pub operations: Vec<PennyLaneOperation>,
70 #[serde(default)]
72 pub observables: Vec<PennyLaneObservable>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct PennyLaneResult {
78 pub probabilities: Vec<f64>,
80 pub state_re: Vec<f64>,
82 pub state_im: Vec<f64>,
84 pub expval: Vec<f64>,
86}
87
88fn pennylane_op_to_gate(
92 op: &PennyLaneOperation,
93 wire_map: &WireMap,
94) -> Result<Box<dyn quantrs2_core::gate::GateOp>, DeviceError> {
95 use quantrs2_core::gate::multi::{Fredkin, Toffoli, CH, CNOT, CRX, CRY, CRZ, CY, CZ, SWAP};
96 use quantrs2_core::gate::single::{
97 Hadamard, Identity, PGate, PauliX, PauliY, PauliZ, Phase, PhaseDagger, RotationX,
98 RotationY, RotationZ, SqrtX, SqrtXDagger, TDagger, UGate, T,
99 };
100
101 let q = |i: usize| -> Result<QubitId, DeviceError> {
103 let wire = op
104 .wires
105 .get(i)
106 .copied()
107 .ok_or_else(|| DeviceError::WrongQubitCount {
108 gate: op.name.clone(),
109 expected: i + 1,
110 actual: op.wires.len(),
111 })?;
112 wire_map
113 .wire_to_qubit(wire)
114 .ok_or(DeviceError::UnknownWire(wire))
115 };
116
117 let p = |i: usize| -> Result<f64, DeviceError> {
119 op.params
120 .get(i)
121 .copied()
122 .ok_or_else(|| DeviceError::WrongParamCount {
123 gate: op.name.clone(),
124 expected: i + 1,
125 actual: op.params.len(),
126 })
127 };
128
129 match op.name.as_str() {
130 "Identity" | "id" | "I" => Ok(Box::new(Identity { target: q(0)? })),
132 "PauliX" | "X" => Ok(Box::new(PauliX { target: q(0)? })),
133 "PauliY" | "Y" => Ok(Box::new(PauliY { target: q(0)? })),
134 "PauliZ" | "Z" => Ok(Box::new(PauliZ { target: q(0)? })),
135 "Hadamard" | "H" => Ok(Box::new(Hadamard { target: q(0)? })),
136 "S" | "Phase" => Ok(Box::new(Phase { target: q(0)? })),
137 "Adjoint(S)" | "S.Adjoint" | "Sdg" | "S†" => Ok(Box::new(PhaseDagger { target: q(0)? })),
138 "T" => Ok(Box::new(T { target: q(0)? })),
139 "Adjoint(T)" | "T.Adjoint" | "Tdg" | "T†" => Ok(Box::new(TDagger { target: q(0)? })),
140 "SX" | "sx" | "√X" => Ok(Box::new(SqrtX { target: q(0)? })),
141 "Adjoint(SX)" | "SX.Adjoint" | "SXdg" | "√X†" => {
142 Ok(Box::new(SqrtXDagger { target: q(0)? }))
143 }
144 "RX" | "rx" => Ok(Box::new(RotationX {
146 target: q(0)?,
147 theta: p(0)?,
148 })),
149 "RY" | "ry" => Ok(Box::new(RotationY {
150 target: q(0)?,
151 theta: p(0)?,
152 })),
153 "RZ" | "rz" => Ok(Box::new(RotationZ {
154 target: q(0)?,
155 theta: p(0)?,
156 })),
157 "PhaseShift" | "P" | "u1" => Ok(Box::new(PGate {
158 target: q(0)?,
159 lambda: p(0)?,
160 })),
161 "U3" | "Rot" | "U" => Ok(Box::new(UGate {
162 target: q(0)?,
163 theta: p(0)?,
164 phi: p(1)?,
165 lambda: p(2)?,
166 })),
167 "CNOT" | "CX" | "cx" => Ok(Box::new(CNOT {
169 control: q(0)?,
170 target: q(1)?,
171 })),
172 "CY" | "cy" => Ok(Box::new(CY {
173 control: q(0)?,
174 target: q(1)?,
175 })),
176 "CZ" | "cz" => Ok(Box::new(CZ {
177 control: q(0)?,
178 target: q(1)?,
179 })),
180 "CH" | "ch" => Ok(Box::new(CH {
181 control: q(0)?,
182 target: q(1)?,
183 })),
184 "SWAP" | "swap" => Ok(Box::new(SWAP {
185 qubit1: q(0)?,
186 qubit2: q(1)?,
187 })),
188 "CRX" | "crx" => Ok(Box::new(CRX {
190 control: q(0)?,
191 target: q(1)?,
192 theta: p(0)?,
193 })),
194 "CRY" | "cry" => Ok(Box::new(CRY {
195 control: q(0)?,
196 target: q(1)?,
197 theta: p(0)?,
198 })),
199 "CRZ" | "crz" => Ok(Box::new(CRZ {
200 control: q(0)?,
201 target: q(1)?,
202 theta: p(0)?,
203 })),
204 "Toffoli" | "CCX" | "ccx" => Ok(Box::new(Toffoli {
206 control1: q(0)?,
207 control2: q(1)?,
208 target: q(2)?,
209 })),
210 "CSWAP" | "Fredkin" | "cswap" => Ok(Box::new(Fredkin {
211 control: q(0)?,
212 target1: q(1)?,
213 target2: q(2)?,
214 })),
215 unknown => Err(DeviceError::UnknownGate(unknown.to_string())),
216 }
217}
218
219fn pauliz_expval(probs: &[f64], qubit: u32) -> f64 {
227 let mut expval = 0.0_f64;
228 for (state, &prob) in probs.iter().enumerate() {
229 let bit = (state >> qubit) & 1;
231 let sign = if bit == 0 { 1.0 } else { -1.0 };
232 expval += sign * prob;
233 }
234 expval
235}
236
237fn paulix_expval(state_re: &[f64], state_im: &[f64], qubit: u32) -> f64 {
244 let mut expval = 0.0_f64;
245 let flip = 1usize << qubit;
246 for i in 0..state_re.len() {
247 if (i >> qubit) & 1 == 0 {
249 let j = i ^ flip;
250 expval += 2.0 * (state_re[i] * state_re[j] + state_im[i] * state_im[j]);
252 }
253 }
254 expval
255}
256
257fn pauliy_expval(state_re: &[f64], state_im: &[f64], qubit: u32) -> f64 {
263 let mut expval = 0.0_f64;
264 let flip = 1usize << qubit;
265 for i in 0..state_re.len() {
266 if (i >> qubit) & 1 == 0 {
268 let j = i ^ flip;
269 expval += 2.0 * (state_re[i] * state_im[j] - state_im[i] * state_re[j]);
271 }
272 }
273 expval
274}
275
276#[derive(Debug)]
280pub enum DeviceError {
281 UnknownGate(String),
283 UnknownWire(usize),
285 WrongQubitCount {
287 gate: String,
289 expected: usize,
291 actual: usize,
293 },
294 WrongParamCount {
296 gate: String,
298 expected: usize,
300 actual: usize,
302 },
303 UnsupportedQubitCount(usize),
305 SimulationFailed(String),
307 JsonError(String),
309}
310
311impl std::fmt::Display for DeviceError {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 match self {
314 Self::UnknownGate(g) => write!(f, "Unknown PennyLane gate: {}", g),
315 Self::UnknownWire(w) => write!(f, "Unknown wire index: {}", w),
316 Self::WrongQubitCount {
317 gate,
318 expected,
319 actual,
320 } => {
321 write!(
322 f,
323 "Gate '{}' expects {} qubit(s), got {}",
324 gate, expected, actual
325 )
326 }
327 Self::WrongParamCount {
328 gate,
329 expected,
330 actual,
331 } => {
332 write!(
333 f,
334 "Gate '{}' expects {} param(s), got {}",
335 gate, expected, actual
336 )
337 }
338 Self::UnsupportedQubitCount(n) => write!(f, "Unsupported qubit count: {}", n),
339 Self::SimulationFailed(msg) => write!(f, "Simulation failed: {}", msg),
340 Self::JsonError(msg) => write!(f, "JSON error: {}", msg),
341 }
342 }
343}
344
345impl std::error::Error for DeviceError {}
346
347pub struct QuantRS2Device {
354 simulator: StateVectorSimulator,
355}
356
357impl QuantRS2Device {
358 pub fn new() -> Self {
360 Self {
361 simulator: StateVectorSimulator::new(),
362 }
363 }
364
365 pub fn with_simulator(simulator: StateVectorSimulator) -> Self {
367 Self { simulator }
368 }
369
370 pub fn execute(&self, circuit: &PennyLaneCircuit) -> Result<PennyLaneResult, DeviceError> {
377 let num_wires = circuit.num_wires;
378
379 let mut wires_set: BTreeSet<usize> = BTreeSet::new();
383 for op in &circuit.operations {
384 for &w in &op.wires {
385 wires_set.insert(w);
386 }
387 }
388 for w in 0..num_wires {
391 wires_set.insert(w);
392 }
393 let wires: Vec<usize> = wires_set.into_iter().collect();
394
395 let effective_qubits = wires.len().max(2);
397 let wire_map = WireMap::from_wires(&wires);
399
400 let mut dynamic = DynamicCircuit::new(effective_qubits)
402 .map_err(|_| DeviceError::UnsupportedQubitCount(effective_qubits))?;
403
404 for op in &circuit.operations {
405 let gate = pennylane_op_to_gate(op, &wire_map)?;
406 apply_boxed_gate(&mut dynamic, gate)?;
407 }
408
409 let result = dynamic
411 .run(&self.simulator)
412 .map_err(|e| DeviceError::SimulationFailed(e.to_string()))?;
413
414 let amplitudes = result.amplitudes();
415 let state_re: Vec<f64> = amplitudes.iter().map(|a| a.re).collect();
416 let state_im: Vec<f64> = amplitudes.iter().map(|a| a.im).collect();
417 let probabilities = result.probabilities();
418
419 let expval: Vec<f64> = circuit
421 .observables
422 .iter()
423 .map(|obs| compute_expval(obs, &probabilities, &state_re, &state_im))
424 .collect();
425
426 Ok(PennyLaneResult {
427 probabilities,
428 state_re,
429 state_im,
430 expval,
431 })
432 }
433
434 pub fn execute_json(&self, json_input: &str) -> Result<String, DeviceError> {
441 let circuit: PennyLaneCircuit =
442 serde_json::from_str(json_input).map_err(|e| DeviceError::JsonError(e.to_string()))?;
443
444 let result = self.execute(&circuit)?;
445
446 serde_json::to_string(&result).map_err(|e| DeviceError::JsonError(e.to_string()))
447 }
448}
449
450impl Default for QuantRS2Device {
451 fn default() -> Self {
452 Self::new()
453 }
454}
455
456fn apply_boxed_gate(
462 circuit: &mut DynamicCircuit,
463 gate: Box<dyn quantrs2_core::gate::GateOp>,
464) -> Result<(), DeviceError> {
465 use quantrs2_core::gate::multi::{Fredkin, Toffoli, CH, CNOT, CRX, CRY, CRZ, CY, CZ, SWAP};
466 use quantrs2_core::gate::single::{
467 Hadamard, Identity, PGate, PauliX, PauliY, PauliZ, Phase, PhaseDagger, RotationX,
468 RotationY, RotationZ, SqrtX, SqrtXDagger, TDagger, UGate, T,
469 };
470
471 let name = gate.name().to_string();
472 let any = gate.as_any();
473
474 macro_rules! try_apply {
475 ($ty:ty) => {
476 if let Some(g) = any.downcast_ref::<$ty>() {
477 return circuit
478 .apply_gate(*g)
479 .map_err(|e| DeviceError::SimulationFailed(e.to_string()));
480 }
481 };
482 }
483
484 try_apply!(Identity);
485 try_apply!(PauliX);
486 try_apply!(PauliY);
487 try_apply!(PauliZ);
488 try_apply!(Hadamard);
489 try_apply!(Phase);
490 try_apply!(PhaseDagger);
491 try_apply!(T);
492 try_apply!(TDagger);
493 try_apply!(SqrtX);
494 try_apply!(SqrtXDagger);
495 try_apply!(RotationX);
496 try_apply!(RotationY);
497 try_apply!(RotationZ);
498 try_apply!(PGate);
499 try_apply!(UGate);
500 try_apply!(CNOT);
501 try_apply!(CY);
502 try_apply!(CZ);
503 try_apply!(CH);
504 try_apply!(SWAP);
505 try_apply!(CRX);
506 try_apply!(CRY);
507 try_apply!(CRZ);
508 try_apply!(Toffoli);
509 try_apply!(Fredkin);
510
511 Err(DeviceError::UnknownGate(name))
512}
513
514fn compute_expval(
517 obs: &PennyLaneObservable,
518 probs: &[f64],
519 state_re: &[f64],
520 state_im: &[f64],
521) -> f64 {
522 match obs.name.as_str() {
523 "PauliZ" | "Z" => {
524 if let Some(&wire) = obs.wires.first() {
525 pauliz_expval(probs, wire as u32)
526 } else {
527 0.0
528 }
529 }
530 "PauliX" | "X" => {
531 if let Some(&wire) = obs.wires.first() {
532 paulix_expval(state_re, state_im, wire as u32)
533 } else {
534 0.0
535 }
536 }
537 "PauliY" | "Y" => {
538 if let Some(&wire) = obs.wires.first() {
539 pauliy_expval(state_re, state_im, wire as u32)
540 } else {
541 0.0
542 }
543 }
544 "Identity" | "I" => 1.0,
545 _ => 0.0,
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 fn bell_circuit() -> PennyLaneCircuit {
554 PennyLaneCircuit {
555 num_wires: 2,
556 operations: vec![
557 PennyLaneOperation {
558 name: "Hadamard".to_string(),
559 wires: vec![0],
560 params: vec![],
561 },
562 PennyLaneOperation {
563 name: "CNOT".to_string(),
564 wires: vec![0, 1],
565 params: vec![],
566 },
567 ],
568 observables: vec![PennyLaneObservable {
569 name: "PauliZ".to_string(),
570 wires: vec![0],
571 }],
572 }
573 }
574
575 #[test]
576 fn test_bell_state_probabilities() {
577 let device = QuantRS2Device::new();
578 let result = device
579 .execute(&bell_circuit())
580 .expect("bell state execution");
581
582 assert_eq!(result.probabilities.len(), 4);
585 assert!(
586 (result.probabilities[0] - 0.5).abs() < 1e-9,
587 "P(|00⟩) should be ~0.5, got {}",
588 result.probabilities[0]
589 );
590 assert!(
591 result.probabilities[1].abs() < 1e-9,
592 "P(|01⟩) should be ~0, got {}",
593 result.probabilities[1]
594 );
595 assert!(
596 result.probabilities[2].abs() < 1e-9,
597 "P(|10⟩) should be ~0, got {}",
598 result.probabilities[2]
599 );
600 assert!(
601 (result.probabilities[3] - 0.5).abs() < 1e-9,
602 "P(|11⟩) should be ~0.5, got {}",
603 result.probabilities[3]
604 );
605 }
606
607 #[test]
608 fn test_bell_state_expval() {
609 let device = QuantRS2Device::new();
610 let result = device
611 .execute(&bell_circuit())
612 .expect("bell state execution");
613
614 assert_eq!(result.expval.len(), 1);
616 assert!(
617 result.expval[0].abs() < 1e-9,
618 "⟨Z⟩ should be ~0 for Bell state, got {}",
619 result.expval[0]
620 );
621 }
622
623 #[test]
624 fn test_json_round_trip() {
625 let device = QuantRS2Device::new();
626 let json_in = r#"{"num_wires":2,"operations":[{"name":"Hadamard","wires":[0],"params":[]},{"name":"CNOT","wires":[0,1],"params":[]}],"observables":[]}"#;
627
628 let json_out = device.execute_json(json_in).expect("json execution");
629 let result: PennyLaneResult = serde_json::from_str(&json_out).expect("deserialize result");
630
631 assert_eq!(result.probabilities.len(), 4);
632 assert!((result.probabilities[0] - 0.5).abs() < 1e-9);
633 }
634
635 #[test]
636 fn test_rotation_gate() {
637 let circuit = PennyLaneCircuit {
638 num_wires: 1,
639 operations: vec![PennyLaneOperation {
640 name: "RX".to_string(),
641 wires: vec![0],
642 params: vec![std::f64::consts::PI],
643 }],
644 observables: vec![PennyLaneObservable {
645 name: "PauliZ".to_string(),
646 wires: vec![0],
647 }],
648 };
649
650 let device = QuantRS2Device::new();
651 let result = device.execute(&circuit).expect("rx(pi) execution");
652
653 let _ = result; }
658
659 #[test]
660 fn test_unknown_gate_error() {
661 let circuit = PennyLaneCircuit {
662 num_wires: 2,
663 operations: vec![PennyLaneOperation {
664 name: "QuantumFourier".to_string(), wires: vec![0, 1],
666 params: vec![],
667 }],
668 observables: vec![],
669 };
670
671 let device = QuantRS2Device::new();
672 let result = device.execute(&circuit);
673 assert!(result.is_err());
674 }
675
676 #[test]
677 fn test_paulix_expval_hadamard() {
678 let circuit = PennyLaneCircuit {
680 num_wires: 2,
681 operations: vec![PennyLaneOperation {
682 name: "Hadamard".to_string(),
683 wires: vec![0],
684 params: vec![],
685 }],
686 observables: vec![PennyLaneObservable {
687 name: "PauliX".to_string(),
688 wires: vec![0],
689 }],
690 };
691
692 let device = QuantRS2Device::new();
693 let result = device.execute(&circuit).expect("H|0⟩ PauliX expval");
694
695 assert_eq!(result.expval.len(), 1);
696 assert!(
697 (result.expval[0] - 1.0).abs() < 1e-9,
698 "⟨X⟩ for H|0⟩ should be 1.0, got {}",
699 result.expval[0]
700 );
701 }
702
703 #[test]
704 fn test_paulix_expval_x_gate() {
705 let circuit = PennyLaneCircuit {
711 num_wires: 2,
712 operations: vec![PennyLaneOperation {
713 name: "PauliX".to_string(),
714 wires: vec![0],
715 params: vec![],
716 }],
717 observables: vec![PennyLaneObservable {
718 name: "PauliX".to_string(),
719 wires: vec![0],
720 }],
721 };
722
723 let device = QuantRS2Device::new();
724 let result = device.execute(&circuit).expect("X|0⟩ PauliX expval");
725
726 assert_eq!(result.expval.len(), 1);
727 assert!(
728 result.expval[0].abs() < 1e-9,
729 "⟨X⟩ for X|0⟩=|1⟩ should be 0.0, got {}",
730 result.expval[0]
731 );
732 }
733}