|
| 1 | +//! Resource tracking within HUGR DFG subgraphs. |
| 2 | +//! |
| 3 | +//! This module implements the resource tracking system. It provides |
| 4 | +//! facilities for tracking linear resources (such as qubits) through quantum |
| 5 | +//! circuits represented as HUGR subgraphs. |
| 6 | +//! |
| 7 | +//! # Overview |
| 8 | +//! |
| 9 | +//! HUGR has a notion of "Value": the data that corresponds to a wire within a |
| 10 | +//! dataflow graph. It further has a notion of "linear value" a.k.a non-copyable |
| 11 | +//! value: a value that cannot be copied or discarded (implicitly). |
| 12 | +//! |
| 13 | +//! As far as HUGR is concerned, a linear value (or any value, for that matter) |
| 14 | +//! is born at an op's output and dies at the next op's input. TKET introduces |
| 15 | +//! the notion of "Resource" to extend the lifetime of a linear value over |
| 16 | +//! multiple ops. |
| 17 | +//! |
| 18 | +//! If a linear value appears both in an op's input and output, we say that it |
| 19 | +//! is "resource-preserving". Using [`ResourceFlow`], we can track resources |
| 20 | +//! as they "flow" through multiple operations. The chains of |
| 21 | +//! resource-preserving ops acting on a same resource form a so-called resource |
| 22 | +//! path. |
| 23 | +//! |
| 24 | +//! # Resources and Copyable Values |
| 25 | +//! |
| 26 | +//! Resource tracking distinguishes between two types of values: |
| 27 | +//! |
| 28 | +//! - **Linear resources**: Non-copyable values that form resource paths through |
| 29 | +//! the circuit. Each resource has a unique [`ResourceId`] and operations on |
| 30 | +//! the same resource are ordered by a [`Position`]. |
| 31 | +//! - **Copyable values**: Regular values that can be copied and discarded |
| 32 | +//! freely. Each is identified by a unique [`CopyableValueId`]. |
| 33 | +//! |
| 34 | +//! # Resource Scope |
| 35 | +//! |
| 36 | +//! Tracking resources is not free: there is a one-off linear cost to compute |
| 37 | +//! the resource paths, plus a linear memory cost to store them. |
| 38 | +//! |
| 39 | +//! Use a [`SiblingSubgraph`] to define a region of a `HUGR`, within which |
| 40 | +//! resources should be tracked. You can then construct a resource-tracked scope |
| 41 | +//! using [`ResourceScope::new`]. |
| 42 | +
|
| 43 | +// Public API exports |
| 44 | +pub use flow::{DefaultResourceFlow, ResourceFlow, UnsupportedOp}; |
| 45 | +pub use scope::{ResourceScope, ResourceScopeConfig}; |
| 46 | +pub use types::{CopyableValueId, OpValue, Position, ResourceAllocator, ResourceId}; |
| 47 | + |
| 48 | +// Internal modules |
| 49 | +mod flow; |
| 50 | +mod scope; |
| 51 | +mod types; |
| 52 | + |
| 53 | +#[cfg(test)] |
| 54 | +mod tests { |
| 55 | + use hugr::{ |
| 56 | + builder::{DFGBuilder, Dataflow, DataflowHugr}, |
| 57 | + extension::prelude::qb_t, |
| 58 | + hugr::views::SiblingSubgraph, |
| 59 | + ops::handle::DataflowParentID, |
| 60 | + types::Signature, |
| 61 | + CircuitUnit, Hugr, |
| 62 | + }; |
| 63 | + |
| 64 | + use itertools::Itertools; |
| 65 | + use rstest::rstest; |
| 66 | + |
| 67 | + use crate::{ |
| 68 | + extension::rotation::{rotation_type, ConstRotation}, |
| 69 | + resource::scope::tests::ResourceScopeReport, |
| 70 | + TketOp, |
| 71 | + }; |
| 72 | + |
| 73 | + use super::ResourceScope; |
| 74 | + |
| 75 | + // Gate being commuted has a non-linear input |
| 76 | + fn circ(n_qubits: usize, add_rz: bool, add_const_rz: bool) -> Hugr { |
| 77 | + let build = || { |
| 78 | + let out_qb_row = vec![qb_t(); n_qubits]; |
| 79 | + let mut inp_qb_row = out_qb_row.clone(); |
| 80 | + if add_rz { |
| 81 | + inp_qb_row.push(rotation_type()); |
| 82 | + }; |
| 83 | + let mut dfg = DFGBuilder::new(Signature::new(inp_qb_row, out_qb_row))?; |
| 84 | + |
| 85 | + let (qubits, f) = if add_rz { |
| 86 | + let mut inputs = dfg.input_wires().collect_vec(); |
| 87 | + let f = inputs.pop().unwrap(); |
| 88 | + (inputs, Some(f)) |
| 89 | + } else { |
| 90 | + (dfg.input_wires().collect_vec(), None) |
| 91 | + }; |
| 92 | + |
| 93 | + let mut circ = dfg.as_circuit(qubits); |
| 94 | + |
| 95 | + for i in 0..n_qubits { |
| 96 | + circ.append(TketOp::H, [i])?; |
| 97 | + } |
| 98 | + for i in (0..n_qubits).step_by(2) { |
| 99 | + if i + 1 < n_qubits { |
| 100 | + circ.append(TketOp::CX, [i, i + 1])?; |
| 101 | + } |
| 102 | + } |
| 103 | + if let Some(f) = f { |
| 104 | + for i in 0..n_qubits { |
| 105 | + circ.append_and_consume( |
| 106 | + TketOp::Rz, |
| 107 | + [CircuitUnit::Linear(i), CircuitUnit::Wire(f)], |
| 108 | + )?; |
| 109 | + } |
| 110 | + } |
| 111 | + if add_const_rz { |
| 112 | + let const_angle = circ.add_constant(ConstRotation::PI_2); |
| 113 | + for i in 0..n_qubits { |
| 114 | + circ.append_and_consume( |
| 115 | + TketOp::Rz, |
| 116 | + [CircuitUnit::Linear(i), CircuitUnit::Wire(const_angle)], |
| 117 | + )?; |
| 118 | + } |
| 119 | + } |
| 120 | + let qbs = circ.finish(); |
| 121 | + dfg.finish_hugr_with_outputs(qbs) |
| 122 | + }; |
| 123 | + build().unwrap() |
| 124 | + } |
| 125 | + |
| 126 | + #[rstest] |
| 127 | + #[case(2, false, false)] |
| 128 | + #[case(2, true, false)] |
| 129 | + #[case(2, false, true)] |
| 130 | + #[case(2, true, true)] |
| 131 | + #[case(4, false, false)] |
| 132 | + #[case(4, true, false)] |
| 133 | + #[case(4, false, true)] |
| 134 | + #[case(4, true, true)] |
| 135 | + fn test_resource_scope_creation( |
| 136 | + #[case] n_qubits: usize, |
| 137 | + #[case] add_rz: bool, |
| 138 | + #[case] add_const_rz: bool, |
| 139 | + ) { |
| 140 | + let circ = circ(n_qubits, add_rz, add_const_rz); |
| 141 | + let subgraph = |
| 142 | + SiblingSubgraph::try_new_dataflow_subgraph::<_, DataflowParentID>(&circ).unwrap(); |
| 143 | + let scope = ResourceScope::new(&circ, subgraph); |
| 144 | + let info = ResourceScopeReport::from(&scope); |
| 145 | + |
| 146 | + let mut name = format!("{n_qubits}_qubits"); |
| 147 | + if add_rz { |
| 148 | + name.push('_'); |
| 149 | + name.push_str("add_rz"); |
| 150 | + } |
| 151 | + if add_const_rz { |
| 152 | + name.push('_'); |
| 153 | + name.push_str("add_const_rz"); |
| 154 | + } |
| 155 | + |
| 156 | + assert_eq!(info.resource_paths.len(), n_qubits); |
| 157 | + assert_eq!(info.n_copyable, add_const_rz as usize + add_rz as usize); |
| 158 | + insta::assert_snapshot!(name, info); |
| 159 | + } |
| 160 | +} |
0 commit comments