Skip to content

fix: Multiple fixes to the pytket encoder#1566

Open
aborgna-q wants to merge 10 commits into
mainfrom
ab/pytket-pass-bug-1514
Open

fix: Multiple fixes to the pytket encoder#1566
aborgna-q wants to merge 10 commits into
mainfrom
ab/pytket-pass-bug-1514

Conversation

@aborgna-q
Copy link
Copy Markdown
Collaborator

@aborgna-q aborgna-q commented May 5, 2026

Fixes all issues reported in #1514, as well as ones detected in #1558 and others seen when processing the guppy-exports test set.
(Best viewed as separate commits)

  • Fixes ClExpr encoding so expression inputs exclude output-only bits. 2bbe580

  • Emits Hugr barriers as pytket Barriers only when every barrier input/output type is pytket-encodable. 9bb239b

  • Allows the decoder to reconstruct tuple-typed wires from individually tracked pytket values. 89e1b34

  • Fixes standalone decoding dropping target signature info when reconstructing parameter inputs. 40ebba9

  • Avoids double-freeing qubits when opaque subgraph outputs are consumed near the decoded region output. 9ac277d

  • Uses the patched tket-json-rs handling for pytket commands with "args": null. 47bc7a8

  • Treats Future<T> as unsupported by pytket encoding, instead of unwrapping known types. 2f823ff

  • Fixes translation of LoadConstants for values that cannot be statically loaded by the encoder (e.g. with external definitions). 958c9e3

  • Makes the region traversal in reassemble_inplace deterministic. Fixes [Bug]: Invalid Hugr during RemoveBarriers tket pass on simple guppy program #1577. d466e94

Ai usage: Used codex to create repro cases and find the bug locations.

@aborgna-q aborgna-q requested a review from cqc-alec May 5, 2026 16:28
@aborgna-q aborgna-q requested a review from a team as a code owner May 5, 2026 16:28
@aborgna-q aborgna-q linked an issue May 5, 2026 that may be closed by this pull request
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 71.78218% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.09%. Comparing base (394a610) to head (524150a).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
tket/src/serialize/pytket/decoder/wires.rs 63.15% 24 Missing and 4 partials ⚠️
tket/src/serialize/pytket/decoder.rs 69.44% 18 Missing and 4 partials ⚠️
tket/src/serialize/pytket/encoder.rs 79.16% 4 Missing and 1 partial ⚠️
tket-qsystem/src/pytket/qsystem.rs 0.00% 1 Missing ⚠️
tket/src/serialize/pytket/circuit.rs 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1566      +/-   ##
==========================================
+ Coverage   83.81%   84.09%   +0.27%     
==========================================
  Files         188      188              
  Lines       29062    29283     +221     
  Branches    27936    28157     +221     
==========================================
+ Hits        24359    24625     +266     
+ Misses       3541     3479      -62     
- Partials     1162     1179      +17     
Flag Coverage Δ
python 92.39% <ø> (ø)
qis-compiler 91.66% <ø> (ø)
rust 83.76% <71.78%> (+0.28%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator

@cqc-alec cqc-alec left a comment

Choose a reason for hiding this comment

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

The changes look reasonable; however, when I tried the procedure described in #1514 with this branch I encountered some more errors.

Specifically:

  1. In the guppylang repo (main branch), run just export-integration-tests.

  2. On this branch of tket2, run:

import os
from hugr import Hugr
from hugr.passes.scope import GlobalScope
from pytket.passes import FullPeepholeOptimise
from tket.passes import PytketHugrPass

p = PytketHugrPass(FullPeepholeOptimise()).with_scope(GlobalScope.PRESERVE_ENTRYPOINT)

hugr_dir = os.path.join("/home", "alec", "r", "guppylang", "guppy-exports") # edit as appropriate
for hugr_file in os.listdir(hugr_dir):
    hugr_path = os.path.join(hugr_dir, hugr_file)
    with open(hugr_path, "rb") as f:
        h = Hugr.from_bytes(f.read())
    try:
        h1 = p(h)
    except:
        print(f"*** Failed for {hugr_file}")

Running this script gives:

thread '<unnamed>' (390845) panicked at tket/src/serialize/pytket/decoder.rs:485:18:
called `Result::unwrap()` on an `Err` value: OperationWiring { op: ExtensionOp(ExtensionOp { def: OpDef { extension: IdentList("tket.quantum"), extension_ref: (Weak), name: "QFree", description: "QFree", misc: {"commutation": Array []}, signature_func: PolyFuncTypeBase { params: [], body: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }, lower_funcs: [], constant_folder: None }, args: [], signature: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }), error: NoCopyLinear { typ: TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear), src: Node(11), src_offset: Port(Outgoing, 0) } }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
*** Failed for tests.integration.test_inout-test_move_back_branch.hugr

thread '<unnamed>' (390845) panicked at tket/src/serialize/pytket/decoder.rs:485:18:
called `Result::unwrap()` on an `Err` value: OperationWiring { op: ExtensionOp(ExtensionOp { def: OpDef { extension: IdentList("tket.quantum"), extension_ref: (Weak), name: "QFree", description: "QFree", misc: {"commutation": Array []}, signature_func: PolyFuncTypeBase { params: [], body: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }, lower_funcs: [], constant_folder: None }, args: [], signature: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }), error: NoCopyLinear { typ: TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear), src: Node(18), src_offset: Port(Outgoing, 0) } }
*** Failed for tests.integration.test_linear-test_while_move_back.hugr
*** Failed for tests.integration.test_qsystem-test_measure_leaked.hugr
*** Failed for tests.integration.test_qsystem-test_lazy_measure_array.hugr
*** Failed for tests.integration.test_quantum-test_barrier.hugr

thread '<unnamed>' (390845) panicked at tket/src/serialize/pytket/decoder.rs:485:18:
called `Result::unwrap()` on an `Err` value: OperationWiring { op: ExtensionOp(ExtensionOp { def: OpDef { extension: IdentList("tket.quantum"), extension_ref: (Weak), name: "QFree", description: "QFree", misc: {"commutation": Array []}, signature_func: PolyFuncTypeBase { params: [], body: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }, lower_funcs: [], constant_folder: None }, args: [], signature: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }), error: NoCopyLinear { typ: TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear), src: Node(85), src_offset: Port(Outgoing, 0) } }
*** Failed for tests.integration.test_inout-test_move_back.hugr

thread '<unnamed>' (390845) panicked at tket/src/serialize/pytket/decoder.rs:485:18:
called `Result::unwrap()` on an `Err` value: OperationWiring { op: ExtensionOp(ExtensionOp { def: OpDef { extension: IdentList("tket.quantum"), extension_ref: (Weak), name: "QFree", description: "QFree", misc: {"commutation": Array []}, signature_func: PolyFuncTypeBase { params: [], body: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }, lower_funcs: [], constant_folder: None }, args: [], signature: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }), error: NoCopyLinear { typ: TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear), src: Node(47), src_offset: Port(Outgoing, 0) } }
*** Failed for tests.integration.test_linear-test_struct_reassign2.hugr
*** Failed for tests.integration.test_extern-test_extern_float.hugr

thread '<unnamed>' (390845) panicked at tket/src/serialize/pytket/decoder.rs:485:18:
called `Result::unwrap()` on an `Err` value: OperationWiring { op: ExtensionOp(ExtensionOp { def: OpDef { extension: IdentList("tket.quantum"), extension_ref: (Weak), name: "QFree", description: "QFree", misc: {"commutation": Array []}, signature_func: PolyFuncTypeBase { params: [], body: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }, lower_funcs: [], constant_folder: None }, args: [], signature: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }), error: NoCopyLinear { typ: TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear), src: Node(191), src_offset: Port(Outgoing, 0) } }
*** Failed for tests.integration.test_tuples-test_control_flow2.hugr
*** Failed for tests.integration.std.test_futures-test_read.hugr
*** Failed for tests.integration.test_qsystem-test_lazy_measure_conditional.hugr

thread '<unnamed>' (390845) panicked at tket/src/serialize/pytket/decoder.rs:485:18:
called `Result::unwrap()` on an `Err` value: OperationWiring { op: ExtensionOp(ExtensionOp { def: OpDef { extension: IdentList("tket.quantum"), extension_ref: (Weak), name: "QFree", description: "QFree", misc: {"commutation": Array []}, signature_func: PolyFuncTypeBase { params: [], body: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }, lower_funcs: [], constant_folder: None }, args: [], signature: FuncTypeBase { input: TypeRowBase { types: [TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear)] }, output: TypeRowBase { types: [] } } }), error: NoCopyLinear { typ: TypeBase(Extension(CustomType { extension: IdentList("prelude"), extension_ref: (Weak), id: "qubit", args: [], bound: Linear }), Linear), src: Node(51), src_offset: Port(Outgoing, 0) } }
*** Failed for tests.integration.test_comprehension-test_linear_discard_struct.hugr

Some of these decoding errors seem to be for cases that succeeded previously (though I haven't double-checked this and there may have been changes in guppylang).

@aborgna-q
Copy link
Copy Markdown
Collaborator Author

Ok, there were more fixes needed -.-'

Fixed them all in separate per-bug commits, and made a release of tket-json-rs to fix the JSON decoding error (Quantinuum/tket-json-rs#185).

I'll update the PR description

@aborgna-q aborgna-q requested a review from cqc-alec May 7, 2026 15:10
@aborgna-q aborgna-q changed the title fix: Minor fixes to the pytket encoder fix: Multiple fixes to the pytket encoder May 7, 2026
@aborgna-q aborgna-q added this to the tket-rs v0.18.x milestone May 7, 2026
@cqc-alec
Copy link
Copy Markdown
Collaborator

cqc-alec commented May 7, 2026

Thanks! This solves almost all of the issues. I still see two errors, which look like the same thing, coming from the files tests.integration.test_poly_py312-test_copy_and_drop_bound.hugr and tests.integration.test_poly_py312-test_copy_bound.hugr:

Traceback (most recent call last):
  File "/home/alec/play/tket2/1514.py", line 28, in <module>
    test("tests.integration.test_poly_py312-test_copy_bound.hugr")
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alec/play/tket2/1514.py", line 15, in test
    h1 = p(h)
  File "/home/alec/r/tket2/.venv/lib/python3.14/site-packages/hugr/passes/composable.py", line 26, in __call__
    return self.run(hugr, inplace=inplace).hugr
           ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alec/r/tket2/tket-py/tket/passes/__init__.py", line 58, in run
    return implement_pass_run(
        self,
    ...<2 lines>...
        copy_call=lambda h: self._run_pytket_pass_on_hugr(h, inplace),
    )
  File "/home/alec/r/tket2/.venv/lib/python3.14/site-packages/hugr/passes/composable.py", line 81, in implement_pass_run
    pass_result = copy_call(hugr)
  File "/home/alec/r/tket2/tket-py/tket/passes/__init__.py", line 62, in <lambda>
    copy_call=lambda h: self._run_pytket_pass_on_hugr(h, inplace),
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/home/alec/r/tket2/tket-py/tket/passes/__init__.py", line 78, in _run_pytket_pass_on_hugr
    _passes.tket1_pass(tk_program._inner, pass_json, scope=self._scope)
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tket.PyTK1DecodeError: Could not find a wire with type [bool] that contains pytket bit arguments [c[1], c[0], c[2], c[3]]. While decoding a pytket Barrier

Any ideas on this? Otherwise we can merge as-is and leave this for later.

@aborgna-q
Copy link
Copy Markdown
Collaborator Author

I haven't been able to reproduce that last issue :/

I added a small PR swapping out a HashMap with an IndexMap to fix #1577.

I say let's merge this and fill in a new issue if we can reproduce that last error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Invalid Hugr during RemoveBarriers tket pass on simple guppy program [Bug]: Errors from PytketHugrPass applied to guppy-generated hugrs

2 participants