use crate::circuit::{Instruction, Parameter, ParameterValue, Qubit};
use crate::compile::PARAMETER_EQ_TOLERANCE;
use crate::compile::commutation::checker::{Commutation, CommutationResult};
use crate::compile::knowledge::library::{RuleKind, RuleLibrary};
use crate::compile::knowledge::matcher::KnowledgeInstructionKey;
use crate::compile::knowledge::rule::{Condition, Rule, RuleItem};
use smallvec::SmallVec;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct RuleCommutationOracle {
rules: Vec<Rule>,
rule_index:
HashMap<(KnowledgeInstructionKey, KnowledgeInstructionKey), SmallVec<[IndexedRule; 4]>>,
}
#[derive(Debug, Clone, Copy)]
struct IndexedRule {
rule_index: usize,
direction: MatchDirection,
}
#[derive(Debug, Clone, Copy)]
enum MatchDirection {
Forward,
Reverse,
}
#[derive(Debug, Clone, Default)]
struct MatchState {
qubits: HashMap<u32, Qubit>,
reverse_qubits: HashMap<Qubit, u32>,
params: HashMap<String, Parameter>,
}
impl RuleCommutationOracle {
pub fn builtin() -> Self {
let library =
RuleLibrary::builtin_rules().expect("builtin compiler knowledge rules must load");
Self::from_library(library)
}
pub fn from_library(library: &RuleLibrary) -> Self {
let rules = library
.rules_by_kind(RuleKind::Commute)
.iter()
.filter_map(|id| library.get(*id))
.cloned()
.collect::<Vec<_>>();
Self::from_rules(&rules)
}
pub fn check(
&self,
lhs_inst: &Instruction,
lhs_qubits: &[Qubit],
lhs_params: &[Parameter],
rhs_inst: &Instruction,
rhs_qubits: &[Qubit],
rhs_params: &[Parameter],
) -> CommutationResult {
let lhs_key = KnowledgeInstructionKey::from_instruction(lhs_inst)?;
let rhs_key = KnowledgeInstructionKey::from_instruction(rhs_inst)?;
let candidates = self.rule_index.get(&(lhs_key, rhs_key))?;
for candidate in candidates {
let rule = &self.rules[candidate.rule_index];
let matched = match candidate.direction {
MatchDirection::Forward => rule_matches(
rule, lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
),
MatchDirection::Reverse => rule_matches(
rule, rhs_inst, rhs_qubits, rhs_params, lhs_inst, lhs_qubits, lhs_params,
),
};
if matched {
return Some(Commutation::Exact);
}
}
None
}
fn from_rules(rules: &[Rule]) -> Self {
let rules = rules
.iter()
.filter(|rule| {
if rule.operations.len() != 2 || rule.target.len() != 2 {
return false;
}
rule.operations[0].equivalent_to(&rule.target[1])
&& rule.operations[1].equivalent_to(&rule.target[0])
})
.cloned()
.collect::<Vec<_>>();
let mut rule_index: HashMap<
(KnowledgeInstructionKey, KnowledgeInstructionKey),
SmallVec<[IndexedRule; 4]>,
> = HashMap::new();
for (index, rule) in rules.iter().enumerate() {
let Some(lhs_key) =
KnowledgeInstructionKey::from_instruction(&rule.operations[0].instruction)
else {
continue;
};
let Some(rhs_key) =
KnowledgeInstructionKey::from_instruction(&rule.operations[1].instruction)
else {
continue;
};
rule_index
.entry((lhs_key.clone(), rhs_key.clone()))
.or_default()
.push(IndexedRule {
rule_index: index,
direction: MatchDirection::Forward,
});
rule_index
.entry((rhs_key, lhs_key))
.or_default()
.push(IndexedRule {
rule_index: index,
direction: MatchDirection::Reverse,
});
}
Self { rules, rule_index }
}
}
fn rule_matches(
rule: &Rule,
lhs_inst: &Instruction,
lhs_qubits: &[Qubit],
lhs_params: &[Parameter],
rhs_inst: &Instruction,
rhs_qubits: &[Qubit],
rhs_params: &[Parameter],
) -> bool {
let mut state = MatchState::default();
match_rule_item(
&rule.operations[0],
lhs_inst,
lhs_qubits,
lhs_params,
&mut state,
) && match_rule_item(
&rule.operations[1],
rhs_inst,
rhs_qubits,
rhs_params,
&mut state,
) && conditions_hold(rule.conditions.as_deref().unwrap_or(&[]), &state.params)
}
fn match_rule_item(
item: &RuleItem,
instruction: &Instruction,
qubits: &[Qubit],
params: &[Parameter],
state: &mut MatchState,
) -> bool {
if !matches!(
(&item.instruction, instruction),
(Instruction::Standard(lhs), Instruction::Standard(rhs)) if lhs == rhs
) && !matches!(
(&item.instruction, instruction),
(Instruction::McGate(lhs), Instruction::McGate(rhs)) if lhs == rhs
) {
return false;
}
if item.qubits.len() != qubits.len() {
return false;
}
for (&rule_qubit, &actual_qubit) in item.qubits.iter().zip(qubits) {
if let Some(bound) = state.qubits.get(&rule_qubit) {
if *bound != actual_qubit {
return false;
}
} else if let Some(other_rule_qubit) = state.reverse_qubits.get(&actual_qubit) {
if *other_rule_qubit != rule_qubit {
return false;
}
} else {
state.qubits.insert(rule_qubit, actual_qubit);
state.reverse_qubits.insert(actual_qubit, rule_qubit);
}
}
let rule_params = item.params.as_deref().unwrap_or(&[]);
if rule_params.len() != params.len() {
return false;
}
rule_params
.iter()
.zip(params)
.all(|(rule_param, actual)| match_parameter(rule_param, actual, &mut state.params))
}
fn match_parameter(
rule_param: &ParameterValue,
actual: &Parameter,
bindings: &mut HashMap<String, Parameter>,
) -> bool {
match rule_param {
ParameterValue::Fixed(value) => {
Parameter::from(*value).provably_equal(actual, PARAMETER_EQ_TOLERANCE)
}
ParameterValue::Param(pattern) => {
if let Some(symbol) = pattern.as_symbol() {
if let Some(bound) = bindings.get(&symbol) {
return bound.provably_equal(actual, PARAMETER_EQ_TOLERANCE);
}
bindings.insert(symbol, actual.clone());
return true;
}
let substituted = pattern.substitute_many(bindings);
substituted.get_symbols().is_empty()
&& substituted.provably_equal(actual, PARAMETER_EQ_TOLERANCE)
}
}
}
fn conditions_hold(conditions: &[Condition], bindings: &HashMap<String, Parameter>) -> bool {
conditions.iter().all(|condition| match condition {
Condition::Eq(lhs, rhs) => {
let lhs = lhs.substitute_many(bindings);
let rhs = rhs.substitute_many(bindings);
lhs.provably_equal(&rhs, PARAMETER_EQ_TOLERANCE)
}
Condition::EqMod(lhs, rhs, modulus) => {
let lhs = lhs.substitute_many(bindings);
let rhs = rhs.substitute_many(bindings);
let modulus = modulus.substitute_many(bindings);
lhs.provably_equal_modulo(&rhs, &modulus, PARAMETER_EQ_TOLERANCE)
}
})
}
#[cfg(test)]
#[path = "./rules_test.rs"]
mod rules_test;