NuriKit v0.1.0b2
Loading...
Searching...
No Matches
vf2pp.h
Go to the documentation of this file.
1//
2// Project NuriKit - Copyright 2024 SNU Compbio Lab.
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#ifndef NURI_CORE_GRAPH_VF2PP_H_
7#define NURI_CORE_GRAPH_VF2PP_H_
8
10#include <algorithm>
11#include <tuple>
12#include <utility>
13#include <vector>
14
15#include <absl/algorithm/container.h>
16#include <absl/base/optimization.h>
17#include <absl/log/absl_check.h>
18#include <Eigen/Dense>
20
21#include "nuri/eigen_config.h"
23#include "nuri/utils.h"
24
25namespace nuri {
29enum class IsoMapType : int {
36};
37
38namespace internal {
39 template <class GT, bool = internal::GraphTraits<GT>::is_degree_constant_time>
40 class Vf2ppDegreeHelper;
41
42 template <class GT>
43 class Vf2ppDegreeHelper<GT, true> {
44 public:
45 explicit Vf2ppDegreeHelper(const GT &graph): graph_(&graph) { }
46
47 int operator[](int i) const { return graph_->degree(i); }
48
49 private:
50 const GT *graph_;
51 };
52
53 template <class GT>
54 class Vf2ppDegreeHelper<GT, false> {
55 public:
56 explicit Vf2ppDegreeHelper(const GT &graph): degree_(graph.size()) {
57 for (int i = 0; i < graph.size(); ++i)
58 degree_[i] = graph.degree(i);
59 }
60
61 int operator[](int i) const { return degree_[i]; }
62
63 private:
64 ArrayXi degree_;
65 };
66
67 template <class GT, class AL1, class AL2>
68 int vf2pp_process_bfs_tree(const GT &query,
69 const Vf2ppDegreeHelper<GT> &degrees,
70 ArrayXi &order, ArrayXb &visited, AL1 &curr_conn,
71 AL2 &query_cnts, int root, int i) {
72 order[i] = root;
73 visited[root] = true;
74
75 int lp = i, rp = i + 1, wp = i + 1;
76
77 for (; i < wp; ++i) {
78 int curr = order[i];
79
80 for (auto nei: query.node(curr)) {
81 if (!visited[nei.dst().id()]) {
82 order[wp++] = nei.dst().id();
83 visited[nei.dst().id()] = true;
84 }
85 }
86
87 if (i < rp)
88 continue;
89
90 for (int j = lp; j < rp; ++j) {
91 int min_pos = j;
92 for (int k = j + 1; k < rp; ++k) {
93 if (curr_conn[order[min_pos]] < curr_conn[order[k]]
94 || (curr_conn[order[min_pos]] == curr_conn[order[k]]
95 && (degrees[order[min_pos]] < degrees[order[k]]
96 || (degrees[order[min_pos]] == degrees[order[k]]
97 && query_cnts[order[min_pos]]
98 > query_cnts[order[k]])))) {
99 min_pos = k;
100 }
101 }
102
103 --query_cnts[order[min_pos]];
104 for (auto nei: query.node(order[min_pos]))
105 ++curr_conn[nei.dst().id()];
106
107 std::swap(order[j], order[min_pos]);
108 }
109
110 lp = rp;
111 rp = wp;
112 }
113
114 return i;
115 }
116
117 template <class GT, class AL>
118 ArrayXi vf2pp_init_order(const GT &query, ConstRef<ArrayXi> qlbl,
119 ConstRef<ArrayXi> tlbl, ArrayXi &target_lcnt,
120 // NOLINTNEXTLINE(*-missing-std-forward)
121 AL &&curr_conn) {
122 ArrayXi order = ArrayXi::Constant(query.size(), -1);
123
124 for (int l: tlbl)
125 ++target_lcnt[l];
126 auto query_cnts = target_lcnt(qlbl);
127
128 Vf2ppDegreeHelper<GT> degrees(query);
129 ArrayXb visited = ArrayXb::Zero(query.size());
130 int iorder = 0;
131 for (int i = 0; iorder < query.size() && i < query.size();) {
132 if (visited[i]) {
133 ++i;
134 continue;
135 }
136
137 int imin = i;
138 for (int j = i + 1; j < query.size(); ++j) {
139 if (!visited[j]
140 && (query_cnts[imin] > query_cnts[j]
141 || (query_cnts[imin] == query_cnts[j]
142 && degrees[imin] < degrees[j]))) {
143 imin = j;
144 }
145 }
146
147 iorder = vf2pp_process_bfs_tree(query, degrees, order, visited, curr_conn,
148 query_cnts, imin, iorder);
149 }
150
151 return order;
152 }
153
154 using Vf2ppLabels = std::vector<std::pair<int, int>>;
155 using Vf2ppLabelMap = std::vector<Vf2ppLabels>;
156
157 template <class GT, class AL>
158 std::pair<Vf2ppLabelMap, Vf2ppLabelMap>
159 vf2pp_init_r_new_r_inout(const GT &query, ConstRef<ArrayXi> qlbl,
160 const ArrayXi &order, ArrayXi &r_inout,
161 // NOLINTNEXTLINE(*-missing-std-forward)
162 ArrayXi &r_new, AL &&visit_count) {
163 // r_inout == _labelTmp1, r_new == _labelTmp2
164 ABSL_DCHECK_EQ(r_inout.size(), r_new.size());
165
166 Vf2ppLabelMap r_inout_labels(query.size()), r_new_labels(query.size());
167
168 r_inout.setZero();
169 r_new.setZero();
170 visit_count.setZero();
171 for (const int i: order) {
172 visit_count[i] = -1;
173
174 for (auto nei: query.node(i)) {
175 const int curr = nei.dst().id();
176 if (visit_count[curr] > 0) {
177 ++r_inout[qlbl[curr]];
178 } else if (visit_count[curr] == 0) {
179 ++r_new[qlbl[curr]];
180 }
181 }
182
183 for (auto nei: query.node(i)) {
184 const int curr = nei.dst().id();
185 const int curr_lbl = qlbl[curr];
186
187 if (r_inout[curr_lbl] > 0) {
188 r_inout_labels[i].push_back({ curr_lbl, r_inout[curr_lbl] });
189 r_inout[curr_lbl] = 0;
190 } else if (r_new[curr_lbl] > 0) {
191 r_new_labels[i].push_back({ curr_lbl, r_new[curr_lbl] });
192 r_new[curr_lbl] = 0;
193 }
194
195 if (visit_count[curr] >= 0)
196 ++visit_count[curr];
197 }
198 }
199
200 return { r_inout_labels, r_new_labels };
201 }
202
203 template <IsoMapType kMt>
204 bool vf2pp_r_matches(const Vf2ppLabels &r_node, const ArrayXi &label_tmp) {
205 return absl::c_none_of(r_node, [&](std::pair<int, int> p) {
206 return kMt == IsoMapType::kGraph ? label_tmp[p.first] != 0
207 : label_tmp[p.first] > 0;
208 });
209 }
210} // namespace internal
211
269template <IsoMapType kMt, class GT, class GU>
270class VF2pp {
271private:
272 const GT &query() const { return *query_; }
273 const GU &target() const { return *target_; }
274
275 auto query_tmp() { return node_tmp_.head(query_->size()); }
276
277 ArrayXi &conn() { return node_tmp_; }
278
279 auto curr_node(int i) const { return query_->node(order_[i]); }
280 auto curr_node() const { return curr_node(depth_); }
281
282 int mapped_node(int i) const { return node_map_[curr_node(i).id()]; }
283 int mapped_node() const { return mapped_node(depth_); }
284
285 auto query_target_ait() const { return query_target_ait_[depth_]; }
286 void update_ait(typename GT::const_adjacency_iterator qa,
287 typename GU::const_adjacency_iterator ta) {
288 query_target_ait_[depth_] = { qa, ta };
289 }
290
291 ArrayXi &r_inout_cnt() { return label_tmp1_; }
292 ArrayXi &r_new_cnt() { return label_tmp2_; }
293
294public:
316 template <class AL1, class AL2>
317 VF2pp(const GT &query, const GU &target, AL1 &&query_lbl, AL2 &&target_lbl)
318 : query_(&query), target_(&target), //
319 qlbl_(std::forward<AL1>(query_lbl)),
320 tlbl_(std::forward<AL2>(target_lbl)),
321 node_map_(ArrayXi::Constant(query.size(), -1)),
322 edge_map_(ArrayXi::Constant(query.num_edges(), -1)),
323 query_target_ait_(query.size(),
324 { query.adj_end(0), target.adj_end(0) }),
325 node_tmp_(ArrayXi::Zero(target.size())) {
326 ABSL_DCHECK(!query.empty());
327 ABSL_DCHECK_LE(query.size(), target.size());
328
329 const int nlabel = nuri::max(qlbl_.maxCoeff(), tlbl_.maxCoeff()) + 1;
330
331 label_tmp1_ = ArrayXi::Zero(nlabel);
332 label_tmp2_ = ArrayXi::Zero(nlabel);
333
334 order_ = internal::vf2pp_init_order(query, qlbl_, tlbl_, label_tmp1_,
335 query_tmp());
336 std::tie(r_inout_, r_new_) = internal::vf2pp_init_r_new_r_inout(
337 query, qlbl_, order_, r_inout_cnt(), r_new_cnt(), query_tmp());
338
339 query_tmp().setZero();
340 }
341
367 template <class NodeMatch, class EdgeMatch>
368 bool next(const NodeMatch &node_match, const EdgeMatch &edge_match) {
369 while (depth_ >= 0) {
370 if (depth_ == query().size()) {
371 if (map_remaining_edges(edge_match)) {
372#ifdef NURI_DEBUG
373 first_ = false;
374#endif
375 ++depth_;
376 return true;
377 }
378
379 --depth_;
380 } else if (depth_ > query().size()) {
381#ifdef NURI_DEBUG
382 ABSL_DCHECK(!first_);
383#endif
384 // previous call to next() returned true
385 clear_stale_maps(node_match, edge_match);
386 }
387
388 const auto qn = curr_node();
389 const int ti = mapped_node();
390
391 auto [qa, ta] = query_target_ait();
392 if (!ta.end()) {
393 ABSL_DCHECK(!qa.end());
394 ABSL_DCHECK_NE(ti, ta->src().id());
395
396 sub_pair(qn.id(), ti, qa->eid());
397 ++ta;
398 } else {
399 if (ti < 0) {
400 auto qb = absl::c_find_if(qn, [&](auto nei) {
401 return node_map_[nei.dst().id()] >= 0;
402 });
403 if (!qb.end())
404 qa = qb->dst().find_adjacent(qn);
405 } else {
406 sub_pair(qn.id(), ti, qa.end() ? -1 : qa->eid());
407 }
408
409 if (qa.end() || ti >= 0) {
410 const int tj =
411 std::find_if(target().begin() + ti + 1, target().end(),
412 [&](auto tn) {
413 bool candidate = kMt == IsoMapType::kSubgraph
414 ? conn()[tn.id()] >= 0
415 : conn()[tn.id()] == 0;
416 return candidate && feas(qn, tn, node_match);
417 })
418 - target().begin();
419
420 if (tj < target().size()) {
421 add_pair(qn.id(), tj, -1, -1);
422 ++depth_;
423 } else {
424 --depth_;
425 }
426
427 continue;
428 }
429
430 ta = target().adj_begin(node_map_[qa->src().id()]);
431 }
432
433 ABSL_DCHECK(ta.end() || !qa.end());
434
435 for (; !ta.end(); ++ta) {
436 auto tn = ta->dst();
437 if (conn()[tn.id()] > 0 //
438 && feas(qn, tn, node_match)
439 && edge_match(query().edge(qa->eid()), target().edge(ta->eid()))) {
440 add_pair(qn.id(), tn.id(), qa->eid(), ta->eid());
441 break;
442 }
443 }
444
445 update_ait(qa, ta);
446 ta.end() ? --depth_ : ++depth_;
447 }
448
449 return false;
450 }
451
459 const ArrayXi &node_map() const & { return node_map_; }
460
470 ArrayXi &&node_map() && { return std::move(node_map_); }
471
479 const ArrayXi &edge_map() const & { return edge_map_; }
480
490 ArrayXi &&edge_map() && { return std::move(edge_map_); }
491
492private:
493 void add_pair(const int qn, const int tn, const int qe, const int te) {
494 ABSL_DCHECK_GE(tn, 0);
495 ABSL_DCHECK_LT(tn, target().size());
496
497 node_map_[qn] = tn;
498 if (qe >= 0)
499 edge_map_[qe] = te;
500
501 conn()[tn] = -1;
502 for (auto nei: target().node(tn)) {
503 if (conn()[nei.dst().id()] != -1)
504 ++conn()[nei.dst().id()];
505 }
506 }
507
508 void sub_pair(const int qn, const int tn, const int qe) {
509 ABSL_DCHECK_GE(tn, 0);
510 ABSL_DCHECK_LT(tn, target().size());
511
512 node_map_[qn] = -1;
513 if (qe >= 0)
514 edge_map_[qe] = -1;
515
516 conn()[tn] = 0;
517 for (auto nei: target().node(tn)) {
518 int curr_conn = conn()[nei.dst().id()];
519 if (curr_conn > 0) {
520 --conn()[nei.dst().id()];
521 } else if (curr_conn == -1) {
522 ++conn()[tn];
523 }
524 }
525 }
526
527 bool cut_by_labels(const typename GT::ConstNodeRef qn,
528 const typename GU::ConstNodeRef tn) {
529 // zero init
530 for (auto [lbl, _]: r_inout_[qn.id()])
531 r_inout_cnt()[lbl] = 0;
532 if constexpr (kMt != IsoMapType::kSubgraph) {
533 for (auto [lbl, _]: r_new_[qn.id()])
534 r_new_cnt()[lbl] = 0;
535 }
536
537 for (auto nei: tn) {
538 const int curr = nei.dst().id();
539 if (conn()[curr] > 0) {
540 --r_inout_cnt()[tlbl_[curr]];
541 } else if constexpr (kMt != IsoMapType::kSubgraph) {
542 if (conn()[curr] == 0)
543 --r_new_cnt()[tlbl_[curr]];
544 }
545 }
546
547 for (auto [lbl, cnt]: r_inout_[qn.id()])
548 r_inout_cnt()[lbl] += cnt;
549 if constexpr (kMt != IsoMapType::kSubgraph) {
550 for (auto [lbl, cnt]: r_new_[qn.id()])
551 r_new_cnt()[lbl] += cnt;
552 }
553
554 const bool r_inout_match =
555 internal::vf2pp_r_matches<kMt>(r_inout_[qn.id()], r_inout_cnt());
556 if constexpr (kMt == IsoMapType::kSubgraph)
557 return r_inout_match;
558
559 return r_inout_match
560 && internal::vf2pp_r_matches<kMt>(r_new_[qn.id()], r_new_cnt());
561 }
562
563 template <class NodeMatch>
564 bool feas(const typename GT::ConstNodeRef qn,
565 const typename GU::ConstNodeRef tn, const NodeMatch &node_match) {
566 if (qlbl_[qn.id()] != tlbl_[tn.id()])
567 return false;
568
569 for (auto qnei: qn)
570 if (node_map_[qnei.dst().id()] >= 0)
571 --conn()[node_map_[qnei.dst().id()]];
572
573 bool is_iso = true;
574 for (auto tnei: tn) {
575 const int curr_conn = conn()[tnei.dst().id()];
576 if (curr_conn < -1) {
577 ++conn()[tnei.dst().id()];
578 } else if constexpr (kMt != IsoMapType::kSubgraph) {
579 if (curr_conn == -1) {
580 is_iso = false;
581 break;
582 }
583 }
584 }
585
586 if (!is_iso) {
587 for (auto qnei: qn) {
588 const int ti = node_map_[qnei.dst().id()];
589 if (ti >= 0)
590 conn()[ti] = -1;
591 }
592 return false;
593 }
594
595 for (auto qnei: qn) {
596 const int ti = node_map_[qnei.dst().id()];
597 if (ti < 0 || conn()[ti] == -1)
598 continue;
599
600 const int curr_conn = conn()[ti];
601 conn()[ti] = -1;
602 if constexpr (kMt != IsoMapType::kSubgraph)
603 return false;
604
605 if (curr_conn < -1)
606 return false;
607 }
608
609 return node_match(qn, tn) && cut_by_labels(qn, tn);
610 }
611
612 constexpr bool is_stale(const typename GT::ConstEdgeRef qe,
613 const typename GU::ConstEdgeRef te) const {
614#ifdef NURI_DEBUG
615 const int curr_src = node_map_[qe.src().id()],
616 curr_dst = node_map_[qe.dst().id()];
617
618 const bool stale =
619 (curr_src != te.src().id() || curr_dst != te.dst().id())
620 && (curr_src != te.dst().id() || curr_dst != te.src().id());
621
622 ABSL_DCHECK(!first_ || !stale) << qe.id() << " " << te.id();
623
624 return stale;
625#else
626 static_cast<void>(*this);
627 static_cast<void>(qe);
628 static_cast<void>(te);
629
630 return false;
631#endif
632 }
633
634 template <class EdgeMatch>
635 bool map_remaining_edges(const EdgeMatch &edge_match) {
636 for (auto qe: query().edges()) {
637 if (edge_map_[qe.id()] >= 0) {
638 ABSL_DCHECK(!is_stale(qe, target().edge(edge_map_[qe.id()])));
639 continue;
640 }
641
642 auto teit = target().find_edge(target().node(node_map_[qe.src().id()]),
643 target().node(node_map_[qe.dst().id()]));
644 ABSL_DCHECK(teit != target().edge_end());
645
646 if (!edge_match(qe, *teit))
647 return false;
648
649 edge_map_[qe.id()] = teit->id();
650 }
651
652 return true;
653 }
654
655 template <class NodeMatch, class EdgeMatch>
656 void clear_stale_maps(const NodeMatch &node_match,
657 const EdgeMatch &edge_match) {
658 // clear first, some edges are mapped after BFS search
659 edge_map_.setConstant(-1);
660
661 // Starting from the deepest stack frame, find first stale node/edge match.
662 // Must be started from the deepest frame because the following frames
663 // depend on the mapping of the current frame. This will stop at first
664 // iteration if non-overlapping match was requested.
665 //
666 // Last entry will be popped off after this function returns, so must be
667 // excluded in this function (< query().size() - 1)
668 int i = 0;
669 for (; i < query().size() - 1; ++i) {
670 int ti = mapped_node(i);
671 ABSL_DCHECK_GE(ti, 0);
672 // check for stale nodes, if non-overlapping node match was requested
673 if (!node_match(curr_node(i), target().node(ti)))
674 break;
675
676 auto [qa, ta] = query_target_ait_[i];
677 if (qa.end())
678 continue;
679
680 // check for stale edges, if non-overlapping edge match was requested
681 if (!edge_match(query().edge(qa->eid()), target().edge(ta->eid())))
682 break;
683
684 ABSL_DCHECK(!ta.end());
685 edge_map_[qa->eid()] = ta->eid();
686 }
687
688 // Simulate stack pop when any of node/edge match fails
689 // Same here, last edge will be popped off so must be excluded (depth_ > i)
690 for (depth_ = query().size() - 1; depth_ > i; --depth_) {
691 auto qn = curr_node();
692 int ti = mapped_node();
693 auto [qa, _] = query_target_ait();
694
695 ABSL_DCHECK_GE(ti, 0);
696 sub_pair(qn.id(), ti, qa.end() ? -1 : qa->eid());
697 update_ait(query().adj_end(0), target().adj_end(0));
698 }
699 }
700
701 const GT *query_;
702 const GU *target_;
703
704 Eigen::Ref<const ArrayXi> qlbl_, tlbl_;
705
706 ArrayXi node_map_, edge_map_;
707
708 // query.size()
709 ArrayXi order_;
710 internal::Vf2ppLabelMap r_inout_, r_new_;
711 std::vector<std::pair<typename GT::const_adjacency_iterator,
712 typename GU::const_adjacency_iterator>>
713 query_target_ait_;
714
715 // max(label) + 1
716 ArrayXi label_tmp1_, label_tmp2_;
717
718 // max(query.size(), target.size())
719 ArrayXi node_tmp_;
720
721 int depth_ = 0;
722#ifdef NURI_DEBUG
723 bool first_ = true;
724#endif
725};
726
742template <IsoMapType kMt, class GT, class GU, class AL1, class AL2>
743VF2pp<kMt, GT, GU> make_vf2pp(const GT &query, const GU &target, AL1 &&qlbl,
744 AL2 &&tlbl) {
745 return VF2pp<kMt, GT, GU>(query, target, std::forward<AL1>(qlbl),
746 std::forward<AL2>(tlbl));
747}
748
750 ArrayXi node_map;
751 ArrayXi edge_map;
752 bool found;
753};
754
779template <IsoMapType kMt, class GT, class GU, class AL1, class AL2,
780 class NodeMatch, class EdgeMatch>
781VF2ppResult vf2pp(const GT &query, const GU &target, AL1 &&qlbl, AL2 &&tlbl,
782 const NodeMatch &node_match, const EdgeMatch &edge_match) {
784 query, target, std::forward<AL1>(qlbl), std::forward<AL2>(tlbl));
785 bool found = vf2pp.next(node_match, edge_match);
786 return { std::move(vf2pp).node_map(), std::move(vf2pp).edge_map(), found };
787}
788
814template <class GT, class GU, class NodeMatch, class EdgeMatch, class AL1,
815 class AL2>
816VF2ppResult vf2pp(const GT &query, const GU &target, AL1 &&qlbl, AL2 &&tlbl,
817 const NodeMatch &node_match, const EdgeMatch &edge_match,
818 IsoMapType mt) {
819 switch (mt) {
822 query, target, //
823 std::forward<AL1>(qlbl), std::forward<AL2>(tlbl), //
824 node_match, edge_match);
827 query, target, //
828 std::forward<AL1>(qlbl), std::forward<AL2>(tlbl), //
829 node_match, edge_match);
832 query, target, //
833 std::forward<AL1>(qlbl), std::forward<AL2>(tlbl), //
834 node_match, edge_match);
835 }
836
837 ABSL_UNREACHABLE();
838}
839
863template <IsoMapType kMt, class GT, class GU, class NodeMatch, class EdgeMatch>
864VF2ppResult vf2pp(const GT &query, const GU &target,
865 const NodeMatch &node_match, const EdgeMatch &edge_match) {
866 ArrayXi label = ArrayXi::Zero(nuri::max(query.size(), target.size()));
867 return vf2pp<kMt>(query, target, label.head(query.size()),
868 label.head(target.size()), node_match, edge_match);
869}
870
895template <class GT, class GU, class NodeMatch, class EdgeMatch>
896VF2ppResult vf2pp(const GT &query, const GU &target,
897 const NodeMatch &node_match, const EdgeMatch &edge_match,
898 IsoMapType mt) {
899 ArrayXi label = ArrayXi::Zero(nuri::max(query.size(), target.size()));
900 return vf2pp(query, target, label.head(query.size()),
901 label.head(target.size()), node_match, edge_match, mt);
902}
903} // namespace nuri
904
905#endif /* NURI_CORE_GRAPH_VF2PP_H_ */
An implementation of VF2++ algorithm for (sub)graph isomorphism.
Definition vf2pp.h:270
VF2pp(const GT &query, const GU &target, AL1 &&query_lbl, AL2 &&target_lbl)
Prepare VF2++ algorithm.
Definition vf2pp.h:317
ArrayXi && edge_map() &&
Move out current edge mapping.
Definition vf2pp.h:490
const ArrayXi & node_map() const &
Get current node mapping.
Definition vf2pp.h:459
bool next(const NodeMatch &node_match, const EdgeMatch &edge_match)
Find next subgraph mapping.
Definition vf2pp.h:368
ArrayXi && node_map() &&
Move out current node mapping.
Definition vf2pp.h:470
const ArrayXi & edge_map() const &
Get current edge mapping.
Definition vf2pp.h:479
Definition crdgen.h:16
VF2ppResult vf2pp(const GT &query, const GU &target, AL1 &&qlbl, AL2 &&tlbl, const NodeMatch &node_match, const EdgeMatch &edge_match)
Find a query-to-target subgraph mapping.
Definition vf2pp.h:781
IsoMapType
The type of isomorphic map to find.
Definition vf2pp.h:29
@ kGraph
Graph isomorphism.
Definition vf2pp.h:35
@ kSubgraph
Subgraph isomorphism.
Definition vf2pp.h:31
@ kInduced
Induced subgraph isomorphism.
Definition vf2pp.h:33
auto edges(const Graph< NT, ET > &g)
Definition adaptor.h:273
boost::graph_traits< Graph< NT, ET > >::vertex_descriptor target(typename boost::graph_traits< Graph< NT, ET > >::edge_descriptor e, const Graph< NT, ET > &)
Definition adaptor.h:215
VF2pp< kMt, GT, GU > make_vf2pp(const GT &query, const GU &target, AL1 &&qlbl, AL2 &&tlbl)
Prepare VF2++ algorithm.
Definition vf2pp.h:743
constexpr T max(T a, T b)
Definition utils.h:51
auto num_edges(const Graph< NT, ET > &g)
Definition adaptor.h:279
Definition vf2pp.h:749
ArrayXi node_map
Definition vf2pp.h:750
ArrayXi edge_map
Definition vf2pp.h:751
bool found
Definition vf2pp.h:752