Skip to content

Commit c9321f1

Browse files
committed
added OrbitGraphProcessor
update update update update added transitive reduction merge if both goups are below threshold integrate OrbitGraphProcessor IsomorphicSubgraphScheduler
1 parent 75de7cf commit c9321f1

15 files changed

+1013
-73
lines changed

include/osp/auxiliary/io/dot_graph_file_reader.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ limitations under the License.
2727
#include <filesystem>
2828
#include <limits>
2929

30-
#include "osp/concepts/computational_dag_concept.hpp"
30+
#include "osp/concepts/constructable_computational_dag_concept.hpp"
3131
#include "osp/auxiliary/io/filepath_checker.hpp"
3232

3333
namespace osp {

include/osp/dag_divider/isomorphism_divider/EftSubgraphScheduler.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class EftSubgraphScheduler {
5151

5252
private:
5353

54-
static constexpr bool verbose = true;
54+
static constexpr bool verbose = false;
5555

5656
using job_id_t = vertex_idx_t<Graph_t>;
5757

include/osp/dag_divider/isomorphism_divider/IsomorphicSubgraphScheduler.hpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ limitations under the License.
1717
*/
1818

1919
#include <iostream>
20-
#include "WavefrontOrbitProcessor.hpp"
20+
#include "OrbitGraphProcessor.hpp"
21+
#include "WavefrontOrbitProcessor.hpp" // For subgraph struct
2122
#include "EftSubgraphScheduler.hpp"
2223
#include "osp/auxiliary/io/DotFileWriter.hpp"
2324
#include "osp/bsp/scheduler/Scheduler.hpp"
@@ -31,7 +32,7 @@ class IsomorphicSubgraphScheduler {
3132

3233
private:
3334

34-
static constexpr bool verbose = true;
35+
static constexpr bool verbose = false;
3536

3637
size_t symmetry_ = 2;
3738
Scheduler<Constr_Graph_t> * bsp_scheduler_;
@@ -52,14 +53,38 @@ class IsomorphicSubgraphScheduler {
5253
}
5354

5455
std::vector<vertex_idx_t<Graph_t>> compute_partition(const BspInstance<Graph_t>& instance) {
55-
WavefrontOrbitProcessor<Graph_t> wavefront(symmetry_);
56-
wavefront.discover_isomorphic_groups(instance.getComputationalDag());
57-
auto isomorphic_groups = wavefront.get_isomorphic_groups();
58-
auto finalized_subgraphs = wavefront.get_finalized_subgraphs();
56+
OrbitGraphProcessor<Graph_t> processor(symmetry_);
57+
processor.discover_isomorphic_groups(instance.getComputationalDag());
58+
const auto& orbit_processor_groups = processor.get_final_groups();
59+
60+
// Adapt data structures from OrbitGraphProcessor to the format expected by the rest of the function.
61+
std::vector<subgraph<Graph_t>> finalized_subgraphs;
62+
std::vector<std::vector<unsigned>> isomorphic_groups;
63+
isomorphic_groups.reserve(orbit_processor_groups.size());
64+
65+
for (const auto& group : orbit_processor_groups) {
66+
std::vector<unsigned> new_iso_group_indices;
67+
new_iso_group_indices.reserve(group.subgraphs.size());
68+
for (const auto& sg_vertices : group.subgraphs) {
69+
subgraph<Graph_t> new_sg;
70+
new_sg.vertices = sg_vertices;
71+
// The following properties are not directly provided by OrbitGraphProcessor
72+
// but are needed by downstream logic. We can compute them.
73+
new_sg.work_weight = 0;
74+
new_sg.memory_weight = 0;
75+
for (const auto& v : sg_vertices) {
76+
new_sg.work_weight += instance.getComputationalDag().vertex_work_weight(v);
77+
new_sg.memory_weight += instance.getComputationalDag().vertex_mem_weight(v);
78+
}
79+
new_iso_group_indices.push_back(static_cast<unsigned>(finalized_subgraphs.size()));
80+
finalized_subgraphs.push_back(std::move(new_sg));
81+
}
82+
isomorphic_groups.push_back(std::move(new_iso_group_indices));
83+
}
5984

6085
if (plot_dot_graphs_) {
6186
DotFileWriter writer;
62-
writer.write_colored_graph("isomorphic_groups.dot", instance.getComputationalDag(), wavefront.get_vertex_color_map());
87+
writer.write_colored_graph("isomorphic_groups.dot", instance.getComputationalDag(), processor.get_final_contraction_map());
6388
}
6489

6590
const unsigned min_proc_type_count = instance.getArchitecture().getMinProcessorTypeCount();
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
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

Comments
 (0)