cqlib_core/compile/commutation/
rules.rs1use crate::circuit::{Instruction, Parameter, ParameterValue, Qubit};
42use crate::compile::PARAMETER_EQ_TOLERANCE;
43use crate::compile::commutation::checker::{Commutation, CommutationResult};
44use crate::compile::knowledge::library::{RuleKind, RuleLibrary};
45use crate::compile::knowledge::matcher::KnowledgeInstructionKey;
46use crate::compile::knowledge::rule::{Condition, Rule, RuleItem};
47use smallvec::SmallVec;
48use std::collections::HashMap;
49
50#[derive(Debug, Clone, Default)]
52pub struct RuleCommutationOracle {
53 rules: Vec<Rule>,
55 rule_index:
57 HashMap<(KnowledgeInstructionKey, KnowledgeInstructionKey), SmallVec<[IndexedRule; 4]>>,
58}
59
60#[derive(Debug, Clone, Copy)]
62struct IndexedRule {
63 rule_index: usize,
65 direction: MatchDirection,
67}
68
69#[derive(Debug, Clone, Copy)]
71enum MatchDirection {
72 Forward,
73 Reverse,
74}
75
76#[derive(Debug, Clone, Default)]
82struct MatchState {
83 qubits: HashMap<u32, Qubit>,
85 reverse_qubits: HashMap<Qubit, u32>,
87 params: HashMap<String, Parameter>,
89}
90
91impl RuleCommutationOracle {
92 pub fn builtin() -> Self {
94 let library =
95 RuleLibrary::builtin_rules().expect("builtin compiler knowledge rules must load");
96 Self::from_library(library)
97 }
98
99 pub fn from_library(library: &RuleLibrary) -> Self {
101 let rules = library
102 .rules_by_kind(RuleKind::Commute)
103 .iter()
104 .filter_map(|id| library.get(*id))
105 .cloned()
106 .collect::<Vec<_>>();
107
108 Self::from_rules(&rules)
109 }
110
111 pub fn check(
117 &self,
118 lhs_inst: &Instruction,
119 lhs_qubits: &[Qubit],
120 lhs_params: &[Parameter],
121 rhs_inst: &Instruction,
122 rhs_qubits: &[Qubit],
123 rhs_params: &[Parameter],
124 ) -> CommutationResult {
125 let lhs_key = KnowledgeInstructionKey::from_instruction(lhs_inst)?;
126 let rhs_key = KnowledgeInstructionKey::from_instruction(rhs_inst)?;
127 let candidates = self.rule_index.get(&(lhs_key, rhs_key))?;
128
129 for candidate in candidates {
130 let rule = &self.rules[candidate.rule_index];
131 let matched = match candidate.direction {
132 MatchDirection::Forward => rule_matches(
133 rule, lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
134 ),
135 MatchDirection::Reverse => rule_matches(
136 rule, rhs_inst, rhs_qubits, rhs_params, lhs_inst, lhs_qubits, lhs_params,
137 ),
138 };
139 if matched {
140 return Some(Commutation::Exact);
141 }
142 }
143 None
144 }
145
146 fn from_rules(rules: &[Rule]) -> Self {
148 let rules = rules
149 .iter()
150 .filter(|rule| {
151 if rule.operations.len() != 2 || rule.target.len() != 2 {
152 return false;
153 }
154
155 rule.operations[0].equivalent_to(&rule.target[1])
156 && rule.operations[1].equivalent_to(&rule.target[0])
157 })
158 .cloned()
159 .collect::<Vec<_>>();
160 let mut rule_index: HashMap<
161 (KnowledgeInstructionKey, KnowledgeInstructionKey),
162 SmallVec<[IndexedRule; 4]>,
163 > = HashMap::new();
164
165 for (index, rule) in rules.iter().enumerate() {
166 let Some(lhs_key) =
167 KnowledgeInstructionKey::from_instruction(&rule.operations[0].instruction)
168 else {
169 continue;
170 };
171 let Some(rhs_key) =
172 KnowledgeInstructionKey::from_instruction(&rule.operations[1].instruction)
173 else {
174 continue;
175 };
176
177 rule_index
178 .entry((lhs_key.clone(), rhs_key.clone()))
179 .or_default()
180 .push(IndexedRule {
181 rule_index: index,
182 direction: MatchDirection::Forward,
183 });
184 rule_index
185 .entry((rhs_key, lhs_key))
186 .or_default()
187 .push(IndexedRule {
188 rule_index: index,
189 direction: MatchDirection::Reverse,
190 });
191 }
192
193 Self { rules, rule_index }
194 }
195}
196
197fn rule_matches(
199 rule: &Rule,
200 lhs_inst: &Instruction,
201 lhs_qubits: &[Qubit],
202 lhs_params: &[Parameter],
203 rhs_inst: &Instruction,
204 rhs_qubits: &[Qubit],
205 rhs_params: &[Parameter],
206) -> bool {
207 let mut state = MatchState::default();
208 match_rule_item(
209 &rule.operations[0],
210 lhs_inst,
211 lhs_qubits,
212 lhs_params,
213 &mut state,
214 ) && match_rule_item(
215 &rule.operations[1],
216 rhs_inst,
217 rhs_qubits,
218 rhs_params,
219 &mut state,
220 ) && conditions_hold(rule.conditions.as_deref().unwrap_or(&[]), &state.params)
221}
222
223fn match_rule_item(
229 item: &RuleItem,
230 instruction: &Instruction,
231 qubits: &[Qubit],
232 params: &[Parameter],
233 state: &mut MatchState,
234) -> bool {
235 if !matches!(
236 (&item.instruction, instruction),
237 (Instruction::Standard(lhs), Instruction::Standard(rhs)) if lhs == rhs
238 ) && !matches!(
239 (&item.instruction, instruction),
240 (Instruction::McGate(lhs), Instruction::McGate(rhs)) if lhs == rhs
241 ) {
242 return false;
243 }
244 if item.qubits.len() != qubits.len() {
245 return false;
246 }
247
248 for (&rule_qubit, &actual_qubit) in item.qubits.iter().zip(qubits) {
249 if let Some(bound) = state.qubits.get(&rule_qubit) {
250 if *bound != actual_qubit {
251 return false;
252 }
253 } else if let Some(other_rule_qubit) = state.reverse_qubits.get(&actual_qubit) {
254 if *other_rule_qubit != rule_qubit {
255 return false;
256 }
257 } else {
258 state.qubits.insert(rule_qubit, actual_qubit);
259 state.reverse_qubits.insert(actual_qubit, rule_qubit);
260 }
261 }
262
263 let rule_params = item.params.as_deref().unwrap_or(&[]);
264 if rule_params.len() != params.len() {
265 return false;
266 }
267
268 rule_params
269 .iter()
270 .zip(params)
271 .all(|(rule_param, actual)| match_parameter(rule_param, actual, &mut state.params))
272}
273
274fn match_parameter(
279 rule_param: &ParameterValue,
280 actual: &Parameter,
281 bindings: &mut HashMap<String, Parameter>,
282) -> bool {
283 match rule_param {
284 ParameterValue::Fixed(value) => {
285 Parameter::from(*value).provably_equal(actual, PARAMETER_EQ_TOLERANCE)
286 }
287 ParameterValue::Param(pattern) => {
288 if let Some(symbol) = pattern.as_symbol() {
289 if let Some(bound) = bindings.get(&symbol) {
290 return bound.provably_equal(actual, PARAMETER_EQ_TOLERANCE);
291 }
292 bindings.insert(symbol, actual.clone());
293 return true;
294 }
295
296 let substituted = pattern.substitute_many(bindings);
297 substituted.get_symbols().is_empty()
298 && substituted.provably_equal(actual, PARAMETER_EQ_TOLERANCE)
299 }
300 }
301}
302
303fn conditions_hold(conditions: &[Condition], bindings: &HashMap<String, Parameter>) -> bool {
305 conditions.iter().all(|condition| match condition {
306 Condition::Eq(lhs, rhs) => {
307 let lhs = lhs.substitute_many(bindings);
308 let rhs = rhs.substitute_many(bindings);
309 lhs.provably_equal(&rhs, PARAMETER_EQ_TOLERANCE)
310 }
311 Condition::EqMod(lhs, rhs, modulus) => {
312 let lhs = lhs.substitute_many(bindings);
313 let rhs = rhs.substitute_many(bindings);
314 let modulus = modulus.substitute_many(bindings);
315 lhs.provably_equal_modulo(&rhs, &modulus, PARAMETER_EQ_TOLERANCE)
316 }
317 })
318}
319
320#[cfg(test)]
321#[path = "./rules_test.rs"]
322mod rules_test;