cqlib_core/compile/commutation/checker.rs
1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2025-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//! High-level gate commutation checker.
14//!
15//! [`CommutationChecker`] is the public entry point for asking whether two
16//! concrete instruction applications may be swapped. It is intentionally
17//! conservative: `None` is not a proof of non-commutation, only a statement
18//! that the configured proof sources did not establish a valid exchange.
19//!
20//! The checker first applies cheap local facts, then algebraic proofs, then
21//! optional knowledge-base commutation rules, and finally optional local-matrix
22//! comparison. This order keeps symbolic queries fast while still supporting
23//! exact numeric fallback for small concrete gates.
24//!
25//! # Examples
26//!
27//! ```rust
28//! use cqlib_core::circuit::{Instruction, Qubit, StandardGate};
29//! use cqlib_core::compile::commutation::{Commutation, CommutationChecker};
30//!
31//! let checker = CommutationChecker::builtin();
32//! let result = checker.check(
33//! &Instruction::Standard(StandardGate::H),
34//! &[Qubit::new(0)],
35//! &[],
36//! &Instruction::Standard(StandardGate::X),
37//! &[Qubit::new(1)],
38//! &[],
39//! );
40//!
41//! assert_eq!(result, Some(Commutation::Exact));
42//! ```
43
44use crate::circuit::{Instruction, Parameter, Qubit, StandardGate};
45use crate::compile::PARAMETER_EQ_TOLERANCE;
46use crate::compile::commutation::algebra::algebraic_commutation;
47use crate::compile::commutation::matrix::matrix_commutation;
48use crate::compile::commutation::rules::RuleCommutationOracle;
49use crate::compile::knowledge::library::RuleLibrary;
50use std::sync::OnceLock;
51
52/// Proven relationship between two commuting operations.
53///
54/// [`Commutation::UpToGlobalPhase`] uses the phase angle `phase` in:
55///
56/// `lhs * rhs = exp(i * phase) * rhs * lhs`
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum Commutation {
59 /// The operations commute exactly.
60 Exact,
61 /// The operations commute after accounting for a global phase.
62 UpToGlobalPhase(Parameter),
63}
64
65impl Commutation {
66 /// Returns whether this proof is exact and does not introduce a global phase.
67 pub fn is_exact(&self) -> bool {
68 matches!(self, Self::Exact)
69 }
70
71 /// Returns the global phase associated with this proof.
72 pub fn phase(&self) -> Parameter {
73 match self {
74 Self::Exact => Parameter::from(0.0),
75 Self::UpToGlobalPhase(phase) => phase.clone(),
76 }
77 }
78}
79
80/// Result of a commutation query.
81///
82/// `None` means the checker cannot prove that the operations commute.
83pub type CommutationResult = Option<Commutation>;
84
85/// Configuration for [`CommutationChecker`].
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct CommutationConfig {
88 /// Enables matching explicit `A; B -> B; A` rules from the compiler knowledge base.
89 pub enable_rule_oracle: bool,
90 /// Enables small local matrix comparison as a fallback for concrete gates.
91 pub enable_matrix_fallback: bool,
92 /// Maximum number of qubits in the union support for matrix fallback.
93 ///
94 /// This bound applies to the sorted union of the two operations' qubit
95 /// supports, not to each operation separately.
96 pub max_matrix_qubits: usize,
97}
98
99impl Default for CommutationConfig {
100 fn default() -> Self {
101 Self {
102 enable_rule_oracle: true,
103 enable_matrix_fallback: true,
104 max_matrix_qubits: 4,
105 }
106 }
107}
108
109/// Reusable commutation checker for compiler passes.
110#[derive(Debug, Clone)]
111pub struct CommutationChecker {
112 /// Optional knowledge-base oracle for explicit commutation rules.
113 rules: Option<RuleCommutationOracle>,
114 /// Runtime knobs controlling slower or less general proof sources.
115 config: CommutationConfig,
116}
117
118impl CommutationChecker {
119 /// Builds a checker with builtin commutation rules and default configuration.
120 pub fn builtin() -> Self {
121 Self::with_config(CommutationConfig::default())
122 }
123
124 /// Builds a checker with builtin commutation rules and an explicit configuration.
125 pub fn with_config(config: CommutationConfig) -> Self {
126 let rules = if config.enable_rule_oracle {
127 Some(RuleCommutationOracle::builtin())
128 } else {
129 None
130 };
131
132 Self { rules, config }
133 }
134
135 /// Builds a checker using commutation rules from an already loaded library.
136 pub fn from_library(library: &RuleLibrary, config: CommutationConfig) -> Self {
137 let rules = if config.enable_rule_oracle {
138 Some(RuleCommutationOracle::from_library(library))
139 } else {
140 None
141 };
142
143 Self { rules, config }
144 }
145
146 /// Returns the active checker configuration.
147 pub const fn config(&self) -> &CommutationConfig {
148 &self.config
149 }
150
151 /// Checks whether two concrete instruction applications commute.
152 ///
153 /// The qubit and parameter slices must match the instruction arities, and
154 /// each operation must use distinct qubits internally. Non-gate
155 /// instructions such as directives, delays, and control-flow gates are
156 /// outside the checker and return `None`.
157 ///
158 /// Proofs are attempted in a fixed order:
159 ///
160 /// 1. cheap local facts: identity, global phase, disjoint support, and the
161 /// same application;
162 /// 2. symbolic algebra over supported gate families;
163 /// 3. explicit knowledge-base commutation rules, when enabled;
164 /// 4. concrete local matrix comparison, when enabled and small enough.
165 pub fn check(
166 &self,
167 lhs_inst: &Instruction,
168 lhs_qubits: &[Qubit],
169 lhs_params: &[Parameter],
170 rhs_inst: &Instruction,
171 rhs_qubits: &[Qubit],
172 rhs_params: &[Parameter],
173 ) -> CommutationResult {
174 let (lhs_expected_qubits, lhs_expected_params) = lhs_inst.gate_arity()?;
175 let (rhs_expected_qubits, rhs_expected_params) = rhs_inst.gate_arity()?;
176 if lhs_qubits.len() != lhs_expected_qubits
177 || lhs_params.len() != lhs_expected_params
178 || rhs_qubits.len() != rhs_expected_qubits
179 || rhs_params.len() != rhs_expected_params
180 || lhs_qubits
181 .iter()
182 .enumerate()
183 .any(|(index, qubit)| lhs_qubits[index + 1..].contains(qubit))
184 || rhs_qubits
185 .iter()
186 .enumerate()
187 .any(|(index, qubit)| rhs_qubits[index + 1..].contains(qubit))
188 {
189 return None;
190 }
191
192 if matches!(lhs_inst, Instruction::Standard(StandardGate::I))
193 || matches!(rhs_inst, Instruction::Standard(StandardGate::I))
194 || matches!(lhs_inst, Instruction::McGate(gate) if *gate.base_gate() == StandardGate::I)
195 || matches!(rhs_inst, Instruction::McGate(gate) if *gate.base_gate() == StandardGate::I)
196 || matches!(lhs_inst, Instruction::Standard(StandardGate::GPhase))
197 || matches!(rhs_inst, Instruction::Standard(StandardGate::GPhase))
198 || !lhs_qubits.iter().any(|qubit| rhs_qubits.contains(qubit))
199 {
200 return Some(Commutation::Exact);
201 }
202 if same_application(
203 lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
204 ) {
205 return Some(Commutation::Exact);
206 }
207
208 if let Some(result) = algebraic_commutation(
209 lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
210 ) {
211 return Some(result);
212 }
213
214 if let Some(rules) = self.rules.as_ref()
215 && let Some(result) = rules.check(
216 lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
217 )
218 {
219 return Some(result);
220 }
221
222 if self.config.enable_matrix_fallback {
223 return matrix_commutation(
224 lhs_inst,
225 lhs_qubits,
226 lhs_params,
227 rhs_inst,
228 rhs_qubits,
229 rhs_params,
230 self.config.max_matrix_qubits,
231 );
232 }
233
234 None
235 }
236}
237
238/// Checks commutation with the shared builtin checker.
239///
240/// The builtin instance enables both knowledge-base rules and matrix fallback
241/// with the default qubit limit.
242///
243/// # Examples
244///
245/// ```rust
246/// use cqlib_core::circuit::{Instruction, Qubit, StandardGate};
247/// use cqlib_core::compile::commutation::{check_commutation, Commutation};
248///
249/// let result = check_commutation(
250/// &Instruction::Standard(StandardGate::X),
251/// &[Qubit::new(0)],
252/// &[],
253/// &Instruction::Standard(StandardGate::X),
254/// &[Qubit::new(0)],
255/// &[],
256/// );
257///
258/// assert_eq!(result, Some(Commutation::Exact));
259/// ```
260pub fn check_commutation(
261 lhs_inst: &Instruction,
262 lhs_qubits: &[Qubit],
263 lhs_params: &[Parameter],
264 rhs_inst: &Instruction,
265 rhs_qubits: &[Qubit],
266 rhs_params: &[Parameter],
267) -> CommutationResult {
268 static CHECKER: OnceLock<CommutationChecker> = OnceLock::new();
269 CHECKER.get_or_init(CommutationChecker::builtin).check(
270 lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
271 )
272}
273
274/// Returns whether both operands are the same gate application.
275///
276/// Parameter comparison is symbolic and tolerant, so expressions that are
277/// provably equal are treated as the same application even when they are not
278/// represented by identical syntax.
279fn same_application(
280 lhs_inst: &Instruction,
281 lhs_qubits: &[Qubit],
282 lhs_params: &[Parameter],
283 rhs_inst: &Instruction,
284 rhs_qubits: &[Qubit],
285 rhs_params: &[Parameter],
286) -> bool {
287 let same_instruction =
288 lhs_inst.is_quantum_gate() && rhs_inst.is_quantum_gate() && lhs_inst == rhs_inst;
289
290 lhs_qubits == rhs_qubits
291 && same_instruction
292 && lhs_params.len() == rhs_params.len()
293 && lhs_params
294 .iter()
295 .zip(rhs_params)
296 .all(|(lhs, rhs)| lhs.provably_equal(rhs, PARAMETER_EQ_TOLERANCE))
297}