Move model configs inside SimulationConfig

这个提交包含在:
nmannall
2024-03-26 17:29:56 +00:00
父节点 3032ede5e4
当前提交 d0db6b3120
共有 3 个文件被更改,包括 189 次插入93 次删除

查看文件

@@ -20,7 +20,7 @@ import logging
import sys
import warnings
from pathlib import Path
from typing import List, Union
from typing import List, Optional, Union
import cython
import numpy as np
@@ -42,10 +42,11 @@ class ModelConfig:
N.B. Multiple models can exist within a simulation
"""
def __init__(self):
def __init__(self, model_num):
self.mode = "3D"
self.grids = []
self.ompthreads = None
self.model_num = model_num
# Store information for CUDA or OpenCL solver
# dev: compute device object.
@@ -70,7 +71,11 @@ class ModelConfig:
except:
deviceID = 0
self.device = {"dev": sim_config.set_model_device(deviceID), "deviceID": deviceID, "snapsgpu2cpu": False}
self.device = {
"dev": sim_config.get_model_device(deviceID),
"deviceID": deviceID,
"snapsgpu2cpu": False,
}
# Total memory usage for all grids in the model. Starts with 50MB overhead.
self.mem_overhead = 65e6
@@ -79,11 +84,18 @@ class ModelConfig:
self.reuse_geometry = False
# String to print at start of each model run
s = f"\n--- Model {model_num + 1}/{sim_config.model_end}, " f"input file: {sim_config.input_file_path}"
self.inputfilestr = Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL
s = (
f"\n--- Model {model_num + 1}/{sim_config.model_end}, "
f"input file: {sim_config.input_file_path}"
)
self.inputfilestr = (
Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL
)
# Output file path and name for specific model
self.appendmodelnumber = "" if sim_config.args.n == 1 else str(model_num + 1) # Indexed from 1
self.appendmodelnumber = (
"" if sim_config.args.n == 1 else str(model_num + 1)
) # Indexed from 1
self.set_output_file_path()
# Numerical dispersion analysis parameters
@@ -111,9 +123,12 @@ class ModelConfig:
"crealfunc": None,
}
def reuse_geometry(self):
return self.model_num != 0 and sim_config.args.geometry_fixed
def get_scene(self):
try:
return sim_config.scenes[model_num]
return sim_config.scenes[self.model_num]
except:
return None
@@ -121,7 +136,7 @@ class ModelConfig:
"""Namespace only used with #python blocks which are deprecated."""
tmp = {
"number_model_runs": sim_config.model_end,
"current_model_run": model_num + 1,
"current_model_run": self.model_num + 1,
"inputfile": sim_config.input_file_path.resolve(),
}
return dict(**sim_config.em_consts, **tmp)
@@ -177,6 +192,14 @@ class SimulationConfig:
N.B. A simulation can consist of multiple models.
"""
# TODO: Make this an enum
em_consts = {
"c": c, # Speed of light in free space (m/s)
"e0": e0, # Permittivity of free space (F/m)
"m0": m0, # Permeability of free space (H/m)
"z0": np.sqrt(m0 / e0), # Impedance of free space (Ohms)
}
def __init__(self, args):
"""
Args:
@@ -185,14 +208,38 @@ class SimulationConfig:
self.args = args
if self.args.taskfarm and self.args.geometry_fixed:
self.geometry_fixed: bool = args.geometry_fixed
self.geometry_only: bool = args.geometry_only
self.gpu: Union[List[str], bool] = args.gpu
self.mpi: List[int] = args.mpi
self.number_of_models: int = args.n
self.opencl: Union[List[str], bool] = args.opencl
self.output_file_path: str = args.outputfile
self.taskfarm: bool = args.taskfarm
self.write_processed_input_file: bool = (
args.write_processed
) # For depreciated Python blocks
if self.taskfarm and self.geometry_fixed:
logger.exception("The geometry fixed option cannot be used with MPI taskfarm.")
raise ValueError
if self.args.gpu and self.args.opencl:
if self.gpu and self.opencl:
logger.exception("You cannot use both CUDA and OpenCl simultaneously.")
raise ValueError
if self.mpi and self.args.subgrid:
logger.exception("You cannot use subgrids with MPI.")
raise ValueError
# Each model in a simulation is given a unique number when the instance of ModelConfig is created
self.current_model = 0
# Instances of ModelConfig that hold model configuration parameters.
# TODO: Consider if this would be better as a dictionary.
# Or maybe a non fixed length list (i.e. append each config)
self.model_configs: List[Optional[ModelConfig]] = [None] * self.number_of_models
# General settings for the simulation
# solver: cpu, cuda, opencl.
# precision: data type for electromagnetic field output (single/double).
@@ -200,25 +247,25 @@ class SimulationConfig:
# progressbars when logging level is greater than
# info (20)
self.general = {"solver": "cpu", "precision": "single", "progressbars": args.log_level <= 20}
self.em_consts = {
"c": c, # Speed of light in free space (m/s)
"e0": e0, # Permittivity of free space (F/m)
"m0": m0, # Permeability of free space (H/m)
"z0": np.sqrt(m0 / e0), # Impedance of free space (Ohms)
self.general = {
"solver": "cpu",
"precision": "single",
"progressbars": args.log_level <= 20,
}
# Store information about host machine
self.hostinfo = get_host_info()
# CUDA
if self.args.gpu is not None:
if self.gpu is not None:
self.general["solver"] = "cuda"
# Both single and double precision are possible on GPUs, but single
# provides best performance.
self.general["precision"] = "single"
self.devices = {"devs": [], "nvcc_opts": None} # pycuda device objects; nvcc compiler options
self.devices = {
"devs": [],
"nvcc_opts": None,
} # pycuda device objects; nvcc compiler options
# Suppress nvcc warnings on Microsoft Windows
if sys.platform == "win32":
self.devices["nvcc_opts"] = ["-w"]
@@ -227,10 +274,13 @@ class SimulationConfig:
self.devices["devs"] = detect_cuda_gpus()
# OpenCL
if self.args.opencl is not None:
if self.opencl is not None:
self.general["solver"] = "opencl"
self.general["precision"] = "single"
self.devices = {"devs": [], "compiler_opts": None} # pyopencl device device(s); compiler options
self.devices = {
"devs": [],
"compiler_opts": None,
} # pyopencl device device(s); compiler options
# Suppress CompilerWarning (sub-class of UserWarning)
warnings.filterwarnings("ignore", category=UserWarning)
@@ -250,12 +300,16 @@ class SimulationConfig:
self.general["subgrid"] and self.general["solver"] == "opencl"
):
logger.exception(
"You cannot currently use CUDA or OpenCL-based " "solvers with models that contain sub-grids."
"You cannot currently use CUDA or OpenCL-based solvers with models that contain sub-grids."
)
raise ValueError
else:
self.general["subgrid"] = False
self.autotranslate_subgrid_coordinates = True
if hasattr(self.args, "autotranslate"):
self.autotranslate_subgrid_coordinates: bool = args.autotranslate
# Scenes parameter may not exist if user enters via CLI
try:
self.scenes = args.scenes if args.scenes is not None else []
@@ -267,26 +321,6 @@ class SimulationConfig:
self._set_input_file_path()
self._set_model_start_end()
def set_model_device(self, deviceID):
"""Specify pycuda/pyopencl object for model.
Args:
deviceID: int of requested deviceID of compute device.
Returns:
dev: requested pycuda/pyopencl device object.
"""
found = False
for ID, dev in self.devices["devs"].items():
if ID == deviceID:
found = True
return dev
if not found:
logger.exception(f"Compute device with device ID {deviceID} does " "not exist.")
raise ValueError
def _set_precision(self):
"""Data type (precision) for electromagnetic field output.
@@ -325,6 +359,15 @@ class SimulationConfig:
elif self.general["solver"] == "opencl":
self.dtypes["C_complex"] = "cdouble"
def _set_input_file_path(self):
"""Sets input file path for CLI or API."""
# API
if self.args.inputfile is None:
self.input_file_path = Path(self.args.outputfile)
# API/CLI
else:
self.input_file_path = Path(self.args.inputfile)
def _set_model_start_end(self):
"""Sets range for number of models to run (internally 0 index)."""
if self.args.i:
@@ -337,30 +380,69 @@ class SimulationConfig:
self.model_start = modelstart
self.model_end = modelend
def _set_input_file_path(self):
"""Sets input file path for CLI or API."""
# API
if self.args.inputfile is None:
self.input_file_path = Path(self.args.outputfile)
# API/CLI
else:
self.input_file_path = Path(self.args.inputfile)
def get_model_device(self, deviceID):
"""Specify pycuda/pyopencl object for model.
Args:
deviceID: int of requested deviceID of compute device.
Returns:
dev: requested pycuda/pyopencl device object.
"""
found = False
for ID, dev in self.devices["devs"].items():
if ID == deviceID:
found = True
return dev
if not found:
logger.exception(f"Compute device with device ID {deviceID} does " "not exist.")
raise ValueError
def get_model_config(self, model_num: Optional[int] = None) -> ModelConfig:
"""Return ModelConfig instance for specific model.
Args:
model_num: number of the model. If None, returns the config for the current model
Returns:
model_config: requested model config
"""
if model_num is None:
model_num = self.current_model
model_config = self.model_configs[model_num]
if model_config is None:
logger.exception(f"Cannot get ModelConfig for model {model_num}. It has not been set.")
raise ValueError
return model_config
def set_model_config(self, model_config: ModelConfig, model_num: Optional[int] = None) -> None:
"""Set ModelConfig instace for specific model.
Args:
model_num: number of the model. If None, sets the config for the current model
"""
if model_num is None:
model_num = self.current_model
self.model_configs[model_num] = model_config
def set_current_model(self, model_num: int) -> None:
"""Set the current model by it's unique identifier
Args:
model_num: unique identifier for the current model
"""
self.current_model = model_num
# Single instance of SimConfig to hold simulation configuration parameters.
sim_config: SimulationConfig = None
# Instances of ModelConfig that hold model configuration parameters.
model_configs: Union[ModelConfig, List[ModelConfig]] = []
# Each model in a simulation is given a unique number when the instance of
# ModelConfig is created
model_num: int = 0
def get_model_config() -> ModelConfig:
"""Return ModelConfig instace for specific model."""
if isinstance(model_configs, ModelConfig):
return model_configs
else:
return model_configs[model_num]
"""Return ModelConfig instance for specific model."""
return sim_config.get_model_config()

查看文件

@@ -28,6 +28,7 @@ from colorama import Fore, Style, init
init()
import gprMax.config as config
from gprMax.config import ModelConfig
from ._version import __version__, codename
from .model_build_run import ModelBuildRun
@@ -81,10 +82,6 @@ class Context:
self._start_simulation()
# Clear list of model configs. It can be retained when gprMax is
# called in a loop, and want to avoid this.
config.model_configs = []
for i in self.model_range:
self._run_model(i)
@@ -99,8 +96,9 @@ class Context:
model_num: index of model to be run
"""
config.model_num = model_num
self._set_model_config()
config.sim_config.set_current_model(model_num)
model_config = self._create_model_config(model_num)
config.sim_config.set_model_config(model_config)
# Always create a grid for the first model. The next model to run
# only gets a new grid if the geometry is not re-used.
@@ -124,10 +122,9 @@ class Context:
gc.collect()
def _set_model_config(self) -> None:
def _create_model_config(self, model_num: int) -> ModelConfig:
"""Create model config and save to global config."""
model_config = config.ModelConfig()
config.model_configs.append(model_config)
return ModelConfig(model_num)
def print_logo_copyright(self) -> None:
"""Prints gprMax logo, version, and copyright/licencing information."""
@@ -193,12 +190,12 @@ class TaskfarmContext(Context):
self.rank = self.comm.rank
self.TaskfarmExecutor = TaskfarmExecutor
def _set_model_config(self) -> None:
def _create_model_config(self, model_num: int) -> ModelConfig:
"""Create model config and save to global config.
Set device in model config according to MPI rank.
"""
model_config = config.ModelConfig()
model_config = super()._create_model_config(model_num)
# Set GPU deviceID according to worker rank
if config.sim_config.general["solver"] == "cuda":
model_config.device = {
@@ -206,7 +203,7 @@ class TaskfarmContext(Context):
"deviceID": self.rank - 1,
"snapsgpu2cpu": False,
}
config.model_configs = model_config
return model_config
def _run_model(self, **work) -> None:
"""Process for running a single model.

查看文件

@@ -73,12 +73,15 @@ class ModelBuildRun:
# Normal model reading/building process; bypassed if geometry information to be reused
self.reuse_geometry() if config.get_model_config().reuse_geometry else self.build_geometry()
logger.info(f"\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}")
logger.info(
f"\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}"
)
# Adjust position of simple sources and receivers if required
if G.srcsteps[0] != 0 or G.srcsteps[1] != 0 or G.srcsteps[2] != 0:
model_num = config.sim_config.current_model
for source in itertools.chain(G.hertziandipoles, G.magneticdipoles):
if config.model_num == 0:
if model_num == 0:
if (
source.xcoord + G.srcsteps[0] * config.sim_config.model_end < 0
or source.xcoord + G.srcsteps[0] * config.sim_config.model_end > G.nx
@@ -87,14 +90,17 @@ class ModelBuildRun:
or source.zcoord + G.srcsteps[2] * config.sim_config.model_end < 0
or source.zcoord + G.srcsteps[2] * config.sim_config.model_end > G.nz
):
logger.exception("Source(s) will be stepped to a position outside the domain.")
logger.exception(
"Source(s) will be stepped to a position outside the domain."
)
raise ValueError
source.xcoord = source.xcoordorigin + config.model_num * G.srcsteps[0]
source.ycoord = source.ycoordorigin + config.model_num * G.srcsteps[1]
source.zcoord = source.zcoordorigin + config.model_num * G.srcsteps[2]
source.xcoord = source.xcoordorigin + model_num * G.srcsteps[0]
source.ycoord = source.ycoordorigin + model_num * G.srcsteps[1]
source.zcoord = source.zcoordorigin + model_num * G.srcsteps[2]
if G.rxsteps[0] != 0 or G.rxsteps[1] != 0 or G.rxsteps[2] != 0:
model_num = config.sim_config.current_model
for receiver in G.rxs:
if config.model_num == 0:
if model_num == 0:
if (
receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end < 0
or receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end > G.nx
@@ -103,11 +109,13 @@ class ModelBuildRun:
or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end < 0
or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end > G.nz
):
logger.exception("Receiver(s) will be stepped to a position outside the domain.")
logger.exception(
"Receiver(s) will be stepped to a position outside the domain."
)
raise ValueError
receiver.xcoord = receiver.xcoordorigin + config.model_num * G.rxsteps[0]
receiver.ycoord = receiver.ycoordorigin + config.model_num * G.rxsteps[1]
receiver.zcoord = receiver.zcoordorigin + config.model_num * G.rxsteps[2]
receiver.xcoord = receiver.xcoordorigin + model_num * G.rxsteps[0]
receiver.ycoord = receiver.ycoordorigin + model_num * G.rxsteps[1]
receiver.zcoord = receiver.zcoordorigin + model_num * G.rxsteps[2]
# Write files for any geometry views and geometry object outputs
gvs = G.geometryviews + [gv for sg in G.subgrids for gv in sg.geometryviews]
@@ -204,7 +212,8 @@ class ModelBuildRun:
results = dispersion_analysis(gb.grid)
if results["error"]:
logger.warning(
f"\nNumerical dispersion analysis [{gb.grid.name}] " f"not carried out as {results['error']}"
f"\nNumerical dispersion analysis [{gb.grid.name}] "
f"not carried out as {results['error']}"
)
elif results["N"] < config.get_model_config().numdispersion["mingridsampling"]:
logger.exception(
@@ -218,7 +227,8 @@ class ModelBuildRun:
raise ValueError
elif (
results["deltavp"]
and np.abs(results["deltavp"]) > config.get_model_config().numdispersion["maxnumericaldisp"]
and np.abs(results["deltavp"])
> config.get_model_config().numdispersion["maxnumericaldisp"]
):
logger.warning(
f"\n[{gb.grid.name}] has potentially significant "
@@ -295,7 +305,7 @@ class ModelBuildRun:
# Print information about and check OpenMP threads
if config.sim_config.general["solver"] == "cpu":
logger.basic(
f"\nModel {config.model_num + 1}/{config.sim_config.model_end} "
f"\nModel {config.sim_config.current_model + 1}/{config.sim_config.model_end} "
f"on {config.sim_config.hostinfo['hostname']} "
f"with OpenMP backend using {config.get_model_config().ompthreads} thread(s)"
)
@@ -308,7 +318,10 @@ class ModelBuildRun:
elif config.sim_config.general["solver"] in ["cuda", "opencl"]:
if config.sim_config.general["solver"] == "opencl":
solvername = "OpenCL"
platformname = " ".join(config.get_model_config().device["dev"].platform.name.split()) + " with "
platformname = (
" ".join(config.get_model_config().device["dev"].platform.name.split())
+ " with "
)
devicename = (
f'Device {config.get_model_config().device["deviceID"]}: '
f'{" ".join(config.get_model_config().device["dev"].name.split())}'
@@ -322,7 +335,7 @@ class ModelBuildRun:
)
logger.basic(
f"\nModel {config.model_num + 1}/{config.sim_config.model_end} "
f"\nModel {config.sim_config.current_model + 1}/{config.sim_config.model_end} "
f"solving on {config.sim_config.hostinfo['hostname']} "
f"with {solvername} backend using {platformname}{devicename}"
)
@@ -353,9 +366,13 @@ class ModelBuildRun:
elif config.sim_config.general["solver"] == "opencl":
mem_str = f" host + unknown for device"
logger.info(f"\nMemory used (estimated): " + f"~{humanize.naturalsize(self.p.memory_full_info().uss)}{mem_str}")
logger.info(
f"Time taken: " + f"{humanize.precisedelta(datetime.timedelta(seconds=solver.solvetime), format='%0.4f')}"
f"\nMemory used (estimated): "
+ f"~{humanize.naturalsize(self.p.memory_full_info().uss)}{mem_str}"
)
logger.info(
f"Time taken: "
+ f"{humanize.precisedelta(datetime.timedelta(seconds=solver.solvetime), format='%0.4f')}"
)