-
Notifications
You must be signed in to change notification settings - Fork 6
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(hugr-py): python hugr builder #1098
Conversation
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, I mainly focused on the test code and left a few comments.
hugr-py/tests/test_hugr_build.py
Outdated
row = [BOOL_T, QB_T] | ||
h = Dfg.endo(row) | ||
a, b = h.inputs() | ||
t = h.make_tuple([a, b], row) |
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.
Needing to specify the types here (and below) is potentially annoying but I understand it may be technically hard to avoid.
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 is annoying - unfortunately to avoid it generically we need to either add an interface for querying ops for their signature, or take guppy's approach of tracking types where possible while building. I prefer the former approach because it's less flaky but it is quite a lot of extra code, and liable to many edge cases for polymorphic/generic ops.
hugr-py/tests/test_hugr_build.py
Outdated
def test_multi_out(): | ||
h = Dfg([INT_T] * 2, [INT_T] * 2) | ||
a, b = h.inputs() | ||
a, b = h.add_op(DIV_OP, [a, b])[:2] |
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.
Could we just write [:]
here (assuming we can't omit the indexing)?
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 know in general how many ports it has, in fact without a stop value an error is raised
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.
annoyingly python is unwilling to just stop an iterator based on how many values it needs to unpack in to - instead it complains that the iterator wants to keep going
NOT_OP = DummyOp( | ||
# TODO get from YAML | ||
sops.CustomOp( | ||
parent=-1, |
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.
Would probably friendlier for users not to need to say parent = -1
; is it avoidable?
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, probably, I just didn't worry about the op interface much yet. We can either generically wrap all serialised ops or change the behaviour of the pydantic models a bit.
hugr-py/tests/test_hugr_build.py
Outdated
def test_add_op(): | ||
h = Dfg.endo([BOOL_T]) | ||
(a,) = h.inputs() | ||
nt = h.add_op(NOT_OP, [a]) |
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.
Did you consider a varargs style (add_op(op, *args)
) so that you could write add_op(NOT_OP, a)
here?
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 clean!
hugr-py/src/hugr/hugr.py
Outdated
class ToPort(Protocol): | ||
def to_port(self) -> "OutPort": ... |
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.
What is the definition of a ToPort
? It's implemented for OutPort
and Node
, but not for Port
nor InPort
.
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.
should really be ToOutPort
, used so you can use a node in place of the single out port of the node (common for single return ops)
hugr-py/src/hugr/hugr.py
Outdated
|
||
|
||
@dataclass() | ||
class Dfg: |
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.
Move this to another file?
hugr-py/src/hugr/hugr.py
Outdated
|
||
|
||
@dataclass() | ||
class Dfg: |
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.
Should we think here about a commands-based DFG construction similar to tket's?
(E.g. dfg.add(CNot(target=a, control=b))
)
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.
see de22b5a...
b25993c
to
5543473
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1098 +/- ##
==========================================
+ Coverage 86.87% 87.04% +0.17%
==========================================
Files 91 93 +2
Lines 18746 19079 +333
Branches 18353 18353
==========================================
+ Hits 16285 16607 +322
- Misses 1611 1622 +11
Partials 850 850
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
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 so far, the API in its current form should be usable by Guppy.
Below are some thoughts:
- Having to specify all types by hand is a bit annoying but should be fine for Guppy since we have figured out all types once we do Hugr generation.
- Still, if I had to use to builder to create a Hugr by hand, I would probably get annoyed by the verbosity (e.g. having to specify all element types of a tuple I want to unpack)
- Guppy won't be able to use the
DFG
pattern, were a subgraph is constructed independently and then inserted into a larger graph since we might want to insert non-local edges. I assume we can still useadd_op
to add a DFG op and then build the contents? Maybe we want anadd_dfg
method onHugr
as well? - How will the builder handle Const-/Function-/State-/CF-edges? This was one of the main pain points of the Guppy Hugr builder and I think this might complicate the API quite a bit.
- Is it the builder's job to ensure that nodes are serialised in the correct order (e.g. input/output are the first children of a DFG)? This becomes relevant if we want to nest graphs by hand and add our own IO nodes.
- Will the builder take care of inserting state edges when creating a non-local edge?
hugr-py/src/hugr/hugr.py
Outdated
_serial_op: T | ||
|
||
def to_serial(self, node: Node, hugr: "Hugr") -> SerialOp: | ||
return SerialOp(root=self._serial_op) # type: ignore |
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.
return SerialOp(root=self._serial_op) # type: ignore | |
return SerialOp(root=self._serial_op.model_copy()) # type: ignore |
NodeData.to_serial
mutates the model by setting the parent, so you need to create a copy every time you serialise
hugr-py/src/hugr/hugr.py
Outdated
def to_port(self) -> "OutPort": | ||
return OutPort(self, 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.
Maybe to_port
should only be allowed if the node only has a single out port?
hugr-py/src/hugr/hugr.py
Outdated
@overload | ||
def __getitem__(self, index: int) -> OutPort: ... | ||
@overload | ||
def __getitem__(self, index: slice) -> Iterable[OutPort]: ... |
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.
Why Iterable[OutPort]
instead of Iterator[OutPort]
, or Generator[OutPort]
?
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.
Is there a good reason why this should be an iterator instead of a list?
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 should be Iterator
yes
I figured since it is backed by a range
it might as well be lazy like range
hugr-py/src/hugr/hugr.py
Outdated
def set_outputs(self, *args: ToPort) -> None: | ||
self._wire_up(self.output_node, args) | ||
|
||
def make_tuple(self, tys: Sequence[Type], *args: ToPort) -> Node: |
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.
How many of these DFG.add_something(...)
methods do we want? I agree they are convenient, but they could also just be commands. Looking at the examples, we could also have DFG.not(...)
and DFG.divmod(...)
, but that would probably go too far?
I'm not sure how we should weigh uniformity vs convenience here?
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 like the use of Commands, will do
If I understand correctly, this test: hugr/hugr-py/tests/test_hugr_build.py Line 225 in 600f18d
|
Ah yes, sorry I must have missed that. That's what I want 👍 |
e431005
to
79fc717
Compare
commit 22ce1b7 Author: Seyon Sivarajah <seyon.sivarajah@quantinuum.com> Date: Fri May 24 15:08:11 2024 +0100 fix paths commit 60407bb Author: Seyon Sivarajah <seyon.sivarajah@quantinuum.com> Date: Fri May 24 14:32:36 2024 +0100 empty commit commit 53fd488 Author: Seyon Sivarajah <seyon.sivarajah@quantinuum.com> Date: Fri May 24 14:23:02 2024 +0100 [REVERTME] add ss/pybuild to CI req
so it can be a feature branch
This reverts commit 6c32c28.
passing run: https://github.com/CQCL/hugr/actions/runs/9289115228/job/25562276103 --------- Co-authored-by: Agustín Borgna <121866228+aborgna-q@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- ## [0.3.0](hugr-py-v0.2.1...hugr-py-v0.3.0) (2024-07-03) ### ⚠ BREAKING CHANGES * * `add_child_op`(`_with_parent`), etc., gone; use `add_child_node`(`_with_parent`) with an (impl Into-)OpType. * `get_nodetype` gone - use `get_optype`. * `NodeType` gone - use `OpType` directly. * Various (Into<)Option<ExtensionSet> params removed from builder methods especially {cfg_,dfg_}builder. * `input_extensions` removed from serialization schema. * the Signature class is gone, but it's not clear how or why you might have been using it... * TailLoop node and associated builder functions now require specifying an ExtensionSet; extension/validate.rs deleted; some changes to Hugrs validated/rejected when the `extension_inference` feature flag is turned on * Type::validate takes extra bool (allow_rowvars); renamed {FunctionType, PolyFuncType}::(validate=>validate_var_len). ### Features * Allow "Row Variables" declared as List<Type> ([#804](#804)) ([3ea4834](3ea4834)) * **hugr-py:** add builders for Conditional and TailLoop ([#1210](#1210)) ([43569a4](43569a4)) * **hugr-py:** add CallIndirect, LoadFunction, Lift, Alias ([#1218](#1218)) ([db09193](db09193)), closes [#1213](#1213) * **hugr-py:** add values and constants ([#1203](#1203)) ([f7ea178](f7ea178)), closes [#1202](#1202) * **hugr-py:** automatically add state order edges for inter-graph edges ([#1165](#1165)) ([5da06e1](5da06e1)) * **hugr-py:** builder for function definition/declaration and call ([#1212](#1212)) ([af062ea](af062ea)) * **hugr-py:** builder ops separate from serialised ops ([#1140](#1140)) ([342eda3](342eda3)) * **hugr-py:** CFG builder ([#1192](#1192)) ([c5ea47f](c5ea47f)), closes [#1188](#1188) * **hugr-py:** define type hierarchy separate from serialized ([#1176](#1176)) ([10f4c42](10f4c42)) * **hugr-py:** only require input type annotations when building ([#1199](#1199)) ([2bb079f](2bb079f)) * **hugr-py:** python hugr builder ([#1098](#1098)) ([23408b5](23408b5)) * **hugr-py:** store children in node weight ([#1160](#1160)) ([1cdaeed](1cdaeed)), closes [#1159](#1159) * **hugr-py:** ToNode interface to treat builders as nodes ([#1193](#1193)) ([1da33e6](1da33e6)) * Validate Extensions using hierarchy, ignore input_extensions, RIP inference ([#1142](#1142)) ([8bec8e9](8bec8e9)) ### Bug Fixes * Add some validation for const nodes ([#1222](#1222)) ([c05edd3](c05edd3)) * **hugr-py:** more ruff lints + fix some typos ([#1246](#1246)) ([f158384](f158384)) * **py:** get rid of pydantic config deprecation warnings ([#1084](#1084)) ([52fcb9d](52fcb9d)) ### Documentation * **hugr-py:** add docs link to README ([#1259](#1259)) ([d2a9148](d2a9148)) * **hugr-py:** build and publish docs ([#1253](#1253)) ([902fc14](902fc14)) * **hugr-py:** docstrings for builder ([#1231](#1231)) ([3e4ac18](3e4ac18)) ### Code Refactoring * Remove "Signature" from hugr-py ([#1186](#1186)) ([65718f7](65718f7)) * Remove NodeType and input_extensions ([#1183](#1183)) ([ea5213d](ea5213d)) --- 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: hugrbot <agustin.borgna+hugrbot@quantinuum.com> Co-authored-by: Seyon Sivarajah <seyon.sivarajah@quantinuum.com>
First steps of #486
Hugr
does some light stable index maintenance, but rewriting is not prioritised as a use-case.