// Copyright(C) 2021, 2022, 2023 National Technology & Engineering Solutions // of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with // NTESS, the U.S. Government retains certain rights in this software. // // See packages/seacas/LICENSE for details #ifdef USE_ZOLTAN #include "zoltan.h" // for Zoltan_Set_Param, etc #include "zoltan_types.h" // for ZOLTAN_ID_PTR*, ZOLTAN_OK, etc #include // for MPI_Finalize, etc #endif #include #include #include #include #include "Cell.h" #include "Grid.h" #include "Ioss_ElementBlock.h" #include "Ioss_ParallelUtils.h" #include "Ioss_Sort.h" extern unsigned int debug_level; namespace { #ifdef USE_ZOLTAN #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define ZCHECK(funcall) \ do { \ ierr = (funcall); \ if (ierr == ZOLTAN_FATAL) { \ fmt::print(stderr, "Error returned from {} ({}:{})\n", TOSTRING(funcall), __FILE__, \ __LINE__); \ goto End; \ } \ } while (0) /*****************************************************************************/ /***** Global data structure used by Zoltan callbacks. *****/ /***** Could implement Zoltan callbacks without global data structure, *****/ /***** but using the global data structure makes implementation quick. *****/ struct { size_t ndot; /* Length of x, y, z, and part (== # of elements) */ int *vwgt; /* vertex weights */ float *x; /* x-coordinates */ float *y; /* y-coordinates */ float *z; /* z-coordinates */ } Zoltan_Data; /*****************************************************************************/ /***** ZOLTAN CALLBACK FUNCTIONS *****/ int zoltan_num_dim(void * /*data*/, int *ierr) { /* Return dimensionality of coordinate data. * Using global data structure Zoltan_Data, initialized in ZOLTAN_RCB_assign. */ *ierr = ZOLTAN_OK; if (Zoltan_Data.z != nullptr) { return 3; } if (Zoltan_Data.y != nullptr) { return 2; } return 1; } int zoltan_num_obj(void * /*data*/, int *ierr) { /* Return number of objects. * Using global data structure Zoltan_Data, initialized in ZOLTAN_RCB_assign. */ *ierr = ZOLTAN_OK; return Zoltan_Data.ndot; } void zoltan_obj_list(void * /*data*/, int /*ngid_ent*/, int /*nlid_ent*/, ZOLTAN_ID_PTR gids, ZOLTAN_ID_PTR /*lids*/, int wdim, float *wgts, int *ierr) { /* Return list of object IDs. * Return only global IDs; don't need local IDs since running in serial. * gids are array indices for coordinate and vwgts arrays. * Using global data structure Zoltan_Data, initialized in ZOLTAN_RCB_assign. */ for (size_t i = 0; i < Zoltan_Data.ndot; i++) { gids[i] = i; if (wdim != 0) { wgts[i] = static_cast(Zoltan_Data.vwgt[i]); } } *ierr = ZOLTAN_OK; } void zoltan_geom(void * /*data*/, int /*ngid_ent*/, int /*nlid_ent*/, int nobj, const ZOLTAN_ID_PTR gids, ZOLTAN_ID_PTR /*lids*/, int ndim, double *geom, int *ierr) { /* Return coordinates for objects. * gids are array indices for coordinate arrays. * Using global data structure Zoltan_Data, initialized in ZOLTAN_RCB_assign. */ for (int i = 0; i < nobj; i++) { size_t j = gids[i]; geom[i * ndim] = Zoltan_Data.x[j]; if (ndim > 1) { geom[i * ndim + 1] = Zoltan_Data.y[j]; } if (ndim > 2) { geom[i * ndim + 2] = Zoltan_Data.z[j]; } } *ierr = ZOLTAN_OK; } #endif } // namespace void decompose_grid(Grid &grid, int ranks, const std::string &method) { if (ranks == 1) { return; } if (method == "CYCLIC" || method == "LINEAR" || method == "RANDOM") { std::vector rank_vec(grid.size()); // Generate a "cyclic" vector 0, 1, 2, ... ranks-1, 0, 1, ... std::generate(rank_vec.begin(), rank_vec.end(), [n = 0, ranks]() mutable { return n++ % ranks; }); if (method == "LINEAR") { Ioss::sort(rank_vec.begin(), rank_vec.end()); } else if (method == "RANDOM") { unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); std::shuffle(rank_vec.begin(), rank_vec.end(), std::default_random_engine(seed)); } size_t k = 0; for (size_t j = 0; j < grid.JJ(); j++) { for (size_t i = 0; i < grid.II(); i++) { auto &cell = grid.get_cell(i, j); cell.set_rank(Loc::C, rank_vec[k++]); } } return; } #ifdef USE_ZOLTAN // Below here are Zoltan decompositions... std::vector x(grid.size()); std::vector y(grid.size()); std::vector w(grid.size()); // Get coordinates of each cell centroid... (actually origin of cell) size_t idx = 0; for (size_t j = 0; j < grid.JJ(); j++) { for (size_t i = 0; i < grid.II(); i++) { const auto &cell = grid.get_cell(i, j); x[idx] = cell.m_offX; y[idx] = cell.m_offY; const auto &element_blocks = cell.region()->get_element_blocks(); for (const auto *block : element_blocks) { w[idx] += block->entity_count(); } idx++; } } /* Copy mesh data and pointers into structure accessible from callback fns. */ Zoltan_Data.ndot = grid.size(); Zoltan_Data.vwgt = w.data(); Zoltan_Data.x = x.data(); Zoltan_Data.y = y.data(); Zoltan_Data.z = nullptr; /* Initialize Zoltan */ int argc = 0; char **argv = nullptr; ZOLTAN_ID_PTR zgids = nullptr; ZOLTAN_ID_PTR zlids = nullptr; /* Useful output from Zoltan_LB_Partition */ int *zprocs = nullptr; /* Useful output from Zoltan_LB_Partition */ int *zparts = nullptr; /* Useful output from Zoltan_LB_Partition */ int ierr = 0; float ver = 0.0; Zoltan_Initialize(argc, argv, &ver); struct Zoltan_Struct *zz = Zoltan_Create(Ioss::ParallelUtils::comm_world()); /* Register Callback functions */ /* Using global Zoltan_Data; could register it here instead as data field. */ ZCHECK(Zoltan_Set_Fn(zz, ZOLTAN_NUM_GEOM_FN_TYPE, reinterpret_cast(zoltan_num_dim), nullptr)); ZCHECK(Zoltan_Set_Fn(zz, ZOLTAN_NUM_OBJ_FN_TYPE, reinterpret_cast(zoltan_num_obj), nullptr)); ZCHECK(Zoltan_Set_Fn(zz, ZOLTAN_OBJ_LIST_FN_TYPE, reinterpret_cast(zoltan_obj_list), nullptr)); ZCHECK(Zoltan_Set_Fn(zz, ZOLTAN_GEOM_MULTI_FN_TYPE, reinterpret_cast(zoltan_geom), nullptr)); /* Set parameters for Zoltan */ ZCHECK(Zoltan_Set_Param(zz, "DEBUG_LEVEL", "0")); { std::string str = fmt::format("{}", ranks); ZCHECK(Zoltan_Set_Param(zz, "NUM_GLOBAL_PARTITIONS", str.c_str())); ZCHECK(Zoltan_Set_Param(zz, "LB_METHOD", method.c_str())); } ZCHECK(Zoltan_Set_Param(zz, "NUM_LID_ENTRIES", "0")); ZCHECK(Zoltan_Set_Param(zz, "REMAP", "0")); ZCHECK(Zoltan_Set_Param(zz, "RETURN_LISTS", "PARTITION_ASSIGNMENTS")); if (Zoltan_Data.vwgt != nullptr) { ZCHECK(Zoltan_Set_Param(zz, "OBJ_WEIGHT_DIM", "1")); } ZCHECK(Zoltan_Set_Param(zz, "RCB_RECTILINEAR_BLOCKS", "1")); /* Call partitioner */ { fmt::print(" Using Zoltan version {:.2}, method {}\n", static_cast(ver), method); int zngid_ent = 0; int znlid_ent = 0; /* Useful output from Zoltan_LB_Partition */ int znobj = 0; ZOLTAN_ID_PTR dummy1 = nullptr; ZOLTAN_ID_PTR dummy2 = nullptr; /* Empty output from Zoltan_LB_Partition */ int dummy0 = 0; int *dummy3 = nullptr; int *dummy4 = nullptr; int changes = 0; ZCHECK(Zoltan_LB_Partition(zz, &changes, &zngid_ent, &znlid_ent, &dummy0, &dummy1, &dummy2, &dummy3, &dummy4, &znobj, &zgids, &zlids, &zprocs, &zparts)); /* Sanity check */ if (grid.size() != static_cast(znobj)) { fmt::print(stderr, "Sanity check failed; ndot {} != znobj {}.\n", grid.size(), static_cast(znobj)); goto End; } idx = 0; for (size_t j = 0; j < grid.JJ(); j++) { for (size_t i = 0; i < grid.II(); i++) { auto &cell = grid.get_cell(i, j); cell.set_rank(Loc::C, zparts[idx++]); } } } End: /* Clean up */ Zoltan_LB_Free_Part(&zgids, &zlids, &zprocs, &zparts); Zoltan_Destroy(&zz); if (ierr != 0) { MPI_Finalize(); exit(-1); } #endif }