Skip to main content

cqlib_core/circuit/
dag.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Operation-level dependency DAG for circuits.
14//!
15//! `CircuitDag` is an algorithm view of [`Circuit`]. It keeps the circuit's
16//! storage IR unchanged while materializing operation dependencies induced by
17//! qubits and runtime classical resources. The DAG is intentionally not a
18//! commutation graph: edges mean "these operations are ordered by a shared
19//! resource in the source program", not "these operations cannot commute".
20
21use crate::circuit::circuit_param::{CircuitParam, ParameterValue};
22use crate::circuit::gate::{ClassicalDataOp, Directive, Instruction};
23use crate::circuit::value_instruction::storage_operation_to_value;
24use crate::circuit::{
25    Circuit, CircuitError, ClassicalControlOp, ClassicalExpr, ClassicalType, ClassicalValue,
26    ClassicalVar, ControlBody, ForOp, IfOp, Operation, Parameter, Qubit, SwitchCase, SwitchOp,
27    ValueClassicalControlOp, ValueControlBody, ValueInstruction, ValueOperation, WhileOp,
28};
29use indexmap::{IndexMap, IndexSet};
30use rustworkx_core::dag_algo::lexicographical_topological_sort;
31use rustworkx_core::petgraph::Direction;
32use rustworkx_core::petgraph::algo::toposort;
33use rustworkx_core::petgraph::prelude::{NodeIndex, StableDiGraph};
34use rustworkx_core::petgraph::visit::{EdgeRef, IntoEdgeReferences};
35use std::collections::{HashMap, HashSet};
36use std::convert::Infallible;
37use std::sync::OnceLock;
38
39/// Resource carried by an edge in a [`CircuitDag`].
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum DagWire {
42    /// A quantum bit timeline.
43    Qubit(Qubit),
44    /// A mutable classical storage location.
45    ClassicalVar(ClassicalVar),
46    /// An immutable runtime classical value, typically a measurement result.
47    ClassicalValue(ClassicalValue),
48    /// Stable ordering resource for operations without a concrete data wire.
49    GlobalOrder,
50}
51
52/// Node in a [`CircuitDag`].
53#[derive(Debug, Clone)]
54pub enum DagNode {
55    /// Input sentinel for a resource timeline.
56    WireIn(DagWire),
57    /// Output sentinel for a resource timeline.
58    WireOut(DagWire),
59    /// Circuit operation node.
60    Operation {
61        /// Storage IR operation.
62        operation: Operation,
63        /// Source-order position used for deterministic traversal.
64        order: usize,
65    },
66}
67
68/// Recursive DAG payload for a structured control-flow operation.
69#[derive(Debug, Clone)]
70pub enum DagControlFlow {
71    /// If/else bodies.
72    If {
73        then_body: Box<CircuitDag>,
74        else_body: Option<Box<CircuitDag>>,
75    },
76    /// While body.
77    While { body: Box<CircuitDag> },
78    /// For body.
79    For { body: Box<CircuitDag> },
80    /// Switch cases and optional default body.
81    Switch {
82        cases: Vec<DagSwitchCase>,
83        default: Option<Box<CircuitDag>>,
84    },
85    /// Structured break.
86    Break,
87    /// Structured continue.
88    Continue,
89}
90
91/// One switch case body in a [`DagControlFlow::Switch`].
92#[derive(Debug, Clone)]
93pub struct DagSwitchCase {
94    pub value: u128,
95    pub body: Box<CircuitDag>,
96}
97
98/// Operation-level dependency DAG for a circuit.
99#[derive(Debug, Clone)]
100pub struct CircuitDag {
101    qubits: IndexSet<Qubit>,
102    symbols: IndexSet<String>,
103    parameters: IndexSet<Parameter>,
104    classical_vars: Vec<ClassicalType>,
105    classical_values: Vec<ClassicalType>,
106    global_phase: CircuitParam,
107    graph: StableDiGraph<DagNode, DagWire>,
108    wire_io: IndexMap<DagWire, [NodeIndex; 2]>,
109    control_flow: HashMap<NodeIndex, DagControlFlow>,
110    order_to_node: Vec<NodeIndex>,
111    topological_op_nodes_cache: OnceLock<Vec<NodeIndex>>,
112    node_layers_cache: OnceLock<IndexMap<NodeIndex, usize>>,
113}
114
115impl CircuitDag {
116    /// Builds a dependency DAG from a circuit.
117    pub fn from_circuit(circuit: &Circuit) -> Result<Self, CircuitError> {
118        let mut dag = Self {
119            qubits: circuit.qubits().into_iter().collect(),
120            symbols: circuit.symbols().clone(),
121            parameters: circuit.parameters().clone(),
122            classical_vars: circuit.classical_vars().to_vec(),
123            classical_values: circuit.classical_values().to_vec(),
124            global_phase: circuit.global_phase_param().clone(),
125            graph: StableDiGraph::new(),
126            wire_io: IndexMap::new(),
127            control_flow: HashMap::new(),
128            order_to_node: Vec::with_capacity(circuit.operations().len()),
129            topological_op_nodes_cache: OnceLock::new(),
130            node_layers_cache: OnceLock::new(),
131        };
132        dag.initialize_wires();
133        dag.add_operations(circuit.operations())?;
134        dag.validate()?;
135        Ok(dag)
136    }
137
138    /// Builds a dependency DAG from a self-contained operation slice.
139    ///
140    /// This is the narrow constructor for pass-local analysis when the caller
141    /// already owns an operation stream and only needs quantum dependency
142    /// structure. It deliberately does not accept parameter tables, classical
143    /// declarations, or a global phase: every operation must be valid against
144    /// exactly the supplied qubit set and must be independent of external
145    /// circuit metadata.
146    ///
147    /// Use [`CircuitDag::from_circuit`] when the operation stream contains
148    /// indexed parameters, classical variables/values, control-flow bodies, or
149    /// any other resource that belongs to a full [`Circuit`]. Analysis passes
150    /// that intentionally ignore those resources should normalize them before
151    /// calling this constructor, for example by resolving parameters and
152    /// replacing non-quantum operations with dependency barriers.
153    ///
154    /// # Errors
155    ///
156    /// Returns an error when `qubits` contains duplicates, an operation
157    /// references a qubit outside that set, or the operation slice is not
158    /// self-contained.
159    pub fn from_operations(
160        qubits: impl IntoIterator<Item = Qubit>,
161        operations: &[Operation],
162    ) -> Result<Self, CircuitError> {
163        let mut qubit_set = IndexSet::new();
164        for qubit in qubits {
165            if !qubit_set.insert(qubit) {
166                return Err(CircuitError::DuplicateQubits);
167            }
168        }
169        let mut dag = Self {
170            qubits: qubit_set,
171            symbols: IndexSet::new(),
172            parameters: IndexSet::new(),
173            classical_vars: Vec::new(),
174            classical_values: Vec::new(),
175            global_phase: CircuitParam::Fixed(0.0),
176            graph: StableDiGraph::new(),
177            wire_io: IndexMap::new(),
178            control_flow: HashMap::new(),
179            order_to_node: Vec::with_capacity(operations.len()),
180            topological_op_nodes_cache: OnceLock::new(),
181            node_layers_cache: OnceLock::new(),
182        };
183        dag.initialize_wires();
184        dag.add_operations(operations)?;
185        dag.validate()?;
186        Ok(dag)
187    }
188
189    /// Reconstructs a circuit from this DAG's deterministic topological order.
190    ///
191    /// # Errors
192    ///
193    /// Returns an error if the DAG contains a cycle, references an invalid
194    /// parameter, or the reconstructed operation stream is rejected by
195    /// [`Circuit::from_operations`].
196    pub fn to_circuit(&self) -> Result<Circuit, CircuitError> {
197        let value_ops = self
198            .topological_operations()?
199            .into_iter()
200            .map(|operation| {
201                storage_operation_to_value(operation, &|param| self.parameter_value(param))
202            })
203            .collect::<Result<Vec<_>, _>>()?;
204        let mut circuit = Circuit::from_operations(
205            self.qubits.iter().copied().collect(),
206            value_ops,
207            Some(self.classical_vars.clone()),
208            Some(self.classical_values.clone()),
209        )?;
210        circuit.set_global_phase(self.global_phase_parameter()?);
211        Ok(circuit)
212    }
213
214    /// Validates graph and metadata invariants.
215    ///
216    /// # Errors
217    ///
218    /// Returns an error if the graph is cyclic, has invalid wire endpoints,
219    /// references resources outside the circuit metadata, or has inconsistent
220    /// control-flow payloads.
221    pub fn validate(&self) -> Result<(), CircuitError> {
222        toposort(&self.graph, None).map_err(|cycle| {
223            CircuitError::InvalidDag(format!(
224                "DAG contains a cycle at node {:?}",
225                cycle.node_id()
226            ))
227        })?;
228        self.validate_wire_io()?;
229
230        let mut produced_values = HashSet::new();
231        for node in self.graph.node_indices() {
232            match &self.graph[node] {
233                DagNode::WireIn(wire) | DagNode::WireOut(wire) => self.validate_wire(*wire)?,
234                DagNode::Operation { operation, order } => {
235                    if self.node_for_order(*order) != Some(node) {
236                        return Err(CircuitError::InvalidDag(format!(
237                            "operation order {order} is not unique"
238                        )));
239                    }
240                    if self.order_to_node.get(*order).copied() != Some(node) {
241                        return Err(CircuitError::InvalidDag(format!(
242                            "operation order {order} is missing from the order index"
243                        )));
244                    }
245                    self.validate_operation(operation)?;
246                    if let Instruction::ClassicalData(op) = &operation.instruction
247                        && let Some(value) = op.result()
248                        && !produced_values.insert(value)
249                    {
250                        return Err(CircuitError::DuplicateClassicalValueDefinition {
251                            index: value.index(),
252                            first: "CircuitDag".to_string(),
253                            second: "CircuitDag".to_string(),
254                        });
255                    }
256                    if let Some(control) = self.control_flow.get(&node) {
257                        self.validate_control_flow_payload(operation, control)?;
258                    }
259                }
260            }
261        }
262
263        for edge in self.graph.edge_references() {
264            self.validate_wire(*edge.weight())?;
265        }
266        Ok(())
267    }
268
269    /// Returns the number of qubits.
270    pub fn num_qubits(&self) -> usize {
271        self.qubits.len()
272    }
273
274    /// Returns the number of operation nodes.
275    pub fn num_ops(&self) -> usize {
276        self.op_nodes().count()
277    }
278
279    /// Returns true if there are no operation nodes.
280    pub fn is_empty(&self) -> bool {
281        self.num_ops() == 0
282    }
283
284    /// Returns circuit qubits in insertion order.
285    pub fn qubits(&self) -> Vec<Qubit> {
286        self.qubits.iter().copied().collect()
287    }
288
289    /// Returns the parameter table.
290    pub fn parameters(&self) -> &IndexSet<Parameter> {
291        &self.parameters
292    }
293
294    /// Interns a parameter into this DAG's parameter table.
295    ///
296    /// Mirrors [`Circuit::add_parameter`]: inserts `param` into the table when
297    /// absent, records its symbolic names, and returns the stable table index
298    /// plus whether the parameter was newly inserted.
299    ///
300    /// [`Circuit::add_parameter`]: crate::circuit::Circuit::add_parameter
301    pub fn add_parameter(&mut self, param: Parameter) -> (usize, bool) {
302        let (index, is_new) = self.parameters.insert_full(param.clone());
303        if is_new {
304            for sym in param.get_symbols() {
305                self.symbols.insert(sym);
306            }
307        }
308        (index, is_new)
309    }
310
311    /// Returns symbolic parameter names referenced by this DAG.
312    pub fn symbols(&self) -> &IndexSet<String> {
313        &self.symbols
314    }
315
316    /// Returns classical variable types.
317    pub fn classical_vars(&self) -> &[ClassicalType] {
318        &self.classical_vars
319    }
320
321    /// Returns classical value types.
322    pub fn classical_values(&self) -> &[ClassicalType] {
323        &self.classical_values
324    }
325
326    /// Iterates all materialized DAG wires.
327    pub fn wires(&self) -> impl Iterator<Item = DagWire> + '_ {
328        self.wire_io.keys().copied()
329    }
330
331    /// Returns whether `wire` has materialized endpoints in this DAG.
332    pub fn has_wire(&self, wire: DagWire) -> bool {
333        self.wire_io.contains_key(&wire)
334    }
335
336    /// Returns whether `wire` has no operation on its timeline.
337    ///
338    /// # Errors
339    ///
340    /// Returns an error if `wire` is not a valid resource for this DAG or its
341    /// materialized timeline is broken.
342    pub fn is_wire_idle(&self, wire: DagWire) -> Result<bool, CircuitError> {
343        Ok(self.nodes_on_wire(wire)?.is_empty())
344    }
345
346    /// Returns the input sentinel for `wire`.
347    pub fn wire_in(&self, wire: DagWire) -> Option<NodeIndex> {
348        self.wire_io.get(&wire).map(|pair| pair[0])
349    }
350
351    /// Returns the output sentinel for `wire`.
352    pub fn wire_out(&self, wire: DagWire) -> Option<NodeIndex> {
353        self.wire_io.get(&wire).map(|pair| pair[1])
354    }
355
356    /// Returns an operation by node index.
357    pub fn operation(&self, node: NodeIndex) -> Option<&Operation> {
358        match self.graph.node_weight(node) {
359            Some(DagNode::Operation { operation, .. }) => Some(operation),
360            _ => None,
361        }
362    }
363
364    /// Returns true if `node` is an operation node.
365    pub fn is_operation(&self, node: NodeIndex) -> bool {
366        self.operation(node).is_some()
367    }
368
369    /// Returns the kind of `node`.
370    pub fn node_kind(&self, node: NodeIndex) -> Option<&'static str> {
371        match self.graph.node_weight(node) {
372            Some(DagNode::WireIn(_)) => Some("wire_in"),
373            Some(DagNode::WireOut(_)) => Some("wire_out"),
374            Some(DagNode::Operation { .. }) => Some("operation"),
375            None => None,
376        }
377    }
378
379    /// Returns recursive control-flow DAGs attached to `node`, if any.
380    pub fn control_flow(&self, node: NodeIndex) -> Option<&DagControlFlow> {
381        self.control_flow.get(&node)
382    }
383
384    /// Iterates operation nodes in deterministic source order.
385    pub fn op_nodes(&self) -> impl Iterator<Item = NodeIndex> + '_ {
386        let mut nodes = self
387            .graph
388            .node_indices()
389            .filter(|node| matches!(self.graph[*node], DagNode::Operation { .. }))
390            .collect::<Vec<_>>();
391        nodes.sort_by_key(|node| self.operation_order(*node).unwrap_or(usize::MAX));
392        nodes.into_iter()
393    }
394
395    /// Returns operation nodes in deterministic topological order.
396    ///
397    /// # Errors
398    ///
399    /// Returns an error if the graph contains a cycle.
400    pub fn topological_op_nodes(&self) -> Result<Vec<NodeIndex>, CircuitError> {
401        Ok(self.topological_op_nodes_ref()?.clone())
402    }
403
404    /// Returns predecessor nodes.
405    pub fn predecessors(&self, node: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
406        self.graph
407            .edges_directed(node, rustworkx_core::petgraph::Incoming)
408            .map(|edge| edge.source())
409    }
410
411    /// Returns successor nodes.
412    pub fn successors(&self, node: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
413        self.graph
414            .edges_directed(node, rustworkx_core::petgraph::Outgoing)
415            .map(|edge| edge.target())
416    }
417
418    /// Returns operation predecessors of `node` connected through `wire`.
419    ///
420    /// # Errors
421    ///
422    /// Returns an error if `node` does not exist or `wire` is not valid for
423    /// this DAG.
424    pub fn predecessors_on_wire(
425        &self,
426        node: NodeIndex,
427        wire: DagWire,
428    ) -> Result<Vec<NodeIndex>, CircuitError> {
429        self.operation_neighbors_on_wire(node, wire, Direction::Incoming)
430    }
431
432    /// Returns operation successors of `node` connected through `wire`.
433    ///
434    /// # Errors
435    ///
436    /// Returns an error if `node` does not exist or `wire` is not valid for
437    /// this DAG.
438    pub fn successors_on_wire(
439        &self,
440        node: NodeIndex,
441        wire: DagWire,
442    ) -> Result<Vec<NodeIndex>, CircuitError> {
443        self.operation_neighbors_on_wire(node, wire, Direction::Outgoing)
444    }
445
446    /// Returns operation predecessors connected by quantum wires.
447    ///
448    /// # Errors
449    ///
450    /// Returns an error if `node` does not exist.
451    pub fn quantum_predecessors(&self, node: NodeIndex) -> Result<Vec<NodeIndex>, CircuitError> {
452        self.operation_neighbors_matching(node, Direction::Incoming, |wire| {
453            matches!(wire, DagWire::Qubit(_))
454        })
455    }
456
457    /// Returns operation successors connected by quantum wires.
458    ///
459    /// # Errors
460    ///
461    /// Returns an error if `node` does not exist.
462    pub fn quantum_successors(&self, node: NodeIndex) -> Result<Vec<NodeIndex>, CircuitError> {
463        self.operation_neighbors_matching(node, Direction::Outgoing, |wire| {
464            matches!(wire, DagWire::Qubit(_))
465        })
466    }
467
468    /// Returns operation predecessors connected by classical wires.
469    ///
470    /// # Errors
471    ///
472    /// Returns an error if `node` does not exist.
473    pub fn classical_predecessors(&self, node: NodeIndex) -> Result<Vec<NodeIndex>, CircuitError> {
474        self.operation_neighbors_matching(node, Direction::Incoming, |wire| {
475            matches!(wire, DagWire::ClassicalVar(_) | DagWire::ClassicalValue(_))
476        })
477    }
478
479    /// Returns operation successors connected by classical wires.
480    ///
481    /// # Errors
482    ///
483    /// Returns an error if `node` does not exist.
484    pub fn classical_successors(&self, node: NodeIndex) -> Result<Vec<NodeIndex>, CircuitError> {
485        self.operation_neighbors_matching(node, Direction::Outgoing, |wire| {
486            matches!(wire, DagWire::ClassicalVar(_) | DagWire::ClassicalValue(_))
487        })
488    }
489
490    /// Returns operation nodes on `wire` in wire order.
491    ///
492    /// # Errors
493    ///
494    /// Returns an error if `wire` is invalid or the stored wire timeline is
495    /// broken or branched.
496    pub fn nodes_on_wire(&self, wire: DagWire) -> Result<Vec<NodeIndex>, CircuitError> {
497        self.validate_wire(wire)?;
498        let Some(input) = self.wire_in(wire) else {
499            return Ok(Vec::new());
500        };
501        let output = self.wire_out(wire).ok_or_else(|| {
502            CircuitError::InvalidDag(format!("wire {:?} has no output endpoint", wire))
503        })?;
504
505        let mut nodes = Vec::new();
506        let mut seen = HashSet::new();
507        let mut current = input;
508        while current != output {
509            if !seen.insert(current) {
510                return Err(CircuitError::InvalidDag(format!(
511                    "wire {:?} contains a cycle",
512                    wire
513                )));
514            }
515            let next = self.next_node_on_wire(current, wire)?.ok_or_else(|| {
516                CircuitError::InvalidDag(format!("wire {:?} is not connected to its output", wire))
517            })?;
518            match self.graph.node_weight(next) {
519                Some(DagNode::Operation { .. }) => nodes.push(next),
520                Some(DagNode::WireOut(found)) if *found == wire => {}
521                Some(_) => {
522                    return Err(CircuitError::InvalidDag(format!(
523                        "wire {:?} reaches an invalid node {:?}",
524                        wire, next
525                    )));
526                }
527                None => {
528                    return Err(CircuitError::InvalidDag(format!(
529                        "wire {:?} reaches missing node {:?}",
530                        wire, next
531                    )));
532                }
533            }
534            current = next;
535        }
536        Ok(nodes)
537    }
538
539    /// Returns operation nodes with no operation predecessor.
540    ///
541    /// # Errors
542    ///
543    /// Returns an error if the graph contains a cycle.
544    pub fn front_layer(&self) -> Result<Vec<NodeIndex>, CircuitError> {
545        Ok(self
546            .topological_op_nodes_ref()?
547            .iter()
548            .copied()
549            .filter(|node| {
550                !self
551                    .predecessors(*node)
552                    .any(|pred| matches!(self.graph[pred], DagNode::Operation { .. }))
553            })
554            .collect())
555    }
556
557    /// Returns ASAP operation layers.
558    ///
559    /// # Errors
560    ///
561    /// Returns an error if the graph contains a cycle or operation predecessor
562    /// layers are inconsistent with the topological order.
563    pub fn layers(&self) -> Result<Vec<Vec<NodeIndex>>, CircuitError> {
564        let mut layers: Vec<Vec<NodeIndex>> = Vec::new();
565        for (node, layer) in self.node_layers_ref()? {
566            if layers.len() <= *layer {
567                layers.resize_with(layer + 1, Vec::new);
568            }
569            layers[*layer].push(*node);
570        }
571        Ok(layers)
572    }
573
574    /// Returns the ASAP layer index for every operation node.
575    ///
576    /// # Errors
577    ///
578    /// Returns an error if the graph contains a cycle or operation predecessor
579    /// layers are inconsistent with the topological order.
580    pub fn node_layers(&self) -> Result<IndexMap<NodeIndex, usize>, CircuitError> {
581        Ok(self.node_layers_ref()?.clone())
582    }
583
584    /// Returns the ASAP dependency depth.
585    pub fn depth(&self) -> Result<usize, CircuitError> {
586        Ok(self
587            .node_layers_ref()?
588            .values()
589            .copied()
590            .max()
591            .map_or(0, |layer| layer + 1))
592    }
593
594    /// Returns true if any top-level operation is structured control flow.
595    pub fn has_control_flow(&self) -> bool {
596        self.op_nodes().any(|node| {
597            matches!(
598                self.operation(node).map(|op| &op.instruction),
599                Some(Instruction::ClassicalControl(_))
600            )
601        })
602    }
603
604    /// Returns true if a control-flow body contains structured control flow.
605    pub fn has_nested_control_flow(&self) -> bool {
606        self.control_flow.values().any(|control| {
607            control
608                .body_dags()
609                .any(|body| body.has_control_flow() || body.has_nested_control_flow())
610        })
611    }
612
613    /// Returns true if any top-level operation directly or recursively measures.
614    pub fn has_measurement(&self) -> bool {
615        self.op_nodes().any(|node| {
616            self.operation(node)
617                .is_some_and(|operation| operation.instruction.has_measurement())
618        })
619    }
620
621    /// Counts top-level operations by instruction name.
622    pub fn operation_count_by_name(&self) -> IndexMap<String, usize> {
623        let mut counts = IndexMap::new();
624        for node in self.op_nodes() {
625            if let Some(operation) = self.operation(node) {
626                let name = operation.instruction.name();
627                *counts.entry(name).or_insert(0) += 1;
628            }
629        }
630        counts
631    }
632
633    /// Counts top-level and nested control-flow body operations by instruction name.
634    pub fn operation_count_by_name_recursive(&self) -> IndexMap<String, usize> {
635        let mut counts = IndexMap::new();
636        self.add_operation_counts_recursive(&mut counts);
637        counts
638    }
639
640    /// Collects contiguous runs on `wire` for operations accepted by `predicate`.
641    ///
642    /// Runs are collected in wire order. Operations for which `predicate`
643    /// returns false break the current run. This method is intended for
644    /// concrete qubit and classical wires. [`DagWire::GlobalOrder`] is accepted
645    /// for completeness, but it only orders operations without concrete data
646    /// resources and is usually not meaningful for optimization runs.
647    ///
648    /// # Errors
649    ///
650    /// Returns an error if `wire` is invalid or its materialized timeline is
651    /// broken.
652    pub fn collect_runs_on_wire<F>(
653        &self,
654        wire: DagWire,
655        mut predicate: F,
656    ) -> Result<Vec<Vec<NodeIndex>>, CircuitError>
657    where
658        F: FnMut(&Operation) -> bool,
659    {
660        let mut runs = Vec::new();
661        let mut current = Vec::new();
662        for node in self.nodes_on_wire(wire)? {
663            let operation = self.operation(node).ok_or_else(|| {
664                CircuitError::InvalidDag(format!("node {:?} is not an operation", node))
665            })?;
666            if predicate(operation) {
667                current.push(node);
668            } else if !current.is_empty() {
669                runs.push(std::mem::take(&mut current));
670            }
671        }
672        if !current.is_empty() {
673            runs.push(current);
674        }
675        Ok(runs)
676    }
677
678    /// Collects contiguous one-qubit gate runs on every qubit wire.
679    ///
680    /// Non-gate operations, measurements, reset, barriers, and multi-qubit
681    /// operations break a run.
682    ///
683    /// # Errors
684    ///
685    /// Returns an error if any materialized qubit timeline is broken.
686    pub fn collect_1q_runs(&self) -> Result<Vec<Vec<NodeIndex>>, CircuitError> {
687        let mut runs = Vec::new();
688        for qubit in &self.qubits {
689            runs.extend(self.collect_runs_on_wire(DagWire::Qubit(*qubit), is_one_qubit_gate)?);
690        }
691        Ok(runs)
692    }
693
694    /// Collects contiguous two-qubit gate runs on every qubit wire.
695    ///
696    /// The same two-qubit operation may appear in the runs for both of its
697    /// qubit wires.
698    ///
699    /// # Errors
700    ///
701    /// Returns an error if any materialized qubit timeline is broken.
702    pub fn collect_2q_runs(&self) -> Result<Vec<Vec<NodeIndex>>, CircuitError> {
703        let mut runs = Vec::new();
704        for qubit in &self.qubits {
705            runs.extend(self.collect_runs_on_wire(DagWire::Qubit(*qubit), is_two_qubit_gate)?);
706        }
707        Ok(runs)
708    }
709
710    /// Appends an operation at the back of the DAG.
711    pub fn apply_operation_back(
712        &mut self,
713        operation: Operation,
714    ) -> Result<NodeIndex, CircuitError> {
715        let mut operations = self.topological_operations()?;
716        let order = operations.len();
717        operations.push(operation);
718        self.rebuild_from_operations(&operations)?;
719        self.node_for_order(order)
720            .ok_or_else(|| CircuitError::InvalidDag("appended operation node missing".to_string()))
721    }
722
723    /// Lowers and appends a self-contained value-level operation at the back of the DAG.
724    ///
725    /// # Errors
726    ///
727    /// Returns errors from value-instruction lowering or DAG rebuilding.
728    pub fn apply_value_operation_back(
729        &mut self,
730        operation: ValueOperation,
731    ) -> Result<NodeIndex, CircuitError> {
732        let operation = self.lower_value_operation(operation)?;
733        self.apply_operation_back(operation)
734    }
735
736    /// Prepends an operation at the front of the DAG.
737    pub fn apply_operation_front(
738        &mut self,
739        operation: Operation,
740    ) -> Result<NodeIndex, CircuitError> {
741        let mut operations = Vec::with_capacity(self.num_ops() + 1);
742        operations.push(operation);
743        operations.extend(self.topological_operations()?);
744        self.rebuild_from_operations(&operations)?;
745        self.node_for_order(0)
746            .ok_or_else(|| CircuitError::InvalidDag("prepended operation node missing".to_string()))
747    }
748
749    /// Lowers and prepends a self-contained value-level operation at the front of the DAG.
750    ///
751    /// # Errors
752    ///
753    /// Returns errors from value-instruction lowering or DAG rebuilding.
754    pub fn apply_value_operation_front(
755        &mut self,
756        operation: ValueOperation,
757    ) -> Result<NodeIndex, CircuitError> {
758        let operation = self.lower_value_operation(operation)?;
759        self.apply_operation_front(operation)
760    }
761
762    /// Removes one operation node.
763    pub fn remove_op_node(&mut self, node: NodeIndex) -> Result<Operation, CircuitError> {
764        let order = self.require_operation_order(node)?;
765        let mut operations = self.topological_operations()?;
766        let removed = operations.remove(order);
767        self.rebuild_from_operations_unchecked(&operations)?;
768        Ok(removed)
769    }
770
771    /// Replaces one operation node.
772    pub fn substitute_node(
773        &mut self,
774        node: NodeIndex,
775        operation: Operation,
776    ) -> Result<(), CircuitError> {
777        let order = self.require_operation_order(node)?;
778        let mut operations = self.topological_operations()?;
779        operations[order] = operation;
780        self.rebuild_from_operations(&operations)
781    }
782
783    /// Lowers and replaces one operation node with a self-contained value-level operation.
784    ///
785    /// # Errors
786    ///
787    /// Returns errors from value-instruction lowering, node validation, or DAG rebuilding.
788    pub fn substitute_value_node(
789        &mut self,
790        node: NodeIndex,
791        operation: ValueOperation,
792    ) -> Result<(), CircuitError> {
793        let operation = self.lower_value_operation(operation)?;
794        self.substitute_node(node, operation)
795    }
796
797    /// Replaces one operation node by another DAG.
798    pub fn substitute_node_with_dag(
799        &mut self,
800        node: NodeIndex,
801        replacement: CircuitDag,
802    ) -> Result<(), CircuitError> {
803        replacement.validate()?;
804        let order = self.require_operation_order(node)?;
805        let old = self.operation(node).ok_or_else(|| {
806            CircuitError::InvalidDag(format!("node {:?} is not an operation", node))
807        })?;
808        let replacement_operations = self.validate_replacement(old, &replacement)?;
809        let mut operations = self.topological_operations()?;
810        operations.splice(order..=order, replacement_operations);
811        self.rebuild_from_operations(&operations)
812    }
813
814    fn initialize_wires(&mut self) {
815        let mut wires = Vec::new();
816        wires.push(DagWire::GlobalOrder);
817        wires.extend(self.qubits.iter().copied().map(DagWire::Qubit));
818        for wire in wires {
819            let input = self.graph.add_node(DagNode::WireIn(wire));
820            let output = self.graph.add_node(DagNode::WireOut(wire));
821            self.wire_io.insert(wire, [input, output]);
822        }
823    }
824
825    fn add_operations(&mut self, operations: &[Operation]) -> Result<(), CircuitError> {
826        let mut frontier = self
827            .wire_io
828            .iter()
829            .map(|(wire, endpoints)| (*wire, endpoints[0]))
830            .collect::<HashMap<_, _>>();
831
832        for (order, operation) in operations.iter().cloned().enumerate() {
833            self.validate_operation(&operation)?;
834            let node = self.graph.add_node(DagNode::Operation {
835                operation: operation.clone(),
836                order,
837            });
838            self.order_to_node.push(node);
839            if let Some(control) = self.build_control_flow_payload(&operation)? {
840                self.control_flow.insert(node, control);
841            }
842
843            let resources = self.operation_resources(&operation);
844            let mut deps = resources.reads.clone();
845            deps.extend(resources.writes.iter().copied());
846            if deps.is_empty() {
847                deps.insert(DagWire::GlobalOrder);
848            }
849
850            for wire in deps {
851                self.ensure_wire(wire)?;
852                if let std::collections::hash_map::Entry::Vacant(entry) = frontier.entry(wire) {
853                    let endpoints = self.wire_io.get(&wire).ok_or_else(|| {
854                        CircuitError::InvalidDag(format!("missing endpoints for wire {:?}", wire))
855                    })?;
856                    entry.insert(endpoints[0]);
857                }
858                let source = frontier.get(&wire).copied().ok_or_else(|| {
859                    CircuitError::InvalidDag(format!("missing frontier for wire {:?}", wire))
860                })?;
861                self.add_edge_once(source, node, wire);
862                frontier.insert(wire, node);
863            }
864        }
865
866        let output_edges = self
867            .wire_io
868            .iter()
869            .map(|(wire, endpoints)| {
870                (
871                    frontier.get(wire).copied().unwrap_or(endpoints[0]),
872                    endpoints[1],
873                    *wire,
874                )
875            })
876            .collect::<Vec<_>>();
877        for (source, target, wire) in output_edges {
878            self.add_edge_once(source, target, wire);
879        }
880        Ok(())
881    }
882
883    fn rebuild_from_operations(&mut self, operations: &[Operation]) -> Result<(), CircuitError> {
884        self.rebuild_from_operations_with_validation(operations, true)
885    }
886
887    fn rebuild_from_operations_unchecked(
888        &mut self,
889        operations: &[Operation],
890    ) -> Result<(), CircuitError> {
891        self.rebuild_from_operations_with_validation(operations, false)
892    }
893
894    fn rebuild_from_operations_with_validation(
895        &mut self,
896        operations: &[Operation],
897        validate: bool,
898    ) -> Result<(), CircuitError> {
899        let mut rebuilt = Self {
900            qubits: self.qubits.clone(),
901            symbols: self.symbols.clone(),
902            parameters: self.parameters.clone(),
903            classical_vars: self.classical_vars.clone(),
904            classical_values: self.classical_values.clone(),
905            global_phase: self.global_phase.clone(),
906            graph: StableDiGraph::new(),
907            wire_io: IndexMap::new(),
908            control_flow: HashMap::new(),
909            order_to_node: Vec::with_capacity(operations.len()),
910            topological_op_nodes_cache: OnceLock::new(),
911            node_layers_cache: OnceLock::new(),
912        };
913        rebuilt.initialize_wires();
914        rebuilt.add_operations(operations)?;
915        if validate {
916            rebuilt.validate()?;
917        }
918        *self = rebuilt;
919        Ok(())
920    }
921
922    fn build_control_flow_payload(
923        &self,
924        operation: &Operation,
925    ) -> Result<Option<DagControlFlow>, CircuitError> {
926        let Instruction::ClassicalControl(control) = &operation.instruction else {
927            return Ok(None);
928        };
929        let build_body = |ops: &[Operation]| -> Result<Box<CircuitDag>, CircuitError> {
930            let mut dag = Self {
931                qubits: self.qubits.clone(),
932                symbols: self.symbols.clone(),
933                parameters: self.parameters.clone(),
934                classical_vars: self.classical_vars.clone(),
935                classical_values: self.classical_values.clone(),
936                global_phase: self.global_phase.clone(),
937                graph: StableDiGraph::new(),
938                wire_io: IndexMap::new(),
939                control_flow: HashMap::new(),
940                order_to_node: Vec::with_capacity(ops.len()),
941                topological_op_nodes_cache: OnceLock::new(),
942                node_layers_cache: OnceLock::new(),
943            };
944            dag.initialize_wires();
945            dag.add_operations(ops)?;
946            dag.validate()?;
947            Ok(Box::new(dag))
948        };
949
950        Ok(Some(match control {
951            ClassicalControlOp::If(op) => DagControlFlow::If {
952                then_body: build_body(op.then_body().operations())?,
953                else_body: op
954                    .else_body()
955                    .map(|body| build_body(body.operations()))
956                    .transpose()?,
957            },
958            ClassicalControlOp::While(op) => DagControlFlow::While {
959                body: build_body(op.body().operations())?,
960            },
961            ClassicalControlOp::For(op) => DagControlFlow::For {
962                body: build_body(op.body().operations())?,
963            },
964            ClassicalControlOp::Switch(op) => DagControlFlow::Switch {
965                cases: op
966                    .cases()
967                    .iter()
968                    .map(|case| {
969                        Ok(DagSwitchCase {
970                            value: case.value(),
971                            body: build_body(case.body().operations())?,
972                        })
973                    })
974                    .collect::<Result<_, CircuitError>>()?,
975                default: op
976                    .default()
977                    .map(|body| build_body(body.operations()))
978                    .transpose()?,
979            },
980            ClassicalControlOp::Break => DagControlFlow::Break,
981            ClassicalControlOp::Continue => DagControlFlow::Continue,
982        }))
983    }
984
985    fn add_edge_once(&mut self, source: NodeIndex, target: NodeIndex, wire: DagWire) {
986        if self
987            .graph
988            .edges_connecting(source, target)
989            .any(|edge| *edge.weight() == wire)
990        {
991            return;
992        }
993        self.graph.add_edge(source, target, wire);
994    }
995
996    fn operation_neighbors_on_wire(
997        &self,
998        node: NodeIndex,
999        wire: DagWire,
1000        direction: Direction,
1001    ) -> Result<Vec<NodeIndex>, CircuitError> {
1002        if self.graph.node_weight(node).is_none() {
1003            return Err(CircuitError::InvalidDag(format!(
1004                "node {:?} is not in the DAG",
1005                node
1006            )));
1007        }
1008        self.validate_wire(wire)?;
1009        Ok(self.operation_neighbors_matching_unchecked(node, direction, |found| found == wire))
1010    }
1011
1012    fn operation_neighbors_matching(
1013        &self,
1014        node: NodeIndex,
1015        direction: Direction,
1016        predicate: impl FnMut(DagWire) -> bool,
1017    ) -> Result<Vec<NodeIndex>, CircuitError> {
1018        if self.graph.node_weight(node).is_none() {
1019            return Err(CircuitError::InvalidDag(format!(
1020                "node {:?} is not in the DAG",
1021                node
1022            )));
1023        }
1024        Ok(self.operation_neighbors_matching_unchecked(node, direction, predicate))
1025    }
1026
1027    fn operation_neighbors_matching_unchecked(
1028        &self,
1029        node: NodeIndex,
1030        direction: Direction,
1031        mut predicate: impl FnMut(DagWire) -> bool,
1032    ) -> Vec<NodeIndex> {
1033        let mut neighbors = IndexSet::new();
1034        for edge in self.graph.edges_directed(node, direction) {
1035            if !predicate(*edge.weight()) {
1036                continue;
1037            }
1038            let neighbor = match direction {
1039                Direction::Incoming => edge.source(),
1040                Direction::Outgoing => edge.target(),
1041            };
1042            if matches!(
1043                self.graph.node_weight(neighbor),
1044                Some(DagNode::Operation { .. })
1045            ) {
1046                neighbors.insert(neighbor);
1047            }
1048        }
1049        let mut neighbors = neighbors.into_iter().collect::<Vec<_>>();
1050        neighbors.sort_by_key(|node| self.topological_sort_key(*node));
1051        neighbors
1052    }
1053
1054    fn next_node_on_wire(
1055        &self,
1056        node: NodeIndex,
1057        wire: DagWire,
1058    ) -> Result<Option<NodeIndex>, CircuitError> {
1059        let mut next = None;
1060        for edge in self
1061            .graph
1062            .edges_directed(node, rustworkx_core::petgraph::Outgoing)
1063            .filter(|edge| *edge.weight() == wire)
1064        {
1065            if next.replace(edge.target()).is_some() {
1066                return Err(CircuitError::InvalidDag(format!(
1067                    "wire {:?} branches at node {:?}",
1068                    wire, node
1069                )));
1070            }
1071        }
1072        Ok(next)
1073    }
1074
1075    fn ensure_wire(&mut self, wire: DagWire) -> Result<(), CircuitError> {
1076        self.validate_wire(wire)?;
1077        if !self.wire_io.contains_key(&wire) {
1078            let input = self.graph.add_node(DagNode::WireIn(wire));
1079            let output = self.graph.add_node(DagNode::WireOut(wire));
1080            self.wire_io.insert(wire, [input, output]);
1081        }
1082        Ok(())
1083    }
1084
1085    fn validate_wire_io(&self) -> Result<(), CircuitError> {
1086        for (wire, endpoints) in &self.wire_io {
1087            match self.graph.node_weight(endpoints[0]) {
1088                Some(DagNode::WireIn(found)) if found == wire => {}
1089                _ => {
1090                    return Err(CircuitError::InvalidDag(format!(
1091                        "wire {:?} has invalid input endpoint",
1092                        wire
1093                    )));
1094                }
1095            }
1096            match self.graph.node_weight(endpoints[1]) {
1097                Some(DagNode::WireOut(found)) if found == wire => {}
1098                _ => {
1099                    return Err(CircuitError::InvalidDag(format!(
1100                        "wire {:?} has invalid output endpoint",
1101                        wire
1102                    )));
1103                }
1104            }
1105        }
1106        Ok(())
1107    }
1108
1109    fn validate_wire(&self, wire: DagWire) -> Result<(), CircuitError> {
1110        match wire {
1111            DagWire::Qubit(qubit) => {
1112                if !self.qubits.contains(&qubit) {
1113                    return Err(CircuitError::QubitNotFound(qubit.id()));
1114                }
1115            }
1116            DagWire::ClassicalVar(var) => {
1117                self.validate_classical_var(var)?;
1118            }
1119            DagWire::ClassicalValue(value) => {
1120                self.validate_classical_value(value)?;
1121            }
1122            DagWire::GlobalOrder => {}
1123        }
1124        Ok(())
1125    }
1126
1127    fn validate_operation(&self, operation: &Operation) -> Result<(), CircuitError> {
1128        for qubit in &operation.qubits {
1129            if !self.qubits.contains(qubit) {
1130                return Err(CircuitError::QubitNotFound(qubit.id()));
1131            }
1132        }
1133        for param in &operation.params {
1134            if let CircuitParam::Index(index) = param
1135                && self.parameters.get_index(*index as usize).is_none()
1136            {
1137                return Err(CircuitError::InvalidParameterIndex(*index));
1138            }
1139        }
1140        self.validate_operation_classical_resources(operation)?;
1141        Ok(())
1142    }
1143
1144    fn validate_classical_var(&self, var: ClassicalVar) -> Result<(), CircuitError> {
1145        match self.classical_vars.get(var.index() as usize) {
1146            Some(ty) if *ty == var.ty() => Ok(()),
1147            Some(_) => Err(CircuitError::InvalidDag(format!(
1148                "classical var {} has mismatched type",
1149                var.index()
1150            ))),
1151            None => Err(CircuitError::ForeignClassicalHandle {
1152                kind: "classical var",
1153                index: var.index(),
1154            }),
1155        }
1156    }
1157
1158    fn validate_classical_value(&self, value: ClassicalValue) -> Result<(), CircuitError> {
1159        match self.classical_values.get(value.index() as usize) {
1160            Some(ty) if *ty == value.ty() => Ok(()),
1161            Some(_) => Err(CircuitError::InvalidDag(format!(
1162                "classical value {} has mismatched type",
1163                value.index()
1164            ))),
1165            None => Err(CircuitError::ForeignClassicalHandle {
1166                kind: "classical value",
1167                index: value.index(),
1168            }),
1169        }
1170    }
1171
1172    fn validate_control_flow_payload(
1173        &self,
1174        operation: &Operation,
1175        control: &DagControlFlow,
1176    ) -> Result<(), CircuitError> {
1177        if !matches!(operation.instruction, Instruction::ClassicalControl(_)) {
1178            return Err(CircuitError::InvalidDag(
1179                "control-flow payload attached to non-control operation".to_string(),
1180            ));
1181        }
1182        match control {
1183            DagControlFlow::If {
1184                then_body,
1185                else_body,
1186            } => {
1187                then_body.validate()?;
1188                if let Some(body) = else_body {
1189                    body.validate()?;
1190                }
1191            }
1192            DagControlFlow::While { body } | DagControlFlow::For { body } => body.validate()?,
1193            DagControlFlow::Switch { cases, default } => {
1194                for case in cases {
1195                    case.body.validate()?;
1196                }
1197                if let Some(body) = default {
1198                    body.validate()?;
1199                }
1200            }
1201            DagControlFlow::Break | DagControlFlow::Continue => {}
1202        }
1203        Ok(())
1204    }
1205
1206    fn parameter_value(&self, parameter: &CircuitParam) -> Result<ParameterValue, CircuitError> {
1207        match parameter {
1208            CircuitParam::Fixed(value) => Ok(ParameterValue::Fixed(*value)),
1209            CircuitParam::Index(index) => self
1210                .parameters
1211                .get_index(*index as usize)
1212                .cloned()
1213                .map(ParameterValue::Param)
1214                .ok_or(CircuitError::InvalidParameterIndex(*index)),
1215        }
1216    }
1217
1218    fn lower_value_operation(
1219        &mut self,
1220        operation: ValueOperation,
1221    ) -> Result<Operation, CircuitError> {
1222        let instruction = self.lower_value_instruction(operation.instruction)?;
1223        let params = operation
1224            .params
1225            .into_iter()
1226            .enumerate()
1227            .map(
1228                |(param_index, param)| -> Result<CircuitParam, CircuitError> {
1229                    match param {
1230                        ParameterValue::Param(param) => {
1231                            let (index, _) = self.add_parameter(param);
1232                            Ok(CircuitParam::Index(index as u32))
1233                        }
1234                        ParameterValue::Fixed(value) => {
1235                            if !value.is_finite() {
1236                                return Err(CircuitError::InvalidParameterValue(
1237                                    param_index,
1238                                    value,
1239                                ));
1240                            }
1241                            Ok(CircuitParam::Fixed(value))
1242                        }
1243                    }
1244                },
1245            )
1246            .collect::<Result<_, _>>()?;
1247        Ok(Operation {
1248            instruction,
1249            qubits: operation.qubits,
1250            params,
1251            label: operation.label,
1252        })
1253    }
1254
1255    fn lower_value_instruction(
1256        &mut self,
1257        instruction: ValueInstruction,
1258    ) -> Result<Instruction, CircuitError> {
1259        let op = match instruction {
1260            ValueInstruction::Instruction(Instruction::ClassicalControl(_)) => {
1261                return Err(CircuitError::InvalidOperation(
1262                    "ValueInstruction::Instruction cannot wrap Instruction::ClassicalControl"
1263                        .to_string(),
1264                ));
1265            }
1266            ValueInstruction::Instruction(instruction) => return Ok(instruction),
1267            ValueInstruction::ClassicalControl(op) => op,
1268        };
1269
1270        let op = match op {
1271            ValueClassicalControlOp::If {
1272                condition,
1273                then_body,
1274                else_body,
1275            } => {
1276                let then_body = self.lower_value_body(then_body)?;
1277                let else_body = else_body
1278                    .map(|body| self.lower_value_body(body))
1279                    .transpose()?;
1280                IfOp::new(condition, then_body, else_body).map(ClassicalControlOp::If)?
1281            }
1282            ValueClassicalControlOp::While { condition, body } => {
1283                let body = self.lower_value_body(body)?;
1284                WhileOp::new(condition, body).map(ClassicalControlOp::While)?
1285            }
1286            ValueClassicalControlOp::For {
1287                var,
1288                start,
1289                stop,
1290                step,
1291                body,
1292            } => {
1293                let body = self.lower_value_body(body)?;
1294                ForOp::new(var, start, stop, step, body).map(ClassicalControlOp::For)?
1295            }
1296            ValueClassicalControlOp::Switch {
1297                target,
1298                cases,
1299                default,
1300            } => {
1301                let cases = cases
1302                    .into_iter()
1303                    .map(|case| {
1304                        Ok(SwitchCase::new(
1305                            case.value,
1306                            self.lower_value_body(case.body)?,
1307                        ))
1308                    })
1309                    .collect::<Result<Vec<_>, CircuitError>>()?;
1310                let default = default
1311                    .map(|body| self.lower_value_body(body))
1312                    .transpose()?;
1313                SwitchOp::new(target, cases, default).map(ClassicalControlOp::Switch)?
1314            }
1315            ValueClassicalControlOp::Break => ClassicalControlOp::Break,
1316            ValueClassicalControlOp::Continue => ClassicalControlOp::Continue,
1317        };
1318        Ok(Instruction::ClassicalControl(op))
1319    }
1320
1321    fn lower_value_body(&mut self, body: ValueControlBody) -> Result<ControlBody, CircuitError> {
1322        body.operations()
1323            .iter()
1324            .cloned()
1325            .map(|operation| self.lower_value_operation(operation))
1326            .collect::<Result<Vec<_>, _>>()
1327            .map(ControlBody::new)
1328    }
1329
1330    fn global_phase_parameter(&self) -> Result<Parameter, CircuitError> {
1331        match self.global_phase {
1332            CircuitParam::Fixed(value) => Ok(Parameter::from(value)),
1333            CircuitParam::Index(index) => self
1334                .parameters
1335                .get_index(index as usize)
1336                .cloned()
1337                .ok_or(CircuitError::InvalidParameterIndex(index)),
1338        }
1339    }
1340
1341    fn topological_operations(&self) -> Result<Vec<Operation>, CircuitError> {
1342        Ok(self
1343            .topological_op_nodes_ref()?
1344            .iter()
1345            .copied()
1346            .map(|node| {
1347                self.operation(node)
1348                    .expect("topological_op_nodes returns only operation nodes")
1349                    .clone()
1350            })
1351            .collect())
1352    }
1353
1354    fn topological_op_nodes_ref(&self) -> Result<&Vec<NodeIndex>, CircuitError> {
1355        if self.topological_op_nodes_cache.get().is_none() {
1356            let nodes = lexicographical_topological_sort(
1357                &self.graph,
1358                |node| Ok::<_, Infallible>(self.topological_sort_key(node)),
1359                false,
1360                None,
1361            )
1362            .map_err(|error| {
1363                CircuitError::InvalidDag(format!("DAG topological sort failed: {error}"))
1364            })?;
1365            let nodes = nodes
1366                .into_iter()
1367                .filter(|node| matches!(self.graph[*node], DagNode::Operation { .. }))
1368                .collect::<Vec<_>>();
1369            let _ = self.topological_op_nodes_cache.set(nodes);
1370        }
1371        self.topological_op_nodes_cache.get().ok_or_else(|| {
1372            CircuitError::InvalidDag("topological operation cache was not initialized".to_string())
1373        })
1374    }
1375
1376    fn node_layers_ref(&self) -> Result<&IndexMap<NodeIndex, usize>, CircuitError> {
1377        if self.node_layers_cache.get().is_none() {
1378            let mut ordered_layers = IndexMap::new();
1379            for node in self.topological_op_nodes_ref()?.iter().copied() {
1380                let mut layer = 0usize;
1381                for pred in self
1382                    .predecessors(node)
1383                    .filter(|pred| matches!(self.graph[*pred], DagNode::Operation { .. }))
1384                {
1385                    let predecessor_layer =
1386                        ordered_layers.get(&pred).copied().ok_or_else(|| {
1387                            CircuitError::InvalidDag(format!(
1388                                "operation predecessor {:?} was not visited before {:?}",
1389                                pred, node
1390                            ))
1391                        })?;
1392                    layer = layer.max(predecessor_layer + 1);
1393                }
1394                ordered_layers.insert(node, layer);
1395            }
1396            let _ = self.node_layers_cache.set(ordered_layers);
1397        }
1398        self.node_layers_cache.get().ok_or_else(|| {
1399            CircuitError::InvalidDag("node layer cache was not initialized".to_string())
1400        })
1401    }
1402
1403    pub(crate) fn operation_order(&self, node: NodeIndex) -> Option<usize> {
1404        match self.graph.node_weight(node) {
1405            Some(DagNode::Operation { order, .. }) => Some(*order),
1406            _ => None,
1407        }
1408    }
1409
1410    fn topological_sort_key(&self, node: NodeIndex) -> (u8, usize, usize) {
1411        match self.graph.node_weight(node) {
1412            Some(DagNode::WireIn(_)) => (0, node.index(), 0),
1413            Some(DagNode::Operation { order, .. }) => (1, *order, node.index()),
1414            Some(DagNode::WireOut(_)) => (2, node.index(), 0),
1415            None => (3, node.index(), 0),
1416        }
1417    }
1418
1419    fn require_operation_order(&self, node: NodeIndex) -> Result<usize, CircuitError> {
1420        self.operation_order(node)
1421            .ok_or_else(|| CircuitError::InvalidDag(format!("node {:?} is not an operation", node)))
1422    }
1423
1424    pub(crate) fn node_for_order(&self, order: usize) -> Option<NodeIndex> {
1425        self.order_to_node.get(order).copied()
1426    }
1427
1428    fn operation_resources(&self, operation: &Operation) -> OperationResources {
1429        let mut resources = OperationResources::from_operation(operation);
1430        if matches!(
1431            operation.instruction,
1432            Instruction::Directive(Directive::Barrier)
1433        ) && operation.qubits.is_empty()
1434        {
1435            resources.writes.clear();
1436            resources
1437                .writes
1438                .extend(self.qubits.iter().copied().map(DagWire::Qubit));
1439        }
1440        resources
1441    }
1442
1443    fn validate_operation_classical_resources(
1444        &self,
1445        operation: &Operation,
1446    ) -> Result<(), CircuitError> {
1447        let resources = self.operation_resources(operation);
1448        for wire in resources.reads.iter().chain(resources.writes.iter()) {
1449            self.validate_wire(*wire)?;
1450        }
1451        Ok(())
1452    }
1453
1454    fn validate_replacement(
1455        &self,
1456        old: &Operation,
1457        replacement: &CircuitDag,
1458    ) -> Result<Vec<Operation>, CircuitError> {
1459        let replacement_operations = replacement.topological_operations()?;
1460        self.validate_replacement_control_flow_shape(old, &replacement_operations)?;
1461        self.validate_replacement_footprint(old, replacement, &replacement_operations)?;
1462        Ok(replacement_operations)
1463    }
1464
1465    fn validate_replacement_control_flow_shape(
1466        &self,
1467        old: &Operation,
1468        replacement_operations: &[Operation],
1469    ) -> Result<(), CircuitError> {
1470        if !matches!(old.instruction, Instruction::ClassicalControl(_)) {
1471            return Ok(());
1472        }
1473
1474        let [replacement_op] = replacement_operations else {
1475            return Err(CircuitError::InvalidDag(
1476                "control-flow operation replacement must contain exactly one operation".to_string(),
1477            ));
1478        };
1479
1480        if !same_control_flow_kind(&old.instruction, &replacement_op.instruction) {
1481            return Err(CircuitError::InvalidDag(
1482                "control-flow operation replacement must preserve the control-flow kind"
1483                    .to_string(),
1484            ));
1485        }
1486        Ok(())
1487    }
1488
1489    fn validate_replacement_footprint(
1490        &self,
1491        old: &Operation,
1492        replacement: &CircuitDag,
1493        replacement_operations: &[Operation],
1494    ) -> Result<(), CircuitError> {
1495        let old_resources = self.operation_resources(old);
1496        let readable = old_resources
1497            .reads
1498            .union(&old_resources.writes)
1499            .copied()
1500            .collect::<IndexSet<_>>();
1501
1502        for operation in replacement_operations {
1503            let resources = replacement.operation_resources(operation);
1504            for wire in &resources.writes {
1505                if *wire != DagWire::GlobalOrder && !old_resources.writes.contains(wire) {
1506                    return Err(CircuitError::InvalidDag(format!(
1507                        "replacement writes wire {:?} outside replaced node write footprint",
1508                        wire
1509                    )));
1510                }
1511            }
1512            for wire in &resources.reads {
1513                if *wire != DagWire::GlobalOrder && !readable.contains(wire) {
1514                    return Err(CircuitError::InvalidDag(format!(
1515                        "replacement reads wire {:?} outside replaced node footprint",
1516                        wire
1517                    )));
1518                }
1519            }
1520        }
1521        Ok(())
1522    }
1523
1524    fn add_operation_counts_recursive(&self, counts: &mut IndexMap<String, usize>) {
1525        for node in self.op_nodes() {
1526            if let Some(operation) = self.operation(node) {
1527                let name = operation.instruction.name();
1528                *counts.entry(name).or_insert(0) += 1;
1529            }
1530            if let Some(control) = self.control_flow(node) {
1531                for body in control.body_dags() {
1532                    body.add_operation_counts_recursive(counts);
1533                }
1534            }
1535        }
1536    }
1537}
1538
1539impl DagControlFlow {
1540    fn body_dags(&self) -> Box<dyn Iterator<Item = &CircuitDag> + '_> {
1541        match self {
1542            DagControlFlow::If {
1543                then_body,
1544                else_body,
1545            } => Box::new(std::iter::once(then_body.as_ref()).chain(else_body.as_deref())),
1546            DagControlFlow::While { body } | DagControlFlow::For { body } => {
1547                Box::new(std::iter::once(body.as_ref()))
1548            }
1549            DagControlFlow::Switch { cases, default } => Box::new(
1550                cases
1551                    .iter()
1552                    .map(|case| case.body.as_ref())
1553                    .chain(default.as_deref()),
1554            ),
1555            DagControlFlow::Break | DagControlFlow::Continue => Box::new(std::iter::empty()),
1556        }
1557    }
1558}
1559
1560#[derive(Debug, Clone, Default)]
1561struct OperationResources {
1562    reads: IndexSet<DagWire>,
1563    writes: IndexSet<DagWire>,
1564}
1565
1566impl OperationResources {
1567    fn from_operation(operation: &Operation) -> Self {
1568        let mut resources = Self::default();
1569        resources
1570            .writes
1571            .extend(operation.qubits.iter().copied().map(DagWire::Qubit));
1572
1573        match &operation.instruction {
1574            Instruction::ClassicalData(op) => match op {
1575                ClassicalDataOp::Store { target, value } => {
1576                    resources.add_expr_reads(value);
1577                    resources.writes.insert(DagWire::ClassicalVar(*target));
1578                }
1579                ClassicalDataOp::MeasureBit { result }
1580                | ClassicalDataOp::MeasureBits { result } => {
1581                    resources.writes.insert(DagWire::ClassicalValue(*result));
1582                }
1583            },
1584            Instruction::ClassicalControl(control) => resources.add_control_resources(control),
1585            _ => {}
1586        }
1587        resources
1588    }
1589
1590    fn add_control_resources(&mut self, control: &ClassicalControlOp) {
1591        match control {
1592            ClassicalControlOp::If(op) => {
1593                self.add_expr_reads(op.condition());
1594                self.add_body_resources(op.then_body().operations());
1595                if let Some(body) = op.else_body() {
1596                    self.add_body_resources(body.operations());
1597                }
1598            }
1599            ClassicalControlOp::While(op) => {
1600                self.add_expr_reads(op.condition());
1601                self.add_body_resources(op.body().operations());
1602            }
1603            ClassicalControlOp::For(op) => {
1604                self.writes.insert(DagWire::ClassicalVar(op.var()));
1605                self.add_expr_reads(op.start());
1606                self.add_expr_reads(op.stop());
1607                self.add_expr_reads(op.step());
1608                self.add_body_resources(op.body().operations());
1609            }
1610            ClassicalControlOp::Switch(op) => {
1611                self.add_expr_reads(op.target());
1612                for case in op.cases() {
1613                    self.add_body_resources(case.body().operations());
1614                }
1615                if let Some(body) = op.default() {
1616                    self.add_body_resources(body.operations());
1617                }
1618            }
1619            ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
1620        }
1621    }
1622
1623    fn add_body_resources(&mut self, operations: &[Operation]) {
1624        for operation in operations {
1625            let nested = Self::from_operation(operation);
1626            self.reads.extend(nested.reads);
1627            self.writes.extend(nested.writes);
1628        }
1629    }
1630
1631    fn add_expr_reads(&mut self, expr: &ClassicalExpr) {
1632        self.reads
1633            .extend(expr.vars().into_iter().map(DagWire::ClassicalVar));
1634        self.reads
1635            .extend(expr.values().into_iter().map(DagWire::ClassicalValue));
1636    }
1637}
1638
1639fn is_one_qubit_gate(operation: &Operation) -> bool {
1640    operation.qubits.len() == 1
1641        && matches!(
1642            operation.instruction,
1643            Instruction::Standard(_)
1644                | Instruction::McGate(_)
1645                | Instruction::UnitaryGate(_)
1646                | Instruction::CircuitGate(_)
1647        )
1648}
1649
1650fn is_two_qubit_gate(operation: &Operation) -> bool {
1651    operation.qubits.len() == 2
1652        && matches!(
1653            operation.instruction,
1654            Instruction::Standard(_)
1655                | Instruction::McGate(_)
1656                | Instruction::UnitaryGate(_)
1657                | Instruction::CircuitGate(_)
1658        )
1659}
1660
1661fn same_control_flow_kind(lhs: &Instruction, rhs: &Instruction) -> bool {
1662    matches!((lhs, rhs), (Instruction::ClassicalControl(lhs), Instruction::ClassicalControl(rhs)) if lhs.kind() == rhs.kind())
1663}
1664
1665impl TryFrom<&Circuit> for CircuitDag {
1666    type Error = CircuitError;
1667
1668    fn try_from(circuit: &Circuit) -> Result<Self, Self::Error> {
1669        Self::from_circuit(circuit)
1670    }
1671}
1672
1673impl TryFrom<&CircuitDag> for Circuit {
1674    type Error = CircuitError;
1675
1676    fn try_from(dag: &CircuitDag) -> Result<Self, Self::Error> {
1677        dag.to_circuit()
1678    }
1679}
1680
1681#[cfg(test)]
1682#[path = "./dag_test.rs"]
1683mod dag_test;