Merge branch '53-create-mpi-fractal-objects' into mpi

这个提交包含在:
Nathan Mannall
2025-06-02 16:58:31 +01:00
当前提交 386d7517db
共有 6 个文件被更改,包括 56 次插入31 次删除

查看文件

@@ -143,7 +143,7 @@ class FractalSurface:
return surfacedims
def generate_fractal_surface(self):
def generate_fractal_surface(self) -> bool:
"""Generate a 2D array with a fractal distribution."""
surfacedims = self.get_surface_dims()
@@ -199,6 +199,8 @@ class FractalSurface:
- ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange) * fractalmin
)
return True
class MPIFractalSurface(FractalSurface):
def __init__(
@@ -218,7 +220,7 @@ class MPIFractalSurface(FractalSurface):
self.comm = comm
self.upper_bound = upper_bound
def generate_fractal_surface(self):
def generate_fractal_surface(self) -> bool:
"""Generate a 2D array with a fractal distribution."""
if self.xs == self.xf:
@@ -247,7 +249,7 @@ class MPIFractalSurface(FractalSurface):
self.start = np.minimum(self.start, self.upper_bound)
self.stop = np.maximum(self.stop, 0)
self.stop = np.minimum(self.stop, self.upper_bound)
return
return False
else:
# Create new cartsesian communicator for the Fractal Surface
comm = self.comm.Split(color=color)
@@ -262,8 +264,17 @@ class MPIFractalSurface(FractalSurface):
# Check domain decomosition is valid for the Fractal Volume
if all([dim > 1 for dim in self.comm.dims]):
raise ValueError(
"Fractal surface must be positioned such that its MPI decomposition is 1 in at least"
f" 1 dimension. Current decompostion is: {self.comm.dims}"
"Fractal surface must be positioned such that its MPI decomposition is 1 in at"
f" least 1 dimension. Current decompostion is: {self.comm.dims}"
)
# Check domain decomosition is valid for the Fractal Volume
if len(self.grass) > 0 and self.comm.size > 1:
raise ValueError(
"Grass cannot currently be split across multiple MPI rank. Either change the MPI "
" decomposition such that the grass object is contained within a single MPI rank,"
" or divide the grass object into multiple sections. Current decompostion is:"
f" {self.comm.dims}"
)
surfacedims = self.get_surface_dims()
@@ -445,3 +456,5 @@ class MPIFractalSurface(FractalSurface):
logger.debug(
f"Generated fractal surface: start={self.start}, stop={self.stop}, size={self.size}, fractalrange={self.fractalrange}"
)
return True

查看文件

@@ -173,14 +173,22 @@ class AddGrass(RotatableMixin, GeometryUserObject):
else:
raise ValueError(f"{self.__str__()} dimensions are not specified correctly")
surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim, seed)
surface = grid.create_fractal_surface(xs, xf, ys, yf, zs, zf, frac_dim, seed)
surface.ID = "grass"
surface.surfaceID = requestedsurface
# Create grass geometry parameters
# Add grass to the surface here as an MPIFractalSurface needs to
# know if grass is present when generate_fractal_surface() is
# called
g = Grass(n_blades, surface.seed)
surface.grass.append(g)
# Set the fractal range to scale the fractal distribution between zero and one
surface.fractalrange = (0, 1)
surface.operatingonID = volume.ID
surface.generate_fractal_surface()
if not surface.generate_fractal_surface():
return
if n_blades > surface.fractalsurface.shape[0] * surface.fractalsurface.shape[1]:
raise ValueError(
f"{self.__str__()} the specified surface is not large "
@@ -220,10 +228,6 @@ class AddGrass(RotatableMixin, GeometryUserObject):
surface.fractalrange[0], surface.fractalrange[1], size=1
)
# Create grass geometry parameters
g = Grass(n_blades, surface.seed)
surface.grass.append(g)
# Check to see if grass has been already defined as a material
if not any(x.ID == "grass" for x in grid.materials):
create_grass(grid)

查看文件

@@ -116,27 +116,23 @@ class FractalBox(RotatableMixin, GeometryUserObject):
xf, yf, zf = p2
if frac_dim < 0:
logger.exception(
raise ValueError(
f"{self.__str__()} requires a positive value for the fractal dimension"
)
raise ValueError
if weighting[0] < 0:
logger.exception(
raise ValueError(
f"{self.__str__()} requires a positive value for the fractal weighting in the x direction"
)
raise ValueError
if weighting[1] < 0:
logger.exception(
raise ValueError(
f"{self.__str__()} requires a positive value for the fractal weighting in the y direction"
)
raise ValueError
if weighting[2] < 0:
logger.exception(
raise ValueError(
f"{self.__str__()} requires a positive value for the fractal weighting in the z direction"
)
if n_materials < 0:
logger.exception(f"{self.__str__()} requires a positive value for the number of bins")
raise ValueError
raise ValueError(f"{self.__str__()} requires a positive value for the number of bins")
# Find materials to use to build fractal volume, either from mixing
# models or normal materials.
@@ -146,26 +142,23 @@ class FractalBox(RotatableMixin, GeometryUserObject):
if mixingmodel:
if nbins == 1:
logger.exception(
raise ValueError(
f"{self.__str__()} must be used with more than one material from the mixing model."
)
raise ValueError
if isinstance(mixingmodel, ListMaterial) and nbins > len(mixingmodel.mat):
logger.exception(
raise ValueError(
f"{self.__str__()} too many materials/bins "
"requested compared to materials in "
"mixing model."
)
raise ValueError
# Create materials from mixing model as number of bins now known
# from fractal_box command.
mixingmodel.calculate_properties(nbins, grid)
elif not material:
logger.exception(
raise ValueError(
f"{self.__str__()} mixing model or material with "
+ "ID {mixing_model_id} does not exist"
)
raise ValueError
self.volume = grid.add_fractal_volume(xs, xf, ys, yf, zs, zf, frac_dim, seed)
self.volume.ID = ID
@@ -685,12 +678,11 @@ class FractalBox(RotatableMixin, GeometryUserObject):
else:
if self.volume.nbins == 1:
logger.exception(
raise ValueError(
f"{self.__str__()} is being used with a "
"single material and no modifications, "
"therefore please use a #box command instead."
)
raise ValueError
else:
if not self.volume.generate_fractal_volume():
return

查看文件

@@ -248,6 +248,20 @@ class GprMaxBaseTest(RunOnlyRegressionTest):
# Set the matplotlib cache to the work filesystem
self.env_vars["MPLCONFIGDIR"] = "${HOME/home/work}/.config/matplotlib"
def build_output_file_path(self, filename: str) -> Path:
"""Build output file Path object from filename.
Using a function to build this allows mixins to reuse or
override it if needed.
Args:
filename: Name of output file with no file extension.
Returns:
Path: Output file Path object
"""
return Path(f"{filename}.h5")
@run_after("init")
def set_file_paths(self):
"""Set default test input and output files.
@@ -256,7 +270,7 @@ class GprMaxBaseTest(RunOnlyRegressionTest):
later in the pipeline.
"""
self.input_file = Path(f"{self.model}.in")
self.output_file = Path(f"{self.model}.h5")
self.output_file = self.build_output_file_path(self.model)
@run_before("run")
def configure_test_run(self):

查看文件

@@ -35,7 +35,8 @@ class ReceiverMixin(GprMaxMixin):
def add_receiver_regression_checks(self):
test_dependency = self.get_test_dependency()
if test_dependency is not None:
reference_file = self.build_reference_filepath(test_dependency.output_file)
output_file = self.build_output_file_path(test_dependency.model)
reference_file = self.build_reference_filepath(output_file)
else:
reference_file = self.build_reference_filepath(self.output_file)

查看文件

@@ -168,7 +168,8 @@ class TestTriangleGeometry(GprMaxRegressionTest):
@rfm.simple_test
class TestAddGrassMpi(MpiMixin, TestAddGrass):
tags = {"test", "mpi", "geometery", "fractal", "surface", "grass"}
mpi_layout = parameter([[1, 2, 2], [3, 1, 3], [4, 1, 4]])
mpi_layout = parameter([[2, 2, 2]])
model = parameter(["add_grass_small"])
test_dependency = TestAddGrass