Skip to content

Commit 89f4745

Browse files
authored
Squashed commit of the following: (#23)
commit 3e0575f Author: tonibohnlein <[email protected]> Date: Thu Oct 23 08:57:24 2025 +0200 revision orbit graph processor commit d810f9e Author: tonibohnlein <[email protected]> Date: Tue Oct 21 16:10:51 2025 +0200 update fix greedy children commit 2d82aa7 Author: tonibohnlein <[email protected]> Date: Mon Oct 20 16:05:08 2025 +0200 update merge rule commit c38fba4 Author: tonibohnlein <[email protected]> Date: Mon Oct 20 14:47:34 2025 +0200 greedyChildren update commit deb8345 Author: tonibohnlein <[email protected]> Date: Mon Oct 20 09:26:31 2025 +0200 IsomorphicSubGraphScheduler update, GreedyChildren node types
1 parent 8b556ea commit 89f4745

File tree

9 files changed

+613
-40
lines changed

9 files changed

+613
-40
lines changed

include/osp/bsp/scheduler/GreedySchedulers/GreedyChildren.hpp

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,41 @@ class GreedyChildren : public Scheduler<Graph_t> {
7272
unsigned processor_to_be_allocated = 0;
7373

7474
for (const auto &par : graph.parents(node)) {
75-
if (processor_set &&
76-
(nodes_assigned_this_superstep.find(par) != nodes_assigned_this_superstep.cend()) &&
77-
(sched.assignedProcessor(par) != processor_to_be_allocated)) {
78-
failed_to_allocate = true;
79-
break;
80-
}
81-
if ((!processor_set) &&
82-
(nodes_assigned_this_superstep.find(par) != nodes_assigned_this_superstep.cend())) {
83-
processor_set = true;
84-
processor_to_be_allocated = sched.assignedProcessor(par);
75+
if (nodes_assigned_this_superstep.count(par)) {
76+
if (!processor_set) {
77+
const unsigned par_proc = sched.assignedProcessor(par);
78+
if(!instance.isCompatible(node, par_proc)) {
79+
failed_to_allocate = true;
80+
break;
81+
}
82+
processor_set = true;
83+
processor_to_be_allocated = par_proc;
84+
} else if (sched.assignedProcessor(par) != processor_to_be_allocated) {
85+
failed_to_allocate = true;
86+
break;
87+
}
8588
}
8689
}
90+
8791
if (failed_to_allocate)
8892
continue;
8993

9094
sched.setAssignedSuperstep(node, superstep_counter);
9195
if (processor_set) {
9296
sched.setAssignedProcessor(node, processor_to_be_allocated);
9397
} else {
94-
95-
auto min_iter = std::min_element(processor_weights.begin(), processor_weights.end());
96-
assert(std::distance(processor_weights.begin(), min_iter) >= 0);
97-
sched.setAssignedProcessor(
98-
node, static_cast<unsigned>(std::distance(processor_weights.begin(), min_iter)));
99-
}
98+
v_workw_t<Graph_t> min_weight = std::numeric_limits<v_workw_t<Graph_t>>::max();
99+
unsigned best_proc = std::numeric_limits<unsigned>::max();
100+
for (unsigned p = 0; p < instance.numberOfProcessors(); ++p) {
101+
if (instance.isCompatible(node, p)) {
102+
if (processor_weights[p] < min_weight) {
103+
min_weight = processor_weights[p];
104+
best_proc = p;
105+
}
106+
}
107+
}
108+
sched.setAssignedProcessor(node, best_proc);
109+
}
100110

101111
nodes_assigned_this_superstep.emplace(node);
102112
processor_weights[sched.assignedProcessor(node)] += graph.vertex_work_weight(node);

include/osp/dag_divider/isomorphism_divider/IsomorphicSubgraphScheduler.hpp

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,15 @@ class IsomorphicSubgraphScheduler {
198198
std::sort(rep_subgraph_vertices_sorted.begin(), rep_subgraph_vertices_sorted.end());
199199

200200
BspInstance<Constr_Graph_t> representative_instance;
201-
create_induced_subgraph(instance.getComputationalDag(), representative_instance.getComputationalDag(), rep_subgraph_vertices_sorted);
202-
201+
auto rep_global_to_local_map = create_induced_subgraph_map(instance.getComputationalDag(), representative_instance.getComputationalDag(), rep_subgraph_vertices_sorted);
202+
203203
representative_instance.setArchitecture(instance.getArchitecture());
204-
std::vector<v_memw_t<Constr_Graph_t>> dummy_mem_weights(sub_sched.node_assigned_worker_per_type[grou_idx].size(), 0);
204+
std::vector<v_memw_t<Constr_Graph_t>> mem_weights(sub_sched.node_assigned_worker_per_type[grou_idx].size(), 0);
205205
for (unsigned proc_type = 0; proc_type < sub_sched.node_assigned_worker_per_type[grou_idx].size(); ++proc_type) {
206-
dummy_mem_weights[proc_type] = static_cast<v_memw_t<Constr_Graph_t>>(instance.getArchitecture().maxMemoryBoundProcType(proc_type));
206+
mem_weights[proc_type] = static_cast<v_memw_t<Constr_Graph_t>>(instance.getArchitecture().maxMemoryBoundProcType(proc_type));
207207
}
208208
const auto& procs_for_group = sub_sched.node_assigned_worker_per_type[grou_idx];
209-
representative_instance.getArchitecture().set_processors_consequ_types(procs_for_group, dummy_mem_weights);
209+
representative_instance.getArchitecture().set_processors_consequ_types(procs_for_group, mem_weights);
210210
representative_instance.setNodeProcessorCompatibility(instance.getProcessorCompatibilityMatrix());
211211

212212
// Schedule the representative to get the pattern
@@ -215,7 +215,20 @@ class IsomorphicSubgraphScheduler {
215215
if constexpr (verbose) {
216216
std::cout << "--- Scheduling representative for group " << grou_idx << " ---" << std::endl;
217217
std::cout << " Number of subgraphs in group: " << group.subgraphs.size() << std::endl;
218-
std::cout << " Representative subgraph size: " << rep_subgraph_vertices_sorted.size() << " vertices" << std::endl;
218+
const auto& rep_dag = representative_instance.getComputationalDag();
219+
std::cout << " Representative subgraph size: " << rep_dag.num_vertices() << " vertices" << std::endl;
220+
std::vector<unsigned> node_type_counts(rep_dag.num_vertex_types(), 0);
221+
for (const auto& v : rep_dag.vertices()) {
222+
node_type_counts[rep_dag.vertex_type(v)]++;
223+
}
224+
std::cout << " Node type counts: ";
225+
for (size_t type_idx = 0; type_idx < node_type_counts.size(); ++type_idx) {
226+
if (node_type_counts[type_idx] > 0) {
227+
std::cout << "T" << type_idx << ":" << node_type_counts[type_idx] << " ";
228+
}
229+
}
230+
std::cout << std::endl;
231+
219232
const auto& sub_arch = representative_instance.getArchitecture();
220233
std::cout << " Sub-architecture for scheduling:" << std::endl;
221234
std::cout << " Processors: " << sub_arch.numberOfProcessors() << std::endl;
@@ -226,9 +239,45 @@ class IsomorphicSubgraphScheduler {
226239
}
227240
std::cout << std::endl;
228241
std::cout << " Sync cost: " << sub_arch.synchronisationCosts() << ", Comm cost: " << sub_arch.communicationCosts() << std::endl;
242+
std::cout << " Sub-problem compatibility matrix:" << std::endl;
243+
const auto & sub_comp_matrix = representative_instance.getNodeNodeCompatabilityMatrix();
244+
for(unsigned i = 0; i < sub_comp_matrix.size(); ++i) {
245+
std::cout << " Node Type " << i << ": [ ";
246+
for (unsigned j = 0; j < sub_comp_matrix[i].size(); ++j) {
247+
std::cout << (sub_comp_matrix[i][j] ? "1" : "0") << " ";
248+
}
249+
std::cout << "]" << std::endl;
250+
}
251+
229252
}
230253
bsp_scheduler_->computeSchedule(bsp_schedule);
231254

255+
if constexpr (verbose) {
256+
std::cout << " Schedule satisfies precedence constraints: ";
257+
std::cout << bsp_schedule.satisfiesPrecedenceConstraints() << std::endl;
258+
std::cout << " Schedule satisfies node type constraints: ";
259+
std::cout << bsp_schedule.satisfiesNodeTypeConstraints() << std::endl;
260+
}
261+
262+
263+
if (plot_dot_graphs_) {
264+
const auto& rep_dag = bsp_schedule.getInstance().getComputationalDag();
265+
std::vector<unsigned> colors(rep_dag.num_vertices());
266+
std::map<std::pair<unsigned, unsigned>, unsigned> proc_ss_to_color;
267+
unsigned next_color = 0;
268+
269+
for (const auto& v : rep_dag.vertices()) {
270+
const auto assignment = std::make_pair(bsp_schedule.assignedProcessor(v), bsp_schedule.assignedSuperstep(v));
271+
if (proc_ss_to_color.find(assignment) == proc_ss_to_color.end()) {
272+
proc_ss_to_color[assignment] = next_color++;
273+
}
274+
colors[v] = proc_ss_to_color[assignment];
275+
}
276+
DotFileWriter writer;
277+
writer.write_colored_graph("iso_group_rep_" + std::to_string(grou_idx) + ".dot", rep_dag, colors);
278+
writer.write_schedule("iso_group_rep_schedule_" + std::to_string(grou_idx) + ".dot", bsp_schedule);
279+
}
280+
232281
// Build data structures for applying the pattern ---
233282
// Map (superstep, processor) -> relative partition ID
234283
std::map<std::pair<unsigned, unsigned>, vertex_idx_t<Graph_t>> sp_proc_to_relative_partition;
@@ -252,13 +301,11 @@ class IsomorphicSubgraphScheduler {
252301
std::unordered_map<vertex_idx_t<Graph_t>, vertex_idx_t<Constr_Graph_t>> current_vertex_to_rep_local_idx;
253302

254303
if (i == 0) { // The first subgraph is the representative itself
255-
for (size_t j = 0; j < rep_subgraph_vertices_sorted.size(); ++j) {
256-
current_vertex_to_rep_local_idx[rep_subgraph_vertices_sorted[j]] = static_cast<vertex_idx_t<Constr_Graph_t>>(j);
257-
}
304+
current_vertex_to_rep_local_idx = std::move(rep_global_to_local_map);
258305
} else { // For other subgraphs, build the isomorphic mapping
259306
Constr_Graph_t current_subgraph_graph;
260307
create_induced_subgraph(instance.getComputationalDag(), current_subgraph_graph, current_subgraph_vertices_sorted);
261-
308+
262309
MerkleHashComputer<Constr_Graph_t> current_hasher(current_subgraph_graph);
263310

264311
for(const auto& [hash, rep_orbit_nodes] : rep_hasher.get_orbits()) {
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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

include/osp/dag_divider/isomorphism_divider/MerkleHashComputer.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ class MerkleHashComputer {
102102
inline const std::vector<std::size_t> &get_vertex_hashes() const { return vertex_hashes; }
103103
inline std::size_t num_orbits() const { return orbits.size(); }
104104

105-
inline const std::vector<VertexType> &get_orbit(const VertexType &v) { return get_orbit_from_hash(get_vertex_hash(v)); }
106-
inline const std::unordered_map<std::size_t, std::vector<VertexType>> &get_orbits() { return orbits; }
105+
inline const std::vector<VertexType> &get_orbit(const VertexType &v) const { return get_orbit_from_hash(get_vertex_hash(v)); }
106+
inline const std::unordered_map<std::size_t, std::vector<VertexType>> &get_orbits() const { return orbits; }
107107

108108
inline const std::vector<VertexType>& get_orbit_from_hash(const std::size_t& hash) const {
109109
return orbits.at(hash);

0 commit comments

Comments
 (0)