1use derive_more::{Display, Error};
4use fxhash::{FxHashMap, FxHasher64};
5use hugr_core::HugrView;
6use hugr_core::hugr::internal::PortgraphNodeMap;
7use hugr_core::ops::OpType;
8use hugr_core::ops::{OpTag, OpTrait};
9use petgraph::visit::{self as pg, Walker};
10use std::hash::{Hash, Hasher};
11
12#[deprecated(
14 note = "`hugr-passes` is deprecated. Use tket::passes instead",
15 since = "0.26.2"
16)]
17pub trait HugrHash: HugrView {
18 fn hugr_hash(&self) -> Result<u64, HashError> {
22 self.region_hash(self.module_root())
23 }
24
25 fn region_hash(&self, node: Self::Node) -> Result<u64, HashError>;
34}
35
36impl<H: HugrView> HugrHash for H {
37 fn region_hash(&self, node: Self::Node) -> Result<u64, HashError> {
38 let node_op = self.get_optype(node);
39 let mut hasher = FxHasher64::default();
40
41 let op: &OpType = self.get_optype(node);
42 hashable_op(op).hash(&mut hasher);
43
44 if OpTag::DataflowParent.is_superset(node_op.tag()) {
45 dfg_hash(self, node)?.hash(&mut hasher);
47 } else {
48 generic_hugr_hash(self, node)?.hash(&mut hasher);
50 }
51
52 Ok(hasher.finish())
53 }
54}
55
56fn dfg_hash<H: HugrView>(dfg_hugr: &H, node: H::Node) -> Result<u64, HashError> {
57 let mut node_hashes = HashState {
58 hashes: FxHashMap::default(),
59 };
60
61 let [_, output_node] = dfg_hugr.get_io(node).expect("DFG region missing I/O nodes");
62
63 let (region, node_map) = dfg_hugr.region_portgraph(node);
64 for pg_node in pg::Topo::new(®ion).iter(®ion) {
65 let child = node_map.from_portgraph(pg_node);
66 let hash = dfg_hash_node(dfg_hugr, child, &mut node_hashes)?;
67 if node_hashes.set_hash(child, hash).is_some() {
68 panic!("Hash already set for node {node}");
69 }
70 }
71
72 node_hashes
73 .get_hash(output_node)
74 .ok_or(HashError::CyclicDFG)
75}
76
77fn generic_hugr_hash<H: HugrView>(hugr: &H, node: H::Node) -> Result<u64, HashError> {
78 let mut child_hashes = Vec::new();
79
80 for child in hugr.children(node) {
81 child_hashes.push(hugr.region_hash(child)?);
82 }
83 child_hashes.sort_unstable();
85 Ok(fxhash::hash64(&child_hashes))
86}
87
88#[derive(Clone, Default, Debug)]
92struct HashState<H: HugrView> {
93 pub hashes: FxHashMap<H::Node, u64>,
95}
96impl<H: HugrView> HashState<H> {
97 #[inline]
99 fn get_hash(&self, node: H::Node) -> Option<u64> {
100 self.hashes.get(&node).copied()
101 }
102
103 #[inline]
107 fn set_hash(&mut self, node: H::Node, hash: u64) -> Option<u64> {
108 self.hashes.insert(node, hash)
109 }
110}
111
112fn hashable_op(op: &OpType) -> impl Hash + use<> {
116 match op {
117 OpType::ExtensionOp(op) if !op.args().is_empty() => {
118 format!(
120 "{}[{}]",
121 op.def().name(),
122 serde_json::to_string(op.args()).unwrap()
123 )
124 }
125 OpType::OpaqueOp(op) if !op.args().is_empty() => {
126 format!(
127 "{}[{}]",
128 op.qualified_id(),
129 serde_json::to_string(op.args()).unwrap()
130 )
131 }
132 _ => op.to_string(),
133 }
134}
135
136fn dfg_hash_node<H: HugrView>(
144 hugr: &H,
145 node: H::Node,
146 state: &mut HashState<H>,
147) -> Result<u64, HashError> {
148 let mut hasher = FxHasher64::default();
149
150 hugr.region_hash(node)?.hash(&mut hasher);
151
152 for input in hugr.node_inputs(node) {
155 let input_hash = hugr
157 .linked_ports(node, input)
158 .map(|(pred_node, pred_port)| {
159 let pred_node_hash = state.get_hash(pred_node);
160 fxhash::hash64(&(pred_node_hash, pred_port, input))
161 })
162 .fold(0, |total, hash| hash ^ total);
163 input_hash.hash(&mut hasher);
164 }
165 Ok(hasher.finish())
166}
167
168#[derive(Debug, Display, Clone, PartialEq, Eq, Error)]
170#[non_exhaustive]
171#[deprecated(
172 note = "`hugr-passes` is deprecated. Use tket::passes instead",
173 since = "0.26.2"
174)]
175pub enum HashError {
176 #[display("The hugr dfg contains a cycle.")]
178 CyclicDFG,
179}
180
181#[cfg(test)]
182mod test {
183 use crate::utils::build_simple_hugr;
184 use crate::utils::test_quantum_extension::{cx_gate, h_gate};
185 use hugr_core::builder::{Dataflow, DataflowSubContainer};
186
187 use super::*;
188 #[test]
189 fn hash_equality() {
190 let hugr1 = build_simple_hugr(2, |mut f_build| {
191 let mut linear = f_build.as_circuit(f_build.input_wires());
194
195 linear
196 .append(h_gate(), [0])?
197 .append(h_gate(), [1])?
198 .append(cx_gate(), [0, 1])?;
199
200 let outs = linear.finish();
201 f_build.finish_with_outputs(outs)
202 })
203 .unwrap();
204
205 let hash1 = hugr1.hugr_hash().unwrap();
206
207 let hugr2 = build_simple_hugr(2, |mut f_build| {
209 let mut linear = f_build.as_circuit(f_build.input_wires());
210
211 linear
212 .append(h_gate(), [1])?
213 .append(h_gate(), [0])?
214 .append(cx_gate(), [0, 1])?;
215
216 let outs = linear.finish();
217 f_build.finish_with_outputs(outs)
218 })
219 .unwrap();
220
221 let hash2 = hugr2.hugr_hash().unwrap();
222
223 assert_eq!(hash1, hash2);
224
225 let hugr3 = build_simple_hugr(2, |mut f_build| {
227 let mut linear = f_build.as_circuit(f_build.input_wires());
228
229 linear
230 .append(h_gate(), [1])?
231 .append(h_gate(), [0])?
232 .append(cx_gate(), [1, 0])?;
233
234 let outs = linear.finish();
235 f_build.finish_with_outputs(outs)
236 })
237 .unwrap();
238 let hash3 = hugr3.hugr_hash().unwrap();
239
240 assert_ne!(hash1, hash3);
241 }
242}