| use pyo3::prelude::*; |
| use serde::{Deserialize, Serialize}; |
| use std::collections::HashMap; |
| use thiserror::Error; |
|
|
| const SUPPORTED_SCHEMA_VERSION: u32 = 1; |
|
|
| |
| #[derive(Debug, Clone, Serialize, Deserialize)] |
| pub enum GateType { |
| |
| I, |
| |
| X, |
| |
| Y, |
| |
| Z, |
| |
| H, |
| |
| S, |
| |
| Sdg, |
| |
| T, |
| |
| Tdg, |
| |
| RX(f64), |
| |
| RY(f64), |
| |
| RZ(f64), |
| |
| P(f64), |
| |
| U3(f64, f64, f64), |
| |
| CX, |
| |
| CY, |
| |
| CZ, |
| |
| CCX, |
| } |
|
|
| |
| #[derive(Debug, Clone, Serialize, Deserialize)] |
| pub struct Gate { |
| pub gate_type: GateType, |
| pub qubits: Vec<usize>, |
| pub parameters: Vec<f64>, |
| } |
|
|
| |
| #[pyclass] |
| #[derive(Debug, Clone, Serialize, Deserialize)] |
| pub struct QuantumCircuit { |
| #[pyo3(get, set)] |
| pub n_qubits: usize, |
| pub gates: Vec<Gate>, |
| #[pyo3(get, set)] |
| pub name: Option<String>, |
| #[serde(default)] |
| pub schema_version: Option<u32>, |
| } |
|
|
| |
| #[derive(Error, Debug)] |
| pub enum CircuitError { |
| #[error("Invalid qubit index: {0} for {1}-qubit circuit")] |
| InvalidQubit(usize, usize), |
| #[error("Gate requires {expected} qubits but got {actual}")] |
| WrongQubitCount { expected: usize, actual: usize }, |
| #[error("Circuit serialization error: {0}")] |
| SerializationError(String), |
| #[error("Missing circuit schema_version; expected {expected}")] |
| MissingSchemaVersion { |
| expected: u32, |
| }, |
| #[error("Unsupported circuit schema_version: expected {expected}, got {actual}")] |
| UnsupportedSchemaVersion { |
| expected: u32, |
| actual: u32, |
| }, |
| } |
|
|
| #[pymethods] |
| impl QuantumCircuit { |
| |
| #[new] |
| pub fn new(n_qubits: usize) -> Self { |
| Self { |
| n_qubits, |
| gates: Vec::new(), |
| name: None, |
| schema_version: None, |
| } |
| } |
|
|
| |
| pub fn add_gate_json(&mut self, json: &str) -> PyResult<()> { |
| let gate: Gate = serde_json::from_str(json) |
| .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid gate JSON: {}", e)))?; |
| |
| self.add_gate(gate).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; |
| Ok(()) |
| } |
|
|
| |
| pub fn to_json_py(&self) -> PyResult<String> { |
| self.to_json().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string())) |
| } |
| } |
|
|
| impl QuantumCircuit { |
| |
| pub fn add_gate(&mut self, gate: Gate) -> Result<(), CircuitError> { |
| |
| for &qubit in &gate.qubits { |
| if qubit >= self.n_qubits { |
| return Err(CircuitError::InvalidQubit(qubit, self.n_qubits)); |
| } |
| } |
|
|
| |
| let expected_qubits = match gate.gate_type { |
| GateType::I | GateType::X | GateType::Y | GateType::Z |
| | GateType::H | GateType::S | GateType::Sdg | GateType::T | GateType::Tdg => 1, |
| GateType::RX(_) | GateType::RY(_) | GateType::RZ(_) | GateType::P(_) => 1, |
| GateType::U3(_, _, _) => 1, |
| GateType::CX | GateType::CY | GateType::CZ => 2, |
| GateType::CCX => 3, |
| }; |
|
|
| if gate.qubits.len() != expected_qubits { |
| return Err(CircuitError::WrongQubitCount { |
| expected: expected_qubits, |
| actual: gate.qubits.len(), |
| }); |
| } |
|
|
| self.gates.push(gate); |
| Ok(()) |
| } |
|
|
| |
| pub fn depth(&self) -> usize { |
| let mut qubit_last_used = vec![0; self.n_qubits]; |
| let mut current_depth = 0; |
|
|
| for gate in &self.gates { |
| let gate_start = gate.qubits.iter() |
| .map(|&q| qubit_last_used[q]) |
| .max() |
| .unwrap_or(0) + 1; |
|
|
| for &qubit in &gate.qubits { |
| qubit_last_used[qubit] = gate_start; |
| } |
|
|
| current_depth = current_depth.max(gate_start); |
| } |
|
|
| current_depth |
| } |
|
|
| |
| pub fn gate_counts(&self) -> HashMap<String, usize> { |
| let mut counts = HashMap::new(); |
| for gate in &self.gates { |
| let gate_name = match gate.gate_type { |
| GateType::I => "i", |
| GateType::X => "x", |
| GateType::Y => "y", |
| GateType::Z => "z", |
| GateType::H => "h", |
| GateType::S => "s", |
| GateType::Sdg => "sdg", |
| GateType::T => "t", |
| GateType::Tdg => "tdg", |
| GateType::RX(_) => "rx", |
| GateType::RY(_) => "ry", |
| GateType::RZ(_) => "rz", |
| GateType::P(_) => "p", |
| GateType::U3(_, _, _) => "u3", |
| GateType::CX => "cx", |
| GateType::CY => "cy", |
| GateType::CZ => "cz", |
| GateType::CCX => "ccx", |
| }.to_string(); |
|
|
| *counts.entry(gate_name).or_insert(0) += 1; |
| } |
| counts |
| } |
|
|
| |
| pub fn to_json(&self) -> Result<String, CircuitError> { |
| serde_json::to_string(self) |
| .map_err(|e| CircuitError::SerializationError(e.to_string())) |
| } |
|
|
| |
| pub fn from_json(json: &str) -> Result<Self, CircuitError> { |
| let mut circuit: QuantumCircuit = serde_json::from_str(json) |
| .map_err(|e| CircuitError::SerializationError(e.to_string()))?; |
| let version = circuit |
| .schema_version |
| .ok_or(CircuitError::MissingSchemaVersion { |
| expected: SUPPORTED_SCHEMA_VERSION, |
| })?; |
| if version != SUPPORTED_SCHEMA_VERSION { |
| return Err(CircuitError::UnsupportedSchemaVersion { |
| expected: SUPPORTED_SCHEMA_VERSION, |
| actual: version, |
| }); |
| } |
| Ok(circuit) |
| } |
| } |
|
|
| impl Gate { |
| |
| pub fn single(gate_type: GateType, qubit: usize) -> Self { |
| let parameters = match gate_type { |
| GateType::RX(theta) | GateType::RY(theta) | GateType::RZ(theta) | GateType::P(theta) => vec![theta], |
| GateType::U3(theta, phi, lambda) => vec![theta, phi, lambda], |
| _ => vec![], |
| }; |
|
|
| Self { |
| gate_type, |
| qubits: vec![qubit], |
| parameters, |
| } |
| } |
|
|
| |
| pub fn double(gate_type: GateType, control: usize, target: usize) -> Self { |
| Self { |
| gate_type, |
| qubits: vec![control, target], |
| parameters: vec![], |
| } |
| } |
|
|
| |
| pub fn triple(gate_type: GateType, q0: usize, q1: usize, q2: usize) -> Self { |
| Self { |
| gate_type, |
| qubits: vec![q0, q1, q2], |
| parameters: vec![], |
| } |
| } |
| } |