Skip to content

Conversation

acl-cqc
Copy link
Contributor

@acl-cqc acl-cqc commented May 20, 2025

closes #1234

Gathers all nonlocal edges, then transforms from "outside in" to maintain invariant that there are no edges incoming at the level being processed.

@acl-cqc acl-cqc self-assigned this May 21, 2025
Copy link
Collaborator

@aborgna-q aborgna-q left a comment

Choose a reason for hiding this comment

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

Adding some comments, I still need to finish localize.rs

}
}

/// Returns an iterator over all non local edges in a Hugr.
Copy link
Collaborator

Choose a reason for hiding this comment

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

drive-by: This is no longer "all nl edges", but the ones that interact with the entrypoint descendants.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. Or rather, it's ones that are incoming to the subtree. So actually only one endpoint need be in the subtree.

So my thinking here was that there were two reasonable policies:

  • All nonlocal edges within the Hugr, ignoring hugr.entrypoint().
  • All nonlocal edges entirely beneath the entrypoint.

I meant to go for the latter, on the grounds that you can then get the former behaviour by passing entrypoint==root. Clearly I missed the latter ;-), I think the sensible thing is to put in that check.

However, if you think it'd be better to do this in stages (e.g. have two methods _root and _entrypoint, make the unsuffixed version delegate to _root but @deprecate it, and maybe eventually rename the _entrypoint variant back to no-suffix) then happy to do that

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed that "All nonlocal edges entirely beneath the entrypoint" is the way to go.
I think most passes should avoid out-of-tree modifications unless explicitly stated (e.g. defining a function in the module).

This should be compatible with the pre-entrypoint API.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed that "All nonlocal edges entirely beneath the entrypoint" is the way to go.
I think most passes should avoid out-of-tree modifications unless explicitly stated (e.g. defining a function in the module).

This should be compatible with the pre-entrypoint API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, filtered to beneath the entrypoint.

hugr.linked_outputs(node, in_p)
.any(|(neighbour_node, _)| parent != hugr.get_parent(neighbour_node))
.then_some((node, in_p))
let (src, _) = hugr.single_linked_output(node, in_p)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We are also now missing edges that connect out of the entrypoint descendants. (I realize it's annoying to check and avoid duplicates :/)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Argh. Yes.

So my thinking was that for transformation, we should mutate the Hugr only beneath the entrypoint, in which case we only want edges with both ends beneath the entrypoint.

Counterexamples?

  • An edge entering the subtree from a sibling of the entrypoint. This requires retargetting that edge to the entrypoint, and mutating the entrypoint node. Since no node outside the subtree is changed, I guess you could argue this is allowed, although I don't think I would myself....(looks to me like you've changed an edge outside the subtree!)
  • Unless the entrypoint is a DataflowBlock, the only possible NL edge leaving it would be an Ext edge from the entrypoint itself (to a descendant of a sibling). I think that edge is outside the subtree....(indeed, localizing it does not even mutate the entrypoint node)
  • If the entrypoint is a Dataflow Block, then Dom edges could leave its children to (descendants of) other DFB siblings-of-the-entrypoint, i.e. outside the tree. But clearly localizing those is gonna involve widespread changes outside the subtree (as well as to the entrypoint node itself).

// Group all the non-local edges in the graph by target node,
// storing for each the source and type (well-defined as these are Value edges).
let nonlocal_edges_map: HashMap<_, _> = nonlocal_edges(hugr)
.filter_map(|(node, inport)| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this a filter_map instead of a map?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good spot, we've already skipped over inports without a single_linked_output in nonlocal_edges(hugr). Thanks!

else {
panic!("impossible")
};
Some((node, (source, ty)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't we have multiple nl edges going to the same node?
This will drop all but one while collecting

Copy link
Contributor Author

@acl-cqc acl-cqc May 23, 2025

Choose a reason for hiding this comment

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

Ooh good point. ("All compliments accrue to Doug but all shortcomings are my fault" 😉 😉 ). Yes, we dedup sources in the ExtraSourceReqs map (which is correct) but should not do so here. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

localize_dfg test extended

@aborgna-q aborgna-q self-requested a review May 21, 2025 15:00
Copy link
Collaborator

@aborgna-q aborgna-q left a comment

Choose a reason for hiding this comment

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

LGTM, should be mergeable once the entrypoint-descendants filter is updated.

@acl-cqc acl-cqc requested a review from a team as a code owner May 28, 2025 08:32
@acl-cqc acl-cqc requested a review from croyzor May 28, 2025 08:32
.then_some((node, in_p))
let (src, _) = hugr.single_linked_output(node, in_p)?;
(hugr.get_parent(node) != hugr.get_parent(src)
&& ancestors(src, hugr).any(|a| a == hugr.entrypoint()))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to use HugrInternals::region_portgraph instead of this ancestors check here but got a bit stuck - it looks like you should use the nodemap to convert Nodes to portgraph NodeIndexes to look up in the portgraph Region, but the nodemap says it is undefined for Nodes outside the region, which prevents using it to check if Nodes are in the region! So I can come back to this if I'm missing something (@aborgna-q ?) but otherwise I'm just gonna accept an O(depth) =~ O(log(n)) cost multiplier for now. (We could optimize by caching entry_descendants in a HashSet if we really wanted.)

@aborgna-q aborgna-q self-requested a review June 2, 2025 09:02
@acl-cqc acl-cqc added this pull request to the merge queue Jun 3, 2025
Merged via the queue into main with commit f8b6d25 Jun 3, 2025
30 checks passed
@acl-cqc acl-cqc deleted the acl/localize_edges branch June 3, 2025 14:10
@hugrbot hugrbot mentioned this pull request Jun 3, 2025
github-merge-queue bot pushed a commit that referenced this pull request Jun 3, 2025
## 🤖 New release

* `hugr-model`: 0.20.0 -> 0.20.1
* `hugr-core`: 0.20.0 -> 0.20.1 (✓ API compatible changes)
* `hugr-llvm`: 0.20.0 -> 0.20.1 (✓ API compatible changes)
* `hugr-passes`: 0.20.0 -> 0.20.1 (✓ API compatible changes)
* `hugr`: 0.20.0 -> 0.20.1 (✓ API compatible changes)
* `hugr-cli`: 0.20.0 -> 0.20.1 (✓ API compatible changes)

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

## `hugr-model`

<blockquote>

##
[0.20.0](hugr-model-v0.19.0...hugr-model-v0.20.0)
- 2025-05-14

### New Features

- [**breaking**] Mark all Error enums as non_exhaustive
([#2056](#2056))
- [**breaking**] Bump MSRV to 1.85
([#2136](#2136))
- Export and import entrypoints via metadata in `hugr-model`.
([#2172](#2172))
- Define text-model envelope formats
([#2188](#2188))
- Import CFG regions without adding an entry block.
([#2200](#2200))
- Symbol applications can leave out prefixes of wildcards.
([#2201](#2201))
</blockquote>

## `hugr-core`

<blockquote>

##
[0.20.1](hugr-core-v0.20.0...hugr-core-v0.20.1)
- 2025-06-03

### Bug Fixes

- check well-definedness of DFG wires in validate
([#2221](#2221))
- Check for order edges in SiblingSubgraph::from_node
([#2223](#2223))
- Make SumType::Unit(N) equal to SumType::General([(); N])
([#2250](#2250))
- canonicalize_nodes sometimes mangles the entrypoint
([#2263](#2263))

### New Features

- Add PersistentHugr ([#2080](#2080))
- Add `Type::used_extensions`
([#2224](#2224))
- Add boundary edge traversal in SimpleReplacement
([#2231](#2231))
- Add signature map function for DFGs
([#2239](#2239))
- PersistentHugr implements HugrView
([#2202](#2202))
- PersistentHugr Walker API
([#2168](#2168))
- Hugr::store_with_exts and auto-include in serde_as
([#2280](#2280))

### Refactor

- tidies/readability improvements to PersistentHugr
([#2251](#2251))
- Deprecate ValidationError::ExtensionError
([#2281](#2281))

### Testing

- Ignore miri errors in tests involving `assert_snapshot`
([#2261](#2261))
</blockquote>

## `hugr-llvm`

<blockquote>

##
[0.20.1](hugr-llvm-v0.20.0...hugr-llvm-v0.20.1)
- 2025-06-03

### Bug Fixes

- Make SumType::Unit(N) equal to SumType::General([(); N])
([#2250](#2250))

### Testing

- Add exec tests for widen op
([#2043](#2043))
</blockquote>

## `hugr-passes`

<blockquote>

##
[0.20.1](hugr-passes-v0.20.0...hugr-passes-v0.20.1)
- 2025-06-03

### Bug Fixes

- Dataflow analysis produces unsound results on Hugrs with entrypoint
([#2255](#2255))

### New Features

- LocalizeEdges pass ([#2237](#2237))
</blockquote>

## `hugr`

<blockquote>

##
[0.20.1](hugr-v0.20.0...hugr-v0.20.1)
- 2025-06-03

### Bug Fixes

- Dataflow analysis produces unsound results on Hugrs with entrypoint
([#2255](#2255))
- check well-definedness of DFG wires in validate
([#2221](#2221))
- Check for order edges in SiblingSubgraph::from_node
([#2223](#2223))
- Make SumType::Unit(N) equal to SumType::General([(); N])
([#2250](#2250))
- canonicalize_nodes sometimes mangles the entrypoint
([#2263](#2263))

### New Features

- LocalizeEdges pass ([#2237](#2237))
- Add PersistentHugr ([#2080](#2080))
- Add `Type::used_extensions`
([#2224](#2224))
- Add boundary edge traversal in SimpleReplacement
([#2231](#2231))
- Add signature map function for DFGs
([#2239](#2239))
- PersistentHugr implements HugrView
([#2202](#2202))
- PersistentHugr Walker API
([#2168](#2168))
- Hugr::store_with_exts and auto-include in serde_as
([#2280](#2280))

### Refactor

- tidies/readability improvements to PersistentHugr
([#2251](#2251))
- Deprecate ValidationError::ExtensionError
([#2281](#2281))

### Testing

- Ignore miri errors in tests involving `assert_snapshot`
([#2261](#2261))
</blockquote>

## `hugr-cli`

<blockquote>

##
[0.20.1](hugr-cli-v0.20.0...hugr-cli-v0.20.1)
- 2025-06-03

### New Features

- support external subcommands via PATH
([#1343](#1343))
([#2278](#2278))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
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.

Create a "remove-nonlocal-edges" pass
4 participants