1+ /*
2+ Copyright 2024 Huawei Technologies Co., Ltd.
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+
9+ http://www.apache.org/licenses/LICENSE-2.0
10+
11+ Unless required by applicable law or agreed to in writing, software
12+ distributed under the License is distributed on an "AS IS" BASIS,
13+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ See the License for the specific language governing permissions and
15+ limitations under the License.
16+ */
17+ #pragma once
18+
19+ #include < functional>
20+ #include < stdexcept>
21+ #include < unordered_map>
22+ #include < unordered_set>
23+ #include < queue>
24+ #include < vector>
25+
26+ #include " MerkleHashComputer.hpp"
27+ #include " osp/graph_algorithms/directed_graph_util.hpp"
28+
29+ namespace osp {
30+
31+ /* *
32+ * @brief Finds a correct isomorphic mapping between a known "representative"
33+ * subgraph and a new "current" subgraph, assuming they are isomorphic.
34+ *
35+ * This class uses a backtracking algorithm pruned by Merkle hashes to
36+ * efficiently find the vertex-to-vertex mapping.
37+ *
38+ * @tparam Graph_t The original graph type (for global vertex IDs).
39+ * @tparam Constr_Graph_t The subgraph/contracted graph type.
40+ */
41+ template <typename Graph_t, typename Constr_Graph_t>
42+ class IsomorphismMapper {
43+
44+ using VertexC = vertex_idx_t <Constr_Graph_t>; // Local vertex ID
45+ using VertexG = vertex_idx_t <Graph_t>; // Global vertex ID
46+
47+ const Constr_Graph_t& rep_graph;
48+ const MerkleHashComputer<Constr_Graph_t> rep_hasher;
49+
50+ public:
51+ /* *
52+ * @brief Constructs an IsomorphismMapper.
53+ * @param representative_graph The subgraph to use as the "pattern".
54+ */
55+ IsomorphismMapper (const Constr_Graph_t& representative_graph)
56+ : rep_graph(representative_graph), rep_hasher(representative_graph),
57+ num_vertices (representative_graph.num_vertices()) {}
58+
59+ virtual ~IsomorphismMapper () = default ;
60+
61+ /* *
62+ * @brief Finds the isomorphism between the representative graph and a new graph.
63+ *
64+ * This method assumes the two graphs are isomorphic and finds one such mapping.
65+ *
66+ * @param current_graph The new isomorphic subgraph.
67+ * @return A map from `current_local_vertex_id` -> `representative_local_vertex_id`.
68+ */
69+ std::unordered_map<VertexC, VertexC> find_mapping (const Constr_Graph_t& current_graph) const {
70+ if (current_graph.num_vertices () != num_vertices) {
71+ throw std::runtime_error (" IsomorphismMapper: Graph sizes do not match." );
72+ }
73+ if (num_vertices == 0 ) {
74+ return {};
75+ }
76+
77+ // 1. Compute hashes and orbits for the current graph.
78+ MerkleHashComputer<Constr_Graph_t> current_hasher (current_graph);
79+ const auto & rep_orbits = rep_hasher.get_orbits ();
80+ const auto & current_orbits = current_hasher.get_orbits ();
81+
82+ // 2. Verify that the orbit structures are identical.
83+ if (rep_orbits.size () != current_orbits.size ()) {
84+ throw std::runtime_error (" IsomorphismMapper: Graphs have a different number of orbits." );
85+ }
86+ for (const auto & [hash, rep_orbit_nodes] : rep_orbits) {
87+ auto it = current_orbits.find (hash);
88+ if (it == current_orbits.end () || it->second .size () != rep_orbit_nodes.size ()) {
89+ throw std::runtime_error (" IsomorphismMapper: Mismatched orbit structure between graphs." );
90+ }
91+ }
92+
93+ // 3. Iteratively map all components of the graph.
94+ std::vector<VertexC> map_current_to_rep (num_vertices, std::numeric_limits<VertexC>::max ());
95+ std::vector<bool > rep_is_mapped (num_vertices, false );
96+ std::vector<bool > current_is_mapped (num_vertices, false );
97+ size_t mapped_count = 0 ;
98+
99+ while (mapped_count < num_vertices) {
100+ std::queue<std::pair<VertexC, VertexC>> q;
101+
102+ // Find an unmapped vertex in the representative graph to seed the next component traversal.
103+ VertexC rep_seed = std::numeric_limits<VertexC>::max ();
104+ for (VertexC i = 0 ; i < num_vertices; ++i) {
105+ if (!rep_is_mapped[i]) {
106+ rep_seed = i;
107+ break ;
108+ }
109+ }
110+
111+ if (rep_seed == std::numeric_limits<VertexC>::max ()) break ; // Should be unreachable if mapped_count < num_vertices
112+
113+ // Find a corresponding unmapped vertex in the current graph's orbit.
114+ const auto & candidates = current_orbits.at (rep_hasher.get_vertex_hash (rep_seed));
115+ VertexC current_seed = std::numeric_limits<VertexC>::max (); // Should always be found
116+ for (const auto & candidate : candidates) {
117+ if (!current_is_mapped[candidate]) {
118+ current_seed = candidate;
119+ break ;
120+ }
121+ }
122+ if (current_seed == std::numeric_limits<VertexC>::max ()) {
123+ throw std::runtime_error (" IsomorphismMapper: Could not find an unmapped candidate to seed component mapping." );
124+ }
125+
126+ // Seed the queue and start the traversal for this component.
127+ q.push ({rep_seed, current_seed});
128+ map_current_to_rep[rep_seed] = current_seed;
129+ rep_is_mapped[rep_seed] = true ;
130+ current_is_mapped[current_seed] = true ;
131+ mapped_count++;
132+
133+ while (!q.empty ()) {
134+ auto [u_rep, u_curr] = q.front ();
135+ q.pop ();
136+
137+ // Match neighbors (both parents and children)
138+ match_neighbors (current_graph, current_hasher, u_rep, u_curr, map_current_to_rep, rep_is_mapped, current_is_mapped, mapped_count, q, true );
139+ match_neighbors (current_graph, current_hasher, u_rep, u_curr, map_current_to_rep, rep_is_mapped, current_is_mapped, mapped_count, q, false );
140+ }
141+ }
142+
143+ if (mapped_count != num_vertices) {
144+ throw std::runtime_error (" IsomorphismMapper: Failed to map all vertices." );
145+ }
146+
147+ // 4. Return the inverted map.
148+ std::unordered_map<VertexC, VertexC> current_local_to_rep_local;
149+ current_local_to_rep_local.reserve (num_vertices);
150+ for (VertexC i = 0 ; i < num_vertices; ++i) current_local_to_rep_local[map_current_to_rep[i]] = i;
151+ return current_local_to_rep_local;
152+ }
153+
154+ private:
155+ const size_t num_vertices;
156+
157+ void match_neighbors (const Constr_Graph_t& current_graph, const MerkleHashComputer<Constr_Graph_t>& current_hasher,
158+ VertexC u_rep, VertexC u_curr, std::vector<VertexC>& map_current_to_rep,
159+ std::vector<bool >& rep_is_mapped, std::vector<bool >& current_is_mapped,
160+ size_t & mapped_count, std::queue<std::pair<VertexC, VertexC>>& q, bool match_children) const {
161+
162+ const auto & rep_neighbors_range = match_children ? rep_graph.children (u_rep) : rep_graph.parents (u_rep);
163+ const auto & curr_neighbors_range = match_children ? current_graph.children (u_curr) : current_graph.parents (u_curr);
164+
165+ for (const auto & v_rep : rep_neighbors_range) {
166+ if (rep_is_mapped[v_rep]) continue ;
167+
168+ for (const auto & v_curr : curr_neighbors_range) {
169+ if (current_is_mapped[v_curr]) continue ;
170+
171+ if (rep_hasher.get_vertex_hash (v_rep) == current_hasher.get_vertex_hash (v_curr)) {
172+ map_current_to_rep[v_rep] = v_curr;
173+ rep_is_mapped[v_rep] = true ;
174+ current_is_mapped[v_curr] = true ;
175+ mapped_count++;
176+ q.push ({v_rep, v_curr});
177+ break ; // Found a match for v_rep, move to the next rep neighbor.
178+ }
179+ }
180+ }
181+ }
182+ };
183+
184+ } // namespace osp
0 commit comments