pub mod mapping;
pub mod output;
pub mod timing;
pub use mapping::*;
pub use output::*;
pub use timing::*;
use crate::{QvmError, Result, QuantumCircuit, Topology, Qubit};
use crate::scheduler::{Schedule, Assignment};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompositeCircuit {
circuits: Vec<ComposedCircuit>,
metadata: CompositeMetadata,
total_duration: u64,
resource_summary: ResourceSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComposedCircuit {
pub job_id: usize,
pub circuit: QuantumCircuit,
pub timing: CircuitTiming,
pub mapping: ResourceMapping,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CompositeMetadata {
pub created_at: u64,
pub circuit_count: usize,
pub scheduler_algorithm: String,
pub composition_strategy: String,
pub quality_metrics: QualityMetrics,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResourceSummary {
pub total_qubits_used: usize,
pub peak_qubit_usage: usize,
pub total_classical_used: usize,
pub utilization_efficiency: f64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QualityMetrics {
pub expected_fidelity: f64,
pub crosstalk_estimate: f64,
pub timing_precision: f64,
pub optimality_score: f64,
}
impl CompositeCircuit {
pub fn new() -> Self {
Self {
circuits: Vec::new(),
metadata: CompositeMetadata::default(),
total_duration: 0,
resource_summary: ResourceSummary::default(),
}
}
pub fn add_circuit(&mut self, circuit: ComposedCircuit) {
self.total_duration = self.total_duration.max(
circuit.timing.start_time + circuit.timing.duration
);
self.circuits.push(circuit);
self.update_metadata();
}
pub fn circuits(&self) -> &[ComposedCircuit] {
&self.circuits
}
pub fn circuit_count(&self) -> usize {
self.circuits.len()
}
pub fn total_duration(&self) -> u64 {
self.total_duration
}
pub fn total_qubits(&self) -> usize {
self.resource_summary.total_qubits_used
}
pub fn total_cbits(&self) -> usize {
self.resource_summary.total_classical_used
}
pub fn to_qasm(&self) -> Result<String> {
let output_generator = OutputGenerator::new();
output_generator.generate_qasm(self)
}
fn update_metadata(&mut self) {
self.metadata.circuit_count = self.circuits.len();
let mut used_qubits = std::collections::HashSet::new();
let mut used_classical = std::collections::HashSet::new();
for circuit in &self.circuits {
for &qubit in &circuit.mapping.qubit_mapping {
used_qubits.insert(qubit);
}
for &classical in &circuit.mapping.classical_mapping {
used_classical.insert(classical);
}
}
self.resource_summary.total_qubits_used = used_qubits.len();
self.resource_summary.total_classical_used = used_classical.len();
self.resource_summary.peak_qubit_usage = used_qubits.len();
self.update_quality_metrics();
}
fn update_quality_metrics(&mut self) {
if self.circuits.is_empty() {
return;
}
let avg_fidelity: f64 = self.circuits.iter()
.map(|c| c.circuit.operations.len() as f64 * 0.99) .sum::<f64>() / self.circuits.len() as f64;
self.metadata.quality_metrics.expected_fidelity = avg_fidelity.min(1.0);
let durations: Vec<u64> = self.circuits.iter()
.map(|c| c.timing.duration)
.collect();
if let (Some(&min_dur), Some(&max_dur)) = (durations.iter().min(), durations.iter().max()) {
self.metadata.quality_metrics.timing_precision = if max_dur > 0 {
1.0 - (max_dur - min_dur) as f64 / max_dur as f64
} else {
1.0
};
}
self.metadata.quality_metrics.optimality_score = 0.8;
}
}
impl Default for CompositeCircuit {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CircuitComposer {
topology: Topology,
mapper: QubitMapper,
timer: CircuitTimer,
config: ComposerConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComposerConfig {
pub optimize_timing: bool,
pub optimize_mapping: bool,
pub buffer_strategy: BufferStrategy,
pub output_format: OutputFormat,
pub optimization_level: u8,
}
impl Default for ComposerConfig {
fn default() -> Self {
Self {
optimize_timing: true,
optimize_mapping: true,
buffer_strategy: BufferStrategy::Automatic,
output_format: OutputFormat::OpenQASM3,
optimization_level: 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BufferStrategy {
None,
Automatic,
Manual,
Adaptive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OutputFormat {
OpenQASM3,
OpenQASM2,
Custom,
}
impl CircuitComposer {
pub fn new(topology: Topology) -> Self {
Self {
mapper: QubitMapper::new(&topology),
timer: CircuitTimer::new(),
topology,
config: ComposerConfig::default(),
}
}
pub fn with_config(topology: Topology, config: ComposerConfig) -> Self {
Self {
mapper: QubitMapper::new(&topology),
timer: CircuitTimer::new(),
topology,
config,
}
}
pub async fn compose(&mut self, schedule: Schedule) -> Result<CompositeCircuit> {
let mut composite = CompositeCircuit::new();
composite.metadata.scheduler_algorithm = schedule.metadata.algorithm.clone();
composite.metadata.composition_strategy = "Sequential".to_string();
for assignment in schedule.assignments {
let composed_circuit = self.compose_assignment(assignment).await?;
composite.add_circuit(composed_circuit);
}
if self.config.optimization_level > 0 {
self.optimize_composite(&mut composite).await?;
}
Ok(composite)
}
async fn compose_assignment(&mut self, assignment: Assignment) -> Result<ComposedCircuit> {
let mapping = self.mapper.create_mapping(
&assignment.qubit_mapping,
&assignment.classical_mapping,
)?;
let timing = self.timer.create_timing(
assignment.start_time,
assignment.duration,
);
let circuit = QuantumCircuit::new(
format!("job_{}", assignment.job_id),
assignment.qubit_mapping.len(),
assignment.classical_mapping.len(),
);
Ok(ComposedCircuit {
job_id: assignment.job_id,
circuit,
timing,
mapping,
})
}
async fn optimize_composite(&mut self, composite: &mut CompositeCircuit) -> Result<()> {
if self.config.optimize_timing {
self.optimize_timing(composite).await?;
}
if self.config.optimize_mapping {
self.optimize_mappings(composite).await?;
}
Ok(())
}
async fn optimize_timing(&mut self, composite: &mut CompositeCircuit) -> Result<()> {
composite.circuits.sort_by_key(|c| c.timing.start_time);
let mut current_time = 0;
for circuit in &mut composite.circuits {
if circuit.timing.start_time > current_time {
circuit.timing.start_time = current_time;
}
current_time = circuit.timing.start_time + circuit.timing.duration;
}
composite.total_duration = current_time;
Ok(())
}
async fn optimize_mappings(&mut self, _composite: &mut CompositeCircuit) -> Result<()> {
Ok(())
}
pub fn insert_buffers(&mut self, composite: &mut CompositeCircuit) -> Result<()> {
match self.config.buffer_strategy {
BufferStrategy::None => Ok(()),
BufferStrategy::Automatic => self.insert_automatic_buffers(composite),
BufferStrategy::Manual => self.insert_manual_buffers(composite),
BufferStrategy::Adaptive => self.insert_adaptive_buffers(composite),
}
}
pub fn insert_reset_operations(&mut self, composite: &mut CompositeCircuit) -> Result<()> {
use crate::circuit_ir::Operation;
for i in 1..composite.circuits.len() {
let (share_qubits, shared_qubit_indices) = {
let current_circuit = &composite.circuits[i];
let prev_circuit = &composite.circuits[i - 1];
let share = self.circuits_share_qubits(current_circuit, prev_circuit);
let indices = if share {
self.get_shared_qubits(current_circuit, prev_circuit)
} else {
Vec::new()
};
(share, indices)
};
if share_qubits {
let current_circuit = &mut composite.circuits[i];
for qubit_idx in shared_qubit_indices {
let reset_op = Operation::Reset {
qubit: Qubit(qubit_idx),
};
current_circuit.circuit.operations.insert(0, reset_op);
}
}
}
Ok(())
}
fn get_shared_qubits(&self, circuit1: &ComposedCircuit, circuit2: &ComposedCircuit) -> Vec<usize> {
let qubits1: std::collections::HashSet<_> = circuit1.mapping.qubit_mapping.iter().collect();
let qubits2: std::collections::HashSet<_> = circuit2.mapping.qubit_mapping.iter().collect();
qubits1.intersection(&qubits2).map(|&&q| q).collect()
}
fn insert_automatic_buffers(&mut self, composite: &mut CompositeCircuit) -> Result<()> {
use crate::circuit_ir::Operation;
use smallvec::SmallVec;
for i in 0..composite.circuits.len() {
for j in (i + 1)..composite.circuits.len() {
if self.circuits_share_qubits(&composite.circuits[i], &composite.circuits[j]) {
let shared_qubits = self.get_shared_qubits(&composite.circuits[i], &composite.circuits[j]);
if !shared_qubits.is_empty() {
let barrier_qubits: SmallVec<[Qubit; 4]> = shared_qubits.into_iter().map(Qubit).collect();
let barrier_op = Operation::Barrier {
qubits: barrier_qubits,
};
composite.circuits[j].circuit.operations.insert(0, barrier_op);
}
let buffer_time = 1000; if composite.circuits[j].timing.start_time <
composite.circuits[i].timing.start_time + composite.circuits[i].timing.duration + buffer_time {
composite.circuits[j].timing.start_time =
composite.circuits[i].timing.start_time + composite.circuits[i].timing.duration + buffer_time;
}
}
}
}
Ok(())
}
fn insert_manual_buffers(&mut self, _composite: &mut CompositeCircuit) -> Result<()> {
Ok(())
}
fn insert_adaptive_buffers(&mut self, composite: &mut CompositeCircuit) -> Result<()> {
self.insert_automatic_buffers(composite)
}
fn circuits_share_qubits(&self, circuit1: &ComposedCircuit, circuit2: &ComposedCircuit) -> bool {
let qubits1: std::collections::HashSet<_> = circuit1.mapping.qubit_mapping.iter().collect();
let qubits2: std::collections::HashSet<_> = circuit2.mapping.qubit_mapping.iter().collect();
!qubits1.is_disjoint(&qubits2)
}
pub fn validate_composite(&self, composite: &CompositeCircuit) -> Result<ValidationReport> {
let mut violations = Vec::new();
let mut warnings = Vec::new();
for i in 0..composite.circuits.len() {
for j in (i + 1)..composite.circuits.len() {
let circuit_i = &composite.circuits[i];
let circuit_j = &composite.circuits[j];
if circuit_i.timing.overlaps_with(&circuit_j.timing) {
if self.circuits_share_qubits(circuit_i, circuit_j) {
violations.push(format!(
"Circuits {} and {} have overlapping timing and share resources",
circuit_i.job_id, circuit_j.job_id
));
}
}
let gap = circuit_j.timing.gap_to(&circuit_i.timing).abs();
if gap < 1000 && self.circuits_share_qubits(circuit_i, circuit_j) {
warnings.push(format!(
"Insufficient buffer time ({} μs) between circuits {} and {}",
gap, circuit_i.job_id, circuit_j.job_id
));
}
}
}
let utilization = composite.resource_summary.utilization_efficiency;
if utilization < 0.3 {
warnings.push(format!(
"Low resource utilization: {:.1}%", utilization * 100.0
));
}
Ok(ValidationReport {
is_valid: violations.is_empty(),
violations,
warnings,
total_circuits: composite.circuits.len(),
total_qubits_used: composite.resource_summary.total_qubits_used,
})
}
pub fn statistics(&self) -> ComposerStatistics {
ComposerStatistics {
total_compositions: 0, avg_composition_time: 0.0,
optimization_level: self.config.optimization_level,
buffer_strategy: self.config.buffer_strategy,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComposerStatistics {
pub total_compositions: usize,
pub avg_composition_time: f64,
pub optimization_level: u8,
pub buffer_strategy: BufferStrategy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationReport {
pub is_valid: bool,
pub violations: Vec<String>,
pub warnings: Vec<String>,
pub total_circuits: usize,
pub total_qubits_used: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::topology::TopologyBuilder;
use crate::scheduler::{Assignment, ScheduleMetadata, ResourceUtilization};
#[test]
fn test_composite_circuit_creation() {
let mut composite = CompositeCircuit::new();
assert_eq!(composite.circuit_count(), 0);
assert_eq!(composite.total_duration(), 0);
}
#[test]
fn test_circuit_composer_creation() {
let topology = TopologyBuilder::grid(3, 3);
let composer = CircuitComposer::new(topology);
assert_eq!(composer.config.optimization_level, 2);
}
#[tokio::test]
async fn test_empty_schedule_composition() {
let topology = TopologyBuilder::grid(2, 2);
let mut composer = CircuitComposer::new(topology);
let schedule = Schedule {
assignments: vec![],
metadata: ScheduleMetadata::default(),
total_duration: 0,
utilization: ResourceUtilization::default(),
};
let composite = composer.compose(schedule).await.unwrap();
assert_eq!(composite.circuit_count(), 0);
}
#[tokio::test]
async fn test_single_assignment_composition() {
let topology = TopologyBuilder::grid(2, 2);
let mut composer = CircuitComposer::new(topology);
let assignment = Assignment::new(0, 1, 1000, 5000)
.with_qubit_mapping(vec![0, 1])
.with_classical_mapping(vec![0, 1]);
let schedule = Schedule {
assignments: vec![assignment],
metadata: ScheduleMetadata::default(),
total_duration: 6000,
utilization: ResourceUtilization::default(),
};
let composite = composer.compose(schedule).await.unwrap();
assert_eq!(composite.circuit_count(), 1);
assert_eq!(composite.total_duration(), 6000);
}
}