Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions tket-qsystem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ cool_asserts.workspace = true
petgraph.workspace = true
rstest.workspace = true
serde_json.workspace = true
tket1-passes = { path = "../tket1-passes" }
rayon.workspace = true

[lints]
workspace = true
104 changes: 81 additions & 23 deletions tket-qsystem/tests/guppy_opt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Tests optimizing Guppy-generated programs.

use rayon::iter::ParallelIterator;
use smol_str::SmolStr;
use std::collections::HashMap;
use std::fs;
Expand All @@ -11,40 +12,62 @@ use hugr::algorithms::ComposablePass;
use hugr::{Hugr, HugrView};
use rstest::{fixture, rstest};
use tket::passes::NormalizeGuppy;
use tket::serialize::pytket::{EncodeOptions, EncodedCircuit};
use tket::Circuit;

use tket1_passes::Tket1Circuit;
use tket_qsystem::QSystemPass;

const GUPPY_EXAMPLES_DIR: &str = "../test_files/guppy_optimization";

fn load_guppy_circuit(name: &str) -> Hugr {
let file = Path::new(GUPPY_EXAMPLES_DIR).join(format!("{name}/{name}.hugr"));
let reader = fs::File::open(file).unwrap();
fn load_guppy_circuit(path: &str) -> std::io::Result<Hugr> {
let file = Path::new(GUPPY_EXAMPLES_DIR).join(format!("{path}.hugr"));
let reader = fs::File::open(file)?;
let reader = BufReader::new(reader);
Hugr::load(reader, None).unwrap()
Ok(Hugr::load(reader, None).unwrap())
}

#[fixture]
fn guppy_angles() -> Hugr {
load_guppy_circuit("angles")
load_guppy_circuit("angles/angles").unwrap()
}

#[fixture]
fn guppy_false_branch() -> Hugr {
load_guppy_circuit("false_branch")
load_guppy_circuit("false_branch/false_branch").unwrap()
}

#[fixture]
fn guppy_nested() -> Hugr {
load_guppy_circuit("nested")
load_guppy_circuit("nested/nested").unwrap()
}

#[fixture]
fn guppy_ranges() -> Hugr {
load_guppy_circuit("ranges")
load_guppy_circuit("ranges/ranges").unwrap()
}

#[fixture]
fn guppy_simple_cx() -> Hugr {
load_guppy_circuit("simple_cx")
load_guppy_circuit("simple_cx/simple_cx").unwrap()
}

fn run_pytket(h: &mut Hugr) {
let circ = Circuit::new(h);
let mut encoded =
EncodedCircuit::new(&circ, EncodeOptions::new().with_subcircuits(true)).unwrap();

encoded
.par_iter_mut()
.for_each(|(_region, serial_circuit)| {
let mut circuit_ptr = Tket1Circuit::from_serial_circuit(serial_circuit).unwrap();
circuit_ptr
.clifford_simp(tket_json_rs::OpType::CX, true)
.unwrap();
*serial_circuit = circuit_ptr.to_serial_circuit().unwrap();
});

encoded.reassemble_inplace(circ.into_hugr(), None).unwrap();
}

fn count_gates(h: &impl HugrView) -> HashMap<SmolStr, usize> {
Expand All @@ -63,37 +86,72 @@ fn count_gates(h: &impl HugrView) -> HashMap<SmolStr, usize> {
///
/// This test is intended to check the current status of the Guppy optimization passes.
///

#[rstest]
#[case::angles(guppy_angles(), [
("tket.quantum.H", 2), ("tket.quantum.QAlloc", 1), ("tket.quantum.Rz", 2), ("tket.quantum.MeasureFree", 1)
])]
#[case::false_branch(guppy_false_branch(), [
("tket.quantum.H", 2), ("tket.quantum.QAlloc", 1), ("tket.quantum.MeasureFree", 1)
])]
#[case::nested(guppy_nested(), [
("tket.quantum.CZ", 1), ("tket.quantum.H", 2), ("tket.quantum.QAlloc", 3), ("tket.quantum.MeasureFree", 3)
])]
#[case::ranges(guppy_ranges(), [
("tket.quantum.QAlloc", 4), ("tket.quantum.MeasureFree", 4), ("tket.quantum.H", 2), ("tket.quantum.CX", 2)
], [
("TKET1.tk1op", 1), ("tket.quantum.QAlloc", 1), ("tket.quantum.H", 1), ("tket.quantum.MeasureFree", 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't looked at what's going on here with this TKET1 op

])]
#[case::simple_cx(guppy_simple_cx(), [
("tket.quantum.QAlloc", 2), ("tket.quantum.CX", 2), ("tket.quantum.MeasureFree", 2)
], [
("tket.quantum.MeasureFree", 2), ("tket.quantum.QAlloc", 2)
])]
#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
fn optimise_guppy<'a>(
fn optimise_guppy(
#[case] mut hugr: Hugr,
#[case] before: impl IntoIterator<Item = (&'a str, usize)>,
#[case] before: impl IntoIterator<Item = (impl Into<SmolStr>, usize)>,
#[case] after: impl IntoIterator<Item = (impl Into<SmolStr>, usize)>,
) {
NormalizeGuppy::default().run(&mut hugr).unwrap();
let before = before.into_iter().map(|(k, v)| (k.into(), v)).collect();
assert_eq!(count_gates(&hugr), before);

// TODO: Run pytket passes here, and check that the circuit is as optimized as possible at this point.
//
// Most example circuits optimize to identity functions, so it may be possible to check for that.
run_pytket(&mut hugr);

let after = after.into_iter().map(|(k, v)| (k.into(), v)).collect();
assert_eq!(count_gates(&hugr), after);

// Lower to QSystem. This may blow up the HUGR size.
QSystemPass::default().run(&mut hugr).unwrap();

hugr.validate().unwrap_or_else(|e| panic!("{e}"));
}

/// Checks that pytket does nothing for these examples.
/// We include gate counts for after the NormalizeGuppy step
/// as our flattening is not sufficient to match the .flat.hugr
#[rstest]
#[case::angles(guppy_angles(), [
("tket.quantum.H", 2), ("tket.quantum.QAlloc", 1), ("tket.quantum.Rz", 2), ("tket.quantum.MeasureFree", 1)
])]
#[case::nested(guppy_nested(), [
("tket.quantum.CZ", 1), ("tket.quantum.H", 2), ("tket.quantum.QAlloc", 3), ("tket.quantum.MeasureFree", 3)
])]
#[case::ranges(guppy_ranges(), [
("tket.quantum.QAlloc", 4), ("tket.quantum.MeasureFree", 4), ("tket.quantum.H", 2), ("tket.quantum.CX", 2)
])]
#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
fn no_optimise_guppy<'a>(
#[case] hugr: Hugr,
#[case] before_after: impl IntoIterator<Item = (&'a str, usize)> + Clone,
) {
optimise_guppy(hugr, before_after.clone(), before_after);
}

/// Check that each example optimizes to the full extent given by the .opt (and .flat) .hugr files.
#[should_panic] /// This does not yet pass for any case!
#[rstest]
#[case::angles("angles")]
#[case::false_branch("false_branch")]
#[case::simple_cx("simple_cx")]
#[case::nested("nested")]
#[case::ranges("ranges")]
fn optimise_guppy_full(#[case] name: &str) {
let hugr = load_guppy_circuit(&format!("{name}/{name}")).unwrap();
let flat = load_guppy_circuit(&format!("{name}/{name}.flat")).unwrap_or(hugr.clone());
let opt = load_guppy_circuit(&format!("{name}/{name}.opt")).unwrap();

optimise_guppy(hugr, count_gates(&flat), count_gates(&opt))
}
Loading