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

Add SiblingSubgraph view #336

Merged
merged 58 commits into from
Aug 22, 2023
Merged

Add SiblingSubgraph view #336

merged 58 commits into from
Aug 22, 2023

Conversation

lmondada
Copy link
Contributor

@lmondada lmondada commented Aug 2, 2023

No description provided.

@lmondada
Copy link
Contributor Author

lmondada commented Aug 3, 2023

I've been trying to implement the HugrView and HugrInternals traits but it's a nightmare in two ways:

  • getting the iterator types right,
  • exposing a PortView that represents the underlying portgraph -- probably need to create yet another type in portgraph that combines Regions and Subgraphs.

For now, I've added a todo flag and will leave this for later.

@acl-cqc
Copy link
Contributor

acl-cqc commented Aug 3, 2023

Hmmm. Region is of course a bit of a misnomer - really, FlatRegion should be something like SiblingGraph and Region something like Descendants. Then you could call this region, or similar....

But if we want a general term, how about Area?

@acl-cqc
Copy link
Contributor

acl-cqc commented Aug 3, 2023

really, FlatRegion should be something like SiblingGraph

Or maybe FlatRegionView should be ChildrenView? (Or FamilyView :-) :-) )

@lmondada
Copy link
Contributor Author

lmondada commented Aug 3, 2023

I like your ideas @acl-cqc , will try to come up with a sensible naming scheme.

@lmondada lmondada mentioned this pull request Aug 3, 2023
@doug-q
Copy link
Collaborator

doug-q commented Aug 7, 2023

I've implemented HugrView here: #355

@lmondada
Copy link
Contributor Author

lmondada commented Aug 7, 2023

Hi @acl-cqc,

I've made a few changes that build on top of #347, #323 and @doug-q 's #355, I think the PR is now in much better shape:

  • SugHugr was renamed to SiblingSubgraph and its semantics were changed to only include the subgraph within the sibling graph and not its further descendants. This is all that I need and will make the HugrView impl much simpler.
  • HugrView and the hierarchy-related views were moved into a common hugr/views folder, along with the new sibling subgraph code.

Regarding change 2: I find the file organisation much cleaner this way, apologies to @ss2165 for changing this twice in a row... I hope you will agree with me, but if you dislike the additional nesting or anything else, happy to revert the change of course!

@lmondada lmondada marked this pull request as ready for review August 7, 2023 14:20
@lmondada lmondada requested a review from acl-cqc August 7, 2023 14:20
@lmondada
Copy link
Contributor Author

lmondada commented Aug 7, 2023

NB (important!): do not merge this until CQCL/portgraph#100 is merged, a new portgraph is released and Cargo.toml changed to pick up the new release!

EDIT: done.

@lmondada lmondada changed the title Add SubHugr view Add SiblingSubgraph view Aug 7, 2023
@lmondada lmondada mentioned this pull request Aug 9, 2023
Copy link
Contributor

@acl-cqc acl-cqc left a comment

Choose a reason for hiding this comment

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

Ok, thanks Luca, there is some fairly serious stuff here, although the really hard work of convexity checking is in Portgraph so I'm not looking at that :-). I've quite a few comments on restructuring though and TBH I'm not certain I've grokked all of what's here (hopefully I will after I've clarified the bits I'm commenting on). So, sorry for the long review, but I reckon we can make this significantly easier to understand and reason about, here goes....

//!
//! Views into subgraphs of HUGRs within a single level of the
//! hierarchy, i.e. within a sibling graph. Such a subgraph is
//! given by a root node, of which all nodes in the sibling subgraph are
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: given -> represented?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

//! Views into subgraphs of HUGRs within a single level of the
//! hierarchy, i.e. within a sibling graph. Such a subgraph is
//! given by a root node, of which all nodes in the sibling subgraph are
//! children, as well as a subgraph boundary, defining the separation between
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe "a set of edges forming the subgraph boundary" ?

/// of the target of the edge. Source edges are not unique as they can involve
/// copies.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Into)]
struct BoundaryEdge(Node, Port);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it might be better to call this by what it is (maybe "TargetPort" or "InPort") rather than by what it's used for later - hopefully the uses will make that clear...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree with you. I've opted instead to implement an actual boundary edge: now this struct also includes whether it's an incoming or outgoing boundary edge, and it panics if the edge does not exist.

struct BoundaryEdge(Node, Port);

impl BoundaryEdge {
fn target_port_index<G: PortView>(&self, g: &G) -> PortIndex {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe call this portgraph_index or pg_index

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've renamed to portgraph_target, along with a bunch of other renames.

fn target_port_index<G: PortView>(&self, g: &G) -> PortIndex {
let Node { index: node } = self.0;
let Port { offset: port } = self.1;
g.port_index(node, port).unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
g.port_index(node, port).unwrap()
g.port_index(self.0.index, self.1.offset).unwrap()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Took your suggestion + some rewriting.

/// - a HugrView to the underlying HUGR,
/// - a root node, of which all nodes in the [`SiblingSubgraph`] must be
/// children,
/// - incoming/outgoing boundary edges, pointing into/out of the subgraph.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "incoming and outgoing" - making clear there are two sets

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rewrote this.

/// The incoming/outgoing edges must be contained within the sibling graph of the
/// root node. Their ordering matters when using the [`SiblingSubgraph`] for
/// replacements, as it will match the ordering of the ports in the replacement.
/// The list of incoming and/or outgoing ports may be empty.
Copy link
Contributor

Choose a reason for hiding this comment

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

...", in which case the SiblingSubgraph contains all children of the parent."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rewrote this.

/// `replacement` must be a hugr with DFG root and its signature must
/// match the signature of the subgraph.
///
/// Panics if
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that specification/hugr.md defines SimpleReplace as requiring the replaced nodes to be convex. That check isn't implemented there, but you could check that here. (That might reduce the utility of allowing non-convex SiblingSubgraphs, too...)

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've changed create_simple_replacement to return a Result and to check that the subgraph is convex.

@@ -48,7 +48,7 @@ where
graph: FlatRegionGraph<'g, Base>,

/// The rest of the HUGR.
hugr: &'g Base,
pub(super) hugr: &'g Base,
Copy link
Contributor

Choose a reason for hiding this comment

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

You can revert this now (yay!)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

indeed!

});
let to_pg = |(n, p): (Node, Port)| pg.port_index(n.index, p.offset).expect("invalid port");
let subpg = Subgraph::new_subgraph(pg, incoming.chain(outgoing).map(to_pg));
if !subpg.is_convex_with_checker(checker) {
Copy link
Contributor

@acl-cqc acl-cqc Aug 21, 2023

Choose a reason for hiding this comment

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

presumably it's more efficient to do the convexity check this way, rather than by checker.is_node_convex on nodes below ? If the latter is ok, you could replace all these last checks by returning try_new_with_checker

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not more efficient, however doing the convexity on nodes only will miss some edge cases. Calling try_new_with_checker after the convexity check with the nodes would do the convexity check twice, so I think this is the only way.

Copy link
Contributor

@acl-cqc acl-cqc left a comment

Choose a reason for hiding this comment

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

Thanks @lmondada for running the marathon :) and some of my misreadings. (Maybe this wasn't a good first-task-after-a-holiday "gearchange"!)

I'm pretty happy with this now except that I think there is an issue with state edges and I'm not sure what we should do about that here.

@acl-cqc
Copy link
Contributor

acl-cqc commented Aug 21, 2023

@acl-cqc Do you think we are ready to merge? As far as I can see there is only this one question that is still open-ish

#336 (comment)

Happy to drop that one, sorry for barking up the wrong tree. I've posted 4 more comments above, 3 of which I think are pretty small/straightforward. That leaves the state edges - it's possible that the best thing to do there is to leave that as a TODO, I'm not sure.

@acl-cqc
Copy link
Contributor

acl-cqc commented Aug 21, 2023

(i.e. relating to #432)

@lmondada
Copy link
Contributor Author

Not at all! All your remarks and questions were very pertinent @acl-cqc. Thank you so much! This review process was very necessary and more than worth it! If anything I'm sorry it took so much of your time!

For the time being, I suggest leaving the order edges question open and merging this in. I've added a TODO for this.

We will come back once we have a clear answer, or @aborgna-q and me figure out that we need order edges after all 😛

@lmondada lmondada requested a review from acl-cqc August 21, 2023 17:12
@acl-cqc
Copy link
Contributor

acl-cqc commented Aug 21, 2023

For the time being, I suggest leaving the order edges question open and merging this in. I've added a TODO for this.

What I'd suggest for the time being is to panic if we find any order edges. I think there are two cases:

  1. Edges in/out of the subgraph. I think these would have to be part of the boundary (if you created a SiblingSubgraph from just the value edges in/out, you'd end up getting more of the graph than you intended, and probably fail the convexity/well-formedness check, right). But I think at the moment if such edges were part of the boundary, you'd filter them out, or else look for corresponding edges in the replacement?? So maybe panic rather than filter out.
  2. Edges in/out of the replacement (i.e. from Input node to the rest, or to the Output node). You filter these out, but again, perhaps best to panic.

Other than that, I think all good, I'll have a final check through in the morning!

Copy link
Contributor

@acl-cqc acl-cqc left a comment

Choose a reason for hiding this comment

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

Looks great, thanks Luca!

/// The incoming boundary (resp. outgoing boundary) is given by the input (resp.
/// output) ports of the subgraph that are linked to nodes outside of the subgraph.
/// The signature of the subgraph is then given by the types of the incoming
/// and outgoing boundary edges. Given a replacement with the same signature,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: edges -> ports? If I understand correctly and you include a node with a port that has many edges to outside the subgraph, the port gets included once, not once per edge?

///
/// More formally, the sibling subgraph defined by $B_I$ and $B_O$ is the
/// graph given by the connected components of the graph with edges
/// $E\B_I\B_O$ which contain at least one node that is either
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: note we haven't defined $E$ here! I quite liked the "connected components of the graph G'=(V, E \ B_I \ B_O)` too as it's then clearer that "which contain at least one node..." refers to the connected components rather than the edges. (But it's not really ambiguous as edges don't contain nodes so ok.)

.outgoing_ports()
.filter_map(|(n, p)| self.base.get_optype(n).signature().get(p).cloned())
.collect_vec();
FunctionType::new(input, output)
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh yes, you can use FunctionType - nice :)

}
let rep_inputs = replacement
.node_outputs(rep_input)
// filter out any non-dataflow ports
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd probably panic here rather than filter out (then we'll know when the TODO comes into play) but ok

EmptySubgraph,
}

// /// A common trait for views of a HUGR sibling subgraph.
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably just remove this? Unless you want to impl it for SiblingGraph and SiblingSubgraph as a no-methods marker trait

///
/// This will return an [`InvalidSubgraph::EmptySubgraph`] error if the
/// subgraph is empty.
pub fn from_sibling_graph(sibling_graph: &'g Base) -> Result<Self, InvalidSubgraph>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: no need/point in making this pub, as tests is hidden. (For now!)


hugr.apply_rewrite(rep).unwrap();

assert_eq!(hugr.node_count(), 4); // Module + Def + In + Out
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be good to have a contrasting assert before you apply the rewrite

Direction::Incoming => base.linked_ports(n, p).collect(),
Direction::Outgoing => vec![(n, p)],
});
let to_pg = |(n, p): (Node, Port)| pg.port_index(n.index, p.offset).expect("invalid port");
Copy link
Contributor

Choose a reason for hiding this comment

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

this might be a place to look for state-order edges and panic(?) if you find them. Else you might comment here that we probably need to deal with such here too (?)

@lmondada lmondada disabled auto-merge August 22, 2023 08:47
@lmondada lmondada enabled auto-merge August 22, 2023 12:13
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 :)

@lmondada lmondada added this pull request to the merge queue Aug 22, 2023
Merged via the queue into main with commit caacecf Aug 22, 2023
@lmondada lmondada deleted the feature/convex branch August 22, 2023 12:45
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.

5 participants