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+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+
16+ @author Toni Boehnlein, Benjamin Lozes, Pal Andras Papp, Raphael S. Steiner
17+ */
18+
19+ #pragma once
20+
21+ #include < vector>
22+
23+ #include " osp/coarser/coarser_util.hpp"
24+ #include " osp/dag_divider/isomorphism_divider/MerkleHashComputer.hpp"
25+ #include " osp/graph_algorithms/directed_graph_path_util.hpp"
26+ #include " osp/graph_algorithms/directed_graph_util.hpp"
27+ #include " osp/graph_algorithms/subgraph_algorithms.hpp"
28+ #include " osp/graph_algorithms/transitive_reduction.hpp"
29+ #include < numeric>
30+
31+ namespace osp {
32+
33+ /* *
34+ * @class OrbitGraphProcessor
35+ * @brief A simple processor that groups nodes of a DAG based on their Merkle hash.
36+ *
37+ * This class uses a MerkleHashComputer to assign a structural hash to each node.
38+ * It then partitions the DAG by grouping all nodes with the same hash into an "orbit".
39+ * A coarse graph is constructed where each node represents one such orbit.
40+ */
41+ template <typename Graph_t, typename node_hash_func_t = uniform_node_hash_func<vertex_idx_t <Graph_t>>>
42+ class OrbitGraphProcessor {
43+ public:
44+ using VertexType = vertex_idx_t <Graph_t>;
45+
46+ // Represents a group of isomorphic subgraphs, corresponding to a single node in a coarse graph.
47+ struct Group {
48+ // Each vector of vertices represents one of the isomorphic subgraphs in this group.
49+ std::vector<std::vector<VertexType>> subgraphs;
50+
51+ inline size_t size () const { return subgraphs.size (); }
52+ // v_workw_t<Graph_t> work_weight_per_subgraph = 0;
53+ };
54+
55+ private:
56+ using MerkleHashComputer_t = MerkleHashComputer<Graph_t, bwd_merkle_node_hash_func<Graph_t>, true >; // MerkleHashComputer<Graph_t, node_hash_func_t, true>;
57+ // using MerkleHashComputer_t = MerkleHashComputer<Graph_t, node_hash_func_t, true>;
58+
59+ // Results from the first (orbit) coarsening step
60+ Graph_t coarse_graph_;
61+ std::vector<VertexType> contraction_map_;
62+
63+ // Results from the second (custom) coarsening step
64+ Graph_t final_coarse_graph_;
65+ std::vector<VertexType> final_contraction_map_;
66+ std::vector<Group> final_groups_;
67+
68+ // --- Algorithm Parameters ---
69+ size_t symmetry_threshold_ = 2 ;
70+ static constexpr bool verbose = false ;
71+
72+ public:
73+ explicit OrbitGraphProcessor (size_t symmetry_threshold = 2 )
74+ : symmetry_threshold_(symmetry_threshold) {}
75+
76+ /* *
77+ * @brief Sets the minimum number of isomorphic subgraphs a merged group must have.
78+ * @param threshold The symmetry threshold.
79+ */
80+ void set_symmetry_threshold (size_t threshold) {
81+ symmetry_threshold_ = threshold;
82+ }
83+
84+ /* *
85+ * @brief Discovers isomorphic groups (orbits) and constructs a coarse graph.
86+ * @param dag The input computational DAG.
87+ */
88+ void discover_isomorphic_groups (const Graph_t &dag) {
89+ coarse_graph_ = Graph_t ();
90+ contraction_map_.clear ();
91+ final_coarse_graph_ = Graph_t ();
92+ final_contraction_map_.clear ();
93+ final_groups_.clear ();
94+
95+ if (dag.num_vertices () == 0 ) {
96+ return ;
97+ }
98+
99+ MerkleHashComputer_t hasher (dag, dag); // The second 'dag' is for the bwd_merkle_node_hash_func
100+ const auto orbits = hasher.get_orbits ();
101+
102+ contraction_map_.assign (dag.num_vertices (), 0 );
103+ VertexType coarse_node_idx = 0 ;
104+
105+ for (const auto & [hash, vertices] : orbits) {
106+ for (const auto v : vertices) {
107+ contraction_map_[v] = coarse_node_idx;
108+ }
109+ coarse_node_idx++;
110+ }
111+
112+ coarser_util::construct_coarse_dag (dag, coarse_graph_, contraction_map_);
113+
114+ Graph_t transitive_reduction;
115+ transitive_reduction_sparse (coarse_graph_, transitive_reduction);
116+ coarse_graph_ = std::move (transitive_reduction);
117+
118+ // --- Step 2: Perform specialized coarsening on the orbit graph ---
119+ perform_coarsening (dag, coarse_graph_);
120+ }
121+
122+ private:
123+ /* *
124+ * @brief Greedily merges nodes in the orbit graph based on structural and symmetry constraints.
125+ */
126+ void perform_coarsening (const Graph_t& original_dag, const Graph_t& initial_coarse_graph) {
127+ final_coarse_graph_ = Graph_t ();
128+ final_contraction_map_.clear ();
129+
130+ if (initial_coarse_graph.num_vertices () == 0 ) {
131+ return ;
132+ }
133+
134+ Graph_t current_coarse_graph = initial_coarse_graph;
135+ std::vector<Group> current_groups (initial_coarse_graph.num_vertices ());
136+ std::vector<VertexType> current_contraction_map = contraction_map_;
137+
138+ // Initialize groups: each group corresponds to an orbit.
139+ for (VertexType i = 0 ; i < original_dag.num_vertices (); ++i) {
140+ const VertexType coarse_node = contraction_map_[i];
141+ current_groups[coarse_node].subgraphs .push_back ({i});
142+ }
143+
144+
145+ bool changed = true ;
146+ while (changed) {
147+ changed = false ;
148+ for (const auto & edge : edges (current_coarse_graph)) {
149+ VertexType u = source (edge, current_coarse_graph);
150+ VertexType v = target (edge, current_coarse_graph);
151+
152+ if (current_coarse_graph.in_degree (v) != 1 ) {
153+ if constexpr (verbose) { std::cout << " - Skipping edge " << u << " -> " << v << " target in-degree > 1" << std::endl; }
154+ continue ;
155+ }
156+
157+ std::vector<std::vector<VertexType>> new_subgraphs;
158+
159+ // --- Check Constraints ---
160+ // 1. Symmetry Threshold
161+ const bool merge_viable = is_merge_viable (original_dag, current_groups[u], current_groups[v], new_subgraphs);
162+ const bool both_below_symmetry_threshold = (current_groups[u].size () < symmetry_threshold_) && (current_groups[v].size () < symmetry_threshold_);
163+ if (!merge_viable && !both_below_symmetry_threshold) {
164+ if constexpr (verbose) { std::cout << " - Merge of " << u << " and " << v << " not viable (symmetry threshold)\n " ; }
165+ continue ;
166+ }
167+
168+ // 2. Acyclicity & Critical Path
169+ Graph_t temp_coarse_graph;
170+ std::vector<VertexType> temp_contraction_map (current_coarse_graph.num_vertices ());
171+ VertexType new_idx = 0 ;
172+ for (VertexType i = 0 ; i < temp_contraction_map.size (); ++i) {
173+ if (i != v) {
174+ temp_contraction_map[i] = new_idx++;
175+ }
176+ }
177+ // Assign 'v' the same new index as 'u'.
178+ temp_contraction_map[v] = temp_contraction_map[u];
179+ coarser_util::construct_coarse_dag (current_coarse_graph, temp_coarse_graph, temp_contraction_map);
180+
181+ if (!is_acyclic (temp_coarse_graph)) {
182+ if constexpr (verbose) { std::cout << " - Merge of " << u << " and " << v << " creates a cycle. Skipping.\n " ; }
183+ continue ;
184+ }
185+
186+ if (critical_path_weight (temp_coarse_graph) > critical_path_weight (current_coarse_graph)) {
187+ if constexpr (verbose) { std::cout << " - Merge of " << u << " and " << v << " increases critical path. Skipping.\n " ; }
188+ continue ;
189+ }
190+
191+ // --- If all checks pass, execute the merge ---
192+ if constexpr (verbose) { std::cout << " - Merging " << v << " into " << u << " . New coarse graph has " << temp_coarse_graph.num_vertices () << " nodes.\n " ; }
193+ // The new coarse graph is the one we just tested
194+ current_coarse_graph = std::move (temp_coarse_graph);
195+
196+ // Update groups
197+ std::vector<Group> next_groups (current_coarse_graph.num_vertices ());
198+ std::vector<VertexType> group_remap (current_groups.size ());
199+ new_idx = 0 ;
200+ for (VertexType i = 0 ; i < group_remap.size (); ++i) {
201+ if (i != v) {
202+ group_remap[i] = new_idx++;
203+ }
204+ }
205+ group_remap[v] = group_remap[u];
206+
207+ // Move existing groups that are not part of the merge
208+ for (VertexType i = 0 ; i < current_groups.size (); ++i) {
209+ if (i != u && i != v) {
210+ next_groups[group_remap[i]] = std::move (current_groups[i]);
211+ }
212+ }
213+ // Install the newly computed merged group
214+ next_groups[group_remap[u]].subgraphs = std::move (new_subgraphs);
215+ current_groups = std::move (next_groups);
216+
217+ // Update the main contraction map
218+ for (VertexType i = 0 ; i < current_contraction_map.size (); ++i) {
219+ current_contraction_map[i] = group_remap[current_contraction_map[i]];
220+ }
221+
222+ changed = true ;
223+ break ; // Restart scan on the new, smaller graph
224+ }
225+ }
226+
227+ // --- Finalize ---
228+ final_coarse_graph_ = std::move (current_coarse_graph);
229+ final_contraction_map_ = std::move (current_contraction_map);
230+ final_groups_ = std::move (current_groups);
231+
232+ if constexpr (verbose) {
233+ print_final_groups_summary ();
234+ }
235+ }
236+
237+ void print_final_groups_summary () const {
238+ std::cout << " \n --- 📦 Final Groups Summary ---\n " ;
239+ std::cout << " Total final groups: " << final_groups_.size () << " \n " ;
240+ for (size_t i = 0 ; i < final_groups_.size (); ++i) {
241+ const auto & group = final_groups_[i];
242+ std::cout << " - Group " << i << " (Size: " << group.subgraphs .size () << " )\n " ;
243+ if (!group.subgraphs .empty () && !group.subgraphs [0 ].empty ()) {
244+ std::cout << " - Rep. Subgraph size: " << group.subgraphs [0 ].size () << " nodes\n " ;
245+ }
246+ }
247+ std::cout << " --------------------------------\n " ;
248+ }
249+
250+ /* *
251+ * @brief Checks if merging two groups is viable based on the resulting number of isomorphic subgraphs.
252+ * This is analogous to WavefrontOrbitProcessor::is_viable_continuation.
253+ * If viable, it populates the `out_new_subgraphs` with the structure of the merged group.
254+ */
255+ bool is_merge_viable (const Graph_t& original_dag, const Group& group_u, const Group& group_v,
256+ std::vector<std::vector<VertexType>>& out_new_subgraphs) const {
257+ // 1. Collect all vertices from both groups.
258+ std::vector<VertexType> all_nodes;
259+ all_nodes.reserve (group_u.subgraphs .size () + group_v.subgraphs .size ()); // Approximation
260+ for (const auto & sg : group_u.subgraphs ) {
261+ all_nodes.insert (all_nodes.end (), sg.begin (), sg.end ());
262+ }
263+ for (const auto & sg : group_v.subgraphs ) {
264+ all_nodes.insert (all_nodes.end (), sg.begin (), sg.end ());
265+ }
266+
267+ // In debug builds, verify that the groups are disjoint as expected.
268+ // This lambda is evaluated only when assertions are enabled.
269+ assert ([&]() {
270+ std::vector<VertexType> temp_nodes_for_check = all_nodes;
271+ std::sort (temp_nodes_for_check.begin (), temp_nodes_for_check.end ());
272+ return std::unique (temp_nodes_for_check.begin (), temp_nodes_for_check.end ()) == temp_nodes_for_check.end ();
273+ }() && " Assumption failed: Vertices in groups being merged are not disjoint." );
274+
275+ // Sort nodes to use the create_induced_subgraph overload that provides an implicit mapping.
276+ std::sort (all_nodes.begin (), all_nodes.end ());
277+
278+ // 2. Create an induced subgraph and find its weakly connected components.
279+ Graph_t induced_subgraph;
280+ create_induced_subgraph (original_dag, induced_subgraph, all_nodes);
281+
282+ std::vector<VertexType> components; // local -> component_id
283+ size_t num_components = compute_weakly_connected_components (induced_subgraph, components);
284+
285+ out_new_subgraphs.assign (num_components, std::vector<VertexType>());
286+ for (VertexType i = 0 ; i < induced_subgraph.num_vertices (); ++i) {
287+ out_new_subgraphs[components[i]].push_back (all_nodes[i]);
288+ }
289+ return num_components >= symmetry_threshold_;
290+ }
291+
292+ public:
293+ const Graph_t& get_coarse_graph () const { return coarse_graph_; }
294+ const std::vector<VertexType>& get_contraction_map () const { return contraction_map_; }
295+ const Graph_t& get_final_coarse_graph () const { return final_coarse_graph_; }
296+ const std::vector<VertexType>& get_final_contraction_map () const { return final_contraction_map_; }
297+ const std::vector<Group>& get_final_groups () const { return final_groups_; }
298+ };
299+
300+ } // namespace osp
0 commit comments