Skip to content

Commit 157c44e

Browse files
authored
feat: PersistentHugr Walker API (#2168)
Closes #2074 Closes #2190
1 parent 3d2ab66 commit 157c44e

File tree

7 files changed

+1163
-6
lines changed

7 files changed

+1163
-6
lines changed

hugr-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ bench = false
2626
[[test]]
2727
name = "model"
2828

29+
[[test]]
30+
name = "persistent_walker_example"
31+
2932
[dependencies]
3033
hugr-model = { version = "0.20.0", path = "../hugr-model" }
3134

hugr-core/src/hugr/persistent.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ mod parents_view;
6565
mod resolver;
6666
mod state_space;
6767
mod trait_impls;
68+
pub mod walker;
69+
70+
pub use walker::{PinnedWire, Walker};
6871

6972
use std::{
7073
collections::{BTreeSet, HashMap, VecDeque},
@@ -75,8 +78,8 @@ use delegate::delegate;
7578
use derive_more::derive::From;
7679
use itertools::{Either, Itertools};
7780
use relrc::RelRc;
78-
use state_space::{CommitData, CommitId};
79-
pub use state_space::{CommitStateSpace, InvalidCommit, PatchNode};
81+
use state_space::CommitData;
82+
pub use state_space::{CommitId, CommitStateSpace, InvalidCommit, PatchNode};
8083

8184
pub use resolver::PointerEqResolver;
8285

@@ -279,6 +282,14 @@ impl PersistentHugr {
279282
Self { state_space }
280283
}
281284

285+
/// Create a [`PersistentHugr`] from a single commit and its ancestors.
286+
// This always defines a valid `PersistentHugr` as the ancestors of a commit
287+
// are guaranteed to be compatible with each other.
288+
pub fn from_commit(commit: Commit) -> Self {
289+
let state_space = CommitStateSpace::try_from_commits([commit]).expect("commit is valid");
290+
Self { state_space }
291+
}
292+
282293
/// Create a [`PersistentHugr`] from a list of commits.
283294
///
284295
/// `Self` will correspond to the HUGR obtained by applying the patches of
@@ -557,7 +568,7 @@ impl PersistentHugr {
557568
// incoming ports are of interest to us if
558569
// (i) they are connected to the output of a replacement (then there will be a
559570
// linked port in a parent commit), or
560-
// (ii) they are deleted by a child commit and is not the same as the out_node
571+
// (ii) they are deleted by a child commit and are not equal to the out_node
561572
// (then there will be a linked port in a child commit)
562573
let is_linked_to_output = curr_repl_out.is_some_and(|curr_repl_out| {
563574
hugr.linked_inputs(out_node.1, out_port)

hugr-core/src/hugr/persistent/state_space.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
};
1818

1919
/// A copyable handle to a [`Commit`] vertex within a [`CommitStateSpace`]
20-
pub(super) type CommitId = relrc::NodeId;
20+
pub type CommitId = relrc::NodeId;
2121

2222
/// A HUGR node within a commit of the commit state space
2323
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug, Hash)]
@@ -155,14 +155,20 @@ impl CommitStateSpace {
155155
/// All commits in the resulting `PersistentHugr` are guaranteed to be
156156
/// compatible. If the selected commits would include two commits which
157157
/// are incompatible, a [`InvalidCommit::IncompatibleHistory`] error is
158-
/// returned.
158+
/// returned. If `commits` is empty, a [`InvalidCommit::NonUniqueBase`]
159+
/// error is returned.
159160
pub fn try_extract_hugr(
160161
&self,
161162
commits: impl IntoIterator<Item = CommitId>,
162163
) -> Result<PersistentHugr, InvalidCommit> {
163164
// Define commits as the set of all ancestors of the given commits
164165
let all_commit_ids = get_all_ancestors(&self.graph, commits);
165166

167+
if all_commit_ids.is_empty() {
168+
return Err(InvalidCommit::NonUniqueBase(0));
169+
}
170+
debug_assert!(all_commit_ids.contains(&self.base()));
171+
166172
// Check that all commits are compatible
167173
for &commit_id in &all_commit_ids {
168174
let selected_children = self
@@ -224,7 +230,7 @@ impl CommitStateSpace {
224230
}
225231

226232
/// Get the set of nodes invalidated by `commit_id` in `parent`.
227-
pub(super) fn invalidation_set(
233+
pub fn invalidation_set(
228234
&self,
229235
commit_id: CommitId,
230236
parent: CommitId,

hugr-core/src/hugr/persistent/tests.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,68 @@ fn create_not_and_to_xor_replacement(hugr: &Hugr) -> SimpleReplacement {
144144
SimpleReplacement::try_new(subgraph, hugr, replacement_hugr).unwrap()
145145
}
146146

147+
/// Creates a state space with 4 commits on top of the base hugr `simple_hugr`:
148+
///
149+
/// ```
150+
/// ┌─────────┐
151+
/// ───┤ (0) NOT ├─┐ ┌─────────┐
152+
/// └─────────┘ └────┤ │
153+
/// ┌─────────┐ │ (2) AND ├───
154+
/// ───┤ (1) NOT ├──────┤ │
155+
/// └─────────┘ └─────────┘
156+
/// ```
157+
///
158+
/// Note that for the sake of these tests, we do not care about the semantics of
159+
/// the rewrites. Unlike real-world use cases, the LHS and RHS here are not
160+
/// functionally equivalent.
161+
/// The state space will contain the following commits:
162+
///
163+
/// ```
164+
/// [commit1]
165+
/// ┌─────────┐ ┌─────────┐ ┌─────────┐
166+
/// ───┤ (0) NOT ├─── --> ───┤ (3) NOT ├─────┤ (4) NOT ├──────
167+
/// └─────────┘ └─────────┘ └─────────┘
168+
///
169+
/// [commit2]
170+
/// ┌─────────┐
171+
/// ───┤ (4) NOT ├─┐ ┌─────────┐ ┌─────────┐
172+
/// └─────────┘ └────┤ │ ─────┤ │
173+
/// │ (2) AND ├─── --> │ (5) XOR ├───
174+
/// ────────────────────┤ │ ─────┤ │
175+
/// └─────────┘ └─────────┘
176+
///
177+
/// [commit3]
178+
/// ┌─────────┐
179+
/// ───┤ (0) NOT ├─┐ ┌─────────┐ ┌─────────┐
180+
/// └─────────┘ └────┤ │ ─────┤ │
181+
/// │ (2) AND ├─── --> │ (6) XOR ├───
182+
/// ────────────────────┤ │ ─────┤ │
183+
/// └─────────┘ └─────────┘
184+
///
185+
/// [commit4]
186+
/// ┌─────────┐ ┌─────────┐ ┌─────────┐
187+
/// ───┤ (1) NOT ├─── --> ───┤ (7) NOT ├─────┤ (8) NOT ├──────
188+
/// └─────────┘ └─────────┘ └─────────┘
189+
/// ```
190+
///
191+
/// Viewed as a history of commits, the commits' hierarchy is as follows
192+
///
193+
/// ```
194+
/// base
195+
/// / | \
196+
/// / | \
197+
/// / | \
198+
/// / | \
199+
/// commit1 commit3 commit4
200+
/// |
201+
/// |
202+
/// |
203+
/// commit2
204+
/// ```
205+
/// where
206+
/// - `commit1` and `commit2` are incompatible with `commit3`
207+
/// - `commit1` and `commit2` are disjoint with `commit4` (i.e. compatible),
208+
/// - `commit2` depends on `commit1`
147209
#[fixture]
148210
pub(super) fn test_state_space() -> (CommitStateSpace, [CommitId; 4]) {
149211
let (base_hugr, [not0_node, not1_node, _and_node]) = simple_hugr();

0 commit comments

Comments
 (0)