use std::collections::HashMap;
use crate::instruction::{Instruction, InstructionHandler, InstructionRole};
use crate::quil::Quil;
use petgraph::{graph::DiGraph, Direction};
use super::BasicBlock;
#[derive(Debug, thiserror::Error)]
pub enum QubitGraphError {
#[error("Unsupported instruction: {}", .0.to_quil_or_debug())]
UnsupportedInstruction(Instruction),
}
#[derive(Debug)]
pub struct QubitGraph<'a> {
graph: DiGraph<&'a Instruction, ()>,
}
impl<'a> QubitGraph<'a> {
pub(crate) fn new<H: InstructionHandler>(
instructions: impl Iterator<Item = &'a Instruction>,
handler: &H,
) -> Result<Self, QubitGraphError> {
let mut last_instruction_for_qubit = HashMap::new();
let mut graph = DiGraph::new();
for instruction in instructions {
match handler.role(instruction) {
InstructionRole::ClassicalCompute => {
if let Instruction::Pragma(_) = instruction {
return Err(QubitGraphError::UnsupportedInstruction(instruction.clone()));
}
} InstructionRole::ControlFlow => match &instruction {
Instruction::Jump(_)
| Instruction::JumpWhen(_)
| Instruction::JumpUnless(_) => {
return Err(QubitGraphError::UnsupportedInstruction(instruction.clone()))
}
_ => {}
},
InstructionRole::ProgramComposition => {} InstructionRole::RFControl => {
return Err(QubitGraphError::UnsupportedInstruction(instruction.clone()))
}
}
let qubits: Vec<_> = instruction.get_qubits().into_iter().collect();
let node = graph.add_node(instruction);
for qubit in qubits {
if let Some(last_instruction) = last_instruction_for_qubit.insert(qubit, node) {
graph.add_edge(last_instruction, node, ());
}
}
}
Ok(Self { graph })
}
pub fn try_from_basic_block<H: InstructionHandler>(
block: &BasicBlock<'a>,
handler: &H,
) -> Result<Self, QubitGraphError> {
QubitGraph::new(block.instructions().iter().copied(), handler)
}
fn path_fold<T, F>(&self, initial_value: T, mut f: F) -> Vec<T>
where
T: Clone + std::fmt::Debug,
F: FnMut(T, &Instruction) -> T,
{
let nodes: Vec<_> = self.graph.externals(Direction::Incoming).collect();
let mut stack = vec![(initial_value, nodes)];
let mut result = Vec::new();
while let Some((acc, nodes)) = stack.pop() {
if nodes.is_empty() {
result.push(acc);
continue;
}
for node in nodes {
let instruction = &self.graph[node];
let value = f(acc.clone(), instruction);
stack.push((
value,
self.graph
.neighbors_directed(node, Direction::Outgoing)
.collect(),
));
}
}
result
}
pub fn gate_depth(&self, gate_minimum_qubit_count: usize) -> usize {
let path_lengths = self.path_fold(0, |depth: usize, instruction: &Instruction| -> usize {
if let Instruction::Gate(gate) = instruction {
if gate.qubits.len() >= gate_minimum_qubit_count {
return depth + 1;
}
}
depth
});
path_lengths.into_iter().max().unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use crate::instruction::DefaultHandler;
use crate::Program;
use rstest::rstest;
use super::*;
use super::super::test_programs::*;
#[rstest]
#[case(QUIL_AS_TREE, 2)]
#[case(QUIL_AS_INVERSE_TREE, 2)]
#[case(QUIL_AS_LINEAR, 4)]
#[case(QUIL_WITH_DIAMOND, 6)]
#[case(QUIL_WITH_SWAP, 3)]
#[case(KITCHEN_SINK_QUIL, 2)]
fn gate_depth(#[case] input: &str, #[case] expected: usize) {
let program: Program = input.parse().unwrap();
let block: BasicBlock = (&program).try_into().unwrap();
let graph = QubitGraph::try_from_basic_block(&block, &DefaultHandler).unwrap();
let depth = graph.gate_depth(1);
assert_eq!(expected, depth);
}
#[rstest]
#[case(QUIL_AS_TREE, 1)]
#[case(QUIL_AS_INVERSE_TREE, 1)]
#[case(QUIL_AS_LINEAR, 0)]
#[case(QUIL_WITH_DIAMOND, 2)]
#[case(QUIL_WITH_SWAP, 1)]
#[case(KITCHEN_SINK_QUIL, 1)]
fn multiqubit_gate_depth(#[case] input: &str, #[case] expected: usize) {
let program: Program = input.parse().unwrap();
let block: BasicBlock = (&program).try_into().unwrap();
let graph = QubitGraph::try_from_basic_block(&block, &DefaultHandler).unwrap();
let depth = graph.gate_depth(2);
assert_eq!(expected, depth);
}
#[rstest]
#[case(QUIL_AS_TREE, Some(2))]
#[case(QUIL_AS_INVERSE_TREE, Some(2))]
#[case(QUIL_AS_LINEAR, Some(4))]
#[case(QUIL_WITH_DIAMOND, Some(6))]
#[case(QUIL_WITH_SWAP, Some(3))]
#[case(KITCHEN_SINK_QUIL, Some(2))]
#[case(QUIL_WITH_JUMP, None)]
#[case(QUIL_WITH_JUMP_WHEN, None)]
#[case(QUIL_WITH_JUMP_UNLESS, None)]
fn gate_depth_conditional(#[case] input: &str, #[case] expected: Option<usize>) {
let program: Program = input.parse().unwrap();
let block = (&program).try_into();
let block: BasicBlock = match block {
Ok(block) => block,
Err(_) => {
if expected.is_none() {
return;
} else {
panic!("Expected block, got error");
}
}
};
let maybe_graph = QubitGraph::try_from_basic_block(&block, &DefaultHandler);
match maybe_graph {
Ok(graph) => {
let depth = graph.gate_depth(1);
assert_eq!(expected, Some(depth));
}
Err(_) => {
assert_eq!(expected, None)
}
}
}
}