Skip to main content

cqlib_core/compile/knowledge/
library.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//! Rule library and indexes for compiler knowledge-base rules.
14//!
15//! A [`RuleLibrary`] owns validated rules, assigns stable [`RuleId`] values,
16//! and precomputes the metadata and first-instruction index needed by matchers. DSL
17//! parsing remains in `rule_dsl`; this module only delegates to it.
18
19use crate::circuit::Instruction;
20use crate::compile::knowledge::matcher::KnowledgeInstructionKey;
21use crate::compile::knowledge::rule::{Rule, RuleValidationError};
22use crate::compile::knowledge::rule_dsl::load::{
23    LoadError, load_rules_from_file, load_rules_from_str,
24};
25use smallvec::SmallVec;
26use std::collections::{BTreeSet, HashMap};
27use std::path::Path;
28use std::sync::OnceLock;
29
30static BUILTIN_RULES: OnceLock<Result<RuleLibrary, RuleLibraryError>> = OnceLock::new();
31
32/// Stable identifier assigned to a rule when it is inserted into a library.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct RuleId(usize);
35
36impl RuleId {
37    /// Returns the rule's current library-local index.
38    pub const fn as_usize(self) -> usize {
39        self.0
40    }
41}
42
43/// Coarse compiler use-case for a rule.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum RuleKind {
46    /// Algebraic simplification that removes redundant structure.
47    Simplify,
48    /// Cancellation rule for inverse or repeated gates.
49    Cancel,
50    /// Merge rule for combining compatible neighboring gates.
51    Merge,
52    /// Explicit commutation rule of the form `A; B -> B; A`.
53    Commute,
54    /// Decomposition or lowering rule.
55    Decompose,
56    /// Canonical representation rule.
57    Canonicalize,
58    /// Rule that rewrites toward a hardware-native instruction set.
59    HardwareNative,
60    /// Rule without a more specific compiler use-case.
61    Other,
62}
63
64/// Precomputed metadata used by rule selection and diagnostics.
65#[derive(Debug, Clone)]
66pub struct RuleMetadata {
67    /// Stable id assigned by the containing library.
68    pub id: RuleId,
69    /// Coarse compiler use-case for this rule.
70    pub kind: RuleKind,
71    /// Number of operations in the match pattern.
72    pub pattern_len: usize,
73    /// Number of operations emitted by the rewrite target.
74    pub rewrite_len: usize,
75    /// Number of distinct rule-local qubit labels used by the rule.
76    pub qubit_count: usize,
77    /// First instruction in the match pattern.
78    pub first_instruction: Instruction,
79    /// Static operation-count delta, `rewrite_len - pattern_len`.
80    pub cost_delta: isize,
81    /// Whether the rule has non-empty parameter conditions.
82    pub has_conditions: bool,
83}
84
85#[derive(Debug, Clone, Default)]
86struct RuleGateMetadata {
87    match_instruction_keys: SmallVec<[KnowledgeInstructionKey; 4]>,
88    rewrite_instruction_keys: SmallVec<[KnowledgeInstructionKey; 4]>,
89}
90
91/// Errors produced while building or extending a rule library.
92#[derive(Debug, Clone, PartialEq, thiserror::Error)]
93pub enum RuleLibraryError {
94    /// A rule failed structural validation.
95    #[error("invalid rule {name}: {source}")]
96    InvalidRule {
97        /// Name of the invalid rule.
98        name: String,
99        /// Validation failure.
100        source: RuleValidationError,
101    },
102    /// A library cannot contain two rules with the same name.
103    #[error("duplicate rule name: {0}")]
104    DuplicateRuleName(String),
105    /// A rule uses an instruction that cannot be indexed by the matcher.
106    #[error("unsupported instruction for rule library index: {instruction}")]
107    UnsupportedInstruction { instruction: String },
108    /// Loading or parsing rule DSL failed.
109    #[error("failed to load rules: {0}")]
110    Load(LoadError),
111}
112
113/// Validated rule collection with indexes for matcher candidate lookup.
114#[derive(Debug, Clone, Default)]
115pub struct RuleLibrary {
116    rules: Vec<Rule>,
117    metadata: Vec<RuleMetadata>,
118    gate_metadata: Vec<RuleGateMetadata>,
119    name_map: HashMap<String, RuleId>,
120    first_instruction_map: Vec<(KnowledgeInstructionKey, SmallVec<[RuleId; 8]>)>,
121    kind_map: HashMap<RuleKind, SmallVec<[RuleId; 8]>>,
122}
123
124impl RuleLibrary {
125    /// Creates an empty rule library.
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    /// Returns the lazily loaded builtin compiler rule library.
131    ///
132    /// Builtin sources are embedded at compile time and validated while the
133    /// library is initialized.
134    pub fn builtin_rules() -> Result<&'static RuleLibrary, RuleLibraryError> {
135        match BUILTIN_RULES.get_or_init(|| {
136            let mut library = RuleLibrary::new();
137
138            for (source, kind) in BUILTIN_RULE_SOURCES {
139                let rules = load_rules_from_str(source).map_err(RuleLibraryError::Load)?;
140                library.extend_rules_with_validation(rules, *kind, false)?;
141            }
142
143            Ok(library)
144        }) {
145            Ok(library) => Ok(library),
146            Err(err) => Err(err.clone()),
147        }
148    }
149
150    /// Builds a library from already constructed rules.
151    ///
152    /// Rules are structurally validated before insertion.
153    pub fn from_rules(rules: Vec<Rule>, kind: RuleKind) -> Result<Self, RuleLibraryError> {
154        let mut library = Self::new();
155        library.extend_rules(rules, kind)?;
156        Ok(library)
157    }
158
159    /// Parses and validates rules from a DSL source string.
160    pub fn from_dsl_str(source: &str, kind: RuleKind) -> Result<Self, RuleLibraryError> {
161        Self::from_rules(
162            load_rules_from_str(source).map_err(RuleLibraryError::Load)?,
163            kind,
164        )
165    }
166
167    /// Loads, parses, and validates rules from a DSL file.
168    pub fn from_dsl_file(path: impl AsRef<Path>, kind: RuleKind) -> Result<Self, RuleLibraryError> {
169        Self::from_rules(
170            load_rules_from_file(path).map_err(RuleLibraryError::Load)?,
171            kind,
172        )
173    }
174
175    /// Adds one rule to the library and returns its assigned id.
176    ///
177    /// When `validate` is `true`, structural validation runs before indexing.
178    /// Builtin rule loading may pass `false` after DSL lowering has already
179    /// produced trusted validated rules.
180    pub fn add_rule(
181        &mut self,
182        rule: Rule,
183        kind: RuleKind,
184        validate: bool,
185    ) -> Result<RuleId, RuleLibraryError> {
186        if validate {
187            rule.validate()
188                .map_err(|source| RuleLibraryError::InvalidRule {
189                    name: rule.name.clone(),
190                    source,
191                })?;
192        }
193
194        if self.name_map.contains_key(&rule.name) {
195            return Err(RuleLibraryError::DuplicateRuleName(rule.name));
196        }
197
198        let id = RuleId(self.rules.len());
199        let first_operation =
200            rule.operations
201                .first()
202                .ok_or_else(|| RuleLibraryError::InvalidRule {
203                    name: rule.name.clone(),
204                    source: RuleValidationError::EmptyMatch,
205                })?;
206        let first_instruction_key = KnowledgeInstructionKey::from_instruction(
207            &first_operation.instruction,
208        )
209        .ok_or_else(|| RuleLibraryError::UnsupportedInstruction {
210            instruction: first_operation.instruction.to_string(),
211        })?;
212        let pattern_len = rule.operations.len();
213        let rewrite_len = rule.target.len();
214        let metadata = RuleMetadata {
215            id,
216            kind,
217            pattern_len,
218            rewrite_len,
219            qubit_count: rule
220                .operations
221                .iter()
222                .chain(&rule.target)
223                .flat_map(|item| item.qubits.iter().copied())
224                .collect::<BTreeSet<_>>()
225                .len(),
226            first_instruction: first_operation.instruction.clone(),
227            cost_delta: rewrite_len as isize - pattern_len as isize,
228            has_conditions: rule
229                .conditions
230                .as_ref()
231                .is_some_and(|conditions| !conditions.is_empty()),
232        };
233        let gate_metadata = build_gate_metadata(&rule)?;
234
235        self.name_map.insert(rule.name.clone(), id);
236        if let Some((_, ids)) = self
237            .first_instruction_map
238            .iter_mut()
239            .find(|(key, _)| key == &first_instruction_key)
240        {
241            ids.push(id);
242        } else {
243            self.first_instruction_map
244                .push((first_instruction_key, SmallVec::from_vec(vec![id])));
245        }
246        self.kind_map.entry(kind).or_default().push(id);
247        self.metadata.push(metadata);
248        self.gate_metadata.push(gate_metadata);
249        self.rules.push(rule);
250
251        Ok(id)
252    }
253
254    /// Extends the library with structurally validated rules of one kind.
255    ///
256    /// The update is atomic with respect to this library: if any rule fails,
257    /// the original library is left unchanged.
258    pub fn extend_rules(
259        &mut self,
260        rules: Vec<Rule>,
261        kind: RuleKind,
262    ) -> Result<SmallVec<[RuleId; 8]>, RuleLibraryError> {
263        self.extend_rules_with_validation(rules, kind, true)
264    }
265
266    fn extend_rules_with_validation(
267        &mut self,
268        rules: Vec<Rule>,
269        kind: RuleKind,
270        validate: bool,
271    ) -> Result<SmallVec<[RuleId; 8]>, RuleLibraryError> {
272        let mut updated = self.clone();
273        let mut ids = SmallVec::new();
274
275        for rule in rules {
276            ids.push(updated.add_rule(rule, kind, validate)?);
277        }
278
279        *self = updated;
280        Ok(ids)
281    }
282
283    /// Returns the number of rules in this library.
284    pub fn len(&self) -> usize {
285        self.rules.len()
286    }
287
288    /// Returns whether this library contains no rules.
289    pub fn is_empty(&self) -> bool {
290        self.rules.is_empty()
291    }
292
293    /// Returns all rules in insertion order.
294    pub fn rules(&self) -> &[Rule] {
295        &self.rules
296    }
297
298    /// Returns a rule by id.
299    pub fn get(&self, id: RuleId) -> Option<&Rule> {
300        self.rules.get(id.0)
301    }
302
303    /// Returns precomputed metadata for a rule id.
304    pub fn metadata(&self, id: RuleId) -> Option<&RuleMetadata> {
305        self.metadata.get(id.0)
306    }
307
308    /// Looks up a rule id by rule name.
309    pub fn id_by_name(&self, name: &str) -> Option<RuleId> {
310        self.name_map.get(name).copied()
311    }
312
313    /// Looks up a rule by rule name.
314    pub fn get_by_name(&self, name: &str) -> Option<&Rule> {
315        self.id_by_name(name).and_then(|id| self.get(id))
316    }
317
318    /// Returns whether a rule with `name` exists.
319    pub fn contains(&self, name: &str) -> bool {
320        self.name_map.contains_key(name)
321    }
322
323    /// Returns rules whose first match instruction has the same matcher key.
324    pub fn candidates_for_first_instruction(
325        &self,
326        instruction: &Instruction,
327    ) -> Result<&[RuleId], RuleLibraryError> {
328        let key = KnowledgeInstructionKey::from_instruction(instruction).ok_or_else(|| {
329            RuleLibraryError::UnsupportedInstruction {
330                instruction: instruction.to_string(),
331            }
332        })?;
333
334        Ok(self
335            .first_instruction_map
336            .iter()
337            .find(|(candidate, _)| candidate == &key)
338            .map(|(_, ids)| ids.as_slice())
339            .unwrap_or(&[]))
340    }
341
342    /// Returns rules registered under a coarse compiler kind.
343    pub fn rules_by_kind(&self, kind: RuleKind) -> &[RuleId] {
344        self.kind_map
345            .get(&kind)
346            .map(SmallVec::as_slice)
347            .unwrap_or(&[])
348    }
349
350    /// Filters rules by required match-side and rewrite-side instruction keys.
351    ///
352    /// A rule is returned only when every instruction key in its match pattern
353    /// is present in `op_instructions` and every rewrite target key is present
354    /// in `target_instructions`.
355    pub fn filter_rule_ids_by_instruction_keys(
356        &self,
357        op_instructions: &[Instruction],
358        target_instructions: &[Instruction],
359    ) -> Result<SmallVec<[RuleId; 16]>, RuleLibraryError> {
360        let op_keys = op_instructions
361            .iter()
362            .map(|instruction| {
363                KnowledgeInstructionKey::from_instruction(instruction).ok_or_else(|| {
364                    RuleLibraryError::UnsupportedInstruction {
365                        instruction: instruction.to_string(),
366                    }
367                })
368            })
369            .collect::<Result<SmallVec<[_; 8]>, _>>()?;
370        let target_keys = target_instructions
371            .iter()
372            .map(|instruction| {
373                KnowledgeInstructionKey::from_instruction(instruction).ok_or_else(|| {
374                    RuleLibraryError::UnsupportedInstruction {
375                        instruction: instruction.to_string(),
376                    }
377                })
378            })
379            .collect::<Result<SmallVec<[_; 8]>, _>>()?;
380
381        let mut ids = SmallVec::new();
382
383        for (index, metadata) in self.gate_metadata.iter().enumerate() {
384            let match_supported = metadata
385                .match_instruction_keys
386                .iter()
387                .all(|required| op_keys.iter().any(|available| available == required));
388            let rewrite_supported = metadata
389                .rewrite_instruction_keys
390                .iter()
391                .all(|required| target_keys.iter().any(|available| available == required));
392
393            if match_supported && rewrite_supported {
394                ids.push(RuleId(index));
395            }
396        }
397
398        Ok(ids)
399    }
400}
401
402fn build_gate_metadata(rule: &Rule) -> Result<RuleGateMetadata, RuleLibraryError> {
403    let mut match_instruction_keys = SmallVec::new();
404    let mut rewrite_instruction_keys = SmallVec::new();
405
406    for item in &rule.operations {
407        match_instruction_keys.push(
408            KnowledgeInstructionKey::from_instruction(&item.instruction).ok_or_else(|| {
409                RuleLibraryError::UnsupportedInstruction {
410                    instruction: item.instruction.to_string(),
411                }
412            })?,
413        );
414    }
415
416    for item in &rule.target {
417        rewrite_instruction_keys.push(
418            KnowledgeInstructionKey::from_instruction(&item.instruction).ok_or_else(|| {
419                RuleLibraryError::UnsupportedInstruction {
420                    instruction: item.instruction.to_string(),
421                }
422            })?,
423        );
424    }
425
426    Ok(RuleGateMetadata {
427        match_instruction_keys,
428        rewrite_instruction_keys,
429    })
430}
431
432const BUILTIN_RULE_SOURCES: &[(&str, RuleKind)] = &[
433    (include_str!("rules/normalize.rule"), RuleKind::Canonicalize),
434    (include_str!("rules/cancel.rule"), RuleKind::Cancel),
435    (include_str!("rules/merge.rule"), RuleKind::Merge),
436    (include_str!("rules/identity.rule"), RuleKind::Simplify),
437    (include_str!("rules/specialize.rule"), RuleKind::Simplify),
438    (include_str!("rules/commutation.rule"), RuleKind::Commute),
439    (
440        include_str!("rules/decompose_ccx.rule"),
441        RuleKind::Decompose,
442    ),
443    (
444        include_str!("rules/decompose_controlled_pauli.rule"),
445        RuleKind::Decompose,
446    ),
447    (
448        include_str!("rules/decompose_controlled_rotation.rule"),
449        RuleKind::Decompose,
450    ),
451    (
452        include_str!("rules/decompose_mc_gate.rule"),
453        RuleKind::Decompose,
454    ),
455    (
456        include_str!("rules/decompose_fsim.rule"),
457        RuleKind::Decompose,
458    ),
459    (
460        include_str!("rules/decompose_ising.rule"),
461        RuleKind::Decompose,
462    ),
463    (
464        include_str!("rules/decompose_phase.rule"),
465        RuleKind::Decompose,
466    ),
467    (
468        include_str!("rules/decompose_qcis.rule"),
469        RuleKind::Decompose,
470    ),
471    (
472        include_str!("rules/decompose_single_clifford.rule"),
473        RuleKind::Decompose,
474    ),
475    (
476        include_str!("rules/decompose_single_rotation.rule"),
477        RuleKind::Decompose,
478    ),
479    (
480        include_str!("rules/decompose_swap.rule"),
481        RuleKind::Decompose,
482    ),
483];
484
485#[cfg(test)]
486#[path = "./library_test.rs"]
487mod library_test;
488
489#[cfg(test)]
490#[path = "builtin_rules_contract_test.rs"]
491mod builtin_rules_contract_test;