Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hugr-py/src/hugr/passes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""A hugr-py passes module for hugr transformations."""
24 changes: 24 additions & 0 deletions hugr-py/src/hugr/passes/composable_pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""A Protocol for a composable pass."""

from typing import Protocol

from typing_extensions import Self

from hugr.hugr.base import Hugr
from hugr.hugr.node_port import Node


class ComposablePass(Protocol):
"""A Protocol which represents a composable Hugr transformation."""

def __call__(self, hugr: Hugr) -> None: ...

def then(self, other: Self) -> Self: ...

def with_entrypoint(self, entrypoint: Node) -> Self: ...
Copy link
Contributor Author

@CalMacCQ CalMacCQ Oct 29, 2025

Choose a reason for hiding this comment

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

I think it should be

def with_entrypoint(self, hugr: Hugr, entrypoint: Node) -> Self: ...

rather than

def with_entrypoint(self, entrypoint: Node) -> Self: ...

Implementing the with_entrypoint method could also be problematic for reasons I dicussed with Agustin earlier.

As far as I know there isn't a way to get the Hugr subgraph given a entrypoint Node in Python. I think in rust there is something like SiblingSubgraph::try_from_nodes

Agustin suggested that we could modify the entrpoint before we apply the pass and then change it back?

def with_entrypoint(self, hugr: Hugr, entrypoint: Node) -> Self:
    original_entrypoint = hugr.entrypoint
    hugr.entrypoint = entrypoint

    # apply some transformation pass 

    hugr.entrypoint = orignal entrypoint
    return something

Not immediately obvious to me how this would work as we aren't returing an object of type Self. Maybe we could do something with a context manager here?


@property
def is_global(self) -> bool: ...

@property
def is_recursive(self) -> bool: ...
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this actually mean? In particular in conjunction with is_global, I guess?

is_global == True might mean, transforms across the whole Hugr (or whole entrypoint subregion), in which case, "is_recursive" may be meaningless?

If is_global == False, perhaps that means it only affects the immediate children of the entrypoint (so the entrypoint identifies a flat circuit), in which case, recursive could mean, is also automatically applied to subcircuits beneath the entrypoint, fair enough.

But I wonder whether it'd be better to combine these into an enum-like thing where we can be more specific (and extensible) about what the possible variants mean?

Copy link
Contributor

@acl-cqc acl-cqc Oct 29, 2025

Choose a reason for hiding this comment

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

For some transformations (this is perhaps getting a bit complex so maybe never mind) there are actually two variables re. scope:

  1. how much of the Hugr to analyze (without changing) - the more you analyze, the better/more accurate your results, but it takes more time and/or memory
  2. how much of the Hugr to transform/change. I.e. how disruptive it can be.

(1.) probably needs to be a superset of (2.), but you might analyze the whole Hugr in order to change only one function, say (and this would enable more optimization in that function, than if you only analyzed that function alone, but the latter would be cheaper).

Loading