cqlib_core/compile/knowledge/
matcher.rs1use crate::circuit::{Instruction, MCGate, Parameter, ParameterValue, Qubit, StandardGate};
23use crate::compile::PARAMETER_EQ_TOLERANCE;
24use crate::compile::knowledge::rule::{Condition, Rule, RuleItem};
25use smallvec::SmallVec;
26use std::collections::HashMap;
27
28#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub enum KnowledgeInstructionKey {
31 Standard(StandardGate),
32 McGate(MCGate),
33}
34
35impl KnowledgeInstructionKey {
36 pub fn from_instruction(instruction: &Instruction) -> Option<Self> {
38 match instruction {
39 Instruction::Standard(gate) => Some(Self::Standard(*gate)),
40 Instruction::McGate(gate) => Some(Self::McGate(gate.as_ref().clone())),
41 _ => None,
42 }
43 }
44
45 pub(crate) fn to_instruction(&self) -> Instruction {
47 match self {
48 Self::Standard(gate) => Instruction::Standard(*gate),
49 Self::McGate(gate) => Instruction::McGate(Box::new(gate.clone())),
50 }
51 }
52
53 pub(crate) fn is_implicit(&self) -> bool {
54 matches!(self, Self::Standard(StandardGate::GPhase))
55 }
56
57 pub(crate) fn num_qubits(&self) -> Option<usize> {
58 Some(match self {
59 Self::Standard(gate) => gate.num_qubits(),
60 Self::McGate(gate) => gate.num_qubits(),
61 })
62 }
63
64 pub(crate) fn num_params(&self) -> Option<usize> {
65 Some(match self {
66 Self::Standard(gate) => gate.num_params(),
67 Self::McGate(gate) => gate.num_params(),
68 })
69 }
70}
71
72#[derive(Debug, Clone, Copy)]
74pub struct ConcreteOperationView<'a> {
75 pub instruction: &'a Instruction,
76 pub qubits: &'a [Qubit],
77 pub params: &'a [Parameter],
78}
79
80impl<'a> ConcreteOperationView<'a> {
81 pub const fn new(
82 instruction: &'a Instruction,
83 qubits: &'a [Qubit],
84 params: &'a [Parameter],
85 ) -> Self {
86 Self {
87 instruction,
88 qubits,
89 params,
90 }
91 }
92
93 pub fn key(&self) -> Option<KnowledgeInstructionKey> {
94 KnowledgeInstructionKey::from_instruction(self.instruction)
95 }
96}
97
98#[derive(Debug, Clone, Default)]
100pub struct MatchBindings {
101 qubits: HashMap<u32, Qubit>,
102 reverse_qubits: HashMap<Qubit, u32>,
103 params: HashMap<String, Parameter>,
104 insertion_log: Vec<BindingInsertion>,
105}
106
107#[derive(Debug, Clone)]
108enum BindingInsertion {
109 Qubit { rule: u32, actual: Qubit },
110 Parameter(String),
111}
112
113impl PartialEq for MatchBindings {
114 fn eq(&self, other: &Self) -> bool {
115 self.qubits == other.qubits
116 && self.reverse_qubits == other.reverse_qubits
117 && self.params == other.params
118 }
119}
120
121impl Eq for MatchBindings {}
122
123impl MatchBindings {
124 pub fn new() -> Self {
125 Self::default()
126 }
127
128 pub(crate) fn clear(&mut self) {
130 self.qubits.clear();
131 self.reverse_qubits.clear();
132 self.params.clear();
133 self.insertion_log.clear();
134 }
135
136 pub fn qubits(&self) -> &HashMap<u32, Qubit> {
138 &self.qubits
139 }
140
141 pub fn qubit(&self, rule_qubit: u32) -> Option<Qubit> {
143 self.qubits.get(&rule_qubit).copied()
144 }
145
146 pub fn params(&self) -> &HashMap<String, Parameter> {
148 &self.params
149 }
150
151 pub fn param(&self, symbol: &str) -> Option<&Parameter> {
153 self.params.get(symbol)
154 }
155
156 pub(crate) fn checkpoint(&self) -> usize {
157 self.insertion_log.len()
158 }
159
160 pub(crate) fn rollback(&mut self, checkpoint: usize) {
161 while self.insertion_log.len() > checkpoint {
162 match self
163 .insertion_log
164 .pop()
165 .expect("binding insertion log length was checked")
166 {
167 BindingInsertion::Qubit { rule, actual } => {
168 self.qubits.remove(&rule);
169 self.reverse_qubits.remove(&actual);
170 }
171 BindingInsertion::Parameter(symbol) => {
172 self.params.remove(&symbol);
173 }
174 }
175 }
176 }
177
178 fn bind_qubits(&mut self, item: &RuleItem, concrete: ConcreteOperationView<'_>) -> bool {
179 for (&rule_qubit, &actual_qubit) in item.qubits.iter().zip(concrete.qubits) {
180 if let Some(bound) = self.qubits.get(&rule_qubit) {
181 if *bound != actual_qubit {
182 return false;
183 }
184 } else if let Some(other_rule_qubit) = self.reverse_qubits.get(&actual_qubit) {
185 if *other_rule_qubit != rule_qubit {
186 return false;
187 }
188 } else {
189 self.qubits.insert(rule_qubit, actual_qubit);
190 self.reverse_qubits.insert(actual_qubit, rule_qubit);
191 self.insertion_log.push(BindingInsertion::Qubit {
192 rule: rule_qubit,
193 actual: actual_qubit,
194 });
195 }
196 }
197 true
198 }
199
200 fn bind_parameters(&mut self, item: &RuleItem, concrete: ConcreteOperationView<'_>) -> bool {
201 let rule_params = item.params.as_deref().unwrap_or(&[]);
202 if rule_params.len() != concrete.params.len() {
203 return false;
204 }
205
206 rule_params
207 .iter()
208 .zip(concrete.params)
209 .all(|(rule_param, actual)| self.match_parameter(rule_param, actual))
210 }
211
212 fn match_parameter(&mut self, rule_param: &ParameterValue, actual: &Parameter) -> bool {
213 match rule_param {
214 ParameterValue::Fixed(value) => {
215 Parameter::from(*value).provably_equal(actual, PARAMETER_EQ_TOLERANCE)
216 }
217 ParameterValue::Param(pattern) => {
218 if let Some(symbol) = pattern.as_symbol() {
219 if let Some(bound) = self.params.get(&symbol) {
220 return bound.provably_equal(actual, PARAMETER_EQ_TOLERANCE);
221 }
222 self.params.insert(symbol.clone(), actual.clone());
223 self.insertion_log.push(BindingInsertion::Parameter(symbol));
224 return true;
225 }
226
227 let substituted = pattern.substitute_many(&self.params);
228 substituted.get_symbols().is_empty()
229 && substituted.provably_equal(actual, PARAMETER_EQ_TOLERANCE)
230 }
231 }
232 }
233}
234
235#[derive(Debug, Clone)]
237pub struct MatchedReplacement {
238 pub instruction: Instruction,
239 pub qubits: SmallVec<[Qubit; 3]>,
240 pub params: SmallVec<[ParameterValue; 3]>,
241 pub key: KnowledgeInstructionKey,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
246pub enum MatchError {
247 #[error("unsupported rule instruction: {instruction}")]
248 UnsupportedRuleInstruction { instruction: String },
249 #[error("rewrite qubit {qubit} is not bound by the match")]
250 UnboundRewriteQubit { qubit: u32 },
251 #[error("rewrite symbol {symbol} is not bound by the match")]
252 UnboundRewriteSymbol { symbol: String },
253}
254
255pub fn match_rule_item(
260 item: &RuleItem,
261 concrete: ConcreteOperationView<'_>,
262 bindings: &mut MatchBindings,
263) -> Result<bool, MatchError> {
264 let Some(item_key) = KnowledgeInstructionKey::from_instruction(&item.instruction) else {
265 return Err(MatchError::UnsupportedRuleInstruction {
266 instruction: item.instruction.to_string(),
267 });
268 };
269 let Some(concrete_key) = concrete.key() else {
270 return Ok(false);
271 };
272 match_rule_item_with_keys(item, &item_key, &concrete_key, concrete, bindings)
273}
274
275pub(crate) fn match_rule_item_with_keys(
277 item: &RuleItem,
278 item_key: &KnowledgeInstructionKey,
279 concrete_key: &KnowledgeInstructionKey,
280 concrete: ConcreteOperationView<'_>,
281 bindings: &mut MatchBindings,
282) -> Result<bool, MatchError> {
283 if item_key != concrete_key || item.qubits.len() != concrete.qubits.len() {
284 return Ok(false);
285 }
286
287 let checkpoint = bindings.checkpoint();
288 if !bindings.bind_qubits(item, concrete) {
289 bindings.rollback(checkpoint);
290 return Ok(false);
291 }
292 if !bindings.bind_parameters(item, concrete) {
293 bindings.rollback(checkpoint);
294 return Ok(false);
295 }
296
297 Ok(true)
298}
299
300pub fn conditions_hold(conditions: Option<&[Condition]>, bindings: &MatchBindings) -> bool {
302 conditions.unwrap_or(&[]).iter().all(|condition| {
303 condition_symbols_bound(condition, bindings)
304 && match condition {
305 Condition::Eq(lhs, rhs) => {
306 let lhs = lhs.substitute_many(&bindings.params);
307 let rhs = rhs.substitute_many(&bindings.params);
308 lhs.provably_equal(&rhs, PARAMETER_EQ_TOLERANCE)
309 }
310 Condition::EqMod(lhs, rhs, modulus) => {
311 let lhs = lhs.substitute_many(&bindings.params);
312 let rhs = rhs.substitute_many(&bindings.params);
313 let modulus = modulus.substitute_many(&bindings.params);
314 lhs.provably_equal_modulo(&rhs, &modulus, PARAMETER_EQ_TOLERANCE)
315 }
316 }
317 })
318}
319
320pub fn instantiate_target(
322 target: &[RuleItem],
323 bindings: &MatchBindings,
324) -> Result<Vec<MatchedReplacement>, MatchError> {
325 let mut replacements = Vec::with_capacity(target.len());
326
327 for item in target {
328 let Some(key) = KnowledgeInstructionKey::from_instruction(&item.instruction) else {
329 return Err(MatchError::UnsupportedRuleInstruction {
330 instruction: item.instruction.to_string(),
331 });
332 };
333 let qubits = item
334 .qubits
335 .iter()
336 .map(|rule_qubit| {
337 bindings
338 .qubit(*rule_qubit)
339 .ok_or(MatchError::UnboundRewriteQubit { qubit: *rule_qubit })
340 })
341 .collect::<Result<SmallVec<[_; 3]>, _>>()?;
342 let params = item
343 .params
344 .as_deref()
345 .unwrap_or(&[])
346 .iter()
347 .map(|value| instantiate_parameter(value, bindings))
348 .collect::<Result<SmallVec<[_; 3]>, _>>()?;
349
350 replacements.push(MatchedReplacement {
351 instruction: item.instruction.clone(),
352 qubits,
353 params,
354 key,
355 });
356 }
357
358 Ok(replacements)
359}
360
361pub fn rule_matches_operations(
367 rule: &Rule,
368 operations: &[ConcreteOperationView<'_>],
369) -> Result<Option<MatchBindings>, MatchError> {
370 if rule.operations.len() != operations.len() {
371 return Ok(None);
372 }
373
374 let mut bindings = MatchBindings::new();
375 for (item, operation) in rule.operations.iter().zip(operations) {
376 if !match_rule_item(item, *operation, &mut bindings)? {
377 return Ok(None);
378 }
379 }
380
381 if !conditions_hold(rule.conditions.as_deref(), &bindings) {
382 return Ok(None);
383 }
384
385 Ok(Some(bindings))
386}
387
388fn condition_symbols_bound(condition: &Condition, bindings: &MatchBindings) -> bool {
389 match condition {
390 Condition::Eq(lhs, rhs) => symbols_bound(lhs, bindings) && symbols_bound(rhs, bindings),
391 Condition::EqMod(lhs, rhs, modulus) => {
392 symbols_bound(lhs, bindings)
393 && symbols_bound(rhs, bindings)
394 && symbols_bound(modulus, bindings)
395 }
396 }
397}
398
399fn symbols_bound(parameter: &Parameter, bindings: &MatchBindings) -> bool {
400 parameter
401 .get_symbols()
402 .iter()
403 .all(|symbol| bindings.params.contains_key(symbol))
404}
405
406fn instantiate_parameter(
407 value: &ParameterValue,
408 bindings: &MatchBindings,
409) -> Result<ParameterValue, MatchError> {
410 let parameter = match value {
411 ParameterValue::Fixed(value) => Parameter::from(*value),
412 ParameterValue::Param(parameter) => {
413 for symbol in parameter.get_symbols() {
414 if !bindings.params.contains_key(&symbol) {
415 return Err(MatchError::UnboundRewriteSymbol { symbol });
416 }
417 }
418 parameter.substitute_many(&bindings.params)
419 }
420 };
421 Ok(ParameterValue::from(parameter))
422}
423
424#[cfg(test)]
425#[path = "./matcher_test.rs"]
426mod matcher_test;