def __init__(self, G): """ Args: G (class): Grid class instance - holds essential parameters describing the model. """ super(Source, self).__init__() self.resistance = None # Coefficients for ABC termination of end of the transmission line self.abcv0 = 0 self.abcv1 = 0 # Spatial step of transmission line (based on magic time step for dispersionless behaviour) self.dl = c * G.dt # Number of cells in the transmission line (initially a long line to calculate incident voltage and current); consider putting ABCs/PML at end self.nl = round_value(0.667 * G.iterations) # Cell position of the one-way injector excitation in the transmission line self.srcpos = 5 # Cell position of where line connects to antenna/main grid self.antpos = 10 self.voltage = np.zeros(self.nl, dtype=floattype) self.current = np.zeros(self.nl, dtype=floattype) self.Vinc = np.zeros(G.iterations, dtype=floattype) self.Iinc = np.zeros(G.iterations, dtype=floattype) self.Vtotal = np.zeros(G.iterations, dtype=floattype) self.Itotal = np.zeros(G.iterations, dtype=floattype)
def check_timewindow(timewindow, dt): """Checks and sets time window and number of iterations. Args: timewindow (float): Time window. dt (float): Time discretisation. Returns: timewindow (float): Time window. iterations (int): Number of interations. """ # Time window could be a string, float or int, so convert to string then check timewindow = str(timewindow) try: timewindow = int(timewindow) iterations = timewindow timewindow = (timewindow - 1) * dt except: timewindow = float(timewindow) if timewindow > 0: iterations = round_value((timewindow / dt)) + 1 else: raise CmdInputError('Time window must have a value greater than zero') return timewindow, iterations
def calculate_blade_geometry(self, blade, height): """Calculates the x and y coordinates for a given height of grass blade. Args: blade (int): Numeric ID of grass blade. height (float): Height of grass blade. Returns: x, y (float): x and y coordinates of grass blade. """ x = self.geometryparams[blade, 2] * (height / self.geometryparams[blade, 0]) * (height / self.geometryparams[blade, 0]) y = self.geometryparams[blade, 3] * (height / self.geometryparams[blade, 1]) * (height / self.geometryparams[blade, 1]) x = round_value(x) y = round_value(y) return x, y
def __init__(self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, dx=None, dy=None, dz=None, filename=None, fileext=None): """ Args: xs, xf, ys, yf, zs, zf (int): Extent of the volume in cells. dx, dy, dz (int): Spatial discretisation in cells. filename (str): Filename to save to. fileext (str): File extension of VTK file - either '.vti' for a per cell geometry view, or '.vtp' for a per cell edge geometry view. """ self.xs = xs self.ys = ys self.zs = zs self.xf = xf self.yf = yf self.zf = zf self.nx = self.xf - self.xs self.ny = self.yf - self.ys self.nz = self.zf - self.zs self.dx = dx self.dy = dy self.dz = dz self.basefilename = filename self.fileext = fileext if self.fileext == '.vti': # Calculate number of cells according to requested sampling for geometry view self.vtk_xscells = round_value(self.xs / self.dx) self.vtk_xfcells = round_value(self.xf / self.dx) self.vtk_yscells = round_value(self.ys / self.dy) self.vtk_yfcells = round_value(self.yf / self.dy) self.vtk_zscells = round_value(self.zs / self.dz) self.vtk_zfcells = round_value(self.zf / self.dz) self.vtk_nxcells = self.vtk_xfcells - self.vtk_xscells self.vtk_nycells = self.vtk_yfcells - self.vtk_yscells self.vtk_nzcells = self.vtk_zfcells - self.vtk_zscells self.datawritesize = int(np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + 2 * (int(np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells)) elif self.fileext == '.vtp': self.vtk_numpoints = (self.nx + 1) * (self.ny + 1) * (self.nz + 1) self.vtk_numpoint_components = 3 self.vtk_numlines = 2 * self.nx * self.ny + 2 * self.ny * self.nz + 2 * self.nx * self.nz + 3 * self.nx * self.ny * self.nz + self.nx + self.ny + self.nz self.vtk_numline_components = 2 self.vtk_connectivity_offset = round_value((self.vtk_numpoints * self.vtk_numpoint_components * np.dtype(np.float32).itemsize) + np.dtype(np.uint32).itemsize) self.vtk_offsets_offset = round_value(self.vtk_connectivity_offset + (self.vtk_numlines * self.vtk_numline_components * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize) self.vtk_materials_offset = round_value(self.vtk_offsets_offset + (self.vtk_numlines * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize) self.datawritesize = np.dtype(np.float32).itemsize * self.vtk_numpoints * self.vtk_numpoint_components + np.dtype(np.uint32).itemsize * self.vtk_numlines * self.vtk_numline_components + np.dtype(np.uint32).itemsize * self.vtk_numlines + np.dtype(np.uint32).itemsize * self.vtk_numlines
def __init__(self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, dx=None, dy=None, dz=None, time=None, filename=None): """ Args: xs, xf, ys, yf, zs, zf (int): Extent of the volume in cells. dx, dy, dz (int): Spatial discretisation in cells. time (int): Iteration number to take the snapshot on. filename (str): Filename to save to. """ self.xs = xs self.ys = ys self.zs = zs self.xf = xf self.yf = yf self.zf = zf self.dx = dx self.dy = dy self.dz = dz self.nx = round_value((self.xf - self.xs) / self.dx) self.ny = round_value((self.yf - self.ys) / self.dy) self.nz = round_value((self.zf - self.zs) / self.dz) self.sx = slice(self.xs, self.xf + self.dx, self.dx) self.sy = slice(self.ys, self.yf + self.dy, self.dy) self.sz = slice(self.zs, self.zf + self.dz, self.dz) self.ncells = self.nx * self.ny * self.nz self.datasizefield = 3 * np.dtype(floattype).itemsize * self.ncells self.vtkdatawritesize = 2 * self.datasizefield + 2 * np.dtype( np.uint32).itemsize self.time = time self.basefilename = filename
def __init__(self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, dx=None, dy=None, dz=None, filename=None, fileext=None): """ Args: xs, xf, ys, yf, zs, zf (int): Extent of the volume in cells. dx, dy, dz (int): Spatial discretisation in cells. filename (str): Filename to save to. fileext (str): File extension of VTK file - either '.vti' for a per cell geometry view, or '.vtp' for a per cell edge geometry view. """ self.xs = xs self.ys = ys self.zs = zs self.xf = xf self.yf = yf self.zf = zf self.nx = self.xf - self.xs self.ny = self.yf - self.ys self.nz = self.zf - self.zs self.dx = dx self.dy = dy self.dz = dz self.basefilename = filename self.fileext = fileext if self.fileext == '.vti': # Calculate number of cells according to requested sampling for geometry view self.vtk_xscells = round_value(self.xs / self.dx) self.vtk_xfcells = round_value(self.xf / self.dx) self.vtk_yscells = round_value(self.ys / self.dy) self.vtk_yfcells = round_value(self.yf / self.dy) self.vtk_zscells = round_value(self.zs / self.dz) self.vtk_zfcells = round_value(self.zf / self.dz) self.vtk_nxcells = self.vtk_xfcells - self.vtk_xscells self.vtk_nycells = self.vtk_yfcells - self.vtk_yscells self.vtk_nzcells = self.vtk_zfcells - self.vtk_zscells self.datawritesize = int(np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + 2 * (int(np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells)) elif self.fileext == '.vtp': self.vtk_numpoints = (self.nx + 1) * (self.ny + 1) * (self.nz + 1) self.vtk_numpoint_components = 3 self.vtk_numlines = 2 * self.nx * self.ny + 2 * self.ny * self.nz + 2 * self.nx * self.nz + 3 * self.nx * self.ny * self.nz + self.nx + self.ny + self.nz self.vtk_numline_components = 2 self.vtk_connectivity_offset = round_value((self.vtk_numpoints * self.vtk_numpoint_components * np.dtype(np.float32).itemsize) + np.dtype(np.uint32).itemsize) self.vtk_offsets_offset = round_value(self.vtk_connectivity_offset + (self.vtk_numlines * self.vtk_numline_components * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize) self.vtk_materials_offset = round_value(self.vtk_offsets_offset + (self.vtk_numlines * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize) self.datawritesize = np.dtype(np.float32).itemsize * self.vtk_numpoints * self.vtk_numpoint_components + np.dtype(np.uint32).itemsize * self.vtk_numlines * self.vtk_numline_components + np.dtype(np.uint32).itemsize * self.vtk_numlines + np.dtype(np.uint32).itemsize * self.vtk_numlines
def calculate_blade_geometry(self, blade, height): """Calculates the x and y coordinates for a given height of grass blade. Args: blade (int): Numeric ID of grass blade. height (float): Height of grass blade. Returns: x, y (float): x and y coordinates of grass blade. """ x = self.geometryparams[blade, 2] * ( height / self.geometryparams[blade, 0]) * ( height / self.geometryparams[blade, 0]) y = self.geometryparams[blade, 3] * ( height / self.geometryparams[blade, 1]) * ( height / self.geometryparams[blade, 1]) x = round_value(x) y = round_value(y) return x, y
def write_vtk_imagedata(self, pbar, G): """Write snapshot data to a VTK ImageData (.vti) file. N.B. No Python 3 support for VTK at time of writing (03/2015) Args: pbar (class): Progress bar class instance. G (class): Grid class instance - holds essential parameters describing the model. """ hfield_offset = 3 * np.dtype( floattype).itemsize * self.ncells + np.dtype(np.uint32).itemsize self.filehandle = open(self.filename, 'wb') self.filehandle.write('<?xml version="1.0"?>\n'.encode('utf-8')) self.filehandle.write( '<VTKFile type="ImageData" version="1.0" byte_order="{}">\n'. format(Snapshot.byteorder).encode('utf-8')) self.filehandle.write( '<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n' .format(self.xs, round_value(self.xf / self.dx), self.ys, round_value(self.yf / self.dy), self.zs, round_value(self.zf / self.dz), self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) self.filehandle.write('<Piece Extent="{} {} {} {} {} {}">\n'.format( self.xs, round_value(self.xf / self.dx), self.ys, round_value(self.yf / self.dy), self.zs, round_value(self.zf / self.dz)).encode('utf-8')) self.filehandle.write( '<CellData Vectors="E-field H-field">\n'.encode('utf-8')) self.filehandle.write( '<DataArray type="{}" Name="E-field" NumberOfComponents="3" format="appended" offset="0" />\n' .format(Snapshot.floatname).encode('utf-8')) self.filehandle.write( '<DataArray type="{}" Name="H-field" NumberOfComponents="3" format="appended" offset="{}" />\n' .format(Snapshot.floatname, hfield_offset).encode('utf-8')) self.filehandle.write( '</CellData>\n</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_' .encode('utf-8')) # Write number of bytes of appended data as UInt32 self.filehandle.write(pack('I', self.datasizefield)) pbar.update(n=4) self.electric.tofile(self.filehandle) pbar.update(n=self.datasizefield) # Write number of bytes of appended data as UInt32 self.filehandle.write(pack('I', self.datasizefield)) pbar.update(n=4) self.magnetic.tofile(self.filehandle) pbar.update(n=self.datasizefield) self.filehandle.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.filehandle.close()
def prepare_vtk_imagedata(self, modelrun, numbermodelruns, G): """Prepares a VTK ImageData (.vti) file for a snapshot. Args: modelrun (int): Current model run number. numbermodelruns (int): Total number of model runs. G (class): Grid class instance - holds essential parameters describing the model. """ # No Python 3 support for VTK at time of writing (03/2015) self.vtk_nx = self.xf - self.xs self.vtk_ny = self.yf - self.ys self.vtk_nz = self.zf - self.zs # Create directory and construct filename from user-supplied name and model run number if numbermodelruns == 1: snapshotdir = os.path.join(G.inputdirectory, os.path.splitext(G.inputfilename)[0] + '_snaps') else: snapshotdir = os.path.join(G.inputdirectory, os.path.splitext(G.inputfilename)[0] + '_snaps' + str(modelrun)) if not os.path.exists(snapshotdir): os.mkdir(snapshotdir) self.filename = os.path.abspath(os.path.join(snapshotdir, self.basefilename + '.vti')) # Calculate number of cells according to requested sampling self.vtk_xscells = round_value(self.xs / self.dx) self.vtk_xfcells = round_value(self.xf / self.dx) self.vtk_yscells = round_value(self.ys / self.dy) self.vtk_yfcells = round_value(self.yf / self.dy) self.vtk_zscells = round_value(self.zs / self.dz) self.vtk_zfcells = round_value(self.zf / self.dz) vtk_hfield_offset = 3 * np.dtype(floattype).itemsize * (self.vtk_xfcells - self.vtk_xscells) * (self.vtk_yfcells - self.vtk_yscells) * (self.vtk_zfcells - self.vtk_zscells) + np.dtype(np.uint32).itemsize vtk_current_offset = 2 * vtk_hfield_offset self.filehandle = open(self.filename, 'wb') self.filehandle.write('<?xml version="1.0"?>\n'.encode('utf-8')) self.filehandle.write('<VTKFile type="ImageData" version="1.0" byte_order="{}">\n'.format(Snapshot.byteorder).encode('utf-8')) self.filehandle.write('<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) self.filehandle.write('<Piece Extent="{} {} {} {} {} {}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) self.filehandle.write('<CellData Vectors="E-field H-field Current">\n'.encode('utf-8')) self.filehandle.write('<DataArray type="{}" Name="E-field" NumberOfComponents="3" format="appended" offset="0" />\n'.format(Snapshot.floatname).encode('utf-8')) self.filehandle.write('<DataArray type="{}" Name="H-field" NumberOfComponents="3" format="appended" offset="{}" />\n'.format(Snapshot.floatname, vtk_hfield_offset).encode('utf-8')) self.filehandle.write('<DataArray type="{}" Name="Current" NumberOfComponents="3" format="appended" offset="{}" />\n'.format(Snapshot.floatname, vtk_current_offset).encode('utf-8')) self.filehandle.write('</CellData>\n</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_'.encode('utf-8')) self.filehandle.close()
def prepare_vtk_imagedata(self, modelrun, numbermodelruns, G): """Prepares a VTK ImageData (.vti) file for a snapshot. Args: modelrun (int): Current model run number. numbermodelruns (int): Total number of model runs. G (class): Grid class instance - holds essential parameters describing the model. """ # No Python 3 support for VTK at time of writing (03/2015) self.vtk_nx = self.xf - self.xs self.vtk_ny = self.yf - self.ys self.vtk_nz = self.zf - self.zs # Create directory and construct filename from user-supplied name and model run number if numbermodelruns == 1: snapshotdir = os.path.join(G.inputdirectory, os.path.splitext(G.inputfilename)[0] + '_snaps') else: snapshotdir = os.path.join(G.inputdirectory, os.path.splitext(G.inputfilename)[0] + '_snaps' + str(modelrun)) if not os.path.exists(snapshotdir): os.mkdir(snapshotdir) self.filename = os.path.abspath(os.path.join(snapshotdir, self.basefilename + '.vti')) # Calculate number of cells according to requested sampling self.vtk_xscells = round_value(self.xs / self.dx) self.vtk_xfcells = round_value(self.xf / self.dx) self.vtk_yscells = round_value(self.ys / self.dy) self.vtk_yfcells = round_value(self.yf / self.dy) self.vtk_zscells = round_value(self.zs / self.dz) self.vtk_zfcells = round_value(self.zf / self.dz) vtk_hfield_offset = 3 * np.dtype(floattype).itemsize * (self.vtk_xfcells - self.vtk_xscells) * (self.vtk_yfcells - self.vtk_yscells) * (self.vtk_zfcells - self.vtk_zscells) + np.dtype(np.uint32).itemsize vtk_current_offset = 2 * vtk_hfield_offset self.filehandle = open(self.filename, 'wb') self.filehandle.write('<?xml version="1.0"?>\n'.encode('utf-8')) self.filehandle.write('<VTKFile type="ImageData" version="1.0" byte_order="{}">\n'.format(Snapshot.byteorder).encode('utf-8')) self.filehandle.write('<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) self.filehandle.write('<Piece Extent="{} {} {} {} {} {}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) self.filehandle.write('<CellData Vectors="E-field H-field Current">\n'.encode('utf-8')) self.filehandle.write('<DataArray type="{}" Name="E-field" NumberOfComponents="3" format="appended" offset="0" />\n'.format(Snapshot.floatname).encode('utf-8')) self.filehandle.write('<DataArray type="{}" Name="H-field" NumberOfComponents="3" format="appended" offset="{}" />\n'.format(Snapshot.floatname, vtk_hfield_offset).encode('utf-8')) self.filehandle.write('<DataArray type="{}" Name="Current" NumberOfComponents="3" format="appended" offset="{}" />\n'.format(Snapshot.floatname, vtk_current_offset).encode('utf-8')) self.filehandle.write('</CellData>\n</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_'.encode('utf-8')) self.filehandle.close()
def __init__(self, G): """ Args: G (class): Grid class instance - holds essential parameters describing the model. """ super().__init__() self.resistance = None # Coefficients for ABC termination of end of the transmission line self.abcv0 = 0 self.abcv1 = 0 # Spatial step of transmission line (N.B if the magic time step is # used it results in instabilities for certain impedances) self.dl = np.sqrt(3) * c * G.dt # Number of cells in the transmission line (initially a long line to # calculate incident voltage and current); consider putting ABCs/PML at end self.nl = round_value(0.667 * G.iterations) # Cell position of the one-way injector excitation in the transmission line self.srcpos = 5 # Cell position of where line connects to antenna/main grid self.antpos = 10 # Voltage values along the line self.voltage = np.zeros(self.nl, dtype=floattype) # Current values along the line self.current = np.zeros(self.nl, dtype=floattype) # Total (incident and scattered) voltage and current self.Vtotal = np.zeros(G.iterations, dtype=floattype) self.Itotal = np.zeros(G.iterations, dtype=floattype)
def process_singlecmds(singlecmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: singlecmds (dict): Commands that can only occur once in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Check validity of command parameters in order needed # messages cmd = '#messages' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].lower() == 'y': G.messages = True elif singlecmds[cmd].lower() == 'n': G.messages = False else: raise CmdInputError(cmd + ' requires input values of either y or n') # Title cmd = '#title' if singlecmds[cmd] is not None: G.title = singlecmds[cmd] if G.messages: print('Model title: {}'.format(G.title)) # Get information about host machine hostinfo = get_host_info() # Number of threads (OpenMP) to use cmd = '#num_threads' if sys.platform == 'darwin': os.environ['OMP_WAIT_POLICY'] = 'ACTIVE' # Should waiting threads consume CPU power (can drastically effect performance) os.environ['OMP_DYNAMIC'] = 'FALSE' # Number of threads may be adjusted by the run time environment to best utilize system resources os.environ['OMP_PLACES'] = 'cores' # Each place corresponds to a single core (having one or more hardware threads) os.environ['OMP_PROC_BIND'] = 'TRUE' # Bind threads to physical cores # os.environ['OMP_DISPLAY_ENV'] = 'TRUE' # Prints OMP version and environment variables (useful for debug) # Catch bug with Windows Subsystem for Linux (https://github.com/Microsoft/BashOnWindows/issues/785) if 'Microsoft' in G.hostinfo['osversion']: os.environ['KMP_AFFINITY'] = 'disabled' del os.environ['OMP_PLACES'] del os.environ['OMP_PROC_BIND'] if singlecmds[cmd] is not None: tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter to specify the number of threads to use') if tmp[0] < 1: raise CmdInputError(cmd + ' requires the value to be an integer not less than one') G.nthreads = tmp[0] os.environ['OMP_NUM_THREADS'] = str(G.nthreads) elif os.environ.get('OMP_NUM_THREADS'): G.nthreads = int(os.environ.get('OMP_NUM_THREADS')) else: # Set number of threads to number of physical CPU cores G.nthreads = hostinfo['physicalcores'] os.environ['OMP_NUM_THREADS'] = str(G.nthreads) if G.messages: print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads)) if G.nthreads > G.hostinfo['physicalcores']: print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL) # Print information about any GPU in use if G.messages: if G.gpu is not None: print('GPU solving using: {} - {}'.format(G.gpu.deviceID, G.gpu.name)) # Spatial discretisation cmd = '#dx_dy_dz' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') if tmp[0] <= 0: raise CmdInputError(cmd + ' requires the x-direction spatial step to be greater than zero') if tmp[1] <= 0: raise CmdInputError(cmd + ' requires the y-direction spatial step to be greater than zero') if tmp[2] <= 0: raise CmdInputError(cmd + ' requires the z-direction spatial step to be greater than zero') G.dx = tmp[0] G.dy = tmp[1] G.dz = tmp[2] if G.messages: print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(G.dx, G.dy, G.dz)) # Domain cmd = '#domain' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.nx = round_value(tmp[0] / G.dx) G.ny = round_value(tmp[1] / G.dy) G.nz = round_value(tmp[2] / G.dz) if G.nx == 0 or G.ny == 0 or G.nz == 0: raise CmdInputError(cmd + ' requires at least one cell in every dimension') if G.messages: print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) # Time step CFL limit (either 2D or 3D); switch off appropriate PMLs for 2D if G.nx == 1: G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.mode = '2D TMx' G.pmlthickness['x0'] = 0 G.pmlthickness['xmax'] = 0 elif G.ny == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) G.mode = '2D TMy' G.pmlthickness['y0'] = 0 G.pmlthickness['ymax'] = 0 elif G.nz == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) G.mode = '2D TMz' G.pmlthickness['z0'] = 0 G.pmlthickness['zmax'] = 0 else: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.mode = '3D' # Round down time step to nearest float with precision one less than hardware maximum. # Avoids inadvertently exceeding the CFL due to binary representation of floating point number. G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1) if G.messages: print('Mode: {}'.format(G.mode)) print('Time step (at CFL limit): {:g} secs'.format(G.dt)) # Time step stability factor cmd = '#time_step_stability_factor' if singlecmds[cmd] is not None: tmp = tuple(float(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if tmp[0] <= 0 or tmp[0] > 1: raise CmdInputError(cmd + ' requires the value of the time step stability factor to be between zero and one') G.dt = G.dt * tmp[0] if G.messages: print('Time step (modified): {:g} secs'.format(G.dt)) # Time window cmd = '#time_window' tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter to specify the time window. Either in seconds or number of iterations.') tmp = tmp[0].lower() # If number of iterations given # The +/- 1 used in calculating the number of iterations is to account for # the fact that the solver (iterations) loop runs from 0 to < G.iterations try: tmp = int(tmp) G.timewindow = (tmp - 1) * G.dt G.iterations = tmp # If real floating point value given except ValueError: tmp = float(tmp) if tmp > 0: G.timewindow = tmp G.iterations = int(np.ceil(tmp / G.dt)) + 1 else: raise CmdInputError(cmd + ' must have a value greater than zero') if G.messages: print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations)) # PML cells cmd = '#pml_cells' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 6: raise CmdInputError(cmd + ' requires either one or six parameter(s)') if len(tmp) == 1: for key in G.pmlthickness.keys(): G.pmlthickness[key] = int(tmp[0]) else: G.pmlthickness['x0'] = int(tmp[0]) G.pmlthickness['y0'] = int(tmp[1]) G.pmlthickness['z0'] = int(tmp[2]) G.pmlthickness['xmax'] = int(tmp[3]) G.pmlthickness['ymax'] = int(tmp[4]) G.pmlthickness['zmax'] = int(tmp[5]) if 2 * G.pmlthickness['x0'] >= G.nx or 2 * G.pmlthickness['y0'] >= G.ny or 2 * G.pmlthickness['z0'] >= G.nz or 2 * G.pmlthickness['xmax'] >= G.nx or 2 * G.pmlthickness['ymax'] >= G.ny or 2 * G.pmlthickness['zmax'] >= G.nz: raise CmdInputError(cmd + ' has too many cells for the domain size') # PML formulation cmd = '#pml_formulation' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].upper() in PML.formulations: G.pmlformulation = singlecmds[cmd].upper() else: raise CmdInputError(cmd + ' PML formulation is not found') # src_steps cmd = '#src_steps' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.srcsteps[0] = round_value(float(tmp[0]) / G.dx) G.srcsteps[1] = round_value(float(tmp[1]) / G.dy) G.srcsteps[2] = round_value(float(tmp[2]) / G.dz) if G.messages: print('Simple sources will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.srcsteps[0] * G.dx, G.srcsteps[1] * G.dy, G.srcsteps[2] * G.dz)) # rx_steps cmd = '#rx_steps' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.rxsteps[0] = round_value(float(tmp[0]) / G.dx) G.rxsteps[1] = round_value(float(tmp[1]) / G.dy) G.rxsteps[2] = round_value(float(tmp[2]) / G.dz) if G.messages: print('All receivers will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.rxsteps[0] * G.dx, G.rxsteps[1] * G.dy, G.rxsteps[2] * G.dz)) # Excitation file for user-defined source waveforms cmd = '#excitation_file' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 3: raise CmdInputError(cmd + ' requires either one or three parameter(s)') excitationfile = tmp[0] # Optional parameters passed directly to scipy.interpolate.interp1d kwargs = dict() if len(tmp) > 1: kwargs['kind'] = tmp[1] kwargs['fill_value'] = tmp[2] else: args, varargs, keywords, defaults = inspect.getargspec(interpolate.interp1d) kwargs = dict(zip(reversed(args), reversed(defaults))) # See if file exists at specified path and if not try input file directory if not os.path.isfile(excitationfile): excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile)) if G.messages: print('\nExcitation file: {}'.format(excitationfile)) # Get waveform names with open(excitationfile, 'r') as f: waveformIDs = f.readline().split() # Read all waveform values into an array waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype) # Time array (if specified) for interpolation, otherwise use simulation time if waveformIDs[0].lower() == 'time': waveformIDs = waveformIDs[1:] waveformtime = waveformvalues[:, 0] waveformvalues = waveformvalues[:, 1:] timestr = 'user-defined time array' else: waveformtime = np.arange(0, G.timewindow + G.dt, G.dt) timestr = 'simulation time array' for waveform in range(len(waveformIDs)): if any(x.ID == waveformIDs[waveform] for x in G.waveforms): raise CmdInputError('Waveform with ID {} already exists'.format(waveformIDs[waveform])) w = Waveform() w.ID = waveformIDs[waveform] w.type = 'user' # Select correct column of waveform values depending on array shape singlewaveformvalues = waveformvalues[:] if len(waveformvalues.shape) == 1 else waveformvalues[:, waveform] # Truncate waveform array if it is longer than time array if len(singlewaveformvalues) > len(waveformtime): singlewaveformvalues = singlewaveformvalues[:len(waveformtime)] # Zero-pad end of waveform array if it is shorter than time array elif len(singlewaveformvalues) < len(waveformtime): tmp = np.zeros(len(waveformtime)) tmp[:len(singlewaveformvalues)] = singlewaveformvalues singlewaveformvalues = tmp # Interpolate waveform values w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs) if G.messages: print('User waveform {} created using {} and, if required, interpolation parameters (kind: {}, fill value: {}).'.format(w.ID, timestr, kwargs['kind'], kwargs['fill_value'])) G.waveforms.append(w) # Set the output directory cmd = '#output_dir' if singlecmds[cmd] is not None: outputdir = singlecmds[cmd] G.outputdirectory = outputdir
def write_vtk(self, modelrun, numbermodelruns, G, pbar): """Writes the geometry information to a VTK file. Either ImageData (.vti) for a per-cell geometry view, or PolygonalData (.vtp) for a per-cell-edge geometry view. N.B. No Python 3 support for VTK at time of writing (03/2015) Args: modelrun (int): Current model run number. numbermodelruns (int): Total number of model runs. G (class): Grid class instance - holds essential parameters describing the model. pbar (class): Progress bar class instance. """ if self.fileext == '.vti': # Create arrays and add numeric IDs for PML, sources and receivers (0 is not set, 1 is PML, srcs and rxs numbered thereafter) self.srcs_pml = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) self.rxs = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) for pml in G.pmls: self.srcs_pml[pml.xs:pml.xf, pml.ys:pml.yf, pml.zs:pml.zf] = 1 for index, src in enumerate(G.hertziandipoles + G.magneticdipoles + G.voltagesources + G.transmissionlines): self.srcs_pml[src.xcoord, src.ycoord, src.zcoord] = index + 2 for index, rx in enumerate(G.rxs): self.rxs[rx.xcoord, rx.ycoord, rx.zcoord] = index + 1 vtk_srcs_pml_offset = round_value( (np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize) vtk_rxs_offset = round_value( (np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize + (np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize) with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write( '<VTKFile type="ImageData" version="1.0" byte_order="{}">\n' .format(GeometryView.byteorder).encode('utf-8')) f.write( '<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n' .format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) f.write('<Piece Extent="{} {} {} {} {} {}">\n'.format( self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write( '<DataArray type="UInt32" Name="Material" format="appended" offset="0" />\n' .encode('utf-8')) f.write( '<DataArray type="Int8" Name="Sources_PML" format="appended" offset="{}" />\n' .format(vtk_srcs_pml_offset).encode('utf-8')) f.write( '<DataArray type="Int8" Name="Receivers" format="appended" offset="{}" />\n' .format(vtk_rxs_offset).encode('utf-8')) f.write('</CellData>\n'.encode('utf-8')) f.write( '</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_'. encode('utf-8')) # Write material IDs datasize = int( np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) # Write number of bytes of appended data as UInt32 f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): pbar.update(n=4) f.write(pack('I', G.solid[i, j, k])) # Write source/PML IDs datasize = int( np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): pbar.update() f.write(pack('b', self.srcs_pml[i, j, k])) # Write receiver IDs datasize = int( np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): pbar.update() f.write(pack('b', self.rxs[i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.write_gprmax_info(f, G) elif self.fileext == '.vtp': with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write( '<VTKFile type="PolyData" version="1.0" byte_order="{}">\n' .format(GeometryView.byteorder).encode('utf-8')) f.write( '<PolyData>\n<Piece NumberOfPoints="{}" NumberOfVerts="0" NumberOfLines="{}" NumberOfStrips="0" NumberOfPolys="0">\n' .format(self.vtk_numpoints, self.vtk_numlines).encode('utf-8')) f.write( '<Points>\n<DataArray type="Float32" NumberOfComponents="3" format="appended" offset="0" />\n</Points>\n' .encode('utf-8')) f.write( '<Lines>\n<DataArray type="UInt32" Name="connectivity" format="appended" offset="{}" />\n' .format(self.vtk_connectivity_offset).encode('utf-8')) f.write( '<DataArray type="UInt32" Name="offsets" format="appended" offset="{}" />\n</Lines>\n' .format(self.vtk_offsets_offset).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write( '<DataArray type="UInt32" Name="Material" format="appended" offset="{}" />\n' .format(self.vtk_materials_offset).encode('utf-8')) f.write('</CellData>\n'.encode('utf-8')) f.write( '</Piece>\n</PolyData>\n<AppendedData encoding="raw">\n_'. encode('utf-8')) # Write points datasize = np.dtype( np.float32 ).itemsize * self.vtk_numpoints * self.vtk_numpoint_components f.write(pack('I', datasize)) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): pbar.update(n=12) f.write(pack('fff', i * G.dx, j * G.dy, k * G.dz)) # Write cell type (line) connectivity for x components datasize = np.dtype( np.uint32 ).itemsize * self.vtk_numlines * self.vtk_numline_components f.write(pack('I', datasize)) vtk_x2 = (self.ny + 1) * (self.nz + 1) for vtk_x1 in range(self.nx * (self.ny + 1) * (self.nz + 1)): pbar.update(n=8) f.write(pack('II', vtk_x1, vtk_x2)) # print('x {} {}'.format(vtk_x1, vtk_x2)) vtk_x2 += 1 # Write cell type (line) connectivity for y components vtk_ycnt1 = 1 vtk_ycnt2 = 0 for vtk_y1 in range( (self.nx + 1) * (self.ny + 1) * (self.nz + 1)): if vtk_y1 >= (vtk_ycnt1 * (self.ny + 1) * (self.nz + 1)) - ( self.nz + 1) and vtk_y1 < vtk_ycnt1 * ( self.ny + 1) * (self.nz + 1): vtk_ycnt2 += 1 else: vtk_y2 = vtk_y1 + self.nz + 1 pbar.update(n=8) f.write(pack('II', vtk_y1, vtk_y2)) # print('y {} {}'.format(vtk_y1, vtk_y2)) if vtk_ycnt2 == self.nz + 1: vtk_ycnt1 += 1 vtk_ycnt2 = 0 # Write cell type (line) connectivity for z components vtk_zcnt = self.nz for vtk_z1 in range((self.nx + 1) * (self.ny + 1) * self.nz + (self.nx + 1) * (self.ny + 1)): if vtk_z1 != vtk_zcnt: vtk_z2 = vtk_z1 + 1 pbar.update(n=8) f.write(pack('II', vtk_z1, vtk_z2)) # print('z {} {}'.format(vtk_z1, vtk_z2)) else: vtk_zcnt += self.nz + 1 # Write cell type (line) offsets vtk_cell_pts = 2 datasize = np.dtype(np.uint32).itemsize * self.vtk_numlines f.write(pack('I', datasize)) for vtk_offsets in range( vtk_cell_pts, (self.vtk_numline_components * self.vtk_numlines) + vtk_cell_pts, vtk_cell_pts): pbar.update(n=4) f.write(pack('I', vtk_offsets)) # Write material IDs per-cell-edge, i.e. from ID array datasize = np.dtype(np.uint32).itemsize * self.vtk_numlines f.write(pack('I', datasize)) for i in range(self.xs, self.xf): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): pbar.update(n=4) f.write(pack('I', G.ID[0, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf): for k in range(self.zs, self.zf + 1): pbar.update(n=4) f.write(pack('I', G.ID[1, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf): pbar.update(n=4) f.write(pack('I', G.ID[2, i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.write_gprmax_info(f, G, materialsonly=True)
def calculate_value(self, time, dt): """Calculates value of the waveform at a specific time. Args: time (float): Absolute time. dt (float): Absolute time discretisation. Returns: waveform (float): Calculated value for waveform. """ # Coefficients for certain waveforms if self.type == 'gaussian' or self.type == 'gaussiandot' or self.type == 'gaussiandotdot': chi = 1 / self.freq zeta = 2 * np.pi * np.pi * self.freq * self.freq delay = time - chi elif self.type == 'gaussiandotnorm' or self.type == 'gaussiandotdotnorm' or self.type == 'ricker': chi = np.sqrt(2) / self.freq zeta = np.pi * np.pi * self.freq * self.freq delay = time - chi # Waveforms if self.type == 'gaussian': waveform = np.exp(-zeta * delay * delay) elif self.type == 'gaussiandot': waveform = -2 * zeta * delay * np.exp(-zeta * delay * delay) elif self.type == 'gaussiandotnorm': normalise = np.sqrt(np.exp(1) / (2 * zeta)) waveform = -2 * zeta * delay * np.exp(-zeta * delay * delay) * normalise elif self.type == 'gaussiandotdot': waveform = 2 * zeta * (2 * zeta * delay * delay - 1) * np.exp(-zeta * delay * delay) elif self.type == 'gaussiandotdotnorm': normalise = 1 / (2 * zeta) waveform = 2 * zeta * (2 * zeta * delay * delay - 1) * np.exp(-zeta * delay * delay) * normalise elif self.type == 'ricker': normalise = 1 / (2 * zeta) waveform = - (2 * zeta * (2 * zeta * delay * delay - 1) * np.exp(-zeta * delay * delay)) * normalise elif self.type == 'sine': waveform = np.sin(2 * np.pi * self.freq * time) if time * self.freq > 1: waveform = 0 elif self.type == 'contsine': rampamp = 0.25 ramp = rampamp * time * self.freq if ramp > 1: ramp = 1 waveform = ramp * np.sin(2 * np.pi * self.freq * time) elif self.type == 'impulse': # time < G.dt condition required to do impulsive magnetic dipole if time == 0 or time < dt: waveform = 1 elif time >= dt: waveform = 0 elif self.type == 'user': index = round_value(time / dt) # Check to see if there are still user specified values and if not use zero if index > len(self.uservalues) - 1: waveform = 0 else: waveform = self.uservalues[index] return waveform
if args.type.lower() not in Waveform.waveformtypes: raise CmdInputError('The waveform must have one of the following types {}'.format(', '.join(Waveform.waveformtypes))) if args.freq <= 0: raise CmdInputError('The waveform requires an excitation frequency value of greater than zero') w = Waveform() w.type = args.type w.amp = args.amp w.freq = args.freq dt = args.dt # Check time window if '.' in args.timewindow or 'e' in args.timewindow: if float(args.timewindow) > 0: timewindow = float(args.timewindow) iterations = round_value((float(args.timewindow) / dt)) + 1 else: raise CmdInputError('Time window must have a value greater than zero') # If number of iterations given else: timewindow = (int(args.timewindow) - 1) * dt iterations = int(args.timewindow) time = np.linspace(0, 1, iterations) time *= (iterations * dt) waveform = np.zeros(len(time)) timeiter = np.nditer(time, flags=['c_index']) while not timeiter.finished: waveform[timeiter.index] = w.calculate_value(timeiter[0], dt) timeiter.iternext()
def write_file(self, modelrun, numbermodelruns, G): """Writes the geometry information to a VTK file. Either ImageData (.vti) for a per cell geometry view, or PolygonalData (.vtp) for a per cell edge geometry view. Args: modelrun (int): Current model run number. numbermodelruns (int): Total number of model runs. G (class): Grid class instance - holds essential parameters describing the model. """ # Construct filename from user-supplied name and model run number if numbermodelruns == 1: self.filename = G.inputdirectory + self.filename else: self.filename = G.inputdirectory + self.filename + str(modelrun) # No Python 3 support for VTK at time of writing (03/2015) self.vtk_nx = self.xf - self.xs self.vtk_ny = self.yf - self.ys self.vtk_nz = self.zf - self.zs if self.type == 'n': self.filename += '.vti' # Calculate number of cells according to requested sampling self.vtk_xscells = round_value(self.xs / self.dx) self.vtk_xfcells = round_value(self.xf / self.dx) self.vtk_yscells = round_value(self.ys / self.dy) self.vtk_yfcells = round_value(self.yf / self.dy) self.vtk_zscells = round_value(self.zs / self.dz) self.vtk_zfcells = round_value(self.zf / self.dz) with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write('<VTKFile type="ImageData" version="1.0" byte_order="{}">\n'.format(GeometryView.byteorder).encode('utf-8')) f.write('<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) f.write('<Piece Extent="{} {} {} {} {} {}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write('<DataArray type="UInt32" Name="Material" format="appended" offset="0" />\n'.encode('utf-8')) f.write('</CellData>\n</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_'.encode('utf-8')) # Calculate number of bytes of appended data section datasize = int(np.dtype(np.uint32).itemsize * (self.vtk_nx / self.dx) * (self.vtk_ny / self.dy) * (self.vtk_nz / self.dz)) # Write number of bytes of appended data as UInt32 f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): f.write(pack('I', G.solid[i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) # Write gprMax specific information which relates material name to material numeric identifier f.write('\n\n<gprMax>\n'.encode('utf-8')) for material in G.materials: f.write('<Material name="{}">{}</Material>\n'.format(material.ID, material.numID).encode('utf-8')) f.write('</gprMax>\n'.encode('utf-8')) elif self.type == 'f': self.filename += '.vtp' vtk_numpoints = (self.vtk_nx + 1) * (self.vtk_ny + 1) * (self.vtk_nz + 1) vtk_numpoint_components = 3 vtk_numlines = 2 * self.vtk_nx * self.vtk_ny + 2 * self.vtk_ny * self.vtk_nz + 2 * self.vtk_nx * self.vtk_nz + 3 * self.vtk_nx * self.vtk_ny * self.vtk_nz + self.vtk_nx + self.vtk_ny + self.vtk_nz vtk_numline_components = 2; vtk_connectivity_offset = (vtk_numpoints * vtk_numpoint_components * np.dtype(np.float32).itemsize) + np.dtype(np.uint32).itemsize vtk_offsets_offset = vtk_connectivity_offset + (vtk_numlines * vtk_numline_components * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize vtk_id_offset = vtk_offsets_offset + (vtk_numlines * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize vtk_offsets_size = vtk_numlines with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write('<VTKFile type="PolyData" version="1.0" byte_order="{}">\n'.format(GeometryView.byteorder).encode('utf-8')) f.write('<PolyData>\n<Piece NumberOfPoints="{}" NumberOfVerts="0" NumberOfLines="{}" NumberOfStrips="0" NumberOfPolys="0">\n'.format(vtk_numpoints, vtk_numlines).encode('utf-8')) f.write('<Points>\n<DataArray type="Float32" NumberOfComponents="3" format="appended" offset="0" />\n</Points>\n'.encode('utf-8')) f.write('<Lines>\n<DataArray type="UInt32" Name="connectivity" format="appended" offset="{}" />\n'.format(vtk_connectivity_offset).encode('utf-8')) f.write('<DataArray type="UInt32" Name="offsets" format="appended" offset="{}" />\n</Lines>\n'.format(vtk_offsets_offset).encode('utf-8')) f.write('<CellData Scalars="Material">\n<DataArray type="UInt32" Name="Material" format="appended" offset="{}" />\n</CellData>\n'.format(vtk_id_offset).encode('utf-8')) f.write('</Piece>\n</PolyData>\n<AppendedData encoding="raw">\n_'.encode('utf-8')) # Write points datasize = np.dtype(np.float32).itemsize * vtk_numpoints * vtk_numpoint_components f.write(pack('I', datasize)) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): f.write(pack('fff', i * G.dx, j * G.dy, k * G.dz)) # Write cell type (line) connectivity for x components datasize = np.dtype(np.uint32).itemsize * vtk_numlines * vtk_numline_components f.write(pack('I', datasize)) vtk_x2 = (self.vtk_ny + 1) * (self.vtk_nz + 1) for vtk_x1 in range(self.vtk_nx * (self.vtk_ny + 1) * (self.vtk_nz + 1)): f.write(pack('II', vtk_x1, vtk_x2)) # print('x {} {}'.format(vtk_x1, vtk_x2)) vtk_x2 += 1 # Write cell type (line) connectivity for y components vtk_ycnt1 = 1 vtk_ycnt2 = 0 for vtk_y1 in range((self.vtk_nx + 1) * (self.vtk_ny + 1) * (self.vtk_nz + 1)): if vtk_y1 >= (vtk_ycnt1 * (self.vtk_ny + 1) * (self.vtk_nz + 1)) - (self.vtk_nz + 1) and vtk_y1 < vtk_ycnt1 * (self.vtk_ny + 1) * (self.vtk_nz + 1): vtk_ycnt2 += 1 else: vtk_y2 = vtk_y1 + self.vtk_nz + 1 f.write(pack('II', vtk_y1, vtk_y2)) # print('y {} {}'.format(vtk_y1, vtk_y2)) if vtk_ycnt2 == self.vtk_nz + 1: vtk_ycnt1 += 1 vtk_ycnt2 = 0 # Write cell type (line) connectivity for z components vtk_zcnt = self.vtk_nz for vtk_z1 in range((self.vtk_nx + 1) * (self.vtk_ny + 1) * self.vtk_nz + (self.vtk_nx + 1) * (self.vtk_ny + 1)): if vtk_z1 != vtk_zcnt: vtk_z2 = vtk_z1 + 1 f.write(pack('II', vtk_z1, vtk_z2)) # print('z {} {}'.format(vtk_z1, vtk_z2)) else: vtk_zcnt += self.vtk_nz + 1 # Write cell type (line) offsets vtk_cell_pts = 2 datasize = np.dtype(np.uint32).itemsize * vtk_offsets_size f.write(pack('I', datasize)) for vtk_offsets in range(vtk_cell_pts, (vtk_numline_components * vtk_numlines) + vtk_cell_pts, vtk_cell_pts): f.write(pack('I', vtk_offsets)) # Write Ex, Ey, Ez values from ID array datasize = np.dtype(np.uint32).itemsize * vtk_numlines f.write(pack('I', datasize)) for i in range(self.xs, self.xf): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): f.write(pack('I', G.ID[0, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf): for k in range(self.zs, self.zf + 1): f.write(pack('I', G.ID[1, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf): f.write(pack('I', G.ID[2, i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) # Write gprMax specific information which relates material name to material numeric identifier f.write('\n\n<gprMax>\n'.encode('utf-8')) for material in G.materials: f.write('<Material name="{}">{}</Material>\n'.format(material.ID, material.numID).encode('utf-8')) f.write('</gprMax>\n'.encode('utf-8'))
def process_singlecmds(singlecmds, multicmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: singlecmds (dict): Commands that can only occur once in the model. multicmds (dict): Commands that can have multiple instances in the model (required to pass to process_materials_file function). G (class): Grid class instance - holds essential parameters describing the model. """ # Check validity of command parameters in order needed # messages cmd = "#messages" if singlecmds[cmd] != "None": tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + " requires exactly one parameter") if singlecmds[cmd].lower() == "y": G.messages = True elif singlecmds[cmd].lower() == "n": G.messages = False else: raise CmdInputError(cmd + " requires input values of either y or n") # Title cmd = "#title" if singlecmds[cmd] != "None": G.title = singlecmds[cmd] if G.messages: print("Model title: {}".format(G.title)) # Number of processors to run on (OpenMP) cmd = "#num_threads" ompthreads = os.environ.get("OMP_NUM_THREADS") if singlecmds[cmd] != "None": tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + " requires exactly one parameter to specify the number of threads to use") if tmp[0] < 1: raise CmdInputError(cmd + " requires the value to be an integer not less than one") G.nthreads = tmp[0] elif ompthreads: G.nthreads = int(ompthreads) else: # Set number of threads to number of physical CPU cores, i.e. avoid hyperthreading with OpenMP G.nthreads = psutil.cpu_count(logical=False) if G.messages: print("Number of threads: {}".format(G.nthreads)) # Spatial discretisation cmd = "#dx_dy_dz" tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + " requires exactly three parameters") if tmp[0] <= 0: raise CmdInputError(cmd + " requires the x-direction spatial step to be greater than zero") if tmp[1] <= 0: raise CmdInputError(cmd + " requires the y-direction spatial step to be greater than zero") if tmp[2] <= 0: raise CmdInputError(cmd + " requires the z-direction spatial step to be greater than zero") G.dx = tmp[0] G.dy = tmp[1] G.dz = tmp[2] if G.messages: print("Spatial discretisation: {:g} x {:g} x {:g}m".format(G.dx, G.dy, G.dz)) # Domain cmd = "#domain" tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + " requires exactly three parameters") G.nx = round_value(tmp[0] / G.dx) G.ny = round_value(tmp[1] / G.dy) G.nz = round_value(tmp[2] / G.dz) if G.messages: print( "Model domain: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)".format( tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz) ) ) mem = ( ( (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * 13 * np.dtype(floattype).itemsize + (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * 18 ) * 1.1 ) + 30e6 print( "Memory (RAM) usage: ~{} required, {} available".format( human_size(mem), human_size(psutil.virtual_memory().total) ) ) # Time step CFL limit - use either 2D or 3D (default) cmd = "#time_step_limit_type" if singlecmds[cmd] != "None": tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + " requires exactly one parameter") if singlecmds[cmd].lower() == "2d": if G.nx == 1: G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) elif G.ny == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) elif G.nz == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) else: raise CmdInputError(cmd + " 2D CFL limit can only be used when one dimension of the domain is one cell") elif singlecmds[cmd].lower() == "3d": G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) else: raise CmdInputError(cmd + " requires input values of either 2D or 3D") else: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number. G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1) if G.messages: print("Time step: {:g} secs".format(G.dt)) # Time step stability factor cmd = "#time_step_stability_factor" if singlecmds[cmd] != "None": tmp = tuple(float(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + " requires exactly one parameter") if tmp[0] <= 0 or tmp[0] > 1: raise CmdInputError( cmd + " requires the value of the time step stability factor to be between zero and one" ) G.dt = G.dt * tmp[0] if G.messages: print("Time step (modified): {:g} secs".format(G.dt)) # Time window cmd = "#time_window" tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError( cmd + " requires exactly one parameter to specify the time window. Either in seconds or number of iterations." ) tmp = tmp[0].lower() # If real floating point value given if "." in tmp or "e" in tmp: if float(tmp) > 0: G.timewindow = float(tmp) G.iterations = round_value((float(tmp) / G.dt)) + 1 else: raise CmdInputError(cmd + " must have a value greater than zero") # If number of iterations given else: G.timewindow = (int(tmp) - 1) * G.dt G.iterations = int(tmp) if G.messages: print("Time window: {:g} secs ({} iterations)".format(G.timewindow, G.iterations)) # PML cmd = "#pml_cells" if singlecmds[cmd] != "None": tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 6: raise CmdInputError(cmd + " requires either one or six parameters") if len(tmp) == 1: G.pmlthickness = (int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0])) else: G.pmlthickness = (int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3]), int(tmp[4]), int(tmp[5])) if ( 2 * G.pmlthickness[0] >= G.nx or 2 * G.pmlthickness[1] >= G.ny or 2 * G.pmlthickness[2] >= G.nz or 2 * G.pmlthickness[3] >= G.nx or 2 * G.pmlthickness[4] >= G.ny or 2 * G.pmlthickness[5] >= G.nz ): raise CmdInputError(cmd + " has too many cells for the domain size") # src_steps cmd = "#src_steps" if singlecmds[cmd] != "None": tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + " requires exactly three parameters") G.srcstepx = round_value(float(tmp[0]) / G.dx) G.srcstepy = round_value(float(tmp[1]) / G.dy) G.srcstepz = round_value(float(tmp[2]) / G.dz) if G.messages: print( "All sources will step {:g}m, {:g}m, {:g}m for each model run.".format( G.srcstepx * G.dx, G.srcstepy * G.dy, G.srcstepz * G.dz ) ) # rx_steps cmd = "#rx_steps" if singlecmds[cmd] != "None": tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + " requires exactly three parameters") G.rxstepx = round_value(float(tmp[0]) / G.dx) G.rxstepy = round_value(float(tmp[1]) / G.dy) G.rxstepz = round_value(float(tmp[2]) / G.dz) if G.messages: print( "All receivers will step {:g}m, {:g}m, {:g}m for each model run.".format( G.rxstepx * G.dx, G.rxstepy * G.dy, G.rxstepz * G.dz ) ) # Excitation file for user-defined source waveforms cmd = "#excitation_file" if singlecmds[cmd] != "None": tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + " requires exactly one parameter") excitationfile = tmp[0] # Open file and get waveform names with open(excitationfile, "r") as f: waveformIDs = f.readline().split() # Read all waveform values into an array waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype) for waveform in range(len(waveformIDs)): if any(x.ID == waveformIDs[waveform] for x in G.waveforms): raise CmdInputError("Waveform with ID {} already exists".format(waveformIDs[waveform])) w = Waveform() w.ID = waveformIDs[waveform] w.type = "user" if len(waveformvalues.shape) == 1: w.uservalues = waveformvalues[:] else: w.uservalues = waveformvalues[:, waveform] if G.messages: print("User waveform {} created.".format(w.ID)) G.waveforms.append(w)
def process_multicmds(multicmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: multicmds (dict): Commands that can have multiple instances in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Waveform definitions cmdname = '#waveform' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 4: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly four parameters') if tmp[0].lower() not in Waveform.types: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have one of the following types {}'.format(','.join(Waveform.types))) if float(tmp[2]) <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires an excitation frequency value of greater than zero') if any(x.ID == tmp[3] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[3])) w = Waveform() w.ID = tmp[3] w.type = tmp[0].lower() w.amp = float(tmp[1]) w.freq = float(tmp[2]) if G.messages: print('Waveform {} of type {} with amplitude {:g}, frequency {:g}Hz created.'.format(w.ID, w.type, w.amp, w.freq)) G.waveforms.append(w) # Voltage source cmdname = '#voltage_source' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) resistance = float(tmp[4]) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') if resistance < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a source resistance of zero or greater') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[5] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[5])) v = VoltageSource() v.polarisation= tmp[0] v.xcoord = xcoord v.ycoord = ycoord v.zcoord = zcoord v.resistance = resistance v.waveformID = tmp[5] if len(tmp) > 6: # Check source start & source remove time parameters start = float(tmp[6]) stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') v.start = start if stop > G.timewindow: v.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop) else: v.start = 0 v.stop = G.timewindow startstop = ' ' if G.messages: print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * G.dx, v.ycoord * G.dy, v.zcoord * G.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID)) G.voltagesources.append(v) # Hertzian dipole cmdname = '#hertzian_dipole' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[4] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) h = HertzianDipole() h.polarisation = tmp[0] h.xcoord = xcoord h.ycoord = ycoord h.zcoord = zcoord h.waveformID = tmp[4] if len(tmp) > 5: # Check source start & source remove time parameters start = float(tmp[5]) stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') h.start = start if stop > G.timewindow: h.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop) else: h.start = 0 h.stop = G.timewindow startstop = ' ' if G.messages: print('Hertzian dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * G.dx, h.ycoord * G.dy, h.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(h.waveformID)) G.hertziandipoles.append(h) # Magnetic dipole cmdname = '#magnetic_dipole' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[4] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) m = MagneticDipole() m.polarisation = tmp[0] m.xcoord = xcoord m.ycoord = ycoord m.zcoord = zcoord m.waveformID = tmp[4] if len(tmp) > 5: # Check source start & source remove time parameters start = float(tmp[5]) stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') m.start = start if stop > G.timewindow: m.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop) else: m.start = 0 m.stop = G.timewindow startstop = ' ' if G.messages: print('Magnetic dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(m.polarisation, m.xcoord * G.dx, m.ycoord * G.dy, m.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(m.waveformID)) G.magneticdipoles.append(m) # Transmission line cmdname = '#transmission_line' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) resistance = float(tmp[4]) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') if resistance <= 0 or resistance > z0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a resistance greater than zero and less than the impedance of free space, i.e. 376.73 Ohms') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[5] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) t = TransmissionLine(G) t.polarisation = tmp[0] t.xcoord = xcoord t.ycoord = ycoord t.zcoord = zcoord t.resistance = resistance t.waveformID = tmp[5] t.calculate_incident_V_I(G) if len(tmp) > 6: # Check source start & source remove time parameters start = float(tmp[6]) stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') t.start = start if stop > G.timewindow: t.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop) else: t.start = 0 t.stop = G.timewindow startstop = ' ' if G.messages: print('Transmission line with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(t.polarisation, t.xcoord * G.dx, t.ycoord * G.dy, t.zcoord * G.dz, t.resistance) + startstop + 'using waveform {} created.'.format(t.waveformID)) G.transmissionlines.append(t) # Receiver cmdname = '#rx' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 3 and len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' has an incorrect number of parameters') # Check position parameters xcoord = round_value(float(tmp[0])/G.dx) ycoord = round_value(float(tmp[1])/G.dy) zcoord = round_value(float(tmp[2])/G.dz) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') r = Rx(xcoord=xcoord, ycoord=ycoord, zcoord=zcoord) # If no ID or outputs are specified, use default i.e Ex, Ey, Ez, Hx, Hy, Hz, Ix, Iy, Iz if len(tmp) == 3: r.outputs = Rx.availableoutputs[0:9] else: r.ID = tmp[3] # Check and add field output names for field in tmp[4::]: if field in Rx.availableoutputs: r.outputs.append(field) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' contains an output type that is not available') if G.messages: print('Receiver at {:g}m, {:g}m, {:g}m with output(s) {} created.'.format(r.xcoord * G.dx, r.ycoord * G.dy, r.zcoord * G.dz, ', '.join(r.outputs))) G.rxs.append(r) # Receiver box cmdname = '#rx_box' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 9: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly nine parameters') xs = round_value(float(tmp[0])/G.dx) xf = round_value(float(tmp[3])/G.dx) ys = round_value(float(tmp[1])/G.dy) yf = round_value(float(tmp[4])/G.dy) zs = round_value(float(tmp[2])/G.dz) zf = round_value(float(tmp[5])/G.dz) dx = round_value(float(tmp[6])/G.dx) dy = round_value(float(tmp[7])/G.dy) dz = round_value(float(tmp[8])/G.dz) if xs < 0 or xs >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower x-coordinate {:g}m is not within the model domain'.format(xs)) if xf < 0 or xf >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper x-coordinate {:g}m is not within the model domain'.format(xf)) if ys < 0 or ys >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower y-coordinate {:g}m is not within the model domain'.format(ys)) if yf < 0 or yf >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper y-coordinate {:g}m is not within the model domain'.format(yf)) if zs < 0 or zs >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower z-coordinate {:g}m is not within the model domain'.format(zs)) if zf < 0 or zf >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper z-coordinate {:g}m is not within the model domain'.format(zf)) if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') for x in range(xs, xf, dx): for y in range(ys, yf, dy): for z in range(zs, zf, dz): r = Rx(xcoord=x, ycoord=y, zcoord=z) G.rxs.append(r) if G.messages: print('Receiver box {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with steps {:g}m, {:g}m, {:g} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dy * G.dy, dz * G.dz)) # Snapshot cmdname = '#snapshot' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') xs = round_value(float(tmp[0])/G.dx) xf = round_value(float(tmp[3])/G.dx) ys = round_value(float(tmp[1])/G.dy) yf = round_value(float(tmp[4])/G.dy) zs = round_value(float(tmp[2])/G.dz) zf = round_value(float(tmp[5])/G.dz) dx = round_value(float(tmp[6])/G.dx) dy = round_value(float(tmp[7])/G.dy) dz = round_value(float(tmp[8])/G.dz) # If real floating point value given if '.' in tmp[9] or 'e' in tmp[9]: if float(tmp[9]) > 0: time = round_value((float(tmp[9]) / G.dt)) + 1 else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time value must be greater than zero') # If number of iterations given else: time = int(tmp[9]) if xs < 0 or xs > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower x-coordinate {:g}m is not within the model domain'.format(xs * G.dx)) if xf < 0 or xf > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper x-coordinate {:g}m is not within the model domain'.format(xf * G.dx)) if ys < 0 or ys > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower y-coordinate {:g}m is not within the model domain'.format(ys * G.dy)) if yf < 0 or yf > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper y-coordinate {:g}m is not within the model domain'.format(yf * G.dy)) if zs < 0 or zs > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower z-coordinate {:g}m is not within the model domain'.format(zs * G.dz)) if zf < 0 or zf > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper z-coordinate {:g}m is not within the model domain'.format(zf * G.dz)) if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if time <= 0 or time > G.iterations: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time value is not valid') s = Snapshot(xs, ys, zs, xf, yf, zf, dx, dy, dz, time, tmp[10]) if G.messages: print('Snapshot from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, at {:g} secs with filename {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dx * G.dy, dx * G.dz, s.time * G.dt, s.filename)) G.snapshots.append(s) # Materials # Create built-in materials m = Material(0, 'pec', G) m.average = False G.materials.append(m) m = Material(1, 'free_space', G) m.average = True G.materials.append(m) cmdname = '#material' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly five parameters') if float(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for static (DC) permittivity') if float(tmp[1]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for conductivity') if float(tmp[2]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for permeability') if float(tmp[3]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for magnetic conductivity') if any(x.ID == tmp[4] for x in G.materials): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[4])) # Create a new instance of the Material class material (start index after pec & free_space) m = Material(len(G.materials), tmp[4], G) m.er = float(tmp[0]) m.se = float(tmp[1]) m.mr = float(tmp[2]) m.sm = float(tmp[3]) if G.messages: print('Material {} with epsr={:g}, sig={:g} S/m; mur={:g}, sig*={:g} S/m created.'.format(m.ID, m.er, m.se, m.mr, m.sm)) # Append the new material object to the materials list G.materials.append(m) cmdname = '#add_dispersion_debye' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 4: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least four parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(2 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'debye' material.poles = poles material.average = False for pole in range(1, 2 * poles, 2): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt: material.deltaer.append(float(tmp[pole])) material.tau.append(float(tmp[pole + 1])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the permittivity difference and relaxation times, and relaxation times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: print('Debye-type disperion added to {} with delta_epsr={}, and tau={} secs created.'.format(material.ID, ','.join('%4.2f' % deltaer for deltaer in material.deltaer), ','.join('%4.3e' % tau for tau in material.tau))) cmdname = '#add_dispersion_lorentz' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(3 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'lorentz' material.poles = poles material.average = False for pole in range(1, 3 * poles, 3): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt and float(tmp[pole + 2]) > G.dt: material.deltaer.append(float(tmp[pole])) material.tau.append(float(tmp[pole + 1])) material.alpha.append(float(tmp[pole + 2])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the permittivity difference and frequencies, and associated times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: print('Lorentz-type disperion added to {} with delta_epsr={}, omega={} secs, and gamma={} created.'.format(material.ID, ','.join('%4.2f' % deltaer for deltaer in material.deltaer), ','.join('%4.3e' % tau for tau in material.tau), ','.join('%4.3e' % alpha for alpha in material.alpha))) cmdname = '#add_dispersion_drude' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(3 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'drude' material.poles = poles material.average = False for pole in range(1, 2 * poles, 2): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt: material.tau.append(float(tmp[pole ])) material.alpha.append(float(tmp[pole + 1])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: print('Drude-type disperion added to {} with omega={} secs, and gamma={} secs created.'.format(material.ID, ','.join('%4.3e' % tau for tau in material.tau), ','.join('%4.3e' % alpha for alpha in material.alpha))) cmdname = '#soil_peplinski' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 7: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at exactly seven parameters') if float(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the sand fraction') if float(tmp[1]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the clay fraction') if float(tmp[2]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the bulk density') if float(tmp[3]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the sand particle density') if float(tmp[4]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the lower limit of the water volumetric fraction') if float(tmp[5]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the upper limit of the water volumetric fraction') if any(x.ID == tmp[6] for x in G.mixingmodels): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[6])) # Create a new instance of the Material class material (start index after pec & free_space) s = PeplinskiSoil(tmp[6], float(tmp[0]), float(tmp[1]), float(tmp[2]), float(tmp[3]), (float(tmp[4]), float(tmp[5]))) if G.messages: print('Mixing model (Peplinski) used to create {} with sand fraction {:g}, clay fraction {:g}, bulk density {:g}g/cm3, sand particle density {:g}g/cm3, and water volumetric fraction {:g} to {:g} created.'.format(s.ID, s.S, s.C, s.rb, s.rs, s.mu[0], s.mu[1])) # Append the new material object to the materials list G.mixingmodels.append(s) # Geometry views (creates VTK-based geometry files) cmdname = '#geometry_view' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') xs = round_value(float(tmp[0])/G.dx) xf = round_value(float(tmp[3])/G.dx) ys = round_value(float(tmp[1])/G.dy) yf = round_value(float(tmp[4])/G.dy) zs = round_value(float(tmp[2])/G.dz) zf = round_value(float(tmp[5])/G.dz) dx = round_value(float(tmp[6])/G.dx) dy = round_value(float(tmp[7])/G.dy) dz = round_value(float(tmp[8])/G.dz) if xs < 0 or xs > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower x-coordinate {:g}m is not within the model domain'.format(xs * G.dx)) if xf < 0 or xf > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper x-coordinate {:g}m is not within the model domain'.format(xf * G.dx)) if ys < 0 or ys > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower y-coordinate {:g}m is not within the model domain'.format(ys * G.dy)) if yf < 0 or yf > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper y-coordinate {:g}m is not within the model domain'.format(yf * G.dy)) if zs < 0 or zs > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower z-coordinate {:g}m is not within the model domain'.format(zs * G.dz)) if zf < 0 or zf > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper z-coordinate {:g}m is not within the model domain'.format(zf * G.dz)) if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx > G.nx or dy > G.ny or dz > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should be less than the domain size') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if tmp[10].lower() != 'n' and tmp[10].lower() != 'f': raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires type to be either n (normal) or f (fine)') g = GeometryView(xs, ys, zs, xf, yf, zf, dx, dy, dz, tmp[9], tmp[10].lower()) if G.messages: print('Geometry view from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, filename {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dy * G.dy, dz * G.dz, g.filename)) # Append the new GeometryView object to the geometry views list G.geometryviews.append(g) # Complex frequency shifted (CFS) PML parameter cmdname = '#pml_cfs' if multicmds[cmdname] != 'None': if len(multicmds[cmdname]) > 2: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' can only be used up to two times, for up to a 2nd order PML') for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 12: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly twelve parameters') if tmp[0] not in CFSParameter.scalingprofiles.keys() or tmp[4] not in CFSParameter.scalingprofiles.keys() or tmp[8] not in CFSParameter.scalingprofiles.keys(): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys()))) if tmp[1] not in CFSParameter.scalingdirections or tmp[5] not in CFSParameter.scalingdirections or tmp[9] not in CFSParameter.scalingdirections: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys()))) if float(tmp[2]) < 0 or float(tmp[3]) < 0 or float(tmp[6]) < 0 or float(tmp[7]) < 0 or float(tmp[10]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' minimum and maximum scaling values must be greater than zero') if float(tmp[6]) < 1: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' minimum scaling value for kappa must be greater than zero') cfs = CFS() cfsalpha = CFSParameter() cfsalpha.ID = 'alpha' cfsalpha.scalingprofile = tmp[0] cfsalpha.scalingdirection = tmp[1] cfsalpha.min = float(tmp[2]) cfsalpha.max = float(tmp[3]) cfskappa = CFSParameter() cfskappa.ID = 'kappa' cfskappa.scalingprofile = tmp[4] cfskappa.scalingdirection = tmp[5] cfskappa.min = float(tmp[6]) cfskappa.max = float(tmp[7]) cfssigma = CFSParameter() cfssigma.ID = 'sigma' cfssigma.scalingprofile = tmp[8] cfssigma.scalingdirection = tmp[9] cfssigma.min = float(tmp[10]) if tmp[11] == 'None': cfssigma.max = None else: cfssigma.max = float(tmp[11]) cfs = CFS() cfs.alpha = cfsalpha cfs.kappa = cfskappa cfs.sigma = cfssigma if G.messages: print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max)) G.cfs.append(cfs)
def calculate_coord(self, coord, val): co = round_value(float(val) / getattr(self, 'd' + coord)) return co
def process_singlecmds(singlecmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: singlecmds (dict): Commands that can only occur once in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Check validity of command parameters in order needed # messages cmd = '#messages' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].lower() == 'y': G.messages = True elif singlecmds[cmd].lower() == 'n': G.messages = False else: raise CmdInputError(cmd + ' requires input values of either y or n') # Title cmd = '#title' if singlecmds[cmd] is not None: G.title = singlecmds[cmd] if G.messages: print('Model title: {}'.format(G.title)) # Get information about host machine hostinfo = get_host_info() # Number of threads (OpenMP) to use cmd = '#num_threads' if sys.platform == 'darwin': os.environ[ 'OMP_WAIT_POLICY'] = 'ACTIVE' # Should waiting threads consume CPU power (can drastically effect performance) os.environ[ 'OMP_DYNAMIC'] = 'FALSE' # Number of threads may be adjusted by the run time environment to best utilize system resources os.environ[ 'OMP_PLACES'] = 'cores' # Each place corresponds to a single core (having one or more hardware threads) os.environ['OMP_PROC_BIND'] = 'TRUE' # Bind threads to physical cores # os.environ['OMP_DISPLAY_ENV'] = 'TRUE' # Prints OMP version and environment variables (useful for debug) # Catch bug with Windows Subsystem for Linux (https://github.com/Microsoft/BashOnWindows/issues/785) if 'Microsoft' in hostinfo['osversion']: os.environ['KMP_AFFINITY'] = 'disabled' del os.environ['OMP_PLACES'] del os.environ['OMP_PROC_BIND'] if singlecmds[cmd] is not None: tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError( cmd + ' requires exactly one parameter to specify the number of threads to use' ) if tmp[0] < 1: raise CmdInputError( cmd + ' requires the value to be an integer not less than one') G.nthreads = tmp[0] os.environ['OMP_NUM_THREADS'] = str(G.nthreads) elif os.environ.get('OMP_NUM_THREADS'): G.nthreads = int(os.environ.get('OMP_NUM_THREADS')) else: # Set number of threads to number of physical CPU cores G.nthreads = hostinfo['physicalcores'] os.environ['OMP_NUM_THREADS'] = str(G.nthreads) if G.messages: print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads)) if G.nthreads > hostinfo['physicalcores']: print( Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.' .format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL) # Spatial discretisation cmd = '#dx_dy_dz' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') if tmp[0] <= 0: raise CmdInputError( cmd + ' requires the x-direction spatial step to be greater than zero') if tmp[1] <= 0: raise CmdInputError( cmd + ' requires the y-direction spatial step to be greater than zero') if tmp[2] <= 0: raise CmdInputError( cmd + ' requires the z-direction spatial step to be greater than zero') G.dx = tmp[0] G.dy = tmp[1] G.dz = tmp[2] if G.messages: print('Spatial discretisation: {:g} x {:g} x {:g}m'.format( G.dx, G.dy, G.dz)) # Domain cmd = '#domain' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.nx = round_value(tmp[0] / G.dx) G.ny = round_value(tmp[1] / G.dy) G.nz = round_value(tmp[2] / G.dz) if G.nx == 0 or G.ny == 0 or G.nz == 0: raise CmdInputError(cmd + ' requires at least one cell in every dimension') if G.messages: print( 'Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)' .format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) # Estimate memory (RAM) usage memestimate = memory_usage(G) # Check if model can be built and/or run on host if memestimate > hostinfo['ram']: raise GeneralError( 'Estimated memory (RAM) required ~{} exceeds {} detected!\n'. format(human_size(memestimate), human_size(hostinfo['ram'], a_kilobyte_is_1024_bytes=True))) if G.messages: print('Estimated memory (RAM) required: ~{}'.format( human_size(memestimate))) # Time step CFL limit (use either 2D or 3D) and default PML thickness if G.nx == 1: G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.dimension = '2D' G.pmlthickness['x0'] = 0 G.pmlthickness['xmax'] = 0 elif G.ny == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) G.dimension = '2D' G.pmlthickness['y0'] = 0 G.pmlthickness['ymax'] = 0 elif G.nz == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) G.dimension = '2D' G.pmlthickness['z0'] = 0 G.pmlthickness['zmax'] = 0 else: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.dimension = '3D' # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number. G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1) if G.messages: print('Time step (at {} CFL limit): {:g} secs'.format( G.dimension, G.dt)) # Time step stability factor cmd = '#time_step_stability_factor' if singlecmds[cmd] is not None: tmp = tuple(float(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if tmp[0] <= 0 or tmp[0] > 1: raise CmdInputError( cmd + ' requires the value of the time step stability factor to be between zero and one' ) G.dt = G.dt * tmp[0] if G.messages: print('Time step (modified): {:g} secs'.format(G.dt)) # Time window cmd = '#time_window' tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError( cmd + ' requires exactly one parameter to specify the time window. Either in seconds or number of iterations.' ) tmp = tmp[0].lower() # If number of iterations given try: tmp = int(tmp) G.timewindow = (tmp - 1) * G.dt G.iterations = tmp # If real floating point value given except: tmp = float(tmp) if tmp > 0: G.timewindow = tmp G.iterations = round_value((tmp / G.dt)) + 1 else: raise CmdInputError(cmd + ' must have a value greater than zero') if G.messages: print('Time window: {:g} secs ({} iterations)'.format( G.timewindow, G.iterations)) # PML cmd = '#pml_cells' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 6: raise CmdInputError(cmd + ' requires either one or six parameters') if len(tmp) == 1: for key in G.pmlthickness.keys(): G.pmlthickness[key] = int(tmp[0]) else: G.pmlthickness['x0'] = int(tmp[0]) G.pmlthickness['y0'] = int(tmp[1]) G.pmlthickness['z0'] = int(tmp[2]) G.pmlthickness['xmax'] = int(tmp[3]) G.pmlthickness['ymax'] = int(tmp[4]) G.pmlthickness['zmax'] = int(tmp[5]) if 2 * G.pmlthickness['x0'] >= G.nx or 2 * G.pmlthickness[ 'y0'] >= G.ny or 2 * G.pmlthickness[ 'z0'] >= G.nz or 2 * G.pmlthickness[ 'xmax'] >= G.nx or 2 * G.pmlthickness[ 'ymax'] >= G.ny or 2 * G.pmlthickness['zmax'] >= G.nz: raise CmdInputError(cmd + ' has too many cells for the domain size') # src_steps cmd = '#src_steps' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.srcsteps[0] = round_value(float(tmp[0]) / G.dx) G.srcsteps[1] = round_value(float(tmp[1]) / G.dy) G.srcsteps[2] = round_value(float(tmp[2]) / G.dz) if G.messages: print( 'Simple sources will step {:g}m, {:g}m, {:g}m for each model run.' .format(G.srcsteps[0] * G.dx, G.srcsteps[1] * G.dy, G.srcsteps[2] * G.dz)) # rx_steps cmd = '#rx_steps' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.rxsteps[0] = round_value(float(tmp[0]) / G.dx) G.rxsteps[1] = round_value(float(tmp[1]) / G.dy) G.rxsteps[2] = round_value(float(tmp[2]) / G.dz) if G.messages: print( 'All receivers will step {:g}m, {:g}m, {:g}m for each model run.' .format(G.rxsteps[0] * G.dx, G.rxsteps[1] * G.dy, G.rxsteps[2] * G.dz)) # Excitation file for user-defined source waveforms cmd = '#excitation_file' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') excitationfile = tmp[0] # See if file exists at specified path and if not try input file directory if not os.path.isfile(excitationfile): excitationfile = os.path.abspath( os.path.join(G.inputdirectory, excitationfile)) # Get waveform names with open(excitationfile, 'r') as f: waveformIDs = f.readline().split() # Read all waveform values into an array waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype) for waveform in range(len(waveformIDs)): if any(x.ID == waveformIDs[waveform] for x in G.waveforms): raise CmdInputError( 'Waveform with ID {} already exists'.format( waveformIDs[waveform])) w = Waveform() w.ID = waveformIDs[waveform] w.type = 'user' if len(waveformvalues.shape) == 1: w.uservalues = waveformvalues[:] else: w.uservalues = waveformvalues[:, waveform] if G.messages: print('User waveform {} created.'.format(w.ID)) G.waveforms.append(w)
def process_multicmds(multicmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: multicmds (dict): Commands that can have multiple instances in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Waveform definitions cmdname = '#waveform' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 4: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly four parameters') if tmp[0].lower() not in Waveform.types: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have one of the following types {}'.format(','.join(Waveform.types))) if float(tmp[2]) <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires an excitation frequency value of greater than zero') if any(x.ID == tmp[3] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[3])) w = Waveform() w.ID = tmp[3] w.type = tmp[0].lower() w.amp = float(tmp[1]) w.freq = float(tmp[2]) if G.messages: print('Waveform {} of type {} with amplitude {:g}, frequency {:g}Hz created.'.format(w.ID, w.type, w.amp, w.freq)) G.waveforms.append(w) # Voltage source cmdname = '#voltage_source' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) resistance = float(tmp[4]) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') if resistance < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a source resistance of zero or greater') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[5] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[5])) v = VoltageSource() v.polarisation= tmp[0] v.xcoord = xcoord v.ycoord = ycoord v.zcoord = zcoord v.resistance = resistance v.waveformID = tmp[5] if len(tmp) > 6: # Check source start & source remove time parameters start = float(tmp[6]) stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') v.start = start if stop > G.timewindow: v.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop) else: v.start = 0 v.stop = G.timewindow startstop = ' ' if G.messages: print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * G.dx, v.ycoord * G.dy, v.zcoord * G.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID)) G.voltagesources.append(v) # Hertzian dipole cmdname = '#hertzian_dipole' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[4] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) h = HertzianDipole() h.polarisation = tmp[0] h.xcoord = xcoord h.ycoord = ycoord h.zcoord = zcoord h.waveformID = tmp[4] if len(tmp) > 5: # Check source start & source remove time parameters start = float(tmp[5]) stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') h.start = start if stop > G.timewindow: h.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop) else: h.start = 0 h.stop = G.timewindow startstop = ' ' if G.messages: print('Hertzian dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * G.dx, h.ycoord * G.dy, h.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(h.waveformID)) G.hertziandipoles.append(h) # Magnetic dipole cmdname = '#magnetic_dipole' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[4] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) m = MagneticDipole() m.polarisation = tmp[0] m.xcoord = xcoord m.ycoord = ycoord m.zcoord = zcoord m.waveformID = tmp[4] if len(tmp) > 5: # Check source start & source remove time parameters start = float(tmp[5]) stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') m.start = start if stop > G.timewindow: m.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop) else: m.start = 0 m.stop = G.timewindow startstop = ' ' if G.messages: print('Magnetic dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(m.polarisation, m.xcoord * G.dx, m.ycoord * G.dy, m.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(m.waveformID)) G.magneticdipoles.append(m) # Transmission line cmdname = '#transmission_line' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') # Check polarity & position parameters if tmp[0].lower() not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = round_value(float(tmp[1])/G.dx) ycoord = round_value(float(tmp[2])/G.dy) zcoord = round_value(float(tmp[3])/G.dz) resistance = float(tmp[4]) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') if resistance <= 0 or resistance > z0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a resistance greater than zero and less than the impedance of free space, i.e. 376.73 Ohms') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[5] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) t = TransmissionLine(G) t.polarisation = tmp[0] t.xcoord = xcoord t.ycoord = ycoord t.zcoord = zcoord t.resistance = resistance t.waveformID = tmp[5] t.calculate_incident_V_I(G) if len(tmp) > 6: # Check source start & source remove time parameters start = float(tmp[6]) stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') t.start = start if stop > G.timewindow: t.stop = G.timewindow startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop) else: t.start = 0 t.stop = G.timewindow startstop = ' ' if G.messages: print('Transmission line with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(t.polarisation, t.xcoord * G.dx, t.ycoord * G.dy, t.zcoord * G.dz, t.resistance) + startstop + 'using waveform {} created.'.format(t.waveformID)) G.transmissionlines.append(t) # Receiver cmdname = '#rx' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 3 and len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' has an incorrect number of parameters') # Check position parameters xcoord = round_value(float(tmp[0])/G.dx) ycoord = round_value(float(tmp[1])/G.dy) zcoord = round_value(float(tmp[2])/G.dz) if xcoord < 0 or xcoord >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' x-coordinate is not within the model domain') if ycoord < 0 or ycoord >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' y-coordinate is not within the model domain') if zcoord < 0 or zcoord >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' z-coordinate is not within the model domain') if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') r = Rx(xcoord=xcoord, ycoord=ycoord, zcoord=zcoord) # If no ID or outputs are specified, use default i.e Ex, Ey, Ez, Hx, Hy, Hz, Ix, Iy, Iz if len(tmp) == 3: r.outputs = Rx.availableoutputs[0:9] else: r.ID = tmp[3] # Check and add field output names for field in tmp[4::]: if field in Rx.availableoutputs: r.outputs.append(field) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' contains an output type that is not available') if G.messages: print('Receiver at {:g}m, {:g}m, {:g}m with output(s) {} created.'.format(r.xcoord * G.dx, r.ycoord * G.dy, r.zcoord * G.dz, ', '.join(r.outputs))) G.rxs.append(r) # Receiver box cmdname = '#rx_box' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 9: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly nine parameters') xs = round_value(float(tmp[0])/G.dx) xf = round_value(float(tmp[3])/G.dx) ys = round_value(float(tmp[1])/G.dy) yf = round_value(float(tmp[4])/G.dy) zs = round_value(float(tmp[2])/G.dz) zf = round_value(float(tmp[5])/G.dz) dx = round_value(float(tmp[6])/G.dx) dy = round_value(float(tmp[7])/G.dy) dz = round_value(float(tmp[8])/G.dz) if xs < 0 or xs >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower x-coordinate {:g}m is not within the model domain'.format(xs)) if xf < 0 or xf >= G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper x-coordinate {:g}m is not within the model domain'.format(xf)) if ys < 0 or ys >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower y-coordinate {:g}m is not within the model domain'.format(ys)) if yf < 0 or yf >= G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper y-coordinate {:g}m is not within the model domain'.format(yf)) if zs < 0 or zs >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower z-coordinate {:g}m is not within the model domain'.format(zs)) if zf < 0 or zf >= G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper z-coordinate {:g}m is not within the model domain'.format(zf)) if xcoord < G.pmlthickness[0] or xcoord > G.nx - G.pmlthickness[3] or ycoord < G.pmlthickness[1] or ycoord > G.ny - G.pmlthickness[4] or zcoord < G.pmlthickness[2] or zcoord > G.nz - G.pmlthickness[5]: print("WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.') if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') for x in range(xs, xf, dx): for y in range(ys, yf, dy): for z in range(zs, zf, dz): r = Rx(xcoord=x, ycoord=y, zcoord=z) G.rxs.append(r) if G.messages: print('Receiver box {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with steps {:g}m, {:g}m, {:g} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dy * G.dy, dz * G.dz)) # Snapshot cmdname = '#snapshot' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') xs = round_value(float(tmp[0])/G.dx) xf = round_value(float(tmp[3])/G.dx) ys = round_value(float(tmp[1])/G.dy) yf = round_value(float(tmp[4])/G.dy) zs = round_value(float(tmp[2])/G.dz) zf = round_value(float(tmp[5])/G.dz) dx = round_value(float(tmp[6])/G.dx) dy = round_value(float(tmp[7])/G.dy) dz = round_value(float(tmp[8])/G.dz) # If real floating point value given if '.' in tmp[9] or 'e' in tmp[9]: if float(tmp[9]) > 0: time = round_value((float(tmp[9]) / G.dt)) + 1 else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time value must be greater than zero') # If number of iterations given else: time = int(tmp[9]) if xs < 0 or xs > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower x-coordinate {:g}m is not within the model domain'.format(xs * G.dx)) if xf < 0 or xf > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper x-coordinate {:g}m is not within the model domain'.format(xf * G.dx)) if ys < 0 or ys > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower y-coordinate {:g}m is not within the model domain'.format(ys * G.dy)) if yf < 0 or yf > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper y-coordinate {:g}m is not within the model domain'.format(yf * G.dy)) if zs < 0 or zs > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower z-coordinate {:g}m is not within the model domain'.format(zs * G.dz)) if zf < 0 or zf > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper z-coordinate {:g}m is not within the model domain'.format(zf * G.dz)) if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if time <= 0 or time > G.iterations: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time value is not valid') s = Snapshot(xs, ys, zs, xf, yf, zf, dx, dy, dz, time, tmp[10]) if G.messages: print('Snapshot from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, at {:g} secs with filename {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dx * G.dy, dx * G.dz, s.time * G.dt, s.filename)) G.snapshots.append(s) # Materials # Create built-in materials m = Material(0, 'pec', G) m.average = False G.materials.append(m) m = Material(1, 'free_space', G) m.average = True G.materials.append(m) cmdname = '#material' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly five parameters') if float(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for static (DC) permittivity') if float(tmp[1]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for conductivity') if float(tmp[2]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for permeability') if float(tmp[3]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for magnetic conductivity') if any(x.ID == tmp[4] for x in G.materials): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[4])) # Create a new instance of the Material class material (start index after pec & free_space) m = Material(len(G.materials), tmp[4], G) m.er = float(tmp[0]) m.se = float(tmp[1]) m.mr = float(tmp[2]) m.sm = float(tmp[3]) if G.messages: print('Material {} with epsr={:g}, sig={:g} S/m; mur={:g}, sig*={:g} S/m created.'.format(m.ID, m.er, m.se, m.mr, m.sm)) # Append the new material object to the materials list G.materials.append(m) cmdname = '#add_dispersion_debye' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 4: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least four parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(2 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'debye' material.poles = poles material.average = False for pole in range(1, 2 * poles, 2): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt: material.deltaer.append(float(tmp[pole])) material.tau.append(float(tmp[pole + 1])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the permittivity difference and relaxation times, and relaxation times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: print('Debye-type disperion added to {} with delta_epsr={}, and tau={} secs created.'.format(material.ID, ','.join('%4.2f' % deltaer for deltaer in material.deltaer), ','.join('%4.3e' % tau for tau in material.tau))) cmdname = '#add_dispersion_lorentz' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(3 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'lorentz' material.poles = poles material.average = False for pole in range(1, 3 * poles, 3): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt and float(tmp[pole + 2]) > G.dt: material.deltaer.append(float(tmp[pole])) material.tau.append(float(tmp[pole + 1])) material.alpha.append(float(tmp[pole + 2])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the permittivity difference and frequencies, and associated times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: print('Lorentz-type disperion added to {} with delta_epsr={}, omega={} secs, and gamma={} created.'.format(material.ID, ','.join('%4.2f' % deltaer for deltaer in material.deltaer), ','.join('%4.3e' % tau for tau in material.tau), ','.join('%4.3e' % alpha for alpha in material.alpha))) cmdname = '#add_dispersion_drude' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(3 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'drude' material.poles = poles material.average = False for pole in range(1, 2 * poles, 2): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt: material.tau.append(float(tmp[pole ])) material.alpha.append(float(tmp[pole + 1])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: print('Drude-type disperion added to {} with omega={} secs, and gamma={} secs created.'.format(material.ID, ','.join('%4.3e' % tau for tau in material.tau), ','.join('%4.3e' % alpha for alpha in material.alpha))) cmdname = '#soil_peplinski' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 7: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at exactly seven parameters') if float(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the sand fraction') if float(tmp[1]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the clay fraction') if float(tmp[2]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the bulk density') if float(tmp[3]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the sand particle density') if float(tmp[4]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the lower limit of the water volumetric fraction') if float(tmp[5]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the upper limit of the water volumetric fraction') if any(x.ID == tmp[6] for x in G.mixingmodels): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[6])) # Create a new instance of the Material class material (start index after pec & free_space) s = PeplinskiSoil(tmp[6], float(tmp[0]), float(tmp[1]), float(tmp[2]), float(tmp[3]), (float(tmp[4]), float(tmp[5]))) if G.messages: print('Mixing model (Peplinski) used to create {} with sand fraction {:g}, clay fraction {:g}, bulk density {:g}g/cm3, sand particle density {:g}g/cm3, and water volumetric fraction {:g} to {:g} created.'.format(s.ID, s.S, s.C, s.rb, s.rs, s.mu[0], s.mu[1])) # Append the new material object to the materials list G.mixingmodels.append(s) # Geometry views (creates VTK-based geometry files) cmdname = '#geometry_view' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') xs = round_value(float(tmp[0])/G.dx) xf = round_value(float(tmp[3])/G.dx) ys = round_value(float(tmp[1])/G.dy) yf = round_value(float(tmp[4])/G.dy) zs = round_value(float(tmp[2])/G.dz) zf = round_value(float(tmp[5])/G.dz) dx = round_value(float(tmp[6])/G.dx) dy = round_value(float(tmp[7])/G.dy) dz = round_value(float(tmp[8])/G.dz) if xs < 0 or xs > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower x-coordinate {:g}m is not within the model domain'.format(xs * G.dx)) if xf < 0 or xf > G.nx: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper x-coordinate {:g}m is not within the model domain'.format(xf * G.dx)) if ys < 0 or ys > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower y-coordinate {:g}m is not within the model domain'.format(ys * G.dy)) if yf < 0 or yf > G.ny: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper y-coordinate {:g}m is not within the model domain'.format(yf * G.dy)) if zs < 0 or zs > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower z-coordinate {:g}m is not within the model domain'.format(zs * G.dz)) if zf < 0 or zf > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the upper z-coordinate {:g}m is not within the model domain'.format(zf * G.dz)) if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx > G.nx or dy > G.ny or dz > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should be less than the domain size') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if tmp[10].lower() != 'n' and tmp[10].lower() != 'f': raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires type to be either n (normal) or f (fine)') g = GeometryView(xs, ys, zs, xf, yf, zf, dx, dy, dz, tmp[9], tmp[10].lower()) if G.messages: print('Geometry view from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, filename {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dy * G.dy, dz * G.dz, g.filename)) # Append the new GeometryView object to the geometry views list G.geometryviews.append(g) # Complex frequency shifted (CFS) PML parameter cmdname = '#pml_cfs' if multicmds[cmdname] != 'None': if len(multicmds[cmdname]) > 2: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' can only be used up to two times, for up to a 2nd order PML') for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 12: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly twelve parameters') if tmp[0] not in CFSParameter.scalingprofiles.keys() or tmp[4] not in CFSParameter.scalingprofiles.keys() or tmp[8] not in CFSParameter.scalingprofiles.keys(): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys()))) if tmp[1] not in CFSParameter.scalingdirections or tmp[5] not in CFSParameter.scalingdirections or tmp[9] not in CFSParameter.scalingdirections: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys()))) if float(tmp[2]) < 0 or float(tmp[3]) < 0 or float(tmp[6]) < 0 or float(tmp[7]) < 0 or float(tmp[10]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' minimum and maximum scaling values must be greater than zero') if float(tmp[6]) < 1: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' minimum scaling value for kappa must be greater than zero') cfsalpha = CFSParameter() cfsalpha.ID = 'alpha' cfsalpha.scalingprofile = tmp[0] cfsalpha.scalingdirection = tmp[1] cfsalpha.min = float(tmp[2]) cfsalpha.max = float(tmp[3]) cfskappa = CFSParameter() cfskappa.ID = 'kappa' cfskappa.scalingprofile = tmp[4] cfskappa.scalingdirection = tmp[5] cfskappa.min = float(tmp[6]) cfskappa.max = float(tmp[7]) cfssigma = CFSParameter() cfssigma.ID = 'sigma' cfssigma.scalingprofile = tmp[8] cfssigma.scalingdirection = tmp[9] cfssigma.min = float(tmp[10]) if tmp[11] == 'None': cfssigma.max = None else: cfssigma.max = float(tmp[11]) cfs = CFS() cfs.alpha = cfsalpha cfs.kappa = cfskappa cfs.sigma = cfssigma if G.messages: print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max)) G.cfs.append(cfs)
if args.freq <= 0: raise CmdInputError( 'The waveform requires an excitation frequency value of greater than zero' ) w = Waveform() w.type = args.type w.amp = args.amp w.freq = args.freq dt = args.dt # Check time window if '.' in args.timewindow or 'e' in args.timewindow: if float(args.timewindow) > 0: timewindow = float(args.timewindow) iterations = round_value((float(args.timewindow) / dt)) + 1 else: raise CmdInputError('Time window must have a value greater than zero') # If number of iterations given else: timewindow = (int(args.timewindow) - 1) * dt iterations = int(args.timewindow) time = np.linspace(0, 1, iterations) time *= (iterations * dt) waveform = np.zeros(len(time)) timeiter = np.nditer(time, flags=['c_index']) while not timeiter.finished: waveform[timeiter.index] = w.calculate_value(timeiter[0], dt) timeiter.iternext()
def write_vtk(self, G, pbar): """ Writes the geometry information to a VTK file. Either ImageData (.vti) for a per-cell geometry view, or PolygonalData (.vtp) for a per-cell-edge geometry view. N.B. No Python 3 support for VTK at time of writing (03/2015) Args: G (class): Grid class instance - holds essential parameters describing the model. pbar (class): Progress bar class instance. """ if self.fileext == '.vti': # Create arrays and add numeric IDs for PML, sources and receivers # (0 is not set, 1 is PML, srcs and rxs numbered thereafter) self.srcs_pml = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) self.rxs = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) for pml in G.pmls: self.srcs_pml[pml.xs:pml.xf, pml.ys:pml.yf, pml.zs:pml.zf] = 1 for index, src in enumerate(G.hertziandipoles + G.magneticdipoles + G.voltagesources + G.transmissionlines): self.srcs_pml[src.xcoord, src.ycoord, src.zcoord] = index + 2 for index, rx in enumerate(G.rxs): self.rxs[rx.xcoord, rx.ycoord, rx.zcoord] = index + 1 vtk_srcs_pml_offset = round_value( (np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize) vtk_rxs_offset = round_value( (np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize + (np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize) with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write( '<VTKFile type="ImageData" version="1.0" byte_order="{}">\n' .format(GeometryView.byteorder).encode('utf-8')) f.write( '<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n' .format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) f.write('<Piece Extent="{} {} {} {} {} {}">\n'.format( self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write( '<DataArray type="UInt32" Name="Material" format="appended" offset="0" />\n' .encode('utf-8')) f.write( '<DataArray type="Int8" Name="Sources_PML" format="appended" offset="{}" />\n' .format(vtk_srcs_pml_offset).encode('utf-8')) f.write( '<DataArray type="Int8" Name="Receivers" format="appended" offset="{}" />\n' .format(vtk_rxs_offset).encode('utf-8')) f.write('</CellData>\n'.encode('utf-8')) f.write( '</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_'. encode('utf-8')) solid_geometry = np.zeros((self.vtk_ncells), dtype=np.uint32) srcs_pml_geometry = np.zeros((self.vtk_ncells), dtype=np.int8) rxs_geometry = np.zeros((self.vtk_ncells), dtype=np.int8) define_normal_geometry(self.xs, self.xf, self.ys, self.yf, self.zs, self.zf, self.dx, self.dy, self.dz, G.solid, self.srcs_pml, self.rxs, solid_geometry, srcs_pml_geometry, rxs_geometry) # Write number of bytes of appended data as UInt32 f.write(pack('I', solid_geometry.nbytes)) pbar.update(n=4) # Write solid array f.write(solid_geometry) pbar.update(n=solid_geometry.nbytes) # Write number of bytes of appended data as UInt32 f.write(pack('I', srcs_pml_geometry.nbytes)) pbar.update(n=4) # Write sources and PML positions f.write(srcs_pml_geometry) pbar.update(n=srcs_pml_geometry.nbytes) # Write number of bytes of appended data as UInt32 f.write(pack('I', rxs_geometry.nbytes)) pbar.update(n=4) # Write receiver positions f.write(rxs_geometry) pbar.update(n=rxs_geometry.nbytes) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.write_gprmax_info(f, G) elif self.fileext == '.vtp': with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write( '<VTKFile type="PolyData" version="1.0" byte_order="{}">\n' .format(GeometryView.byteorder).encode('utf-8')) f.write( '<PolyData>\n<Piece NumberOfPoints="{}" NumberOfVerts="0" NumberOfLines="{}" NumberOfStrips="0" NumberOfPolys="0">\n' .format(self.vtk_numpoints, self.vtk_numlines).encode('utf-8')) f.write( '<Points>\n<DataArray type="Float32" NumberOfComponents="3" format="appended" offset="0" />\n</Points>\n' .encode('utf-8')) f.write( '<Lines>\n<DataArray type="UInt32" Name="connectivity" format="appended" offset="{}" />\n' .format(self.vtk_connectivity_offset).encode('utf-8')) f.write( '<DataArray type="UInt32" Name="offsets" format="appended" offset="{}" />\n</Lines>\n' .format(self.vtk_offsets_offset).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write( '<DataArray type="UInt32" Name="Material" format="appended" offset="{}" />\n' .format(self.vtk_materials_offset).encode('utf-8')) f.write('</CellData>\n'.encode('utf-8')) f.write( '</Piece>\n</PolyData>\n<AppendedData encoding="raw">\n_'. encode('utf-8')) # Coordinates of each point points = np.zeros((self.vtk_numpoints, 3), dtype=np.float32) # Number of x components # Node connectivity. Each index contains a pair of connected x nodes x_lines = np.zeros((self.vtk_nxlines, 2), dtype=np.uint32) # Material at Ex location in Yee cell. x_materials = np.zeros((self.vtk_nxlines), dtype=np.uint32) y_lines = np.zeros((self.vtk_nylines, 2), dtype=np.uint32) y_materials = np.zeros((self.vtk_nylines), dtype=np.uint32) z_lines = np.zeros((self.vtk_nzlines, 2), dtype=np.uint32) z_materials = np.zeros((self.vtk_nzlines), dtype=np.uint32) define_fine_geometry(self.nx, self.ny, self.nz, self.xs, self.xf, self.ys, self.yf, self.zs, self.zf, G.dx, G.dy, G.dz, G.ID, points, x_lines, x_materials, y_lines, y_materials, z_lines, z_materials) # Write point data f.write(pack('I', points.nbytes)) f.write(points) pbar.update(n=points.nbytes) # Write connectivity data f.write( pack( 'I', np.dtype(np.uint32).itemsize * self.vtk_numlines * self.vtk_numline_components)) pbar.update(n=4) f.write(x_lines) pbar.update(n=x_lines.nbytes) f.write(y_lines) pbar.update(n=y_lines.nbytes) f.write(z_lines) pbar.update(n=z_lines.nbytes) # Write cell type (line) offsets f.write( pack('I', np.dtype(np.uint32).itemsize * self.vtk_numlines)) pbar.update(n=4) for vtk_offsets in range( self.vtk_numline_components, (self.vtk_numline_components * self.vtk_numlines) + self.vtk_numline_components, self.vtk_numline_components): f.write(pack('I', vtk_offsets)) pbar.update(n=4) # Write material IDs per-cell-edge, i.e. from ID array f.write( pack('I', np.dtype(np.uint32).itemsize * self.vtk_numlines)) pbar.update(n=4) f.write(x_materials) pbar.update(n=x_materials.nbytes) f.write(y_materials) pbar.update(n=y_materials.nbytes) f.write(z_materials) pbar.update(n=z_materials.nbytes) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.write_gprmax_info(f, G, materialsonly=True)
def write_vtk(self, modelrun, numbermodelruns, G, pbar): """Writes the geometry information to a VTK file. Either ImageData (.vti) for a per-cell geometry view, or PolygonalData (.vtp) for a per-cell-edge geometry view. N.B. No Python 3 support for VTK at time of writing (03/2015) Args: modelrun (int): Current model run number. numbermodelruns (int): Total number of model runs. G (class): Grid class instance - holds essential parameters describing the model. pbar (class): Progress bar class instance. """ if self.fileext == '.vti': # Create arrays and add numeric IDs for PML, sources and receivers (0 is not set, 1 is PML, srcs and rxs numbered thereafter) self.srcs_pml = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) self.rxs = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) for pml in G.pmls: self.srcs_pml[pml.xs:pml.xf, pml.ys:pml.yf, pml.zs:pml.zf] = 1 for index, src in enumerate(G.hertziandipoles + G.magneticdipoles + G.voltagesources + G.transmissionlines): self.srcs_pml[src.xcoord, src.ycoord, src.zcoord] = index + 2 for index, rx in enumerate(G.rxs): self.rxs[rx.xcoord, rx.ycoord, rx.zcoord] = index + 1 vtk_srcs_pml_offset = round_value((np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize) vtk_rxs_offset = round_value((np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize + (np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) + np.dtype(np.uint32).itemsize) with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write('<VTKFile type="ImageData" version="1.0" byte_order="{}">\n'.format(GeometryView.byteorder).encode('utf-8')) f.write('<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) f.write('<Piece Extent="{} {} {} {} {} {}">\n'.format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write('<DataArray type="UInt32" Name="Material" format="appended" offset="0" />\n'.encode('utf-8')) f.write('<DataArray type="Int8" Name="Sources_PML" format="appended" offset="{}" />\n'.format(vtk_srcs_pml_offset).encode('utf-8')) f.write('<DataArray type="Int8" Name="Receivers" format="appended" offset="{}" />\n'.format(vtk_rxs_offset).encode('utf-8')) f.write('</CellData>\n'.encode('utf-8')) f.write('</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_'.encode('utf-8')) # Write material IDs datasize = int(np.dtype(np.uint32).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) # Write number of bytes of appended data as UInt32 f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): pbar.update(n=4) f.write(pack('I', G.solid[i, j, k])) # Write source/PML IDs datasize = int(np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): pbar.update() f.write(pack('b', self.srcs_pml[i, j, k])) # Write receiver IDs datasize = int(np.dtype(np.int8).itemsize * self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): pbar.update() f.write(pack('b', self.rxs[i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.write_gprmax_info(f, G) elif self.fileext == '.vtp': with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write('<VTKFile type="PolyData" version="1.0" byte_order="{}">\n'.format(GeometryView.byteorder).encode('utf-8')) f.write('<PolyData>\n<Piece NumberOfPoints="{}" NumberOfVerts="0" NumberOfLines="{}" NumberOfStrips="0" NumberOfPolys="0">\n'.format(self.vtk_numpoints, self.vtk_numlines).encode('utf-8')) f.write('<Points>\n<DataArray type="Float32" NumberOfComponents="3" format="appended" offset="0" />\n</Points>\n'.encode('utf-8')) f.write('<Lines>\n<DataArray type="UInt32" Name="connectivity" format="appended" offset="{}" />\n'.format(self.vtk_connectivity_offset).encode('utf-8')) f.write('<DataArray type="UInt32" Name="offsets" format="appended" offset="{}" />\n</Lines>\n'.format(self.vtk_offsets_offset).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write('<DataArray type="UInt32" Name="Material" format="appended" offset="{}" />\n'.format(self.vtk_materials_offset).encode('utf-8')) f.write('</CellData>\n'.encode('utf-8')) f.write('</Piece>\n</PolyData>\n<AppendedData encoding="raw">\n_'.encode('utf-8')) # Write points datasize = np.dtype(np.float32).itemsize * self.vtk_numpoints * self.vtk_numpoint_components f.write(pack('I', datasize)) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): pbar.update(n=12) f.write(pack('fff', i * G.dx, j * G.dy, k * G.dz)) # Write cell type (line) connectivity for x components datasize = np.dtype(np.uint32).itemsize * self.vtk_numlines * self.vtk_numline_components f.write(pack('I', datasize)) vtk_x2 = (self.ny + 1) * (self.nz + 1) for vtk_x1 in range(self.nx * (self.ny + 1) * (self.nz + 1)): pbar.update(n=8) f.write(pack('II', vtk_x1, vtk_x2)) # print('x {} {}'.format(vtk_x1, vtk_x2)) vtk_x2 += 1 # Write cell type (line) connectivity for y components vtk_ycnt1 = 1 vtk_ycnt2 = 0 for vtk_y1 in range((self.nx + 1) * (self.ny + 1) * (self.nz + 1)): if vtk_y1 >= (vtk_ycnt1 * (self.ny + 1) * (self.nz + 1)) - (self.nz + 1) and vtk_y1 < vtk_ycnt1 * (self.ny + 1) * (self.nz + 1): vtk_ycnt2 += 1 else: vtk_y2 = vtk_y1 + self.nz + 1 pbar.update(n=8) f.write(pack('II', vtk_y1, vtk_y2)) # print('y {} {}'.format(vtk_y1, vtk_y2)) if vtk_ycnt2 == self.nz + 1: vtk_ycnt1 += 1 vtk_ycnt2 = 0 # Write cell type (line) connectivity for z components vtk_zcnt = self.nz for vtk_z1 in range((self.nx + 1) * (self.ny + 1) * self.nz + (self.nx + 1) * (self.ny + 1)): if vtk_z1 != vtk_zcnt: vtk_z2 = vtk_z1 + 1 pbar.update(n=8) f.write(pack('II', vtk_z1, vtk_z2)) # print('z {} {}'.format(vtk_z1, vtk_z2)) else: vtk_zcnt += self.nz + 1 # Write cell type (line) offsets vtk_cell_pts = 2 datasize = np.dtype(np.uint32).itemsize * self.vtk_numlines f.write(pack('I', datasize)) for vtk_offsets in range(vtk_cell_pts, (self.vtk_numline_components * self.vtk_numlines) + vtk_cell_pts, vtk_cell_pts): pbar.update(n=4) f.write(pack('I', vtk_offsets)) # Write material IDs per-cell-edge, i.e. from ID array datasize = np.dtype(np.uint32).itemsize * self.vtk_numlines f.write(pack('I', datasize)) for i in range(self.xs, self.xf): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): pbar.update(n=4) f.write(pack('I', G.ID[0, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf): for k in range(self.zs, self.zf + 1): pbar.update(n=4) f.write(pack('I', G.ID[1, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf): pbar.update(n=4) f.write(pack('I', G.ID[2, i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) self.write_gprmax_info(f, G, materialsonly=True)
def write_file(self, modelrun, numbermodelruns, G): """Writes the geometry information to a VTK file. Either ImageData (.vti) for a per cell geometry view, or PolygonalData (.vtp) for a per cell edge geometry view. Args: modelrun (int): Current model run number. numbermodelruns (int): Total number of model runs. G (class): Grid class instance - holds essential parameters describing the model. """ # Construct filename from user-supplied name and model run number if numbermodelruns == 1: self.filename = G.inputdirectory + self.filename else: self.filename = G.inputdirectory + self.filename + str(modelrun) # No Python 3 support for VTK at time of writing (03/2015) self.vtk_nx = self.xf - self.xs self.vtk_ny = self.yf - self.ys self.vtk_nz = self.zf - self.zs if self.type == 'n': self.filename += '.vti' # Calculate number of cells according to requested sampling self.vtk_xscells = round_value(self.xs / self.dx) self.vtk_xfcells = round_value(self.xf / self.dx) self.vtk_yscells = round_value(self.ys / self.dy) self.vtk_yfcells = round_value(self.yf / self.dy) self.vtk_zscells = round_value(self.zs / self.dz) self.vtk_zfcells = round_value(self.zf / self.dz) with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write( '<VTKFile type="ImageData" version="1.0" byte_order="{}">\n' .format(GeometryView.byteorder).encode('utf-8')) f.write( '<ImageData WholeExtent="{} {} {} {} {} {}" Origin="0 0 0" Spacing="{:.3} {:.3} {:.3}">\n' .format(self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells, self.dx * G.dx, self.dy * G.dy, self.dz * G.dz).encode('utf-8')) f.write('<Piece Extent="{} {} {} {} {} {}">\n'.format( self.vtk_xscells, self.vtk_xfcells, self.vtk_yscells, self.vtk_yfcells, self.vtk_zscells, self.vtk_zfcells).encode('utf-8')) f.write('<CellData Scalars="Material">\n'.encode('utf-8')) f.write( '<DataArray type="UInt32" Name="Material" format="appended" offset="0" />\n' .encode('utf-8')) f.write( '</CellData>\n</Piece>\n</ImageData>\n<AppendedData encoding="raw">\n_' .encode('utf-8')) # Calculate number of bytes of appended data section datasize = int( np.dtype(np.uint32).itemsize * (self.vtk_nx / self.dx) * (self.vtk_ny / self.dy) * (self.vtk_nz / self.dz)) # Write number of bytes of appended data as UInt32 f.write(pack('I', datasize)) for k in range(self.zs, self.zf, self.dz): for j in range(self.ys, self.yf, self.dy): for i in range(self.xs, self.xf, self.dx): f.write(pack('I', G.solid[i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) # Write gprMax specific information which relates material name to material numeric identifier f.write('\n\n<gprMax>\n'.encode('utf-8')) for material in G.materials: f.write('<Material name="{}">{}</Material>\n'.format( material.ID, material.numID).encode('utf-8')) f.write('</gprMax>\n'.encode('utf-8')) elif self.type == 'f': self.filename += '.vtp' vtk_numpoints = (self.vtk_nx + 1) * (self.vtk_ny + 1) * (self.vtk_nz + 1) vtk_numpoint_components = 3 vtk_numlines = 2 * self.vtk_nx * self.vtk_ny + 2 * self.vtk_ny * self.vtk_nz + 2 * self.vtk_nx * self.vtk_nz + 3 * self.vtk_nx * self.vtk_ny * self.vtk_nz + self.vtk_nx + self.vtk_ny + self.vtk_nz vtk_numline_components = 2 vtk_connectivity_offset = ( vtk_numpoints * vtk_numpoint_components * np.dtype(np.float32).itemsize) + np.dtype(np.uint32).itemsize vtk_offsets_offset = vtk_connectivity_offset + ( vtk_numlines * vtk_numline_components * np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize vtk_id_offset = vtk_offsets_offset + (vtk_numlines * np.dtype( np.uint32).itemsize) + np.dtype(np.uint32).itemsize vtk_offsets_size = vtk_numlines with open(self.filename, 'wb') as f: f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write( '<VTKFile type="PolyData" version="1.0" byte_order="{}">\n' .format(GeometryView.byteorder).encode('utf-8')) f.write( '<PolyData>\n<Piece NumberOfPoints="{}" NumberOfVerts="0" NumberOfLines="{}" NumberOfStrips="0" NumberOfPolys="0">\n' .format(vtk_numpoints, vtk_numlines).encode('utf-8')) f.write( '<Points>\n<DataArray type="Float32" NumberOfComponents="3" format="appended" offset="0" />\n</Points>\n' .encode('utf-8')) f.write( '<Lines>\n<DataArray type="UInt32" Name="connectivity" format="appended" offset="{}" />\n' .format(vtk_connectivity_offset).encode('utf-8')) f.write( '<DataArray type="UInt32" Name="offsets" format="appended" offset="{}" />\n</Lines>\n' .format(vtk_offsets_offset).encode('utf-8')) f.write( '<CellData Scalars="Material">\n<DataArray type="UInt32" Name="Material" format="appended" offset="{}" />\n</CellData>\n' .format(vtk_id_offset).encode('utf-8')) f.write( '</Piece>\n</PolyData>\n<AppendedData encoding="raw">\n_'. encode('utf-8')) # Write points datasize = np.dtype( np.float32 ).itemsize * vtk_numpoints * vtk_numpoint_components f.write(pack('I', datasize)) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): f.write(pack('fff', i * G.dx, j * G.dy, k * G.dz)) # Write cell type (line) connectivity for x components datasize = np.dtype( np.uint32).itemsize * vtk_numlines * vtk_numline_components f.write(pack('I', datasize)) vtk_x2 = (self.vtk_ny + 1) * (self.vtk_nz + 1) for vtk_x1 in range(self.vtk_nx * (self.vtk_ny + 1) * (self.vtk_nz + 1)): f.write(pack('II', vtk_x1, vtk_x2)) # print('x {} {}'.format(vtk_x1, vtk_x2)) vtk_x2 += 1 # Write cell type (line) connectivity for y components vtk_ycnt1 = 1 vtk_ycnt2 = 0 for vtk_y1 in range( (self.vtk_nx + 1) * (self.vtk_ny + 1) * (self.vtk_nz + 1)): if vtk_y1 >= (vtk_ycnt1 * (self.vtk_ny + 1) * (self.vtk_nz + 1)) - ( self.vtk_nz + 1) and vtk_y1 < vtk_ycnt1 * ( self.vtk_ny + 1) * (self.vtk_nz + 1): vtk_ycnt2 += 1 else: vtk_y2 = vtk_y1 + self.vtk_nz + 1 f.write(pack('II', vtk_y1, vtk_y2)) # print('y {} {}'.format(vtk_y1, vtk_y2)) if vtk_ycnt2 == self.vtk_nz + 1: vtk_ycnt1 += 1 vtk_ycnt2 = 0 # Write cell type (line) connectivity for z components vtk_zcnt = self.vtk_nz for vtk_z1 in range((self.vtk_nx + 1) * (self.vtk_ny + 1) * self.vtk_nz + (self.vtk_nx + 1) * (self.vtk_ny + 1)): if vtk_z1 != vtk_zcnt: vtk_z2 = vtk_z1 + 1 f.write(pack('II', vtk_z1, vtk_z2)) # print('z {} {}'.format(vtk_z1, vtk_z2)) else: vtk_zcnt += self.vtk_nz + 1 # Write cell type (line) offsets vtk_cell_pts = 2 datasize = np.dtype(np.uint32).itemsize * vtk_offsets_size f.write(pack('I', datasize)) for vtk_offsets in range( vtk_cell_pts, (vtk_numline_components * vtk_numlines) + vtk_cell_pts, vtk_cell_pts): f.write(pack('I', vtk_offsets)) # Write Ex, Ey, Ez values from ID array datasize = np.dtype(np.uint32).itemsize * vtk_numlines f.write(pack('I', datasize)) for i in range(self.xs, self.xf): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf + 1): f.write(pack('I', G.ID[0, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf): for k in range(self.zs, self.zf + 1): f.write(pack('I', G.ID[1, i, j, k])) for i in range(self.xs, self.xf + 1): for j in range(self.ys, self.yf + 1): for k in range(self.zs, self.zf): f.write(pack('I', G.ID[2, i, j, k])) f.write('\n</AppendedData>\n</VTKFile>'.encode('utf-8')) # Write gprMax specific information which relates material name to material numeric identifier f.write('\n\n<gprMax>\n'.encode('utf-8')) for material in G.materials: f.write('<Material name="{}">{}</Material>\n'.format( material.ID, material.numID).encode('utf-8')) f.write('</gprMax>\n'.encode('utf-8'))
def process_singlecmds(singlecmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: singlecmds (dict): Commands that can only occur once in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Check validity of command parameters in order needed # messages cmd = '#messages' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].lower() == 'y': G.messages = True elif singlecmds[cmd].lower() == 'n': G.messages = False else: raise CmdInputError(cmd + ' requires input values of either y or n') # Title cmd = '#title' if singlecmds[cmd] != 'None': G.title = singlecmds[cmd] if G.messages: print('Model title: {}'.format(G.title)) # Number of threads (OpenMP) to use cmd = '#num_threads' if sys.platform == 'darwin': os.environ['OMP_WAIT_POLICY'] = 'ACTIVE' # What to do with threads when they are waiting; can drastically effect performance os.environ['OMP_DYNAMIC'] = 'FALSE' os.environ['OMP_PROC_BIND'] = 'TRUE' # Bind threads to physical cores if singlecmds[cmd] != 'None': tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter to specify the number of threads to use') if tmp[0] < 1: raise CmdInputError(cmd + ' requires the value to be an integer not less than one') G.nthreads = tmp[0] os.environ['OMP_NUM_THREADS'] = str(G.nthreads) elif os.environ.get('OMP_NUM_THREADS'): G.nthreads = int(os.environ.get('OMP_NUM_THREADS')) else: # Set number of threads to number of physical CPU cores, i.e. avoid hyperthreading with OpenMP G.nthreads = psutil.cpu_count(logical=False) os.environ['OMP_NUM_THREADS'] = str(G.nthreads) if G.messages: machineID, cpuID, osversion = get_machine_cpu_os() print('Number of threads: {} ({})'.format(G.nthreads, cpuID)) if G.nthreads > psutil.cpu_count(logical=False): print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, psutil.cpu_count(logical=False)) + Style.RESET_ALL) # Spatial discretisation cmd = '#dx_dy_dz' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') if tmp[0] <= 0: raise CmdInputError(cmd + ' requires the x-direction spatial step to be greater than zero') if tmp[1] <= 0: raise CmdInputError(cmd + ' requires the y-direction spatial step to be greater than zero') if tmp[2] <= 0: raise CmdInputError(cmd + ' requires the z-direction spatial step to be greater than zero') G.dx = tmp[0] G.dy = tmp[1] G.dz = tmp[2] if G.messages: print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(G.dx, G.dy, G.dz)) # Domain cmd = '#domain' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.nx = round_value(tmp[0] / G.dx) G.ny = round_value(tmp[1] / G.dy) G.nz = round_value(tmp[2] / G.dz) if G.nx == 0 or G.ny == 0 or G.nz == 0: raise CmdInputError(cmd + ' requires at least one cell in every dimension') if G.messages: print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) # Estimate memory (RAM) usage stdoverhead = 70e6 floatarrays = (6 + 6 + 1) * (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * np.dtype(floattype).itemsize # 6 x field arrays + 6 x ID arrays + 1 x solid array rigidarray = (12 + 6) * (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * np.dtype(np.int8).itemsize memestimate = stdoverhead + floatarrays + rigidarray if memestimate > psutil.virtual_memory().total: print(Fore.RED + 'WARNING: Estimated memory (RAM) required ~{} exceeds {} detected!\n'.format(human_size(memestimate), human_size(psutil.virtual_memory().total, a_kilobyte_is_1024_bytes=True)) + Style.RESET_ALL) if G.messages: print('Estimated memory (RAM) required: ~{} ({} detected)'.format(human_size(memestimate), human_size(psutil.virtual_memory().total, a_kilobyte_is_1024_bytes=True))) # Time step CFL limit (use either 2D or 3D) and default PML thickness if G.nx == 1: G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.dimension = '2D' G.pmlthickness['xminus'] = 0 G.pmlthickness['xplus'] = 0 elif G.ny == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) G.dimension = '2D' G.pmlthickness['yminus'] = 0 G.pmlthickness['yplus'] = 0 elif G.nz == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) G.dimension = '2D' G.pmlthickness['zminus'] = 0 G.pmlthickness['zplus'] = 0 else: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.dimension = '3D' # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number. G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1) if G.messages: print('Time step (at {} CFL limit): {:g} secs'.format(G.dimension, G.dt)) # Time step stability factor cmd = '#time_step_stability_factor' if singlecmds[cmd] != 'None': tmp = tuple(float(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if tmp[0] <= 0 or tmp[0] > 1: raise CmdInputError(cmd + ' requires the value of the time step stability factor to be between zero and one') G.dt = G.dt * tmp[0] if G.messages: print('Time step (modified): {:g} secs'.format(G.dt)) # Time window cmd = '#time_window' tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter to specify the time window. Either in seconds or number of iterations.') tmp = tmp[0].lower() # If number of iterations given try: tmp = int(tmp) G.timewindow = (tmp - 1) * G.dt G.iterations = tmp # If real floating point value given except: tmp = float(tmp) if tmp > 0: G.timewindow = tmp G.iterations = round_value((tmp / G.dt)) + 1 else: raise CmdInputError(cmd + ' must have a value greater than zero') if G.messages: print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations)) # PML cmd = '#pml_cells' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 6: raise CmdInputError(cmd + ' requires either one or six parameters') if len(tmp) == 1: for key in G.pmlthickness.keys(): G.pmlthickness[key] = int(tmp[0]) else: G.pmlthickness['xminus'] = int(tmp[0]) G.pmlthickness['yminus'] = int(tmp[1]) G.pmlthickness['zminus'] = int(tmp[2]) G.pmlthickness['xplus'] = int(tmp[3]) G.pmlthickness['yplus'] = int(tmp[4]) G.pmlthickness['zplus'] = int(tmp[5]) if 2 * G.pmlthickness['xminus'] >= G.nx or 2 * G.pmlthickness['yminus'] >= G.ny or 2 * G.pmlthickness['zminus'] >= G.nz or 2 * G.pmlthickness['xplus'] >= G.nx or 2 * G.pmlthickness['yplus'] >= G.ny or 2 * G.pmlthickness['zplus'] >= G.nz: raise CmdInputError(cmd + ' has too many cells for the domain size') # src_steps cmd = '#src_steps' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.srcsteps[0] = round_value(float(tmp[0]) / G.dx) G.srcsteps[1] = round_value(float(tmp[1]) / G.dy) G.srcsteps[2] = round_value(float(tmp[2]) / G.dz) if G.messages: print('Simple sources will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.srcsteps[0] * G.dx, G.srcsteps[1] * G.dy, G.srcsteps[2] * G.dz)) # rx_steps cmd = '#rx_steps' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.rxsteps[0] = round_value(float(tmp[0]) / G.dx) G.rxsteps[1] = round_value(float(tmp[1]) / G.dy) G.rxsteps[2] = round_value(float(tmp[2]) / G.dz) if G.messages: print('All receivers will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.rxsteps[0] * G.dx, G.rxsteps[1] * G.dy, G.rxsteps[2] * G.dz)) # Excitation file for user-defined source waveforms cmd = '#excitation_file' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') excitationfile = tmp[0] # See if file exists at specified path and if not try input file directory if not os.path.isfile(excitationfile): excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile)) # Get waveform names with open(excitationfile, 'r') as f: waveformIDs = f.readline().split() # Read all waveform values into an array waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype) for waveform in range(len(waveformIDs)): if any(x.ID == waveformIDs[waveform] for x in G.waveforms): raise CmdInputError('Waveform with ID {} already exists'.format(waveformIDs[waveform])) w = Waveform() w.ID = waveformIDs[waveform] w.type = 'user' if len(waveformvalues.shape) == 1: w.uservalues = waveformvalues[:] else: w.uservalues = waveformvalues[:, waveform] if G.messages: print('User waveform {} created.'.format(w.ID)) G.waveforms.append(w)
def dispersion_analysis(G): """ Analysis of numerical dispersion (Taflove et al, 2005, p112) - worse case of maximum frequency and minimum wavelength Args: G (class): Grid class instance - holds essential parameters describing the model. Returns: results (dict): Results from dispersion analysis """ # Physical phase velocity error (percentage); grid sampling density; # material with maximum permittivity; maximum significant frequency; error message results = { 'deltavp': False, 'N': False, 'material': False, 'maxfreq': [], 'error': '' } # Find maximum significant frequency if G.waveforms: for waveform in G.waveforms: if waveform.type == 'sine' or waveform.type == 'contsine': results['maxfreq'].append(4 * waveform.freq) elif waveform.type == 'impulse': results['error'] = 'impulse waveform used.' else: # User-defined waveform if waveform.type == 'user': iterations = G.iterations # Built-in waveform else: # Time to analyse waveform - 4*pulse_width as using entire # time window can result in demanding FFT waveform.calculate_coefficients() iterations = round_value(4 * waveform.chi / G.dt) if iterations > G.iterations: iterations = G.iterations waveformvalues = np.zeros(G.iterations) for iteration in range(G.iterations): waveformvalues[iteration] = waveform.calculate_value( iteration * G.dt, G.dt) # Ensure source waveform is not being overly truncated before attempting any FFT if np.abs(waveformvalues[-1]) < np.abs( np.amax(waveformvalues)) / 100: # FFT freqs, power = fft_power(waveformvalues, G.dt) # Get frequency for max power freqmaxpower = np.where(np.isclose(power, 0))[0][0] # Set maximum frequency to a threshold drop from maximum power, ignoring DC value try: freqthres = np.where( power[freqmaxpower:] < -G.highestfreqthres )[0][0] + freqmaxpower results['maxfreq'].append(freqs[freqthres]) except ValueError: results[ 'error'] = 'unable to calculate maximum power from waveform, most likely due to undersampling.' # Ignore case where someone is using a waveform with zero amplitude, i.e. on a receiver elif waveform.amp == 0: pass # If waveform is truncated don't do any further analysis else: results[ 'error'] = 'waveform does not fit within specified time window and is therefore being truncated.' else: results['error'] = 'no waveform detected.' if results['maxfreq']: results['maxfreq'] = max(results['maxfreq']) # Find minimum wavelength (material with maximum permittivity) maxer = 0 matmaxer = '' for x in G.materials: if x.se != float('inf'): er = x.er # If there are dispersive materials calculate the complex relative permittivity # at maximum frequency and take the real part if x.poles > 0: er = x.calculate_er(results['maxfreq']) er = er.real if er > maxer: maxer = er matmaxer = x.ID results['material'] = next(x for x in G.materials if x.ID == matmaxer) # Minimum velocity minvelocity = c / np.sqrt(maxer) # Minimum wavelength minwavelength = minvelocity / results['maxfreq'] # Maximum spatial step if '3D' in G.mode: delta = max(G.dx, G.dy, G.dz) elif '2D' in G.mode: if G.nx == 1: delta = max(G.dy, G.dz) elif G.ny == 1: delta = max(G.dx, G.dz) elif G.nz == 1: delta = max(G.dx, G.dy) # Courant stability factor S = (c * G.dt) / delta # Grid sampling density results['N'] = minwavelength / delta # Check grid sampling will result in physical wave propagation if int(np.floor(results['N'])) >= G.mingridsampling: # Numerical phase velocity vp = np.pi / (results['N'] * np.arcsin((1 / S) * np.sin( (np.pi * S) / results['N']))) # Physical phase velocity error (percentage) results['deltavp'] = (((vp * c) - c) / c) * 100 # Store rounded down value of grid sampling density results['N'] = int(np.floor(results['N'])) return results
def process_multicmds(multicmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: multicmds (dict): Commands that can have multiple instances in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Check if coordinates are within the bounds of the grid def check_coordinates(x, y, z, name=''): try: G.within_bounds(x=x, y=y, z=z) except ValueError as err: s = "'{}: {} ' {} {}-coordinate is not within the model domain".format(cmdname, ' '.join(tmp), name, err.args[0]) raise CmdInputError(s) # Waveform definitions cmdname = '#waveform' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 4: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly four parameters') if tmp[0].lower() not in Waveform.types: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have one of the following types {}'.format(','.join(Waveform.types))) if float(tmp[2]) <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires an excitation frequency value of greater than zero') if any(x.ID == tmp[3] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[3])) w = Waveform() w.ID = tmp[3] w.type = tmp[0].lower() w.amp = float(tmp[1]) w.freq = float(tmp[2]) if G.messages: print('Waveform {} of type {} with maximum amplitude scaling {:g}, frequency {:g}Hz created.'.format(w.ID, w.type, w.amp, w.freq)) G.waveforms.append(w) # Voltage source cmdname = '#voltage_source' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') # Check polarity & position parameters polarisation = tmp[0].lower() if polarisation not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = G.calculate_coord('x', tmp[1]) ycoord = G.calculate_coord('y', tmp[2]) zcoord = G.calculate_coord('z', tmp[3]) resistance = float(tmp[4]) check_coordinates(xcoord, ycoord, zcoord) if xcoord < G.pmlthickness['xminus'] or xcoord > G.nx - G.pmlthickness['xplus'] or ycoord < G.pmlthickness['yminus'] or ycoord > G.ny - G.pmlthickness['yplus'] or zcoord < G.pmlthickness['zminus'] or zcoord > G.nz - G.pmlthickness['zplus']: print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) if resistance < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a source resistance of zero or greater') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[5] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[5])) v = VoltageSource() v.polarisation = polarisation v.xcoord = xcoord v.ycoord = ycoord v.zcoord = zcoord v.ID = v.__class__.__name__ + '(' + str(v.xcoord) + ',' + str(v.ycoord) + ',' + str(v.zcoord) + ')' v.resistance = resistance v.waveformID = tmp[5] if len(tmp) > 6: # Check source start & source remove time parameters start = float(tmp[6]) stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') v.start = start if stop > G.timewindow: v.stop = G.timewindow else: v.stop = stop startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop) else: v.start = 0 v.stop = G.timewindow startstop = ' ' if G.messages: print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * G.dx, v.ycoord * G.dy, v.zcoord * G.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID)) G.voltagesources.append(v) # Hertzian dipole cmdname = '#hertzian_dipole' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') # Check polarity & position parameters polarisation = tmp[0].lower() if polarisation not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = G.calculate_coord('x', tmp[1]) ycoord = G.calculate_coord('y', tmp[2]) zcoord = G.calculate_coord('z', tmp[3]) check_coordinates(xcoord, ycoord, zcoord) if xcoord < G.pmlthickness['xminus'] or xcoord > G.nx - G.pmlthickness['xplus'] or ycoord < G.pmlthickness['yminus'] or ycoord > G.ny - G.pmlthickness['yplus'] or zcoord < G.pmlthickness['zminus'] or zcoord > G.nz - G.pmlthickness['zplus']: print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[4] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) h = HertzianDipole() h.polarisation = polarisation # Set length of dipole to grid size in polaristion direction if h.polarisation == 'x': h.dl = G.dx elif h.polarisation == 'y': h.dl = G.dy elif h.polarisation == 'z': h.dl = G.dz h.xcoord = xcoord h.ycoord = ycoord h.zcoord = zcoord h.xcoordorigin = xcoord h.ycoordorigin = ycoord h.zcoordorigin = zcoord h.ID = h.__class__.__name__ + '(' + str(h.xcoord) + ',' + str(h.ycoord) + ',' + str(h.zcoord) + ')' h.waveformID = tmp[4] if len(tmp) > 5: # Check source start & source remove time parameters start = float(tmp[5]) stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') h.start = start if stop > G.timewindow: h.stop = G.timewindow else: h.stop = stop startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop) else: h.start = 0 h.stop = G.timewindow startstop = ' ' if G.messages: if G.dimension == '2D': print('Hertzian dipole is a line source in 2D with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * G.dx, h.ycoord * G.dy, h.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(h.waveformID)) else: print('Hertzian dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * G.dx, h.ycoord * G.dy, h.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(h.waveformID)) G.hertziandipoles.append(h) # Magnetic dipole cmdname = '#magnetic_dipole' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') # Check polarity & position parameters polarisation = tmp[0].lower() if polarisation not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = G.calculate_coord('x', tmp[1]) ycoord = G.calculate_coord('y', tmp[2]) zcoord = G.calculate_coord('z', tmp[3]) check_coordinates(xcoord, ycoord, zcoord) if xcoord < G.pmlthickness['xminus'] or xcoord > G.nx - G.pmlthickness['xplus'] or ycoord < G.pmlthickness['yminus'] or ycoord > G.ny - G.pmlthickness['yplus'] or zcoord < G.pmlthickness['zminus'] or zcoord > G.nz - G.pmlthickness['zplus']: print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[4] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) m = MagneticDipole() m.polarisation = polarisation m.xcoord = xcoord m.ycoord = ycoord m.zcoord = zcoord m.xcoordorigin = xcoord m.ycoordorigin = ycoord m.zcoordorigin = zcoord m.ID = m.__class__.__name__ + '(' + str(m.xcoord) + ',' + str(m.ycoord) + ',' + str(m.zcoord) + ')' m.waveformID = tmp[4] if len(tmp) > 5: # Check source start & source remove time parameters start = float(tmp[5]) stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') m.start = start if stop > G.timewindow: m.stop = G.timewindow else: m.stop = stop startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop) else: m.start = 0 m.stop = G.timewindow startstop = ' ' if G.messages: print('Magnetic dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(m.polarisation, m.xcoord * G.dx, m.ycoord * G.dy, m.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(m.waveformID)) G.magneticdipoles.append(m) # Transmission line cmdname = '#transmission_line' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') # Check polarity & position parameters polarisation = tmp[0].lower() if polarisation not in ('x', 'y', 'z'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') xcoord = G.calculate_coord('x', tmp[1]) ycoord = G.calculate_coord('y', tmp[2]) zcoord = G.calculate_coord('z', tmp[3]) resistance = float(tmp[4]) check_coordinates(xcoord, ycoord, zcoord) if xcoord < G.pmlthickness['xminus'] or xcoord > G.nx - G.pmlthickness['xplus'] or ycoord < G.pmlthickness['yminus'] or ycoord > G.ny - G.pmlthickness['yplus'] or zcoord < G.pmlthickness['zminus'] or zcoord > G.nz - G.pmlthickness['zplus']: print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) if resistance <= 0 or resistance >= z0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a resistance greater than zero and less than the impedance of free space, i.e. 376.73 Ohms') # Check if there is a waveformID in the waveforms list if not any(x.ID == tmp[5] for x in G.waveforms): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) t = TransmissionLine(G) t.polarisation = polarisation t.xcoord = xcoord t.ycoord = ycoord t.zcoord = zcoord t.ID = t.__class__.__name__ + '(' + str(t.xcoord) + ',' + str(t.ycoord) + ',' + str(t.zcoord) + ')' t.resistance = resistance t.waveformID = tmp[5] t.calculate_incident_V_I(G) if len(tmp) > 6: # Check source start & source remove time parameters start = float(tmp[6]) stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time to remove the source should not be less than zero') if stop - start <= 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' duration of the source should not be zero or less') t.start = start if stop > G.timewindow: t.stop = G.timewindow else: t.stop = stop startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop) else: t.start = 0 t.stop = G.timewindow startstop = ' ' if G.messages: print('Transmission line with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(t.polarisation, t.xcoord * G.dx, t.ycoord * G.dy, t.zcoord * G.dz, t.resistance) + startstop + 'using waveform {} created.'.format(t.waveformID)) G.transmissionlines.append(t) # Receiver cmdname = '#rx' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 3 and len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' has an incorrect number of parameters') # Check position parameters xcoord = round_value(float(tmp[0]) / G.dx) ycoord = round_value(float(tmp[1]) / G.dy) zcoord = round_value(float(tmp[2]) / G.dz) check_coordinates(xcoord, ycoord, zcoord) if xcoord < G.pmlthickness['xminus'] or xcoord > G.nx - G.pmlthickness['xplus'] or ycoord < G.pmlthickness['yminus'] or ycoord > G.ny - G.pmlthickness['yplus'] or zcoord < G.pmlthickness['zminus'] or zcoord > G.nz - G.pmlthickness['zplus']: print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) r = Rx() r.xcoord = xcoord r.ycoord = ycoord r.zcoord = zcoord r.xcoordorigin = xcoord r.ycoordorigin = ycoord r.zcoordorigin = zcoord # If no ID or outputs are specified, use default i.e Ex, Ey, Ez, Hx, Hy, Hz, Ix, Iy, Iz if len(tmp) == 3: r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')' for key in Rx.defaultoutputs: r.outputs[key] = np.zeros(G.iterations, dtype=floattype) else: r.ID = tmp[3] # Check and add field output names for field in tmp[4::]: if field in Rx.availableoutputs: r.outputs[field] = np.zeros(G.iterations, dtype=floattype) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' contains an output type that is not available') if G.messages: print('Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * G.dx, r.ycoord * G.dy, r.zcoord * G.dz, ', '.join(r.outputs))) G.rxs.append(r) # Receiver array cmdname = '#rx_array' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 9: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly nine parameters') xs = G.calculate_coord('x', tmp[0]) ys = G.calculate_coord('y', tmp[1]) zs = G.calculate_coord('z', tmp[2]) xf = G.calculate_coord('x', tmp[3]) yf = G.calculate_coord('y', tmp[4]) zf = G.calculate_coord('z', tmp[5]) dx = G.calculate_coord('x', tmp[6]) dy = G.calculate_coord('y', tmp[7]) dz = G.calculate_coord('z', tmp[8]) check_coordinates(xs, ys, zs, name='lower') check_coordinates(xf, yf, zf, name='upper') if xcoord < G.pmlthickness['xminus'] or xcoord > G.nx - G.pmlthickness['xplus'] or ycoord < G.pmlthickness['yminus'] or ycoord > G.ny - G.pmlthickness['yplus'] or zcoord < G.pmlthickness['zminus'] or zcoord > G.nz - G.pmlthickness['zplus']: print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) if xs > xf or ys > yf or zs > zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx < G.dx: if dx == 0: dx = 1 else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if dy < G.dy: if dy == 0: dy = 1 else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if dz < G.dz: if dz == 0: dz = 1 else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if G.messages: print('Receiver array {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with steps {:g}m, {:g}m, {:g}m'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dy * G.dy, dz * G.dz)) for x in range(xs, xf + 1, dx): for y in range(ys, yf + 1, dy): for z in range(zs, zf + 1, dz): r = Rx() r.xcoord = x r.ycoord = y r.zcoord = z r.xcoordorigin = x r.ycoordorigin = y r.zcoordorigin = z r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')' for key in Rx.defaultoutputs: r.outputs[key] = np.zeros(G.iterations, dtype=floattype) if G.messages: print(' Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * G.dx, r.ycoord * G.dy, r.zcoord * G.dz, ', '.join(r.outputs))) G.rxs.append(r) # Snapshot cmdname = '#snapshot' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') xs = G.calculate_coord('x', tmp[0]) ys = G.calculate_coord('y', tmp[1]) zs = G.calculate_coord('z', tmp[2]) xf = G.calculate_coord('x', tmp[3]) yf = G.calculate_coord('y', tmp[4]) zf = G.calculate_coord('z', tmp[5]) dx = G.calculate_coord('x', tmp[6]) dy = G.calculate_coord('y', tmp[7]) dz = G.calculate_coord('z', tmp[8]) # If number of iterations given try: time = int(tmp[9]) # If real floating point value given except: time = float(tmp[9]) if time > 0: time = round_value((time / G.dt)) + 1 else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time value must be greater than zero') check_coordinates(xs, ys, zs, name='lower') check_coordinates(xf, yf, zf, name='upper') if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if time <= 0 or time > G.iterations: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' time value is not valid') s = Snapshot(xs, ys, zs, xf, yf, zf, dx, dy, dz, time, tmp[10]) if G.messages: print('Snapshot from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, at {:g} secs with filename {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dx * G.dy, dx * G.dz, s.time * G.dt, s.basefilename)) G.snapshots.append(s) # Materials cmdname = '#material' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly five parameters') if float(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for static (DC) permittivity') if tmp[1] != 'inf': se = float(tmp[1]) if se < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for conductivity') else: se = float('inf') if float(tmp[2]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for permeability') if float(tmp[3]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for magnetic conductivity') if any(x.ID == tmp[4] for x in G.materials): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[4])) # Create a new instance of the Material class material (start index after pec & free_space) m = Material(len(G.materials), tmp[4]) m.er = float(tmp[0]) m.se = se m.mr = float(tmp[2]) m.sm = float(tmp[3]) # Set material averaging to False if infinite conductivity, i.e. pec if m.se == float('inf'): m.averagable = False if G.messages: tqdm.write('Material {} with epsr={:g}, sig={:g} S/m; mur={:g}, sig*={:g} S/m created.'.format(m.ID, m.er, m.se, m.mr, m.sm)) # Append the new material object to the materials list G.materials.append(m) cmdname = '#add_dispersion_debye' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 4: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least four parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(2 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'debye' material.poles = poles material.averagable = False for pole in range(1, 2 * poles, 2): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt: material.deltaer.append(float(tmp[pole])) material.tau.append(float(tmp[pole + 1])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the permittivity difference and relaxation times, and relaxation times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: tqdm.write('Debye disperion added to {} with delta_epsr={}, and tau={} secs created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau))) cmdname = '#add_dispersion_lorentz' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(3 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'lorentz' material.poles = poles material.averagable = False for pole in range(1, 3 * poles, 3): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt and float(tmp[pole + 2]) > G.dt: material.deltaer.append(float(tmp[pole])) material.tau.append(float(tmp[pole + 1])) material.alpha.append(float(tmp[pole + 2])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the permittivity difference and frequencies, and associated times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: tqdm.write('Lorentz disperion added to {} with delta_epsr={}, omega={} secs, and gamma={} created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha))) cmdname = '#add_dispersion_drude' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for number of poles') poles = int(tmp[0]) materialsrequested = tmp[(3 * poles) + 1:len(tmp)] # Look up requested materials in existing list of material instances materials = [y for x in materialsrequested for y in G.materials if y.ID == x] if len(materials) != len(materialsrequested): notfound = [x for x in materialsrequested if x not in materials] raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) for material in materials: material.type = 'drude' material.poles = poles material.averagable = False for pole in range(1, 2 * poles, 2): if float(tmp[pole]) > 0 and float(tmp[pole + 1]) > G.dt: material.tau.append(float(tmp[pole])) material.alpha.append(float(tmp[pole + 1])) else: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.') if material.poles > Material.maxpoles: Material.maxpoles = material.poles if G.messages: tqdm.write('Drude disperion added to {} with omega={} secs, and gamma={} secs created.'.format(material.ID, ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha))) cmdname = '#soil_peplinski' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 7: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at exactly seven parameters') if float(tmp[0]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the sand fraction') if float(tmp[1]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the clay fraction') if float(tmp[2]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the bulk density') if float(tmp[3]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the sand particle density') if float(tmp[4]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the lower limit of the water volumetric fraction') if float(tmp[5]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a positive value for the upper limit of the water volumetric fraction') if any(x.ID == tmp[6] for x in G.mixingmodels): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' with ID {} already exists'.format(tmp[6])) # Create a new instance of the Material class material (start index after pec & free_space) s = PeplinskiSoil(tmp[6], float(tmp[0]), float(tmp[1]), float(tmp[2]), float(tmp[3]), (float(tmp[4]), float(tmp[5]))) if G.messages: print('Mixing model (Peplinski) used to create {} with sand fraction {:g}, clay fraction {:g}, bulk density {:g}g/cm3, sand particle density {:g}g/cm3, and water volumetric fraction {:g} to {:g} created.'.format(s.ID, s.S, s.C, s.rb, s.rs, s.mu[0], s.mu[1])) # Append the new material object to the materials list G.mixingmodels.append(s) # Geometry views (creates VTK-based geometry files) cmdname = '#geometry_view' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') xs = G.calculate_coord('x', tmp[0]) ys = G.calculate_coord('y', tmp[1]) zs = G.calculate_coord('z', tmp[2]) xf = G.calculate_coord('x', tmp[3]) yf = G.calculate_coord('y', tmp[4]) zf = G.calculate_coord('z', tmp[5]) dx = G.calculate_coord('x', tmp[6]) dy = G.calculate_coord('y', tmp[7]) dz = G.calculate_coord('z', tmp[8]) check_coordinates(xs, ys, zs, name='lower') check_coordinates(xf, yf, zf, name='upper') if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') if dx < 0 or dy < 0 or dz < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than zero') if dx > G.nx or dy > G.ny or dz > G.nz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should be less than the domain size') if dx < G.dx or dy < G.dy or dz < G.dz: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the step size should not be less than the spatial discretisation') if tmp[10].lower() != 'n' and tmp[10].lower() != 'f': raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires type to be either n (normal) or f (fine)') if tmp[10].lower() == 'f' and (dx * G.dx != G.dx or dy * G.dy != G.dy or dz * G.dz != G.dz): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires the spatial discretisation for the geometry view to be the same as the model for geometry view of type f (fine)') # Set type of geometry file if tmp[10].lower() == 'n': fileext = '.vti' else: fileext = '.vtp' g = GeometryView(xs, ys, zs, xf, yf, zf, dx, dy, dz, tmp[9], fileext) if G.messages: print('Geometry view from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, with filename base {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, dx * G.dx, dy * G.dy, dz * G.dz, g.basefilename)) # Append the new GeometryView object to the geometry views list G.geometryviews.append(g) # Geometry object(s) output cmdname = '#geometry_objects_write' if multicmds[cmdname] != 'None': for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 7: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly seven parameters') xs = G.calculate_coord('x', tmp[0]) ys = G.calculate_coord('y', tmp[1]) zs = G.calculate_coord('z', tmp[2]) xf = G.calculate_coord('x', tmp[3]) yf = G.calculate_coord('y', tmp[4]) zf = G.calculate_coord('z', tmp[5]) check_coordinates(xs, ys, zs, name='lower') check_coordinates(xf, yf, zf, name='upper') if xs >= xf or ys >= yf or zs >= zf: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' the lower coordinates should be less than the upper coordinates') g = GeometryObjects(xs, ys, zs, xf, yf, zf, tmp[6]) if G.messages: print('Geometry objects in the volume from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, will be written to {}, with materials written to {}'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, g.filename, g.materialsfilename)) # Append the new GeometryView object to the geometry objects to write list G.geometryobjectswrite.append(g) # Complex frequency shifted (CFS) PML parameter cmdname = '#pml_cfs' if multicmds[cmdname] != 'None': if len(multicmds[cmdname]) > 2: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' can only be used up to two times, for up to a 2nd order PML') for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 12: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly twelve parameters') if tmp[0] not in CFSParameter.scalingprofiles.keys() or tmp[4] not in CFSParameter.scalingprofiles.keys() or tmp[8] not in CFSParameter.scalingprofiles.keys(): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys()))) if tmp[1] not in CFSParameter.scalingdirections or tmp[5] not in CFSParameter.scalingdirections or tmp[9] not in CFSParameter.scalingdirections: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys()))) if float(tmp[2]) < 0 or float(tmp[3]) < 0 or float(tmp[6]) < 0 or float(tmp[7]) < 0 or float(tmp[10]) < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' minimum and maximum scaling values must be greater than zero') if float(tmp[6]) < 1: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' minimum scaling value for kappa must be greater than or equal to one') cfsalpha = CFSParameter() cfsalpha.ID = 'alpha' cfsalpha.scalingprofile = tmp[0] cfsalpha.scalingdirection = tmp[1] cfsalpha.min = float(tmp[2]) cfsalpha.max = float(tmp[3]) cfskappa = CFSParameter() cfskappa.ID = 'kappa' cfskappa.scalingprofile = tmp[4] cfskappa.scalingdirection = tmp[5] cfskappa.min = float(tmp[6]) cfskappa.max = float(tmp[7]) cfssigma = CFSParameter() cfssigma.ID = 'sigma' cfssigma.scalingprofile = tmp[8] cfssigma.scalingdirection = tmp[9] cfssigma.min = float(tmp[10]) if tmp[11] == 'None': cfssigma.max = None else: cfssigma.max = float(tmp[11]) cfs = CFS() cfs.alpha = cfsalpha cfs.kappa = cfskappa cfs.sigma = cfssigma if G.messages: print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max)) G.cfs.append(cfs)
def process_singlecmds(singlecmds, multicmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: singlecmds (dict): Commands that can only occur once in the model. multicmds (dict): Commands that can have multiple instances in the model (required to pass to process_materials_file function). G (class): Grid class instance - holds essential parameters describing the model. """ # Check validity of command parameters in order needed # messages cmd = '#messages' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].lower() == 'y': G.messages = True elif singlecmds[cmd].lower() == 'n': G.messages = False else: raise CmdInputError(cmd + ' requires input values of either y or n') # Title cmd = '#title' if singlecmds[cmd] != 'None': G.title = singlecmds[cmd] if G.messages: print('Model title: {}'.format(G.title)) # Number of processors to run on (OpenMP) cmd = '#num_threads' ompthreads = os.environ.get('OMP_NUM_THREADS') if singlecmds[cmd] != 'None': tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError( cmd + ' requires exactly one parameter to specify the number of threads to use' ) if tmp[0] < 1: raise CmdInputError( cmd + ' requires the value to be an integer not less than one') G.nthreads = tmp[0] elif ompthreads: G.nthreads = int(ompthreads) else: # Set number of threads to number of physical CPU cores, i.e. avoid hyperthreading with OpenMP G.nthreads = psutil.cpu_count(logical=False) if G.messages: print('Number of threads: {}'.format(G.nthreads)) # Spatial discretisation cmd = '#dx_dy_dz' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') if tmp[0] <= 0: raise CmdInputError( cmd + ' requires the x-direction spatial step to be greater than zero') if tmp[1] <= 0: raise CmdInputError( cmd + ' requires the y-direction spatial step to be greater than zero') if tmp[2] <= 0: raise CmdInputError( cmd + ' requires the z-direction spatial step to be greater than zero') G.dx = tmp[0] G.dy = tmp[1] G.dz = tmp[2] if G.messages: print('Spatial discretisation: {:g} x {:g} x {:g}m'.format( G.dx, G.dy, G.dz)) # Domain cmd = '#domain' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.nx = round_value(tmp[0] / G.dx) G.ny = round_value(tmp[1] / G.dy) G.nz = round_value(tmp[2] / G.dz) if G.nx == 0 or G.ny == 0 or G.nz == 0: raise CmdInputError(cmd + ' requires at least one cell in every dimension') if G.messages: print( 'Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)' .format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) # Guesstimate at memory usage mem = (((G.nx + 1) * (G.ny + 1) * (G.nz + 1) * 13 * np.dtype(floattype).itemsize + (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * 18) * 1.1) + 30e6 print('Memory (RAM) usage: ~{} required, {} available'.format( human_size(mem), human_size(psutil.virtual_memory().total))) # Time step CFL limit - use either 2D or 3D (default) cmd = '#time_step_limit_type' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].lower() == '2d': if G.nx == 1: G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) elif G.ny == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) elif G.nz == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) else: raise CmdInputError( cmd + ' 2D CFL limit can only be used when one dimension of the domain is one cell' ) elif singlecmds[cmd].lower() == '3d': G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) else: raise CmdInputError(cmd + ' requires input values of either 2D or 3D') else: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number. G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1) if G.messages: print('Time step: {:g} secs'.format(G.dt)) # Time step stability factor cmd = '#time_step_stability_factor' if singlecmds[cmd] != 'None': tmp = tuple(float(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if tmp[0] <= 0 or tmp[0] > 1: raise CmdInputError( cmd + ' requires the value of the time step stability factor to be between zero and one' ) G.dt = G.dt * tmp[0] if G.messages: print('Time step (modified): {:g} secs'.format(G.dt)) # Time window cmd = '#time_window' tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError( cmd + ' requires exactly one parameter to specify the time window. Either in seconds or number of iterations.' ) tmp = tmp[0].lower() # If real floating point value given if '.' in tmp or 'e' in tmp: if float(tmp) > 0: G.timewindow = float(tmp) G.iterations = round_value((float(tmp) / G.dt)) + 1 else: raise CmdInputError(cmd + ' must have a value greater than zero') # If number of iterations given else: G.timewindow = (int(tmp) - 1) * G.dt G.iterations = int(tmp) if G.messages: print('Time window: {:g} secs ({} iterations)'.format( G.timewindow, G.iterations)) # PML cmd = '#pml_cells' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 6: raise CmdInputError(cmd + ' requires either one or six parameters') if len(tmp) == 1: G.pmlthickness = (int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0])) else: G.pmlthickness = (int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3]), int(tmp[4]), int(tmp[5])) if 2 * G.pmlthickness[0] >= G.nx or 2 * G.pmlthickness[ 1] >= G.ny or 2 * G.pmlthickness[2] >= G.nz or 2 * G.pmlthickness[ 3] >= G.nx or 2 * G.pmlthickness[ 4] >= G.ny or 2 * G.pmlthickness[5] >= G.nz: raise CmdInputError(cmd + ' has too many cells for the domain size') # src_steps cmd = '#src_steps' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.srcstepx = round_value(float(tmp[0]) / G.dx) G.srcstepy = round_value(float(tmp[1]) / G.dy) G.srcstepz = round_value(float(tmp[2]) / G.dz) if G.messages: print( 'All sources will step {:g}m, {:g}m, {:g}m for each model run.' .format(G.srcstepx * G.dx, G.srcstepy * G.dy, G.srcstepz * G.dz)) # rx_steps cmd = '#rx_steps' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.rxstepx = round_value(float(tmp[0]) / G.dx) G.rxstepy = round_value(float(tmp[1]) / G.dy) G.rxstepz = round_value(float(tmp[2]) / G.dz) if G.messages: print( 'All receivers will step {:g}m, {:g}m, {:g}m for each model run.' .format(G.rxstepx * G.dx, G.rxstepy * G.dy, G.rxstepz * G.dz)) # Excitation file for user-defined source waveforms cmd = '#excitation_file' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') excitationfile = tmp[0] # See if file exists at specified path and if not try input file directory if not os.path.isfile(excitationfile): excitationfile = os.path.join(G.inputdirectory, excitationfile) # Get waveform names with open(excitationfile, 'r') as f: waveformIDs = f.readline().split() # Read all waveform values into an array waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype) for waveform in range(len(waveformIDs)): if any(x.ID == waveformIDs[waveform] for x in G.waveforms): raise CmdInputError( 'Waveform with ID {} already exists'.format( waveformIDs[waveform])) w = Waveform() w.ID = waveformIDs[waveform] w.type = 'user' if len(waveformvalues.shape) == 1: w.uservalues = waveformvalues[:] else: w.uservalues = waveformvalues[:, waveform] if G.messages: print('User waveform {} created.'.format(w.ID)) G.waveforms.append(w)
def process_singlecmds(singlecmds, G): """Checks the validity of command parameters and creates instances of classes of parameters. Args: singlecmds (dict): Commands that can only occur once in the model. G (class): Grid class instance - holds essential parameters describing the model. """ # Check validity of command parameters in order needed # messages cmd = '#messages' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if singlecmds[cmd].lower() == 'y': G.messages = True elif singlecmds[cmd].lower() == 'n': G.messages = False else: raise CmdInputError(cmd + ' requires input values of either y or n') # Title cmd = '#title' if singlecmds[cmd] != 'None': G.title = singlecmds[cmd] if G.messages: print('Model title: {}'.format(G.title)) # Number of processors to run on (OpenMP) cmd = '#num_threads' os.environ['OMP_WAIT_POLICY'] = 'active' os.environ['OMP_DYNAMIC'] = 'false' os.environ['OMP_PROC_BIND'] = 'true' if singlecmds[cmd] != 'None': tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter to specify the number of threads to use') if tmp[0] < 1: raise CmdInputError(cmd + ' requires the value to be an integer not less than one') G.nthreads = tmp[0] os.environ['OMP_NUM_THREADS'] = str(G.nthreads) elif os.environ.get('OMP_NUM_THREADS'): G.nthreads = int(os.environ.get('OMP_NUM_THREADS')) else: # Set number of threads to number of physical CPU cores, i.e. avoid hyperthreading with OpenMP G.nthreads = psutil.cpu_count(logical=False) os.environ['OMP_NUM_THREADS'] = str(G.nthreads) if G.messages: print('Number of threads: {}'.format(G.nthreads)) if G.nthreads > psutil.cpu_count(logical=False): print('\nWARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, psutil.cpu_count(logical=False))) # Spatial discretisation cmd = '#dx_dy_dz' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') if tmp[0] <= 0: raise CmdInputError(cmd + ' requires the x-direction spatial step to be greater than zero') if tmp[1] <= 0: raise CmdInputError(cmd + ' requires the y-direction spatial step to be greater than zero') if tmp[2] <= 0: raise CmdInputError(cmd + ' requires the z-direction spatial step to be greater than zero') G.dx = tmp[0] G.dy = tmp[1] G.dz = tmp[2] if G.messages: print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(G.dx, G.dy, G.dz)) # Domain cmd = '#domain' tmp = [float(x) for x in singlecmds[cmd].split()] if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.nx = round_value(tmp[0]/G.dx) G.ny = round_value(tmp[1]/G.dy) G.nz = round_value(tmp[2]/G.dz) if G.nx == 0 or G.ny == 0 or G.nz == 0: raise CmdInputError(cmd + ' requires at least one cell in every dimension') if G.messages: print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) # Guesstimate at memory usage mem = (((G.nx + 1) * (G.ny + 1) * (G.nz + 1) * 13 * np.dtype(floattype).itemsize + (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * 18) * 1.1) + 30e6 print('Memory (RAM) usage: ~{} required, {} available'.format(human_size(mem), human_size(psutil.virtual_memory().total))) # Time step CFL limit (use either 2D or 3D) and default PML thickness if G.nx == 1: G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.dtlimit = '2D' G.pmlthickness = (0, G.pmlthickness, G.pmlthickness, 0, G.pmlthickness, G.pmlthickness) elif G.ny == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) G.dtlimit = '2D' G.pmlthickness = (G.pmlthickness, 0, G.pmlthickness, G.pmlthickness, 0, G.pmlthickness) elif G.nz == 1: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) G.dtlimit = '2D' G.pmlthickness = (G.pmlthickness, G.pmlthickness, 0, G.pmlthickness, G.pmlthickness, 0) else: G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.dtlimit = '3D' G.pmlthickness = (G.pmlthickness, G.pmlthickness, G.pmlthickness, G.pmlthickness, G.pmlthickness, G.pmlthickness) # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number. G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1) if G.messages: print('Time step (at {} CFL limit): {:g} secs'.format(G.dtlimit, G.dt)) # Time step stability factor cmd = '#time_step_stability_factor' if singlecmds[cmd] != 'None': tmp = tuple(float(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') if tmp[0] <= 0 or tmp[0] > 1: raise CmdInputError(cmd + ' requires the value of the time step stability factor to be between zero and one') G.dt = G.dt * tmp[0] if G.messages: print('Time step (modified): {:g} secs'.format(G.dt)) # Time window cmd = '#time_window' tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter to specify the time window. Either in seconds or number of iterations.') tmp = tmp[0].lower() # If number of iterations given try: tmp = int(tmp) G.timewindow = (tmp - 1) * G.dt G.iterations = tmp # If real floating point value given except: tmp = float(tmp) if tmp > 0: G.timewindow = tmp G.iterations = round_value((tmp / G.dt)) + 1 else: raise CmdInputError(cmd + ' must have a value greater than zero') if G.messages: print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations)) # PML cmd = '#pml_cells' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1 and len(tmp) != 6: raise CmdInputError(cmd + ' requires either one or six parameters') if len(tmp) == 1: G.pmlthickness = (int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0]), int(tmp[0])) else: G.pmlthickness = (int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3]), int(tmp[4]), int(tmp[5])) if 2*G.pmlthickness[0] >= G.nx or 2*G.pmlthickness[1] >= G.ny or 2*G.pmlthickness[2] >= G.nz or 2*G.pmlthickness[3] >= G.nx or 2*G.pmlthickness[4] >= G.ny or 2*G.pmlthickness[5] >= G.nz: raise CmdInputError(cmd + ' has too many cells for the domain size') # src_steps cmd = '#src_steps' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.srcstepx = round_value(float(tmp[0])/G.dx) G.srcstepy = round_value(float(tmp[1])/G.dy) G.srcstepz = round_value(float(tmp[2])/G.dz) if G.messages: print('Simple sources will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.srcstepx * G.dx, G.srcstepy * G.dy, G.srcstepz * G.dz)) # rx_steps cmd = '#rx_steps' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') G.rxstepx = round_value(float(tmp[0])/G.dx) G.rxstepy = round_value(float(tmp[1])/G.dy) G.rxstepz = round_value(float(tmp[2])/G.dz) if G.messages: print('All receivers will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.rxstepx * G.dx, G.rxstepy * G.dy, G.rxstepz * G.dz)) # Excitation file for user-defined source waveforms cmd = '#excitation_file' if singlecmds[cmd] != 'None': tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') excitationfile = tmp[0] # See if file exists at specified path and if not try input file directory if not os.path.isfile(excitationfile): excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile)) # Get waveform names with open(excitationfile, 'r') as f: waveformIDs = f.readline().split() # Read all waveform values into an array waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype) for waveform in range(len(waveformIDs)): if any(x.ID == waveformIDs[waveform] for x in G.waveforms): raise CmdInputError('Waveform with ID {} already exists'.format(waveformIDs[waveform])) w = Waveform() w.ID = waveformIDs[waveform] w.type = 'user' if len(waveformvalues.shape) == 1: w.uservalues = waveformvalues[:] else: w.uservalues = waveformvalues[:,waveform] if G.messages: print('User waveform {} created.'.format(w.ID)) G.waveforms.append(w)
def calculate_coord(self, coord, val): co = round_value(float(val) / getattr(self, 'd' + coord)) return co
def calculate_value(self, time, dt): """Calculates value of the waveform at a specific time. Args: time (float): Absolute time. dt (float): Absolute time discretisation. Returns: ampvalue (float): Calculated value for waveform. """ self.calculate_coefficients() # Waveforms if self.type == 'gaussian': delay = time - self.chi ampvalue = np.exp(-self.zeta * delay**2) elif self.type == 'gaussiandot' or self.type == 'gaussianprime': delay = time - self.chi ampvalue = -2 * self.zeta * delay * np.exp(-self.zeta * delay**2) elif self.type == 'gaussiandotnorm': delay = time - self.chi normalise = np.sqrt(np.exp(1) / (2 * self.zeta)) ampvalue = -2 * self.zeta * delay * np.exp( -self.zeta * delay**2) * normalise elif self.type == 'gaussiandotdot' or self.type == 'gaussiandoubleprime': delay = time - self.chi ampvalue = 2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp( -self.zeta * delay**2) elif self.type == 'gaussiandotdotnorm': delay = time - self.chi normalise = 1 / (2 * self.zeta) ampvalue = 2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp( -self.zeta * delay**2) * normalise elif self.type == 'ricker': delay = time - self.chi normalise = 1 / (2 * self.zeta) ampvalue = -(2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2)) * normalise elif self.type == 'sine': ampvalue = np.sin(2 * np.pi * self.freq * time) if time * self.freq > 1: ampvalue = 0 elif self.type == 'contsine': rampamp = 0.25 ramp = rampamp * time * self.freq if ramp > 1: ramp = 1 ampvalue = ramp * np.sin(2 * np.pi * self.freq * time) elif self.type == 'impulse': # time < dt condition required to do impulsive magnetic dipole if time == 0 or time < dt: ampvalue = 1 elif time >= dt: ampvalue = 0 elif self.type == 'user': index = round_value(time / dt) # Check to see if there are still user specified values and if not use zero if index > len(self.uservalues) - 1: ampvalue = 0 else: ampvalue = self.uservalues[index] ampvalue *= self.amp return ampvalue
def calculate_value(self, time, dt): """Calculates value of the waveform at a specific time. Args: time (float): Absolute time. dt (float): Absolute time discretisation. Returns: waveform (float): Calculated value for waveform. """ # Coefficients for certain waveforms if self.type == 'gaussian' or self.type == 'gaussiandot' or self.type == 'gaussiandotdot': chi = 1 / self.freq zeta = 2 * np.pi**2 * self.freq**2 delay = time - chi elif self.type == 'gaussiandotnorm' or self.type == 'gaussiandotdotnorm' or self.type == 'ricker': chi = np.sqrt(2) / self.freq zeta = np.pi**2 * self.freq**2 delay = time - chi # Waveforms if self.type == 'gaussian': waveform = np.exp(-zeta * delay**2) elif self.type == 'gaussiandot': waveform = -2 * zeta * delay * np.exp(-zeta * delay**2) elif self.type == 'gaussiandotnorm': normalise = np.sqrt(np.exp(1) / (2 * zeta)) waveform = -2 * zeta * delay * np.exp(-zeta * delay**2) * normalise elif self.type == 'gaussiandotdot': waveform = 2 * zeta * (2 * zeta * delay**2 - 1) * np.exp( -zeta * delay**2) elif self.type == 'gaussiandotdotnorm': normalise = 1 / (2 * zeta) waveform = 2 * zeta * (2 * zeta * delay**2 - 1) * np.exp( -zeta * delay**2) * normalise elif self.type == 'ricker': normalise = 1 / (2 * zeta) waveform = -(2 * zeta * (2 * zeta * delay**2 - 1) * np.exp(-zeta * delay**2)) * normalise elif self.type == 'sine': waveform = np.sin(2 * np.pi * self.freq * time) if time * self.freq > 1: waveform = 0 elif self.type == 'contsine': rampamp = 0.25 ramp = rampamp * time * self.freq if ramp > 1: ramp = 1 waveform = ramp * np.sin(2 * np.pi * self.freq * time) elif self.type == 'impulse': # time < dt condition required to do impulsive magnetic dipole if time == 0 or time < dt: waveform = 1 elif time >= dt: waveform = 0 elif self.type == 'user': index = round_value(time / dt) # Check to see if there are still user specified values and if not use zero if index > len(self.uservalues) - 1: waveform = 0 else: waveform = self.uservalues[index] return waveform
def hertzian_dipole_fs(timewindow, dt, dxdydz, rx): """Analytical solution of a z-directed Hertzian dipole in free space with a Gaussian current waveform (http://dx.doi.org/10.1016/0021-9991(83)90103-1). Args: timewindow (float): Length of time window (seconds). dt (float): Time step (seconds). dxdydz (float): Tuple of spatial resolution (metres). rx (float): Tuple of coordinates of receiver position relative to transmitter position (metres). Returns: fields (float): Array contain electric and magnetic field components. """ # Waveform w = Waveform() w.type = 'gaussiandot' w.amp = 1 w.freq = 1e9 # Waveform integral wint = Waveform() wint.type = 'gaussian' wint.amp = w.amp wint.freq = w.freq # Waveform first derivative wdot = Waveform() wdot.type = 'gaussiandotdot' wdot.amp = w.amp wdot.freq = w.freq # Time iterations = round_value(timewindow / dt) time = np.linspace(0, timewindow, iterations) # Spatial resolution dx = dxdydz[0] dy = dxdydz[1] dz = dxdydz[2] # Coordinates of Rx relative to Tx x = rx[0] y = rx[1] z = rx[2] if z == 0: sign_z = 1 else: sign_z = np.sign(z) # Coordinates of Rx for Ex FDTD component Ex_x = x + 0.5 * dx Ex_y = y Ex_z = z - 0.5 * dz Er_x = np.sqrt((Ex_x**2 + Ex_y**2 + Ex_z**2)) tau_Ex = Er_x / c # Coordinates of Rx for Ey FDTD component Ey_x = x Ey_y = y + 0.5 * dy Ey_z = z - 0.5 * dz Er_y = np.sqrt((Ey_x**2 + Ey_y**2 + Ey_z**2)) tau_Ey = Er_y / c # Coordinates of Rx for Ez FDTD component Ez_x = x Ez_y = y Ez_z = z Er_z = np.sqrt((Ez_x**2 + Ez_y**2 + Ez_z**2)) tau_Ez = Er_z / c # Coordinates of Rx for Hx FDTD component Hx_x = x Hx_y = y + 0.5 * dy Hx_z = z Hr_x = np.sqrt((Hx_x**2 + Hx_y**2 + Hx_z**2)) tau_Hx = Hr_x / c # Coordinates of Rx for Hy FDTD component Hy_x = x + 0.5 * dx Hy_y = y Hy_z = z Hr_y = np.sqrt((Hy_x**2 + Hy_y**2 + Hy_z**2)) tau_Hy = Hr_y / c # Coordinates of Rx for Hz FDTD component Hz_x = x + 0.5 * dx Hz_y = y + 0.5 * dy Hz_z = z - 0.5 * dz Hr_z = np.sqrt((Hz_x**2 + Hz_y**2 + Hz_z**2)) tau_Hz = Hr_z / c # Initialise fields fields = np.zeros((iterations, 6)) # Calculate fields for timestep in range(iterations): # Calculate values for waveform fint_Ex = wint.calculate_value((timestep * dt) - tau_Ex, dt) * dx f_Ex = w.calculate_value((timestep * dt) - tau_Ex, dt) * dx fdot_Ex = wdot.calculate_value((timestep * dt) - tau_Ex, dt) * dx fint_Ey = wint.calculate_value((timestep * dt) - tau_Ey, dt) * dy f_Ey= w.calculate_value((timestep * dt) - tau_Ey, dt) * dy fdot_Ey = wdot.calculate_value((timestep * dt) - tau_Ey, dt) * dy fint_Ez = wint.calculate_value((timestep * dt) - tau_Ez, dt) * dz f_Ez = w.calculate_value((timestep * dt) - tau_Ez, dt) * dz fdot_Ez = wdot.calculate_value((timestep * dt) - tau_Ez, dt) * dz fint_Hx = wint.calculate_value((timestep * dt) - tau_Hx, dt) * dx f_Hx = w.calculate_value((timestep * dt) - tau_Hx, dt) * dx fdot_Hx = wdot.calculate_value((timestep * dt) - tau_Hx, dt) * dx fint_Hy = wint.calculate_value((timestep * dt) - tau_Hy, dt) * dy f_Hy= w.calculate_value((timestep * dt) - tau_Hy, dt) * dy fdot_Hy = wdot.calculate_value((timestep * dt) - tau_Hy, dt) * dy fint_Hz = wint.calculate_value((timestep * dt) - tau_Hz, dt) * dz f_Hz = w.calculate_value((timestep * dt) - tau_Hz, dt) * dz fdot_Hz = wdot.calculate_value((timestep * dt) - tau_Hz, dt) * dz # Ex fields[timestep, 0] = ((Ex_x * Ex_z) / (4 * np.pi * e0 * Er_x**5)) * (3 * (fint_Ex + (tau_Ex * f_Ex)) + (tau_Ex**2 * fdot_Ex)) # Ey try: tmp = Ey_y / Ey_x except ZeroDivisionError: tmp = 0 fields[timestep, 1] = tmp * ((Ey_x * Ey_z) / (4 * np.pi * e0 * Er_y**5)) * (3 * (fint_Ey + (tau_Ey * f_Ey)) + (tau_Ey**2 * fdot_Ey)) # Ez fields[timestep, 2] = (1 / (4 * np.pi * e0 * Er_z**5)) * ((2 * Ez_z**2 - (Ez_x**2 + Ez_y**2)) * (fint_Ez + (tau_Ez * f_Ez)) - (Ez_x**2 + Ez_y**2) * tau_Ez**2 * fdot_Ez) # Hx fields[timestep, 3] = - (Hx_y / (4 * np.pi * Hr_x**3)) * (f_Hx + (tau_Hx * fdot_Hx)) # Hy try: tmp = Hy_x / Hy_y except ZeroDivisionError: tmp = 0 fields[timestep, 4] = - tmp * (- (Hy_y / (4 * np.pi * Hr_y**3)) * (f_Hy + (tau_Hy * fdot_Hy))) # Hz fields[timestep, 5] = 0 return fields