use crate::circuit::{Instruction, MCGate, Parameter, Qubit, StandardGate};
use crate::compile::PARAMETER_EQ_TOLERANCE;
use crate::compile::commutation::checker::{Commutation, CommutationResult};
use smallvec::{SmallVec, smallvec};
use std::f64::consts::{FRAC_PI_2, PI};
const TWO_PI: f64 = 2.0 * PI;
const FOUR_PI: f64 = 4.0 * PI;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PauliAxis {
X,
Y,
Z,
}
#[derive(Debug, Clone)]
enum Axis {
Pauli(PauliAxis),
Planar(Parameter),
}
#[derive(Debug, Clone)]
struct AxisTerm {
qubit: Qubit,
axis: Axis,
}
type AxisString = SmallVec<[AxisTerm; 2]>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AxisOpKind {
Product,
Rotation,
}
#[derive(Debug, Clone)]
struct AxisOp {
terms: AxisString,
kind: AxisOpKind,
}
#[derive(Debug, Clone)]
struct ControlledAxis {
controls: SmallVec<[Qubit; 4]>,
target: Qubit,
axis: Axis,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AxisRelation {
Same,
Orthogonal,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TwoQubitFamily {
Swap,
Fsim,
SymmetricPauliRotation,
SymmetricDiagonal,
}
pub fn algebraic_commutation(
lhs_inst: &Instruction,
lhs_qubits: &[Qubit],
lhs_params: &[Parameter],
rhs_inst: &Instruction,
rhs_qubits: &[Qubit],
rhs_params: &[Parameter],
) -> CommutationResult {
if instruction_is_global_phase(lhs_inst, lhs_params)
|| instruction_is_global_phase(rhs_inst, rhs_params)
{
return Some(Commutation::Exact);
}
if instruction_is_diagonal(lhs_inst) && instruction_is_diagonal(rhs_inst) {
return Some(Commutation::Exact);
}
if let (Some(lhs), Some(rhs)) = (
axis_op_for_instruction(lhs_inst, lhs_qubits, lhs_params),
axis_op_for_instruction(rhs_inst, rhs_qubits, rhs_params),
) && let Some(result) = axis_ops_commutation(&lhs, &rhs)
{
return Some(result);
}
if let Some(result) = controlled_commutation(
lhs_inst, lhs_qubits, lhs_params, rhs_inst, rhs_qubits, rhs_params,
) {
return Some(result);
}
special_two_qubit_commutation(lhs_inst, lhs_qubits, rhs_inst, rhs_qubits)
}
fn instruction_is_global_phase(instruction: &Instruction, params: &[Parameter]) -> bool {
match instruction {
Instruction::Standard(gate) => standard_is_global_phase(*gate, params),
Instruction::McGate(gate) if gate.num_qubits() == gate.base_gate().num_qubits() => {
standard_is_global_phase(*gate.base_gate(), params)
}
Instruction::McGate(gate) => standard_is_identity(*gate.base_gate(), params),
_ => false,
}
}
fn standard_is_global_phase(gate: StandardGate, params: &[Parameter]) -> bool {
match gate {
StandardGate::I | StandardGate::GPhase => true,
StandardGate::RX
| StandardGate::RY
| StandardGate::RZ
| StandardGate::RXX
| StandardGate::RYY
| StandardGate::RZZ
| StandardGate::RZX
| StandardGate::RXY => angle_eq_mod(¶ms[0], 0.0, TWO_PI),
StandardGate::Phase => angle_eq_mod(¶ms[0], 0.0, TWO_PI),
StandardGate::CRX | StandardGate::CRY | StandardGate::CRZ => {
angle_eq_mod(¶ms[0], 0.0, FOUR_PI)
}
StandardGate::FSIM => {
angle_eq_mod(¶ms[0], 0.0, TWO_PI) && angle_eq_mod(¶ms[1], 0.0, TWO_PI)
}
_ => false,
}
}
fn standard_is_identity(gate: StandardGate, params: &[Parameter]) -> bool {
match gate {
StandardGate::I => true,
StandardGate::GPhase => angle_eq_mod(¶ms[0], 0.0, TWO_PI),
StandardGate::RX
| StandardGate::RY
| StandardGate::RZ
| StandardGate::RXX
| StandardGate::RYY
| StandardGate::RZZ
| StandardGate::RZX
| StandardGate::RXY
| StandardGate::CRX
| StandardGate::CRY
| StandardGate::CRZ => angle_eq_mod(¶ms[0], 0.0, FOUR_PI),
StandardGate::Phase => angle_eq_mod(¶ms[0], 0.0, TWO_PI),
StandardGate::FSIM => {
angle_eq_mod(¶ms[0], 0.0, TWO_PI) && angle_eq_mod(¶ms[1], 0.0, TWO_PI)
}
_ => false,
}
}
fn instruction_is_diagonal(instruction: &Instruction) -> bool {
match instruction {
Instruction::Standard(gate) => gate.is_diagonal(),
Instruction::McGate(gate) => gate.base_gate().is_diagonal(),
_ => false,
}
}
fn axis_ops_commutation(lhs: &AxisOp, rhs: &AxisOp) -> CommutationResult {
let anti_commutations = axis_string_anti_commutations(&lhs.terms, &rhs.terms)?;
if anti_commutations % 2 == 0 {
return Some(Commutation::Exact);
}
if lhs.kind == AxisOpKind::Product && rhs.kind == AxisOpKind::Product {
return Some(Commutation::UpToGlobalPhase(Parameter::from(PI)));
}
None
}
fn axis_string_anti_commutations(lhs: &AxisString, rhs: &AxisString) -> Option<usize> {
let mut anti_commutations = 0usize;
for lhs_term in lhs {
for rhs_term in rhs {
if lhs_term.qubit != rhs_term.qubit {
continue;
}
match axis_relation(&lhs_term.axis, &rhs_term.axis) {
AxisRelation::Same => {}
AxisRelation::Orthogonal => anti_commutations += 1,
AxisRelation::Unknown => return None,
}
}
}
Some(anti_commutations)
}
fn axis_op_for_instruction(
instruction: &Instruction,
qubits: &[Qubit],
params: &[Parameter],
) -> Option<AxisOp> {
match instruction {
Instruction::Standard(gate) => axis_op_for_standard(*gate, qubits, params),
Instruction::McGate(gate) if gate.num_qubits() == gate.base_gate().num_qubits() => {
axis_op_for_standard(*gate.base_gate(), qubits, params)
}
_ => None,
}
}
fn axis_op_for_standard(
gate: StandardGate,
qubits: &[Qubit],
params: &[Parameter],
) -> Option<AxisOp> {
match gate {
StandardGate::X if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::X),
}],
kind: AxisOpKind::Product,
}),
StandardGate::Y if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Y),
}],
kind: AxisOpKind::Product,
}),
StandardGate::Z if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Z),
}],
kind: AxisOpKind::Product,
}),
StandardGate::X2P | StandardGate::X2M if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::X),
}],
kind: AxisOpKind::Rotation,
}),
StandardGate::Y2P | StandardGate::Y2M if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Y),
}],
kind: AxisOpKind::Rotation,
}),
StandardGate::S | StandardGate::SDG | StandardGate::T | StandardGate::TDG
if qubits.len() == 1 =>
{
Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Z),
}],
kind: AxisOpKind::Rotation,
})
}
StandardGate::RX if qubits.len() == 1 => Some(rotation_or_product(
¶ms[0],
smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::X),
}],
)),
StandardGate::RY if qubits.len() == 1 => Some(rotation_or_product(
¶ms[0],
smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Y),
}],
)),
StandardGate::RZ | StandardGate::Phase if qubits.len() == 1 => Some(rotation_or_product(
¶ms[0],
smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Z),
}],
)),
StandardGate::RXX if qubits.len() == 2 => Some(rotation_or_product(
¶ms[0],
smallvec![
AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::X),
},
AxisTerm {
qubit: qubits[1],
axis: Axis::Pauli(PauliAxis::X),
}
],
)),
StandardGate::RYY if qubits.len() == 2 => Some(rotation_or_product(
¶ms[0],
smallvec![
AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Y),
},
AxisTerm {
qubit: qubits[1],
axis: Axis::Pauli(PauliAxis::Y),
}
],
)),
StandardGate::RZZ if qubits.len() == 2 => Some(rotation_or_product(
¶ms[0],
smallvec![
AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Z),
},
AxisTerm {
qubit: qubits[1],
axis: Axis::Pauli(PauliAxis::Z),
}
],
)),
StandardGate::RZX if qubits.len() == 2 => Some(rotation_or_product(
¶ms[0],
smallvec![
AxisTerm {
qubit: qubits[0],
axis: Axis::Pauli(PauliAxis::Z),
},
AxisTerm {
qubit: qubits[1],
axis: Axis::Pauli(PauliAxis::X),
}
],
)),
StandardGate::RXY if qubits.len() == 1 => Some(rotation_or_product(
¶ms[0],
smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Planar(params[1].clone()),
}],
)),
StandardGate::XY if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Planar(params[0].clone()),
}],
kind: AxisOpKind::Product,
}),
StandardGate::XY2P | StandardGate::XY2M if qubits.len() == 1 => Some(AxisOp {
terms: smallvec![AxisTerm {
qubit: qubits[0],
axis: Axis::Planar(params[0].clone()),
}],
kind: AxisOpKind::Rotation,
}),
_ => None,
}
}
fn rotation_or_product(angle: &Parameter, terms: AxisString) -> AxisOp {
if angle_eq_mod(angle, 0.0, TWO_PI) {
return AxisOp {
terms: SmallVec::new(),
kind: AxisOpKind::Rotation,
};
}
AxisOp {
terms,
kind: if angle_eq_mod(angle, PI, TWO_PI) {
AxisOpKind::Product
} else {
AxisOpKind::Rotation
},
}
}
fn controlled_commutation(
lhs_inst: &Instruction,
lhs_qubits: &[Qubit],
lhs_params: &[Parameter],
rhs_inst: &Instruction,
rhs_qubits: &[Qubit],
rhs_params: &[Parameter],
) -> CommutationResult {
let lhs_controlled = controlled_axis_for_instruction(lhs_inst, lhs_qubits, lhs_params);
let rhs_controlled = controlled_axis_for_instruction(rhs_inst, rhs_qubits, rhs_params);
match (lhs_controlled, rhs_controlled) {
(Some(lhs), Some(rhs)) => controlled_pair_commutation(&lhs, &rhs),
(Some(controlled), None) => {
let rhs_axis = axis_op_for_instruction(rhs_inst, rhs_qubits, rhs_params)?;
controlled_axis_op_commutation(&controlled, &rhs_axis)
}
(None, Some(controlled)) => {
let lhs_axis = axis_op_for_instruction(lhs_inst, lhs_qubits, lhs_params)?;
controlled_axis_op_commutation(&controlled, &lhs_axis)
}
(None, None) => None,
}
}
fn controlled_pair_commutation(lhs: &ControlledAxis, rhs: &ControlledAxis) -> CommutationResult {
if lhs.controls.contains(&rhs.target) || rhs.controls.contains(&lhs.target) {
return None;
}
if lhs.target == rhs.target {
if axis_relation(&lhs.axis, &rhs.axis) == AxisRelation::Same {
return Some(Commutation::Exact);
}
return None;
}
Some(Commutation::Exact)
}
fn controlled_axis_op_commutation(
controlled: &ControlledAxis,
axis_op: &AxisOp,
) -> CommutationResult {
for term in &axis_op.terms {
if controlled.controls.contains(&term.qubit)
&& axis_relation(&term.axis, &Axis::Pauli(PauliAxis::Z)) != AxisRelation::Same
{
return None;
}
if term.qubit == controlled.target
&& axis_relation(&term.axis, &controlled.axis) != AxisRelation::Same
{
return None;
}
}
Some(Commutation::Exact)
}
fn controlled_axis_for_instruction(
instruction: &Instruction,
qubits: &[Qubit],
params: &[Parameter],
) -> Option<ControlledAxis> {
match instruction {
Instruction::Standard(gate) => controlled_axis_for_standard(*gate, qubits),
Instruction::McGate(gate) => controlled_axis_for_mc_gate(gate, qubits, params),
_ => None,
}
}
fn controlled_axis_for_standard(gate: StandardGate, qubits: &[Qubit]) -> Option<ControlledAxis> {
match gate {
StandardGate::CX if qubits.len() == 2 => Some(ControlledAxis {
controls: smallvec![qubits[0]],
target: qubits[1],
axis: Axis::Pauli(PauliAxis::X),
}),
StandardGate::CY if qubits.len() == 2 => Some(ControlledAxis {
controls: smallvec![qubits[0]],
target: qubits[1],
axis: Axis::Pauli(PauliAxis::Y),
}),
StandardGate::CZ if qubits.len() == 2 => Some(ControlledAxis {
controls: smallvec![qubits[0]],
target: qubits[1],
axis: Axis::Pauli(PauliAxis::Z),
}),
StandardGate::CCX if qubits.len() == 3 => Some(ControlledAxis {
controls: smallvec![qubits[0], qubits[1]],
target: qubits[2],
axis: Axis::Pauli(PauliAxis::X),
}),
StandardGate::CRX if qubits.len() == 2 => Some(ControlledAxis {
controls: smallvec![qubits[0]],
target: qubits[1],
axis: Axis::Pauli(PauliAxis::X),
}),
StandardGate::CRY if qubits.len() == 2 => Some(ControlledAxis {
controls: smallvec![qubits[0]],
target: qubits[1],
axis: Axis::Pauli(PauliAxis::Y),
}),
StandardGate::CRZ if qubits.len() == 2 => Some(ControlledAxis {
controls: smallvec![qubits[0]],
target: qubits[1],
axis: Axis::Pauli(PauliAxis::Z),
}),
_ => None,
}
}
fn controlled_axis_for_mc_gate(
gate: &MCGate,
qubits: &[Qubit],
params: &[Parameter],
) -> Option<ControlledAxis> {
let added_controls = gate
.num_qubits()
.checked_sub(gate.base_gate().num_qubits())?;
if added_controls == 0 {
return controlled_axis_for_standard(*gate.base_gate(), qubits);
}
let base_qubits = &qubits[added_controls..];
let mut controlled =
if let Some(controlled) = controlled_axis_for_standard(*gate.base_gate(), base_qubits) {
controlled
} else {
let axis_op = axis_op_for_standard(*gate.base_gate(), base_qubits, params)?;
if axis_op.terms.len() != 1 {
return None;
}
let term = axis_op.terms[0].clone();
ControlledAxis {
controls: SmallVec::new(),
target: term.qubit,
axis: term.axis,
}
};
for &control in qubits[..added_controls].iter().rev() {
controlled.controls.insert(0, control);
}
Some(controlled)
}
fn special_two_qubit_commutation(
lhs_inst: &Instruction,
lhs_qubits: &[Qubit],
rhs_inst: &Instruction,
rhs_qubits: &[Qubit],
) -> CommutationResult {
let lhs = two_qubit_family(lhs_inst, lhs_qubits)?;
let rhs = two_qubit_family(rhs_inst, rhs_qubits)?;
if lhs.0 != rhs.0 {
return None;
}
match (lhs.1, rhs.1) {
(TwoQubitFamily::Swap, TwoQubitFamily::Swap)
| (TwoQubitFamily::Fsim, TwoQubitFamily::Fsim)
| (TwoQubitFamily::Swap, TwoQubitFamily::Fsim)
| (TwoQubitFamily::Fsim, TwoQubitFamily::Swap)
| (TwoQubitFamily::Swap, TwoQubitFamily::SymmetricPauliRotation)
| (TwoQubitFamily::SymmetricPauliRotation, TwoQubitFamily::Swap)
| (TwoQubitFamily::Swap, TwoQubitFamily::SymmetricDiagonal)
| (TwoQubitFamily::SymmetricDiagonal, TwoQubitFamily::Swap)
| (TwoQubitFamily::Fsim, TwoQubitFamily::SymmetricDiagonal)
| (TwoQubitFamily::SymmetricDiagonal, TwoQubitFamily::Fsim) => Some(Commutation::Exact),
_ => None,
}
}
fn two_qubit_family(
instruction: &Instruction,
qubits: &[Qubit],
) -> Option<((Qubit, Qubit), TwoQubitFamily)> {
let gate = match instruction {
Instruction::Standard(gate) => *gate,
Instruction::McGate(gate) if gate.num_qubits() == gate.base_gate().num_qubits() => {
*gate.base_gate()
}
_ => return None,
};
if qubits.len() != 2 {
return None;
}
let pair = if qubits[0] <= qubits[1] {
(qubits[0], qubits[1])
} else {
(qubits[1], qubits[0])
};
let family = match gate {
StandardGate::SWAP => TwoQubitFamily::Swap,
StandardGate::FSIM => TwoQubitFamily::Fsim,
StandardGate::RXX | StandardGate::RYY => TwoQubitFamily::SymmetricPauliRotation,
StandardGate::RZZ | StandardGate::CZ => TwoQubitFamily::SymmetricDiagonal,
_ => return None,
};
Some((pair, family))
}
fn axis_relation(lhs: &Axis, rhs: &Axis) -> AxisRelation {
match (lhs, rhs) {
(Axis::Pauli(lhs), Axis::Pauli(rhs)) if lhs == rhs => AxisRelation::Same,
(Axis::Pauli(_), Axis::Pauli(_)) => AxisRelation::Orthogonal,
(Axis::Planar(_), Axis::Pauli(PauliAxis::Z))
| (Axis::Pauli(PauliAxis::Z), Axis::Planar(_)) => AxisRelation::Orthogonal,
(Axis::Planar(phi), Axis::Pauli(axis)) | (Axis::Pauli(axis), Axis::Planar(phi)) => {
match *axis {
PauliAxis::X if angle_eq_mod(phi, 0.0, PI) => AxisRelation::Same,
PauliAxis::X if angle_eq_mod(phi, FRAC_PI_2, PI) => AxisRelation::Orthogonal,
PauliAxis::Y if angle_eq_mod(phi, FRAC_PI_2, PI) => AxisRelation::Same,
PauliAxis::Y if angle_eq_mod(phi, 0.0, PI) => AxisRelation::Orthogonal,
PauliAxis::Z => AxisRelation::Orthogonal,
_ => AxisRelation::Unknown,
}
}
(Axis::Planar(lhs), Axis::Planar(rhs)) => {
if lhs.provably_equal_modulo(rhs, &Parameter::from(PI), PARAMETER_EQ_TOLERANCE) {
return AxisRelation::Same;
}
let rhs_plus_half_pi = rhs.clone() + FRAC_PI_2;
if lhs.provably_equal_modulo(
&rhs_plus_half_pi,
&Parameter::from(PI),
PARAMETER_EQ_TOLERANCE,
) {
return AxisRelation::Orthogonal;
}
AxisRelation::Unknown
}
}
}
fn angle_eq_mod(angle: &Parameter, expected: f64, modulus: f64) -> bool {
angle.provably_equal_modulo(
&Parameter::from(expected),
&Parameter::from(modulus),
PARAMETER_EQ_TOLERANCE,
)
}