Skip to content
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

feat: Drop linear bits, improve pytket encoding/decoding #420

Merged
merged 20 commits into from
Jun 25, 2024

Conversation

aborgna-q
Copy link
Collaborator

@aborgna-q aborgna-q commented Jun 21, 2024

Removes the ad-hoc LINEAR_BIT used for decoding pytket circuits, and uses non-linear BOOL_Ts instead.
This now lets us encode guppy circuits with measurements;

module = GuppyModule("test")
module.load(quantum)

@guppy(module)
def my_func(q0: qubit, q1: qubit) -> tuple[bool,]:
    q0 = phased_x(q0, py(math.pi / 2), py(-math.pi / 2))
    q0 = rz(q0, py(math.pi))
    q1 = phased_x(q1, py(math.pi / 2), py(-math.pi / 2))
    q1 = rz(q1, py(math.pi))
    q0, q1 = zz_max(q0, q1)
    _ = measure(q0)
    return (measure(q1),)

circ = guppy_to_circuit(my_func)
print(to_hugr_mermaid(circ))

tk1 = circ.to_tket1()
render_circuit_jupyter(tk1)

circ2 = Tk2Circuit(tk1)
print(to_hugr_mermaid(circ2))

Mermaid diagram (rooted on the DataflowBlock):

graph LR
    subgraph 0 ["(0) Module"]
        direction LR
        subgraph 7 ["(7) FuncDefn"]
            direction LR
            3["(3) Input"]
            3--"0:0<br>qubit"-->8
            3--"1:1<br>qubit"-->8
            6["(6) Output"]
            subgraph 8 ["(8) CFG"]
                direction LR
                subgraph 1 ["(1) DataflowBlock"]
                    direction LR
                    4["(4) Input"]
                    4--"0:0<br>qubit"-->13
                    4--"1:0<br>qubit"-->21
                    5["(5) Output"]
                    9["(9) const:custom:f64(1.5707963267948966)"]
                    9--"0:0<br>float64"-->10
                    10["(10) LoadConstant"]
                    10--"0:1<br>float64"-->13
                    11["(11) const:custom:f64(-1.5707963267948966)"]
                    11--"0:0<br>float64"-->12
                    12["(12) LoadConstant"]
                    12--"0:2<br>float64"-->13
                    13["(13) quantum.tket2.PhasedX"]
                    13--"0:0<br>qubit"-->16
                    14["(14) const:custom:f64(3.141592653589793)"]
                    14--"0:0<br>float64"-->15
                    15["(15) LoadConstant"]
                    15--"0:1<br>float64"-->16
                    16["(16) quantum.tket2.RzF64"]
                    16--"0:0<br>qubit"-->25
                    17["(17) const:custom:f64(1.5707963267948966)"]
                    17--"0:0<br>float64"-->18
                    18["(18) LoadConstant"]
                    18--"0:1<br>float64"-->21
                    19["(19) const:custom:f64(-1.5707963267948966)"]
                    19--"0:0<br>float64"-->20
                    20["(20) LoadConstant"]
                    20--"0:2<br>float64"-->21
                    21["(21) quantum.tket2.PhasedX"]
                    21--"0:0<br>qubit"-->24
                    22["(22) const:custom:f64(3.141592653589793)"]
                    22--"0:0<br>float64"-->23
                    23["(23) LoadConstant"]
                    23--"0:1<br>float64"-->24
                    24["(24) quantum.tket2.RzF64"]
                    24--"0:1<br>qubit"-->25
                    25["(25) quantum.tket2.ZZMax"]
                    25--"0:0<br>qubit"-->26
                    25--"1:1<br>qubit"-->26
                    26["(26) MakeTuple"]
                    26--"0:0<br>[qubit, qubit]"-->27
                    27["(27) UnpackTuple"]
                    27--"0:0<br>qubit"-->28
                    27--"1:0<br>qubit"-->30
                    28["(28) quantum.tket2.Measure"]
                    28--"0:0<br>qubit"-->29
                    29["(29) quantum.tket2.QFree"]
                    30["(30) quantum.tket2.Measure"]
                    30--"0:0<br>qubit"-->31
                    30--"1:0<br>[]+[]"-->32
                    31["(31) quantum.tket2.QFree"]
                    32["(32) MakeTuple"]
                    32--"0:0<br>[[]+[]]"-->33
                    33["(33) UnpackTuple"]
                    33--"0:1<br>[]+[]"-->5
                    34["(34) Tag"]
                    34--"0:0<br>[]"-->5
                end
                1-."0:0".->2
                2["(2) ExitBlock"]
            end
            8--"0:0<br>[]+[]"-->6
        end
    end
Loading

tket1 circuit:
circuit
Re-extracted circuit:

graph LR
    subgraph 0 ["(0) FuncDefn"]
        direction LR
        1["(1) Input"]
        1--"0:0<br>qubit"-->7
        1--"1:0<br>qubit"-->12
        2["(2) Output"]
        3["(3) const:custom:f64(1.5707963267948966)"]
        3--"0:0<br>float64"-->4
        4["(4) LoadConstant"]
        4--"0:1<br>float64"-->7
        5["(5) const:custom:f64(-1.5707963267948966)"]
        5--"0:0<br>float64"-->6
        6["(6) LoadConstant"]
        6--"0:2<br>float64"-->7
        7["(7) quantum.tket2.PhasedX"]
        7--"0:0<br>qubit"-->15
        8["(8) const:custom:f64(1.5707963267948966)"]
        8--"0:0<br>float64"-->9
        9["(9) LoadConstant"]
        9--"0:1<br>float64"-->12
        10["(10) const:custom:f64(-1.5707963267948966)"]
        10--"0:0<br>float64"-->11
        11["(11) LoadConstant"]
        11--"0:2<br>float64"-->12
        12["(12) quantum.tket2.PhasedX"]
        12--"0:0<br>qubit"-->18
        13["(13) const:custom:f64(3.141592653589793)"]
        13--"0:0<br>float64"-->14
        14["(14) LoadConstant"]
        14--"0:1<br>float64"-->15
        15["(15) quantum.tket2.RzF64"]
        15--"0:0<br>qubit"-->19
        16["(16) const:custom:f64(3.141592653589793)"]
        16--"0:0<br>float64"-->17
        17["(17) LoadConstant"]
        17--"0:1<br>float64"-->18
        18["(18) quantum.tket2.RzF64"]
        18--"0:1<br>qubit"-->19
        19["(19) quantum.tket2.ZZMax"]
        19--"0:0<br>qubit"-->21
        19--"1:0<br>qubit"-->20
        20["(20) quantum.tket2.Measure"]
        20--"0:1<br>qubit"-->2
        20--"1:2<br>[]+[]"-->2
        21["(21) quantum.tket2.Measure"]
        21--"0:0<br>qubit"-->2
        21--"1:3<br>[]+[]"-->2
    end
Loading

This required multiple improvements to the encoder/decoder logic, including

  • Tk1Op::Native operations (backed by a Tk2Op) can now have different number of input/output qubit/bits.
  • Added support for tket2 circuits with different input and output signatures.
  • Added support for QAlloc/QFree operations (generated by guppy) by adding extra input/outputs to the circuit.
  • Added support for pytket's implicit permutations, and recalculates the value when encoding a tket2 circuit.
  • Preserve the opgroup value from decoded pytket operations.
  • Improved error reporting.

Closes #379

Copy link

codecov bot commented Jun 21, 2024

Codecov Report

Attention: Patch coverage is 83.56589% with 106 lines in your changes missing coverage. Please review.

Project coverage is 81.26%. Comparing base (5499817) to head (4649e17).

Files Patch % Lines
tket2/src/serialize/pytket/encoder.rs 82.86% 47 Missing and 14 partials ⚠️
tket2/src/serialize/pytket/decoder.rs 79.47% 24 Missing and 7 partials ⚠️
tket2/src/serialize/pytket/op/native.rs 88.63% 2 Missing and 3 partials ⚠️
tket2/src/serialize/pytket/tests.rs 88.23% 4 Missing ⚠️
tket2/src/serialize/pytket.rs 72.72% 0 Missing and 3 partials ⚠️
tket2/src/serialize/pytket/op.rs 94.87% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #420      +/-   ##
==========================================
- Coverage   81.41%   81.26%   -0.16%     
==========================================
  Files          59       59              
  Lines        5423     5838     +415     
  Branches     4938     5349     +411     
==========================================
+ Hits         4415     4744     +329     
- Misses        790      854      +64     
- Partials      218      240      +22     
Flag Coverage Δ
python 97.54% <100.00%> (+0.02%) ⬆️
rust 79.77% <83.41%> (-0.06%) ⬇️

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.

@aborgna-q
Copy link
Collaborator Author

The reduced coverage is mostly related to error paths and functionality that's still unused (e.g. input bools in operations; there's no classically controlled ops in tket2 yet so we cannot test that).

@aborgna-q aborgna-q marked this pull request as ready for review June 21, 2024 14:19
@aborgna-q aborgna-q requested a review from ss2165 June 21, 2024 14:19
@aborgna-q aborgna-q changed the title feat: Support non-linear bits, improve pytket encoding/decoding feat: Drop linear bits, improve pytket encoding/decoding Jun 21, 2024
Copy link
Member

@ss2165 ss2165 left a comment

Choose a reason for hiding this comment

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

much more flexible than i anticipated

main concerns are with use of default "q" and "c" registers

|p: &Vec<circuit_json::Permutation>| -> HashMap<circuit_json::Register, circuit_json::Register> {p.iter().map(|p| (p.0.clone(), p.1.clone())).collect()};
let _permutation_a = get_permutation(&a.implicit_permutation);
let _permutation_b = get_permutation(&b.implicit_permutation);

Copy link
Member

Choose a reason for hiding this comment

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

is there supposed to be an assert here that the permutations are equal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We cannot unsure that anymore, as circuits with different permutations may still be equal.

I removed this bit and added a simple validation instead.

tket2/src/serialize/pytket/decoder.rs Outdated Show resolved Hide resolved
.map(|p| (p.1.clone(), p.0.clone()))
.collect();
for qubit in &serialcirc.qubits {
output_qubits.push(output_to_input.get(qubit).unwrap_or(qubit).clone());
Copy link
Member

Choose a reason for hiding this comment

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

i find this confusing because you're pushing the result of an "output->input" mapping to "output_qubits", I don't think its wrong but can you add some clarifying comments or renames?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added better comments, with an example.

We are storing the input name for each output register, in the order that outputs appear in a circuit.

let wire = self.register_wires.remove(&register).unwrap();
outputs.push(wire);
}
for wire in self.register_wires.into_values() {
Copy link
Member

Choose a reason for hiding this comment

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

how does this case come about? I note it is also not covered by test

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It shouldn't, since ordered_wires always matches the qubit_registers keys.
I replaced the loop with an assert.

}

// Assign the new output wires to some register, if needed.
for (register, wire) in output_registers.into_iter().zip_eq(wires) {
Copy link
Member

Choose a reason for hiding this comment

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

so this reuses existing registers rather than creating a scratch/temp one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We're just following the registers indicated by the serial circuit, we don't need temp registers here since they all have been declared in SerialCircuit::{qu,}bit already.

tket2/src/serialize/pytket/encoder.rs Outdated Show resolved Hide resolved

/// Add a new register unit for a bit wire.
pub fn add_qubit_register(&mut self, unit_id: usize) -> &RegisterUnit {
let reg = RegisterUnit("q".to_string(), vec![self.qubit_to_reg.len() as i64]);
Copy link
Member

Choose a reason for hiding this comment

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

if "q" register isn't already being used this could suddenly create a very large "q" register with mostly unused qubits right? Not too much of a problem for tket but if then lowered to something that creates whole registers would be annoying. Maybe worth maintaining a "next free qubit"?

}
}

// TODO: Look at the circuit outputs to determine the final permutation.
Copy link
Member

Choose a reason for hiding this comment

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

now or later?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a comment. It needs some extra plumbing, so I'll add it as an issue.

Comment on lines 420 to 421
None => RegisterUnit("c".to_string(), vec![self.bit_to_reg.len() as i64]),
};
Copy link
Member

Choose a reason for hiding this comment

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

not covered, also might be a problem for targets that enforce bit register max sizes for the problems mentioned in the qubit version

.zip(circuit_output_order)
.map(|(out, circ_out)| (RegisterHash::from(out), circ_out))
.collect();
// The final permutation is the composition of these two mappings.
Copy link
Member

Choose a reason for hiding this comment

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

the comments are very helpful

@aborgna-q aborgna-q requested a review from a team as a code owner June 25, 2024 09:57
@aborgna-q aborgna-q requested a review from ss2165 June 25, 2024 09:57
Copy link
Member

@ss2165 ss2165 left a comment

Choose a reason for hiding this comment

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

down to just two questions!

let mut last_unit: Option<u16> = None;
for reg in existing {
if reg.0 != register {
continue;
Copy link
Member

Choose a reason for hiding this comment

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

suggests there should be some tests with multiple register names?

if reg.0 != register {
continue;
}
last_unit = Some(reg.1[0] as u16);
Copy link
Member

Choose a reason for hiding this comment

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

does this assume the existing are always returned in order (i.e. q[0] is checked before q[1])?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

🤦Yep

@aborgna-q aborgna-q requested a review from ss2165 June 25, 2024 11:29
Copy link
Member

@ss2165 ss2165 left a comment

Choose a reason for hiding this comment

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

:shipit:

@aborgna-q aborgna-q added this pull request to the merge queue Jun 25, 2024
Merged via the queue into main with commit a6e9e13 Jun 25, 2024
16 checks passed
@aborgna-q aborgna-q deleted the ab/bits-to-pytket branch June 25, 2024 11:52
@hugrbot hugrbot mentioned this pull request Jun 25, 2024
@hugrbot hugrbot mentioned this pull request Aug 1, 2024
github-merge-queue bot pushed a commit that referenced this pull request Aug 1, 2024
## 🤖 New release
* `tket2`: 0.1.0-alpha.2 -> 0.1.0
* `tket2-hseries`: 0.1.0

<details><summary><i><b>Changelog</b></i></summary><p>

## `tket2`
<blockquote>

##
[0.1.0](tket2-v0.1.0-alpha.2...tket2-v0.1.0)
- 2024-08-01

### Bug Fixes
- Single source of truth for circuit names, and better circuit errors
([#390](#390))
- Support non-DFG circuits
([#391](#391))
- Portmatching not matching const edges
([#444](#444))
- Pattern matcher discriminating on opaqueOp description
([#441](#441))
- `extract_dfg` inserting the output node with an invalid child order
([#442](#442))
- Recompile ecc sets after
[#441](#441)
([#484](#484))

### Documentation
- Update tket2-py readme
([#431](#431))
- Better error reporting in portmatching
([#437](#437))
- Improved multi-threading docs for Badger
([#495](#495))

### New Features
- `Circuit::operations` ([#395](#395))
- tuple unpack rewrite ([#406](#406))
- guppy → pytket conversion
([#407](#407))
- Drop linear bits, improve pytket encoding/decoding
([#420](#420))
- *(py)* Allow using `Tk2Op`s in the builder
([#436](#436))
- Initial support for `TailLoop` as circuit parent
([#417](#417))
- Support tuple unpacking with multiple unpacks
([#470](#470))
- Partial tuple unpack ([#475](#475))
- [**breaking**] Compress binary ECCs using zlib
([#498](#498))
- Add timeout options and stats to Badger
([#496](#496))
- Expose advanced Badger timeout options to tket2-py
([#506](#506))

### Refactor
- [**breaking**] Simplify tket1 conversion errors
([#408](#408))
- Cleanup tket1 serialized op structures
([#419](#419))

### Testing
- Add coverage for Badger split circuit multi-threading
([#505](#505))
</blockquote>

## `tket2-hseries`
<blockquote>

##
[0.1.0](https://github.com/CQCL/tket2/releases/tag/tket2-hseries-v0.1.0)
- 2024-08-01

### New Features
- [**breaking**] init tket2-hseries
([#368](#368))
- *(tket2-hseries)* Add `tket2.futures` Hugr extension
([#471](#471))
- Add lazify-measure pass
([#482](#482))
- add results extensions
([#494](#494))
- *(tket2-hseries)* [**breaking**] Add `HSeriesPass`
([#487](#487))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/MarcoIeni/release-plz/).

---------

Co-authored-by: Douglas Wilson <[email protected]>
@hugrbot hugrbot mentioned this pull request Aug 5, 2024
github-merge-queue bot pushed a commit that referenced this pull request Aug 15, 2024
## 🤖 New release
* `tket2`: 0.1.0 -> 0.1.1
* `tket2-hseries`: 0.1.0 -> 0.1.1

<details><summary><i><b>Changelog</b></i></summary><p>

## `tket2`
<blockquote>

##
[0.1.0](tket2-v0.1.0-alpha.2...tket2-v0.1.0)
- 2024-08-01

### Bug Fixes
- Single source of truth for circuit names, and better circuit errors
([#390](#390))
- Support non-DFG circuits
([#391](#391))
- Portmatching not matching const edges
([#444](#444))
- Pattern matcher discriminating on opaqueOp description
([#441](#441))
- `extract_dfg` inserting the output node with an invalid child order
([#442](#442))
- Recompile ecc sets after
[#441](#441)
([#484](#484))

### Documentation
- Update tket2-py readme
([#431](#431))
- Better error reporting in portmatching
([#437](#437))
- Improved multi-threading docs for Badger
([#495](#495))

### New Features
- `Circuit::operations` ([#395](#395))
- tuple unpack rewrite ([#406](#406))
- guppy → pytket conversion
([#407](#407))
- Drop linear bits, improve pytket encoding/decoding
([#420](#420))
- *(py)* Allow using `Tk2Op`s in the builder
([#436](#436))
- Initial support for `TailLoop` as circuit parent
([#417](#417))
- Support tuple unpacking with multiple unpacks
([#470](#470))
- Partial tuple unpack ([#475](#475))
- [**breaking**] Compress binary ECCs using zlib
([#498](#498))
- Add timeout options and stats to Badger
([#496](#496))
- Expose advanced Badger timeout options to tket2-py
([#506](#506))

### Refactor
- [**breaking**] Simplify tket1 conversion errors
([#408](#408))
- Cleanup tket1 serialized op structures
([#419](#419))

### Testing
- Add coverage for Badger split circuit multi-threading
([#505](#505))
</blockquote>

## `tket2-hseries`
<blockquote>

##
[0.1.1](tket2-hseries-v0.1.0...tket2-hseries-v0.1.1)
- 2024-08-15

### New Features
- *(tket2-hseries)* make result operation internals public
([#542](#542))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/MarcoIeni/release-plz/).

---------

Co-authored-by: Seyon Sivarajah <[email protected]>
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.

Convert hugr's BOOLs into tket1-compatible LINEAR_BITs
2 participants