diff --git a/packages/seacas/applications/slice/CMakeLists.txt b/packages/seacas/applications/slice/CMakeLists.txt index 08d4167478..846ef38957 100644 --- a/packages/seacas/applications/slice/CMakeLists.txt +++ b/packages/seacas/applications/slice/CMakeLists.txt @@ -5,6 +5,13 @@ IF (TPL_ENABLE_METIS) ADD_DEFINITIONS(-DUSE_METIS) ENDIF() +ASSERT_DEFINED(${PROJECT_NAME}_ENABLE_Zoltan) +IF (${PROJECT_NAME}_ENABLE_Zoltan) + TRIBITS_INCLUDE_DIRECTORIES(${Zoltan_INCLUDE_DIRS}) + SET(ZOLTAN_DEP zoltan) + ADD_DEFINITIONS(-DUSE_ZOLTAN) +ENDIF() + TRIBITS_INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) SET(HEADERS "") diff --git a/packages/seacas/applications/slice/SL_SystemInterface.C b/packages/seacas/applications/slice/SL_SystemInterface.C index 057202edb7..c4f86326b8 100644 --- a/packages/seacas/applications/slice/SL_SystemInterface.C +++ b/packages/seacas/applications/slice/SL_SystemInterface.C @@ -1,4 +1,4 @@ -// Copyright(C) 1999-2022 National Technology & Engineering Solutions +// Copyright(C) 1999-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. // @@ -58,8 +58,15 @@ void SystemInterface::enroll_options() "\t\t'linear' : #elem/#proc to each processor\n" "\t\t'scattered': Shuffle elements to each processor (cyclic)\n" "\t\t'random' : Random distribution of elements, maintains balance\n" +#if USE_METIS "\t\t'rb' : Metis multilevel recursive bisection\n" "\t\t'kway' : Metis multilevel k-way graph partitioning\n" +#endif +#if USE_ZOLTAN + "\t\t'rib' : Zoltan recursive-inertial-bisection\n" + "\t\t'rcb' : Zoltan recursive-coordinate-bisection\n" + "\t\t'hsfc' : Zoltan hilbert-space-filling curve\n" +#endif "\t\t'variable' : Read element-processor assignment from an element variable\n" "\t\t'map' : Read element-processor assignment from an element map [processor_id]\n" "\t\t'file' : Read element-processor assignment from file", @@ -161,8 +168,7 @@ void SystemInterface::enroll_options() "\t\t 4 = Progress information in File/Rank.\n" "\t\t 8 = File/Rank Decomposition information.\n" "\t\t 16 = Chain/Line generation/decomp information.\n", - "\t\t 32 = Show decomposition histogram (elements / rank).", - "0"); + "\t\t 32 = Show decomposition histogram (elements / rank).", "0"); options_.enroll("version", GetLongOption::NoValue, "Print version and exit", nullptr); @@ -219,7 +225,8 @@ bool SystemInterface::parse_options(int argc, char **argv) if (options_.retrieve("help") != nullptr) { options_.usage(); fmt::print(stderr, "\n\t Can also set options via SLICE_OPTIONS environment variable.\n"); - fmt::print(stderr, "\n\tDocumentation: https://sandialabs.github.io/seacas-docs/sphinx/html/index.html#slice\n"); + fmt::print(stderr, "\n\tDocumentation: " + "https://sandialabs.github.io/seacas-docs/sphinx/html/index.html#slice\n"); fmt::print(stderr, "\n\t->->-> Send email to gsjaardema@gmail.com for slice support.<-<-<-\n"); exit(EXIT_SUCCESS); } diff --git a/packages/seacas/applications/slice/SL_Version.h b/packages/seacas/applications/slice/SL_Version.h index 3f6ddf8d85..0f0a8eaf97 100644 --- a/packages/seacas/applications/slice/SL_Version.h +++ b/packages/seacas/applications/slice/SL_Version.h @@ -9,6 +9,6 @@ static std::array qainfo{ "slice", - "2023/09/06", - "2.0.02", + "2023/09/07", + "2.1.00", }; diff --git a/packages/seacas/applications/slice/Slice.C b/packages/seacas/applications/slice/Slice.C index 9c5f05044e..e3c03fe7af 100644 --- a/packages/seacas/applications/slice/Slice.C +++ b/packages/seacas/applications/slice/Slice.C @@ -47,6 +47,12 @@ using idx_t = int; #endif +#if USE_ZOLTAN +#include // for Zoltan_Initialize +#include // for Zoltan +extern "C" int Zoltan_get_global_id_type(char **name); +#endif + #include #ifdef SEACAS_HAVE_MPI @@ -87,6 +93,10 @@ namespace { } } + template + void decompose_zoltan(const Ioss::Region ®ion, int ranks, const std::string &method, + std::vector &elem_to_proc, IOSS_MAYBE_UNUSED INT dummy); + // Add the chain maps for file-per-rank output... template void output_chain_maps(std::vector &proc_region, const Ioss::chain_t &chains, @@ -195,16 +205,16 @@ namespace { if (sizeof(INT) == 8) { elem_to_proc64.reserve(elem_to_proc.size()); for (const auto &etp : elem_to_proc) { - elem_to_proc64.push_back(etp); + elem_to_proc64.push_back(etp); } } for (const auto &block : blocks) { size_t num_elem = block->entity_count(); if (sizeof(INT) == 8) { - block->put_field_data(decomp_variable_name, (void *)&elem_to_proc64[offset], -1); + block->put_field_data(decomp_variable_name, (void *)&elem_to_proc64[offset], -1); } else { - block->put_field_data(decomp_variable_name, (void *)&elem_to_proc[offset], -1); + block->put_field_data(decomp_variable_name, (void *)&elem_to_proc[offset], -1); } if (add_chain_info) { std::vector chain; @@ -312,7 +322,8 @@ namespace { void filename_substitution(std::string &filename, const SystemInterface &interFace); template - void output_decomposition_statistics(const std::vector &elem_to_proc, int proc_count, size_t number_elements); + void output_decomposition_statistics(const std::vector &elem_to_proc, int proc_count, + size_t number_elements); template void slice(Ioss::Region ®ion, const std::string &nemfile, SystemInterface &interFace, @@ -364,6 +375,64 @@ namespace { fmt::print(stderr, "Setting common_nodes to {}\n", common_nodes); return common_nodes; } + + void decompose_metis(const Ioss::Region ®ion, SystemInterface &interFace, + std::vector &elem_to_proc) + { + size_t element_count = region.get_property("element_count").get_int(); + + std::vector pointer; + std::vector adjacency; + + double start = seacas_timer(); + create_adjacency_list(region, pointer, adjacency, dummy); + double end = seacas_timer(); + fmt::print(stderr, "\tCreate Adjacency List = {:.5}\n", end - start); + + // Call Metis to get the partition... + { + start = seacas_timer(); + idx_t elem_count = element_count; + idx_t common = get_common_node_count(region); + idx_t proc_count = interFace.processor_count(); + idx_t obj_val = 0; + std::vector options((METIS_NOPTIONS)); + METIS_SetDefaultOptions(&options[0]); + if (interFace.decomposition_method() == "kway") { + options[METIS_OPTION_PTYPE] = METIS_PTYPE_KWAY; + } + else { + options[METIS_OPTION_PTYPE] = METIS_PTYPE_RB; + } + + options[METIS_OPTION_OBJTYPE] = METIS_OBJTYPE_CUT; + if (interFace.contiguous_decomposition()) { + options[METIS_OPTION_CONTIG] = 1; + } + options[METIS_OPTION_DBGLVL] = 2; + options[METIS_OPTION_MINCONN] = 1; + + idx_t node_count = region.get_property("node_count").get_int(); + std::vector node_partition(node_count); + std::vector elem_partition(element_count); + + fmt::print(stderr, "\tCalling METIS Decomposition routine.\n"); + + METIS_PartMeshDual(&elem_count, &node_count, &pointer[0], &adjacency[0], nullptr, nullptr, + &common, &proc_count, nullptr, &options[0], &obj_val, &elem_partition[0], + &node_partition[0]); + + Ioss::Utils::clear(node_partition); + elem_to_proc.reserve(element_count); + std::copy(elem_partition.begin(), elem_partition.end(), std::back_inserter(elem_to_proc)); + + end = seacas_timer(); + fmt::print(stderr, "\tMETIS Partition = {:.5}\n", end - start); + fmt::print(stderr, "Objective value = {}\n", obj_val); + + // TODO Check Error... + } + } #endif } // namespace // ======================================================================== @@ -573,60 +642,23 @@ namespace { } } + else if (interFace.decomposition_method() == "rcb" || + interFace.decomposition_method() == "rib" || + interFace.decomposition_method() == "hsfc") { +#if USE_ZOLTAN + decompose_zoltan(region, interFace.processor_count(), interFace.decomposition_method(), + elem_to_proc, dummy); +#else + fmt::print(stderr, "ERROR: Zoltan library not enabled in this version of slice.\n" + " The 'rcb', 'rib', and 'hsfc' methods are not available.\n\n"); + std::exit(1); +#endif + } + else if (interFace.decomposition_method() == "rb" || interFace.decomposition_method() == "kway") { #if USE_METIS - std::vector pointer; - std::vector adjacency; - - double start = seacas_timer(); - create_adjacency_list(region, pointer, adjacency, dummy); - double end = seacas_timer(); - fmt::print(stderr, "\tCreate Adjacency List = {:.5}\n", end - start); - - // Call Metis to get the partition... - { - start = seacas_timer(); - idx_t elem_count = element_count; - idx_t common = get_common_node_count(region); - idx_t proc_count = interFace.processor_count(); - idx_t obj_val = 0; - std::vector options((METIS_NOPTIONS)); - METIS_SetDefaultOptions(&options[0]); - if (interFace.decomposition_method() == "kway") { - options[METIS_OPTION_PTYPE] = METIS_PTYPE_KWAY; - } - else { - options[METIS_OPTION_PTYPE] = METIS_PTYPE_RB; - } - - options[METIS_OPTION_OBJTYPE] = METIS_OBJTYPE_CUT; - if (interFace.contiguous_decomposition()) { - options[METIS_OPTION_CONTIG] = 1; - } - options[METIS_OPTION_DBGLVL] = 2; - options[METIS_OPTION_MINCONN] = 1; - - idx_t node_count = region.get_property("node_count").get_int(); - std::vector node_partition(node_count); - std::vector elem_partition(element_count); - - fmt::print(stderr, "\tCalling METIS Decomposition routine.\n"); - - METIS_PartMeshDual(&elem_count, &node_count, &pointer[0], &adjacency[0], nullptr, nullptr, - &common, &proc_count, nullptr, &options[0], &obj_val, &elem_partition[0], - &node_partition[0]); - - Ioss::Utils::clear(node_partition); - elem_to_proc.reserve(element_count); - std::copy(elem_partition.begin(), elem_partition.end(), std::back_inserter(elem_to_proc)); - - end = seacas_timer(); - fmt::print(stderr, "\tMETIS Partition = {:.5}\n", end - start); - fmt::print(stderr, "Objective value = {}\n", obj_val); - - // TODO Check Error... - } + decompose_metis(region, interFace, elem_to_proc); #else fmt::print(stderr, "ERROR: Metis library not enabled in this version of slice.\n" " The 'rb' and 'kway' methods are not available.\n\n"); @@ -843,6 +875,12 @@ namespace { } } } + else { + fmt::print(stderr, "ERROR: Unrecognized decomposition method '{}'\n\n", + interFace.decomposition_method()); + exit(EXIT_FAILURE); + } + assert(elem_to_proc.size() == element_count); } @@ -856,11 +894,11 @@ namespace { for (size_t i = 0; i < element_chains.size(); i++) { auto &chain_entry = element_chains[i]; if (chain_entry.link >= 0) { - chains[chain_entry.element].push_back(i + 1); - if ((debug_level & 16) != 0) { - fmt::print("[{}]: element {}, link {}, processor {}\n", i + 1, chain_entry.element, - chain_entry.link, elem_to_proc[i]); - } + chains[chain_entry.element].push_back(i + 1); + if ((debug_level & 16) != 0) { + fmt::print("[{}]: element {}, link {}, processor {}\n", i + 1, chain_entry.element, + chain_entry.link, elem_to_proc[i]); + } } } @@ -1921,7 +1959,8 @@ namespace { } if (debug_level & 32) { - output_decomposition_statistics(elem_to_proc, interFace.processor_count(), elem_to_proc.size()); + output_decomposition_statistics(elem_to_proc, interFace.processor_count(), + elem_to_proc.size()); } if (!create_split_files) { @@ -1959,7 +1998,7 @@ namespace { add_decomp_map(output_region, interFace.decomposition_variable(), line_decomp); output_decomp_map(output_region, elem_to_proc, element_chains, interFace.decomposition_variable(), line_decomp); - progress("output_decomp_map"); + progress("output_decomp_map"); } if (interFace.outputDecompField_) { @@ -1967,7 +2006,7 @@ namespace { add_decomp_field(output_region, interFace.decomposition_variable(), line_decomp); output_decomp_field(output_region, elem_to_proc, element_chains, interFace.decomposition_variable(), line_decomp); - progress("output_decomp_field"); + progress("output_decomp_field"); } return; @@ -2237,8 +2276,9 @@ namespace { } template - void output_decomposition_statistics(const std::vector &elem_to_proc, int proc_count, size_t number_elements) - { + void output_decomposition_statistics(const std::vector &elem_to_proc, int proc_count, + size_t number_elements) + { // Output histogram of elements / rank... std::vector elem_per_rank(proc_count); for (INT proc : elem_to_proc) { @@ -2266,31 +2306,256 @@ namespace { else { int max_star = 40; int min_star = max_star * ((double)min_work / (double)(max_work)); - min_star = std::max(1, min_star); + min_star = std::max(1, min_star); int delta = max_star - min_star; double avg_work = (double)number_elements / (double)proc_count; for (size_t i = 0; i < elem_per_rank.size(); i++) { - int star_cnt = - (double)(elem_per_rank[i] - min_work) / (max_work - min_work) * delta + min_star; - std::string stars(star_cnt, '*'); - std::string format = "\tProcessor {:{}}, work = {:{}} ({:.2f})\t{}\n"; - if (elem_per_rank[i] == max_work) { - fmt::print(fg(fmt::color::red), format, i, proc_width, fmt::group_digits(elem_per_rank[i]), - work_width, (double)elem_per_rank[i] / avg_work, stars); - } - else if (elem_per_rank[i] == min_work) { - fmt::print(fg(fmt::color::green), format, i, proc_width, - fmt::group_digits(elem_per_rank[i]), work_width, elem_per_rank[i] / avg_work, stars); - } - else { - fmt::print(format, i, proc_width, fmt::group_digits(elem_per_rank[i]), work_width, - elem_per_rank[i] / avg_work, stars); - } + int star_cnt = + (double)(elem_per_rank[i] - min_work) / (max_work - min_work) * delta + min_star; + std::string stars(star_cnt, '*'); + std::string format = "\tProcessor {:{}}, work = {:{}} ({:.2f})\t{}\n"; + if (elem_per_rank[i] == max_work) { + fmt::print(fg(fmt::color::red), format, i, proc_width, + fmt::group_digits(elem_per_rank[i]), work_width, + (double)elem_per_rank[i] / avg_work, stars); + } + else if (elem_per_rank[i] == min_work) { + fmt::print(fg(fmt::color::green), format, i, proc_width, + fmt::group_digits(elem_per_rank[i]), work_width, elem_per_rank[i] / avg_work, + stars); + } + else { + fmt::print(format, i, proc_width, fmt::group_digits(elem_per_rank[i]), work_width, + elem_per_rank[i] / avg_work, stars); + } } // Output Histogram... output_histogram(elem_per_rank, (size_t)avg_work, median); } } + +#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 */ + double *x; /* x-coordinates */ + double *y; /* y-coordinates */ + double *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 + + template + void decompose_zoltan(const Ioss::Region ®ion, int ranks, const std::string &method, + std::vector &elem_to_proc, IOSS_MAYBE_UNUSED INT dummy) + { + if (ranks == 1) { + return; + } + +#ifdef USE_ZOLTAN + size_t element_count = region.get_property("element_count").get_int(); + + // Below here are Zoltan decompositions... + std::vector x(element_count); + std::vector y(element_count); + std::vector z(element_count); + + const auto *nb = region.get_node_blocks()[0]; + std::vector coor; + nb->get_field_data("mesh_model_coordinates", coor); + + const auto &blocks = region.get_element_blocks(); + size_t el = 0; + for (auto &eb : blocks) { + std::vector connectivity; + eb->get_field_data("connectivity_raw", connectivity); + size_t blk_element_count = eb->entity_count(); + size_t blk_element_nodes = eb->topology()->number_nodes(); + + for (size_t j = 0; j < blk_element_count; j++) { + for (size_t k = 0; k < blk_element_nodes; k++) { + auto node = connectivity[j * blk_element_nodes + k] - 1; + x[el] += coor[node * 3 + 0]; + y[el] += coor[node * 3 + 1]; + z[el] += coor[node * 3 + 2]; + } + x[el] /= blk_element_nodes; + y[el] /= blk_element_nodes; + z[el] /= blk_element_nodes; + fmt::print("Centroid for element {} is {}, {}, {}\n", el + 1, x[el], y[el], z[el]); + el++; + } + } + + /* Copy mesh data and pointers into structure accessible from callback fns. */ + Zoltan_Data.ndot = element_count; + Zoltan_Data.vwgt = nullptr; + Zoltan_Data.x = x.data(); + Zoltan_Data.y = y.data(); + Zoltan_Data.z = z.data(); + + /* 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 (element_count != static_cast(znobj)) { + fmt::print(stderr, "Sanity check failed; ndot {} != znobj {}.\n", element_count, + static_cast(znobj)); + goto End; + } + + elem_to_proc.resize(element_count); + for (size_t i = 0; i < element_count; i++) { + elem_to_proc[i] = zparts[i]; + } + } + + End: + /* Clean up */ + Zoltan_LB_Free_Part(&zgids, &zlids, &zprocs, &zparts); + Zoltan_Destroy(&zz); + if (ierr != 0) { + MPI_Finalize(); + exit(-1); + } +#endif + } } // namespace diff --git a/packages/seacas/applications/slice/cmake/Dependencies.cmake b/packages/seacas/applications/slice/cmake/Dependencies.cmake index 253b734110..c38922d3f5 100644 --- a/packages/seacas/applications/slice/cmake/Dependencies.cmake +++ b/packages/seacas/applications/slice/cmake/Dependencies.cmake @@ -1,4 +1,5 @@ TRIBITS_PACKAGE_DEFINE_DEPENDENCIES( LIB_REQUIRED_PACKAGES SEACASExodus SEACASIoss SEACASSuplibC SEACASSuplibCpp + LIB_OPTIONAL_PACKAGES Zoltan LIB_OPTIONAL_TPLS METIS )