diff --git a/changelog-entries/214.md b/changelog-entries/214.md new file mode 100644 index 00000000..e7c3c601 --- /dev/null +++ b/changelog-entries/214.md @@ -0,0 +1 @@ +- Added configurable partitioning algorithm to mapping tester. diff --git a/changelog-entries/224.md b/changelog-entries/224.md new file mode 100644 index 00000000..b1089834 --- /dev/null +++ b/changelog-entries/224.md @@ -0,0 +1 @@ +- Changed partitioning algorihm to use a fixed seed which can be passed via the `--seed` flag. This makes partitioning deterministic. diff --git a/examples/mapping_tester/setup-test.json b/examples/mapping_tester/setup-test.json index b61943d1..dd4f538e 100644 --- a/examples/mapping_tester/setup-test.json +++ b/examples/mapping_tester/setup-test.json @@ -1,6 +1,7 @@ { "general": { "function": "0.78 + cos(10*(x+y+z))", + "partitioning": "topology", "ranks": { "A": [ 2 diff --git a/src/metisAPI.cpp b/src/metisAPI.cpp index 6ab656bb..2616125a 100644 --- a/src/metisAPI.cpp +++ b/src/metisAPI.cpp @@ -1,16 +1,15 @@ #include #include #include -extern "C" void partitionMetis(idx_t cell_count, idx_t point_count, idx_t *cellptr, idx_t *celldata, idx_t nparts, idx_t *point_partition); +extern "C" void partitionMetis(idx_t cell_count, idx_t point_count, idx_t *cellptr, idx_t *celldata, idx_t nparts, idx_t seed, idx_t *point_partition); extern "C" int typewidth(); -void partitionMetis(idx_t cell_count, idx_t point_count, idx_t *cellptr, idx_t *celldata, idx_t nparts, idx_t *point_partition) +void partitionMetis(idx_t cell_count, idx_t point_count, idx_t *cellptr, idx_t *celldata, idx_t nparts, idx_t seed, idx_t *point_partition) { idx_t options[METIS_NOPTIONS]; METIS_SetDefaultOptions(options); // Make it deterministic - // TODO: Pass via interface - options[METIS_OPTION_SEED] = 2025; + options[METIS_OPTION_SEED] = seed; std::vector cell_partition(cell_count); idx_t objval; // TODO: Check return value of the function (and potentially add an assert) diff --git a/src/precice-aste-partition b/src/precice-aste-partition index b9f17894..6b681c75 100755 --- a/src/precice-aste-partition +++ b/src/precice-aste-partition @@ -85,13 +85,11 @@ class MeshPartitioner: def run(args) -> None: logger = MeshPartitioner.get_logger() mesh_name = args.in_meshname - algorithm = args.algorithm - if not algorithm: - logger.info('No algorithm given. Defaulting to "meshfree"') - algorithm = "meshfree" mesh = MeshPartitioner.read_mesh(mesh_name) if args.numparts > 1: - part = MeshPartitioner.partition(mesh, args.numparts, algorithm) + part = MeshPartitioner.partition( + mesh, args.numparts, args.algorithm, args.seed + ) else: if args.directory: # Get the absolute directory where we want to store the mesh @@ -160,12 +158,22 @@ class MeshPartitioner: "-a", dest="algorithm", choices=["meshfree", "topology", "uniform"], + default="meshfree", help="""Change the algorithm used for determining a partition. A meshfree algorithm works on arbitrary meshes without needing topological information. A topology-based algorithm needs topology information and is therefore useless on point clouds. A uniform algorithm will assume a uniform 2d mesh laid out somehow in 3d and partition accordingly.""", ) + parser.add_argument( + "--seed", + "-s", + type=int, + default=2025, # arbitrary default seed + help="""Seed to pass on to partitioning algorithms which may use it to end up with deterministic behaviour. + Not all algorithms may use it though. + """, + ) parser.add_argument( "--log", "-l", @@ -192,30 +200,36 @@ class MeshPartitioner: return logging.getLogger("---[ASTE-Partition]") @staticmethod - def partition(mesh: Mesh, numparts: int, algorithm): + def partition(mesh: Mesh, numparts: int, algorithm: str, seed: int): """ Partitions a mesh using METIS or kmeans. This does not call METIS directly, but instead uses a small C++ Wrapper around shared library libmetisAPI for convenience. This shared library must be provided if this function should be called. """ if algorithm == "meshfree": - return MeshPartitioner.partition_kmeans(mesh, numparts) + return MeshPartitioner.partition_kmeans(mesh, numparts, seed) elif algorithm == "topology": - return MeshPartitioner.partition_metis(mesh, numparts) + return MeshPartitioner.partition_metis(mesh, numparts, seed) elif algorithm == "uniform": labels = MeshPartitioner.partition_uniform(mesh, numparts) if labels is None: - return MeshPartitioner.partition(mesh, numparts, "meshfree") + return MeshPartitioner.partition(mesh, numparts, "meshfree", seed) return labels @staticmethod - def partition_kmeans(mesh: Mesh, numparts: int): + def partition_kmeans(mesh: Mesh, numparts: int, seed: int): """Partitions a mesh using k-means. This is a meshfree algorithm and requires scipy""" + import scipy from scipy.cluster.vq import kmeans2 points = np.copy(mesh.points) points = MeshPartitioner.reduce_dimension(points) - _, label = kmeans2(points, numparts) + # scipy 1.15 renamed seed to rng + major, minor = map(int, scipy.__version__.split(".")[:2]) + if major == 1 and minor < 15: + _, label = kmeans2(points, numparts, rng=seed) + else: + _, label = kmeans2(points, numparts, seed=seed) return label @staticmethod @@ -296,7 +310,7 @@ class MeshPartitioner: return mesh[:, :-1] @staticmethod - def partition_metis(mesh: Mesh, numparts: int): + def partition_metis(mesh: Mesh, numparts: int, seed: int): """ Partitions a mesh using METIS. This does not call METIS directly, but instead uses a small C++ Wrapper libmetisAPI.so for convenience. @@ -339,8 +353,9 @@ class MeshPartitioner: partition = (idx_t * len(mesh.points))() cell_ptr = (idx_t * len(cellPtr))(*cellPtr) cell_data = (idx_t * len(cellData))(*cellData) + the_seed = idx_t(seed) libmetis.partitionMetis( - cell_count, point_count, cell_ptr, cell_data, num_parts, partition + cell_count, point_count, cell_ptr, cell_data, num_parts, seed, partition ) return np.ctypeslib.as_array(partition) diff --git a/tools/mapping-tester/preparemeshes.py b/tools/mapping-tester/preparemeshes.py index 44d7800f..8ed93544 100755 --- a/tools/mapping-tester/preparemeshes.py +++ b/tools/mapping-tester/preparemeshes.py @@ -64,7 +64,7 @@ def prepareMainMesh(meshdir, name, file, function, force=False): ) -def preparePartMesh(meshdir, name, p, force=False): +def preparePartMesh(meshdir, name, p, force, algorithm): if p == 1: return @@ -91,7 +91,7 @@ def preparePartMesh(meshdir, name, p, force=False): "--mesh", mainMesh, "--algorithm", - "topology", + algorithm, "-o", partMesh, "--directory", @@ -111,6 +111,14 @@ def main(argv): print('Warning: outdir "{}" already exisits.'.format(outdir)) meshdir = os.path.join(outdir, "meshes") function = setup["general"]["function"] + algorithm = setup["general"].get("partitioning", "meshfree") + + if "partitioning" in setup["general"]: + print(f"Using partitioning algorithm {algorithm}") + else: + print( + "You haven't specified a partitioning algorihm in the general section of the setup.json. This defaults to meshfree, which uses a non-deterministic k-means clusterting. Partitionings will be non-reproducible." + ) partitions = set( [int(rank) for pranks in setup["general"]["ranks"].values() for rank in pranks] @@ -128,7 +136,7 @@ def main(argv): prepareMainMesh(meshdir, name, file, function, args.force) for p in partitions: - preparePartMesh(meshdir, name, p, args.force) + preparePartMesh(meshdir, name, p, args.force, algorithm) return 0