Create new geometry views/objects using grid views

这个提交包含在:
nmannall
2025-01-13 15:41:42 +00:00
父节点 4e0dadd981
当前提交 24b1d0363a
共有 5 个文件被更改,包括 939 次插入0 次删除

查看文件

@@ -0,0 +1,181 @@
from io import TextIOWrapper
from pathlib import Path
from typing import Generic
import h5py
import numpy as np
from mpi4py import MPI
from tqdm import tqdm
from gprMax import config
from gprMax._version import __version__
from gprMax.grid.fdtd_grid import FDTDGrid
from gprMax.materials import Material
from gprMax.output_controllers.grid_view import GridViewType, MPIGridView
class GeometryObjects(Generic[GridViewType]):
"""Geometry objects to be written to file."""
def __init__(self, grid_view: GridViewType, filename: str):
"""
Args:
xs, xf, ys, yf, zs, zf: ints for extent of the volume in cells.
filename: string for filename.
"""
self.grid_view = grid_view
# Set filenames
parts = config.sim_config.input_file_path.with_suffix("").parts
self.filename = Path(*parts[:-1], filename)
self.filename_hdf5 = self.filename.with_suffix(".h5")
self.filename_materials = Path(self.filename, f"{self.filename}_materials")
self.filename_materials = self.filename_materials.with_suffix(".txt")
# Sizes of arrays to write necessary to update progress bar
self.solidsize = np.prod(self.grid_view.size + 1) * np.dtype(np.uint32).itemsize
self.rigidsize = 18 * np.prod(self.grid_view.size + 1) * np.dtype(np.int8).itemsize
self.IDsize = 6 * np.prod(self.grid_view.size + 1) * np.dtype(np.uint32).itemsize
self.datawritesize = self.solidsize + self.rigidsize + self.IDsize
@property
def grid(self) -> FDTDGrid:
return self.grid_view.grid
def write_metadata(self, file_handler: h5py.File, title: str):
file_handler.attrs["gprMax"] = __version__
file_handler.attrs["Title"] = title
file_handler.attrs["dx_dy_dz"] = (self.grid.dx, self.grid.dy, self.grid.dz)
def output_material(self, material: Material, file: TextIOWrapper):
file.write(
f"#material: {material.er:g} {material.se:g} "
f"{material.mr:g} {material.sm:g} {material.ID}\n"
)
if hasattr(material, "poles"):
if "debye" in material.type:
dispersionstr = "#add_dispersion_debye: " f"{material.poles:g} "
for pole in range(material.poles):
dispersionstr += f"{material.deltaer[pole]:g} " f"{material.tau[pole]:g} "
elif "lorenz" in material.type:
dispersionstr = f"#add_dispersion_lorenz: " f"{material.poles:g} "
for pole in range(material.poles):
dispersionstr += (
f"{material.deltaer[pole]:g} "
f"{material.tau[pole]:g} "
f"{material.alpha[pole]:g} "
)
elif "drude" in material.type:
dispersionstr = f"#add_dispersion_drude: " f"{material.poles:g} "
for pole in range(material.poles):
dispersionstr += f"{material.tau[pole]:g} " f"{material.alpha[pole]:g} "
dispersionstr += material.ID
file.write(dispersionstr + "\n")
def write_hdf5(self, title: str, G: FDTDGrid, pbar: tqdm):
"""Writes a geometry objects file in HDF5 format.
Args:
G: FDTDGrid class describing a grid in a model.
pbar: Progress bar class instance.
"""
self.grid_view.initialise_materials()
ID = self.grid_view.get_ID()
data = self.grid_view.get_solid().astype(np.int16)
rigidE = self.grid_view.get_rigidE()
rigidH = self.grid_view.get_rigidH()
ID = self.grid_view.map_to_view_materials(ID)
data = self.grid_view.map_to_view_materials(data)
with h5py.File(self.filename_hdf5, "w") as fdata:
self.write_metadata(fdata, title)
fdata["/data"] = data
pbar.update(self.solidsize)
fdata["/rigidE"] = rigidE
fdata["/rigidH"] = rigidH
pbar.update(self.rigidsize)
fdata["/ID"] = ID
pbar.update(self.IDsize)
# Write materials list to a text file
with open(self.filename_materials, "w") as fmaterials:
for material in self.grid_view.materials:
self.output_material(material, fmaterials)
class MPIGeometryObjects(GeometryObjects[MPIGridView]):
def __init__(
self,
grid_view: MPIGridView,
filename: str,
comm: MPI.Comm = None,
):
super().__init__(grid_view, filename)
self.comm = comm
def write_hdf5(self, title: str, pbar: tqdm):
"""Writes a geometry objects file in HDF5 format.
Args:
G: FDTDGrid class describing a grid in a model.
pbar: Progress bar class instance.
"""
self.grid_view.initialise_materials(self.comm)
ID = self.grid_view.get_ID()
data = self.grid_view.get_solid().astype(np.int16)
rigidE = self.grid_view.get_rigidE()
rigidH = self.grid_view.get_rigidH()
ID = self.grid_view.map_to_view_materials(ID)
data = self.grid_view.map_to_view_materials(data)
with h5py.File(self.filename_hdf5, "w", driver="mpio", comm=self.comm) as fdata:
self.write_metadata(fdata, title)
dset_slice = (
self.grid_view.get_slice(0),
self.grid_view.get_slice(1),
self.grid_view.get_slice(2),
)
dset = fdata.create_dataset("/data", self.grid_view.global_size, dtype=data.dtype)
dset[dset_slice] = data
pbar.update(self.solidsize)
rigid_E_dataset = fdata.create_dataset(
"/rigidE", (12, *self.grid_view.global_size), dtype=rigidE.dtype
)
rigid_E_dataset[:, dset_slice[0], dset_slice[1], dset_slice[2]] = rigidE
rigid_H_dataset = fdata.create_dataset(
"/rigidH", (6, *self.grid_view.global_size), dtype=rigidH.dtype
)
rigid_H_dataset[:, dset_slice[0], dset_slice[1], dset_slice[2]] = rigidH
pbar.update(self.rigidsize)
dset_slice = (
self.grid_view.get_slice(0, upper_bound_exclusive=False),
self.grid_view.get_slice(1, upper_bound_exclusive=False),
self.grid_view.get_slice(2, upper_bound_exclusive=False),
)
dset = fdata.create_dataset(
"/ID", (6, *(self.grid_view.global_size + 1)), dtype=ID.dtype
)
dset[:, dset_slice[0], dset_slice[1], dset_slice[2]] = ID
pbar.update(self.IDsize)
# Write materials list to a text file
if self.grid_view.materials is not None:
with open(self.filename_materials, "w") as materials_file:
for material in self.grid_view.materials:
self.output_material(material, materials_file)

查看文件

@@ -0,0 +1,171 @@
# Copyright (C) 2015-2024: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
#
# gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import numpy as np
from mpi4py import MPI
from gprMax._version import __version__
from gprMax.cython.geometry_outputs import get_line_properties
from gprMax.grid.mpi_grid import MPIGrid
from gprMax.output_controllers.geometry_views import GeometryView, GridViewType, Metadata
from gprMax.output_controllers.grid_view import MPIGridView
from gprMax.subgrids.grid import SubGridBaseGrid
from gprMax.vtkhdf_filehandlers.vtk_unstructured_grid import VtkUnstructuredGrid
from gprMax.vtkhdf_filehandlers.vtkhdf import VtkCellType
logger = logging.getLogger(__name__)
class GeometryViewLines(GeometryView[GridViewType]):
"""Unstructured grid for a per-cell-edge geometry view."""
def prep_vtk(self):
"""Prepares data for writing to VTKHDF file."""
ID = self.grid_view.get_ID()
x = np.arange(self.grid_view.nx + 1, dtype=np.float64)
y = np.arange(self.grid_view.ny + 1, dtype=np.float64)
z = np.arange(self.grid_view.nz + 1, dtype=np.float64)
coords = np.meshgrid(x, y, z, indexing="ij")
self.points = np.vstack(list(map(np.ravel, coords))).T
self.points += self.grid_view.start
self.points *= self.grid_view.step * self.grid.dl
# Add offset to subgrid geometry to correctly locate within main grid
if isinstance(self.grid, SubGridBaseGrid):
offset = [self.grid.i0, self.grid.j0, self.grid.k0]
self.points += offset * self.grid.dl * self.grid.ratio
# Each point is the 'source' for 3 lines.
# NB: Excluding points at the far edge of the geometry as those
# are the 'source' for no lines
n_lines = 3 * np.prod(self.grid_view.size)
self.cell_types = np.full(n_lines, VtkCellType.LINE)
self.cell_offsets = np.arange(0, 2 * n_lines + 2, 2, dtype=np.intc)
self.connectivity, self.material_data = get_line_properties(
n_lines, *self.grid_view.size, ID
)
assert isinstance(self.connectivity, np.ndarray)
assert isinstance(self.material_data, np.ndarray)
# Write information about any PMLs, sources, receivers
self.metadata = Metadata(self.grid_view, averaged_materials=True, materials_only=True)
# Number of bytes of data to be written to file
self.nbytes = (
self.points.nbytes
+ self.cell_types.nbytes
+ self.connectivity.nbytes
+ self.cell_offsets.nbytes
+ self.material_data.nbytes
)
def write_vtk(self):
"""Writes geometry information to a VTKHDF file."""
# Write the VTK file
with VtkUnstructuredGrid(
self.filename,
self.points,
self.cell_types,
self.connectivity,
self.cell_offsets,
) as f:
f.add_cell_data("Material", self.material_data)
self.metadata.write_to_vtkhdf(f)
class MPIGeometryViewLines(GeometryViewLines[MPIGridView]):
"""Image data for a per-cell geometry view."""
def __init__(self, grid_view: MPIGridView, filename: str, comm: MPI.Comm):
super().__init__(grid_view, filename)
self.comm = comm
@property
def grid(self) -> MPIGrid:
return self.grid_view.grid
def prep_vtk(self):
"""Prepares data for writing to VTKHDF file."""
ID = self.grid_view.get_ID()
x = np.arange(self.grid_view.gx + 1, dtype=np.float64)
y = np.arange(self.grid_view.gy + 1, dtype=np.float64)
z = np.arange(self.grid_view.gz + 1, dtype=np.float64)
coords = np.meshgrid(x, y, z, indexing="ij")
self.points = np.vstack(list(map(np.ravel, coords))).T
self.points += self.grid_view.global_start
self.points *= self.grid_view.step * self.grid.dl
# Add offset to subgrid geometry to correctly locate within main grid
if isinstance(self.grid, SubGridBaseGrid):
offset = [self.grid.i0, self.grid.j0, self.grid.k0]
self.points += offset * self.grid.dl * self.grid.ratio
# Each point is the 'source' for 3 lines.
# NB: Excluding points at the far edge of the geometry as those
# are the 'source' for no lines
n_lines = 3 * np.prod(self.grid_view.global_size)
self.cell_types = np.full(n_lines, VtkCellType.LINE)
self.cell_offsets = np.arange(0, 2 * n_lines + 2, 2, dtype=np.intc)
self.connectivity, self.material_data = get_line_properties(
n_lines, *self.grid_view.size, ID
)
assert isinstance(self.connectivity, np.ndarray)
assert isinstance(self.material_data, np.ndarray)
# Write information about any PMLs, sources, receivers
self.metadata = Metadata(self.grid_view, averaged_materials=True, materials_only=True)
# Number of bytes of data to be written to file
self.nbytes = (
self.points.nbytes
+ self.cell_types.nbytes
+ self.connectivity.nbytes
+ self.cell_offsets.nbytes
+ self.material_data.nbytes
)
# Use global material IDs rather than local IDs
self.material_data = self.grid.local_to_global_material_id_map(self.material_data)
def write_vtk(self):
"""Writes geometry information to a VTKHDF file."""
with VtkUnstructuredGrid(
self.filename,
self.points,
self.cell_types,
self.connectivity,
self.cell_offsets,
comm=self.comm,
) as f:
f.add_cell_data("Material", self.material_data, self.grid_view.offset)
self.metadata.write_to_vtkhdf(f)

查看文件

@@ -0,0 +1,95 @@
# Copyright (C) 2015-2024: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
#
# gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import numpy as np
from mpi4py import MPI
from gprMax._version import __version__
from gprMax.grid.mpi_grid import MPIGrid
from gprMax.output_controllers.geometry_views import GeometryView, GridViewType, Metadata
from gprMax.output_controllers.grid_view import MPIGridView
from gprMax.subgrids.grid import SubGridBaseGrid
from gprMax.vtkhdf_filehandlers.vtk_image_data import VtkImageData
logger = logging.getLogger(__name__)
class GeometryViewVoxels(GeometryView[GridViewType]):
"""Image data for a per-cell geometry view."""
def prep_vtk(self):
"""Prepares data for writing to VTKHDF file."""
self.material_data = self.grid_view.get_solid()
if isinstance(self.grid, SubGridBaseGrid):
self.origin = np.array(
[
(self.grid.i0 * self.grid.dx * self.grid.ratio),
(self.grid.j0 * self.grid.dy * self.grid.ratio),
(self.grid.k0 * self.grid.dz * self.grid.ratio),
]
)
else:
self.origin = self.grid_view.start * self.grid.dl
self.spacing = self.grid_view.step * self.grid.dl
# Write information about any PMLs, sources, receivers
self.metadata = Metadata(self.grid_view)
self.nbytes = self.material_data.nbytes
def write_vtk(self):
"""Writes geometry information to a VTKHDF file."""
with VtkImageData(self.filename, self.grid_view.size, self.origin, self.spacing) as f:
f.add_cell_data("Material", self.material_data)
self.metadata.write_to_vtkhdf(f)
class MPIGeometryViewVoxels(GeometryViewVoxels[MPIGridView]):
"""Image data for a per-cell geometry view."""
def __init__(self, grid_view: MPIGridView, filename: str, comm: MPI.Comm):
super().__init__(grid_view, filename)
self.comm = comm
@property
def grid(self) -> MPIGrid:
return self.grid_view.grid
def prep_vtk(self):
"""Prepares data for writing to VTKHDF file."""
super().prep_vtk()
# Use global material IDs rather than local IDs
self.material_data = self.grid.local_to_global_material_id_map(self.material_data)
def write_vtk(self):
"""Writes geometry information to a VTKHDF file."""
with VtkImageData(
self.filename, self.grid_view.global_size, self.origin, self.spacing, comm=self.comm
) as f:
f.add_cell_data("Material", self.material_data, self.grid_view.offset)
self.metadata.write_to_vtkhdf(f)

查看文件

@@ -0,0 +1,239 @@
# Copyright (C) 2015-2024: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
#
# gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import sys
from abc import abstractmethod
from pathlib import Path
from typing import Dict, Generic, List, Sequence, Tuple, Union
import h5py
import numpy as np
import numpy.typing as npt
from tqdm import tqdm
import gprMax.config as config
from gprMax._version import __version__
from gprMax.grid.fdtd_grid import FDTDGrid
from gprMax.output_controllers.grid_view import GridType, GridView, GridViewType
from gprMax.receivers import Rx
from gprMax.sources import Source
from gprMax.utilities.utilities import get_terminal_width
from gprMax.vtkhdf_filehandlers.vtkhdf import VtkHdfFile
logger = logging.getLogger(__name__)
def save_geometry_views(gvs: "List[GeometryView]"):
"""Creates and saves geometryviews.
Args:
gvs: list of all GeometryViews.
"""
logger.info("")
for i, gv in enumerate(gvs):
gv.set_filename()
gv.prep_vtk()
pbar = tqdm(
total=gv.nbytes,
unit="byte",
unit_scale=True,
desc=f"Writing geometry view file {i + 1}/{len(gvs)}, {gv.filename.name}",
ncols=get_terminal_width() - 1,
file=sys.stdout,
disable=not config.sim_config.general["progressbars"],
)
gv.write_vtk()
pbar.update(gv.nbytes)
pbar.close()
logger.info("")
class GeometryView(Generic[GridViewType]):
"""Base class for Geometry Views."""
FILE_EXTENSION = ".vtkhdf"
def __init__(
self,
grid_view: GridViewType,
filename: str,
):
"""
Args:
xs, xf, ys, yf, zs, zf: ints for extent of geometry view in cells.
dx, dy, dz: ints for spatial discretisation of geometry view in cells.
filename: string for filename.
grid: FDTDGrid class describing a grid in a model.
"""
self.grid_view = grid_view
self.filenamebase = filename
self.nbytes = None
self.material_data = None
self.materials = None
@property
def grid(self) -> FDTDGrid:
return self.grid_view.grid
def set_filename(self):
"""Construct filename from user-supplied name and model number."""
parts = config.get_model_config().output_file_path.parts
self.filename = Path(
*parts[:-1], self.filenamebase + config.get_model_config().appendmodelnumber
).with_suffix(self.FILE_EXTENSION)
@abstractmethod
def prep_vtk(self):
pass
@abstractmethod
def write_vtk(self):
pass
class Metadata(Generic[GridType]):
"""Comments can be strings included in the header of XML VTK file, and are
used to hold extra (gprMax) information about the VTK data.
"""
def __init__(
self,
gv: GridView[GridType],
averaged_materials: bool = False,
materials_only: bool = False,
):
self.gv = gv
self.averaged_materials = averaged_materials
self.materials_only = materials_only
self.gprmax_version = __version__
self.dx_dy_dz = self.grid.dl
self.nx_ny_nz = np.array([self.grid.nx, self.grid.ny, self.grid.nz], dtype=np.intc)
self.materials = self.materials_comment()
# Write information on PMLs, sources, and receivers
if not self.materials_only:
# Information on PML thickness
if self.grid.pmls["slabs"]:
self.pml_thickness = self.pml_gv_comment()
else:
self.pml_thickness = None
srcs = (
self.grid.hertziandipoles
+ self.grid.magneticdipoles
+ self.grid.voltagesources
+ self.grid.transmissionlines
)
if srcs:
self.source_ids, self.source_positions = self.srcs_rx_gv_comment(srcs)
else:
self.source_ids = None
self.source_positions = None
if self.grid.rxs:
self.receiver_ids, self.receiver_positions = self.srcs_rx_gv_comment(self.grid.rxs)
else:
self.receiver_ids = None
self.receiver_positions = None
@property
def grid(self) -> GridType:
return self.gv.grid
def write_to_vtkhdf(self, file_handler: VtkHdfFile):
file_handler.add_field_data(
"gprMax_version",
self.gprmax_version,
dtype=h5py.string_dtype(),
)
file_handler.add_field_data("dx_dy_dz", self.dx_dy_dz)
file_handler.add_field_data("nx_ny_nz", self.nx_ny_nz)
file_handler.add_field_data(
"material_ids",
self.materials,
dtype=h5py.string_dtype(),
)
if not self.materials_only:
if self.pml_thickness is not None:
file_handler.add_field_data("pml_thickness", self.pml_thickness)
if self.source_ids is not None and self.source_positions is not None:
file_handler.add_field_data(
"source_ids", self.source_ids, dtype=h5py.string_dtype()
)
file_handler.add_field_data("sources", self.source_positions)
if self.receiver_ids is not None and self.receiver_positions is not None:
file_handler.add_field_data(
"receiver_ids", self.receiver_ids, dtype=h5py.string_dtype()
)
file_handler.add_field_data("receivers", self.receiver_positions)
def pml_gv_comment(self) -> List[int]:
grid = self.grid
# Only render PMLs if they are in the geometry view
thickness: Dict[str, int] = grid.pmls["thickness"]
gv_pml_depth = dict.fromkeys(thickness, 0)
if self.gv.xs < thickness["x0"]:
gv_pml_depth["x0"] = thickness["x0"] - self.gv.xs
if self.gv.ys < thickness["y0"]:
gv_pml_depth["y0"] = thickness["y0"] - self.gv.ys
if thickness["z0"] - self.gv.zs > 0:
gv_pml_depth["z0"] = thickness["z0"] - self.gv.zs
if self.gv.xf > grid.nx - thickness["xmax"]:
gv_pml_depth["xmax"] = self.gv.xf - (grid.nx - thickness["xmax"])
if self.gv.yf > grid.ny - thickness["ymax"]:
gv_pml_depth["ymax"] = self.gv.yf - (grid.ny - thickness["ymax"])
if self.gv.zf > grid.nz - thickness["zmax"]:
gv_pml_depth["zmax"] = self.gv.zf - (grid.nz - thickness["zmax"])
return list(gv_pml_depth.values())
def srcs_rx_gv_comment(
self, srcs: Union[Sequence[Source], List[Rx]]
) -> Tuple[List[str], npt.NDArray[np.float32]]:
"""Used to name sources and/or receivers."""
names: List[str] = []
positions: npt.NDArray[np.float32] = np.empty((len(srcs), 3))
for index, src in enumerate(srcs):
position = src.coord * self.grid.dl
names.append(src.ID)
positions[index] = position
return names, positions
def dx_dy_dz_comment(self) -> npt.NDArray[np.float64]:
return self.grid.dl
def nx_ny_nz_comment(self) -> npt.NDArray[np.intc]:
return np.array([self.grid.nx, self.grid.ny, self.grid.nz], dtype=np.intc)
def materials_comment(self) -> List[str]:
if not self.averaged_materials:
return [m.ID for m in self.grid.materials if m.type != "dielectric-smoothed"]
else:
return [m.ID for m in self.grid.materials]

查看文件

@@ -0,0 +1,253 @@
from typing import Generic
import numpy as np
import numpy.typing as npt
from mpi4py import MPI
from typing_extensions import TypeVar
from gprMax.grid.fdtd_grid import FDTDGrid
from gprMax.grid.mpi_grid import MPIGrid
from gprMax.materials import Material
GridType = TypeVar("GridType", bound=FDTDGrid, default="FDTDGrid")
GridViewType = TypeVar("GridViewType", bound="GridView[GridType]", default="GridView")
class GridView(Generic[GridType]):
def __init__(
self,
grid: GridType,
xs: int,
ys: int,
zs: int,
xf: int,
yf: int,
zf: int,
dx: int = 1,
dy: int = 1,
dz: int = 1,
):
"""
Args:
xs, xf, ys, yf, zs, zf: ints for extent of the volume in cells.
filename: string for filename.
"""
self.start = np.array([xs, ys, zs], dtype=np.int32)
self.stop = np.array([xf, yf, zf], dtype=np.int32)
self.step = np.array([dx, dy, dz], dtype=np.int32)
self.size = np.ceil((self.stop - self.start) / self.step).astype(np.int32)
self.grid = grid
self._ID = None
# Properties for backwards compatibility
@property
def xs(self) -> int:
return self.start[0]
@property
def ys(self) -> int:
return self.start[1]
@property
def zs(self) -> int:
return self.start[2]
@property
def xf(self) -> int:
return self.stop[0]
@property
def yf(self) -> int:
return self.stop[1]
@property
def zf(self) -> int:
return self.stop[2]
@property
def dx(self) -> int:
return self.step[0]
@property
def dy(self) -> int:
return self.step[1]
@property
def dz(self) -> int:
return self.step[2]
@property
def nx(self) -> int:
return self.size[0]
@property
def ny(self) -> int:
return self.size[1]
@property
def nz(self) -> int:
return self.size[2]
def get_slice(self, index: int, upper_bound_exclusive: bool = True):
if upper_bound_exclusive:
stop = self.stop[index]
else:
stop = self.stop[index] + self.step[index]
return slice(self.start[index], stop, self.step[index])
def slice_array(self, array: npt.NDArray, upper_bound_exclusive: bool = True):
return np.ascontiguousarray(
array[
...,
self.get_slice(0, upper_bound_exclusive),
self.get_slice(1, upper_bound_exclusive),
self.get_slice(2, upper_bound_exclusive),
]
)
def initialise_materials(self):
ID = self.get_ID(force_refresh=True)
materials_in_grid_view = np.unique(ID)
self.materials = np.array(self.grid.materials, dtype=Material)[materials_in_grid_view]
self.materials.sort()
# Create map from material ID to 0 - number of materials
materials_map = {material.numID: index for index, material in enumerate(self.materials)}
self.map_materials_func = np.vectorize(lambda id: materials_map[id])
def map_to_view_materials(self, array: npt.NDArray):
return self.map_materials_func(array)
def get_ID(self, force_refresh=False):
if self._ID is None or force_refresh:
self._ID = self.slice_array(self.grid.ID, upper_bound_exclusive=False)
return self._ID
def get_solid(self):
return self.slice_array(self.grid.solid)
def get_rigidE(self):
return self.slice_array(self.grid.rigidE)
def get_rigidH(self):
return self.slice_array(self.grid.rigidH)
def get_Ex(self):
return self.slice_array(self.grid.Ex, upper_bound_exclusive=False)
def get_Ey(self):
return self.slice_array(self.grid.Ey, upper_bound_exclusive=False)
def get_Ez(self):
return self.slice_array(self.grid.Ez, upper_bound_exclusive=False)
def get_Hx(self):
return self.slice_array(self.grid.Hx, upper_bound_exclusive=False)
def get_Hy(self):
return self.slice_array(self.grid.Hy, upper_bound_exclusive=False)
def get_Hz(self):
return self.slice_array(self.grid.Hz, upper_bound_exclusive=False)
class MPIGridView(GridView[MPIGrid]):
def __init__(
self,
grid: MPIGrid,
xs: int,
ys: int,
zs: int,
xf: int,
yf: int,
zf: int,
dx: int = 1,
dy: int = 1,
dz: int = 1,
):
super().__init__(grid, xs, ys, zs, xf, yf, zf, dx, dy, dz)
self.global_size = self.size
# Calculate start for the local grid
self.global_start = self.start
self.start = self.grid.global_to_local_coordinate(self.start)
# Bring start into the local grid (and not in the negative halo)
# local_start must still be aligned with the provided step.
self.start = np.where(
self.start < self.grid.negative_halo_offset,
self.grid.negative_halo_offset
+ ((self.start - self.grid.negative_halo_offset) % self.step),
self.start,
)
# Calculate stop for the local grid
self.stop = self.grid.global_to_local_coordinate(self.stop)
self.has_positive_neighbour = self.stop > self.grid.size
# Limit stop such that it is at most one step beyond the max
# index of the grid. As stop is the upper bound, it is
# exclusive, meaning when used to slice an array (with the
# provided step), the last element accessed will one step below
# stop.
# Note: using self.grid.size as an index in any dimension would
# fall in the positive halo (this counts as outside the local
# grid).
self.stop = np.where(
self.has_positive_neighbour,
self.grid.size + ((self.stop - self.grid.size) % self.step),
self.stop,
)
# Calculate offset for the local grid view
self.offset = self.grid.local_to_global_coordinate(self.start) - self.global_start
# Update local size
self.size = self.stop - self.start
@property
def gx(self) -> int:
return self.global_size[0]
@property
def gy(self) -> int:
return self.global_size[1]
@property
def gz(self) -> int:
return self.global_size[2]
def get_slice(self, index: int, upper_bound_exclusive: bool = True):
if upper_bound_exclusive:
stop = self.stop[index]
else:
# Make slice of array one step larger if this rank does not
# have a positive neighbour
stop = np.where(
self.has_positive_neighbour,
self.stop,
self.stop + self.step,
)
return slice(self.start[index], stop, self.step[index])
def initialise_materials(self, comm: MPI.Comm):
ID = self.get_ID(force_refresh=True)
local_material_ids = np.unique(ID)
local_materials = np.array(self.grid.materials, dtype=Material)[local_material_ids]
self.materials, material_id_map = self.grid.remap_material_ids(
local_materials.tolist(), comm
)
# Create map from material ID to 0 - number of materials
self.map_materials_func = np.vectorize(lambda id: material_id_map(id))