Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pest_derive = "2.8.0"
pretty = "0.12.4"
pretty_assertions = "1.4.1"
zstd = "0.13.2"
relrc = "0.4.1"
relrc = "0.4.6"

# These public dependencies usually require breaking changes downstream, so we
# try to be as permissive as possible.
Expand Down
2 changes: 1 addition & 1 deletion hugr-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ thiserror = { workspace = true }
typetag = { workspace = true }
semver = { workspace = true, features = ["serde"] }
zstd = { workspace = true, optional = true }
relrc = { workspace = true, features = ["petgraph"] }
relrc = { workspace = true, features = ["petgraph", "serde"] }

[dev-dependencies]
rstest = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions hugr-core/src/hugr/patch/simple_replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use thiserror::Error;
use super::inline_dfg::InlineDFGError;
use super::{BoundaryPort, HostPort, PatchHugrMut, PatchVerification, ReplacementPort};

pub mod serial;

/// Specification of a simple replacement operation.
///
/// # Type parameters
Expand Down
116 changes: 116 additions & 0 deletions hugr-core/src/hugr/patch/simple_replace/serial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Serialisation of [`SimpleReplacement`]

use super::*;

/// Serialized format for [`SimpleReplacement`]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SerialSimpleReplacement<H, N> {
/// The subgraph to be replaced
pub subgraph: SiblingSubgraph<N>,
/// The replacement Hugr
pub replacement: H,
}

impl<N> SimpleReplacement<N> {
/// Create a new [`SimpleReplacement`] from its serialized format
pub fn from_serial<H: Into<Hugr>>(value: SerialSimpleReplacement<H, N>) -> Self {
let SerialSimpleReplacement {
subgraph,
replacement,
} = value;
SimpleReplacement {
subgraph,
replacement: replacement.into(),
}
}

/// Convert a [`SimpleReplacement`] into its serialized format
pub fn into_serial<H: From<Hugr>>(self) -> SerialSimpleReplacement<H, N> {
let SimpleReplacement {
subgraph,
replacement,
} = self;
SerialSimpleReplacement {
subgraph,
replacement: replacement.into(),
}
}

/// Create its serialized format from a reference to [`SimpleReplacement`]
pub fn to_serial<'a, H>(&'a self) -> SerialSimpleReplacement<H, N>
where
N: Clone,
H: From<&'a Hugr>,
{
let SimpleReplacement {
subgraph,
replacement,
} = self;
SerialSimpleReplacement {
subgraph: subgraph.clone(),
replacement: replacement.into(),
}
}
}

impl<N, H: From<Hugr>> From<SimpleReplacement<N>> for SerialSimpleReplacement<H, N> {
fn from(value: SimpleReplacement<N>) -> Self {
value.into_serial()
}
}

impl<H: Into<Hugr>, N> From<SerialSimpleReplacement<H, N>> for SimpleReplacement<N> {
fn from(value: SerialSimpleReplacement<H, N>) -> Self {
SimpleReplacement::from_serial(value)
}
}

#[cfg(test)]
mod test {
use super::super::test::*;
use super::*;
use crate::{envelope::serde_with::AsStringEnvelope, utils::test_quantum_extension::cx_gate};

use derive_more::derive::{From, Into};
use rstest::rstest;
use serde_with::serde_as;

#[serde_as]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, From, Into)]
struct WrappedHugr {
#[serde_as(as = "AsStringEnvelope")]
pub hugr: Hugr,
}

impl<'h> From<&'h Hugr> for WrappedHugr {
fn from(value: &'h Hugr) -> Self {
WrappedHugr {
hugr: value.clone(),
}
}
}

#[rstest]
fn test_serial(simple_hugr: Hugr, dfg_hugr: Hugr) {
let h: Hugr = simple_hugr;
// 1. Locate the CX and its successor H's in h
let h_node_cx: Node = h
.entry_descendants()
.find(|node: &Node| *h.get_optype(*node) == cx_gate().into())
.unwrap();
let (h_node_h0, h_node_h1) = h.output_neighbours(h_node_cx).collect_tuple().unwrap();
let s: Vec<Node> = vec![h_node_cx, h_node_h0, h_node_h1].into_iter().collect();
// 2. Construct a new DFG-rooted hugr for the replacement
let replacement = dfg_hugr;
// 4. Define the replacement
let r = SimpleReplacement {
subgraph: SiblingSubgraph::try_from_nodes(s, &h).unwrap(),
replacement,
};

let other_repl_serial = r.to_serial::<WrappedHugr>();
let repl_serial = r.into_serial::<WrappedHugr>();

assert_eq!(repl_serial, other_repl_serial);
}
}
7 changes: 7 additions & 0 deletions hugr-core/src/hugr/persistent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,5 +760,12 @@ fn find_conflicting_node<'a>(
})
}

pub mod serial {
//! Serialization formats of [`CommitStateSpace`](super::CommitStateSpace)
//! and related types
#[doc(inline)]
pub use super::state_space::serial::*;
}

#[cfg(test)]
mod tests;
2 changes: 1 addition & 1 deletion hugr-core/src/hugr/persistent/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use relrc::EquivalenceResolver;
///
/// This is a trivial resolver (to be expanded on later), that considers two
/// patches equivalent if they point to the same data in memory.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PointerEqResolver;

impl<N, E: Clone> EquivalenceResolver<N, E> for PointerEqResolver {
Expand Down
6 changes: 5 additions & 1 deletion hugr-core/src/hugr/persistent/state_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ use crate::{
ops::OpType,
};

pub mod serial;

/// A copyable handle to a [`Commit`] vertex within a [`CommitStateSpace`]
pub type CommitId = relrc::NodeId;

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

impl std::fmt::Display for PatchNode {
Expand Down
144 changes: 144 additions & 0 deletions hugr-core/src/hugr/persistent/state_space/serial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use relrc::serialization::SerializedHistoryGraph;

use super::*;
use crate::hugr::patch::simple_replace::serial::SerialSimpleReplacement;

/// Serialized format for [`PersistentReplacement`]
pub type SerialPersistentReplacement<H> = SerialSimpleReplacement<H, PatchNode>;

/// Serialized format for CommitData
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SerialCommitData<H> {
/// Base commit containing a Hugr
Base(H),
/// Replacement commit containing a serialized replacement
Replacement(SerialPersistentReplacement<H>),
}

impl CommitData {
/// Create a new [`CommitData`]` from its serialized format
pub fn from_serial<H: Into<Hugr>>(value: SerialCommitData<H>) -> Self {
match value {
SerialCommitData::Base(h) => CommitData::Base(h.into()),
SerialCommitData::Replacement(replacement) => {
CommitData::Replacement(replacement.into())
}
}
}

/// Convert this [`CommitData`] into its serialized format
pub fn into_serial<H: From<Hugr>>(self) -> SerialCommitData<H> {
match self {
CommitData::Base(h) => SerialCommitData::Base(h.into()),
CommitData::Replacement(replacement) => {
SerialCommitData::Replacement(replacement.into_serial())
}
}
}
}

impl<H: From<Hugr>> From<CommitData> for SerialCommitData<H> {
fn from(value: CommitData) -> Self {
value.into_serial()
}
}

impl<H: Into<Hugr>> From<SerialCommitData<H>> for CommitData {
fn from(value: SerialCommitData<H>) -> Self {
CommitData::from_serial(value)
}
}

/// Serialized format for commit state space
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SerialCommitStateSpace<H> {
/// The serialized history graph containing commit data
pub graph: SerializedHistoryGraph<SerialCommitData<H>, (), PointerEqResolver>,
/// The base commit ID
pub base_commit: CommitId,
}

impl CommitStateSpace {
/// Create a new [`CommitStateSpace`] from its serialized format
pub fn from_serial<H: Into<Hugr> + Clone>(value: SerialCommitStateSpace<H>) -> Self {
let SerialCommitStateSpace { graph, base_commit } = value;

// Deserialize the SerializedHistoryGraph into a HistoryGraph with CommitData
let graph = graph.map_nodes(|n| CommitData::from_serial(n));
let graph = HistoryGraph::try_from_serialized(graph, PointerEqResolver)
.expect("failed to deserialize history graph");

Self { graph, base_commit }
}

/// Convert a [`CommitStateSpace`] into its serialized format
pub fn into_serial<H: From<Hugr>>(self) -> SerialCommitStateSpace<H> {
let Self { graph, base_commit } = self;
let graph = graph.to_serialized();
let graph = graph.map_nodes(|n| n.into_serial());
SerialCommitStateSpace { graph, base_commit }
}

/// Create a serialized format from a reference to [`CommitStateSpace`]
pub fn to_serial<H>(&self) -> SerialCommitStateSpace<H>
where
H: From<Hugr>,
{
let Self { graph, base_commit } = self;
let graph = graph.to_serialized();
let graph = graph.map_nodes(|n| n.into_serial());
SerialCommitStateSpace {
graph,
base_commit: *base_commit,
}
}
}

impl<H: From<Hugr>> From<CommitStateSpace> for SerialCommitStateSpace<H> {
fn from(value: CommitStateSpace) -> Self {
value.into_serial()
}
}

impl<H: Clone + Into<Hugr>> From<SerialCommitStateSpace<H>> for CommitStateSpace {
fn from(value: SerialCommitStateSpace<H>) -> Self {
CommitStateSpace::from_serial(value)
}
}

#[cfg(test)]
mod tests {
use derive_more::derive::Into;
use rstest::rstest;
use serde_with::serde_as;

use super::*;
use crate::{
envelope::serde_with::AsStringEnvelope, hugr::persistent::tests::test_state_space,
};

#[serde_as]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, From, Into)]
struct WrappedHugr {
#[serde_as(as = "AsStringEnvelope")]
pub hugr: Hugr,
}

#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
#[rstest]
fn test_serialize_state_space(test_state_space: (CommitStateSpace, [CommitId; 4])) {
let (state_space, _) = test_state_space;
let serialized = state_space.to_serial::<WrappedHugr>();

let deser = CommitStateSpace::from_serial(serialized);
let _serialized_2 = deser.to_serial::<WrappedHugr>();

// TODO: add this once PointerEqResolver is replaced by a deterministic resolver
// insta::assert_snapshot!(serde_json::to_string_pretty(&serialized).unwrap());
// see https://github.com/CQCL/hugr/issues/2299
// assert_eq!(
// serde_json::to_string(&serialized),
// serde_json::to_string(&serialized_2)
// );
}
}
2 changes: 1 addition & 1 deletion hugr-core/src/hugr/views/sibling_subgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use super::root_checked::RootCheckable;
/// The `boundary_port` and `signature` methods will panic if any are found.
/// State order edges are also unsupported in replacements in
/// `create_simple_replacement`.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SiblingSubgraph<N = Node> {
/// The nodes of the induced subgraph.
nodes: Vec<N>,
Expand Down
Loading