Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
155 changes: 106 additions & 49 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 @@ -9,42 +10,50 @@ use tket::extension::{TKET1_EXTENSION_ID, TKET_EXTENSION_ID};

use hugr::algorithms::ComposablePass;
use hugr::{Hugr, HugrView};
use rstest::{fixture, rstest};
use rstest::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();
let reader = BufReader::new(reader);
Hugr::load(reader, None).unwrap()
enum HugrFileType {
Original,
Flat,
Optimized,
}

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

#[fixture]
fn guppy_false_branch() -> Hugr {
load_guppy_circuit("false_branch")
fn load_guppy_circuit(name: &str, file_type: HugrFileType) -> std::io::Result<Hugr> {
let suffix = match file_type {
HugrFileType::Original => "",
HugrFileType::Flat => ".flat",
HugrFileType::Optimized => ".opt",
};
let file = Path::new(GUPPY_EXAMPLES_DIR).join(format!("{name}/{name}{suffix}.hugr"));
let reader = fs::File::open(file)?;
let reader = BufReader::new(reader);
Ok(Hugr::load(reader, None).unwrap())
}

#[fixture]
fn guppy_nested() -> Hugr {
load_guppy_circuit("nested")
}
fn run_pytket(h: &mut Hugr) {
let circ = Circuit::new(h);
let mut encoded =
EncodedCircuit::new(&circ, EncodeOptions::new().with_subcircuits(true)).unwrap();

#[fixture]
fn guppy_ranges() -> Hugr {
load_guppy_circuit("ranges")
}
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();
});

#[fixture]
fn guppy_simple_cx() -> Hugr {
load_guppy_circuit("simple_cx")
encoded.reassemble_inplace(circ.into_hugr(), None).unwrap();
}

fn count_gates(h: &impl HugrView) -> HashMap<SmolStr, usize> {
Expand All @@ -63,34 +72,82 @@ 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)
])]
#[case::simple_cx(guppy_simple_cx(), [
("tket.quantum.QAlloc", 2), ("tket.quantum.CX", 2), ("tket.quantum.MeasureFree", 2)
])]
#[should_panic = "xfail"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note this homebrew xfail mechanism, which allows the test to fail but only if the gate counts are exactly as expected in the test header.

Could do the same to flatten_guppy too?

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

#[case::angles("angles", Some(vec![
("tket.quantum.Rz", 2), ("tket.quantum.MeasureFree", 1), ("tket.quantum.H", 2), ("tket.quantum.QAlloc", 1)
]))]
#[should_panic = "xfail"]
#[case::simple_cx("simple_cx", Some(vec![
("tket.quantum.QAlloc", 2), ("tket.quantum.MeasureFree", 2),
]))]
#[should_panic = "xfail"]
#[case::nested("nested", Some(vec![
("tket.quantum.CZ", 6), ("tket.quantum.QAlloc", 3), ("tket.quantum.MeasureFree", 3), ("tket.quantum.H", 6)
]))]
#[should_panic = "xfail"]
#[case::ranges("ranges", Some(vec![
("tket.quantum.H", 8), ("tket.quantum.MeasureFree", 4), ("tket.quantum.QAlloc", 4), ("tket.quantum.CX", 6)
]))]
#[should_panic = "xfail"]
#[case::false_branch("false_branch", Some(vec![
("TKET1.tk1op", 2), ("tket.quantum.QAlloc", 1), ("tket.quantum.MeasureFree", 1)
]))]
#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
fn optimise_guppy<'a>(
#[case] mut hugr: Hugr,
#[case] before: impl IntoIterator<Item = (&'a str, usize)>,
) {
fn optimise_guppy_pytket(#[case] name: &str, #[case] xfail: Option<Vec<(&str, usize)>>) {
let mut hugr = load_guppy_circuit(name, HugrFileType::Flat)
.unwrap_or_else(|_| load_guppy_circuit(name, HugrFileType::Original).unwrap());
run_pytket(&mut hugr);
let should_xfail = xfail.is_some();
let expected_counts = match xfail {
Some(counts) => counts.into_iter().map(|(k, v)| (k.into(), v)).collect(),
None => count_gates(&load_guppy_circuit(name, HugrFileType::Optimized).unwrap()),
};
assert_eq!(count_gates(&hugr), expected_counts);
if should_xfail {
panic!("xfail");
}
}

#[rstest]
#[case::angles("angles")]
#[should_panic]
#[case::nested("nested")]
#[should_panic]
#[case::ranges("ranges")]
#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
fn flatten_guppy(#[case] name: &str) {
let mut hugr = load_guppy_circuit(name, HugrFileType::Original).unwrap();
NormalizeGuppy::default().run(&mut hugr).unwrap();
let before = before.into_iter().map(|(k, v)| (k.into(), v)).collect();
assert_eq!(count_gates(&hugr), before);
let target = load_guppy_circuit(name, HugrFileType::Flat).unwrap();
assert_eq!(count_gates(&hugr), count_gates(&target));
}

/// Check that each example optimizes to the full extent given by the .opt (and .flat) .hugr files.
#[rstest]
#[case::angles("angles")]
#[case::false_branch("false_branch")]
#[case::simple_cx("simple_cx")]
#[case::nested("nested")]
#[case::ranges("ranges")]
#[should_panic] // This does not yet pass for any case!
fn optimise_guppy(#[case] name: &str) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To some extent this test is redundant, it's a kind of composition of the other two, but I think it's good to have as a summary (the other two are merely diagnostic really)

let mut hugr = load_guppy_circuit(name, HugrFileType::Original).unwrap();
let flat = count_gates(
load_guppy_circuit(name, HugrFileType::Flat)
.ok()
.as_ref()
.unwrap_or(&hugr),
);
let opt = count_gates(&load_guppy_circuit(name, HugrFileType::Optimized).unwrap());

NormalizeGuppy::default().run(&mut hugr).unwrap();
assert_eq!(count_gates(&hugr), flat);

run_pytket(&mut hugr);

// 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.
assert_eq!(count_gates(&hugr), opt);

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