def create_outputs(model, realization_dir, runname, formats): """ This functions creates output files based on the model output in the current run directory. This calls mainly the functions defined below for the different datatypes. Parameters ---------- model : Model instance (see model.py) The model object holding all the data arrays realization_dir : str Directory where the current realization results should be stored. runname : str Name of the model run formats : list of str List of output formats """ # This dictionary links the output format names from the ini-file with output # functions and file endings. # If you want to add an output format, add a function and add the description here. output_desc = { 'mat':to_mat, 'py':to_pickle, 'npz':to_npz, 'h5':to_hdf5, 'vtr':to_vtr, 'mf':to_modflow, 'mf6':to_mf6, 'hgs':to_hgs, } for fmt in formats: if fmt not in output_desc: raise ValueError('No such output format: ' + fmt) fname = pathlib.Path(realization_dir) / runname try: output_desc[fmt](model, str(fname.resolve())) except ImportError as e: # in case of an import error, only warn the user instead of raising # an error warnings.warn(str(e)) print_to_stdout('Saved', fmt, 'output to', realization_dir)
def parameters(inifile): """ Parses the inifile and returns all sections as dictionaries. Furthermore it sets to correct directory names for the run settings. Parameters ---------- inifile : path Path to inifile. If ``inifile`` is set to 0, the ini-file for the MADE test case is used. Returns ------- run : dict HyVR run settings (number of simulations, where to store results) model_dict : dict model setup parameters strata_dict : dict strata setup parameters hydraulics : dict Parsed hydraulics section of the ini-file flowtrans : dict Flow and transport settings for output elements : list of dicts List of dictionaries of settings for :class:`hyvr.model.ae_types.AEType`. """ print_to_stdout("Reading parameter file") # test case if inifile == 0: from pkg_resources import resource_filename inifile = resource_filename(__name__, str(pathlib.Path('../made.ini'))) # read file p = cp.ConfigParser() try: p.read(inifile, encoding='utf-8') except cp.MissingSectionHeaderError: # this is probably caused by a wrong encoding p.read(inifile, encoding='utf-8-sig') if len(p.sections()) == 0: raise FileNotFoundError( "Parameter file {:s} not found!".format(inifile)) old_format = 'dataoutputs' in dict(p['run']) if old_format: run, model, strata, hydraulics, flowtrans, elements = get_new_parameters_from_deprecated( *parse_deprecated_inifile(p)) else: run, model, strata, hydraulics, flowtrans, elements = parse_inifile(p) # Runname and Modeldir # ==================== # The following code sets # - runname: based on this, the output directory is named # - modeldir: the directory where to create the output directory # - rundir: the output directory # if runname is not given, it's the part of the ini-file before # "_parameters.ini" or ".ini" # in the test case runname is given ini_path = pathlib.Path(inifile).parent.resolve() ini_name = pathlib.Path(inifile).name if run['runname'] is None: if ini_name.endswith("autogenerated_backup.ini"): run['runname'] = ini_name[0:-25] else: run['runname'] = ".".join(ini_name.split('.')[0:-1]) # separate task: find modeldir # modeldir is either the given option or the path of the .ini file # rundir is modeldir/runname # But: # If the .ini-filename ends with "autogenerated_backup.ini", the directory of the # ini-file is the rundir and modeldir is the directory above. # This should overwrite all other settings (except overwrite_old_output) if ini_name.endswith("_autogenerated_backup.ini"): run['modeldir'] = ini_path.parent.resolve() run['rundir'] = ini_path else: # either inipath or the chosen modeldir if run['modeldir'] is None: run['modeldir'] = ini_path if run['modeldir'] == 'select': directory = input( 'Please input the model directory save path, or press <enter> to' 'save in default directory:\n') if len(run['modeldir']) == 0: run['modeldir'] = inipath else: run['modeldir'] = pathlib.Path(directory).resolve() run['rundir'] = run['modeldir'] / run['runname'] return run, model, strata, hydraulics, flowtrans, elements
def generate_model(self): """ Generates the model. This function generates the full model by creating all strata, the AEs within the strata and the geometrical objects within these. The method starts from the domain top and calls the strata generation function for each stratum, which handles the generation of architectural elements. """ hu.print_to_stdout('Generating model...') strata_dict = self.strata_dict self.strata = [] self.n_strata = len(strata_dict['strata']) # These are the additional parameters that a stratum needs param_names = [ 'bg_facies', 'bg_azim', 'bg_dip', 'ae_prob', 'ae_z_mean', 'avul', 'avul_prob', 'strata', ] top_surface = ContactSurface(self.grid, mode='flat', z=self.grid.lz) for si in range(self.n_strata): # create top surface if si == self.n_strata - 1: bottom_surface = ContactSurface(self.grid, mode='flat', z=self.grid.z0) else: bottom_surface = ContactSurface(self.grid, **strata_dict['contact_models'][-1-si]) # if the bottom surface is above the top surface, we will assign # the top surface values instead bottom_surface.use_lower_surface_value(top_surface) stratum_params = {name:strata_dict[name][-1-si] for name in param_names} ae_types = [self.ae_types[ae_type] for ae_type in strata_dict['ae_in_strata'][-1-si]] stratum = Stratum(bottom_surface, top_surface, stratum_params, ae_types, self.grid, ) self.strata.append(stratum) # use old top as bottom top_surface = bottom_surface # stratum boundaries as arrays self.strata_zmins = np.array([stratum.zmin for stratum in self.strata]) self.strata_zmaxs = np.array([stratum.zmax for stratum in self.strata]) # enumerate all objects and AEs num_ae = 0 num_ha = 0 for stratum in self.strata: for ae in stratum.aes: ae.num = num_ae num_ae += 1 for obj in ae.object_list: obj.num_ha = num_ha num_ha += 1
def generate_hydraulic_parameters(self, hydraulics): """ Generates (heterogeneous) hydraulic parameters Parameters ---------- hydraulics : dictionary parsed hydraulics section of the ini-file """ hu.print_to_stdout('Generating hydraulic parameters') het = self.generate_heterogeneity ani = self.generate_anisotropy ha_arr = self.data['ha'] fac = self.data['facies'] ae_arr = self.data['ae'] azim = self.data['azim'] dip = self.data['dip'] # Initialise storage arrays: # hydraulic conductivity self.data['k_iso'] = np.zeros((self.grid.nx, self.grid.ny, self.grid.nz), dtype=np.float) # porosity self.data['poros' ]= np.zeros((self.grid.nx, self.grid.ny, self.grid.nz), dtype=np.float) # anisotropy ratio self.data['anirat' ]= np.ones((self.grid.nx, self.grid.ny, self.grid.nz), dtype=np.float) k_iso = self.data['k_iso'] poros = self.data['poros'] anirat = self.data['anirat'] if het: # Heterogeneous case for mi in np.unique(ha_arr): for fi in np.unique(fac[ha_arr == mi]): mifi = (ha_arr == mi) & (fac == fi) # Get mask for relevant values if self.heterogeneity_level == 'internal': # Generate internal heterogeneity - hydraulic conductivity k_flat = hu.specsim(self.grid, hydraulics['sig_y'][fi], hydraulics['ycorlengths'][fi], covmod='exp', selection_mask=mifi) k_flat = np.exp(k_flat) * hydraulics['k_h'][fi] k_iso[mifi] = k_flat # Generate internal heterogeneity - porosity n_flat = hu.specsim(self.grid, hydraulics['sig_n'][fi], hydraulics['ncorlengths'][fi], covmod='exp', selection_mask=mifi) + hydraulics['n'][fi] poros[mifi] = n_flat elif self.heterogeneity_level == 'facies': # Assign heterogeneity at facies level only k_iso[mifi] = hydraulics['k_h'][fi] poros[mifi] = hydraulics['n'][fi] # Assign anisotropy ratio anirat[mifi] = hydraulics['k_ratio'][fi] """ Assign background heterogeneity per architectural element """ if self.heterogeneity_level == 'internal': # for si, aei in enumerate(ae_lu): for stratum in self.strata: for ae in stratum.aes: m0 = ha_arr == 0 ms = ae_arr == ae.num ae_mask = m0 & ms # Get material that equals zero within in architectural element if np.all(ae_mask == False): continue ae_bg_facies = ae.bg_facies # Assign background material k_ae_flat = hu.specsim(self.grid, hydraulics['sig_y'][ae_bg_facies], hydraulics['ycorlengths'][ae_bg_facies], covmod='exp', selection_mask=ae_mask) k_ae_flat = np.exp(k_ae_flat) * hydraulics['k_h'][ae_bg_facies] # back-transform from log space k_iso[ae_mask] = k_ae_flat # Generate internal heterogeneity - porosity n_flat = hu.specsim(self.grid, hydraulics['sig_n'][ae_bg_facies], hydraulics['ncorlengths'][ae_bg_facies], covmod='exp', selection_mask=ae_mask) n_flat = n_flat + hydraulics['n'][ae_bg_facies] poros[ae_mask] = n_flat # Assign background anisotropy ratio anirat[ae_mask] = hydraulics['k_ratio'][ae_bg_facies] # Assign architectural element trends if ae.type_params['k_ztrend'] is not None: # Vertical trend zf_vec = np.linspace(*ae.type_params['k_ztrend'], self.grid.nz) # Z factor at each elevation k_iso *= zf_vec if ae.type_params['k_xtrend'] is not None: xf_vec = np.linspace(*ae.type_params['k_xtrend'], self.grid.nx) k_iso = np.transpose(k_iso.transpose() * xf_vec) """ Assign trends to hydraulic parameters globally """ if hydraulics['k_ztrend'] is not None: zf_vec = np.linspace(*hydraulics['k_ztrend'], self.grid.nz) # Z factor at each elevation k_iso *= zf_vec if hydraulics['k_xtrend'] is not None: xf_vec = np.linspace(*hydraulics['k_xtrend'], self.grid.nx) k_iso = np.transpose(k_iso.transpose() * xf_vec) if hydraulics['n_ztrend'] is not None: zf_vec = np.linspace(*hydraulics['n_ztrend'], self.grid.nz) # Z factor at each elevation poros *= zf_vec if hydraulics['n_xtrend'] is not None: xf_vec = np.linspace(*hydraulics['n_xtrend'], self.grid.nx) poros = np.transpose(poros.transpose() * xf_vec) else: # Homogeneous case for hy_idx, hyi in enumerate(hydraulics['hydro']): hyi = hy_idx k_iso[fac == hyi] = hydraulics['k_h'][hy_idx] poros[fac == hyi] = hydraulics['n'][hy_idx] anirat[fac == hyi] = hydraulics['k_ratio'][hy_idx] # Assignment of anisotropy self.data['ktensors'] = np.zeros((self.grid.nx, self.grid.ny, self.grid.nz, 3, 3), dtype=np.float) ho.set_anisotropic_ktensor(self.data['ktensors'], k_iso, azim*np.pi/180, dip*np.pi/180, anirat)
def __init__(self, bottom_surface, top_surface, params, ae_types, grid): """ Stratum constructor. This generates the stratum by generating the AEs within. Parameters ---------- bottom_surface : ContactSurface object The bottom surface of the stratum top_surface : ContactSurface object The top surface of the stratum params : dict Dictionary with other necessary parameters ae_types : list of AEType objects The possible AE types within this stratum grid : Grid object """ self.bottom_surface = bottom_surface self.top_surface = top_surface self.params = params self.name = params['strata'] self.ae_types = ae_types self.ae_type_names = [ae_type.name for ae_type in ae_types] self.bg_facies = params['bg_facies'] self.bg_azim = params['bg_azim'] self.bg_dip = params['bg_dip'] # generate AEs self.zmin = self.bottom_surface.zmin self.zmax = self.top_surface.zmax self.ae_zmins = [] self.ae_zmaxs = [] self.aes = [] # first top is top of stratum ae_top_surface = self.top_surface # choose type of first AE curr_ae_type = prob_choose(self.ae_types, self.params['ae_prob']) znow = self.top_surface.zmean while znow > self.bottom_surface.zmean: num_type = self.ae_type_names.index(curr_ae_type.name) hu.print_to_stdout('Generating', curr_ae_type.name, 'from {:2.3f} m'.format(znow)) # find height of AE mean_height = self.params['ae_z_mean'][num_type] # TODO: It might be nice to specify the height variance in the inifile height = np.random.normal(mean_height, mean_height * 0.1) # potential avulsion if np.random.uniform() <= self.params['avul_prob'][0]: dz = -np.random.uniform(*self.params['avul']) else: dz = 0 znow -= height + dz # create bottom contact surface # ----------------------------- # the type of the bottom surface is determined by the type of the # AE below, if it is not the lowest AE next_ae_type = prob_choose(self.ae_types, self.params['ae_prob']) if znow <= self.bottom_surface.zmean: # limit top surface of uppermost AE to top surface of stratum znow = self.bottom_surface.zmean ae_bottom_surface = self.bottom_surface else: bottom_surface_params = next_ae_type.params['contact_model'] bottom_surface_params['z'] = znow ae_bottom_surface = ContactSurface(grid, **bottom_surface_params) # if the bottom surface is above the top surface, we will assign # the (lower) top surface values instead use_lower_surface_value(ae_bottom_surface, ae_top_surface) # close to the bottom it might also be possible that the ae_bottom # is below the stratum bottom if the stratum bottom is not flat # in this case we will assign the (higher) bottom surface value instead use_higher_surface_value(ae_bottom_surface, self.bottom_surface) # generate element: this will place all the objects within an AE element = curr_ae_type.generate_realization( ae_bottom_surface, ae_top_surface, self, grid) self.aes.append(element) # if the minimum/maximum depth changed, update it ae_zmin = element.zmin if ae_zmin < self.zmin: self.zmin = ae_zmin self.ae_zmins.append(ae_zmin) ae_zmax = element.zmax if ae_zmax > self.zmax: self.zmax = ae_zmax self.ae_zmaxs.append(ae_zmax) # current bottom is top of next, next ae is new current ae ae_top_surface = ae_bottom_surface curr_ae_type = next_ae_type self.n_ae = len(self.aes)
def parameters(inifile): """ Get parameters for hierarchical facies modelling from a .ini-file Parameters: inifile (str): Parameter file path Returns: - run *(dict)* - Model run parameters - model *(dict)* - Model domain parameters - sequences *(dict)* - Sequence parameters - hydraulics *(dict)* - Hydraulic properties parameters - flowtrans *(dict)* - Flow & transport simulation parameters - elements *(dict)* - Architectural elements and parameters """ print_to_stdout("Reading parameter file") # test case if inifile == 0: from pkg_resources import resource_filename inifile = resource_filename(__name__, os.path.join(os.pardir, 'made.ini')) # read file p = cp.ConfigParser() try: p.read(inifile, encoding='utf-8') except cp.MissingSectionHeaderError: # this is probably caused by a wrong encoding p.read(inifile, encoding='utf-8-sig') if len(p.sections()) == 0: raise FileNotFoundError( "Parameter file {:s} not found!".format(inifile)) old_format = 'dataoutputs' in dict(p['run']) if old_format: run, model, strata, hydraulics, flowtrans, elements = get_new_parameters_from_deprecated( *parse_deprecated_inifile(p)) else: run, model, strata, hydraulics, flowtrans, elements = parse_inifile(p) # Runname and Modeldir # ==================== # if runname is not given, it's the part of the ini-file before # "_parameters.ini" or ".ini" # in the test case runname is given ini_path = os.path.dirname(os.path.abspath(inifile)) ini_name = os.path.basename(os.path.abspath(inifile)) if run['runname'] is None: if ini_name[-25:] == "autogenerated_backup.ini": run['runname'] = ini_name[0:-25] else: run['runname'] = ini_name[0:-4] # separate task: find modeldir # modeldir is either the given option or the path of the .ini file # rundir is modeldir/runname # But: # If the .ini-filename ends with "autogenerated_backup.ini", the directory of the # ini-file is the rundir and modeldir is the directory above. # This should overwrite all other settings (except overwrite_old_output) if ini_name[-25:] == "_autogenerated_backup.ini": run['modeldir'] = os.path.abspath(os.path.join(ini_path, os.pardir)) run['rundir'] = os.path.abspath(ini_path) else: # either inipath or the chosen modeldir if run['modeldir'] is None: run['modeldir'] = ini_path if run['modeldir'] == 'select': run['modeldir'] = input( 'Please input the model directory save path, or press <enter> to' 'save in default directory:\n') if len(run['modeldir']) == 0: run['modeldir'] = inipath run['modeldir'] = os.path.abspath(run['modeldir']) run['rundir'] = os.path.join(run['modeldir'], run['runname']) return run, model, strata, hydraulics, flowtrans, elements
def gen_extpar(channel_type, model, ae, count, ani=True): """ Generate extruded parabola geometries: * Flow regime is assumed to be reasonably constant so the major geometry of the extruded parabolas doesn't change so much * 'Migration' of the extruded parabolas according to a shift vector Parameters ---------- extpar_type : ExtrudedParabolaType model : Model object ae : AERealization object ae : list AE parameters TODO: use AERealization instead count : int Material number and/or identifier ani : bool, optional (default: True) Whether anisotropy should be generated Returns ------- count : int Material number and/or identifier """ # This is just for convenience ae_arr = model.data['ae'] ha_arr = model.data['ha'] hat_arr = model.data['hat'] fac = model.data['fac'] azim = model.data['azim'] dip = model.data['dip'] x2 = model.X[:, :, 0] y2 = model.Y[:, :, 0] # TODO: replace z3 with model.Z _, _, z3 = np.meshgrid(range(0, model.nx), range(0, model.ny), range(0, model.nz), indexing='ij') # 2-D grid # start location total_extpar = int(channel_type.channel_no) if model.display: # Place troughs in domain centre for display features xstart = (model.x0 + model.lx) / 2 * np.ones(total_extpar) ystart = np.random.uniform(model.y0, model.y0 + model.ly, total_extpar) else: # Randomly place curve starting points # TODO: this was the original setting for xstart, why was this hard-coded? # xstart = np.random.uniform(-10, 0, total_extpar) xstart = np.random.uniform(model.x0, model.xmax, total_extpar) ystart = np.random.uniform(model.y0, model.ymax, total_extpar) # loop over extruded parabola top depths ch_bot = ae.z_bottom ch_bot += channel_type.depth * channel_type.buffer ch_top = ae.z_top ch_dz = channel_type.agg ch_top += ch_dz for znow in np.arange(ch_bot, ch_top, ch_dz): hu.print_to_stdout('z = {:2.4f}m'.format(znow)) # Assign linear trend to extruded parabola sizes if channel_type.geo_ztrend is not None: zfactor = np.interp( znow, [model.z0, model.z0 + model.lz], [channel_type.geo_ztrend[0], channel_type.geo_ztrend[1]]) else: zfactor = 1 z_ch_width = channel_type.width * zfactor z_ch_depth = channel_type.depth * zfactor if channel_type.channel_start is not None: cstart = channel_type.channel_start else: cstart = [] # Loop over total extruded parabolas per system for chan in range(0, total_extpar): """ Loop over multiple extruded parabolas at 'timestep' """ aha = ferguson_curve(model, channel_type.h, channel_type.k, channel_type.ds, channel_type.eps_factor, disp=model.display, ch_start=cstart) """ Get flow direction in extruded parabolas for azimuth """ # For periodicity shift trajectories into model unit cell if model.periodic: aha[aha[:, 1] < yvec[0], 1] += model.ly aha[aha[:, 1] > yvec[-1], 1] -= model.ly # initialize 2-D distance matrix D = 1e20 * np.ones_like(x2) # initialize sum of inverse-distance weights sumW = np.zeros(np.shape(x2), dtype=float) W = np.zeros(np.shape(x2), dtype=float) # initialize velocity orientation at this level vx_znow = np.zeros(np.shape(x2), dtype=float) vy_znow = np.zeros(np.shape(x2), dtype=float) # loop over all points of trajectory for ii in range(0, len(aha)): # distance to current point R = np.sqrt((x2 - aha[ii][0])**2 + (y2 - aha[ii][1])**2) # smallest distance of entire grid to all points so far D[R < D] = R[R < D] # inverse-distance weight for velocity interpolation W[:] = 1e-20 W[R < z_ch_width / 2] = 1 / (R[R < z_ch_width / 2] + 1e-20) # velocity interpolation in 2-D vx_znow += aha[ii][2] * W vy_znow += aha[ii][3] * W sumW += W vx_znow /= sumW vy_znow /= sumW # Assign facies sets with dip values if sum(channel_type.dip) > 0: do, fd, dv, av = hu.dip_sets( model, channel_type, znow, curve=[aha[:, 0], aha[:, 1], vx_znow, vy_znow]) else: do = np.ones((model.nx, model.ny, model.nz)) + count fd = np.ones((model.nx, model.ny, model.nz), dtype=int) * int( np.random.choice(channel_type.facies)) dv = 0.0 av = np.zeros((model.nx, model.ny, model.nz)) """ Copy results into 3-D field """ # Iterate over all nodes below current top elevation znow_index = int(np.around( (znow - model.z0) / model.dz)) # TODO: fix around stuff zbelow_index = int( np.around((znow - z_ch_depth - model.z0) / model.dz)) d_range = np.arange(max(0, zbelow_index), min(model.nz, znow_index)) # Depth range if len( d_range ) > 0: # Only compute if extruded parabola depth range is finite # Get mask arrays for each condition in_extpar = D[:, :, None]**2 <= z_ch_width**2 / 4 - ( (znow_index - z3) * model.dz * z_ch_width / (z_ch_depth * 2))**2 # is grid cell in extruded parabola finite_v = ~np.isnan( vx_znow) # Only assign if velocity is finite below_top = ae_arr <= ae.num # Don't assign values to locations higher than top contact surface chan_mask = in_extpar * finite_v[:, :, None] * below_top if znow_index <= z3[:, :, -1].max(): # Set mask above top of extruded parabola to False chan_mask[:, :, znow_index:-1] = False # Assign properties fac[chan_mask] = fd[chan_mask] ha_arr[chan_mask] = count hat_arr[chan_mask] = channel_type.ae_id ae_arr[chan_mask] = ae.num if channel_type.lag is not None: in_lag = ( znow - z_ch_depth + float(channel_type.lag[0]) ) > z3 * model.dz # Is grid cell in extruded parabola fac[np.logical_and(in_extpar, in_lag)] = int(channel_type.lag[1]) if ani: # calcuate azimuth, to 1 degree azim2d = np.round( (np.arctan2(vx_znow, vy_znow) - np.pi / 2) * 180 / np.pi) azim3d = azim2d[:, :, None] * np.ones( (model.nx, model.ny, model.nz)) azim[chan_mask] = azim3d[chan_mask] dip[chan_mask] = dv count += 1 # Shift starting values with migration vector from parameter file if channel_type.mig: # TODO: fix this xstart += np.random.uniform(-channel_type.mig[0], channel_type.mig[0]) ystart += np.random.uniform(-channel_type.mig[1], channel_type.mig[1]) return count