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. """ # 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, 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 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 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)
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 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 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)