-
Notifications
You must be signed in to change notification settings - Fork 11
fix: Encoding of opaque subgraphs with no associated qubit/bit #1295
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
Conversation
| csv = "1.4.0" | ||
| delegate = "0.13.5" | ||
| derive_more = "2.0.1" | ||
| derive_more = "2.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by bump so I don't have to write a workaround for derive_more#454
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reckon I've gone to some massive effort to avoid this in some of my other PR(/attempt)s....great to see that! :)
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1295 +/- ##
==========================================
+ Coverage 79.53% 79.55% +0.02%
==========================================
Files 160 160
Lines 20571 20578 +7
Branches 19605 19612 +7
==========================================
+ Hits 16361 16371 +10
+ Misses 3227 3225 -2
+ Partials 983 982 -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:
|
|
Github seems to be returning This happened once before in an incident, so it may be same this time? Anyways, it's safe to ignore the bot diagnostics. |
Does there ever need to be more than one? If there are no qubit wires between the "multiple subgraphs", that can just be one big subgraph, no? And if there are qubit wires, then the subgraphs to which those wires are input, become barriers? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this "fixes" the UnsupportedSubgraphHasNoRegisters error seen on optimize_flattened_guppy::case_2_angles in my #1270 by converting it to a NoMatchingParameter....
Generally seems like a plan, but I'd at least like to hear if you think (and why) we really need multiple additional-subgraphs. Combining them together seems very much like what we do with barriers ATM (?) and the tricky cases are also (only) the ones that happen with unsupported subgraphs midcircuit, AFAICS.
| return Err(PytketEncodeError::UnsupportedSubgraphHasNoRegisters { | ||
| // We cannot associate this subgraph to any qubit or bit register in the pytket circuit, | ||
| // so we'll store it in the [`AdditionalSubgraph`]s instead when finishing the encoding. | ||
| self.non_emitted_subgraphs.push(AdditionalSubgraph { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be as simple as, just add the nodes to the unique additional-subgraph, perhaps with some assert that the resulting subgraph is convex - obviously if that assert breaks then my supposition is false, but ATM I don't see how it can.
(A qubit wire directly from one unsupported bit to another is OK I guess, as long as there are no supported ops on said wire.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't store the nodes here, but rather a subgraphID that's registered in the subgraph tracker.
Merging them would require adding some union-find thing to the tracker, or doing the backtracking lookahead I mentioned in the PR description.
I don't think it's really worth the effort/complexity for no real upsides.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah ok, put a comment somewhere in case we ever decide reducing #barriers / merging these things becomes important, and leave it
| /// A program with an unsupported subgraph not associated to any qubit or bit. | ||
| /// <https://github.com/CQCL/tket2/issues/1294> | ||
| #[fixture] | ||
| fn circ_unsupported_subgraph_no_registers() -> Circuit { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test looks fine, but you should probably add one which (currently) requires multiple additional-subgraphs...challenge if you can come up with a case that can't be encoded as just one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Modifying this to follow the structure from my comment above.
Co-authored-by: Alan Lawrence <[email protected]>
| /// Subgraphs of the region that could not be encoded as a pytket commands, | ||
| /// and have no qubit/bits in their boundary that could be used to emit an | ||
| /// opaque barrier command in the [`serial_circuit`]. | ||
| pub additional_subgraphs: Vec<AdditionalSubgraph>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does there ever need to be more than one? If there are no qubit wires between the "multiple subgraphs", that can just be one big subgraph, no? And if there are qubit wires, then the subgraphs to which those wires are input, become barriers?
The subgraphs we extract are always convex. We could technically remove that invariant and merge different subgraphs together, but at the end of the day they are just sets of nodes so we don't win anything from doing that (And it complicates the logic of ID tracking).
Combining them together seems very much like what we do with barriers ATM
This is using the exact same behaviour we use for barriers;
when we require the register/param in an edge coming from an unsupported op we do a greedy BFS extracting all connected not-yet-extracted nodes we have seen.
This produces convex subgraphs (due to the toposort traversal of nodes), but may produce multiple subgraphs in some cases.
E.g, if we have
graph LR
subgraph 0 ["(0) Module"]
direction LR
subgraph 1 ["(1) [**FuncDefn: #quot;test#quot;**]"]
direction LR
style 1 stroke:#832561,stroke-width:3px
16["(16) Unsupported 1"]
17["(17) Unsupported 2"]
18["(18) Supported 1"]
19["(19) Supported 2"]
16-->18
17-->19
16-->17
end
end
and we extract nodes in order 16,18,17,19, we'll create two subgraphs where one containing {16,17} would have sufficed.
This doesn't really worsen anything. We'll just have two barriers / two external subgraphs instead of one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, didn't realize that the algorithm only took nodes already found and marked unsupported in the traversal but not yet put in a subgraph. So the alternative algorithm of (1) partitioning nodes into two sets, and (2) trying to build maximal convex subgraphs from the unsupported nodes, would give fewer barriers, including at most one initial subgraph....but ok, this is no big deal.
Might be worth mentioning in a comment that all elements in the Vec here could have been combined into one subgraph in case we ever come back to this (I don't think it's worth raising an issue!).
(That is necessarily true, right? Any unsupported op after a supported one will become a barrier, not part of the additional/initial block...right?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if it comes after then it is connected by a bit/qubit so it will appear as a barrier inline.
| return Err(PytketEncodeError::UnsupportedSubgraphHasNoRegisters { | ||
| // We cannot associate this subgraph to any qubit or bit register in the pytket circuit, | ||
| // so we'll store it in the [`AdditionalSubgraph`]s instead when finishing the encoding. | ||
| self.non_emitted_subgraphs.push(AdditionalSubgraph { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't store the nodes here, but rather a subgraphID that's registered in the subgraph tracker.
Merging them would require adding some union-find thing to the tracker, or doing the backtracking lookahead I mentioned in the PR description.
I don't think it's really worth the effort/complexity for no real upsides.
| /// A program with an unsupported subgraph not associated to any qubit or bit. | ||
| /// <https://github.com/CQCL/tket2/issues/1294> | ||
| #[fixture] | ||
| fn circ_unsupported_subgraph_no_registers() -> Circuit { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Modifying this to follow the structure from my comment above.
46fb309 to
689b222
Compare
acl-cqc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks @aborgna-q, a few one-liner/nits is all I can see :)
| wire_qubits.unwrap().iter().cloned(), | ||
| wire_bits.unwrap().iter().cloned(), | ||
| )?; | ||
| } else if [rotation_type(), float64_type()].contains(ty) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider a static/constant group of these two...I think may be used elsewhere too. (It'd have to be a LazyStatic tho, so probably don't bother if this is the only place)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I got called out for prematurely optimizing this before :P
There's a couple cases in the decoder, I'll replace all with a single lazy lock.
| return Err(PytketEncodeError::UnsupportedSubgraphHasNoRegisters { | ||
| // We cannot associate this subgraph to any qubit or bit register in the pytket circuit, | ||
| // so we'll store it in the [`AdditionalSubgraph`]s instead when finishing the encoding. | ||
| self.non_emitted_subgraphs.push(AdditionalSubgraph { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah ok, put a comment somewhere in case we ever decide reducing #barriers / merging these things becomes important, and leave it
tket/src/serialize/pytket/encoder.rs
Outdated
| } else { | ||
| // If there are registers to which to attach, emit it as a barrier command. | ||
|
|
||
| // Create pytket operation, and add the subcircuit as hugr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think payload is a reference to an external subgraphid here, not a hugr, right?
Fixes #1294.
This PR addresses a problem when emitting subgraphs that cannot be put in a pytket barrier since they were not associated with registers.
Given the following graph:
graph LR subgraph 0 ["(0) Module"] direction LR subgraph 1 ["(1) [**FuncDefn: #quot;test#quot;**]"] direction LR style 1 stroke:#832561,stroke-width:3px 2["(2) Input"] 3["(3) Output"] 16["(16) Unsupported OP"] 17["(17) tket.rotation.from_halfturns_unchecked"] 16--"0:0<br>float64"-->17 17--"0:0<br>rotation"-->3 end endThe encoding of
from_halfturns_uncheckedwould request the unsupported op to be emitted in an opaque subgraph, but we are unable to put that operation alone in a barrier.While #1294 proposes doing a lookahead on the wires and backtracking/marking
from_halfturns_uncheckedas unsupported as well, the solution here is simpler.We just let the
EncodedCircuitmetadata carry multiple subgraphs not linked to barriers, so we register them there.The downside is that now the circuit above is not encodable as a standalone pytket circuit (since the metadata stays on the rust in-memory object), but we can change it in the future if needed.