def get_ppt(self,ppt_nx,ppt_ny,ppt_dz,zone_array,ppt_name_root='pt',param_name_root='k_',ipar_init=1): '''Generates a dataframe of pilot point locations. Note that grid_util.globalxy_to_localxy uses the northwest corner of the model grid as the origin. However, the PEST pilot points file assumes the SW corner of the model grid as the origin (i.e., it prints coordinates as northings and eastings.)''' # Generate the pilot point grid in the row/col plane x_vector = np.linspace(0,self.delr * self.ncol,ppt_nx+1)[:-1] x_vector = np.add(x_vector,0.5*(x_vector[1]-x_vector[0])) y_vector = np.linspace(0,self.delc * self.nrow,ppt_ny+1)[:-1] y_vector = np.add(y_vector,0.5*(y_vector[1]-y_vector[0])) ppt_df = pd.DataFrame(columns=['PPT_Name','Param_Name','Easting','Northing','Elevation','Zone','Value']) icount = 0 for ix,jy in product(x_vector,y_vector): # row and column returned from this function in PYTHON indexing irow,icol,_,_ = gu.modelxy_to_rowcol(ix,(self.nrow*self.delc - jy),self.delr,self.delc) # Generate the vertical dimension of the pilot point grid # This algorithm assumes that for any (row,col), no active cells are found # beneath any inactive cells. z_0 = self.midpoints[irow,icol][0] z_vector = np.arange(z_0,z_0 - np.sum(self.layer_depths),-(abs(ppt_dz))) # The PEST 10-character limit precludes # writing row,col, and layer identifiers into the pilot point name. # As an alternative, I've simply labeled them A,B,C, etc., starting # with the top pilot point in a given (row,col) for iz,iletter in zip(z_vector,string.uppercase[:(len(z_vector))]): ilay,_ = gu.globalz_to_localz(irow,icol,iz,self.midpoints,self.bottoms,self.layer_depths) if (gu.check_if_active(irow,icol,ilay,self.ibound) == True): izone = zone_array[irow-1,icol-1,ilay-1] ppt_name = ppt_name_root + str(irow) + '_' + str(icol) + iletter param_name = param_name_root + str(irow) + '_' + str(icol) + iletter ppt_df.loc[icount,:] = [ppt_name,param_name,ix,jy,iz,izone,ipar_init] icount += 1 return ppt_df
def get_ppt(self, ppt_nx, ppt_ny, ppt_dz, zone_array, ppt_name_root='pt', param_name_root='k_', ipar_init=1): '''Generates a dataframe of pilot point locations. Note that grid_util.globalxy_to_localxy uses the northwest corner of the model grid as the origin. However, the PEST pilot points file assumes the SW corner of the model grid as the origin (i.e., it prints coordinates as northings and eastings.)''' # Generate the pilot point grid in the row/col plane x_vector = np.linspace(0, self.delr * self.ncol, ppt_nx + 1)[:-1] x_vector = np.add(x_vector, 0.5 * (x_vector[1] - x_vector[0])) y_vector = np.linspace(0, self.delc * self.nrow, ppt_ny + 1)[:-1] y_vector = np.add(y_vector, 0.5 * (y_vector[1] - y_vector[0])) ppt_df = pd.DataFrame(columns=[ 'PPT_Name', 'Param_Name', 'Easting', 'Northing', 'Elevation', 'Zone', 'Value' ]) icount = 0 for ix, jy in product(x_vector, y_vector): # row and column returned from this function in PYTHON indexing irow, icol, _, _ = gu.modelxy_to_rowcol( ix, (self.nrow * self.delc - jy), self.delr, self.delc) # Generate the vertical dimension of the pilot point grid # This algorithm assumes that for any (row,col), no active cells are found # beneath any inactive cells. z_0 = self.midpoints[irow, icol][0] z_vector = np.arange(z_0, z_0 - np.sum(self.layer_depths), -(abs(ppt_dz))) # The PEST 10-character limit precludes # writing row,col, and layer identifiers into the pilot point name. # As an alternative, I've simply labeled them A,B,C, etc., starting # with the top pilot point in a given (row,col) for iz, iletter in zip(z_vector, string.uppercase[:(len(z_vector))]): ilay, _ = gu.globalz_to_localz(irow, icol, iz, self.midpoints, self.bottoms, self.layer_depths) if (gu.check_if_active(irow, icol, ilay, self.ibound) == True): izone = zone_array[irow - 1, icol - 1, ilay - 1] ppt_name = ppt_name_root + str(irow) + '_' + str( icol) + iletter param_name = param_name_root + str(irow) + '_' + str( icol) + iletter ppt_df.loc[icount, :] = [ ppt_name, param_name, ix, jy, iz, izone, ipar_init ] icount += 1 return ppt_df
def get_well_startloc(start_df,top=None,bottoms=None,midpoints=None,min_particle_separation=1,\ reg_cylinder=None,particles_per_cell=None,delr=None,delc=None,lay_thick=None): '''Returns a dictionary of particle starting locations for input to MODPATH package writing functions. Removes duplicate station LOCATIONS from the dataframe that may include multiple observations at a particular station. If the specs for a registration volume are provided, distributes particles throughout the registration volume. If no registration volume is provided, distributes particles in well screen. (NOTE: Function currently only allows generation of a registration CYLINDER.) Returns PYTHON indexing.''' nlay, nrow, ncol = np.shape(bottoms) start_df = start_df.reset_index() start_df = start_df.drop_duplicates('index').set_index('index') start_df.index.names = ['station_nm'] start_df = start_df.drop(['ObsName', 'TobDate'], axis=1) # If the dimensions of a registration cylinder are provided, generate a # vogel disc in order to determine the distance between points. This will # be used to assign the number of vertical increments to the registration # cylinder. Otherwise (i.e., for cases in which particles are only generated # from the well screen), use the min_particle_separation parameter to vertically # discretize the well screen. if (reg_cylinder is not None): iradius, dz_top, dz_bottom = reg_cylinder['Radius'], reg_cylinder[ 'Top'], reg_cylinder['Bottom'] particles_per_disc = get_disc_particles(iradius, particles_per_cell, delr, delc) points_xy = vogel_disc((0, 0), particles_per_disc, iradius) distance_between_points = np.max( [dist_bt_points(points_xy), min_particle_separation]) else: distance_between_points = min_particle_separation dz_top, dz_bottom = 0, 0 # Loop through all the observation locations particle_start_dict = dict() for iname, irow in start_df.iterrows(): print 'Generating particle starting locations for %s.' % (iname) # Read the well location from the dataframe x0, y0, ztop, zbottom = irow['ModelX'], irow['ModelY'], irow[ 'GlobZtop'], irow['GlobZbot'] # Update the upper and lower bounds on the starting particle locations ztop = ztop + dz_top zbottom = zbottom - dz_bottom nz_increments = int(np.ceil( (ztop - zbottom) / distance_between_points)) # Distribute the particles throughout the well screen or the associated # registration volume. 'vert_particle_line' is a vector of elevations # at which the particles are set if (reg_cylinder is not None): if (nrow == 1 ): # For 2D case generate a plane rather than a cylinder points_xy, vert_particle_line = points_on_plane( (x0, y0), iradius, ztop, zbottom, particles_per_cell, delc, lay_thick) else: points_xy = vogel_disc((x0, y0), particles_per_disc, iradius) vert_particle_line = np.linspace(zbottom, ztop, num=nz_increments) else: # Only generate particles within the well screen points_xy = [(x0, y0)] vert_particle_line = np.linspace(zbottom, ztop, num=nz_increments) # Convert the global elevations to layer number with localz for ivert in vert_particle_line: for ipoint in points_xy: xx, yy = ipoint[0], ipoint[1] irow, jcol, local_x, local_y = gu.modelxy_to_localxy( xx, yy, delr, delc, nrow, ncol) # Do not place particles above the model top or below the model bottom if (ivert > top[irow, jcol]) or (ivert < bottoms[-1, irow, jcol]): break ilay, local_z = gu.globalz_to_localz(irow, jcol, ivert, midpoints, bottoms, lay_thick) if iname not in particle_start_dict: particle_start_dict[iname] = [(ilay, irow, jcol, local_x, local_y, local_z)] else: particle_start_dict[iname].append( (ilay, irow, jcol, local_x, local_y, local_z)) return particle_start_dict
def get_well_startloc( start_df, top=None, bottoms=None, midpoints=None, min_particle_separation=1, reg_cylinder=None, particles_per_cell=None, delr=None, delc=None, lay_thick=None, ): """Returns a dictionary of particle starting locations for input to MODPATH package writing functions. Removes duplicate station LOCATIONS from the dataframe that may include multiple observations at a particular station. If the specs for a registration volume are provided, distributes particles throughout the registration volume. If no registration volume is provided, distributes particles in well screen. (NOTE: Function currently only allows generation of a registration CYLINDER.) Returns PYTHON indexing.""" nlay, nrow, ncol = np.shape(bottoms) start_df = start_df.reset_index() start_df = start_df.drop_duplicates("index").set_index("index") start_df.index.names = ["station_nm"] start_df = start_df.drop(["ObsName", "TobDate"], axis=1) # If the dimensions of a registration cylinder are provided, generate a # vogel disc in order to determine the distance between points. This will # be used to assign the number of vertical increments to the registration # cylinder. Otherwise (i.e., for cases in which particles are only generated # from the well screen), use the min_particle_separation parameter to vertically # discretize the well screen. if reg_cylinder is not None: iradius, dz_top, dz_bottom = reg_cylinder["Radius"], reg_cylinder["Top"], reg_cylinder["Bottom"] particles_per_disc = get_disc_particles(iradius, particles_per_cell, delr, delc) points_xy = vogel_disc((0, 0), particles_per_disc, iradius) distance_between_points = np.max([dist_bt_points(points_xy), min_particle_separation]) else: distance_between_points = min_particle_separation dz_top, dz_bottom = 0, 0 # Loop through all the observation locations particle_start_dict = dict() for iname, irow in start_df.iterrows(): print "Generating particle starting locations for %s." % (iname) # Read the well location from the dataframe x0, y0, ztop, zbottom = irow["ModelX"], irow["ModelY"], irow["GlobZtop"], irow["GlobZbot"] # Update the upper and lower bounds on the starting particle locations ztop = ztop + dz_top zbottom = zbottom - dz_bottom nz_increments = int(np.ceil((ztop - zbottom) / distance_between_points)) # Distribute the particles throughout the well screen or the associated # registration volume. 'vert_particle_line' is a vector of elevations # at which the particles are set if reg_cylinder is not None: if nrow == 1: # For 2D case generate a plane rather than a cylinder points_xy, vert_particle_line = points_on_plane( (x0, y0), iradius, ztop, zbottom, particles_per_cell, delc, lay_thick ) else: points_xy = vogel_disc((x0, y0), particles_per_disc, iradius) vert_particle_line = np.linspace(zbottom, ztop, num=nz_increments) else: # Only generate particles within the well screen points_xy = [(x0, y0)] vert_particle_line = np.linspace(zbottom, ztop, num=nz_increments) # Convert the global elevations to layer number with localz for ivert in vert_particle_line: for ipoint in points_xy: xx, yy = ipoint[0], ipoint[1] irow, jcol, local_x, local_y = gu.modelxy_to_localxy(xx, yy, delr, delc, nrow, ncol) # Do not place particles above the model top or below the model bottom if (ivert > top[irow, jcol]) or (ivert < bottoms[-1, irow, jcol]): break ilay, local_z = gu.globalz_to_localz(irow, jcol, ivert, midpoints, bottoms, lay_thick) if iname not in particle_start_dict: particle_start_dict[iname] = [(ilay, irow, jcol, local_x, local_y, local_z)] else: particle_start_dict[iname].append((ilay, irow, jcol, local_x, local_y, local_z)) return particle_start_dict