Skip to main content

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}