1use std::collections::BTreeMap;
20use std::fmt;
21
22use crate::{
23 Basis, Bloq, BloqNode, BloqNodeId, CycleDetected, NodeProvenance, PortRole, SubGraph,
24 TemporalPipeRef,
25};
26
27#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct NodeKey {
35 provenance: KeyProvenance,
36 ordinal: u32,
37}
38
39impl NodeKey {
40 #[must_use]
47 pub fn ordinal(&self) -> u32 {
48 self.ordinal
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
58enum KeyProvenance {
59 Blocks(Vec<[i32; 3]>),
60 Pipe {
61 src: [i32; 3],
62 dst: [i32; 3],
63 hadamard: bool,
64 },
65 SpatialPort {
66 source: [i32; 3],
67 role: PortRole,
68 },
69 Padding {
70 src: [i32; 3],
71 dst: [i32; 3],
72 rounds: u32,
73 },
74 Generator(u32),
75 Action(u32),
76 BranchSelector(String),
77 OutputFrame {
78 port: [i32; 3],
79 basis: Basis,
80 },
81}
82
83impl fmt::Display for NodeKey {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let coord = |f: &mut fmt::Formatter<'_>, [x, y, z]: [i32; 3]| write!(f, "({x},{y},{z})");
88 match &self.provenance {
89 KeyProvenance::Blocks(blocks) => {
90 write!(f, "blocks")?;
91 for &block in blocks {
92 write!(f, " ")?;
93 coord(f, block)?;
94 }
95 Ok(())
96 }
97 KeyProvenance::Pipe { src, dst, hadamard } => {
98 write!(f, "pipe ")?;
99 coord(f, *src)?;
100 write!(f, ">")?;
101 coord(f, *dst)?;
102 if *hadamard {
103 write!(f, " H")?;
104 }
105 Ok(())
106 }
107 KeyProvenance::SpatialPort { source, role } => {
108 write!(f, "spatial-port {} ", role.as_str())?;
109 coord(f, *source)
110 }
111 KeyProvenance::Padding { src, dst, rounds } => {
112 write!(f, "padding ")?;
113 coord(f, *src)?;
114 write!(f, ">")?;
115 coord(f, *dst)?;
116 write!(f, " rounds {rounds}")
117 }
118 KeyProvenance::Generator(ordinal) => write!(f, "generator {ordinal}"),
119 KeyProvenance::Action(ordinal) => write!(f, "action {ordinal}"),
120 KeyProvenance::BranchSelector(name) => write!(f, "selector {name}"),
121 KeyProvenance::OutputFrame { port, basis } => {
122 let basis = match basis {
123 Basis::X => "x",
124 Basis::Z => "z",
125 };
126 write!(f, "frame {basis} ")?;
127 coord(f, *port)
128 }
129 }?;
130 write!(f, "#{}", self.ordinal)
131 }
132}
133
134fn pipe_key(pipe: &TemporalPipeRef) -> ([i32; 3], [i32; 3]) {
135 (pipe.src.to_array(), pipe.dst.to_array())
136}
137
138impl BloqNode {
139 #[must_use]
149 pub fn stable_key(&self) -> Option<NodeKey> {
150 let provenance = match &self.provenance {
151 NodeProvenance::BlockComponent { members } => {
152 let mut blocks: Vec<_> =
153 members.iter().map(|member| member.pos.to_array()).collect();
154 blocks.sort_unstable();
155 KeyProvenance::Blocks(blocks)
156 }
157 NodeProvenance::TemporalPipe { pipe } => {
158 let (src, dst) = pipe_key(pipe);
159 KeyProvenance::Pipe {
160 src,
161 dst,
162 hadamard: pipe.hadamard,
163 }
164 }
165 NodeProvenance::SpatialPortSubstitution { source, role } => {
166 KeyProvenance::SpatialPort {
167 source: source.to_array(),
168 role: *role,
169 }
170 }
171 NodeProvenance::MemoryPadding { pipe, rounds } => {
172 let (src, dst) = pipe_key(pipe);
173 KeyProvenance::Padding {
174 src,
175 dst,
176 rounds: *rounds,
177 }
178 }
179 NodeProvenance::Generator { ordinal } => KeyProvenance::Generator(*ordinal),
180 NodeProvenance::Action { ordinal } => KeyProvenance::Action(*ordinal),
181 NodeProvenance::BranchSelector { name } => KeyProvenance::BranchSelector(name.clone()),
182 NodeProvenance::OutputFrame { port, basis } => KeyProvenance::OutputFrame {
183 port: port.to_array(),
184 basis: *basis,
185 },
186 NodeProvenance::None => return None,
187 };
188 Some(NodeKey {
189 provenance,
190 ordinal: 0,
191 })
192 }
193}
194
195impl SubGraph {
196 pub fn stable_keys(&self) -> Result<Vec<(NodeKey, BloqNodeId)>, CycleDetected> {
208 let mut ordinals: BTreeMap<KeyProvenance, u32> = BTreeMap::new();
209 let mut keys = Vec::new();
210 for id in self.deterministic_emit_order()? {
211 let Some(key) = self[id].stable_key() else {
212 continue;
213 };
214 let next = ordinals.entry(key.provenance.clone()).or_default();
215 let ordinal = *next;
216 *next += 1;
217 keys.push((
218 NodeKey {
219 provenance: key.provenance,
220 ordinal,
221 },
222 id,
223 ));
224 }
225 Ok(keys)
226 }
227}
228
229impl Bloq {
230 pub fn stable_key_map(&self) -> Result<BTreeMap<NodeKey, BloqNodeId>, CycleDetected> {
238 Ok(self.top().stable_keys()?.into_iter().collect())
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 fn program(base: u32) -> Bloq {
254 let (a, b, c, d, e) = (base, base + 1, base + 2, base + 3, base + 4);
255 Bloq::from_text(&format!(
256 "\
257BLOQIR 1
258
259graph {{
260 n{a} quantum {{
261 from blocks (1,0,0) (0,0,0)
262 }}
263 n{b} quantum {{
264 from pipe (0,0,0)>(0,0,1)
265 }}
266 n{c} quantum {{
267 from padding (0,0,1)>(0,0,2) rounds 4
268 }}
269 n{d} quantum {{
270 from padding (0,0,1)>(0,0,2) rounds 4
271 }}
272 n{e} quantum {{
273 from blocks (0,0,2)
274 }}
275 n{a} -> n{b} order
276 n{b} -> n{c} order
277 n{c} -> n{d} order
278 n{d} -> n{e} order
279}}
280"
281 ))
282 .expect("valid .bloqir text")
283 }
284
285 #[test]
286 fn keys_name_provenance_and_disambiguate_with_an_ordinal() {
287 let program = program(90);
288
289 let rendered: Vec<_> = program
290 .top()
291 .stable_keys()
292 .expect("acyclic program")
293 .into_iter()
294 .map(|(key, id)| (key.to_string(), id))
295 .collect();
296
297 assert_eq!(
298 rendered,
299 [
300 ("blocks (0,0,0) (1,0,0)#0".to_owned(), BloqNodeId(90)),
301 ("pipe (0,0,0)>(0,0,1)#0".to_owned(), BloqNodeId(91)),
302 (
303 "padding (0,0,1)>(0,0,2) rounds 4#0".to_owned(),
304 BloqNodeId(92)
305 ),
306 (
307 "padding (0,0,1)>(0,0,2) rounds 4#1".to_owned(),
308 BloqNodeId(93)
309 ),
310 ("blocks (0,0,2)#0".to_owned(), BloqNodeId(94)),
311 ],
312 "members sort, and the two padding nodes separate by ordinal"
313 );
314 }
315
316 #[test]
318 fn keys_survive_renumbering() {
319 let plain = program(0).stable_key_map().expect("keyed");
320 let shifted = program(90).stable_key_map().expect("keyed");
321
322 let keys: Vec<_> = plain.keys().collect();
323 assert_eq!(
324 keys,
325 shifted.keys().collect::<Vec<_>>(),
326 "the same source constructs key the same way in both programs"
327 );
328 assert_eq!(plain[keys[0]], BloqNodeId(0));
331 assert_eq!(shifted[keys[0]], BloqNodeId(90));
332 }
333
334 #[test]
335 fn key_map_rejects_a_cyclic_program() {
336 let mut program = program(0);
337 program.add_edge(BloqNodeId(4), BloqNodeId(0), crate::BloqEdge::Order);
338
339 assert_eq!(program.stable_key_map(), Err(CycleDetected));
340 }
341
342 #[test]
343 fn provenance_free_nodes_have_no_key() {
344 let program = Bloq::from_text(
345 "\
346BLOQIR 1
347
348graph {
349 n0 rus in0 {
350 body {
351 }
352 }
353 n1 quantum {
354 from blocks (0,0,0)
355 }
356}
357",
358 )
359 .expect("valid .bloqir text");
360
361 assert_eq!(program[BloqNodeId(0)].stable_key(), None);
362 assert_eq!(
363 program
364 .stable_key_map()
365 .expect("keyed")
366 .into_values()
367 .collect::<Vec<_>>(),
368 [BloqNodeId(1)],
369 "the region has no key"
370 );
371 }
372}