use super::gate::GateInstruction;
use crate::{
error::KetError,
ir::gate::{DecomposedGate, GatePropriety, Param, QuantumGate},
};
use itertools::Itertools;
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Default, Serialize)]
pub struct QubitOp {
pub propriety: GatePropriety,
pub read_qubits: BTreeSet<usize>,
}
#[derive(Debug, Clone, Default)]
pub struct BasicBlock {
pub gates: Vec<GateInstruction>,
pub qubits_op: BTreeMap<usize, QubitOp>,
pub global: Option<f64>,
}
impl BasicBlock {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn push_gate(&mut self, inst: GateInstruction, check_propiety: bool) {
if check_propiety {
self.qubits_op
.entry(inst.target)
.or_default()
.propriety
.restrict(inst.gate.propriety());
for c in &inst.control {
self.qubits_op
.entry(inst.target)
.or_default()
.read_qubits
.insert(*c);
}
}
self.gates.push(inst);
}
fn remove_gate(&mut self, index: usize) {
self.gates.remove(index);
}
fn merge_gate(&mut self, index: usize, merged_gate: QuantumGate) {
let inst = &self.gates[index];
self.qubits_op
.entry(inst.target)
.or_default()
.propriety
.restrict(inst.gate.propriety());
self.gates[index].gate = merged_gate;
}
fn append_instruction(
&mut self,
new_inst: GateInstruction,
epsilon: Option<f64>,
check_propiety: bool,
) {
let epsilon = epsilon.unwrap_or(1e-10);
if new_inst.gate.is_near_zero(epsilon) {
return;
}
for (index, inst) in self.gates.iter().enumerate().rev() {
let shares_qubits = inst.target == new_inst.target
|| inst.control.contains(&new_inst.target)
|| new_inst.control.contains(&inst.target)
|| inst.control.iter().any(|c| new_inst.control.contains(c));
if shares_qubits {
let same_target = inst.target == new_inst.target;
let same_controls = inst.control == new_inst.control;
if same_target && same_controls {
if inst.gate.inverse() == new_inst.gate {
return self.remove_gate(index);
} else if let Some(merged_gate) = inst.gate.merge(&new_inst.gate) {
if merged_gate.is_near_zero(epsilon) {
return self.remove_gate(index);
}
return self.merge_gate(index, merged_gate);
}
}
if !inst.commutes_with(&new_inst) {
break;
}
}
}
self.push_gate(new_inst, check_propiety);
}
pub fn append_gate(&mut self, gate: QuantumGate, target: usize) {
self.append_instruction(GateInstruction::new(gate, target), None, true);
}
#[must_use]
pub fn inverse(&self) -> Self {
Self {
gates: self
.gates
.iter()
.rev()
.map(GateInstruction::inverse)
.collect_vec(),
global: self.global.map(|g| -g),
qubits_op: self.qubits_op.clone(),
}
}
pub fn control(&self, control_qubits: &[usize]) -> Result<Self, KetError> {
let gates: Result<Vec<_>, _> = self
.gates
.iter()
.map(|gate| gate.control(control_qubits))
.collect();
let gates = gates?;
let qubits_op = self
.qubits_op
.iter()
.map(|(target, op)| {
let mut op = op.to_owned();
for c in control_qubits {
op.read_qubits.insert(*c);
}
(*target, op)
})
.collect();
let mut result = Self {
gates,
global: None,
qubits_op,
};
if let Some(phase) = &self.global {
result.append_instruction(
GateInstruction {
gate: QuantumGate::Phase(Param::Value(*phase)),
target: control_qubits[0],
control: control_qubits
.iter()
.skip(1)
.copied()
.collect::<BTreeSet<_>>(),
control_locked: false,
is_approximated: false,
decomposed: None,
},
None,
true,
);
}
Ok(result)
}
pub fn enable_approximated_decomposition(&mut self) {
self.gates
.iter_mut()
.for_each(GateInstruction::enable_approximated_decomposition);
}
pub fn lock_control(&mut self) {
self.gates
.iter_mut()
.for_each(GateInstruction::lock_control);
}
pub fn append_block(&mut self, mut block: Self, epsilon: Option<f64>) {
for inst in block.gates {
self.append_instruction(inst, epsilon, false);
}
self.global = match (self.global, block.global) {
(None, None) => None,
(None, Some(g)) | (Some(g), None) => Some(g),
(Some(g1), Some(g2)) => Some(g1 + g2),
};
for (&qubit, op) in &mut block.qubits_op {
let self_op = self.qubits_op.entry(qubit).or_default();
self_op.propriety.restrict(op.propriety);
self_op.read_qubits.append(&mut op.read_qubits);
}
}
pub fn add_global_phase(&mut self, phase: f64) {
self.global = Some(self.global.unwrap_or_default() + phase);
}
#[must_use]
pub fn max_qubit_index(&self) -> Option<usize> {
let max_read = self.qubits_op.values().flat_map(|op| &op.read_qubits).max();
let max_written = self.qubits_op.keys().max();
match (max_read, max_written) {
(None, None) => None,
(None, Some(&w)) => Some(w),
(Some(&r), None) => Some(r),
(Some(&r), Some(&w)) => Some(r.max(w)),
}
}
#[must_use]
pub fn flat_gates(
&self,
parameters: Option<&[f64]>,
shift: Option<(usize, usize, f64)>, ) -> Vec<DecomposedGate> {
let mut instance_counts = std::collections::HashMap::new();
self.gates
.iter()
.flat_map(|gate| {
if let Some(gates) = &gate.decomposed {
gates
.iter()
.map(|g| match g {
DecomposedGate::U(qg, target) => DecomposedGate::U(*qg, *target),
DecomposedGate::CNOT(c, t) => DecomposedGate::CNOT(*c, *t),
})
.collect_vec()
} else {
assert!(gate.control.is_empty());
let qg = &gate.gate;
let mut final_gate = if let Some(parameters) = parameters {
qg.set_parameter(parameters)
} else {
*qg
};
if let Some((shift_param, shift_inst, shift_amt)) = shift {
let mut p_idx = None;
match qg {
QuantumGate::RotationX(Param::Ref { index, .. })
| QuantumGate::RotationY(Param::Ref { index, .. })
| QuantumGate::RotationZ(Param::Ref { index, .. })
| QuantumGate::Phase(Param::Ref { index, .. }) => {
p_idx = Some(*index);
}
_ => {}
}
if let Some(idx) = p_idx {
if idx == shift_param {
let count = instance_counts.entry(idx).or_insert(0);
if *count == shift_inst {
final_gate = match final_gate {
QuantumGate::RotationX(Param::Value(v)) => {
QuantumGate::RotationX(Param::Value(v + shift_amt))
}
QuantumGate::RotationY(Param::Value(v)) => {
QuantumGate::RotationY(Param::Value(v + shift_amt))
}
QuantumGate::RotationZ(Param::Value(v)) => {
QuantumGate::RotationZ(Param::Value(v + shift_amt))
}
QuantumGate::Phase(Param::Value(v)) => {
QuantumGate::Phase(Param::Value(v + shift_amt))
}
_ => final_gate,
};
}
*count += 1;
}
}
}
vec![(final_gate, gate.target).into()]
}
})
.collect_vec()
}
pub fn set_as_diagonal(&mut self) {
self.qubits_op.values_mut().for_each(|op| {
op.propriety.broaden(GatePropriety::Diagonal);
});
}
pub fn set_as_permutation(&mut self) {
self.qubits_op.values_mut().for_each(|op| {
op.propriety.broaden(GatePropriety::Permutation);
});
}
}