Skip to content

Commit

Permalink
Redesign implementing circuits and related stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
jlapeyre committed Sep 25, 2024
1 parent 2a56204 commit 11d5709
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 74 deletions.
4 changes: 2 additions & 2 deletions crates/accelerate/src/xx_decompose/circuits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fn xx_circuit_step(source: &Coordinate, strength: f64, target: &Coordinate,

let mut permute_source_for_overlap: Option<Vec<GateData>> = None;
let mut permute_target_for_overlap: Option<Vec<GateData>> = None;

for reflection_name in &weyl::REFLECTION_NAMES {
let (reflected_source_coord, source_reflection, reflection_phase_shift) = weyl::apply_reflection(*reflection_name, source);
for source_shift_name in &weyl::SHIFT_NAMES {
Expand Down Expand Up @@ -166,7 +166,7 @@ fn xx_circuit_step(source: &Coordinate, strength: f64, target: &Coordinate,
// target = affix source prefix
// and computing just the prefix / affix circuits.


return Ok(())
}

Expand Down
15 changes: 15 additions & 0 deletions crates/accelerate/src/xx_decompose/decomposer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::xx_decompose::utilities::Square;
use crate::euler_one_qubit_decomposer::EulerBasis;
use hashbrown::HashMap;

use super::types::Circuit2Q;

struct Point {
a: f64,
Expand Down Expand Up @@ -29,3 +31,16 @@ fn _average_infidelity(p: Point, q: Point) -> f64 {
+ (a0 - a1).sin().sq() * (b0 - b1).sin().sq() * (c0 - c1).sin().sq()))
}

// The Python class XXDecomposeer has an attribute `backup_optimizer`, which allows the
// caller to inject an alternative optimizer to run under certain conditions. For various
// reasons, I prefer omit this field. Instead, `XXDecomposer` can signal failure somehow.
pub(crate) struct XXDecomposer {
basis_fidelity: Option<HashMap<f64, f64>>,
euler_basis: EulerBasis,
embodiments: Option<HashMap<f64, Circuit2Q>>,
}

// basis_fidelity: dict | float = 1.0,
// euler_basis: str = "U",
// embodiments: dict[float, QuantumCircuit] | None = None,
// backup_optimizer: Callable[..., QuantumCircuit] | None = None,
28 changes: 20 additions & 8 deletions crates/accelerate/src/xx_decompose/embodiments.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
// The Python version embodiments.py is not used in XXDecomposer
// itself. More precisely, functions and data in this file are
// not reference in the module xx_decompose.
// Rather it is meant to be (and is) used by consumers of XXDecomposer

// We will keep embodiments in Python for the moment.
// The easiest way to convert to Rust for the moment
// is via CircuitData machinery
use qiskit_circuit::operations::StandardGate as StdGate;
use crate::xx_decompose::types::{GateData, Circuit2Q};

fn rzx_circuit(theta: f64) -> Circuit2Q {
let gates = vec! [
GateData::oneq_no_param(StdGate::HGate, 0),
GateData::twoq_param(StdGate::RZXGate, theta, 0, 1),
GateData::oneq_no_param(StdGate::HGate, 0),
];
Circuit2Q::from_gates(gates)
}

fn rzz_circuit(theta: f64) -> Circuit2Q {
let gates = vec! [
GateData::oneq_no_param(StdGate::HGate, 0),
GateData::oneq_no_param(StdGate::HGate, 1),
GateData::twoq_param(StdGate::RZZGate, theta, 0, 1),
GateData::oneq_no_param(StdGate::HGate, 0),
GateData::oneq_no_param(StdGate::HGate, 1),
];
Circuit2Q::from_gates(gates)
}
1 change: 1 addition & 0 deletions crates/accelerate/src/xx_decompose/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ mod types;
mod weyl;
mod polytopes;
mod paths;
mod embodiments;
28 changes: 20 additions & 8 deletions crates/accelerate/src/xx_decompose/polytopes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ impl XXPolytope {
]
}


fn _member(&self, _point: &Coordinate) {
let mut point: Coordinate = (*_point).clone();
if point.needs_reflect0() {
point.reflect0();
}
}

/// Returns True when `point` is a member of `self`.
fn member(&self, point: Vec<Vec<f64>>) -> bool {
let offsets = self._offsets();
Expand Down Expand Up @@ -244,17 +252,13 @@ impl XXPolytope {
true
}

// rows = reflected_point[:, 0] >= np.pi / 4 + EPSILON
// reflected_point[rows, 0] = np.pi / 2 - reflected_point[rows, 0]
// reflected_point = reflected_point.reshape(point.shape)
fn nearest(point: &[f64; 3]) {

// return np.all(
// self._offsets + np.einsum("ij,...j->...i", A, reflected_point) >= -EPSILON, axis=-1
// )
}

// Method add_strength appears in the original Python
// Method `add_strength` appears in the original Python.
// But it is only called in the test suite.
// fn add_strength() {}

} // impl XXPolytope {

// Comment from polytopes.py:
Expand Down Expand Up @@ -290,6 +294,7 @@ static A: [[i32; 3]; 7] = [
// scipy can compute the pseudo-inverse of each 3-vector.
// The result is A1inv.

// dim(A1) = (7, 1, 3)
static A1: [[[f64; 3]; 1] ; 7] =
[[[ 1., -1., 0.]],

Expand All @@ -305,6 +310,13 @@ static A1: [[[f64; 3]; 1] ; 7] =

[[ 0., 0., -1.]]];


// A1INV each 3x1 element is the pseudoinverse
// of the corresponding 1x3 element in A1.
// The vector-pseudoinverse of v is the (co)vector iv
// such that iv · v equals one.
//
// dim(A1INV) = (7, 3, 1)
static A1INV: [[[f64; 1]; 3] ; 7] =
[[[ 0.5 ],
[-0.5 ],
Expand Down
116 changes: 94 additions & 22 deletions crates/accelerate/src/xx_decompose/types.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,132 @@
use std::f64::consts::PI;
use std::ops::Index;
use num_complex::Complex64;
use qiskit_circuit::operations::StandardGate;
use qiskit_circuit::operations::StandardGate as StdGate;
use crate::xx_decompose::utilities::EPSILON;

// One-qubit, one-or-zero parameter, gates
// Only rotation gates and H gate are supported fully.
// Represent gates with:
// * One or two qubits
// * Zero or one (angle) parameters
// `GateData` is meant to support two-qubit circuits. So qubit indices take values
// zero and one. There are a lot of ways to encode the information:
// * Distinguish one or two qubit gate
// * Indexes of first and second qubits.
// We could do this in three bits if need be. We´ll try to hide the implementation.
#[derive(Copy, Clone)]
pub(crate) struct GateData {
pub gate: StandardGate,
pub gate: StdGate,
pub param: Option<f64>,
pub qubit: i32,
pub qubit0: i32, // Takes values zero and one
pub qubit1: Option<i32>,
}

impl GateData {
// TODO: This method works, but is obsolete. Need fewer ways to instantiate.
pub(crate) fn with_param(gate: StandardGate, param: f64, qubit: i32) -> GateData {
GateData { gate, param: Some(param), qubit }

pub(crate) fn oneq_no_param(gate: StdGate, qubit: i32) -> GateData {
GateData {gate, param: None, qubit0: qubit, qubit1: None }
}

pub(crate) fn oneq_param(gate: StdGate, param: f64, qubit: i32) -> GateData {
GateData {gate, param: Some(param), qubit0: qubit, qubit1: None }
}

pub(crate) fn twoq_no_param(gate: StdGate, qubit0: i32, qubit1: i32) -> GateData {
GateData {gate, param: None, qubit0, qubit1: Some(qubit1) }
}

pub(crate) fn twoq_param(gate: StdGate, param: f64, qubit0: i32, qubit1: i32) -> GateData {
GateData {gate, param: Some(param), qubit0, qubit1: Some(qubit1) }
}

// TODO: what kind of name is good for predicates?
pub(crate) fn has_param(&self) -> bool {
self.param.is_some()
}

pub(crate) fn get_param(&self) -> f64 {
self.param.unwrap()
}

pub(crate) fn get_name(&self) -> StdGate {
self.gate
}

pub(crate) fn is_oneq(&self) -> bool {
self.qubit1.is_none()
}

pub(crate) fn is_twoq(&self) -> bool {
! self.is_oneq()
}

pub(crate) fn reverse(&self) -> GateData {
let mut gate = self.clone();
if self.has_param() {
gate.param = Some(-self.get_param());
return gate
}
let gate_name = match self.get_name() {
StdGate::HGate |
StdGate::XGate | StdGate::YGate | StdGate::ZGate |
StdGate::CXGate | StdGate::CYGate | StdGate::CZGate |
StdGate::CHGate
=> self.get_name(),
StdGate::SGate => StdGate::SdgGate,
StdGate::TGate => StdGate::TdgGate,
StdGate::SXGate => StdGate::SXdgGate,
_ => panic!("No support for this gate"),
};
gate.gate = gate_name;
gate
}
}

// A circuit composed of 1Q gates.
// Circuit may have more than one qubit.
pub(crate) struct Circuit1Q {
// A two-qubit circuit composed of one- and two-qubit gates
pub(crate) struct Circuit2Q {
gates: Vec<GateData>,
phase: Complex64,
}

impl Circuit1Q {
impl Circuit2Q {

// Reverse the quantum circuit by reversing the order of gates,
// reflecting the parameter (angle) in rotation gates, and reversing
// the circuit phase. This is correct for the gates used in this decomposer.
// This decomposer has only rotation gates and H gates until the last step,
// at which point we introduce Python and CircuitData.
fn reverse(&self) -> Circuit1Q {
fn reverse(&self) -> Circuit2Q {
let gates: Vec<GateData> = self.gates
.iter()
.rev()
.map(|g| match g {
// Reverse rotations
GateData{ gate, param: Some(param), qubit } => GateData { gate: *gate, param: Some(-param), qubit: *qubit },
// Copy other gates
GateData{ gate, param: None, qubit } => GateData { gate: *gate, param: None, qubit: *qubit },
})
.map(|g| g.reverse())
.collect();
Circuit1Q {gates, phase: -self.phase}
Circuit2Q {gates, phase: -self.phase}
}

pub(crate) fn from_gates(gates: Vec<GateData>) -> Circuit2Q {
Circuit2Q { gates, phase: Complex64::new(0.0, 0.0) }
}
}

// TODO: Need Display for user-facing error message.
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub(crate) struct Coordinate {
data: [f64; 3]
}

impl Coordinate {

pub(crate) fn reflect0(&mut self) {
self.data[0] = PI / 2. - self.data[0];
}

pub(crate) fn needs_reflect0(&self) -> bool {
if self.data[0] >= PI / 4. + EPSILON {
return true
}
false
}

pub(crate) fn reflect(&self, scalars: &[f64; 3]) -> Coordinate {
Coordinate {
data: [self.data[0] * (scalars[0]),
Expand All @@ -80,7 +152,7 @@ impl Coordinate {

pub(crate) fn iter(&self) -> std::slice::Iter<'_, f64> {
self.data.iter()
}
}
}

// Forward indexing into `Coordinate` to the field `data`.
Expand Down
Loading

0 comments on commit 11d5709

Please sign in to comment.