Skip to content

refactor: add HugrView::scheduling_graph, deprecate region_portgraph#3015

Merged
acl-cqc merged 36 commits into
mainfrom
acl/syn_edge_prep
May 5, 2026
Merged

refactor: add HugrView::scheduling_graph, deprecate region_portgraph#3015
acl-cqc merged 36 commits into
mainfrom
acl/syn_edge_prep

Conversation

@acl-cqc
Copy link
Copy Markdown
Contributor

@acl-cqc acl-cqc commented Apr 2, 2026

This paves the way for #2946, but does not solve. For that, we will need to add "fake" edges: for each nonlocal edge, from the source to the sibling that is ancestor of the target. (Currently stored in Hugr as order edges but these will be removed). However, it will not be possible to add these fake edges to the region_portgraph: being a portgraph (view of hugr), there is no port for the fake edges.

Instead this PR introduces a new method scheduling_graph returning a new SchedulingGraph structure: a generic petgraph, which augments a view of the Hugr with an explicit list of extra edges. In this PR, said list is empty, as the Hugr still contains the order edges necessary to ensure topological orderings are sound. #2951 removes the order edges from Hugr and generates a list of those required as part of the SchedulingGraph.

This PR includes moving sibling_subgraph.rs tests into a separate tests.rs, and also copies the TopoConvexChecker from portgraph (reparametrized to work on a petgraph).

Add some tests demonstrating existing behaviour of SiblingSubgraph and scheduling_graph with order edges, these are then updated in #2951 to show the (i.e. breaking) changes in behaviour.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 5, 2026

Codecov Report

❌ Patch coverage is 74.19355% with 72 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.07%. Comparing base (db8c074) to head (ea9f210).

Files with missing lines Patch % Lines
hugr-core/src/hugr/views/syn_edge.rs 31.76% 56 Missing and 2 partials ⚠️
hugr-core/src/hugr/views/sibling_subgraph.rs 90.90% 7 Missing and 1 partial ⚠️
hugr-core/src/hugr/views.rs 92.85% 3 Missing ⚠️
...ugr-core/src/hugr/views/sibling_subgraph/convex.rs 94.33% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3015      +/-   ##
==========================================
- Coverage   81.32%   81.07%   -0.26%     
==========================================
  Files         240      242       +2     
  Lines       45407    45183     -224     
  Branches    39175    38951     -224     
==========================================
- Hits        36929    36633     -296     
- Misses       6488     6585      +97     
+ Partials     1990     1965      -25     
Flag Coverage Δ
python 88.89% <ø> (ø)
rust 79.82% <74.19%> (-0.30%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@acl-cqc acl-cqc force-pushed the acl/syn_edge_prep branch from 6db3845 to 521319f Compare April 9, 2026 08:49
@acl-cqc acl-cqc requested a review from aborgna-q April 9, 2026 08:49
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 9, 2026

Merging this PR will degrade performance by 23.4%

❌ 6 regressed benchmarks
✅ 23 untouched benchmarks
⏩ 6 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
fewnode_subgraph[10] 137.5 µs 156 µs -11.88%
fewnode_subgraph[1000] 5.3 ms 7 ms -23.4%
multinode_subgraph[1000] 14.3 ms 15.9 ms -10.05%
multinode_subgraph[10] 135 µs 154.6 µs -12.65%
fewnode_subgraph[100] 613.3 µs 788.4 µs -22.21%
multinode_subgraph[100] 1.1 ms 1.2 ms -14.15%

Comparing acl/syn_edge_prep (ea9f210) with main (db8c074)

Open in CodSpeed

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

};
use std::collections::BTreeSet;

use portgraph::{SecondaryMap, UnmanagedDenseMap};
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This file is basically a straight copy of TopoConvexChecker from portgraph then slightly reparametrized

@@ -0,0 +1,778 @@
use std::collections::BTreeSet;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tests moved out of sibling_subgraph.rs


impl<T: LinkView> pv::GraphBase for SynEdgeWrapper<T> {
type NodeId = NIdx<T::NodeIndexBase>;
type EdgeId = (
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note this is significantly more informative than portgraph's petgraph-traits, but perhaps it shouldn't be

Comment thread hugr-core/src/hugr/views/syn_edge.rs Outdated

impl<T: LinkView> pv::NodeIndexable for SynEdgeWrapper<T> {
fn node_bound(&self) -> usize {
// ALAN copied from petgraph; but are `NodeIndex`es always dense?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was a bit worried here??

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ouch, that looks wrong.

It should be self.region_view.node_capacity() instead, since node indices do not get compacted after a removal.

/// but at least this distinguishes synthetic edges from original edges.
///
/// [FlatRegion]: portgraph::view::FlatRegion
type EdgeWeight = MaybeSynEdge<(PortOffset<T::PortOffsetBase>, PortOffset<T::PortOffsetBase>)>;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Again, portgraph's petgraph-traits does not provide any edge info, not even just the port offsets

/// Access to the graph, sufficient to allow [pv::Topo]
pub fn petgraph(
&self,
) -> impl pv::NodeCount
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this type is part of the hiding, and quite deliberate. It could be much more detailed (NodeId/EdgeId types etc.)

/// Skip convexity check.
SkipConvexity,
mod hidden {
#![expect(deprecated)] // Remove enum along with TopoConvexChecker
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This mod hidden is so we can do this to capture the deprecation warning from #[derive(Default)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can avoid the module by using allow instead of expect. (Seems like a bug, but this works)

#[deprecated]
#[allow(deprecated)]
#[derive(Default)]
pub enum E {
    #[default]
    A
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hah! Yes, that is odd, I had tried expect. Thanks!

/// particularly when many checks must be performed.
pub type LineConvexChecker<'g, Base> =
ConvexChecker<'g, Base, portgraph::algorithms::LineConvexChecker<CheckerRegion<'g, Base>>>;
pub type LineConvexChecker<'g, Base> = PortgraphCheckerWithNodes<
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So I've put some effort into trying to keep this alive, on the grounds that it's potentially much more efficient than TopoConvexChecker. I think it works only for graphs with no container nodes, in which case there will be no syn edges, so it'll still be as ok as it ever was.

But, we could also drop it and plan to reimplement line checking somewhere else if we want it, possibly based off Quantinuum/tket2#1289 somehow

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice! If it works with your changes I would keep it.

outputs: &OutgoingPorts<H::Node>,
function_calls: &IncomingPorts<H::Node>,
) -> Result<Vec<H::Node>, InvalidSubgraph<H::Node>> {
// Compute the nodes inside the boundary ignoring synthetic edges -
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is key, can you check that you agree?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The logic looks good to me

// that would lead to needing those synthetic edges) then the subgraph is invalid
// (nodes would not share the same parent)!
let node_indices = make_pg_subgraph::<H>(
self.checker.graph().region_view.clone(),
Copy link
Copy Markdown
Contributor Author

@acl-cqc acl-cqc Apr 9, 2026

Choose a reason for hiding this comment

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

So I guess this is the same backdoor as portgraph_no_syn_edges, and a slight shame I can't use the same method, but here I think it is the right thing to do

@acl-cqc acl-cqc marked this pull request as ready for review April 9, 2026 09:26
@acl-cqc acl-cqc requested a review from a team as a code owner April 9, 2026 09:26
Copy link
Copy Markdown
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 asides from the node bound bug.

/// Skip convexity check.
SkipConvexity,
mod hidden {
#![expect(deprecated)] // Remove enum along with TopoConvexChecker
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can avoid the module by using allow instead of expect. (Seems like a bug, but this works)

#[deprecated]
#[allow(deprecated)]
#[derive(Default)]
pub enum E {
    #[default]
    A
}

/// particularly when many checks must be performed.
pub type LineConvexChecker<'g, Base> =
ConvexChecker<'g, Base, portgraph::algorithms::LineConvexChecker<CheckerRegion<'g, Base>>>;
pub type LineConvexChecker<'g, Base> = PortgraphCheckerWithNodes<
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice! If it works with your changes I would keep it.

outputs: &OutgoingPorts<H::Node>,
function_calls: &IncomingPorts<H::Node>,
) -> Result<Vec<H::Node>, InvalidSubgraph<H::Node>> {
// Compute the nodes inside the boundary ignoring synthetic edges -
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The logic looks good to me


/// A view of a flat region, including ordering constraints from nonlocal edges,
/// suitable for use with petgraph algorithms.
fn scheduling_graph(&self, parent: Self::Node) -> SchedulingGraph<'_, Self> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like scheduling_graph, it conveys well the causal order meaning.

Comment thread hugr-core/src/hugr/views/syn_edge.rs Outdated

impl<T: LinkView> pv::NodeIndexable for SynEdgeWrapper<T> {
fn node_bound(&self) -> usize {
// ALAN copied from petgraph; but are `NodeIndex`es always dense?
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ouch, that looks wrong.

It should be self.region_view.node_capacity() instead, since node indices do not get compacted after a removal.

@acl-cqc acl-cqc added this pull request to the merge queue May 5, 2026
Merged via the queue into main with commit d6e11c8 May 5, 2026
28 of 30 checks passed
@acl-cqc acl-cqc deleted the acl/syn_edge_prep branch May 5, 2026 09:50
This was referenced Apr 30, 2026
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.

2 participants