Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Phase struct #31

Merged
merged 2 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Phase struct
  • Loading branch information
ABorgna committed May 6, 2024
commit 69e75d2173a0671fb39a3ad210108bfb2880f6c9
219 changes: 81 additions & 138 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ rayon = "1.5.0"
regex = "1.4.3"
rustc-hash = "1.1.0"
derive_more = "0.99.17"
rstest = "0.18.2"
rstest = "0.19.0"
serde = "1.0.199"
serde_json = "1.0.116"
thiserror = "1.0.59"

[workspace.package]
version = "0.1.0"
authors = ["Aleks Kissinger <[email protected]>"]
edition = "2018"
edition = "2021"
rust-version = "1.75"
homepage = "https://github.com/Quantomatic/quizx"
license-file = "LICENSE"
5 changes: 3 additions & 2 deletions pybindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use quizx::extract::ToCircuit;
use quizx::graph::*;
use quizx::phase::Phase;

#[pymodule]
fn _quizx(m: &Bound<'_, PyModule>) -> PyResult<()> {
Expand Down Expand Up @@ -160,7 +161,7 @@ impl VecGraph {
3 => VType::H,
_ => VType::B,
};
let phase = Rational64::new(phase.0, phase.1);
let phase = Phase::new((phase.0, phase.1));
self.g.add_vertex_with_data(VData {
ty,
phase,
Expand Down Expand Up @@ -246,7 +247,7 @@ impl VecGraph {
}

fn phase(&self, v: usize) -> (i64, i64) {
let p = self.g.phase(v);
let p = self.g.phase(v).to_rational();
(*p.numer(), *p.denom())
}

Expand Down
60 changes: 31 additions & 29 deletions quizx/src/basic_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
//! transformations, or even panic, if `check_X` doesn't return true.

use crate::graph::*;
use crate::phase::Phase;
use crate::scalar::*;
use num::traits::Zero;
use num::Rational64;
Expand Down Expand Up @@ -209,7 +210,7 @@ pub fn pi_copy_unchecked(g: &mut impl GraphLike, v: V) {

// Push a pi to all the surrounding nodes
for neighbor in g.neighbor_vec(v) {
g.add_to_phase(neighbor, 1.into());
g.add_to_phase(neighbor, 1);
}
}

Expand Down Expand Up @@ -269,15 +270,15 @@ checked_rule1!(check_color_change, color_change_unchecked, color_change);
/// surrounded by H-edges connected to other Z spiders.
pub fn check_local_comp(g: &impl GraphLike, v: V) -> bool {
g.vertex_type(v) == VType::Z
&& *g.phase(v).denom() == 2
&& g.phase(v).is_proper_clifford()
&& g.incident_edges(v)
.all(|(v0, et)| g.vertex_type(v0) == VType::Z && et == EType::H)
}

/// Apply a local complementation
///
/// This is the version that deletes the targeted vertex. In
/// other words, it is an N-ary generalisatio of the Euler
/// other words, it is an N-ary generalization of the Euler
/// decomposition rule.
pub fn local_comp_unchecked(g: &mut impl GraphLike, v: V) {
let p = g.phase(v);
Expand All @@ -294,7 +295,8 @@ pub fn local_comp_unchecked(g: &mut impl GraphLike, v: V) {

let x = ns.len() as i32;
g.scalar_mut().mul_sqrt2_pow(((x - 1) * (x - 2)) / 2);
g.scalar_mut().mul_phase(Rational64::new(*p.numer(), 4));
g.scalar_mut()
.mul_phase(Rational64::new(*p.to_rational().numer(), 4));
}

checked_rule1!(check_local_comp, local_comp_unchecked, local_comp);
Expand All @@ -307,8 +309,8 @@ pub fn check_pivot(g: &impl GraphLike, v0: V, v1: V) -> bool {
g.vertex_type(v0) == VType::Z
&& g.vertex_type(v1) == VType::Z
&& g.edge_type_opt(v0, v1) == Some(EType::H)
&& g.phase(v0).is_integer()
&& g.phase(v1).is_integer()
&& g.phase(v0).is_pauli()
&& g.phase(v1).is_pauli()
&& g.incident_edges(v0)
.all(|(w, et)| g.vertex_type(w) == VType::Z && et == EType::H)
&& g.incident_edges(v1)
Expand Down Expand Up @@ -367,7 +369,7 @@ fn unfuse_boundary(g: &mut impl GraphLike, v: V, b: V) {
}
let vd = VData {
ty: VType::Z,
phase: Rational64::zero(),
phase: Phase::zero(),
row: g.row(v),
qubit: g.qubit(v),
};
Expand All @@ -381,12 +383,12 @@ fn unfuse_boundary(g: &mut impl GraphLike, v: V, b: V) {
///
/// If the vertex already has a Pauli phase, this is a noop.
fn unfuse_gadget(g: &mut impl GraphLike, v: V) {
if g.phase(v).is_integer() {
if g.phase(v).is_pauli() {
return;
}
let vd = VData {
ty: VType::Z,
phase: Rational64::zero(),
phase: Phase::zero(),
row: g.row(v),
qubit: g.qubit(v),
};
Expand Down Expand Up @@ -430,15 +432,15 @@ pub fn check_gen_pivot(g: &impl GraphLike, v0: V, v1: V) -> bool {
// check that a vertex is interior, has phase 0 or pi, and is not
// a phase gadget
fn is_interior_pauli(g: &impl GraphLike, v: V) -> bool {
g.phase(v).is_integer()
g.phase(v).is_pauli()
&& g.neighbors(v)
.all(|n| g.vertex_type(n) == VType::Z && g.degree(n) > 1)
}

// check that a vertex is interior, has phase 0 or pi, and is not
// a phase gadget
fn is_boundary_pauli(g: &impl GraphLike, v: V) -> bool {
g.phase(v).is_integer() && g.neighbors(v).any(|n| g.vertex_type(n) == VType::B)
g.phase(v).is_pauli() && g.neighbors(v).any(|n| g.vertex_type(n) == VType::B)
}

/// Check gen_pivot applies and at least one vertex is interior Pauli
Expand Down Expand Up @@ -579,7 +581,7 @@ pub fn remove_pair_unchecked(g: &mut impl GraphLike, v0: V, v1: V) {
*g.scalar_mut() *= ScalarN::one_plus_phase(p0 + p1);
// different colors
} else {
let p2 = Rational64::one() + p0 + p1;
let p2 = Phase::one() + p0 + p1;
*g.scalar_mut() *= ScalarN::one()
+ ScalarN::from_phase(p0)
+ ScalarN::from_phase(p1)
Expand Down Expand Up @@ -640,7 +642,7 @@ mod tests {

assert_eq!(g.to_tensor4(), h.to_tensor4());

assert_eq!(g.phase(vs[2]), Rational64::new(3, 4));
assert_eq!(g.phase(vs[2]), Rational64::new(3, 4).into());
}

#[test]
Expand Down Expand Up @@ -694,7 +696,7 @@ mod tests {
println!("\n\nth =\n{}", th);
assert_eq!(tg, th);

assert_eq!(g.phase(vs[2]), Rational64::new(3, 4));
assert_eq!(g.phase(vs[2]), Rational64::new(3, 4).into());
}

#[test]
Expand Down Expand Up @@ -739,7 +741,7 @@ mod tests {
assert_eq!(tg, th);

for i in 1..5 {
assert_eq!(g.phase(i), Rational64::new(-1, 2));
assert_eq!(g.phase(i), Rational64::new(-1, 2).into());
}

assert_eq!(
Expand Down Expand Up @@ -784,8 +786,8 @@ mod tests {
assert_eq!(h.num_vertices(), 5);
assert_eq!(h.num_edges(), 6);

assert_eq!(h.phase(0), Rational64::new(0, 1));
assert_eq!(h.phase(6), Rational64::new(1, 1));
assert_eq!(h.phase(0), Rational64::new(0, 1).into());
assert_eq!(h.phase(6), Rational64::new(1, 1).into());

let mut inputs: Vec<usize> = Vec::new();
let mut outputs: Vec<usize> = Vec::new();
Expand Down Expand Up @@ -836,8 +838,8 @@ mod tests {
assert_eq!(g.num_vertices(), 5);
assert_eq!(g.num_edges(), 6);

assert_eq!(g.phase(0), Rational64::new(1, 1));
assert_eq!(g.phase(6), Rational64::new(1, 1));
assert_eq!(g.phase(0), Rational64::new(1, 1).into());
assert_eq!(g.phase(6), Rational64::new(1, 1).into());
}

#[test]
Expand Down Expand Up @@ -936,13 +938,13 @@ mod tests {

assert!(gadget_fusion(&mut graph, gs[0], gs[1]));
assert!(graph
.find_vertex(|v| graph.phase(v) == Rational64::new(3, 4))
.find_vertex(|v| graph.phase(v) == Rational64::new(3, 4).into())
.is_some());
assert!(graph
.find_vertex(|v| graph.phase(v) == Rational64::new(1, 4))
.find_vertex(|v| graph.phase(v) == Rational64::new(1, 4).into())
.is_none());
assert!(graph
.find_vertex(|v| graph.phase(v) == Rational64::new(1, 2))
.find_vertex(|v| graph.phase(v) == Rational64::new(1, 2).into())
.is_none());
// println!("{}", g.to_tensor4());
// println!("{}", h.to_tensor4());
Expand Down Expand Up @@ -990,13 +992,13 @@ mod tests {
fn pi_copy_1() {
let mut g = Graph::new();
let vs = [
g.add_vertex_with_phase(VType::Z, 1.into()),
g.add_vertex_with_phase(VType::Z, (1, 2).into()),
g.add_vertex_with_phase(VType::X, (1, 4).into()),
g.add_vertex_with_phase(VType::Z, 0.into()),
g.add_vertex_with_phase(VType::X, (3, 2).into()),
g.add_vertex_with_phase(VType::Z, (3, 4).into()),
g.add_vertex_with_phase(VType::B, 0.into()),
g.add_vertex_with_phase(VType::Z, 1),
g.add_vertex_with_phase(VType::Z, (1, 2)),
g.add_vertex_with_phase(VType::X, (1, 4)),
g.add_vertex_with_phase(VType::Z, 0),
g.add_vertex_with_phase(VType::X, (3, 2)),
g.add_vertex_with_phase(VType::Z, (3, 4)),
g.add_vertex_with_phase(VType::B, 0),
];

g.set_inputs(vec![vs[6]]);
Expand Down
26 changes: 13 additions & 13 deletions quizx/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use crate::gate::*;
use crate::graph::*;
use crate::linalg::RowOps;
use crate::scalar::Mod2;
use crate::phase::Phase;
use num::{Rational64, Zero};
use openqasm::{ast::Symbol, translate::Value, GenericError, ProgramVisitor};
use std::collections::VecDeque;
Expand Down Expand Up @@ -71,7 +71,7 @@ impl CircuitStats {
s.cliff += 1;
}
ZPhase | XPhase => {
if *g.phase.denom() == 1 || *g.phase.denom() == 2 {
if g.phase.is_clifford() {
s.cliff += 1;
} else {
s.non_cliff += 1;
Expand Down Expand Up @@ -142,11 +142,11 @@ impl Circuit {
self.gates.push_front(g);
}

pub fn add_gate_with_phase(&mut self, name: &str, qs: Vec<usize>, phase: Rational64) {
pub fn add_gate_with_phase(&mut self, name: &str, qs: Vec<usize>, phase: impl Into<Phase>) {
self.push(Gate {
t: GType::from_qasm_name(name),
qs,
phase: phase.mod2(),
phase: phase.into(),
});
}

Expand Down Expand Up @@ -256,7 +256,7 @@ impl Circuit {
for i in 0..self.nqubits {
let v = graph.add_vertex_with_data(VData {
ty: VType::B,
phase: Rational64::zero(),
phase: Phase::zero(),
qubit: i as i32,
row: 1,
});
Expand Down Expand Up @@ -284,7 +284,7 @@ impl Circuit {
if let Some(v0) = q {
let v = graph.add_vertex_with_data(VData {
ty: VType::B,
phase: Rational64::zero(),
phase: Phase::zero(),
qubit: i as i32,
row: last_row + 1,
});
Expand Down Expand Up @@ -436,22 +436,22 @@ impl openqasm::GateWriter for &mut CircuitWriter {
params: &[Value],
regs: &[usize],
) -> Result<(), Self::Error> {
fn param_to_ratio(value: Value) -> num::Rational64 {
fn param_to_phase(value: Value) -> Phase {
if value.a.is_zero() {
num::Rational64::new(*value.b.numer(), *value.b.denom()).mod2()
Rational64::new(*value.b.numer(), *value.b.denom()).into()
} else {
let a = *value.a.numer() as f32 / *value.a.denom() as f32;
let mut p = num::Rational64::approximate_float(a / std::f32::consts::PI)
.unwrap_or(0.into());
p += num::Rational64::new(*value.b.numer(), *value.b.denom());
p.mod2()
let mut r =
Rational64::approximate_float(a / std::f32::consts::PI).unwrap_or(0.into());
r += Rational64::new(*value.b.numer(), *value.b.denom());
Phase::new(r)
}
}

let mut g = Gate::from_qasm_name(name.as_str());
g.qs.extend_from_slice(regs);
if !params.is_empty() {
g.phase = param_to_ratio(params[0]);
g.phase = param_to_phase(params[0]);
}

self.circuit.push(g);
Expand Down
24 changes: 9 additions & 15 deletions quizx/src/decompose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl<G: GraphLike> Decomposer<G> {
pub fn merge(mut ds: Vec<Decomposer<G>>) -> Decomposer<G> {
if let Some(mut d) = ds.pop() {
while let Some(d1) = ds.pop() {
d.scalar = d.scalar + d1.scalar;
d.scalar += d1.scalar;
d.nterms += d1.nterms;
d.stack.extend(d1.stack);
d.done.extend(d1.done);
Expand Down Expand Up @@ -202,13 +202,7 @@ impl<G: GraphLike> Decomposer<G> {
if self.use_cats {
let cat_nodes = Decomposer::cat_ts(&g); //gadget_ts(&g);
//println!("{:?}", gadget_nodes);
let nts = cat_nodes.iter().fold(0, |acc, &x| {
if g.phase(x).denom() == &4 {
acc + 1
} else {
acc
}
});
let nts = cat_nodes.iter().filter(|&&x| g.phase(x).is_t()).count();
if nts > 2 {
// println!("using cat!");
return self.push_cat_decomp(depth + 1, &g, &cat_nodes);
Expand Down Expand Up @@ -266,7 +260,7 @@ impl<G: GraphLike> Decomposer<G> {
let mut t = vec![];

for v in g.vertices() {
if *g.phase(v).denom() == 4 {
if g.phase(v).is_t() {
t.push(v);
}
if t.len() == 6 {
Expand All @@ -279,7 +273,7 @@ impl<G: GraphLike> Decomposer<G> {

/// Pick <= 6 T gates from the given graph, chosen at random
pub fn random_ts(g: &G, rng: &mut impl Rng) -> Vec<V> {
let mut all_t: Vec<_> = g.vertices().filter(|&v| *g.phase(v).denom() == 4).collect();
let mut all_t: Vec<_> = g.vertices().filter(|&v| g.phase(v).is_t()).collect();
let mut t = vec![];

while t.len() < 6 && !all_t.is_empty() {
Expand All @@ -294,14 +288,14 @@ impl<G: GraphLike> Decomposer<G> {
/// The fist vertex in the result is the Clifford spider
pub fn cat_ts(g: &G) -> Vec<V> {
// the graph g is supposed to be completely simplified
let prefered_order = [4, 6, 5, 3];
let preferred_order = [4, 6, 5, 3];
let mut res = vec![];
let mut index = None;
for v in g.vertices() {
if g.phase(v).denom() == &1 {
if g.phase(v).is_pauli() {
let mut neigh = g.neighbor_vec(v);
if neigh.len() <= 6 {
if let Some(this_ind) = prefered_order.iter().position(|&r| r == neigh.len()) {
if let Some(this_ind) = preferred_order.iter().position(|&r| r == neigh.len()) {
match index {
Some(ind) if this_ind < ind => {
res = vec![v];
Expand Down Expand Up @@ -421,7 +415,7 @@ impl<G: GraphLike> Decomposer<G> {
// verts[0] is a 0- or pi-spider, linked to all and only to vs in verts[1..] which are T-spiders
let mut g = g.clone(); // that is annoying ...
let mut verts = Vec::from(verts);
if g.phase(verts[0]).numer() == &1 {
if g.phase(verts[0]).is_pauli() {
g.set_phase(verts[0], Rational64::new(0, 1));
let mut neigh = g.neighbor_vec(verts[1]);
neigh.retain(|&x| x != verts[0]);
Expand All @@ -430,7 +424,7 @@ impl<G: GraphLike> Decomposer<G> {
}
let tmp = g.phase(verts[1]);
*g.scalar_mut() *= ScalarN::from_phase(tmp);
g.set_phase(verts[1], g.phase(verts[1]) * Rational64::new(-1, 1));
g.set_phase(verts[1], g.phase(verts[1]) * -1);
}
if [3, 5].contains(&verts[1..].len()) {
let w = g.add_vertex(VType::Z);
Expand Down
Loading
Loading