use crate::circuit::{Instruction, MCGate, Parameter, ParameterValue, StandardGate};
use crate::compile::PARAMETER_EQ_TOLERANCE;
use smallvec::SmallVec;
use std::collections::{BTreeSet, HashSet};
#[derive(Debug, Clone)]
pub struct RuleItem {
pub instruction: Instruction,
pub qubits: SmallVec<[u32; 3]>,
pub params: Option<SmallVec<[ParameterValue; 1]>>,
}
impl RuleItem {
pub fn standard(gate: StandardGate, qubits: &[u32], params: Vec<ParameterValue>) -> Self {
Self {
instruction: Instruction::Standard(gate),
qubits: SmallVec::from_slice(qubits),
params: if params.is_empty() {
None
} else {
Some(SmallVec::from_vec(params))
},
}
}
pub fn mc_gate(gate: MCGate, qubits: &[u32], params: Vec<ParameterValue>) -> Self {
Self {
instruction: Instruction::McGate(Box::new(gate)),
qubits: SmallVec::from_slice(qubits),
params: if params.is_empty() {
None
} else {
Some(SmallVec::from_vec(params))
},
}
}
pub fn symbols(&self) -> HashSet<String> {
let mut symbols = HashSet::new();
if let Some(params) = &self.params {
for param in params {
if let ParameterValue::Param(param) = param {
symbols.extend(param.get_symbols());
}
}
}
symbols
}
pub fn validate(&self) -> Result<(), RuleValidationError> {
let (instruction, expected_qubits, expected_params) = match &self.instruction {
Instruction::Standard(gate) => (gate.to_string(), gate.num_qubits(), gate.num_params()),
Instruction::McGate(gate) => (
format!(
"MC{}[{}]",
gate.base_gate(),
gate.num_qubits() - gate.base_gate().num_qubits()
),
gate.num_qubits(),
gate.num_params(),
),
other => {
return Err(RuleValidationError::UnsupportedInstruction {
instruction: other.to_string(),
});
}
};
if self.qubits.len() != expected_qubits {
return Err(RuleValidationError::WrongQubitCount {
instruction: instruction.clone(),
expected: expected_qubits,
got: self.qubits.len(),
});
}
let got_params = self.params.as_ref().map_or(0, SmallVec::len);
if got_params != expected_params {
return Err(RuleValidationError::WrongParamCount {
instruction: instruction.clone(),
expected: expected_params,
got: got_params,
});
}
let mut seen = SmallVec::<[u32; 3]>::new();
for &qubit in &self.qubits {
if seen.contains(&qubit) {
return Err(RuleValidationError::DuplicateQubit {
instruction: instruction.clone(),
qubit,
});
}
seen.push(qubit);
}
Ok(())
}
pub fn equivalent_to(&self, other: &Self) -> bool {
let lhs_params = self.params.as_deref().unwrap_or(&[]);
let rhs_params = other.params.as_deref().unwrap_or(&[]);
self.instruction == other.instruction
&& self.qubits == other.qubits
&& lhs_params.len() == rhs_params.len()
&& lhs_params.iter().zip(rhs_params).all(|(lhs, rhs)| {
let lhs: Parameter = lhs.into();
lhs.provably_equal(&rhs.into(), PARAMETER_EQ_TOLERANCE)
})
}
}
#[derive(Debug, Clone)]
pub enum Condition {
Eq(Parameter, Parameter),
EqMod(Parameter, Parameter, Parameter),
}
impl Condition {
pub fn symbols(&self) -> HashSet<String> {
let mut symbols = HashSet::new();
match self {
Condition::Eq(lhs, rhs) => {
symbols.extend(lhs.get_symbols());
symbols.extend(rhs.get_symbols());
}
Condition::EqMod(lhs, rhs, modulus) => {
symbols.extend(lhs.get_symbols());
symbols.extend(rhs.get_symbols());
symbols.extend(modulus.get_symbols());
}
}
symbols
}
}
#[derive(Debug, Clone)]
pub struct Rule {
pub name: String,
pub operations: SmallVec<[RuleItem; 4]>,
pub conditions: Option<SmallVec<[Condition; 2]>>,
pub target: SmallVec<[RuleItem; 4]>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RuleValidationError {
#[error("rule match block is empty")]
EmptyMatch,
#[error("unsupported instruction: {instruction}")]
UnsupportedInstruction { instruction: String },
#[error("wrong qubit count for {instruction}: expected {expected}, got {got}")]
WrongQubitCount {
instruction: String,
expected: usize,
got: usize,
},
#[error("wrong parameter count for {instruction}: expected {expected}, got {got}")]
WrongParamCount {
instruction: String,
expected: usize,
got: usize,
},
#[error("duplicate qubit {qubit} in {instruction}")]
DuplicateQubit { instruction: String, qubit: u32 },
#[error("rewrite qubit {qubit} is not bound by the match block")]
UnboundRewriteQubit { qubit: u32 },
#[error("rewrite symbol {symbol} is not bound by the match block")]
UnboundRewriteSymbol { symbol: String },
#[error("condition symbol {symbol} is not bound by the match block")]
UnboundConditionSymbol { symbol: String },
#[error("qubit labels must be dense from 0, found {labels:?}")]
NonDenseQubitLabels { labels: Vec<u32> },
}
impl Rule {
pub fn new(name: &str, ops: Vec<RuleItem>, target: Vec<RuleItem>) -> Rule {
Rule {
name: name.to_string(),
operations: SmallVec::from_vec(ops),
conditions: None,
target: SmallVec::from_vec(target),
}
}
pub fn validate(&self) -> Result<(), RuleValidationError> {
if self.operations.is_empty() {
return Err(RuleValidationError::EmptyMatch);
}
for item in &self.operations {
item.validate()?;
}
for item in &self.target {
item.validate()?;
}
let match_qubits = self.operation_qubits();
validate_dense_qubit_labels(&match_qubits)?;
for item in &self.target {
for &qubit in &item.qubits {
if !match_qubits.contains(&qubit) {
return Err(RuleValidationError::UnboundRewriteQubit { qubit });
}
}
}
let bound_symbols = self.operation_symbols();
for symbol in self.target_symbols() {
if !bound_symbols.contains(&symbol) {
return Err(RuleValidationError::UnboundRewriteSymbol { symbol });
}
}
for symbol in self.condition_symbols() {
if !bound_symbols.contains(&symbol) {
return Err(RuleValidationError::UnboundConditionSymbol { symbol });
}
}
Ok(())
}
pub fn num_qubits(&self) -> usize {
let mut op_qubits = self.operation_qubits();
op_qubits.extend(self.target_qubits());
op_qubits.len().max(1)
}
pub fn collect_free_symbols(&self) -> HashSet<String> {
let mut symbols = self.operation_symbols();
symbols.extend(self.target_symbols());
symbols.extend(self.condition_symbols());
symbols
}
pub fn target_qubits(&self) -> BTreeSet<u32> {
self.target
.iter()
.flat_map(|qs| qs.qubits.iter().cloned())
.collect()
}
pub fn operation_qubits(&self) -> BTreeSet<u32> {
self.operations
.iter()
.flat_map(|qs| qs.qubits.iter().cloned())
.collect()
}
pub fn operation_symbols(&self) -> HashSet<String> {
self.operations.iter().flat_map(RuleItem::symbols).collect()
}
pub fn target_symbols(&self) -> HashSet<String> {
self.target.iter().flat_map(RuleItem::symbols).collect()
}
pub fn condition_symbols(&self) -> HashSet<String> {
self.conditions
.iter()
.flatten()
.flat_map(Condition::symbols)
.collect()
}
}
fn validate_dense_qubit_labels(labels: &BTreeSet<u32>) -> Result<(), RuleValidationError> {
for (expected, actual) in labels.iter().copied().enumerate() {
if expected as u32 != actual {
return Err(RuleValidationError::NonDenseQubitLabels {
labels: labels.iter().copied().collect(),
});
}
}
Ok(())
}
#[cfg(test)]
#[path = "./rule_test.rs"]
mod rule_test;