pub mod operations;
pub mod parser;
pub mod builder;
pub mod parser_demo;
pub use operations::*;
pub use parser::*;
pub use builder::*;
use crate::{QvmError, Result};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Qubit(pub usize);
impl Qubit {
pub fn new(index: usize) -> Self {
Self(index)
}
pub fn index(&self) -> usize {
self.0
}
}
impl From<usize> for Qubit {
fn from(index: usize) -> Self {
Self(index)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ClassicalBit(pub usize);
impl ClassicalBit {
pub fn new(index: usize) -> Self {
Self(index)
}
pub fn index(&self) -> usize {
self.0
}
}
impl From<usize> for ClassicalBit {
fn from(index: usize) -> Self {
Self(index)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantumCircuit {
pub name: String,
pub num_qubits: usize,
pub num_classical: usize,
pub operations: Vec<Operation>,
pub metadata: CircuitMetadata,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CircuitMetadata {
pub estimated_time: Option<f64>,
pub connectivity_requirements: Vec<(Qubit, Qubit)>,
pub measurement_requirements: Vec<Qubit>,
pub priority: i32,
pub tags: Vec<String>,
}
impl QuantumCircuit {
pub fn new(name: String, num_qubits: usize, num_classical: usize) -> Self {
Self {
name,
num_qubits,
num_classical,
operations: Vec::new(),
metadata: CircuitMetadata::default(),
}
}
pub fn from_qasm(qasm: &str) -> Result<Self> {
parser::parse_qasm3(qasm)
}
pub fn to_qasm(&self) -> Result<String> {
let mut qasm = String::new();
qasm.push_str(&format!("// Circuit: {}\n", self.name));
qasm.push_str("OPENQASM 3.0;\n");
qasm.push_str("include \"stdgates.inc\";\n\n");
if self.num_qubits > 0 {
qasm.push_str(&format!("qubit[{}] q;\n", self.num_qubits));
}
if self.num_classical > 0 {
qasm.push_str(&format!("bit[{}] c;\n", self.num_classical));
}
if self.num_qubits > 0 || self.num_classical > 0 {
qasm.push_str("\n");
}
for operation in &self.operations {
qasm.push_str(&operation.to_qasm());
qasm.push_str("\n");
}
Ok(qasm)
}
pub fn add_operation(&mut self, operation: Operation) {
self.validate_operation(&operation).expect("Invalid operation for circuit");
self.operations.push(operation);
}
pub fn validate_operation(&self, operation: &Operation) -> Result<()> {
match operation {
Operation::SingleQubit { qubit, .. } => {
if qubit.index() >= self.num_qubits {
return Err(QvmError::invalid_circuit(
format!("Qubit {} out of range for circuit with {} qubits",
qubit.index(), self.num_qubits)
));
}
}
Operation::TwoQubit { control, target, .. } => {
if control.index() >= self.num_qubits || target.index() >= self.num_qubits {
return Err(QvmError::invalid_circuit("Qubit out of range".to_string()));
}
if control == target {
return Err(QvmError::invalid_circuit("Control and target cannot be the same".to_string()));
}
}
Operation::Measurement { qubit, classical, .. } => {
if qubit.index() >= self.num_qubits {
return Err(QvmError::invalid_circuit("Measurement qubit out of range".to_string()));
}
if classical.index() >= self.num_classical {
return Err(QvmError::invalid_circuit("Classical bit out of range".to_string()));
}
}
_ => {}
}
Ok(())
}
pub fn used_qubits(&self) -> Vec<Qubit> {
let mut used = std::collections::HashSet::new();
for op in &self.operations {
op.qubits().into_iter().for_each(|q| { used.insert(q); });
}
let mut result: Vec<_> = used.into_iter().collect();
result.sort_by_key(|q| q.index());
result
}
pub fn depth(&self) -> usize {
self.operations.len()
}
pub fn two_qubit_gate_count(&self) -> usize {
self.operations.iter()
.filter(|op| matches!(op, Operation::TwoQubit { .. }))
.count()
}
pub fn uses_qubit(&self, qubit: Qubit) -> bool {
self.operations.iter()
.any(|op| op.qubits().contains(&qubit))
}
pub fn two_qubit_interactions(&self) -> Vec<(Qubit, Qubit)> {
self.operations.iter()
.filter_map(|op| {
if let Operation::TwoQubit { control, target, .. } = op {
Some((*control, *target))
} else {
None
}
})
.collect()
}
}
impl Default for QuantumCircuit {
fn default() -> Self {
Self::new("circuit".to_string(), 0, 0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circuit_creation() {
let circuit = QuantumCircuit::new("test".to_string(), 3, 2);
assert_eq!(circuit.name, "test");
assert_eq!(circuit.num_qubits, 3);
assert_eq!(circuit.num_classical, 2);
}
#[test]
fn test_qubit_validation() {
let mut circuit = QuantumCircuit::new("test".to_string(), 2, 1);
let valid_op = Operation::SingleQubit {
gate: SingleQubitGate::X,
qubit: Qubit(0),
parameters: SmallVec::new(),
};
circuit.add_operation(valid_op);
assert_eq!(circuit.operations.len(), 1);
let invalid_op = Operation::SingleQubit {
gate: SingleQubitGate::X,
qubit: Qubit(5),
parameters: SmallVec::new(),
};
assert!(circuit.validate_operation(&invalid_op).is_err());
}
#[test]
fn test_qasm_generation() {
let mut circuit = QuantumCircuit::new("test".to_string(), 2, 1);
circuit.add_operation(Operation::SingleQubit {
gate: SingleQubitGate::X,
qubit: Qubit(0),
parameters: SmallVec::new(),
});
let qasm = circuit.to_qasm().unwrap();
assert!(qasm.contains("OPENQASM 3.0"));
assert!(qasm.contains("qubit[2] q"));
assert!(qasm.contains("x q[0]"));
}
#[test]
fn test_used_qubits() {
let mut circuit = QuantumCircuit::new("test".to_string(), 5, 0);
circuit.add_operation(Operation::SingleQubit {
gate: SingleQubitGate::X,
qubit: Qubit(0),
parameters: SmallVec::new(),
});
circuit.add_operation(Operation::SingleQubit {
gate: SingleQubitGate::H,
qubit: Qubit(3),
parameters: SmallVec::new(),
});
let used = circuit.used_qubits();
assert_eq!(used, vec![Qubit(0), Qubit(3)]);
}
}