feat: Allow running arbitrary serializable pytket passes on hugrs#1266
feat: Allow running arbitrary serializable pytket passes on hugrs#1266
Conversation
| /// JSON encoding of the clifford simp pytket pass. | ||
| const CLIFFORD_SIMP_STR: &str = r#"{"StandardPass": {"allow_swaps": true, "name": "CliffordSimp", "target_2qb_gate": "CX"}, "pass_class": "StandardPass"}"#; |
There was a problem hiding this comment.
We may want to add the pass schema to tket_json_rs and use that here instead (with some helpers for common passes).
All the tket1-passes code is private, so that can be done later without breaking the API
There was a problem hiding this comment.
Duplicating strings that I'd rather not see at all in both internal tests and external tests is a bit of a PITA, indeed. (And these strings are the same as those built, much more nicely, in tket-py, right?)
We wouldn't want to publish them from tket1-passes (say) so that we can use them with this repo as a temporary measure, because they'd have to be pub in some crate? (Given the right way to make these strings is currently only available in python, your longer-term solution of tket_json_rs sounds good.)
There was a problem hiding this comment.
See the other comment about keeping the unsafe bindings crate small.
If we're defining builders for serialized passes we should put that in tket-json-rs instead.
I added an issue Quantinuum/tket-json-rs#159
There was a problem hiding this comment.
Yah OK, maybe there is no better interim solution.
There was a problem hiding this comment.
tket-c-api still exposes the specific pass bindings, but we don't need to use them here.
Dropping them from this file lets us reduce the unsafe surface.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1266 +/- ##
==========================================
- Coverage 79.46% 79.44% -0.03%
==========================================
Files 160 160
Lines 20378 20381 +3
Branches 19446 19424 -22
==========================================
- Hits 16194 16192 -2
- Misses 3201 3205 +4
- Partials 983 984 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| """Split a circuit into chunks of at most `max_chunk_size` gates.""" | ||
|
|
||
| def clifford_simp( | ||
| def tket1_pass( |
There was a problem hiding this comment.
Would the return type of tket1_pass eventaully be Hugr?
There was a problem hiding this comment.
We'll always return a rust-backed Hugr here, so multiple calls can be chained together.
acl-cqc
left a comment
There was a problem hiding this comment.
Looks good to me, but before I approve, I am confused about this business about tket-c-api / conan "configuration"...?
| let py = circ.py(); | ||
| encoded_circ | ||
| .par_iter_mut() | ||
| .try_for_each(|(_, circ)| -> Result<(), tket1_passes::PassError> { |
There was a problem hiding this comment.
How about moving this parallel/loop, possibly the whole try_with_circ, into tket1-passes passing a closure Fn(&mut Tket1Circuit) and probably that qsystem_decoder_config()
There was a problem hiding this comment.
Into tket1-passes? That crate is just providing bindings and constraining the unsafe surface.
It doesn't know anything about EncodedCircuit (or even the tket crate), I'm not sure we want to grow the library more than necessary.
There was a problem hiding this comment.
Ah OK. Hadn't realized quite how narrow tket1-passes is (i.e. that it didn't even reference tket2).
Not for this PR, but I have wonder if tket1-passes even belongs in the tket2 repo TBH. What does it provide - rust wrappers around serialized tket1 json? That sounds like tket-json-rs, no?
There was a problem hiding this comment.
I nonetheless wonder whether there is somewhere we could expose a rust function that does this circuit-encoding, run pass everywhere, decode trick. I guess it would have to be the main tket(2) crate, so that it could be used both here and by tket-qsystem (tests)...I admit the latter are only tests but it's always good for the test to exercise more of the actual "production" code path!!
There was a problem hiding this comment.
That sounds like tket-json-rs, no?
🤔
Not a bad idea, though it may be annoying to share the custom build + conan config in CI across repos. I'll look into it.
There was a problem hiding this comment.
A rust function that does this circuit-encoding, run pass everywhere, decode trick.
Yeah, EncodedCircuit could have a run_everywhere(self, SerialCircuit -> SerialCircuit) call. I added an issue: #1279
| tk1_circ.squash_phasedx_rz() | ||
| })?; | ||
| encoded_circ | ||
| .reassemble_inplace(circ.hugr_mut(), Some(Arc::new(qsystem_decoder_config()))) |
There was a problem hiding this comment.
should qsystem_decoder_config() instead be a parameter to fn tket1_pass ?
There was a problem hiding this comment.
We don't expose the encoding/decoding config to python. There's no reason to offer anything other than the full set of available extensions here.
Due to ABI compatibility, new extension encoder/decoders can only be linked at the rust level so python code cannot add new ones.
There was a problem hiding this comment.
I guess it's the "qsystem" in qsystem_decoder_config that makes me think this might not always be wanted, e.g. if we were compiling for a non-qsystem...(qsystem_encoder_config above similarly).
There was a problem hiding this comment.
Uhm, true.
We haven't really decided what to do to support all the pytket operations that are not part of our core set (#430 (comment)). Once/if we do that we may want to choose different configs here...
There was a problem hiding this comment.
Is this considered public?
If so I'd add the encoder and/or decoder config as parameters. You don't even need to expose them in python.
If it's not public...then nvm for now; we can add optional extra parameters to the python function later. (Possibly even without making anything qsystem explicitly available, just as a None = default, if we care.)
There was a problem hiding this comment.
Not public, all the rust code in tket-py is used solely for the bindings.
| circ: Circuit, | ||
| *, | ||
| allow_swaps: bool = True, | ||
| target_2qb_gate: str = "CX", |
There was a problem hiding this comment.
There's only CX in TketOp, tk2 is part of qsystem instead.
This is just temporary code waiting for the Pass implementation. I think Callum was going to define an enum for this gate selection there.
There was a problem hiding this comment.
sounds good...ok, but do you want this as public api or hidden/internal/etc. somehow?
There was a problem hiding this comment.
I only made it public here to avoid removing existing functionality (since the pass call was already being exported).
Callum's #1269 will remove this and replace it with a Pass, I just didn't want to drop the function before that's in.
| circ: Circuit, | ||
| *, | ||
| allow_swaps: bool = True, | ||
| target_2qb_gate: str = "CX", |
|
|
||
|
|
||
| def clifford_simp( | ||
| circ: Circuit, |
There was a problem hiding this comment.
I guess the Circuit -> Circuit here might (in future) change to a function returning a ComposeablePass....
There was a problem hiding this comment.
We're dropping this altogether, and defining only the ComposablePass.
There was a problem hiding this comment.
Is that gonna be in another PR, then?
| /// JSON encoding of the clifford simp pytket pass. | ||
| const CLIFFORD_SIMP_STR: &str = r#"{"StandardPass": {"allow_swaps": true, "name": "CliffordSimp", "target_2qb_gate": "CX"}, "pass_class": "StandardPass"}"#; |
There was a problem hiding this comment.
Duplicating strings that I'd rather not see at all in both internal tests and external tests is a bit of a PITA, indeed. (And these strings are the same as those built, much more nicely, in tket-py, right?)
We wouldn't want to publish them from tket1-passes (say) so that we can use them with this repo as a temporary measure, because they'd have to be pub in some crate? (Given the right way to make these strings is currently only available in python, your longer-term solution of tket_json_rs sounds good.)
acl-cqc
left a comment
There was a problem hiding this comment.
Ok, thanks @aborgna-q - this is pretty cool and useful, I think there may be a few rough edges but let's get this in.
| tk1_circ.squash_phasedx_rz() | ||
| })?; | ||
| encoded_circ | ||
| .reassemble_inplace(circ.hugr_mut(), Some(Arc::new(qsystem_decoder_config()))) |
There was a problem hiding this comment.
Is this considered public?
If so I'd add the encoder and/or decoder config as parameters. You don't even need to expose them in python.
If it's not public...then nvm for now; we can add optional extra parameters to the python function later. (Possibly even without making anything qsystem explicitly available, just as a None = default, if we care.)
|
|
||
|
|
||
| def clifford_simp( | ||
| circ: Circuit, |
There was a problem hiding this comment.
Is that gonna be in another PR, then?
| circ: Circuit, | ||
| *, | ||
| allow_swaps: bool = True, | ||
| target_2qb_gate: str = "CX", |
There was a problem hiding this comment.
sounds good...ok, but do you want this as public api or hidden/internal/etc. somehow?
| Parameters: | ||
| :param allow_swaps: Whether to allow implicit wire swaps | ||
| :param target_2qb_gate: Target two-qubit gate (either CX or TK2) | ||
| :param cx_fidelity: Estimated CX gate fidelity, used when `target_2qb_gate` is CX |
There was a problem hiding this comment.
Of course this raises the possibility of dropping target_2qb_gate and having here target_cx_fidelity: float | None = 1.0 where None means to target TK2 and anything else means to target CX....
that does seem a bit non-obvious tho, happy as is.
There was a problem hiding this comment.
Nonemeans to target TK2 and anything else means to target CX
That sounds awful 😅
Again, this is just copying pytket's API for now, but maybe we'll want to have an enum in #1269
|
|
||
| - Two-qubit operations can always be expressed in a minimal form using at most three CXs, or as a single TK2 gate (also known as the KAK or Cartan decomposition). | ||
| - It is generally recommended to squash to TK2 gates, and then use the DecomposeTK2 pass for noise-aware decomposition to other gate sets. | ||
| - For backward compatibility, decompositions to CX are also supported. In this case, `cx_fidelity` can be provided to perform approximate decompositions to CX gates. |
There was a problem hiding this comment.
It does seem a little odd for the default parameters to be those for the backward compatibility mode....
There was a problem hiding this comment.
That's verbatim from pytket docs.
I promise well clean it up in the pass update :)
|
Nice, as soon as this is merged I can finish #1269 |
🤖 I have created a release *beep* *boop* --- ## [0.12.13](tket-py-v0.12.12...tket-py-v0.12.13) (2025-12-10) ### Features * Allow running arbitrary serializable pytket passes on hugrs ([#1266](#1266)) ([24875e0](24875e0)) * do constant folding by default in NormalizeGuppy ([#1309](#1309)) ([3838c49](3838c49)) * implement `ComposablePass` for normalize Guppy pass ([#1286](#1286)) ([d72d84d](d72d84d)) * implement `ComposeablePass` interface for any serializable pytket pass. ([#1269](#1269)) ([71cb1f2](71cb1f2)) ### Bug Fixes * update incomplete API docs for passes module ([#1308](#1308)) ([0276ba2](0276ba2)) * update incorrect version info in notebook ([#1312](#1312)) ([af160ed](af160ed)) * Use qsystem extensions on Tk2Circuit.from_bytes/str ([#1296](#1296)) ([df9f52a](df9f52a)) ### Documentation * add notebook on optimization of Guppy programs ([#1287](#1287)) ([e82f959](e82f959)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: Agustín Borgna <[email protected]>
## 🤖 New release * `tket`: 0.16.0 -> 0.17.0 (✓ API compatible changes) * `tket-qsystem`: 0.22.0 -> 0.23.0 (✓ API compatible changes) <details><summary><i><b>Changelog</b></i></summary><p> ## `tket` <blockquote> ## [0.17.0](tket-v0.16.0...tket-v0.17.0) - 2026-02-02 ### Bug Fixes - *(encoded-circ)* Track unsupported wires between input and output ([#1224](#1224)) - Multiple fixes to the pytket encoder ([#1226](#1226)) - Don't use opgroup in pytket barrier encoding ([#1251](#1251)) - guppy_to_circuit always returns num_operations = 0 ([#1200](#1200)) - *(pytket-decoder)* Avoid QAllocating and immediately freeing qubits ([#1256](#1256)) - Encoding of opaque subgraphs with no associated qubit/bit ([#1295](#1295)) - [**breaking**] Don't rely on command params for pytket barriers ([#1298](#1298)) - Track output qubits in CircuitInfo ([#1304](#1304)) - Wrongly reused qubit IDs in pytket encoding ([#1358](#1358)) ### New Features - Deprecate local find_tuple_unpack rewrite ([#1188](#1188)) - Add CopyableExpressionAST ([#1209](#1209)) - `NormalizeGuppy` pass to simplify generated structure ([#1220](#1220)) - [**breaking**] pytket EncodedCircuit struct for in-place pytket optimisation ([#1211](#1211)) - [**breaking**] Interval is independent of resource IDs and scope position ([#1205](#1205)) - Don't translate usizes to pytket ([#1241](#1241)) - BorrowSquashPass to elide redundant borrow/return ops ([#1159](#1159)) - [**breaking**] Bump hugr to 0.25.0 ([#1325](#1325)) - Remove order edges in NormalizeGuppy pass ([#1326](#1326)) - [**breaking**] Remove deprecated unpack tuple pass ([#1387](#1387)) ### Refactor - Remove contain_qubits, use TypeUnpacker ([#1283](#1283)) - [**breaking**] Replace Subcircuit with SiblingSubgraph ([#1288](#1288)) - *(metadata)* [**breaking**] Migrate all metadata keys onto the new metadata traits ([#1328](#1328)) </blockquote> ## `tket-qsystem` <blockquote> ## [0.23.0](tket-qsystem-v0.22.0...tket-qsystem-v0.23.0) - 2026-02-02 ### Bug Fixes - [**breaking**] Don't rely on command params for pytket barriers ([#1298](#1298)) - Wrongly reused qubit IDs in pytket encoding ([#1358](#1358)) ### New Features - `NormalizeGuppy` pass to simplify generated structure ([#1220](#1220)) - Allow running arbitrary serializable pytket passes on hugrs ([#1266](#1266)) - BorrowSquashPass to elide redundant borrow/return ops ([#1159](#1159)) - [**breaking**] Bump hugr to 0.25.0 ([#1325](#1325)) - Remove order edges in NormalizeGuppy pass ([#1326](#1326)) - hide new public funcs introduced by linearization ([#1333](#1333)) ### Testing - regenerate guppy_opt examples, and count gates ([#1249](#1249)) - run pytket on guppy_opt tests, measure (very limited) success ([#1250](#1250)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/).
Bumps the
tket-c-apidependency to include Quantinuum/tket#2064 and replaces the pass bindings intket1-passeswith a generic method that takes a serialized pass and applies it.I kept the three "clifford_simp"/"two_qubit_squash"/"squash_phasedx_rz" pass functions available in
tket-py.passes, but they now initialize a pytket pass and call the new generic binding.The docs are copied directly from pytket's.
Those python functions will be replaced soon with @CalMacCQ's
PytketPassdefinition, so no need to look to much into them.