1use std::collections::{BTreeMap, HashSet};
4
5use glam::IVec3;
6
7use super::stabilizer::SearchBudget;
8use super::{
9 DerivedSurface, NodeKind, RuntimeBasisError, RuntimeStabilizerBasis, Stabilizer,
10 StabilizerError, StabilizerGenerators, ZXGraph,
11};
12use crate::{Action, ModuleCertificationLimits, Pauli};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ReadoutCoordinates {
17 PublicRows,
19 SourceOutcomes,
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct ReadoutPlan {
26 pub coordinates: ReadoutCoordinates,
28 pub sites: Vec<IVec3>,
30 pub branches: Vec<(Vec<bool>, DerivedSurface)>,
34 pub deadline: i64,
36}
37
38impl ReadoutPlan {
39 pub fn select(&self, mut value: impl FnMut(IVec3) -> bool) -> &DerivedSurface {
44 let values = self
45 .sites
46 .iter()
47 .map(|&site| value(site))
48 .collect::<Vec<_>>();
49 &self
50 .branches
51 .iter()
52 .find(|(branch, _)| *branch == values)
53 .expect("reachable assignment has a certified readout")
54 .1
55 }
56}
57
58#[derive(Debug, Clone, Copy)]
60pub enum NamedReadout<'a> {
61 Fixed(&'a Stabilizer),
63 Guarded(&'a ReadoutPlan),
65}
66
67impl<'a> NamedReadout<'a> {
68 pub fn select(self, value: impl FnMut(IVec3) -> bool) -> &'a Stabilizer {
70 match self {
71 Self::Fixed(surface) => surface,
72 Self::Guarded(recipe) => &recipe.select(value).stabilizer,
73 }
74 }
75
76 pub fn surfaces(self) -> impl Iterator<Item = &'a Stabilizer> {
78 let (fixed, branches) = match self {
79 Self::Fixed(surface) => (Some(surface), &[][..]),
80 Self::Guarded(recipe) => (None, recipe.branches.as_slice()),
81 };
82 fixed
83 .into_iter()
84 .chain(branches.iter().map(|(_, surface)| &surface.stabilizer))
85 }
86}
87
88#[derive(Clone, Copy)]
90pub(crate) enum ReadoutClosure {
91 Required,
92 #[cfg(feature = "verify")]
93 Analysis,
94}
95
96pub(crate) fn plan_causal_readouts(
98 generators: &StabilizerGenerators,
99 dag: &crate::ActionDag,
100 limits: ModuleCertificationLimits,
101 closure: ReadoutClosure,
102) -> Result<BTreeMap<String, ReadoutPlan>, RuntimeBasisError> {
103 let limit = limits.max_guarded_domain_size;
104 let mut budget = SearchBudget::with_limits(limits);
105 let pending = generators
106 .generators
107 .iter()
108 .filter_map(|generator| {
109 let name = generator.measurement_name()?;
110 if generator.readout_plan().is_some() {
111 return None;
112 }
113 let sites = dag
114 .resolve_sites_for_measurement(name)
115 .expect("named source action");
116 (!sites.is_empty()).then_some((name, generator, sites))
117 })
118 .collect::<Vec<_>>();
119 if pending.is_empty() {
120 return Ok(BTreeMap::new());
121 }
122 let rank = generators.basis_rank();
123 budget.check_matrix(
124 rank.saturating_mul(3),
125 generators.zx_graph.total_ids(),
126 0,
127 0,
128 )?;
129 let basis = RuntimeStabilizerBasis::from_generators(generators).with_t_nodes_as_ports();
130 pending
131 .into_iter()
132 .map(|(name, generator, sites)| {
133 budget.visit("causal readout search states")?;
134 let deadline = generator
135 .stabilizer
136 .measurement_deadline(&generators.zx_graph)
137 .expect("measurement has support");
138 let domain = dag
139 .resolve_value_domain_bounded_with_limits(&sites, limit, limits.boolean_limits())
140 .map_err(|error| error.into_stabilizer("readout-domain branches", limit))?;
141 let mut branches = Vec::new();
142 for values in domain.values().chunks(64) {
143 budget.check_matrix(
144 rank.saturating_mul(values.len().saturating_add(3)),
145 generators.zx_graph.total_ids(),
146 0,
147 0,
148 )?;
149 let fills = values
150 .iter()
151 .map(|values| selective_fills(&generators.zx_graph, &sites, values))
152 .collect::<Vec<_>>();
153 for (values, branch) in values
154 .iter()
155 .zip(basis.apply_selective_fill_assignments(&fills)?)
156 {
157 budget.visit("causal readout search states")?;
158 let surface = match closure {
159 ReadoutClosure::Required => {
160 branch.derive_causal_measurement_surface(name, deadline)
161 }
162 #[cfg(feature = "verify")]
163 ReadoutClosure::Analysis => branch
164 .derive_causal_measurement_surface(name, deadline)
165 .or_else(|error| match error {
166 RuntimeBasisError::Stabilizer(
167 StabilizerError::UnavailableControlParity { .. },
168 ) => branch.derive_measurement_surface(name, deadline),
169 error => Err(error),
170 }),
171 }?;
172 branches.push((values.clone(), surface));
173 }
174 }
175 Ok((
176 name.to_owned(),
177 ReadoutPlan {
178 coordinates: ReadoutCoordinates::PublicRows,
179 sites,
180 branches,
181 deadline,
182 },
183 ))
184 })
185 .collect()
186}
187
188pub(crate) fn plan_readouts(
189 generators: &StabilizerGenerators,
190 limits: ModuleCertificationLimits,
191) -> Result<BTreeMap<String, ReadoutPlan>, RuntimeBasisError> {
192 let zx = &generators.zx_graph;
193 let mut budget = SearchBudget::with_limits(limits);
194 let rank = generators.basis_rank();
195 budget.check_matrix(
196 rank.saturating_mul(3),
197 zx.total_ids(),
198 rank,
199 generators
200 .generators
201 .len()
202 .saturating_add(zx.nodes().len().saturating_mul(2))
203 .saturating_add(zx.action_graph().ordered_nodes().count()),
204 )?;
205 let dag = zx.action_graph();
206 let final_deadline = zx
207 .nodes()
208 .iter()
209 .map(|node| i64::from(node.pos.z) + 1)
210 .max()
211 .unwrap_or(0);
212 let actions = dag.ordered_nodes().collect::<Vec<_>>();
213 let mut predecessors = vec![Vec::new(); actions.len()];
214 for (from, to, _) in dag
215 .dependencies()
216 .filter(|(_, _, kind)| !kind.is_implicit())
217 {
218 predecessors[to].push(from);
219 }
220 let basis = RuntimeStabilizerBasis::for_source_readouts(generators)?;
221 let mut known = HashSet::new();
222 let mut readouts = BTreeMap::new();
223 loop {
224 budget.visit("composed readout search states")?;
225 loop {
226 let prior = known.len();
227 for node in &actions {
228 if !matches!(node.action, Action::Measure { .. })
229 && predecessors[node.ordinal]
230 .iter()
231 .all(|ordinal| known.contains(ordinal))
232 {
233 known.insert(node.ordinal);
234 }
235 }
236 if prior == known.len() {
237 break;
238 }
239 }
240 let mut sites = actions
241 .iter()
242 .filter_map(|node| match node.action {
243 Action::Resolve { target, .. } if known.contains(&node.ordinal) => Some(target),
244 _ => None,
245 })
246 .collect::<Vec<_>>();
247 sites.sort_unstable_by_key(IVec3::to_array);
248 let absent = unavailable_feedback(zx, &known);
249 let available = generators
250 .generators
251 .iter()
252 .enumerate()
253 .filter_map(|(index, generator)| {
254 generator
255 .measurement_name()
256 .filter(|name| readouts.contains_key(*name))
257 .map(|_| index)
258 })
259 .collect::<Vec<_>>();
260 let pending = actions
261 .iter()
262 .filter_map(|node| match &node.action {
263 Action::Measure { name, .. } if !known.contains(&node.ordinal) => {
264 Some((node.ordinal, name))
265 }
266 _ => None,
267 })
268 .collect::<Vec<_>>();
269 if pending.is_empty() {
270 return Ok(readouts);
271 }
272 let baseline = dag
273 .first_resolve_values_with_limits(&sites, limits.boolean_limits())?
274 .expect("valid resolve domain");
275 let baseline = basis.apply_selective_fills(&selective_fills(zx, &sites, &baseline))?;
276 let mut candidates = Vec::new();
277 for (ordinal, name) in &pending {
278 budget.visit("composed readout search states")?;
279 let derive = |basis: &RuntimeStabilizerBasis| {
280 let mut surface =
281 basis.derive_readout_from_known(name, final_deadline, &absent, &available)?;
282 surface
283 .readout_ordinals
284 .retain(|ordinal| available.contains(ordinal));
285 Ok(surface)
286 };
287 let recipe = derive(&baseline).and_then(|surface| {
288 plan_guarded_surface(&basis, &sites, surface, &derive, limits, &mut budget)
289 });
290 match recipe {
291 Ok(recipe) => candidates.push((*ordinal, (*name).clone(), recipe)),
292 Err(RuntimeBasisError::Stabilizer(StabilizerError::UnavailableControlParity {
293 ..
294 })) => {}
295 Err(error) => return Err(error),
296 }
297 }
298 if candidates.is_empty() {
299 return Err(StabilizerError::UnavailableControlParity {
300 mvar: pending[0].1.clone(),
301 deadline: final_deadline,
302 }
303 .into());
304 }
305 for (ordinal, name, mut recipe) in candidates {
306 for &predecessor in recipe
307 .branches
308 .iter()
309 .flat_map(|(_, branch)| &branch.readout_ordinals)
310 {
311 let name = generators.generators[predecessor]
312 .measurement_name()
313 .expect("known named readout");
314 recipe.deadline = recipe.deadline.max(readouts[name].deadline);
315 }
316 readouts.insert(name, recipe);
317 known.insert(ordinal);
318 }
319 }
320}
321
322fn plan_guarded_surface(
323 basis: &RuntimeStabilizerBasis,
324 sites: &[IVec3],
325 initial: DerivedSurface,
326 derive: &impl Fn(&RuntimeStabilizerBasis) -> Result<DerivedSurface, RuntimeBasisError>,
327 limits: ModuleCertificationLimits,
328 budget: &mut SearchBudget,
329) -> Result<ReadoutPlan, RuntimeBasisError> {
330 let limit = limits.max_guarded_domain_size;
331 let zx = basis.zx_graph();
332 if sites.is_empty() && limit != 0 {
333 return Ok(ReadoutPlan {
335 coordinates: ReadoutCoordinates::SourceOutcomes,
336 sites: Vec::new(),
337 deadline: initial.stabilizer.measurement_deadline(zx).unwrap_or(0),
338 branches: vec![(Vec::new(), initial)],
339 });
340 }
341 let support = |surface: &DerivedSurface| {
342 sites
343 .iter()
344 .copied()
345 .filter(|&pos| {
346 surface
347 .stabilizer
348 .paulis
349 .get(zx.node_at(pos).expect("resolve node").id)
350 != Pauli::I
351 })
352 .collect::<Vec<_>>()
353 };
354 let mut local = support(&initial);
355 loop {
356 budget.visit("guarded readout search states")?;
357 let domain = zx
358 .action_graph()
359 .resolve_value_domain_bounded_with_limits(&local, limit, limits.boolean_limits())
360 .map_err(|error| error.into_stabilizer("guarded-domain branches", limit))?;
361 let mut branches = Vec::new();
362 let mut missing = None;
363 'assignments: for values in domain.values().chunks(64) {
364 budget.check_matrix(
365 basis
366 .generators()
367 .len()
368 .saturating_mul(values.len().saturating_add(3)),
369 zx.total_ids(),
370 0,
371 0,
372 )?;
373 let fills = values
374 .iter()
375 .map(|values| selective_fills(zx, &local, values))
376 .collect::<Vec<_>>();
377 for (values, branch) in values
378 .iter()
379 .zip(basis.apply_selective_fill_assignments(&fills)?)
380 {
381 budget.visit("guarded readout search states")?;
382 match derive(&branch) {
383 Ok(surface) => branches.push((values.clone(), surface)),
384 Err(RuntimeBasisError::Stabilizer(
385 StabilizerError::UnavailableControlParity { .. },
386 )) => {
387 missing = Some(values.clone());
388 break 'assignments;
389 }
390 Err(error) => return Err(error),
391 }
392 }
393 }
394 if let Some(missing) = missing {
395 let fixed = local.iter().copied().zip(missing).collect::<Vec<_>>();
396 let values = zx
397 .action_graph()
398 .complete_resolve_values_with_limits(sites, &fixed, limits.boolean_limits())?
399 .expect("projected assignment has an extension");
400 let branch = basis.apply_selective_fills(&selective_fills(zx, sites, &values))?;
401 let surface = derive(&branch)?;
402 let prior = local.len();
403 local.extend(support(&surface));
404 local.sort_unstable_by_key(IVec3::to_array);
405 local.dedup();
406 if local.len() == prior {
407 local = sites.to_vec();
408 }
409 continue;
410 }
411 let (latest, deadline) = branches
415 .iter()
416 .enumerate()
417 .map(|(index, (_, surface))| {
418 (
419 index,
420 surface.stabilizer.measurement_deadline(zx).unwrap_or(0),
421 )
422 })
423 .max_by_key(|&(_, deadline)| deadline)
424 .expect("nonempty resolve domain");
425 branches.swap(0, latest);
426 if branches
427 .iter()
428 .all(|(_, surface)| surface == &branches[0].1)
429 && support(&branches[0].1).is_empty()
430 {
431 branches.truncate(1);
432 branches[0].0.clear();
433 local.clear();
434 }
435 return Ok(ReadoutPlan {
436 coordinates: ReadoutCoordinates::SourceOutcomes,
437 sites: local,
438 branches,
439 deadline,
440 });
441 }
442}
443
444fn selective_fills(
445 zx: &ZXGraph,
446 sites: &[IVec3],
447 values: &[bool],
448) -> Vec<(IVec3, crate::PauliBasis)> {
449 sites
450 .iter()
451 .zip(values)
452 .map(|(&pos, &value)| {
453 let NodeKind::Selective(kind) = zx.node_at(pos).expect("resolve node").kind else {
454 unreachable!("resolve sites name selective nodes")
455 };
456 (
457 pos,
458 if value {
459 kind.pauli_if_true()
460 } else {
461 kind.pauli_if_false()
462 },
463 )
464 })
465 .collect()
466}
467
468fn unavailable_feedback(zx: &ZXGraph, known: &HashSet<usize>) -> Vec<Vec<(usize, Pauli)>> {
469 zx.action_graph()
470 .ordered_nodes()
471 .filter(|node| !known.contains(&node.ordinal))
472 .filter_map(|action| {
473 let Action::Feedback { targets, .. } = &action.action else {
474 return None;
475 };
476 Some(
477 targets
478 .iter()
479 .map(|target| zx.feedback_column(target).expect("validated feedback wire"))
480 .collect(),
481 )
482 })
483 .collect()
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn readout_matrix_limit_counts_private_completion_rows() {
492 let mut generators = crate::GalleryItem::CNOT
493 .build()
494 .materialize_root_graph()
495 .expect("gallery flat projection")
496 .stabilizers()
497 .unwrap();
498 let private = generators.generators.pop().unwrap().stabilizer.paulis;
499 let generators = StabilizerGenerators::from_parts(
500 generators.zx_graph,
501 generators.generators,
502 vec![private],
503 );
504 plan_readouts(&generators, ModuleCertificationLimits::UNLIMITED).unwrap();
505 assert!(matches!(
508 plan_readouts(
509 &generators,
510 ModuleCertificationLimits {
511 max_matrix_words: 160,
512 ..ModuleCertificationLimits::UNLIMITED
513 }
514 ),
515 Err(RuntimeBasisError::Stabilizer(
516 StabilizerError::ResourceLimited {
517 phase: "dense matrix words",
518 observed: 196,
519 limit: 160,
520 }
521 ))
522 ));
523 }
524
525 #[test]
526 fn unavailable_feedback_uses_the_joint_pauli_action() {
527 let source = crate::BlockGraph::from_blog_text(
528 "BLOG 1.0
529 0: ZXZ [0,0,0]
530 1: ZXZ [0,0,1]
531 2: ZXZ [0,0,2]
532 0 -> +Z
533 1 -> +Z
534 m = measure 1
535 ",
536 )
537 .unwrap();
538 for last in [IVec3::ZERO, 2 * IVec3::Z] {
539 let targets = [IVec3::ZERO, last]
540 .map(|target| crate::FeedbackTarget {
541 target,
542 pauli: crate::PauliBasis::X,
543 direction: None,
544 })
545 .to_vec();
546 let mut graph = source.clone();
547 let mut actions = vec![Action::Feedback {
548 targets: targets.clone(),
549 condition: Some(crate::Expr::Var("m".into())),
550 }];
551 actions.extend(source.actions());
552 graph.set_actions(actions).unwrap();
553 let generators = graph.stabilizers().unwrap();
554 let plans = plan_readouts(&generators, ModuleCertificationLimits::DEFAULT).unwrap();
555 let surface = &plans["m"].branches[0].1.stabilizer;
556 assert!(surface.odd_anticommutes_feedback(&targets[..1], Some(&generators.zx_graph)));
557 assert!(!surface.odd_anticommutes_feedback(&targets, Some(&generators.zx_graph)));
558 }
559 }
560}