/
mpi_nqueens.cpp
179 lines (158 loc) · 6.63 KB
/
mpi_nqueens.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/**
* @file mpi_nqueens.cpp
* @author Patrick Flick <patrick.flick@gmail.com>
* @brief Implements the parallel, master-worker nqueens solver.
*
* Copyright (c) 2016 Georgia Institute of Technology. All Rights Reserved.
*/
/*********************************************************************
* Implement your solutions here! *
*********************************************************************/
#include "mpi_nqueens.h"
#include <mpi.h>
#include <vector>
#include "nqueens.h"
#define REQUESTTAG 0
#define WORKTAG 1
#define KILLTAG 2
// stores all local solutions.
struct SolutionStore {
// store solutions in a static member variable
static std::vector<unsigned int>& solutions() {
static std::vector<unsigned int> sols;
return sols;
}
static void add_solution(const std::vector<unsigned int>& sol) {
// add solution to static member
solutions().insert(solutions().end(), sol.begin(), sol.end());
}
static void clear_solutions() {
solutions().clear();
}
};
/**
* @brief The master's call back function for each found solution.
*
* This is the callback function for the master process, that is called
* from within the nqueens solver, whenever a valid solution of level
* `k` is found.
*
* This function will send the partial solution to a worker which has
* completed his previously assigned work. As such this function must
* also first receive the solution from the worker before sending out
* the new work.
*
* @param solution The valid solution. This is passed from within the
* nqueens solver function.
*/
void master_solution_func(std::vector<unsigned int>& solution) {
// TODO: receive solutions or work-requests from a worker and then
// proceed to send this partial solution to that worker.
MPI_Status status;
unsigned int num;
MPI_Recv(&num, 1, MPI_UNSIGNED, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
if (status.MPI_TAG != REQUESTTAG) { //check if it's the workers' initial request
//receive solution from worker
std::vector<unsigned int> sol(num);
MPI_Recv(&(sol.front()), num, MPI_UNSIGNED, status.MPI_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
SolutionStore::add_solution(sol);
}
//send partial solution to worker
MPI_Send(&(solution.front()), solution.capacity(), MPI_UNSIGNED, status.MPI_SOURCE, WORKTAG, MPI_COMM_WORLD);
}
/**
* @brief Performs the master's main work.
*
* This function performs the master process' work. It will sets up the data
* structure to save the solution and call the nqueens solver by passing
* the master's callback function.
* After all work has been dispatched, this function will send the termination
* message to all worker processes, receive any remaining results, and then return.
*
* @param n The size of the nqueens problem.
* @param k The number of levels the master process will solve before
* passing further work to a worker process.
*/
std::vector<unsigned int> master_main(unsigned int n, unsigned int k) {
// TODO: send parameters (n,k) to workers via broadcast (MPI_Bcast)
MPI_Bcast(&n, 1, MPI_UNSIGNED, 0, MPI_COMM_WORLD);
MPI_Bcast(&k, 1, MPI_UNSIGNED, 0, MPI_COMM_WORLD);
// allocate the vector for the solution permutations
std::vector<unsigned int> pos(n);
// generate all partial solutions (up to level k) and call the
// master solution function
nqueens_by_level(pos, 0, k, &master_solution_func);
// TODO: get remaining solutions from workers and send termination messages
int size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Status status;
unsigned int num;
for (int rank = 1; rank < size; rank++) { //receive remaining solutions
MPI_Recv(&num, 1, MPI_UNSIGNED, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
std::vector<unsigned int> sol(num);
MPI_Recv(&(sol.front()), num, MPI_UNSIGNED, status.MPI_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
SolutionStore::add_solution(sol);
}
for (int rank = 1; rank < size; rank++) { //terminate workers
MPI_Send(&(pos.front()), n, MPI_UNSIGNED, rank, KILLTAG, MPI_COMM_WORLD);
}
// TODO: return all combined solutions
std::vector<unsigned int> allsolutions = SolutionStore::solutions();
SolutionStore::clear_solutions();
return allsolutions;
}
/**
* @brief The workers' call back function for each found solution.
*
* This is the callback function for the worker processes, that is called
* from within the nqueens solver, whenever a valid solution is found.
*
* This function saves the solution into the worker's solution cache.
*
* @param solution The valid solution. This is passed from within the
* nqueens solver function.
*/
void worker_solution_func(std::vector<unsigned int>& solution) {
// TODO: save the solution into a local cache
SolutionStore::add_solution(solution);
}
/**
* @brief Performs the worker's main work.
*
* This function implements the functionality of the worker process.
* The worker will receive partially completed work items from the
* master process and will then complete the assigned work and send
* back the results. Then again the worker will receive more work from the
* master process.
* If no more work is available (termination message is received instead of
* new work), then this function will return.
*/
void worker_main() {
unsigned int n, k;
// TODO receive the parameters `n` and `k` from the master process via MPI_Bcast
MPI_Bcast(&n, 1, MPI_UNSIGNED, 0, MPI_COMM_WORLD);
MPI_Bcast(&k, 1, MPI_UNSIGNED, 0, MPI_COMM_WORLD);
//Request Work
unsigned int rcv = 0;
MPI_Send(&rcv, 1, MPI_UNSIGNED, 0, REQUESTTAG, MPI_COMM_WORLD);
// TODO: implement the worker's functions: receive partially completed solutions,
// calculate all possible solutions starting with these queen positions
// and send solutions to the master process. then ask for more work.
std::vector<unsigned int> partial(n);
MPI_Status status;
while (1) {
MPI_Recv(&(partial.front()), n, MPI_UNSIGNED, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
if (status.MPI_TAG == KILLTAG)
return; //terminate
else {
// generate all solutions and call the
// worker solution function
nqueens_by_level(partial, k, n, &worker_solution_func);
std::vector<unsigned int> sols = SolutionStore::solutions();
SolutionStore::clear_solutions();
unsigned int len = sols.size();
MPI_Send(&len, 1, MPI_UNSIGNED, 0, WORKTAG, MPI_COMM_WORLD);
MPI_Send(&(sols.front()), len, MPI_UNSIGNED, 0, WORKTAG, MPI_COMM_WORLD);
}
}
}