Skip to content

Commit 74721a5

Browse files
authored
feat: Add serial data types for SimpleReplacement and PersistentHugr (#2300)
This PR provides serde serialization/deserialization support for `SimpleReplacement` as well as the main types in the `PersistentHugr` implementation as discussed in #2253. This can also be used as a template for other types that require ser/deser implementations in the future. Closes #2253
1 parent 214b8df commit 74721a5

10 files changed

Lines changed: 281 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pest_derive = "2.8.0"
9090
pretty = "0.12.4"
9191
pretty_assertions = "1.4.1"
9292
zstd = "0.13.2"
93-
relrc = "0.4.1"
93+
relrc = "0.4.6"
9494

9595
# These public dependencies usually require breaking changes downstream, so we
9696
# try to be as permissive as possible.

hugr-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ thiserror = { workspace = true }
6363
typetag = { workspace = true }
6464
semver = { workspace = true, features = ["serde"] }
6565
zstd = { workspace = true, optional = true }
66-
relrc = { workspace = true, features = ["petgraph"] }
66+
relrc = { workspace = true, features = ["petgraph", "serde"] }
6767

6868
[dev-dependencies]
6969
rstest = { workspace = true }

hugr-core/src/hugr/patch/simple_replace.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use thiserror::Error;
1717
use super::inline_dfg::InlineDFGError;
1818
use super::{BoundaryPort, HostPort, PatchHugrMut, PatchVerification, ReplacementPort};
1919

20+
pub mod serial;
21+
2022
/// Specification of a simple replacement operation.
2123
///
2224
/// # Type parameters
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//! Serialisation of [`SimpleReplacement`]
2+
3+
use super::*;
4+
5+
/// Serialized format for [`SimpleReplacement`]
6+
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7+
pub struct SerialSimpleReplacement<H, N> {
8+
/// The subgraph to be replaced
9+
pub subgraph: SiblingSubgraph<N>,
10+
/// The replacement Hugr
11+
pub replacement: H,
12+
}
13+
14+
impl<N> SimpleReplacement<N> {
15+
/// Create a new [`SimpleReplacement`] from its serialized format
16+
pub fn from_serial<H: Into<Hugr>>(value: SerialSimpleReplacement<H, N>) -> Self {
17+
let SerialSimpleReplacement {
18+
subgraph,
19+
replacement,
20+
} = value;
21+
SimpleReplacement {
22+
subgraph,
23+
replacement: replacement.into(),
24+
}
25+
}
26+
27+
/// Convert a [`SimpleReplacement`] into its serialized format
28+
pub fn into_serial<H: From<Hugr>>(self) -> SerialSimpleReplacement<H, N> {
29+
let SimpleReplacement {
30+
subgraph,
31+
replacement,
32+
} = self;
33+
SerialSimpleReplacement {
34+
subgraph,
35+
replacement: replacement.into(),
36+
}
37+
}
38+
39+
/// Create its serialized format from a reference to [`SimpleReplacement`]
40+
pub fn to_serial<'a, H>(&'a self) -> SerialSimpleReplacement<H, N>
41+
where
42+
N: Clone,
43+
H: From<&'a Hugr>,
44+
{
45+
let SimpleReplacement {
46+
subgraph,
47+
replacement,
48+
} = self;
49+
SerialSimpleReplacement {
50+
subgraph: subgraph.clone(),
51+
replacement: replacement.into(),
52+
}
53+
}
54+
}
55+
56+
impl<N, H: From<Hugr>> From<SimpleReplacement<N>> for SerialSimpleReplacement<H, N> {
57+
fn from(value: SimpleReplacement<N>) -> Self {
58+
value.into_serial()
59+
}
60+
}
61+
62+
impl<H: Into<Hugr>, N> From<SerialSimpleReplacement<H, N>> for SimpleReplacement<N> {
63+
fn from(value: SerialSimpleReplacement<H, N>) -> Self {
64+
SimpleReplacement::from_serial(value)
65+
}
66+
}
67+
68+
#[cfg(test)]
69+
mod test {
70+
use super::super::test::*;
71+
use super::*;
72+
use crate::{envelope::serde_with::AsStringEnvelope, utils::test_quantum_extension::cx_gate};
73+
74+
use derive_more::derive::{From, Into};
75+
use rstest::rstest;
76+
use serde_with::serde_as;
77+
78+
#[serde_as]
79+
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, From, Into)]
80+
struct WrappedHugr {
81+
#[serde_as(as = "AsStringEnvelope")]
82+
pub hugr: Hugr,
83+
}
84+
85+
impl<'h> From<&'h Hugr> for WrappedHugr {
86+
fn from(value: &'h Hugr) -> Self {
87+
WrappedHugr {
88+
hugr: value.clone(),
89+
}
90+
}
91+
}
92+
93+
#[rstest]
94+
fn test_serial(simple_hugr: Hugr, dfg_hugr: Hugr) {
95+
let h: Hugr = simple_hugr;
96+
// 1. Locate the CX and its successor H's in h
97+
let h_node_cx: Node = h
98+
.entry_descendants()
99+
.find(|node: &Node| *h.get_optype(*node) == cx_gate().into())
100+
.unwrap();
101+
let (h_node_h0, h_node_h1) = h.output_neighbours(h_node_cx).collect_tuple().unwrap();
102+
let s: Vec<Node> = vec![h_node_cx, h_node_h0, h_node_h1].into_iter().collect();
103+
// 2. Construct a new DFG-rooted hugr for the replacement
104+
let replacement = dfg_hugr;
105+
// 4. Define the replacement
106+
let r = SimpleReplacement {
107+
subgraph: SiblingSubgraph::try_from_nodes(s, &h).unwrap(),
108+
replacement,
109+
};
110+
111+
let other_repl_serial = r.to_serial::<WrappedHugr>();
112+
let repl_serial = r.into_serial::<WrappedHugr>();
113+
114+
assert_eq!(repl_serial, other_repl_serial);
115+
}
116+
}

hugr-core/src/hugr/persistent.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,5 +760,12 @@ fn find_conflicting_node<'a>(
760760
})
761761
}
762762

763+
pub mod serial {
764+
//! Serialization formats of [`CommitStateSpace`](super::CommitStateSpace)
765+
//! and related types
766+
#[doc(inline)]
767+
pub use super::state_space::serial::*;
768+
}
769+
763770
#[cfg(test)]
764771
mod tests;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use relrc::EquivalenceResolver;
77
///
88
/// This is a trivial resolver (to be expanded on later), that considers two
99
/// patches equivalent if they point to the same data in memory.
10-
#[derive(Clone, Debug, Default)]
10+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
1111
pub struct PointerEqResolver;
1212

1313
impl<N, E: Clone> EquivalenceResolver<N, E> for PointerEqResolver {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ use crate::{
1616
ops::OpType,
1717
};
1818

19+
pub mod serial;
20+
1921
/// A copyable handle to a [`Commit`] vertex within a [`CommitStateSpace`]
2022
pub type CommitId = relrc::NodeId;
2123

2224
/// A HUGR node within a commit of the commit state space
23-
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug, Hash)]
25+
#[derive(
26+
Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug, Hash, serde::Serialize, serde::Deserialize,
27+
)]
2428
pub struct PatchNode(pub CommitId, pub Node);
2529

2630
impl std::fmt::Display for PatchNode {
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use relrc::serialization::SerializedHistoryGraph;
2+
3+
use super::*;
4+
use crate::hugr::patch::simple_replace::serial::SerialSimpleReplacement;
5+
6+
/// Serialized format for [`PersistentReplacement`]
7+
pub type SerialPersistentReplacement<H> = SerialSimpleReplacement<H, PatchNode>;
8+
9+
/// Serialized format for CommitData
10+
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
11+
pub enum SerialCommitData<H> {
12+
/// Base commit containing a Hugr
13+
Base(H),
14+
/// Replacement commit containing a serialized replacement
15+
Replacement(SerialPersistentReplacement<H>),
16+
}
17+
18+
impl CommitData {
19+
/// Create a new [`CommitData`]` from its serialized format
20+
pub fn from_serial<H: Into<Hugr>>(value: SerialCommitData<H>) -> Self {
21+
match value {
22+
SerialCommitData::Base(h) => CommitData::Base(h.into()),
23+
SerialCommitData::Replacement(replacement) => {
24+
CommitData::Replacement(replacement.into())
25+
}
26+
}
27+
}
28+
29+
/// Convert this [`CommitData`] into its serialized format
30+
pub fn into_serial<H: From<Hugr>>(self) -> SerialCommitData<H> {
31+
match self {
32+
CommitData::Base(h) => SerialCommitData::Base(h.into()),
33+
CommitData::Replacement(replacement) => {
34+
SerialCommitData::Replacement(replacement.into_serial())
35+
}
36+
}
37+
}
38+
}
39+
40+
impl<H: From<Hugr>> From<CommitData> for SerialCommitData<H> {
41+
fn from(value: CommitData) -> Self {
42+
value.into_serial()
43+
}
44+
}
45+
46+
impl<H: Into<Hugr>> From<SerialCommitData<H>> for CommitData {
47+
fn from(value: SerialCommitData<H>) -> Self {
48+
CommitData::from_serial(value)
49+
}
50+
}
51+
52+
/// Serialized format for commit state space
53+
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54+
pub struct SerialCommitStateSpace<H> {
55+
/// The serialized history graph containing commit data
56+
pub graph: SerializedHistoryGraph<SerialCommitData<H>, (), PointerEqResolver>,
57+
/// The base commit ID
58+
pub base_commit: CommitId,
59+
}
60+
61+
impl CommitStateSpace {
62+
/// Create a new [`CommitStateSpace`] from its serialized format
63+
pub fn from_serial<H: Into<Hugr> + Clone>(value: SerialCommitStateSpace<H>) -> Self {
64+
let SerialCommitStateSpace { graph, base_commit } = value;
65+
66+
// Deserialize the SerializedHistoryGraph into a HistoryGraph with CommitData
67+
let graph = graph.map_nodes(|n| CommitData::from_serial(n));
68+
let graph = HistoryGraph::try_from_serialized(graph, PointerEqResolver)
69+
.expect("failed to deserialize history graph");
70+
71+
Self { graph, base_commit }
72+
}
73+
74+
/// Convert a [`CommitStateSpace`] into its serialized format
75+
pub fn into_serial<H: From<Hugr>>(self) -> SerialCommitStateSpace<H> {
76+
let Self { graph, base_commit } = self;
77+
let graph = graph.to_serialized();
78+
let graph = graph.map_nodes(|n| n.into_serial());
79+
SerialCommitStateSpace { graph, base_commit }
80+
}
81+
82+
/// Create a serialized format from a reference to [`CommitStateSpace`]
83+
pub fn to_serial<H>(&self) -> SerialCommitStateSpace<H>
84+
where
85+
H: From<Hugr>,
86+
{
87+
let Self { graph, base_commit } = self;
88+
let graph = graph.to_serialized();
89+
let graph = graph.map_nodes(|n| n.into_serial());
90+
SerialCommitStateSpace {
91+
graph,
92+
base_commit: *base_commit,
93+
}
94+
}
95+
}
96+
97+
impl<H: From<Hugr>> From<CommitStateSpace> for SerialCommitStateSpace<H> {
98+
fn from(value: CommitStateSpace) -> Self {
99+
value.into_serial()
100+
}
101+
}
102+
103+
impl<H: Clone + Into<Hugr>> From<SerialCommitStateSpace<H>> for CommitStateSpace {
104+
fn from(value: SerialCommitStateSpace<H>) -> Self {
105+
CommitStateSpace::from_serial(value)
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod tests {
111+
use derive_more::derive::Into;
112+
use rstest::rstest;
113+
use serde_with::serde_as;
114+
115+
use super::*;
116+
use crate::{
117+
envelope::serde_with::AsStringEnvelope, hugr::persistent::tests::test_state_space,
118+
};
119+
120+
#[serde_as]
121+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, From, Into)]
122+
struct WrappedHugr {
123+
#[serde_as(as = "AsStringEnvelope")]
124+
pub hugr: Hugr,
125+
}
126+
127+
#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
128+
#[rstest]
129+
fn test_serialize_state_space(test_state_space: (CommitStateSpace, [CommitId; 4])) {
130+
let (state_space, _) = test_state_space;
131+
let serialized = state_space.to_serial::<WrappedHugr>();
132+
133+
let deser = CommitStateSpace::from_serial(serialized);
134+
let _serialized_2 = deser.to_serial::<WrappedHugr>();
135+
136+
// TODO: add this once PointerEqResolver is replaced by a deterministic resolver
137+
// insta::assert_snapshot!(serde_json::to_string_pretty(&serialized).unwrap());
138+
// see https://github.com/CQCL/hugr/issues/2299
139+
// assert_eq!(
140+
// serde_json::to_string(&serialized),
141+
// serde_json::to_string(&serialized_2)
142+
// );
143+
}
144+
}

hugr-core/src/hugr/views/sibling_subgraph.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use super::root_checked::RootCheckable;
5050
/// The `boundary_port` and `signature` methods will panic if any are found.
5151
/// State order edges are also unsupported in replacements in
5252
/// `create_simple_replacement`.
53-
#[derive(Clone, Debug)]
53+
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5454
pub struct SiblingSubgraph<N = Node> {
5555
/// The nodes of the induced subgraph.
5656
nodes: Vec<N>,

0 commit comments

Comments
 (0)