1#![allow(
29 clippy::cast_possible_truncation,
30 clippy::cast_possible_wrap,
31 clippy::cast_sign_loss,
32 clippy::many_single_char_names,
35 clippy::similar_names,
36 clippy::doc_markdown,
38 clippy::if_not_else,
40 clippy::struct_excessive_bools
42)]
43
44use crate::core::graph::EdgeId;
45use crate::core::{Graph, IgraphError, IgraphResult};
46
47#[derive(Debug, Clone)]
50pub struct LadSubisomorphism {
51 pub iso: bool,
53 pub map: Vec<u32>,
56}
57
58struct Tgraph {
60 nb_vertices: i64,
61 nb_succ: Vec<i64>,
62 succ: Vec<Vec<i64>>,
63 is_edge: Vec<bool>,
64}
65
66impl Tgraph {
67 #[inline]
68 fn is_edge(&self, i: i64, j: i64) -> bool {
69 self.is_edge[(i * self.nb_vertices + j) as usize]
70 }
71}
72
73fn create_graph(g: &Graph) -> IgraphResult<Tgraph> {
77 let n = i64::from(g.vcount());
78 let directed = g.is_directed();
79 let mut succ: Vec<Vec<i64>> = vec![Vec::new(); n as usize];
80 for eid in 0..g.ecount() {
81 let (a, b) = g.edge(eid as EdgeId)?;
82 let (a, b) = (i64::from(a), i64::from(b));
83 if directed {
84 succ[a as usize].push(b);
85 } else if a == b {
86 succ[a as usize].push(a); } else {
88 succ[a as usize].push(b);
89 succ[b as usize].push(a);
90 }
91 }
92 let mut is_edge = vec![false; (n * n) as usize];
93 let mut nb_succ = vec![0i64; n as usize];
94 for i in 0..n as usize {
95 succ[i].sort_unstable();
96 for w in 0..succ[i].len() {
97 if w > 0 && succ[i][w] == succ[i][w - 1] {
98 return Err(IgraphError::InvalidArgument(
99 "LAD functions do not support graphs with multi-edges.".into(),
100 ));
101 }
102 let j = succ[i][w];
103 is_edge[i * n as usize + j as usize] = true;
104 }
105 nb_succ[i] = succ[i].len() as i64;
106 }
107 Ok(Tgraph {
108 nb_vertices: n,
109 nb_succ,
110 succ,
111 is_edge,
112 })
113}
114
115struct Tdomain {
118 nb_t: i64,
119 nb_val: Vec<i64>,
120 first_val: Vec<i64>,
121 val: Vec<i64>,
122 pos_in_val: Vec<i64>,
123 first_match: Vec<i64>,
124 matching: Vec<i64>,
125 next_out_to_filter: i64,
126 last_in_to_filter: i64,
127 to_filter: Vec<i64>,
128 marked_to_filter: Vec<bool>,
129 global_matching_p: Vec<i64>,
130 global_matching_t: Vec<i64>,
131}
132
133impl Tdomain {
134 #[inline]
135 fn pos(&self, u: i64, v: i64) -> i64 {
136 self.pos_in_val[(u * self.nb_t + v) as usize]
137 }
138 #[inline]
139 fn set_pos(&mut self, u: i64, v: i64, p: i64) {
140 let nb_t = self.nb_t;
141 self.pos_in_val[(u * nb_t + v) as usize] = p;
142 }
143 #[inline]
144 fn first_match(&self, u: i64, v: i64) -> i64 {
145 self.first_match[(u * self.nb_t + v) as usize]
146 }
147 #[inline]
148 fn is_in_d(&self, u: i64, v: i64) -> bool {
149 self.pos(u, v) < self.first_val[u as usize] + self.nb_val[u as usize]
150 }
151 #[inline]
152 fn to_filter_empty(&self) -> bool {
153 self.next_out_to_filter < 0
154 }
155 fn reset_to_filter(&mut self) {
156 for m in &mut self.marked_to_filter {
157 *m = false;
158 }
159 self.next_out_to_filter = -1;
160 }
161 fn next_to_filter(&mut self, size: i64) -> i64 {
162 let u = self.to_filter[self.next_out_to_filter as usize];
163 self.marked_to_filter[u as usize] = false;
164 if self.next_out_to_filter == self.last_in_to_filter {
165 self.next_out_to_filter = -1;
166 } else if self.next_out_to_filter == size - 1 {
167 self.next_out_to_filter = 0;
168 } else {
169 self.next_out_to_filter += 1;
170 }
171 u
172 }
173 fn add_to_filter(&mut self, u: i64, size: i64) {
174 if self.marked_to_filter[u as usize] {
175 return;
176 }
177 self.marked_to_filter[u as usize] = true;
178 if self.next_out_to_filter < 0 {
179 self.last_in_to_filter = 0;
180 self.next_out_to_filter = 0;
181 } else if self.last_in_to_filter == size - 1 {
182 self.last_in_to_filter = 0;
183 } else {
184 self.last_in_to_filter += 1;
185 }
186 self.to_filter[self.last_in_to_filter as usize] = u;
187 }
188}
189
190fn augmenting_path(u: i64, d: &mut Tdomain, nb_v: i64) -> bool {
195 let nb_v = nb_v as usize;
196 let mut fifo = vec![0i64; nb_v];
197 let mut pred = vec![0i64; nb_v];
198 let mut marked = vec![false; nb_v];
199 let mut next_in = 0usize;
200 let mut next_out = 0usize;
201
202 let fv = d.first_val[u as usize];
203 for i in 0..d.nb_val[u as usize] {
204 let v = d.val[(fv + i) as usize];
205 if d.global_matching_t[v as usize] < 0 {
206 d.global_matching_p[u as usize] = v;
207 d.global_matching_t[v as usize] = u;
208 return true;
209 }
210 pred[v as usize] = u;
211 fifo[next_in] = v;
212 next_in += 1;
213 marked[v as usize] = true;
214 }
215 while next_out < next_in {
216 let u2 = d.global_matching_t[fifo[next_out] as usize];
217 next_out += 1;
218 let fv2 = d.first_val[u2 as usize];
219 for i in 0..d.nb_val[u2 as usize] {
220 let v = d.val[(fv2 + i) as usize];
221 if d.global_matching_t[v as usize] < 0 {
222 let mut u2m = u2;
223 let mut vv = v;
224 while u2m != u {
225 let v2 = d.global_matching_p[u2m as usize];
226 d.global_matching_p[u2m as usize] = vv;
227 d.global_matching_t[vv as usize] = u2m;
228 vv = v2;
229 u2m = pred[vv as usize];
230 }
231 d.global_matching_p[u as usize] = vv;
232 d.global_matching_t[vv as usize] = u;
233 return true;
234 }
235 if !marked[v as usize] {
236 pred[v as usize] = u2;
237 fifo[next_in] = v;
238 next_in += 1;
239 marked[v as usize] = true;
240 }
241 }
242 }
243 false
244}
245
246fn remove_all_values_but_one(u: i64, v: i64, d: &mut Tdomain, gp: &Tgraph, gt: &Tgraph) -> bool {
250 for j in 0..gp.succ[u as usize].len() {
251 let s = gp.succ[u as usize][j];
252 d.add_to_filter(s, gp.nb_vertices);
253 }
254 let old_pos = d.pos(u, v);
255 let new_pos = d.first_val[u as usize];
256 d.val[old_pos as usize] = d.val[new_pos as usize];
257 d.val[new_pos as usize] = v;
258 let a = d.val[new_pos as usize];
259 let b = d.val[old_pos as usize];
260 d.set_pos(u, a, new_pos);
261 d.set_pos(u, b, old_pos);
262 d.nb_val[u as usize] = 1;
263 if d.global_matching_p[u as usize] != v {
264 let old = d.global_matching_p[u as usize];
265 d.global_matching_t[old as usize] = -1;
266 d.global_matching_p[u as usize] = -1;
267 augmenting_path(u, d, gt.nb_vertices)
268 } else {
269 true
270 }
271}
272
273fn remove_value(u: i64, v: i64, d: &mut Tdomain, gp: &Tgraph, gt: &Tgraph) -> bool {
277 for j in 0..gp.succ[u as usize].len() {
278 let s = gp.succ[u as usize][j];
279 d.add_to_filter(s, gp.nb_vertices);
280 }
281 let old_pos = d.pos(u, v);
282 d.nb_val[u as usize] -= 1;
283 let new_pos = d.first_val[u as usize] + d.nb_val[u as usize];
284 d.val[old_pos as usize] = d.val[new_pos as usize];
285 d.val[new_pos as usize] = v;
286 let a = d.val[old_pos as usize];
287 let b = d.val[new_pos as usize];
288 d.set_pos(u, a, old_pos);
289 d.set_pos(u, b, new_pos);
290 if d.global_matching_p[u as usize] == v {
291 d.global_matching_p[u as usize] = -1;
292 d.global_matching_t[v as usize] = -1;
293 augmenting_path(u, d, gt.nb_vertices)
294 } else {
295 true
296 }
297}
298
299fn match_vertices(
304 mut stack: Vec<i64>,
305 induced: bool,
306 d: &mut Tdomain,
307 gp: &Tgraph,
308 gt: &Tgraph,
309) -> bool {
310 while let Some(u) = stack.pop() {
311 let v = d.val[d.first_val[u as usize] as usize];
312 for u2 in 0..gp.nb_vertices {
313 if u == u2 {
314 continue;
315 }
316 let old_nb_val = d.nb_val[u2 as usize];
317 if d.is_in_d(u2, v) && !remove_value(u2, v, d, gp, gt) {
318 return true;
319 }
320 if gp.is_edge(u, u2) {
321 let mut j = d.first_val[u2 as usize];
322 while j < d.first_val[u2 as usize] + d.nb_val[u2 as usize] {
323 let val_j = d.val[j as usize];
324 if gt.is_edge(v, val_j) {
325 j += 1;
326 } else if !remove_value(u2, val_j, d, gp, gt) {
327 return true;
328 }
329 }
330 } else if induced {
331 if d.nb_val[u2 as usize] < gt.nb_succ[v as usize] {
332 let mut j = d.first_val[u2 as usize];
333 while j < d.first_val[u2 as usize] + d.nb_val[u2 as usize] {
334 let val_j = d.val[j as usize];
335 if !gt.is_edge(v, val_j) {
336 j += 1;
337 } else if !remove_value(u2, val_j, d, gp, gt) {
338 return true;
339 }
340 }
341 } else {
342 for j in 0..gt.nb_succ[v as usize] {
343 let v2 = gt.succ[v as usize][j as usize];
344 if d.is_in_d(u2, v2) && !remove_value(u2, v2, d, gp, gt) {
345 return true;
346 }
347 }
348 }
349 }
350 if d.nb_val[u2 as usize] == 0 {
351 return true;
352 }
353 if d.nb_val[u2 as usize] == 1 && old_nb_val > 1 {
354 stack.push(u2);
355 }
356 }
357 }
358 false
359}
360
361fn match_vertex(u: i64, induced: bool, d: &mut Tdomain, gp: &Tgraph, gt: &Tgraph) -> bool {
365 !match_vertices(vec![u], induced, d, gp, gt)
366}
367
368fn compare(mu: &mut [i64], mv: &mut [i64]) -> bool {
371 mu.sort_unstable();
372 mv.sort_unstable();
373 let mut i = mv.len() as i64 - 1;
374 for j in (0..mu.len()).rev() {
375 if mu[j] > mv[i as usize] {
376 return false;
377 }
378 i -= 1;
379 }
380 true
381}
382
383#[allow(clippy::too_many_lines)]
388fn init_domains(domains: Option<&[Vec<u32>]>, gp: &Tgraph, gt: &Tgraph) -> (Tdomain, bool) {
389 let nb_p = gp.nb_vertices;
390 let nb_t = gt.nb_vertices;
391 let mut val: Vec<i64> = Vec::new();
392 let mut nb_val = vec![0i64; nb_p as usize];
393 let mut first_val = vec![0i64; nb_p as usize];
394 let mut pos_in_val = vec![0i64; (nb_p * nb_t) as usize];
395 let mut first_match = vec![0i64; (nb_p * nb_t) as usize];
396 let global_matching_p = vec![-1i64; nb_p as usize];
397 let global_matching_t = vec![-1i64; nb_t as usize];
398 let mut to_filter = vec![0i64; nb_p as usize];
399 let mut marked_to_filter = vec![false; nb_p as usize];
400
401 let mut val_size: i64 = 0;
402 let mut matching_size: i64 = 0;
403 let mut dom = vec![false; nb_t as usize];
404
405 for u in 0..nb_p {
406 if let Some(doms) = domains {
407 dom.fill(false);
408 for &v in &doms[u as usize] {
409 dom[v as usize] = true;
410 }
411 }
412 marked_to_filter[u as usize] = true;
413 to_filter[u as usize] = u;
414 nb_val[u as usize] = 0;
415 first_val[u as usize] = val_size;
416 for v in 0..nb_t {
417 if domains.is_some() && !dom[v as usize] {
418 pos_in_val[(u * nb_t + v) as usize] = first_val[u as usize] + nb_t;
419 } else {
420 first_match[(u * nb_t + v) as usize] = matching_size;
421 matching_size += gp.nb_succ[u as usize];
422 if gp.nb_succ[u as usize] <= gt.nb_succ[v as usize] {
423 let mut mu: Vec<i64> = gp.succ[u as usize]
424 .iter()
425 .map(|&w| gp.nb_succ[w as usize])
426 .collect();
427 let mut mv: Vec<i64> = gt.succ[v as usize]
428 .iter()
429 .map(|&w| gt.nb_succ[w as usize])
430 .collect();
431 if compare(&mut mu, &mut mv) {
432 val.push(v);
433 nb_val[u as usize] += 1;
434 pos_in_val[(u * nb_t + v) as usize] = val_size;
435 val_size += 1;
436 } else {
437 pos_in_val[(u * nb_t + v) as usize] = first_val[u as usize] + nb_t;
438 }
439 } else {
440 pos_in_val[(u * nb_t + v) as usize] = first_val[u as usize] + nb_t;
441 }
442 }
443 }
444 if nb_val[u as usize] == 0 {
445 let d = Tdomain {
446 nb_t,
447 nb_val,
448 first_val,
449 val: Vec::new(),
450 pos_in_val,
451 first_match,
452 matching: Vec::new(),
453 next_out_to_filter: -1,
454 last_in_to_filter: 0,
455 to_filter,
456 marked_to_filter,
457 global_matching_p,
458 global_matching_t,
459 };
460 return (d, true);
461 }
462 }
463
464 let matching = vec![-1i64; matching_size as usize];
465 let d = Tdomain {
466 nb_t,
467 nb_val,
468 first_val,
469 val,
470 pos_in_val,
471 first_match,
472 matching,
473 next_out_to_filter: 0,
474 last_in_to_filter: nb_p - 1,
475 to_filter,
476 marked_to_filter,
477 global_matching_p,
478 global_matching_t,
479 };
480 (d, false)
481}
482
483const WHITE: i64 = 0;
484const GREY: i64 = 1;
485const BLACK: i64 = 2;
486const TO_BE_DELETED: i64 = 3;
487const DELETED: i64 = 4;
488
489fn add_to_delete(u: i64, list: &mut [i64], nb: &mut i64, marked: &mut [i64]) {
490 if marked[u as usize] < TO_BE_DELETED {
491 list[*nb as usize] = u;
492 *nb += 1;
493 marked[u as usize] = TO_BE_DELETED;
494 }
495}
496
497#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
501fn update_matching(
502 size_of_u: i64,
503 size_of_v: i64,
504 degree: &[i64],
505 first_adj: &[i64],
506 adj: &[i64],
507 matched_with_u: &mut [i64],
508) -> bool {
509 if size_of_u > size_of_v {
510 return true;
511 }
512 let su = size_of_u as usize;
513 let sv = size_of_v as usize;
514 let mut matched_with_v = vec![-1i64; sv];
515 let mut nb_pred = vec![0i64; sv];
516 let mut pred = vec![0i64; sv * su];
517 let mut nb_succ = vec![0i64; su];
518 let mut succ = vec![0i64; su * sv];
519 let mut list_v = vec![0i64; sv];
520 let mut list_u = vec![0i64; su];
521 let mut list_dv = vec![0i64; sv];
522 let mut list_du = vec![0i64; su];
523 let mut marked_v = vec![0i64; sv];
524 let mut marked_u = vec![0i64; su];
525 let mut unmatched = vec![0i64; su];
526 let mut pos_in_unmatched = vec![0i64; su];
527 let mut path: Vec<i64> = Vec::new();
528 let mut nb_unmatched: i64 = 0;
529
530 for u in 0..su {
531 if matched_with_u[u] >= 0 {
532 matched_with_v[matched_with_u[u] as usize] = u as i64;
533 } else {
534 pos_in_unmatched[u] = nb_unmatched;
535 unmatched[nb_unmatched as usize] = u as i64;
536 nb_unmatched += 1;
537 }
538 }
539
540 let mut j: i64 = 0;
542 while j < nb_unmatched {
543 let u = unmatched[j as usize];
544 let start = first_adj[u as usize];
545 let end = start + degree[u as usize];
546 let mut i = start;
547 while i < end && matched_with_v[adj[i as usize] as usize] >= 0 {
548 i += 1;
549 }
550 if i == end {
551 j += 1;
552 } else {
553 let v = adj[i as usize];
554 matched_with_u[u as usize] = v;
555 matched_with_v[v as usize] = u;
556 nb_unmatched -= 1;
557 unmatched[j as usize] = unmatched[nb_unmatched as usize];
558 pos_in_unmatched[unmatched[j as usize] as usize] = j;
559 }
560 }
561
562 while nb_unmatched > 0 {
563 marked_u.fill(WHITE);
565 nb_succ.fill(0);
566 marked_v.fill(WHITE);
567 nb_pred.fill(0);
568 let mut nb_v: i64 = 0;
569 for jj in 0..nb_unmatched {
570 let u = unmatched[jj as usize];
571 marked_u[u as usize] = BLACK;
572 let start = first_adj[u as usize];
573 let end = start + degree[u as usize];
574 for i in start..end {
575 let v = adj[i as usize];
576 pred[(v * size_of_u + nb_pred[v as usize]) as usize] = u;
577 nb_pred[v as usize] += 1;
578 succ[(u * size_of_v + nb_succ[u as usize]) as usize] = v;
579 nb_succ[u as usize] += 1;
580 if marked_v[v as usize] == WHITE {
581 marked_v[v as usize] = GREY;
582 list_v[nb_v as usize] = v;
583 nb_v += 1;
584 }
585 }
586 }
587 let mut stop = false;
588 while !stop && nb_v > 0 {
589 let mut nb_u: i64 = 0;
590 for i in 0..nb_v {
591 let v = list_v[i as usize];
592 marked_v[v as usize] = BLACK;
593 let u = matched_with_v[v as usize];
594 if marked_u[u as usize] == WHITE {
595 marked_u[u as usize] = GREY;
596 list_u[nb_u as usize] = u;
597 nb_u += 1;
598 }
599 }
600 nb_v = 0;
601 for jj in 0..nb_u {
602 let u = list_u[jj as usize];
603 marked_u[u as usize] = BLACK;
604 let start = first_adj[u as usize];
605 let end = start + degree[u as usize];
606 for i in start..end {
607 let v = adj[i as usize];
608 if marked_v[v as usize] != BLACK {
609 pred[(v * size_of_u + nb_pred[v as usize]) as usize] = u;
610 nb_pred[v as usize] += 1;
611 succ[(u * size_of_v + nb_succ[u as usize]) as usize] = v;
612 nb_succ[u as usize] += 1;
613 if marked_v[v as usize] == WHITE {
614 marked_v[v as usize] = GREY;
615 list_v[nb_v as usize] = v;
616 nb_v += 1;
617 }
618 if matched_with_v[v as usize] == -1 {
619 stop = true;
620 }
621 }
622 }
623 }
624 }
625 if nb_v == 0 {
626 return true;
627 }
628
629 for k in 0..nb_v {
631 let mut v = list_v[k as usize];
632 if matched_with_v[v as usize] == -1 && nb_pred[v as usize] > 0 {
633 path.clear();
634 path.push(v);
635 let mut nb_dv: i64 = 0;
636 let mut nb_du: i64 = 0;
637 add_to_delete(v, &mut list_dv, &mut nb_dv, &mut marked_v);
638 let mut u;
639 loop {
640 u = pred[(v * size_of_u) as usize];
641 path.push(u);
642 add_to_delete(u, &mut list_du, &mut nb_du, &mut marked_u);
643 if matched_with_u[u as usize] != -1 {
644 v = matched_with_u[u as usize];
645 path.push(v);
646 add_to_delete(v, &mut list_dv, &mut nb_dv, &mut marked_v);
647 }
648 if matched_with_u[u as usize] == -1 {
649 break;
650 }
651 }
652
653 while nb_dv > 0 || nb_du > 0 {
654 while nb_dv > 0 {
655 nb_dv -= 1;
656 let vv = list_dv[nb_dv as usize];
657 marked_v[vv as usize] = DELETED;
658 let uu = matched_with_v[vv as usize];
659 if uu != -1 {
660 add_to_delete(uu, &mut list_du, &mut nb_du, &mut marked_u);
661 }
662 for i in 0..nb_pred[vv as usize] {
663 let up = pred[(vv * size_of_u + i) as usize];
664 let mut jj: i64 = 0;
665 while jj < nb_succ[up as usize]
666 && vv != succ[(up * size_of_v + jj) as usize]
667 {
668 jj += 1;
669 }
670 nb_succ[up as usize] -= 1;
671 succ[(up * size_of_v + jj) as usize] =
672 succ[(up * size_of_v + nb_succ[up as usize]) as usize];
673 if nb_succ[up as usize] == 0 {
674 add_to_delete(up, &mut list_du, &mut nb_du, &mut marked_u);
675 }
676 }
677 }
678 while nb_du > 0 {
679 nb_du -= 1;
680 let uu = list_du[nb_du as usize];
681 marked_u[uu as usize] = DELETED;
682 let vv = matched_with_u[uu as usize];
683 if vv != -1 {
684 add_to_delete(vv, &mut list_dv, &mut nb_dv, &mut marked_v);
685 }
686 for i in 0..nb_succ[uu as usize] {
687 let vp = succ[(uu * size_of_v + i) as usize];
688 let mut jj: i64 = 0;
689 while jj < nb_pred[vp as usize]
690 && uu != pred[(vp * size_of_u + jj) as usize]
691 {
692 jj += 1;
693 }
694 nb_pred[vp as usize] -= 1;
695 pred[(vp * size_of_u + jj) as usize] =
696 pred[(vp * size_of_u + nb_pred[vp as usize]) as usize];
697 if nb_pred[vp as usize] == 0 {
698 add_to_delete(vp, &mut list_dv, &mut nb_dv, &mut marked_v);
699 }
700 }
701 }
702 }
703 let last = path[path.len() - 1];
704 let i = pos_in_unmatched[last as usize];
705 nb_unmatched -= 1;
706 unmatched[i as usize] = unmatched[nb_unmatched as usize];
707 pos_in_unmatched[unmatched[i as usize] as usize] = i;
708 while path.len() > 1 {
709 let pu = path.pop().unwrap_or(-1);
710 let pv = path.pop().unwrap_or(-1);
711 matched_with_u[pu as usize] = pv;
712 matched_with_v[pv as usize] = pu;
713 }
714 }
715 }
716 }
717 false
718}
719
720#[allow(clippy::too_many_arguments)]
722fn dfs(
723 nb_u: i64,
724 u: i64,
725 marked: &mut [bool],
726 nb_succ: &[i64],
727 succ: &[i64],
728 matched_with_u: &[i64],
729 order: &mut [i64],
730 nb: &mut i64,
731) {
732 let v = matched_with_u[u as usize];
733 marked[u as usize] = true;
734 if v >= 0 {
735 for i in 0..nb_succ[v as usize] {
736 let w = succ[(v * nb_u + i) as usize];
737 if !marked[w as usize] {
738 dfs(nb_u, w, marked, nb_succ, succ, matched_with_u, order, nb);
739 }
740 }
741 }
742 order[*nb as usize] = u;
743 *nb -= 1;
744}
745
746#[allow(clippy::too_many_arguments)]
750fn scc(
751 nb_u: i64,
752 nb_v: i64,
753 num_v: &mut [i64],
754 num_u: &mut [i64],
755 nb_succ: &[i64],
756 succ: &[i64],
757 nb_pred: &[i64],
758 pred: &[i64],
759 matched_with_u: &[i64],
760 matched_with_v: &[i64],
761) {
762 let mut order = vec![0i64; nb_u as usize];
763 let mut marked = vec![false; nb_u as usize];
764 let mut fifo = vec![0i64; nb_v as usize];
765 let mut nb = nb_u - 1;
766 for u in 0..nb_u {
767 if !marked[u as usize] {
768 dfs(
769 nb_u,
770 u,
771 &mut marked,
772 nb_succ,
773 succ,
774 matched_with_u,
775 &mut order,
776 &mut nb,
777 );
778 }
779 }
780 let mut nb_scc: i64 = 0;
781 for x in num_u.iter_mut() {
782 *x = -1;
783 }
784 for x in num_v.iter_mut() {
785 *x = -1;
786 }
787 for i in 0..nb_u {
788 let u0 = order[i as usize];
789 let v0 = matched_with_u[u0 as usize];
790 if v0 == -1 {
791 continue;
792 }
793 if num_v[v0 as usize] == -1 {
794 nb_scc += 1;
795 let mut k: i64 = 1;
796 fifo[0] = v0;
797 num_v[v0 as usize] = nb_scc;
798 while k > 0 {
799 k -= 1;
800 let v = fifo[k as usize];
801 let u = matched_with_v[v as usize];
802 if u != -1 {
803 num_u[u as usize] = nb_scc;
804 for j in 0..nb_pred[u as usize] {
805 let vp = pred[(u * nb_v + j) as usize];
806 if num_v[vp as usize] == -1 {
807 num_v[vp as usize] = nb_scc;
808 fifo[k as usize] = vp;
809 k += 1;
810 }
811 }
812 }
813 }
814 }
815 }
816}
817
818#[allow(clippy::too_many_lines)]
822fn ensure_gac_all_diff(induced: bool, gp: &Tgraph, gt: &Tgraph, d: &mut Tdomain) -> bool {
823 let nb_p = gp.nb_vertices;
824 let nb_t = gt.nb_vertices;
825 let mut nb_pred = vec![0i64; nb_p as usize];
826 let mut pred = vec![0i64; (nb_p * nb_t) as usize];
827 let mut nb_succ = vec![0i64; nb_t as usize];
828 let mut succ = vec![0i64; (nb_t * nb_p) as usize];
829 let mut num_v = vec![0i64; nb_t as usize];
830 let mut num_u = vec![0i64; nb_p as usize];
831 let mut used = vec![false; (nb_p * nb_t) as usize];
832 let mut list = vec![0i64; nb_t as usize];
833 let mut nb: i64 = 0;
834
835 for u in 0..nb_p {
836 let fv = d.first_val[u as usize];
837 for i in 0..d.nb_val[u as usize] {
838 let v = d.val[(fv + i) as usize];
839 used[(u * nb_t + v) as usize] = false;
840 if v != d.global_matching_p[u as usize] {
841 pred[(u * nb_t + nb_pred[u as usize]) as usize] = v;
842 nb_pred[u as usize] += 1;
843 succ[(v * nb_p + nb_succ[v as usize]) as usize] = u;
844 nb_succ[v as usize] += 1;
845 }
846 }
847 }
848
849 for v in 0..nb_t {
851 if d.global_matching_t[v as usize] < 0 {
852 list[nb as usize] = v;
853 nb += 1;
854 num_v[v as usize] = 1;
855 }
856 }
857 while nb > 0 {
858 nb -= 1;
859 let v = list[nb as usize];
860 for i in 0..nb_succ[v as usize] {
861 let u = succ[(v * nb_p + i) as usize];
862 used[(u * nb_t + v) as usize] = true;
863 if num_u[u as usize] == 0 {
864 num_u[u as usize] = 1;
865 let w = d.global_matching_p[u as usize];
866 used[(u * nb_t + w) as usize] = true;
867 if num_v[w as usize] == 0 {
868 list[nb as usize] = w;
869 nb += 1;
870 num_v[w as usize] = 1;
871 }
872 }
873 }
874 }
875
876 scc(
877 nb_p,
878 nb_t,
879 &mut num_v,
880 &mut num_u,
881 &nb_succ,
882 &succ,
883 &nb_pred,
884 &pred,
885 &d.global_matching_p,
886 &d.global_matching_t,
887 );
888
889 let mut to_match: Vec<i64> = Vec::new();
890 for u in 0..nb_p {
891 let old_nb_val = d.nb_val[u as usize];
892 let mut i: i64 = 0;
898 while i < d.nb_val[u as usize] {
899 let v = d.val[(d.first_val[u as usize] + i) as usize];
900 if !used[(u * nb_t + v) as usize]
901 && num_v[v as usize] != num_u[u as usize]
902 && d.global_matching_p[u as usize] != v
903 && !remove_value(u, v, d, gp, gt)
904 {
905 return true;
906 }
907 i += 1;
908 }
909 if d.nb_val[u as usize] == 0 {
910 return true;
911 }
912 if old_nb_val > 1 && d.nb_val[u as usize] == 1 {
913 to_match.push(u);
914 }
915 }
916 match_vertices(to_match, induced, d, gp, gt)
917}
918
919#[allow(clippy::too_many_lines)]
922fn check_lad(u: i64, v: i64, d: &mut Tdomain, gp: &Tgraph, gt: &Tgraph) -> bool {
923 let nb_succ_u = gp.nb_succ[u as usize];
924 let fm = d.first_match(u, v);
925
926 if nb_succ_u == 1 {
928 let u2 = gp.succ[u as usize][0];
929 let v2 = d.matching[fm as usize];
930 if v2 != -1 && d.is_in_d(u2, v2) {
931 return true;
932 }
933 let start = d.first_val[u2 as usize];
934 let end = start + d.nb_val[u2 as usize];
935 for i in start..end {
936 if gt.is_edge(v, d.val[i as usize]) {
937 d.matching[fm as usize] = d.val[i as usize];
938 return true;
939 }
940 }
941 return false;
942 }
943
944 let mut nb_matched = 0i64;
946 for i in 0..nb_succ_u {
947 let u2 = gp.succ[u as usize][i as usize];
948 let v2 = d.matching[(fm + i) as usize];
949 if v2 != -1 && d.is_in_d(u2, v2) {
950 nb_matched += 1;
951 }
952 }
953 if nb_matched == nb_succ_u {
954 return true;
955 }
956
957 let nb_t = gt.nb_vertices;
958 let mut num = vec![-1i64; nb_t as usize];
959 let mut num_inv = vec![0i64; nb_t as usize];
960 let mut nb_comp = vec![0i64; nb_succ_u as usize];
961 let mut first_comp = vec![0i64; nb_succ_u as usize];
962 let mut comp = vec![0i64; (nb_succ_u * nb_t) as usize];
963 let mut matched_with_u = vec![0i64; nb_succ_u as usize];
964 let mut nb_num = 0i64;
965 let mut pos_in_comp = 0i64;
966
967 for i in 0..nb_succ_u {
968 let u2 = gp.succ[u as usize][i as usize];
969 nb_comp[i as usize] = 0;
970 first_comp[i as usize] = pos_in_comp;
971 if d.nb_val[u2 as usize] > gt.nb_succ[v as usize] {
972 let start = d.first_val[u2 as usize];
973 let end = start + d.nb_val[u2 as usize];
974 for j in start..end {
975 let v2 = d.val[j as usize];
976 if gt.is_edge(v, v2) {
977 if num[v2 as usize] < 0 {
978 num[v2 as usize] = nb_num;
979 num_inv[nb_num as usize] = v2;
980 nb_num += 1;
981 }
982 comp[pos_in_comp as usize] = num[v2 as usize];
983 pos_in_comp += 1;
984 nb_comp[i as usize] += 1;
985 }
986 }
987 } else {
988 for j in 0..gt.nb_succ[v as usize] {
989 let v2 = gt.succ[v as usize][j as usize];
990 if d.is_in_d(u2, v2) {
991 if num[v2 as usize] < 0 {
992 num[v2 as usize] = nb_num;
993 num_inv[nb_num as usize] = v2;
994 nb_num += 1;
995 }
996 comp[pos_in_comp as usize] = num[v2 as usize];
997 pos_in_comp += 1;
998 nb_comp[i as usize] += 1;
999 }
1000 }
1001 }
1002 if nb_comp[i as usize] == 0 {
1003 return false;
1004 }
1005 let v2 = d.matching[(fm + i) as usize];
1006 matched_with_u[i as usize] = if v2 != -1 && d.is_in_d(u2, v2) {
1007 num[v2 as usize]
1008 } else {
1009 -1
1010 };
1011 }
1012
1013 let invalid = update_matching(
1014 nb_succ_u,
1015 nb_num,
1016 &nb_comp,
1017 &first_comp,
1018 &comp,
1019 &mut matched_with_u,
1020 );
1021 if invalid {
1022 return false;
1023 }
1024 for i in 0..nb_succ_u {
1025 d.matching[(fm + i) as usize] = num_inv[matched_with_u[i as usize] as usize];
1026 }
1027 true
1028}
1029
1030fn filter(induced: bool, d: &mut Tdomain, gp: &Tgraph, gt: &Tgraph) -> bool {
1033 while !d.to_filter_empty() {
1034 while !d.to_filter_empty() {
1035 let u = d.next_to_filter(gp.nb_vertices);
1036 let old_nb_val = d.nb_val[u as usize];
1037 let mut i = d.first_val[u as usize];
1038 while i < d.first_val[u as usize] + d.nb_val[u as usize] {
1039 let v = d.val[i as usize];
1040 if check_lad(u, v, d, gp, gt) {
1041 i += 1;
1042 } else if !remove_value(u, v, d, gp, gt) {
1043 return false;
1044 }
1045 }
1046 if d.nb_val[u as usize] == 1 && old_nb_val > 1 && !match_vertex(u, induced, d, gp, gt) {
1047 return false;
1048 }
1049 if d.nb_val[u as usize] == 0 {
1050 return false;
1051 }
1052 }
1053 if ensure_gac_all_diff(induced, gp, gt, d) {
1054 return false;
1055 }
1056 }
1057 true
1058}
1059
1060struct Solver {
1062 first_sol: bool,
1063 iso: bool,
1064 map: Vec<i64>,
1065 maps: Vec<Vec<i64>>,
1066 want_map: bool,
1067 want_maps: bool,
1068 nb_sol: i64,
1069}
1070
1071fn solve(induced: bool, d: &mut Tdomain, gp: &Tgraph, gt: &Tgraph, s: &mut Solver) {
1075 let nb_p = gp.nb_vertices;
1076 if !filter(induced, d, gp, gt) {
1077 d.reset_to_filter();
1078 return;
1079 }
1080
1081 let mut nb_val_snapshot = vec![0i64; nb_p as usize];
1082 let mut global_snapshot = vec![0i64; nb_p as usize];
1083 let mut min_dom = -1i64;
1084 for u in 0..nb_p {
1085 nb_val_snapshot[u as usize] = d.nb_val[u as usize];
1086 if d.nb_val[u as usize] > 1
1087 && (min_dom < 0 || d.nb_val[u as usize] < d.nb_val[min_dom as usize])
1088 {
1089 min_dom = u;
1090 }
1091 global_snapshot[u as usize] = d.global_matching_p[u as usize];
1092 }
1093
1094 if min_dom == -1 {
1095 s.iso = true;
1097 s.nb_sol += 1;
1098 if s.want_map && s.map.is_empty() {
1099 s.map = (0..nb_p)
1100 .map(|u| d.val[d.first_val[u as usize] as usize])
1101 .collect();
1102 }
1103 if s.want_maps {
1104 let m: Vec<i64> = (0..nb_p)
1105 .map(|u| d.val[d.first_val[u as usize] as usize])
1106 .collect();
1107 s.maps.push(m);
1108 }
1109 d.reset_to_filter();
1110 return;
1111 }
1112
1113 let nb_min = nb_val_snapshot[min_dom as usize];
1114 let mut branch_vals = vec![0i64; nb_min as usize];
1115 for i in 0..nb_min {
1116 branch_vals[i as usize] = d.val[(d.first_val[min_dom as usize] + i) as usize];
1117 }
1118
1119 let mut i = 0i64;
1120 while i < nb_min && (!s.first_sol || s.nb_sol == 0) {
1121 let v = branch_vals[i as usize];
1122 let ok = remove_all_values_but_one(min_dom, v, d, gp, gt)
1123 && match_vertex(min_dom, induced, d, gp, gt);
1124 if ok {
1125 solve(induced, d, gp, gt, s);
1126 } else {
1127 d.reset_to_filter();
1128 }
1129 for x in &mut d.global_matching_t {
1131 *x = -1;
1132 }
1133 for u in 0..nb_p {
1134 d.nb_val[u as usize] = nb_val_snapshot[u as usize];
1135 d.global_matching_p[u as usize] = global_snapshot[u as usize];
1136 d.global_matching_t[global_snapshot[u as usize] as usize] = u;
1137 }
1138 i += 1;
1139 }
1140}
1141
1142fn lad_engine(
1146 pattern: &Graph,
1147 target: &Graph,
1148 domains: Option<&[Vec<u32>]>,
1149 induced: bool,
1150 first_sol: bool,
1151) -> IgraphResult<(bool, Vec<u32>, Vec<Vec<u32>>)> {
1152 if pattern.is_directed() != target.is_directed() {
1153 return Err(IgraphError::InvalidArgument(
1154 "Cannot search for a directed pattern in an undirected target or vice versa.".into(),
1155 ));
1156 }
1157
1158 let want_maps = !first_sol;
1159 let n_pat = pattern.vcount();
1160
1161 if n_pat == 0 {
1163 let maps = if want_maps {
1164 vec![Vec::new()]
1165 } else {
1166 Vec::new()
1167 };
1168 return Ok((true, Vec::new(), maps));
1169 }
1170
1171 if let Some(doms) = domains {
1172 if doms.len() != n_pat as usize {
1173 return Err(IgraphError::InvalidArgument(format!(
1174 "Domain list length ({}) must match the number of pattern vertices ({}).",
1175 doms.len(),
1176 n_pat
1177 )));
1178 }
1179 let n_t = target.vcount();
1180 for d in doms {
1181 for &v in d {
1182 if v >= n_t {
1183 return Err(IgraphError::InvalidArgument(format!(
1184 "Domain contains target vertex id {v} out of range (target has {n_t} vertices)."
1185 )));
1186 }
1187 }
1188 }
1189 }
1190
1191 let gp = create_graph(pattern)?;
1192 let gt = create_graph(target)?;
1193
1194 if gp.nb_vertices > gt.nb_vertices {
1195 return Ok((false, Vec::new(), Vec::new()));
1196 }
1197
1198 let (mut d, invalid_domain) = init_domains(domains, &gp, >);
1199 if invalid_domain {
1200 return Ok((false, Vec::new(), Vec::new()));
1201 }
1202
1203 let nb_p = gp.nb_vertices;
1205 let mut gmp = std::mem::take(&mut d.global_matching_p);
1206 let invalid = update_matching(
1207 gp.nb_vertices,
1208 gt.nb_vertices,
1209 &d.nb_val,
1210 &d.first_val,
1211 &d.val,
1212 &mut gmp,
1213 );
1214 d.global_matching_p = gmp;
1215 if invalid {
1216 return Ok((false, Vec::new(), Vec::new()));
1217 }
1218
1219 if ensure_gac_all_diff(induced, &gp, >, &mut d) {
1220 return Ok((false, Vec::new(), Vec::new()));
1221 }
1222
1223 for u in 0..nb_p {
1224 let v = d.global_matching_p[u as usize];
1225 d.global_matching_t[v as usize] = u;
1226 }
1227
1228 let mut to_match: Vec<i64> = Vec::new();
1229 for u in 0..nb_p {
1230 if d.nb_val[u as usize] == 1 {
1231 to_match.push(u);
1232 }
1233 }
1234 if match_vertices(to_match, induced, &mut d, &gp, >) {
1235 return Ok((false, Vec::new(), Vec::new()));
1236 }
1237
1238 let mut solver = Solver {
1239 first_sol,
1240 iso: false,
1241 map: Vec::new(),
1242 maps: Vec::new(),
1243 want_map: !want_maps,
1244 want_maps,
1245 nb_sol: 0,
1246 };
1247 solve(induced, &mut d, &gp, >, &mut solver);
1248
1249 let map_u32: Vec<u32> = solver.map.iter().map(|&x| x as u32).collect();
1250 let maps_u32: Vec<Vec<u32>> = solver
1251 .maps
1252 .iter()
1253 .map(|m| m.iter().map(|&x| x as u32).collect())
1254 .collect();
1255 Ok((solver.iso, map_u32, maps_u32))
1256}
1257
1258pub fn subisomorphic_lad(
1292 pattern: &Graph,
1293 target: &Graph,
1294 domains: Option<&[Vec<u32>]>,
1295 induced: bool,
1296) -> IgraphResult<LadSubisomorphism> {
1297 let (iso, map, _) = lad_engine(pattern, target, domains, induced, true)?;
1298 Ok(LadSubisomorphism { iso, map })
1299}
1300
1301pub fn get_subisomorphisms_lad(
1325 pattern: &Graph,
1326 target: &Graph,
1327 domains: Option<&[Vec<u32>]>,
1328 induced: bool,
1329) -> IgraphResult<Vec<Vec<u32>>> {
1330 let (_, _, maps) = lad_engine(pattern, target, domains, induced, false)?;
1331 Ok(maps)
1332}
1333
1334#[cfg(test)]
1335mod tests {
1336 use super::*;
1337
1338 fn undirected(n: u32, edges: &[(u32, u32)]) -> Graph {
1339 let mut g = Graph::new(n, false).expect("graph init");
1340 for &(a, b) in edges {
1341 g.add_edge(a, b).expect("edge in range");
1342 }
1343 g
1344 }
1345
1346 fn example_graphs() -> (Graph, Graph) {
1348 let target = undirected(
1349 9,
1350 &[
1351 (0, 1),
1352 (0, 4),
1353 (0, 6),
1354 (1, 4),
1355 (1, 2),
1356 (2, 3),
1357 (3, 4),
1358 (3, 5),
1359 (3, 7),
1360 (3, 8),
1361 (4, 5),
1362 (4, 6),
1363 (5, 6),
1364 (5, 8),
1365 (7, 8),
1366 ],
1367 );
1368 let pattern = undirected(5, &[(0, 1), (0, 4), (1, 4), (1, 2), (2, 3), (3, 4)]);
1369 (pattern, target)
1370 }
1371
1372 fn sorted(mut maps: Vec<Vec<u32>>) -> Vec<Vec<u32>> {
1373 maps.sort();
1374 maps
1375 }
1376
1377 fn is_valid_embedding(map: &[u32], pattern: &Graph, target: &Graph) -> bool {
1379 if map.len() != pattern.vcount() as usize {
1380 return false;
1381 }
1382 let mut seen = std::collections::HashSet::new();
1384 if !map.iter().all(|&v| seen.insert(v)) {
1385 return false;
1386 }
1387 for eid in 0..pattern.ecount() {
1388 let (a, b) = pattern.edge(eid as EdgeId).expect("pattern edge");
1389 if target
1390 .find_eid(map[a as usize], map[b as usize])
1391 .expect("valid target vertices")
1392 .is_none()
1393 {
1394 return false;
1395 }
1396 }
1397 true
1398 }
1399
1400 #[test]
1401 fn example_oracle_monomorphism() {
1402 let (pattern, target) = example_graphs();
1403 let maps = get_subisomorphisms_lad(&pattern, &target, None, false).expect("lad");
1404 assert_eq!(maps.len(), 20, "monomorphism count from the C example");
1405 for m in &maps {
1406 assert!(is_valid_embedding(m, &pattern, &target));
1407 }
1408 }
1409
1410 #[test]
1411 fn example_oracle_induced() {
1412 let (pattern, target) = example_graphs();
1413 let maps = get_subisomorphisms_lad(&pattern, &target, None, true).expect("lad");
1414 let expected = sorted(vec![
1415 vec![0, 1, 2, 3, 4],
1416 vec![5, 3, 2, 1, 4],
1417 vec![5, 4, 1, 2, 3],
1418 vec![0, 4, 3, 2, 1],
1419 ]);
1420 assert_eq!(
1421 sorted(maps),
1422 expected,
1423 "induced embeddings from the C example"
1424 );
1425 }
1426
1427 #[test]
1428 fn example_oracle_domains() {
1429 let (pattern, target) = example_graphs();
1430 let domains = vec![
1431 vec![0u32, 2, 8],
1432 vec![4, 5, 6, 7],
1433 vec![1, 3, 5, 6, 7, 8],
1434 vec![0, 2, 8],
1435 vec![1, 3, 7, 8],
1436 ];
1437 let maps = get_subisomorphisms_lad(&pattern, &target, Some(&domains), false).expect("lad");
1438 assert_eq!(
1439 maps,
1440 vec![vec![0u32, 4, 3, 2, 1]],
1441 "domain-restricted embedding"
1442 );
1443 }
1444
1445 #[test]
1446 fn first_solution_mode() {
1447 let (pattern, target) = example_graphs();
1448 let res = subisomorphic_lad(&pattern, &target, None, false).expect("lad");
1449 assert!(res.iso);
1450 assert!(is_valid_embedding(&res.map, &pattern, &target));
1451 }
1452
1453 #[test]
1454 fn null_pattern_is_trivially_iso() {
1455 let target = undirected(3, &[(0, 1), (1, 2)]);
1456 let empty = Graph::new(0, false).expect("graph init");
1457 let res = subisomorphic_lad(&empty, &target, None, false).expect("lad");
1458 assert!(res.iso);
1459 assert!(res.map.is_empty());
1460 let maps = get_subisomorphisms_lad(&empty, &target, None, false).expect("lad");
1461 assert_eq!(maps, vec![Vec::<u32>::new()]);
1462 }
1463
1464 #[test]
1465 fn pattern_larger_than_target() {
1466 let pattern = undirected(4, &[(0, 1), (1, 2), (2, 3)]);
1467 let target = undirected(3, &[(0, 1), (1, 2)]);
1468 let res = subisomorphic_lad(&pattern, &target, None, false).expect("lad");
1469 assert!(!res.iso);
1470 assert!(res.map.is_empty());
1471 }
1472
1473 #[test]
1474 fn triangle_not_in_path() {
1475 let triangle = undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1476 let path = undirected(3, &[(0, 1), (1, 2)]);
1477 let res = subisomorphic_lad(&triangle, &path, None, false).expect("lad");
1478 assert!(!res.iso);
1479 }
1480
1481 #[test]
1482 fn edge_into_triangle_has_six_maps() {
1483 let target = undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1484 let pattern = undirected(2, &[(0, 1)]);
1485 let maps = get_subisomorphisms_lad(&pattern, &target, None, false).expect("lad");
1486 assert_eq!(maps.len(), 6);
1487 }
1488
1489 #[test]
1490 fn multi_edge_rejected() {
1491 let mut pattern = Graph::new(2, false).expect("graph init");
1492 pattern.add_edge(0, 1).expect("edge");
1493 pattern.add_edge(0, 1).expect("edge");
1494 let target = undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1495 assert!(subisomorphic_lad(&pattern, &target, None, false).is_err());
1496 }
1497
1498 #[test]
1499 fn directedness_mismatch_rejected() {
1500 let pattern = Graph::new(2, true).expect("graph init");
1501 let target = undirected(3, &[(0, 1), (1, 2)]);
1502 assert!(subisomorphic_lad(&pattern, &target, None, false).is_err());
1503 }
1504}