-
Notifications
You must be signed in to change notification settings - Fork 11
test: run pytket on guppy_opt tests, measure (very limited) success #1250
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
Changes from all commits
68c22b4
b9b004b
a3902c6
1dfadbb
40988dc
c0cca11
bc57a69
50bdfe2
0ebc1b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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; | ||
|
|
@@ -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> { | ||
|
|
@@ -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"] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.