Skip to content

Conversation

lmondada
Copy link
Contributor

@lmondada lmondada commented May 13, 2025

SimpleReplacement is now so much simpler!!

I've just gone ahead and removed the nu_inp and nu_out maps of SimpleReplacement. Makes defining replacements so much less verbose! But you will probably (rightfully!) be sceptical that this can work.

The crux is: there is already a boundary map defined in the subgraph: SiblingSubgraph. My claim is: We can restrict nu_inp and nu_out to be bijective maps between the boundary as defined by SiblingSubgraph and the ports at the Input/Output nodes of the replacement. And once the map is bijective, we might as well restrict it to the canonical identity map, mapping the i-th port in the subgraph to the i-th port in the replacement.

To convince yourself of that:

nu_inp: (Node, IncomingPort) -> (HostNode, IncomingPort)

Non-injective case: that’s using a value from the host HUGR multiple times (copying). This is covered by having a replacement HUGR that copies a value at the input.

Non-surjective case: that’s discarding a value from host. This is covered by having a replacement that discards a value at the input.

nu_out: (HostNode, IncomingPort) -> (Node, IncomingPort)

Non-injective case: that’s using a value produced by replacement multiple times in the host HUGR. This is replaced by copying the value in the replacement to multiple outputs

Non-surjective case: that’s discarding a value from replacement. Replaced by not linking that value to the output node.

"The proof is convincing, but what about the following case?"

Consider a subgraph given by nodes 1 and 2 and consider a wire with an outgoing port in 1 and two incoming ports, one in 2 and one in 3.

flowchart LR
 subgraph G["Subgraph"]
        1
        2
  end
    1 --> 2 & 3
Loading

The previous version of nu_out was able to handle this case, by specifying that the incoming port in 3 is a boundary output port, whereas the incoming port in 2 is not. The new version is not, because the output boundary is now the outgoing port in 1. Is the proof broken? No!! In fact, whilst the maps nu_inp and nu_out can express this case, the subgraph field of SimpleReplacement cannot represent this subgraph (it is "non-convex", according to our definition of convexity). So this PR is in no way a regression in functionality. I will open a PR to handle this case in SiblingSubgraph, at which point we can update SimpleReplacement to handle it too.

BREAKING CHANGE: SimpleReplacement::new was replaced by SimpleReplacement::try_new. SimpleReplacement::{map_host_output, map_replacement_input} take and return opposite port directions.

@lmondada lmondada requested a review from a team as a code owner May 13, 2025 14:30
@lmondada lmondada requested a review from ss2165 May 13, 2025 14:30
Copy link

codecov bot commented May 13, 2025

Codecov Report

Attention: Patch coverage is 88.68778% with 25 lines in your changes missing coverage. Please review.

Project coverage is 82.06%. Comparing base (fe12bde) to head (c0d0119).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
hugr-core/src/hugr/patch/simple_replace.rs 91.14% 15 Missing and 2 partials ⚠️
hugr-core/src/hugr/views/sibling_subgraph.rs 72.41% 6 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2208      +/-   ##
==========================================
- Coverage   82.10%   82.06%   -0.05%     
==========================================
  Files         230      230              
  Lines       40943    40905      -38     
  Branches    37044    37006      -38     
==========================================
- Hits        33617    33568      -49     
- Misses       5362     5369       +7     
- Partials     1964     1968       +4     
Flag Coverage Δ
python 85.63% <ø> (ø)
rust 81.68% <88.68%> (-0.05%) ⬇️

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.

@hugrbot
Copy link
Collaborator

hugrbot commented May 13, 2025

This PR contains breaking changes to the public Rust API.

cargo-semver-checks summary

--- failure enum_missing: pub enum removed or renamed ---

Description:
A publicly-visible enum cannot be imported by its prior path. A `pub use` may have been removed, or the enum itself may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.41.0/src/lints/enum_missing.ron

Failed in:
enum hugr_core::hugr::patch::simple_replace::OutputBoundaryMap, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:68

--- failure inherent_method_missing: pub method removed or renamed ---

Description:
A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.41.0/src/lints/inherent_method_missing.ron

Failed in:
SimpleReplacement::new, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:155
SimpleReplacement::new, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:155
SimpleReplacement::new, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:155
SimpleReplacement::new, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:155

--- failure method_requires_different_generic_type_params: method now requires a different number of generic type parameters ---

Description:
A method now requires a different number of generic type parameters than it used to. Uses of this method that supplied the previous number of generic types will be broken.
      ref: https://doc.rust-lang.org/reference/items/generics.html
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.41.0/src/lints/method_requires_different_generic_type_params.ron

Failed in:
hugr_core::hugr::patch::simple_replace::SimpleReplacement::map_host_output takes 0 generic types instead of 1, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:319
hugr_core::hugr::patch::SimpleReplacement::map_host_output takes 0 generic types instead of 1, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:319
hugr_core::hugr::SimpleReplacement::map_host_output takes 0 generic types instead of 1, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:319
hugr_core::SimpleReplacement::map_host_output takes 0 generic types instead of 1, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/hugr/patch/simple_replace.rs:319

@lmondada lmondada changed the title feat!: Remove boundary map in SimpleReplacement feat: Remove boundary map in SimpleReplacement May 13, 2025
@lmondada lmondada force-pushed the feat/simpfliy-simple-replace branch 2 times, most recently from 0eed63d to 8c0f118 Compare May 13, 2025 15:14
@lmondada lmondada changed the title feat: Remove boundary map in SimpleReplacement feat!: Remove boundary map in SimpleReplacement May 13, 2025
@lmondada lmondada force-pushed the feat/simpfliy-simple-replace branch from 8c0f118 to 7ae8088 Compare May 13, 2025 15:17
@ss2165
Copy link
Member

ss2165 commented May 13, 2025

we might as well restrict it to the canonical identity map

this would prevent say using the same replacement with different maps for alternative wirings. Perhaps we should offer a utility that can wrap a replacement with the require re-wiring/copy/discards? Urgency of this depends on how breaking this PR is downstream.

let subgraph_sig = subgraph.signature(host);
let repl_sig = replacement
.inner_function_type()
.expect("replacement is a DFG");
Copy link
Member

Choose a reason for hiding this comment

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

this seems like something that should be reported as InvalidReplacement

});

// The incoming ports at the input boundary of `replacement`
let [repl_inp, _] = self.get_replacement_io().expect("replacement is a DFG");
Copy link
Member

Choose a reason for hiding this comment

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

"replacement is a DFG" is checked at construction so get_replacement_io should be
infallible IMO (move the expect inside the call)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks!

.incoming_ports()
.iter()
.map(|node_ports| {
let (node, port) = *node_ports.first().expect("non-empty boundary partition");
Copy link
Member

Choose a reason for hiding this comment

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

should "empty boundary partition" be prevented as an error on construction?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. This is already the case: it is prevented at construction time by SiblingSubgraph (see validate_subgraph) function in sibling_subgraph.rs.

Comment on lines 503 to 504
hugr.get_optype(node).port_kind(port).unwrap().is_value()
}
Copy link
Member

Choose a reason for hiding this comment

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

the unwrap is scary

Suggested change
hugr.get_optype(node).port_kind(port).unwrap().is_value()
}
hugr.get_optype(node).port_kind(port).as_ref().is_some_and(EdgeKind::is_value)
}

assert_eq!(h.entry_descendants().count(), 6);
}

#[rstest]
Copy link
Member

Choose a reason for hiding this comment

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

what's happened to these two tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They had been added in #2151 and covered the OutputBoundaryMap struct (now deleted), as well as the multiple ways in which the boundary map could be defined. I've now realised that we can get rid of all of that.

replacement: n,
nu_inp,
nu_out: nu_out.into(),
// nu_inp,
Copy link
Member

Choose a reason for hiding this comment

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

remove comments (and below)

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, sorry for missing these

}
// Check that every outgoing port of a node in the subgraph whose target is not in the subgraph
// belongs to outputs.
// Check that every outgoing port of a node in the subgraph whose target is not
Copy link
Member

Choose a reason for hiding this comment

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

nit: lots of noise from docstring re-formatting, would have been nicer as a pre-cursor

Copy link
Contributor Author

@lmondada lmondada May 13, 2025

Choose a reason for hiding this comment

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

sorry, bad github pre-commit formatting on my side, I've undone these changes. The diff should look clean now.

@ss2165 ss2165 added this to the hugr-rs 0̶.̶1̶6̶ 0.20 milestone May 13, 2025
@lmondada
Copy link
Contributor Author

lmondada commented May 13, 2025

this would prevent say using the same replacement with different maps for alternative wirings. Perhaps we should offer a utility that can wrap a replacement with the require re-wiring/copy/discards? Urgency of this depends on how breaking this PR is downstream.

I like this idea a lot. I would imagine something like:

impl Hugr {
    /// Change the function type of a DFG HUGR by reordering/copying/discarding
    /// inputs and outputs of self.
    /// 
    /// Both `new_to_old_inputs` and `old_to_new_outputs` must be bijective when
    /// restricted to the set of linear values.
    fn change_function_type(
        &self,
        new_to_old_inputs: impl Fn(OutgoingPort) -> OutgoingPort,
        old_to_new_outputs: impl Fn(IncomingPort) -> IncomingPort,
    ) -> Hugr {
    }
}

I think it makes more sense to have this in a separate PR. Currently, all instances of SimpleReplacements are constructed using SiblingSubgraph::create_simple_replacement and so no code is broken by the changes in this PR (I've checked in hugr and tket2)

@lmondada lmondada force-pushed the feat/simpfliy-simple-replace branch from 79135c6 to 5af940a Compare May 13, 2025 17:59
@lmondada lmondada force-pushed the feat/simpfliy-simple-replace branch from 4b77070 to c0d0119 Compare May 13, 2025 18:06
@lmondada lmondada requested a review from ss2165 May 13, 2025 18:07
@lmondada
Copy link
Contributor Author

Copy link
Member

@ss2165 ss2165 left a comment

Choose a reason for hiding this comment

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

🟥 🟥 🟥

@lmondada lmondada added this pull request to the merge queue May 13, 2025
Merged via the queue into main with commit db21092 May 13, 2025
27 checks passed
@lmondada lmondada deleted the feat/simpfliy-simple-replace branch May 13, 2025 20:18
This was referenced May 14, 2025
lmondada added a commit that referenced this pull request May 15, 2025
github-merge-queue bot pushed a commit that referenced this pull request May 22, 2025
The boundary map was [recently
removed](#2208) in `SimpleReplacement`.
Whereas previously, a boundary map could be defined to e.g. permutate
the inputs and outputs between the subgraph and the provided
replacement, the replacement must now match the subgraph function type
as is.

This PR adds a `map_function_type` method on DFG HUGRs that can be used
to reorder and copy/discard inputs and outputs to the DFG pointed by the
entrypoint. For the HUGR to be valid, it is also necessary to update the
signatures and links in any enclosing DFGs in the ancestors of the
entrypoint.

Closes #2211

---------

Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com>
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.

3 participants