Skip to main content

cqlib_core/compile/transform/rewrite/
config.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//! Configuration and local cost model for knowledge-based rewrite.
14
15use crate::circuit::{Instruction, MCGate, StandardGate};
16use crate::compile::error::CompilerError;
17use crate::compile::knowledge::library::RuleKind;
18
19/// High-level rule application mode.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum RewriteMode {
22    /// Conservative optimization. Accepted rewrites must strictly improve local cost.
23    Optimize,
24    /// Explicit lowering. Decomposition and hardware-native rules may expand locally.
25    Lowering,
26}
27
28/// Stable configuration for the knowledge-based rewrite transformer.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct RewriteConfig {
31    max_rounds: u8,
32    max_window_ops: usize,
33    max_pattern_len: usize,
34    recurse_control_flow: bool,
35    skip_labeled_ops: bool,
36    preserve_two_qubit_connectivity: bool,
37    enabled_kinds: Vec<RuleKind>,
38    mode: RewriteMode,
39    target_instructions: Option<Vec<TargetInstruction>>,
40}
41
42/// Gate-like instruction accepted by target-basis lowering.
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub(super) enum TargetInstruction {
45    Standard(StandardGate),
46    McGate(MCGate),
47}
48
49impl Default for RewriteConfig {
50    fn default() -> Self {
51        Self::production()
52    }
53}
54
55impl RewriteConfig {
56    /// Production defaults for conservative logical optimization.
57    pub fn production() -> Self {
58        Self {
59            max_rounds: 8,
60            max_window_ops: 16,
61            max_pattern_len: 8,
62            recurse_control_flow: true,
63            skip_labeled_ops: true,
64            preserve_two_qubit_connectivity: false,
65            enabled_kinds: vec![
66                RuleKind::Simplify,
67                RuleKind::Cancel,
68                RuleKind::Merge,
69                RuleKind::Canonicalize,
70            ],
71            mode: RewriteMode::Optimize,
72            target_instructions: None,
73        }
74    }
75
76    /// Defaults for explicit knowledge-based lowering.
77    pub fn lowering() -> Self {
78        Self {
79            enabled_kinds: vec![
80                RuleKind::Simplify,
81                RuleKind::Cancel,
82                RuleKind::Merge,
83                RuleKind::Canonicalize,
84                RuleKind::Decompose,
85                RuleKind::HardwareNative,
86            ],
87            mode: RewriteMode::Lowering,
88            ..Self::production()
89        }
90    }
91
92    /// Returns a new production configuration.
93    pub fn new() -> Self {
94        Self::production()
95    }
96
97    pub fn with_max_rounds(mut self, max_rounds: u8) -> Self {
98        self.max_rounds = max_rounds;
99        self
100    }
101
102    pub fn with_max_window_ops(mut self, max_window_ops: usize) -> Self {
103        self.max_window_ops = max_window_ops;
104        self
105    }
106
107    pub fn with_max_pattern_len(mut self, max_pattern_len: usize) -> Self {
108        self.max_pattern_len = max_pattern_len;
109        self
110    }
111
112    pub fn recurse_control_flow(mut self, enabled: bool) -> Self {
113        self.recurse_control_flow = enabled;
114        self
115    }
116
117    pub fn skip_labeled_ops(mut self, enabled: bool) -> Self {
118        self.skip_labeled_ops = enabled;
119        self
120    }
121
122    /// Restricts rewrites to those that cannot introduce new undirected
123    /// two-qubit connectivity.
124    ///
125    /// This is the post-routing safety policy: once a circuit has been
126    /// physically mapped, every two-qubit operation acts on a device coupling
127    /// edge, and a rewrite rule whose replacement uses a qubit pair absent
128    /// from its match (e.g. a CX bridge collapsing `CX 0 1, CX 1 2, CX 0 1,
129    /// CX 1 2` to `CX 0 2`) would break that mapping. When enabled, rules
130    /// are statically filtered before matching so replacements only act on
131    /// pairs already present in the matched operations.
132    ///
133    /// The guarantee is undirected topology connectivity, not ordered native
134    /// legality: direction flips such as `CX 0 1 -> H 0, H 1, CX 1 0, H 0,
135    /// H 1` remain allowed, since gate direction is the device-lowering
136    /// stage's responsibility.
137    pub fn with_preserve_two_qubit_connectivity(mut self, enabled: bool) -> Self {
138        self.preserve_two_qubit_connectivity = enabled;
139        self
140    }
141
142    pub fn with_enabled_kinds(mut self, kinds: Vec<RuleKind>) -> Self {
143        self.enabled_kinds = kinds;
144        self
145    }
146
147    pub fn with_mode(mut self, mode: RewriteMode) -> Self {
148        self.mode = mode;
149        self
150    }
151
152    /// Restricts lowering to an explicit gate-like target instruction basis.
153    pub fn with_target_instructions(
154        self,
155        target_instructions: Vec<Instruction>,
156    ) -> Result<Self, CompilerError> {
157        self.try_with_target_instructions(target_instructions)
158    }
159
160    /// Restricts lowering to an explicit gate-like target instruction basis.
161    pub fn try_with_target_instructions(
162        mut self,
163        target_instructions: Vec<Instruction>,
164    ) -> Result<Self, CompilerError> {
165        if target_instructions.is_empty() {
166            return Err(CompilerError::InvalidInput(
167                "rewrite target instruction basis must not be empty".to_string(),
168            ));
169        }
170
171        let mut deduped = Vec::with_capacity(target_instructions.len());
172        for instruction in target_instructions {
173            let instruction = match instruction {
174                Instruction::Standard(gate) => TargetInstruction::Standard(gate),
175                Instruction::McGate(gate) => TargetInstruction::McGate(*gate),
176                other => {
177                    return Err(CompilerError::InvalidInput(format!(
178                        "unsupported rewrite target instruction {other:?}"
179                    )));
180                }
181            };
182
183            if !deduped.contains(&instruction) {
184                deduped.push(instruction);
185            }
186        }
187
188        self.target_instructions = Some(deduped);
189        Ok(self)
190    }
191
192    pub const fn max_rounds(&self) -> u8 {
193        self.max_rounds
194    }
195
196    pub const fn max_window_ops(&self) -> usize {
197        self.max_window_ops
198    }
199
200    pub const fn max_pattern_len(&self) -> usize {
201        self.max_pattern_len
202    }
203
204    pub const fn recurses_control_flow(&self) -> bool {
205        self.recurse_control_flow
206    }
207
208    pub const fn skips_labeled_ops(&self) -> bool {
209        self.skip_labeled_ops
210    }
211
212    /// Returns whether rewrites must preserve undirected two-qubit
213    /// connectivity, rejecting rules that may act on new qubit pairs.
214    pub const fn preserve_two_qubit_connectivity(&self) -> bool {
215        self.preserve_two_qubit_connectivity
216    }
217
218    pub const fn mode(&self) -> RewriteMode {
219        self.mode
220    }
221
222    /// Returns the rule categories enabled by this configuration.
223    pub fn enabled_kinds(&self) -> &[RuleKind] {
224        &self.enabled_kinds
225    }
226
227    /// Returns the configured target instruction basis in insertion order.
228    pub fn target_instruction_basis(&self) -> Option<Vec<Instruction>> {
229        self.target_instructions.as_ref().map(|instructions| {
230            instructions
231                .iter()
232                .map(|instruction| match instruction {
233                    TargetInstruction::Standard(gate) => Instruction::Standard(*gate),
234                    TargetInstruction::McGate(gate) => Instruction::McGate(Box::new(gate.clone())),
235                })
236                .collect()
237        })
238    }
239
240    pub(super) fn target_instructions(&self) -> Option<&[TargetInstruction]> {
241        self.target_instructions.as_deref()
242    }
243
244    pub(super) fn enables_kind(&self, kind: RuleKind) -> bool {
245        self.enabled_kinds.contains(&kind)
246    }
247
248    pub(super) fn allows_kind(&self, kind: RuleKind) -> bool {
249        if !self.enables_kind(kind) {
250            return false;
251        }
252
253        !matches!(kind, RuleKind::Decompose | RuleKind::HardwareNative)
254            || self.mode == RewriteMode::Lowering
255    }
256
257    pub(super) fn allows_rewrite(
258        &self,
259        kind: RuleKind,
260        before: LocalRewriteCost,
261        after: LocalRewriteCost,
262    ) -> bool {
263        if after.unsupported_ops > before.unsupported_ops {
264            return false;
265        }
266        if after < before {
267            return true;
268        }
269        if self.target_instructions.is_some() {
270            return false;
271        }
272
273        self.mode == RewriteMode::Lowering
274            && matches!(kind, RuleKind::Decompose | RuleKind::HardwareNative)
275    }
276}
277
278/// Local rewrite cost used to enforce termination in logical rewrite mode.
279#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
280pub(super) struct LocalRewriteCost {
281    pub(super) unsupported_ops: usize,
282    pub(super) lowering_distance: usize,
283    pub(super) two_qubit_ops: usize,
284    pub(super) multi_qubit_ops: usize,
285    pub(super) depth_estimate: usize,
286    pub(super) total_ops: usize,
287    pub(super) parameterized_ops: usize,
288    // Break otherwise equal costs by expression count, so RXY -> XY2P/RX
289    // specializations strictly decrease cost without permitting neutral cycles.
290    pub(super) parameter_count: usize,
291}
292
293/// Policy for counting `GPhase` operations in local rewrite cost.
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub(super) enum GPhaseCost {
296    ExplicitOperation,
297    ImplicitReplacement,
298}
299
300impl LocalRewriteCost {
301    pub(super) fn add_gate_like(
302        &mut self,
303        standard_gate: Option<StandardGate>,
304        target_supported: bool,
305        qubit_count: usize,
306        param_count: usize,
307        gphase_cost: GPhaseCost,
308    ) -> bool {
309        if standard_gate == Some(StandardGate::GPhase)
310            && gphase_cost == GPhaseCost::ImplicitReplacement
311        {
312            return false;
313        }
314
315        self.total_ops += 1;
316        if !target_supported {
317            self.unsupported_ops += 1;
318        }
319        match qubit_count {
320            0 | 1 => {}
321            2 => self.two_qubit_ops += 1,
322            _ => self.multi_qubit_ops += 1,
323        }
324        if param_count > 0 {
325            self.parameterized_ops += 1;
326        }
327        self.parameter_count += param_count;
328        true
329    }
330}