quil_rs/program/analysis/
qubit_graph.rs1use std::collections::HashMap;
18
19use crate::instruction::{Instruction, InstructionHandler, InstructionRole};
20use crate::quil::Quil;
21use petgraph::{graph::DiGraph, Direction};
22
23use super::BasicBlock;
24
25#[derive(Debug, thiserror::Error)]
26pub enum QubitGraphError {
27 #[error("Unsupported instruction: {}", .0.to_quil_or_debug())]
28 UnsupportedInstruction(Instruction),
29}
30
31#[derive(Debug)]
38pub struct QubitGraph<'a> {
39 graph: DiGraph<&'a Instruction, ()>,
40}
41
42impl<'a> QubitGraph<'a> {
43 pub(crate) fn new(
44 instructions: impl Iterator<Item = &'a Instruction>,
45 ) -> Result<Self, QubitGraphError> {
46 let mut last_instruction_for_qubit = HashMap::new();
47 let mut graph = DiGraph::new();
48 let mut handler = InstructionHandler::default();
49
50 for instruction in instructions {
51 match handler.role_for_instruction(instruction) {
52 InstructionRole::ClassicalCompute => {
53 if let Instruction::Pragma(_) = instruction {
54 return Err(QubitGraphError::UnsupportedInstruction(instruction.clone()));
55 }
56 } InstructionRole::ControlFlow => match &instruction {
58 Instruction::Jump(_)
59 | Instruction::JumpWhen(_)
60 | Instruction::JumpUnless(_) => {
61 return Err(QubitGraphError::UnsupportedInstruction(instruction.clone()))
62 }
63 _ => {}
64 },
65 InstructionRole::ProgramComposition => {} InstructionRole::RFControl => {
67 return Err(QubitGraphError::UnsupportedInstruction(instruction.clone()))
68 }
69 }
70
71 let qubits: Vec<_> = instruction.get_qubits().into_iter().collect();
72
73 let node = graph.add_node(instruction);
74
75 for qubit in qubits {
76 if let Some(last_instruction) = last_instruction_for_qubit.insert(qubit, node) {
77 graph.add_edge(last_instruction, node, ());
78 }
79 }
80 }
81
82 Ok(Self { graph })
83 }
84
85 fn path_fold<T, F>(&self, initial_value: T, mut f: F) -> Vec<T>
125 where
126 T: Clone + std::fmt::Debug,
127 F: FnMut(T, &Instruction) -> T,
128 {
129 let nodes: Vec<_> = self.graph.externals(Direction::Incoming).collect();
130 let mut stack = vec![(initial_value, nodes)];
131 let mut result = Vec::new();
132
133 while let Some((acc, nodes)) = stack.pop() {
134 if nodes.is_empty() {
135 result.push(acc);
136 continue;
137 }
138
139 for node in nodes {
140 let instruction = &self.graph[node];
141 let value = f(acc.clone(), instruction);
142 stack.push((
143 value,
144 self.graph
145 .neighbors_directed(node, Direction::Outgoing)
146 .collect(),
147 ));
148 }
149 }
150
151 result
152 }
153
154 pub fn gate_depth(&self, gate_minimum_qubit_count: usize) -> usize {
161 let path_lengths = self.path_fold(0, |depth: usize, instruction: &Instruction| -> usize {
162 if let Instruction::Gate(gate) = instruction {
163 if gate.qubits.len() >= gate_minimum_qubit_count {
164 return depth + 1;
165 }
166 }
167 depth
168 });
169 path_lengths.into_iter().max().unwrap_or_default()
170 }
171}
172
173impl<'a> TryFrom<&'_ BasicBlock<'a>> for QubitGraph<'a> {
174 type Error = QubitGraphError;
175
176 fn try_from(block: &BasicBlock<'a>) -> Result<Self, Self::Error> {
177 QubitGraph::new(block.instructions().iter().copied())
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use crate::Program;
184 use rstest::rstest;
185
186 use super::*;
187
188 use super::super::test_programs::*;
189
190 #[rstest]
191 #[case(QUIL_AS_TREE, 2)]
192 #[case(QUIL_AS_INVERSE_TREE, 2)]
193 #[case(QUIL_AS_LINEAR, 4)]
194 #[case(QUIL_WITH_DIAMOND, 6)]
195 #[case(QUIL_WITH_SWAP, 3)]
196 #[case(KITCHEN_SINK_QUIL, 2)]
197 fn gate_depth(#[case] input: &str, #[case] expected: usize) {
198 let program: Program = input.parse().unwrap();
199 let block: BasicBlock = (&program).try_into().unwrap();
200 let graph: QubitGraph = (&block).try_into().unwrap();
201 let depth = graph.gate_depth(1);
202 assert_eq!(expected, depth);
203 }
204
205 #[rstest]
206 #[case(QUIL_AS_TREE, 1)]
207 #[case(QUIL_AS_INVERSE_TREE, 1)]
208 #[case(QUIL_AS_LINEAR, 0)]
209 #[case(QUIL_WITH_DIAMOND, 2)]
210 #[case(QUIL_WITH_SWAP, 1)]
211 #[case(KITCHEN_SINK_QUIL, 1)]
212 fn multiqubit_gate_depth(#[case] input: &str, #[case] expected: usize) {
213 let program: Program = input.parse().unwrap();
214 let block: BasicBlock = (&program).try_into().unwrap();
215 let graph: QubitGraph = (&block).try_into().unwrap();
216 let depth = graph.gate_depth(2);
217 assert_eq!(expected, depth);
218 }
219
220 #[rstest]
221 #[case(QUIL_AS_TREE, Some(2))]
222 #[case(QUIL_AS_INVERSE_TREE, Some(2))]
223 #[case(QUIL_AS_LINEAR, Some(4))]
224 #[case(QUIL_WITH_DIAMOND, Some(6))]
225 #[case(QUIL_WITH_SWAP, Some(3))]
226 #[case(KITCHEN_SINK_QUIL, Some(2))]
227 #[case(QUIL_WITH_JUMP, None)]
228 #[case(QUIL_WITH_JUMP_WHEN, None)]
229 #[case(QUIL_WITH_JUMP_UNLESS, None)]
230 fn gate_depth_conditional(#[case] input: &str, #[case] expected: Option<usize>) {
231 let program: Program = input.parse().unwrap();
232 let block = (&program).try_into();
233 let block: BasicBlock = match block {
234 Ok(block) => block,
235 Err(_) => {
236 if expected.is_none() {
237 return;
238 } else {
239 panic!("Expected block, got error");
240 }
241 }
242 };
243
244 let maybe_graph: Result<QubitGraph, _> = (&block).try_into();
245 match maybe_graph {
246 Ok(graph) => {
247 let depth = graph.gate_depth(1);
248 assert_eq!(expected, Some(depth));
249 }
250 Err(_) => {
251 assert_eq!(expected, None)
252 }
253 }
254 }
255}