Skip to content
Snippets Groups Projects
Commit 904c2346 authored by Peter W. Draper's avatar Peter W. Draper
Browse files

Add iterative similarity checks for the refined partition, this seems to give...

Add iterative similarity checks for the refined partition, this seems to give better solutions than just depending on the first
parent 45fc8d43
No related branches found
No related tags found
2 merge requests!506Add ParMETIS support,!503WIP: Use ParMETIS to reduce the amount of particle movement
...@@ -280,6 +280,119 @@ static void split_metis(struct space *s, int nregions, int *celllist) { ...@@ -280,6 +280,119 @@ static void split_metis(struct space *s, int nregions, int *celllist) {
#if defined(WITH_MPI) && defined(HAVE_PARMETIS) #if defined(WITH_MPI) && defined(HAVE_PARMETIS)
/* qsort support. */
struct indexval {
int index;
int count;
int old;
int new;
};
static int indexvalcmp(const void *p1, const void *p2) {
const struct indexval *iv1 = (const struct indexval *)p1;
const struct indexval *iv2 = (const struct indexval *)p2;
return iv2->count - iv1->count;
}
/**
* @brief Check if a permutation of the region indices of our cells will
* reduce the amount of particle movement.
*
* @param newlist the new list of regions for our cells.
* @param oldlist the old list of regions for our cells.
* @param nregions the number of regions.
* @param ncells the number of cells.
* @param permlist the permutation of the newlist.
*
* @result fraction of regions that have been permuted.
*/
static float permute_regions(int *newlist, int *oldlist, int nregions,
int ncells, int *permlist) {
/* We want a solution in which the current region assignments of the cells
* are preserved when possible, to avoid unneccesary particle movement. So
* create a 2d-array of counts of cells that are common to all pairs of old
* and new lists. Each element of the array has a count of cells and an
* unique index so we can sort into decreasing counts.
*/
int indmax = nregions * nregions;
struct indexval *ivs = malloc(sizeof(struct indexval) * indmax);
bzero(ivs, sizeof(struct indexval) * indmax);
for (int k = 0; k < ncells; k++) {
int index = newlist[k] + nregions * oldlist[k];
ivs[index].count++;
ivs[index].index = index;
ivs[index].old = oldlist[k];
ivs[index].new = newlist[k];
}
qsort(ivs, indmax, sizeof(struct indexval), indexvalcmp);
/* Go through the ivs using the largest counts first, these are the
* regions with the most cells in common, old partition to new. If not
* returning the permutation, avoid the associated work. */
int *oldmap = NULL;
int *newmap = NULL;
if (permlist != NULL)
oldmap = permlist; /* Reuse this */
else
oldmap = malloc(sizeof(int) * nregions);
newmap = malloc(sizeof(int) * nregions);
for (int k = 0; k < nregions; k++) {
oldmap[k] = -1;
newmap[k] = -1;
}
int ncommon = 0;
for (int k = 0; k < indmax; k++) {
/* Stop when all regions with common cells have been considered. */
if (ivs[k].count == 0) break;
/* Store old and new IDs, if not already used. */
if (newmap[ivs[k].new] == -1 && oldmap[ivs[k].old] == -1) {
newmap[ivs[k].new] = ivs[k].old;
oldmap[ivs[k].old] = ivs[k].new;
/* If these are the same regions, we count that. */
if (ivs[k].new == ivs[k].old) ncommon++;
//message("%d -> %d", ivs[k].old, ivs[k].new);
}
}
if (permlist != NULL) {
/* Handle any regions that did not get selected by picking an unused rank
* from oldmap and assigning to newmap. */
int spare = 0;
for (int k = 0; k < nregions; k++) {
if (newmap[k] == -1) {
for (int j = spare; j < nregions; j++) {
if (oldmap[j] == -1) {
newmap[k] = j;
oldmap[j] = j;
spare = j;
break;
}
}
}
}
/* Permute the newlist into this order as result. */
for (int k = 0; k < ncells; k++) {
permlist[k] = newmap[newlist[k]];
}
free(newmap);
} else {
free(newmap);
free(oldmap);
}
return (float)ncommon /(float)nregions;
}
#endif
#if defined(WITH_MPI) && defined(HAVE_PARMETIS)
/** /**
* @brief Partition the given space into a number of connected regions using * @brief Partition the given space into a number of connected regions using
* ParMETIS. * ParMETIS.
...@@ -304,13 +417,15 @@ static void split_metis(struct space *s, int nregions, int *celllist) { ...@@ -304,13 +417,15 @@ static void split_metis(struct space *s, int nregions, int *celllist) {
* in CSR format, so same as adjncy array. Need to be in the range of * in CSR format, so same as adjncy array. Need to be in the range of
* idx_t. * idx_t.
* @param refine whether to refine an existing partition, or create a new one. * @param refine whether to refine an existing partition, or create a new one.
* @param seed seed for random numbers, usually when refining this is best
* kept to the same value. Use -1 for the ParMETIS default.
* @param celllist on exit this contains the ids of the selected regions, * @param celllist on exit this contains the ids of the selected regions,
* sizeof number of cells. If refine is 1, then this should contain * sizeof number of cells. If refine is 1, then this should contain
* the old partition on entry. * the old partition on entry.
*/ */
static void pick_parmetis(int nodeID, struct space *s, int nregions, static void pick_parmetis(int nodeID, struct space *s, int nregions,
double *vertexw, double *edgew, int refine, double *vertexw, double *edgew, int refine,
int *celllist) { int seed, int *celllist) {
int res; int res;
MPI_Comm comm; MPI_Comm comm;
MPI_Status status; MPI_Status status;
...@@ -545,7 +660,7 @@ static void pick_parmetis(int nodeID, struct space *s, int nregions, ...@@ -545,7 +660,7 @@ static void pick_parmetis(int nodeID, struct space *s, int nregions,
idx_t options[10]; idx_t options[10];
options[0] = 1; options[0] = 1;
options[1] = 0; options[1] = 0;
options[2] = -1; options[2] = seed;
idx_t nparts = nregions; idx_t nparts = nregions;
idx_t wgtflag = 0; idx_t wgtflag = 0;
if (edgew != NULL) wgtflag += 1; if (edgew != NULL) wgtflag += 1;
...@@ -556,65 +671,160 @@ static void pick_parmetis(int nodeID, struct space *s, int nregions, ...@@ -556,65 +671,160 @@ static void pick_parmetis(int nodeID, struct space *s, int nregions,
real_t ubvec[1]; real_t ubvec[1];
ubvec[0] = 1.05; ubvec[0] = 1.05;
if (refine) { /* Create a new partition. */
if (!refine) {
if (nodeID == 0) message("Creating new partition");
if (ParMETIS_V3_PartKway(vtxdist, xadj, adjncy, weights_v, weights_e,
&wgtflag, &numflag, &ncon, &nparts, tpwgts, ubvec,
options, &edgecut, regionid, &comm) != METIS_OK)
error("Call to ParMETIS_V3_PartKway failed.");
/* Gather the regionids from the other ranks. XXX async version XXX */
if (nodeID == 0) {
idx_t *remoteids = NULL;
for (int rank = 0, j = 0; rank < nregions; rank++) {
int nvt = vtxdist[rank + 1] - vtxdist[rank];
/* Use the existing partition, uncouple as we do not have the cells if (rank == 0) {
/* Locals. */
remoteids = regionid;
} else {
remoteids = (idx_t *)malloc(sizeof(idx_t) * nvt);
res = MPI_Recv((void *)remoteids, nvt, IDX_T, rank, 1, comm, &status);
if (res != MPI_SUCCESS)
mpi_error(res, "Failed to receive new regionids");
}
for (int k = 0; k < nvt; k++) celllist[j + k] = remoteids[k];
j += nvt;
if (rank != 0) free(remoteids);
}
/* Check that the regionids are ok. */
for (int k = 0; k < ncells; k++)
if (celllist[k] < 0 || celllist[k] >= nregions)
error("Got bad nodeID %" PRIDX " for cell %i.", celllist[k], k);
} else {
res = MPI_Send(regionid, vtxdist[nodeID + 1] - vtxdist[nodeID], IDX_T, 0,
1, comm);
if (res != MPI_SUCCESS) mpi_error(res, "Failed to send new regionids");
}
/* And everyone gets a copy. */
res = MPI_Bcast(celllist, s->nr_cells, MPI_INT, 0, MPI_COMM_WORLD);
if (res != MPI_SUCCESS) mpi_error(res, "Failed to broadcast new celllist");
} else {
/* Refine an existing partition, uncouple as we do not have the cells
* present on their expected ranks. */ * present on their expected ranks. */
options[3] = PARMETIS_PSR_UNCOUPLED; options[3] = PARMETIS_PSR_UNCOUPLED;
if (nodeID == 0) message("Refining partition"); if (nodeID == 0) message("Refining partition");
if (ParMETIS_V3_RefineKway(vtxdist, xadj, adjncy, weights_v, weights_e, /* Now we potentially iterate for a better solution, stochastically these
&wgtflag, &numflag, &ncon, &nparts, tpwgts, * are identical as all we vary is the random seed, but we do get
ubvec, options, &edgecut, regionid, * different solutions. */
&comm) != METIS_OK) int maxiter = 5;
error("Call to ParMETIS_V3_RefineKway failed."); float maxsim = 0.0f;
} else { /* Keep local copies so we can compare the results. */
int *newcelllist = (int *)malloc(sizeof(int) * ncells);
int *keepcelllist = (int *)malloc(sizeof(int) * ncells);
int *myregionid = (idx_t *)malloc(sizeof(idx_t) * (nverts + 1));
if (nodeID == 0) message("Creating new partition"); for (int iter = 0; iter < maxiter; iter++) {
/* Restore regionids. */
memcpy(myregionid, regionid, sizeof(idx_t) * (nverts + 1));
if (ParMETIS_V3_PartKway(vtxdist, xadj, adjncy, weights_v, weights_e, if (ParMETIS_V3_RefineKway(vtxdist, xadj, adjncy, weights_v, weights_e,
&wgtflag, &numflag, &ncon, &nparts, tpwgts, ubvec, &wgtflag, &numflag, &ncon, &nparts, tpwgts,
options, &edgecut, regionid, &comm) != METIS_OK) ubvec, options, &edgecut, myregionid,
error("Call to ParMETIS_V3_PartKway failed."); &comm) != METIS_OK)
} error("Call to ParMETIS_V3_RefineKway failed.");
/* Gather the regionids from the other ranks. XXX async version XXX */
if (nodeID == 0) {
idx_t *remoteids = NULL;
for (int rank = 0, j = 0; rank < nregions; rank++) {
int nvt = vtxdist[rank + 1] - vtxdist[rank];
if (rank == 0) { /* Gather the regionids from the other ranks. XXX async version XXX */
/* Locals. */ if (nodeID == 0) {
remoteids = regionid;
idx_t *remoteids = NULL;
for (int rank = 0, j = 0; rank < nregions; rank++) {
int nvt = vtxdist[rank + 1] - vtxdist[rank];
if (rank == 0) {
/* Locals. */
remoteids = myregionid;
} else {
remoteids = (idx_t *)malloc(sizeof(idx_t) * nvt);
res = MPI_Recv((void *)remoteids, nvt, IDX_T, rank, 1, comm,
&status);
if (res != MPI_SUCCESS)
mpi_error(res, "Failed to receive new regionids");
}
for (int k = 0; k < nvt; k++) newcelllist[j + k] = remoteids[k];
j += nvt;
if (rank != 0) free(remoteids);
}
} else { } else {
remoteids = (idx_t *)malloc(sizeof(idx_t) * nvt); res = MPI_Send(myregionid, vtxdist[nodeID + 1] - vtxdist[nodeID], IDX_T,
res = MPI_Recv((void *)remoteids, nvt, IDX_T, rank, 1, comm, &status); 0, 1, comm);
if (res != MPI_SUCCESS) if (res != MPI_SUCCESS) mpi_error(res, "Failed to send new regionids");
mpi_error(res, "Failed to receive new regionids");
} }
for (int k = 0; k < nvt; k++) { /* And everyone gets a copy. */
celllist[j + k] = remoteids[k]; res = MPI_Bcast(newcelllist, s->nr_cells, MPI_INT, 0, MPI_COMM_WORLD);
} if (res != MPI_SUCCESS) mpi_error(res, "Failed to broadcast new celllist");
j += nvt;
if (rank != 0) free(remoteids); /* Now check the similarity to the old partition. */
} float similarity = permute_regions(newcelllist, celllist, nregions,
ncells, NULL);
if (nodeID == 0) message("similarity = %f", similarity);
if (similarity > 0.75f || iter == (maxiter - 1)) {
/* Time to stop. */
if (nodeID == 0 && (iter == (maxiter - 1))) message("forced stop");
if (similarity < maxsim ) {
/* Swap back to the best list. */
if (nodeID == 0)
message("swap to better solution %f < %f", similarity, maxsim);
int *tmp = newcelllist;
newcelllist = keepcelllist;
keepcelllist = tmp;
}
/* Check that the regionids are ok. */ /* Check that the regionids are ok. */
for (int k = 0; k < ncells; k++) for (int k = 0; k < ncells; k++)
if (celllist[k] < 0 || celllist[k] >= nregions) if (newcelllist[k] < 0 || newcelllist[k] >= nregions)
error("Got bad nodeID %" PRIDX " for cell %i.", celllist[k], k); error("Got bad nodeID %" PRIDX " for cell %i.", newcelllist[k], k);
} else { /* And keep. */
res = MPI_Send(regionid, vtxdist[nodeID + 1] - vtxdist[nodeID], IDX_T, 0, 1, memcpy(celllist, newcelllist, sizeof(int) * ncells);
comm); break;
if (res != MPI_SUCCESS) mpi_error(res, "Failed to send new regionids");
} else {
if (similarity > maxsim) {
if (nodeID == 0) message("saving solution");
maxsim = similarity;
/* Keep this list. */
int *tmp = newcelllist;
newcelllist = keepcelllist;
keepcelllist = tmp;
}
/* Have another go, with different random seed. */
options[2] = getticks() % INT_MAX;
if (nodeID == 0) message("setting new seed (%d)", options[2]);
}
}
free(newcelllist);
free(keepcelllist);
free(myregionid);
} }
/* And everyone gets a copy? */
res = MPI_Bcast(celllist, s->nr_cells, MPI_INT, 0, MPI_COMM_WORLD);
if (res != MPI_SUCCESS) mpi_error(res, "Failed to broadcast new celllist");
/* Clean up. */ /* Clean up. */
if (weights_v != NULL) free(weights_v); if (weights_v != NULL) free(weights_v);
...@@ -873,10 +1083,10 @@ static void repart_edge_parmetis(int bothweights, int timebins, ...@@ -873,10 +1083,10 @@ static void repart_edge_parmetis(int bothweights, int timebins,
/* And partition, use both weights or not as requested. */ /* And partition, use both weights or not as requested. */
if (bothweights) if (bothweights)
pick_parmetis(nodeID, s, nr_nodes, weights_v, weights_e, refine, pick_parmetis(nodeID, s, nr_nodes, weights_v, weights_e, refine,
repartition->celllist); -1, repartition->celllist);
else else
pick_parmetis(nodeID, s, nr_nodes, NULL, weights_e, refine, pick_parmetis(nodeID, s, nr_nodes, NULL, weights_e, refine,
repartition->celllist); -1, repartition->celllist);
/* Check that all cells have good values. All nodes have same copy, so just /* Check that all cells have good values. All nodes have same copy, so just
* check on one. */ * check on one. */
...@@ -1042,7 +1252,7 @@ void partition_initial_partition(struct partition *initial_partition, ...@@ -1042,7 +1252,7 @@ void partition_initial_partition(struct partition *initial_partition,
/* Do the calculation. */ /* Do the calculation. */
int *celllist = (int *)malloc(sizeof(int) * s->nr_cells); int *celllist = (int *)malloc(sizeof(int) * s->nr_cells);
if (celllist == NULL) error("Failed to allocate celllist"); if (celllist == NULL) error("Failed to allocate celllist");
pick_parmetis(nodeID, s, nr_nodes, weights, NULL, 0, celllist); pick_parmetis(nodeID, s, nr_nodes, weights, NULL, 0, -1, celllist);
/* And apply to our cells */ /* And apply to our cells */
split_metis(s, nr_nodes, celllist); split_metis(s, nr_nodes, celllist);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment