Skip to main content

cqlib_core/compile/transform/
analysis.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//! Structural circuit analysis shared by compiler transforms.
14//!
15//! The analysis reports stable facts about the current IR shape so workflow and
16//! transforms can skip inapplicable work without rescanning the full operation
17//! tree repeatedly.
18
19use crate::circuit::{
20    Circuit, ClassicalControlOp, Directive, Instruction, Operation, StandardGate,
21};
22
23/// Structural facts about a circuit relevant to compiler transforms.
24#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct CircuitAnalysis {
26    pub has_classical_data: bool,
27    pub has_classical_control: bool,
28    pub has_measurement: bool,
29    pub has_classical_values: bool,
30    pub has_classical_vars: bool,
31    pub has_runtime_classical: bool,
32    pub needs_classical_handle_preservation: bool,
33    pub has_circuit_gate_definitions: bool,
34    pub has_unitary_circuit_definitions: bool,
35    pub has_unitary_gates: bool,
36    pub has_mc_gates: bool,
37}
38
39impl CircuitAnalysis {
40    /// Computes structural facts for `circuit`.
41    pub fn analyze(circuit: &Circuit) -> Self {
42        WorkflowCircuitAnalysis::analyze(circuit).public
43    }
44}
45
46/// Allocation-free workflow facts that are intentionally not part of the
47/// public compiler API.
48///
49/// Circuit-backed definitions remain opaque until the definition pass expands
50/// them. A changed circuit invalidates this summary, so operations revealed by
51/// expansion are discovered by the next analysis of the current IR revision.
52#[derive(Debug, Clone, Default, PartialEq, Eq)]
53pub(crate) struct WorkflowCircuitAnalysis {
54    public: CircuitAnalysis,
55    standard_gates: StandardGateSet,
56    instruction_kinds: InstructionKindSet,
57    has_gate_like_operation_over_two_qubits: bool,
58    /// Exact correction remains target-dependent; this fact only identifies
59    /// circuits for which physical operand direction can be relevant.
60    has_direction_sensitive_two_qubit_operation: bool,
61    has_reset: bool,
62    has_control_flow: bool,
63}
64
65impl WorkflowCircuitAnalysis {
66    pub(crate) fn analyze(circuit: &Circuit) -> Self {
67        let mut analysis = Self {
68            public: CircuitAnalysis {
69                has_classical_values: !circuit.classical_values().is_empty(),
70                has_classical_vars: !circuit.classical_vars().is_empty(),
71                ..CircuitAnalysis::default()
72            },
73            ..Self::default()
74        };
75        analysis.scan_operations(circuit.operations());
76        analysis.public.has_runtime_classical = analysis.public.has_classical_data
77            || analysis.public.has_classical_control
78            || analysis.public.has_classical_values
79            || analysis.public.has_classical_vars;
80        analysis.public.needs_classical_handle_preservation = analysis.public.has_runtime_classical;
81        analysis
82    }
83
84    pub(crate) const fn public(&self) -> &CircuitAnalysis {
85        &self.public
86    }
87
88    pub(crate) const fn has_unexpanded_definitions(&self) -> bool {
89        self.public.has_circuit_gate_definitions || self.public.has_unitary_circuit_definitions
90    }
91
92    pub(crate) const fn has_unitary_gates(&self) -> bool {
93        self.public.has_unitary_gates
94    }
95
96    pub(crate) const fn has_mc_gates(&self) -> bool {
97        self.public.has_mc_gates
98    }
99
100    pub(crate) const fn has_gate_like_operation_over_two_qubits(&self) -> bool {
101        self.has_gate_like_operation_over_two_qubits
102    }
103
104    pub(crate) fn standard_gates(&self) -> impl Iterator<Item = StandardGate> + '_ {
105        StandardGate::all()
106            .iter()
107            .copied()
108            .filter(|gate| self.standard_gates.contains(*gate))
109    }
110
111    pub(crate) const fn has_extended_gate_like_operations(&self) -> bool {
112        self.instruction_kinds.contains(InstructionKind::McGate)
113            || self
114                .instruction_kinds
115                .contains(InstructionKind::UnitaryGate)
116            || self
117                .instruction_kinds
118                .contains(InstructionKind::CircuitGate)
119    }
120
121    fn scan_operations(&mut self, operations: &[Operation]) {
122        for operation in operations {
123            self.scan_operation(operation);
124        }
125    }
126
127    fn scan_operation(&mut self, operation: &Operation) {
128        match &operation.instruction {
129            Instruction::Standard(gate) => {
130                self.instruction_kinds.insert(InstructionKind::Standard);
131                self.standard_gates.insert(*gate);
132                self.has_gate_like_operation_over_two_qubits |= operation.qubits.len() > 2;
133                self.has_direction_sensitive_two_qubit_operation |=
134                    gate.num_qubits() == 2 && !gate.is_invariant_under_operand_swap();
135            }
136            Instruction::ClassicalData(op) => {
137                self.instruction_kinds
138                    .insert(InstructionKind::ClassicalData);
139                self.public.has_classical_data = true;
140                if op.result().is_some() {
141                    self.public.has_measurement = true;
142                }
143            }
144            Instruction::ClassicalControl(op) => {
145                self.instruction_kinds
146                    .insert(InstructionKind::ClassicalControl);
147                self.public.has_classical_control = true;
148                self.has_control_flow = true;
149                self.scan_control_flow(op);
150            }
151            Instruction::CircuitGate(_) => {
152                self.instruction_kinds.insert(InstructionKind::CircuitGate);
153                self.public.has_circuit_gate_definitions = true;
154                self.has_gate_like_operation_over_two_qubits |= operation.qubits.len() > 2;
155            }
156            Instruction::UnitaryGate(gate) => {
157                self.instruction_kinds.insert(InstructionKind::UnitaryGate);
158                self.public.has_unitary_gates = true;
159                if gate.circuit().is_some() {
160                    self.public.has_unitary_circuit_definitions = true;
161                }
162                self.has_gate_like_operation_over_two_qubits |= operation.qubits.len() > 2;
163            }
164            Instruction::McGate(_) => {
165                self.instruction_kinds.insert(InstructionKind::McGate);
166                self.public.has_mc_gates = true;
167                self.has_gate_like_operation_over_two_qubits |= operation.qubits.len() > 2;
168            }
169            Instruction::Directive(directive) => {
170                self.instruction_kinds.insert(InstructionKind::Directive);
171                match directive {
172                    Directive::Measure => self.public.has_measurement = true,
173                    Directive::Reset => self.has_reset = true,
174                    Directive::Barrier => {}
175                }
176            }
177            Instruction::Delay => self.instruction_kinds.insert(InstructionKind::Delay),
178        }
179    }
180
181    fn scan_control_flow(&mut self, op: &ClassicalControlOp) {
182        for body in op.bodies() {
183            self.scan_operations(body.operations());
184        }
185    }
186}
187
188#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
189struct StandardGateSet(u64);
190
191impl StandardGateSet {
192    fn insert(&mut self, gate: StandardGate) {
193        self.0 |= 1_u64 << gate as u8;
194    }
195
196    const fn contains(self, gate: StandardGate) -> bool {
197        self.0 & (1_u64 << gate as u8) != 0
198    }
199}
200
201#[repr(u8)]
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203enum InstructionKind {
204    Standard,
205    McGate,
206    UnitaryGate,
207    CircuitGate,
208    Directive,
209    ClassicalData,
210    ClassicalControl,
211    Delay,
212}
213
214#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
215struct InstructionKindSet(u16);
216
217impl InstructionKindSet {
218    fn insert(&mut self, kind: InstructionKind) {
219        self.0 |= 1_u16 << kind as u8;
220    }
221
222    const fn contains(self, kind: InstructionKind) -> bool {
223        self.0 & (1_u16 << kind as u8) != 0
224    }
225}
226
227#[cfg(test)]
228#[path = "./analysis_test.rs"]
229mod analysis_test;