6#ifndef NURI_CORE_GRAPH_VF2PP_H_
7#define NURI_CORE_GRAPH_VF2PP_H_
15#include <absl/algorithm/container.h>
16#include <absl/base/optimization.h>
17#include <absl/log/absl_check.h>
39 template <class GT, bool = internal::GraphTraits<GT>::is_degree_constant_time>
40 class Vf2ppDegreeHelper;
43 class Vf2ppDegreeHelper<GT, true> {
45 explicit Vf2ppDegreeHelper(
const GT &graph): graph_(&graph) { }
47 int operator[](
int i)
const {
return graph_->degree(i); }
54 class Vf2ppDegreeHelper<GT, false> {
56 explicit Vf2ppDegreeHelper(
const GT &graph): degree_(graph.size()) {
57 for (
int i = 0; i < graph.size(); ++i)
58 degree_[i] = graph.degree(i);
61 int operator[](
int i)
const {
return degree_[i]; }
67 template <
class GT,
class AL1,
class AL2>
68 int vf2pp_process_bfs_tree(
const GT &query,
69 const Vf2ppDegreeHelper<GT> °rees,
70 ArrayXi &order, ArrayXb &visited, AL1 &curr_conn,
71 AL2 &query_cnts,
int root,
int i) {
75 int lp = i, rp = i + 1, wp = i + 1;
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;
90 for (
int j = lp; j < rp; ++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]])))) {
103 --query_cnts[order[min_pos]];
104 for (
auto nei: query.node(order[min_pos]))
105 ++curr_conn[nei.dst().id()];
107 std::swap(order[j], order[min_pos]);
117 template <
class GT,
class AL>
118 ArrayXi vf2pp_init_order(
const GT &query, ConstRef<ArrayXi> qlbl,
119 ConstRef<ArrayXi> tlbl, ArrayXi &target_lcnt,
122 ArrayXi order = ArrayXi::Constant(query.size(), -1);
126 auto query_cnts = target_lcnt(qlbl);
128 Vf2ppDegreeHelper<GT> degrees(query);
129 ArrayXb visited = ArrayXb::Zero(query.size());
131 for (
int i = 0; iorder < query.size() && i < query.size();) {
138 for (
int j = i + 1; j < query.size(); ++j) {
140 && (query_cnts[imin] > query_cnts[j]
141 || (query_cnts[imin] == query_cnts[j]
142 && degrees[imin] < degrees[j]))) {
147 iorder = vf2pp_process_bfs_tree(query, degrees, order, visited, curr_conn,
148 query_cnts, imin, iorder);
154 using Vf2ppLabels = std::vector<std::pair<int, int>>;
155 using Vf2ppLabelMap = std::vector<Vf2ppLabels>;
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,
162 ArrayXi &r_new, AL &&visit_count) {
164 ABSL_DCHECK_EQ(r_inout.size(), r_new.size());
166 Vf2ppLabelMap r_inout_labels(query.size()), r_new_labels(query.size());
170 visit_count.setZero();
171 for (
const int i: order) {
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) {
183 for (
auto nei: query.node(i)) {
184 const int curr = nei.dst().id();
185 const int curr_lbl = qlbl[curr];
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] });
195 if (visit_count[curr] >= 0)
200 return { r_inout_labels, r_new_labels };
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) {
207 : label_tmp[p.first] > 0;
269template <IsoMapType kMt,
class GT,
class GU>
272 const GT &query()
const {
return *query_; }
273 const GU &target()
const {
return *target_; }
275 auto query_tmp() {
return node_tmp_.head(query_->size()); }
277 ArrayXi &conn() {
return node_tmp_; }
279 auto curr_node(
int i)
const {
return query_->node(order_[i]); }
280 auto curr_node()
const {
return curr_node(depth_); }
282 int mapped_node(
int i)
const {
return node_map_[curr_node(i).id()]; }
283 int mapped_node()
const {
return mapped_node(depth_); }
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 };
291 ArrayXi &r_inout_cnt() {
return label_tmp1_; }
292 ArrayXi &r_new_cnt() {
return label_tmp2_; }
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());
329 const int nlabel =
nuri::max(qlbl_.maxCoeff(), tlbl_.maxCoeff()) + 1;
331 label_tmp1_ = ArrayXi::Zero(nlabel);
332 label_tmp2_ = ArrayXi::Zero(nlabel);
334 order_ = internal::vf2pp_init_order(query, qlbl_, tlbl_, label_tmp1_,
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());
339 query_tmp().setZero();
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)) {
380 }
else if (depth_ > query().size()) {
382 ABSL_DCHECK(!first_);
385 clear_stale_maps(node_match, edge_match);
388 const auto qn = curr_node();
389 const int ti = mapped_node();
391 auto [qa, ta] = query_target_ait();
393 ABSL_DCHECK(!qa.end());
394 ABSL_DCHECK_NE(ti, ta->src().id());
396 sub_pair(qn.id(), ti, qa->eid());
400 auto qb = absl::c_find_if(qn, [&](
auto nei) {
401 return node_map_[nei.dst().id()] >= 0;
404 qa = qb->dst().find_adjacent(qn);
406 sub_pair(qn.id(), ti, qa.end() ? -1 : qa->eid());
409 if (qa.end() || ti >= 0) {
411 std::find_if(target().begin() + ti + 1, target().end(),
414 ? conn()[tn.id()] >= 0
415 : conn()[tn.id()] == 0;
416 return candidate && feas(qn, tn, node_match);
420 if (tj < target().size()) {
421 add_pair(qn.id(), tj, -1, -1);
430 ta = target().adj_begin(node_map_[qa->src().id()]);
433 ABSL_DCHECK(ta.end() || !qa.end());
435 for (; !ta.end(); ++ta) {
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());
446 ta.end() ? --depth_ : ++depth_;
459 const ArrayXi &
node_map() const & {
return node_map_; }
470 ArrayXi &&
node_map() && {
return std::move(node_map_); }
479 const ArrayXi &
edge_map() const & {
return edge_map_; }
490 ArrayXi &&
edge_map() && {
return std::move(edge_map_); }
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());
502 for (
auto nei:
target().node(tn)) {
503 if (conn()[nei.dst().id()] != -1)
504 ++conn()[nei.dst().id()];
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());
517 for (
auto nei: target().node(tn)) {
518 int curr_conn = conn()[nei.dst().id()];
520 --conn()[nei.dst().id()];
521 }
else if (curr_conn == -1) {
527 bool cut_by_labels(
const typename GT::ConstNodeRef qn,
528 const typename GU::ConstNodeRef tn) {
530 for (
auto [lbl, _]: r_inout_[qn.id()])
531 r_inout_cnt()[lbl] = 0;
533 for (
auto [lbl, _]: r_new_[qn.id()])
534 r_new_cnt()[lbl] = 0;
538 const int curr = nei.dst().id();
539 if (conn()[curr] > 0) {
540 --r_inout_cnt()[tlbl_[curr]];
542 if (conn()[curr] == 0)
543 --r_new_cnt()[tlbl_[curr]];
547 for (
auto [lbl, cnt]: r_inout_[qn.id()])
548 r_inout_cnt()[lbl] += cnt;
550 for (
auto [lbl, cnt]: r_new_[qn.id()])
551 r_new_cnt()[lbl] += cnt;
554 const bool r_inout_match =
555 internal::vf2pp_r_matches<kMt>(r_inout_[qn.id()], r_inout_cnt());
557 return r_inout_match;
560 && internal::vf2pp_r_matches<kMt>(r_new_[qn.id()], r_new_cnt());
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()])
570 if (node_map_[qnei.dst().id()] >= 0)
571 --conn()[node_map_[qnei.dst().id()]];
574 for (
auto tnei: tn) {
575 const int curr_conn = conn()[tnei.dst().id()];
576 if (curr_conn < -1) {
577 ++conn()[tnei.dst().id()];
579 if (curr_conn == -1) {
587 for (
auto qnei: qn) {
588 const int ti = node_map_[qnei.dst().id()];
595 for (
auto qnei: qn) {
596 const int ti = node_map_[qnei.dst().id()];
597 if (ti < 0 || conn()[ti] == -1)
600 const int curr_conn = conn()[ti];
609 return node_match(qn, tn) && cut_by_labels(qn, tn);
612 constexpr bool is_stale(
const typename GT::ConstEdgeRef qe,
613 const typename GU::ConstEdgeRef te)
const {
615 const int curr_src = node_map_[qe.src().id()],
616 curr_dst = node_map_[qe.dst().id()];
619 (curr_src != te.src().
id() || curr_dst != te.dst().id())
620 && (curr_src != te.dst().
id() || curr_dst != te.src().id());
622 ABSL_DCHECK(!first_ || !stale) << qe.id() <<
" " << te.id();
626 static_cast<void>(*this);
627 static_cast<void>(qe);
628 static_cast<void>(te);
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()])));
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());
646 if (!edge_match(qe, *teit))
649 edge_map_[qe.id()] = teit->id();
655 template <
class NodeMatch,
class EdgeMatch>
656 void clear_stale_maps(
const NodeMatch &node_match,
657 const EdgeMatch &edge_match) {
659 edge_map_.setConstant(-1);
669 for (; i < query().size() - 1; ++i) {
670 int ti = mapped_node(i);
671 ABSL_DCHECK_GE(ti, 0);
673 if (!node_match(curr_node(i), target().node(ti)))
676 auto [qa, ta] = query_target_ait_[i];
681 if (!edge_match(query().edge(qa->eid()), target().edge(ta->eid())))
684 ABSL_DCHECK(!ta.end());
685 edge_map_[qa->eid()] = ta->eid();
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();
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));
704 Eigen::Ref<const ArrayXi> qlbl_, tlbl_;
706 ArrayXi node_map_, edge_map_;
710 internal::Vf2ppLabelMap r_inout_, r_new_;
711 std::vector<std::pair<
typename GT::const_adjacency_iterator,
712 typename GU::const_adjacency_iterator>>
716 ArrayXi label_tmp1_, label_tmp2_;
742template <IsoMapType kMt,
class GT,
class GU,
class AL1,
class AL2>
746 std::forward<AL2>(tlbl));
779template <
IsoMapType kMt,
class GT,
class GU,
class AL1,
class AL2,
780 class NodeMatch,
class EdgeMatch>
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 };
814template <
class GT,
class GU,
class NodeMatch,
class EdgeMatch,
class AL1,
817 const NodeMatch &node_match,
const EdgeMatch &edge_match,
823 std::forward<AL1>(qlbl), std::forward<AL2>(tlbl),
824 node_match, edge_match);
828 std::forward<AL1>(qlbl), std::forward<AL2>(tlbl),
829 node_match, edge_match);
833 std::forward<AL1>(qlbl), std::forward<AL2>(tlbl),
834 node_match, edge_match);
863template <IsoMapType kMt,
class GT,
class GU,
class NodeMatch,
class EdgeMatch>
865 const NodeMatch &node_match,
const EdgeMatch &edge_match) {
868 label.head(
target.size()), node_match, edge_match);
895template <
class GT,
class GU,
class NodeMatch,
class EdgeMatch>
897 const NodeMatch &node_match,
const EdgeMatch &edge_match,
900 return vf2pp(query,
target, label.head(query.size()),
901 label.head(
target.size()), node_match, edge_match, mt);
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
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
ArrayXi node_map
Definition vf2pp.h:750
ArrayXi edge_map
Definition vf2pp.h:751
bool found
Definition vf2pp.h:752