1use log::{debug, trace};
2use rand::seq::IteratorRandom;
3use rand::seq::SliceRandom;
4use rand::thread_rng;
5
6use crate::primitives::{Index, Label};
7use crate::{Graph, Isomorphism, Node};
8#[must_use]
14pub fn match_subgraph<I: Index, NL: Label, EL: Label>(
15 graph: &Graph<I, NL, EL>,
16 subgraph: &Graph<I, NL, EL>,
17) -> Option<Isomorphism<I>> {
18 custom(graph, subgraph)
19}
20
21fn custom<I: Index, NL: Label, EL: Label>(
22 graph: &Graph<I, NL, EL>,
23 subgraph: &Graph<I, NL, EL>,
24) -> Option<Isomorphism<I>> {
25 let q_s = subgraph.nodes.iter().choose(&mut thread_rng())?;
27 trace!("Starting matching with query node: {:?}", &q_s);
28 for g_s in graph.nodes.iter().filter(|g| g.is_isomorph_to(q_s)) {
30 trace!("Starting matching with graph node: {:?}", &g_s);
31 let ret = custom_with_initial_nodes_2(graph, subgraph, q_s, g_s, &Isomorphism::default());
32 if ret.is_some() {
33 return ret;
34 }
35 }
36 debug!("No partial isomorphism found");
37 None
38}
39
40fn combine_neighbours<I: Index, NL: Label>(
43 graph: &[Node<I, NL>],
44 query: &[Node<I, NL>],
45 isomorphism: &Isomorphism<I>,
46) -> Isomorphism<I> {
47 let mut iso = Isomorphism::default();
48 for q in query {
49 for g in graph {
50 if q.is_isomorph_to(g)
51 && !isomorphism.contains_nodeid_on_lhs(&q.id)
52 && !isomorphism.contains_nodeid_on_rhs(&g.id)
53 {
54 iso.0.insert(q.id.clone(), g.id.clone());
55 }
56 }
57 }
58 iso
59}
60
61#[test]
62fn combine_neighbours_simple() {
63 let q = vec![Node {
64 id: 1u32,
65 label: "a",
66 }];
67 let g = vec![
68 Node {
69 id: 1u32,
70 label: "a",
71 },
72 Node { id: 3, label: "a" },
73 ];
74 let possible_matches = combine_neighbours(&g, &q, &Isomorphism::default());
75 let reference: Isomorphism<u32> = (&[(1, 1), (1, 3)]).into();
76 assert_eq!(reference, possible_matches);
77}
78
79#[test]
80fn combine_neighbours_with_filter() {
81 let g = vec![
82 Node {
83 id: 1u32,
84 label: "a",
85 },
86 Node { id: 3, label: "a" },
87 ];
88 let q = vec![Node {
89 id: 1u32,
90 label: "a",
91 }];
92
93 let possible_isomorphisms: Isomorphism<u32> = (&[(2, 0)]).into();
94
95 let possible_matches = combine_neighbours(&g, &q, &possible_isomorphisms);
96 let reference: Isomorphism<u32> = (&[(1, 1), (1, 3)]).into();
97 assert_eq!(reference, possible_matches);
98
99 let possible_matches = combine_neighbours(&q, &g, &possible_isomorphisms);
100 let reference: Isomorphism<u32> = (&[(1, 1), (3, 1)]).into();
101 assert_eq!(reference, possible_matches);
102}
103
104#[test]
105fn combine_neighbours_with_filter_2() {
106 let g: Vec<Node<u32, _>> = vec![
107 Node { id: 0, label: "a" },
108 Node { id: 2, label: "a" },
109 Node { id: 3, label: "a" },
110 ];
111 let q: Vec<Node<u32, _>> = vec![Node { id: 0, label: "a" }, Node { id: 2, label: "a" }];
112
113 let possible_isomorphisms: Isomorphism<u32> = (&[(1, 1)]).into();
114 let possible_matches = combine_neighbours(&g, &q, &possible_isomorphisms);
115
116 let reference: Isomorphism<u32> = (&[(0, 0), (0, 2), (0, 3), (2, 0), (2, 2), (2, 3)]).into();
117 assert_eq!(reference, possible_matches);
118}
119
120fn custom_explore_all_options_2<I: Index, NL: Label, EL: Label>(
122 graph: &Graph<I, NL, EL>,
123 subgraph: &Graph<I, NL, EL>,
124 isomorphism: &Isomorphism<I>,
125) -> Option<Isomorphism<I>> {
126 let mut keys = isomorphism.0.keys().collect::<Vec<_>>();
128 keys.shuffle(&mut thread_rng());
129
130 let mut number_of_hopless_nodes = 0;
131 for q in &keys {
132 let q = *q;
133 let g = isomorphism.0.get(q).unwrap();
134 let g_potentials = graph.neighbours(g);
135 let q_potentials = subgraph.neighbours(q);
136 trace!("");
137 trace!(" Used mapping {:?}", (&q, &g));
138 trace!(" g neigbours {:?}", &g_potentials);
139 trace!(" q neigbours {:?}", &q_potentials);
140 trace!("limiting mappings {:?}", &isomorphism);
141 let possible_matches = combine_neighbours(&g_potentials, &q_potentials, isomorphism);
142 trace!(" possible_matches {:?}", &possible_matches);
143 for (q_s, g_s) in &possible_matches.0 {
144 let q_s = subgraph.get_node_by_id(q_s)?;
145 let g_s = graph.get_node_by_id(g_s)?;
146 let ret = custom_with_initial_nodes_2(graph, subgraph, q_s, g_s, isomorphism);
147 if ret.is_some() {
148 return ret;
149 }
150 }
151 if possible_matches.0.is_empty() {
153 number_of_hopless_nodes += 1;
154 }
155 }
156 if number_of_hopless_nodes != isomorphism.0.len() {
157 return None;
158 }
159 let nodes_to_remove = isomorphism.0.keys().cloned().collect::<Vec<_>>();
163 let reduced_query: Graph<I, NL, EL> = subgraph - &nodes_to_remove[..];
164 if reduced_query.nodes.is_empty() {
165 trace!("This doesn't lead to anything ... we should go back.");
167 return None;
168 }
169 trace!("Match found for connected subgraph, but the following graph'island' is still unmapped:\n{:?}", &reduced_query);
170 let q_s = reduced_query.nodes.iter().choose(&mut thread_rng())?;
172 let mut isomorphism = isomorphism.clone();
173 for g_s in graph.nodes.iter().filter(|g| g.is_isomorph_to(q_s)) {
174 let iso = Isomorphism::default();
175 let ret = custom_with_initial_nodes_2(graph, &reduced_query, q_s, g_s, &iso)?;
176 if isomorphism.can_be_safely_merged(&ret) {
177 isomorphism.0.extend(ret.0);
178 return Some(isomorphism.clone());
179 }
180 }
181 trace!("Disconnected islands don't match. Back we go ...");
183 None
184}
185
186fn custom_with_initial_nodes_2<I: Index, NL: Label, EL: Label>(
187 graph: &Graph<I, NL, EL>,
188 subgraph: &Graph<I, NL, EL>,
189 q_s: &Node<I, NL>,
190 g_s: &Node<I, NL>,
191 isomorphism: &Isomorphism<I>,
192) -> Option<Isomorphism<I>> {
193 let mut isomorphism = isomorphism.clone();
194 isomorphism.0.insert(q_s.id.clone(), g_s.id.clone());
195 trace!("The current isomorphism reads : {:?}", &isomorphism);
196 if isomorphism.0.len() == subgraph.nodes.len() {
197 debug!("(Sub-)Match found!");
198 return Some(isomorphism);
199 }
200
201 custom_explore_all_options_2(graph, subgraph, &isomorphism)
202}
203
204#[must_use]
221pub fn are_graphs_isomorph<I: Index, NL: Label, EL: Label>(
222 lhs: &Graph<I, NL, EL>,
223 rhs: &Graph<I, NL, EL>,
224) -> bool {
225 crate::match_subgraph(lhs, rhs).is_some() && crate::match_subgraph(rhs, lhs).is_some()
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
232 use crate::Edge;
233
234 #[test]
235 fn simple_unique_match() {
236 let _ = pretty_env_logger::try_init_timed();
237 let graph = Graph {
238 nodes: vec![Node::new(0u32, "a"), Node::new(1, "b")],
239 edges: vec![Edge::new_unlabeled(0, 1)],
240 };
241 let query = graph.clone();
242 let possible_isomorphisms: Vec<Isomorphism<u32>> = vec![(&[(0, 0), (1, 1)]).into()];
243 let custom_iso =
244 match_subgraph(&graph, &query).expect("There is at least one possible isomorphism");
245 assert!(possible_isomorphisms.contains(&custom_iso));
246 }
247
248 #[test]
249 fn trivial_disconnected_graphs() {
250 let _ = pretty_env_logger::try_init_timed();
251 let graph = Graph::<_, _, &str> {
252 nodes: vec![Node::new(0u32, "a"), Node::new(1, "b")],
253 edges: vec![],
254 };
255 let query = Graph::<_, _, &str> {
256 nodes: vec![Node::new(10u32, "a"), Node::new(11, "b")],
257 edges: vec![],
258 };
259 let possible_isomorphisms: Vec<Isomorphism<u32>> = vec![(&[(10, 0), (11, 1)]).into()];
260 let custom_iso =
261 match_subgraph(&graph, &query).expect("There is at least one possible isomorphism");
262 assert!(possible_isomorphisms.contains(&custom_iso));
263 }
264
265 #[test]
266 fn simple_nonunique_match() {
267 let _ = pretty_env_logger::try_init_timed();
268 let graph = Graph {
269 nodes: vec![Node::new(0u32, "a"), Node::new(1, "a"), Node::new(2, "b")],
270 edges: vec![Edge::new_unlabeled(0, 2), Edge::new_unlabeled(1, 2)],
271 };
272 let query = Graph {
273 nodes: vec![Node::new(0u32, "a"), Node::new(1, "b")],
274 edges: vec![Edge::new_unlabeled(0, 1)],
275 };
276 let possible_isomorphisms: Vec<Isomorphism<u32>> =
277 vec![(&[(0, 0), (1, 2)]).into(), (&[(0, 1), (1, 2)]).into()];
278 let custom_iso =
279 match_subgraph(&graph, &query).expect("There is at least one possible isomorphism");
280 assert!(possible_isomorphisms.contains(&custom_iso));
281 }
282
283 #[test]
284 fn nonunique_match_with_cycles() {
285 let graph = Graph {
286 nodes: vec![
287 Node::new(0u32, "a"),
288 Node::new(1, "a"),
289 Node::new(2, "a"),
290 Node::new(3, "a"),
291 ],
292 edges: vec![
293 Edge::new_unlabeled(0, 1),
294 Edge::new_unlabeled(1, 2),
295 Edge::new_unlabeled(1, 3),
296 Edge::new_unlabeled(3, 0),
297 ],
298 };
299 let query = Graph {
300 nodes: vec![Node::new(0u32, "a"), Node::new(1, "a"), Node::new(2, "a")],
301 edges: vec![Edge::new_unlabeled(0, 1), Edge::new_unlabeled(1, 2)],
302 };
303 let possible_isomorphisms: Vec<Isomorphism<u32>> = vec![
304 (&[(0, 0), (1, 1), (2, 2)]).into(),
306 (&[(0, 2), (1, 1), (2, 0)]).into(),
307 (&[(0, 0), (1, 1), (2, 3)]).into(),
309 (&[(0, 1), (1, 3), (2, 0)]).into(),
310 (&[(0, 3), (1, 0), (2, 1)]).into(),
311 (&[(0, 3), (1, 1), (2, 0)]).into(),
313 (&[(0, 0), (1, 3), (2, 1)]).into(),
314 (&[(0, 1), (1, 0), (2, 3)]).into(),
315 ];
316 for _ in 0..60 {
317 let custom_iso =
318 match_subgraph(&graph, &query).expect("There is at least one possible isomorphism");
319 log::info!("Found isormorphism : {:#?}", custom_iso);
320 assert!(possible_isomorphisms.contains(&custom_iso));
321 }
322 }
323}