"""The patch module defines the classes necessary to describe finite-volumedata and the grid that it lives on.Typical usage:* create the grid:: grid = Grid2d(nx, ny)* create the data that lives on that grid:: data = CellCenterData2d(grid) bc = BC(xlb="reflect", xrb="reflect", ylb="outflow", yrb="outflow") data.register_var("density", bc) ... data.create()* initialize some data:: dens = data.get_var("density") dens[:, :] = ...* fill the ghost cells:: data.fill_BC("density")"""importh5pyimportnumpyasnpimportpyro.mesh.boundaryasbndfrompyro.mesh.array_indexerimportArrayIndexer,ArrayIndexerFCfrompyro.utilimportmsg
[docs]classGrid2d:""" the 2-d grid class. The grid object will contain the coordinate information (at various centerings). A basic (1-d) representation of the layout is:: | | | X | | | | X | | | +--*--+- // -+--*--X--*--+--*--+- // -+--*--+--*--X--*--+- // -+--*--+ 0 ng-1 ng ng+1 ... ng+nx-1 ng+nx 2ng+nx-1 ilo ihi |<- ng guardcells->|<---- nx interior zones ----->|<- ng guardcells->| The '*' marks the data locations. """# pylint: disable=too-many-instance-attributesdef__init__(self,nx,ny,*,ng=1,xmin=0.0,xmax=1.0,ymin=0.0,ymax=1.0):""" Create a Grid2d object. The only data that we require is the number of points that make up the mesh in each direction. Optionally we take the extrema of the domain (default is [0,1]x[0,1]) and number of ghost cells (default is 1). Note that the Grid2d object only defines the discretization, it does not know about the boundary conditions, as these can vary depending on the variable. Parameters ---------- nx : int Number of zones in the x-direction ny : int Number of zones in the y-direction ng : int, optional Number of ghost cells xmin : float, optional Physical coordinate at the lower x boundary xmax : float, optional Physical coordinate at the upper x boundary ymin : float, optional Physical coordinate at the lower y boundary ymax : float, optional Physical coordinate at the upper y boundary """# pylint: disable=too-many-arguments# size of gridself.nx=int(nx)self.ny=int(ny)self.ng=int(ng)self.qx=int(2*ng+nx)self.qy=int(2*ng+ny)# domain extremaself.xmin=xminself.xmax=xmaxself.ymin=yminself.ymax=ymax# compute the indices of the block interior (excluding guardcells)self.ilo=self.ngself.ihi=self.ng+self.nx-1self.jlo=self.ngself.jhi=self.ng+self.ny-1# center of the grid (for convenience)self.ic=self.ilo+self.nx//2-1self.jc=self.jlo+self.ny//2-1# define the coordinate information at the left, center, and right# zone coordinatesself.dx=(xmax-xmin)/nxself.xl=(np.arange(self.qx)-ng)*self.dx+xminself.xr=(np.arange(self.qx)+1.0-ng)*self.dx+xminself.x=0.5*(self.xl+self.xr)self.dy=(ymax-ymin)/nyself.yl=(np.arange(self.qy)-ng)*self.dy+yminself.yr=(np.arange(self.qy)+1.0-ng)*self.dy+yminself.y=0.5*(self.yl+self.yr)# 2-d versions of the zone coordinatesx2d,y2d=np.meshgrid(self.x,self.y,indexing='ij')self.x2d=ArrayIndexer(d=x2d,grid=self)self.y2d=ArrayIndexer(d=y2d,grid=self)xl2d,yl2d=np.meshgrid(self.xl,self.yl,indexing='ij')self.xl2d=ArrayIndexer(d=xl2d,grid=self)self.yl2d=ArrayIndexer(d=yl2d,grid=self)xr2d,yr2d=np.meshgrid(self.xr,self.yr,indexing='ij')self.xr2d=ArrayIndexer(d=xr2d,grid=self)self.yr2d=ArrayIndexer(d=yr2d,grid=self)
[docs]defscratch_array(self,*,nvar=1,dtype=np.float64):""" return a standard numpy array dimensioned to have the size and number of ghostcells as the parent grid """ifnvar==1:_tmp=np.zeros((self.qx,self.qy),dtype=dtype)else:_tmp=np.zeros((self.qx,self.qy,nvar),dtype=dtype)returnArrayIndexer(d=_tmp,grid=self)
[docs]defcoarse_like(self,N):""" return a new grid object coarsened by a factor n, but with all the other properties the same """returnGrid2d(self.nx//N,self.ny//N,ng=self.ng,xmin=self.xmin,xmax=self.xmax,ymin=self.ymin,ymax=self.ymax)
[docs]deffine_like(self,N):""" return a new grid object finer by a factor n, but with all the other properties the same """returnGrid2d(self.nx*N,self.ny*N,ng=self.ng,xmin=self.xmin,xmax=self.xmax,ymin=self.ymin,ymax=self.ymax)
def__str__(self):""" print out some basic information about the grid object """returnf"2-d grid: nx = {self.nx}, ny = {self.ny}, ng = {self.ng}"def__eq__(self,other):""" are two grids equivalent? """result=(self.nx==other.nxandself.ny==other.nyandself.ng==other.ngandself.xmin==other.xminandself.xmax==other.xmaxandself.ymin==other.yminandself.ymax==other.ymax)returnresult
[docs]classCartesian2d(Grid2d):""" This class defines a 2D Cartesian Grid. Define: x = x y = y """def__init__(self,nx,ny,*,ng=1,xmin=0.0,xmax=1.0,ymin=0.0,ymax=1.0):super().__init__(nx,ny,ng=ng,xmin=xmin,xmax=xmax,ymin=ymin,ymax=ymax)self.coord_type=0# Length of the side in x- and y-directionself.Lx=ArrayIndexer(np.full((self.qx,self.qy),self.dx),grid=self)self.Ly=ArrayIndexer(np.full((self.qx,self.qy),self.dy),grid=self)# This is area of the side that is perpendicular to x.self.Ax=self.Ly# This is area of the side that is perpendicular to y.self.Ay=self.Lx# Spatial derivative of log(area). It's zero for Cartesianself.dlogAx=ArrayIndexer(np.zeros_like(self.Ax),grid=self)self.dlogAy=ArrayIndexer(np.zeros_like(self.Ay),grid=self)# Volume of the cell.self.V=ArrayIndexer(np.full((self.qx,self.qy),self.dx*self.dy),grid=self)def__str__(self):""" print out some basic information about the grid object """returnf"Cartesian 2D Grid: xmin = {self.xmin}, xmax = {self.xmax}, "+ \
f"ymin = {self.ymin}, ymax = {self.ymax}, "+ \
f"nx = {self.nx}, ny = {self.ny}, ng = {self.ng}"
[docs]classSphericalPolar(Grid2d):""" This class defines a spherical polar grid. This is technically a 3D geometry but assumes azimuthal symmetry, and zero velocity in phi-direction. Hence 2D. Define: r = x theta = y """def__init__(self,nx,ny,*,ng=1,xmin=0.2,xmax=1.0,ymin=0.0,ymax=1.0):super().__init__(nx,ny,ng=ng,xmin=xmin,xmax=xmax,ymin=ymin,ymax=ymax)# Make sure theta is within [0, PI]assertymin>=0.0andymax<=np.pi,"y or \u03b8 should be within [0, \u03c0]."# Make sure the ghost cells doesn't extend out negative x(r)assertxmin-ng*self.dx>=0.0, \
"xmin (r-direction), must be large enough so ghost cell doesn't have negative x."self.coord_type=1# Length of the side along r-direction, drself.Lx=ArrayIndexer(np.full((self.qx,self.qy),self.dx),grid=self)# Length of the side along theta-direction, r*dthetaself.Ly=ArrayIndexer(self.x2d*self.dy,grid=self)# Returns an array of the face area that points in the r(x) direction.# dL_theta x dL_phi = r^2 * sin(theta) * dtheta * dphi# dAr_l = - r{i-1/2}^2 * 2pi * cos(theta{i+1/2}) - cos(theta{i-1/2})self.Ax=np.abs(-2.0*np.pi*self.xl2d**2*(np.cos(self.yr2d)-np.cos(self.yl2d)))# Returns an array of the face area that points in the theta(y) direction.# dL_phi x dL_r = dr * r * sin(theta) * dphi# dAtheta_l = pi * sin(theta{i-1/2}) * (r{i+1/2}^2 - r{i-1/2}^2)self.Ay=np.abs(np.pi*np.sin(self.yl2d)*(self.xr2d**2-self.xl2d**2))# dlogAx = 1 / r^2 d( r^2 ) / dr = 2 / rself.dlogAx=2.0/self.x2d# dlogAy = 1 / (r sin(theta)) d( sin(theta) )/dtheta = cot(theta) / rself.dlogAy=1.0/(np.tan(self.y2d)*self.x2d)# Returns an array of the volume of each cell.# dV = dL_r * dL_theta * dL_phi# = (dr) * (r * dtheta) * (r * sin(theta) * dphi)# dV = - 2*np.pi / 3 * (cos(theta{i+1/2}) - cos(theta{i-1/2})) * (r{i+1/2}^3 - r{i-1/2}^3)self.V=np.abs(-2.0*np.pi/3.0*(np.cos(self.yr2d)-np.cos(self.yl2d))*(self.xr2d-self.xl2d)*(self.xr2d**2+self.xl2d**2+self.xr2d*self.xl2d))def__str__(self):""" print out some basic information about the grid object """return"Spherical Polar 2D Grid: Define x : r, y : \u03b8. "+ \
f"xmin (r) = {self.xmin}, xmax= {self.xmax}, "+ \
f"ymin = {self.ymin}, ymax = {self.ymax}, "+ \
f"nx = {self.nx}, ny = {self.ny}, ng = {self.ng}"
[docs]classCellCenterData2d:""" A class to define cell-centered data that lives on a grid. A CellCenterData2d object is built in a multi-step process before it can be used. * Create the object. We pass in a grid object to describe where the data lives:: my_data = patch.CellCenterData2d(myGrid) * Register any variables that we expect to live on this patch. Here BC describes the boundary conditions for that variable:: my_data.register_var('density', BC) my_data.register_var('x-momentum', BC) ... * Register any auxiliary data -- these are any parameters that are needed to interpret the data outside of the simulation (for example, the gamma for the equation of state):: my_data.set_aux(keyword, value) * Finish the initialization of the patch:: my_data.create() This last step actually allocates the storage for the state variables. Once this is done, the patch is considered to be locked. New variables cannot be added. """# pylint: disable=too-many-instance-attributesdef__init__(self,grid,*,dtype=np.float64):""" Initialize the CellCenterData2d object. Parameters ---------- grid : Grid2d object The grid upon which the data will live dtype : NumPy data type, optional The datatype of the data we wish to create (defaults to np.float64 """self.grid=gridself.dtype=dtypeself.data=Noneself.names=[]self.vars=self.names# backwards compatibility hackself.nvar=0self.ivars=[]self.aux={}# derived variables will have a callback functionself.derives=[]self.BCs={}# timeself.t=-1.0self.initialized=0
[docs]defregister_var(self,name,bc):""" Register a variable with CellCenterData2d object. Parameters ---------- name : str The variable name bc : BC object The boundary conditions that describe the actions to take for this variable at the physical domain boundaries. """ifself.initialized==1:msg.fail("ERROR: grid already initialized")self.names.append(name)self.nvar+=1self.BCs[name]=bc
[docs]defset_aux(self,keyword,value):""" Set any auxiliary (scalar) data. This data is simply carried along with the CellCenterData2d object Parameters ---------- keyword : str The name of the datum value : any time The value to associate with the keyword """self.aux[keyword]=value
[docs]defadd_derived(self,func):""" Register a function to compute derived variable Parameters ---------- func : function A function to call to derive the variable. This function should take two arguments, a CellCenterData2d object and a string variable name (or list of variables) """self.derives.append(func)
[docs]defcreate(self):""" Called after all the variables are registered and allocates the storage for the state data. """ifself.initialized==1:msg.fail("ERROR: grid already initialized")_tmp=np.zeros((self.grid.qx,self.grid.qy,self.nvar),dtype=self.dtype)self.data=ArrayIndexer(_tmp,grid=self.grid)self.initialized=1
def__str__(self):""" print out some basic information about the CellCenterData2d object """ifself.initialized==0:my_str="CellCenterData2d object not yet initialized"returnmy_strmy_str=f"cc data: nx = {self.grid.nx}, ny = {self.grid.ny}, ng = {self.grid.ng}\n"my_str+=f" nvars = {self.nvar}\n"my_str+=" variables:\n"forninrange(self.nvar):name=self.names[n]my_str+=f"{name:>16s}: min: {self.min(name):15.10f} max: {self.max(name):15.10f}\n"my_str+=f"{' ':>16s} BCs: -x: {self.BCs[name].xlb:12s} +x: {self.BCs[name].xrb:12s}"my_str+=f" -y: {self.BCs[name].ylb:12s} +y: {self.BCs[name].yrb:12s}\n"returnmy_str
[docs]defget_var(self,name):""" Return a data array for the variable described by name. Stored variables will be checked first, and then any derived variables will be checked. For a stored variable, changes made to this are automatically reflected in the CellCenterData2d object. Parameters ---------- name : str The name of the variable to access Returns ------- out : ndarray The array of data corresponding to the variable name """# ns = [self.names.index(name) for name in self.names]try:n=self.names.index(name)exceptValueError:forfinself.derives:try:var=f(self,name)exceptTypeError:var=f(self,name,self.ivars,self.grid)iflen(var)>0:returnvarraiseKeyError(f"name {name} is not valid")fromNonereturnself.get_var_by_index(n)
[docs]defget_var_by_index(self,n):""" Return a data array for the variable with index n in the data array. Any changes made to this are automatically reflected in the CellCenterData2d object. Parameters ---------- n : int The index of the variable to access Returns ------- out : ndarray The array of data corresponding to the index """returnArrayIndexer(d=self.data[:,:,n],grid=self.grid)
[docs]defget_vars(self):""" Return the entire data array. Any changes made to this are automatically reflected in the CellCenterData2d object. Returns ------- out : ndarray The array of data """returnArrayIndexer(d=self.data,grid=self.grid)
[docs]defget_aux(self,keyword):""" Get the auxiliary data associated with keyword Parameters ---------- keyword : str The name of the auxiliary data to access Returns ------- out : variable type The value corresponding to the keyword """ifkeywordinself.aux:returnself.aux[keyword]returnNone
[docs]defzero(self,name):""" Zero out the data array associated with variable name. Parameters ---------- name : str The name of the variable to zero """n=self.names.index(name)self.data[:,:,n]=0.0
[docs]deffill_BC_all(self):""" Fill boundary conditions on all variables. """fornameinself.names:self.fill_BC(name)
[docs]deffill_BC(self,name):""" Fill the boundary conditions. This operates on a single state variable at a time, to allow for maximum flexibility. We do periodic, reflect-even, reflect-odd, and outflow Each variable name has a corresponding BC stored in the CellCenterData2d object -- we refer to this to figure out the action to take at each boundary. Parameters ---------- name : str The name of the variable for which to fill the BCs. """n=self.names.index(name)self.data.fill_ghost(n=n,bc=self.BCs[name])# that will handle the standard type of BCs, but if we asked# for a custom BC, we handle it hereifself.BCs[name].xlbinbnd.ext_bcs:try:bnd.ext_bcs[self.BCs[name].xlb](self.BCs[name].xlb,"xlb",name,self,self.ivars)exceptTypeError:bnd.ext_bcs[self.BCs[name].xlb](self.BCs[name].xlb,"xlb",name,self)ifself.BCs[name].xrbinbnd.ext_bcs:try:bnd.ext_bcs[self.BCs[name].xrb](self.BCs[name].xrb,"xrb",name,self)exceptTypeError:bnd.ext_bcs[self.BCs[name].xrb](self.BCs[name].xrb,"xrb",name,self,self.ivars)ifself.BCs[name].ylbinbnd.ext_bcs:try:bnd.ext_bcs[self.BCs[name].ylb](self.BCs[name].ylb,"ylb",name,self)exceptTypeError:bnd.ext_bcs[self.BCs[name].ylb](self.BCs[name].ylb,"ylb",name,self,self.ivars)ifself.BCs[name].yrbinbnd.ext_bcs:try:bnd.ext_bcs[self.BCs[name].yrb](self.BCs[name].yrb,"yrb",name,self)exceptTypeError:bnd.ext_bcs[self.BCs[name].yrb](self.BCs[name].yrb,"yrb",name,self,self.ivars)
[docs]defmin(self,name,*,ng=0):""" return the minimum of the variable name in the domain's valid region """n=self.names.index(name)returnnp.min(self.data.v(buf=ng,n=n))
[docs]defmax(self,name,*,ng=0):""" return the maximum of the variable name in the domain's valid region """n=self.names.index(name)returnnp.max(self.data.v(buf=ng,n=n))
[docs]defrestrict(self,varname,N=2):""" Restrict the variable varname to a coarser grid (factor of 2 coarser) and return an array with the resulting data (and same number of ghostcells) """fine_grid=self.gridfdata=self.get_var(varname)# allocate an array for the coarsely gridded datacoarse_grid=fine_grid.coarse_like(N)cdata=coarse_grid.scratch_array()# fill the coarse array with the restricted data -- just# by averaging the fine cells into the corresponding coarse cell# that encompasses them.ifN==2:cdata.v()[:,:]= \
0.25*(fdata.v(s=2)+fdata.ip(1,s=2)+fdata.jp(1,s=2)+fdata.ip_jp(1,1,s=2))elifN==4:cdata.v()[:,:]= \
(fdata.v(s=4)+fdata.ip(1,s=4)+fdata.ip(2,s=4)+fdata.ip(3,s=4)+fdata.jp(1,s=4)+fdata.ip_jp(1,1,s=4)+fdata.ip_jp(2,1,s=4)+fdata.ip_jp(3,1,s=4)+fdata.jp(2,s=4)+fdata.ip_jp(1,2,s=4)+fdata.ip_jp(2,2,s=4)+fdata.ip_jp(3,2,s=4)+fdata.jp(3,s=4)+fdata.ip_jp(1,3,s=4)+fdata.ip_jp(2,3,s=4)+fdata.ip_jp(3,3,s=4))/16.0else:raiseValueError("restriction is only allowed by 2 or 4")returncdata
[docs]defprolong(self,varname):""" Prolong the data in the current (coarse) grid to a finer (factor of 2 finer) grid. Return an array with the resulting data (and same number of ghostcells). Only the data for the variable varname will be operated upon. We will reconstruct the data in the zone from the zone-averaged variables using the same limited slopes as in the advection routine. Getting a good multidimensional reconstruction polynomial is hard -- we want it to be bilinear and monotonic -- we settle for having each slope be independently monotonic:: (x) (y) f(x,y) = m x/dx + m y/dy + <f> where the m's are the limited differences in each direction. When averaged over the parent cell, this reproduces <f>. Each zone's reconstrution will be averaged over 4 children:: +-----------+ +-----+-----+ | | | | | | | | 3 | 4 | | <f> | --> +-----+-----+ | | | | | | | | 1 | 2 | +-----------+ +-----+-----+ We will fill each of the finer resolution zones by filling all the 1's together, using a stride 2 into the fine array. Then the 2's and ..., this allows us to operate in a vector fashion. All operations will use the same slopes for their respective parents. """coarse_grid=self.gridcdata=self.get_var(varname)# allocate an array for the finely gridded datafine_grid=coarse_grid.fine_like(2)fdata=fine_grid.scratch_array()# slopes for the coarse datam_x=coarse_grid.scratch_array()m_x.v()[:,:]=0.5*(cdata.ip(1)-cdata.ip(-1))m_y=coarse_grid.scratch_array()m_y.v()[:,:]=0.5*(cdata.jp(1)-cdata.jp(-1))# fill the childrenfdata.v(s=2)[:,:]=cdata.v()-0.25*m_x.v()-0.25*m_y.v()# 1 childfdata.ip(1,s=2)[:,:]=cdata.v()+0.25*m_x.v()-0.25*m_y.v()# 2fdata.jp(1,s=2)[:,:]=cdata.v()-0.25*m_x.v()+0.25*m_y.v()# 3fdata.ip_jp(1,1,s=2)[:,:]=cdata.v()+0.25*m_x.v()+0.25*m_y.v()# 4returnfdata
[docs]defwrite(self,filename):""" create an output file in HDF5 format and write out our data and grid. """ifnotfilename.endswith(".h5"):filename+=".h5"withh5py.File(filename,"w")asf:self.write_data(f)
[docs]defwrite_data(self,f):""" write the data out to an hdf5 file -- here, f is an h5py File pbject """# auxiliary datagaux=f.create_group("aux")fork,vinself.aux.items():gaux.attrs[k]=v# grid informationggrid=f.create_group("grid")ggrid.attrs["nx"]=self.grid.nxggrid.attrs["ny"]=self.grid.nyggrid.attrs["ng"]=self.grid.ngggrid.attrs["xmin"]=self.grid.xminggrid.attrs["xmax"]=self.grid.xmaxggrid.attrs["ymin"]=self.grid.yminggrid.attrs["ymax"]=self.grid.ymaxtry:ggrid.attrs["coord_type"]=self.grid.coord_typeexceptAttributeError:pass# datagstate=f.create_group("state")forninrange(self.nvar):gvar=gstate.create_group(self.names[n])gvar.create_dataset("data",data=self.get_var_by_index(n).v())gvar.attrs["xlb"]=self.BCs[self.names[n]].xlbgvar.attrs["xrb"]=self.BCs[self.names[n]].xrbgvar.attrs["ylb"]=self.BCs[self.names[n]].ylbgvar.attrs["yrb"]=self.BCs[self.names[n]].yrb
[docs]defpretty_print(self,var,fmt=None):"""print out the contents of the data array with pretty formatting indicating where ghost cells are."""a=self.get_var(var)a.pretty_print(fmt=fmt)
[docs]classFaceCenterData2d(CellCenterData2d):""" A class to define face-centered data that lives on a grid. Data can be face-centered in x or y. This is built in the same multistep process as a CellCenterData2d object"""def__init__(self,grid,idir,dtype=np.float64):""" Initialize the FaceCenterData2d object Parameters ---------- grid : Grid2d object The grid upon which the data will live idir : the direction in which we are face-centered (this will be 1 for x or 2 for y) dtype : NumPy data type, optional The datatype of the data we wish to create (defaults to np.float64 """super().__init__(grid,dtype=dtype)self.idir=idir
[docs]defadd_derived(self,func):raiseNotImplementedError("derived variables not yet supported for face-centered data")
[docs]defcreate(self):"""Called after all the variables are registered and allocates the storage for the state data. For face-centered data, we have one more zone in the face-centered direction. """ifself.initialized==1:msg.fail("ERROR: grid already initialized")ifself.idir==1:_tmp=np.zeros((self.grid.qx+1,self.grid.qy,self.nvar),dtype=self.dtype)self.data=ArrayIndexerFC(_tmp,idir=self.idir,grid=self.grid)elifself.idir==2:_tmp=np.zeros((self.grid.qx,self.grid.qy+1,self.nvar),dtype=self.dtype)self.data=ArrayIndexerFC(_tmp,idir=self.idir,grid=self.grid)self.initialized=1
def__str__(self):""" print out some basic information about the FaceCenterData2d object """ifself.initialized==0:my_str="FaceCenterData2d object not yet initialized"returnmy_strmy_str=f"fc data: idir = {self.idir}, nx = {self.grid.nx}, ny = {self.grid.ny}, ng = {self.grid.ng}\n"my_str+=f" nvars = {self.nvar}\n"my_str+=" variables:\n"forninrange(self.nvar):name=self.names[n]my_str+=f"{name:>16s}: min: {self.min(name):15.10f} max: {self.max(name):15.10f}\n"my_str+=f"{' ':>16s} BCs: -x: {self.BCs[name].xlb:12s} +x: {self.BCs[name].xrb:12s}"my_str+=f" -y: {self.BCs[name].ylb:12s} +y: {self.BCs[name].yrb:12s}\n"returnmy_str
[docs]defget_var_by_index(self,n):""" Return a data array for the variable with index n in the data array. Any changes made to this are automatically reflected in the CellCenterData2d object. Parameters ---------- n : int The index of the variable to access Returns ------- out : ndarray The array of data corresponding to the index """returnArrayIndexerFC(d=self.data[:,:,n],idir=self.idir,grid=self.grid)
[docs]defget_vars(self):""" Return the entire data array. Any changes made to this are automatically reflected in the CellCenterData2d object. Returns ------- out : ndarray The array of data """returnArrayIndexerFC(d=self.data,idir=self.idir,grid=self.grid)
[docs]deffill_BC(self,name):""" Fill the boundary conditions. This operates on a single state variable at a time, to allow for maximum flexibility. We do periodic, reflect-even, reflect-odd, and outflow Each variable name has a corresponding BC stored in the CellCenterData2d object -- we refer to this to figure out the action to take at each boundary. Parameters ---------- name : str The name of the variable for which to fill the BCs. """n=self.names.index(name)self.data.fill_ghost(n=n,bc=self.BCs[name])ifself.BCs[name].xlbinbnd.ext_bcsor \
self.BCs[name].xrbinbnd.ext_bcsor \
self.BCs[name].ylbinbnd.ext_bcsor \
self.BCs[name].yrbinbnd.ext_bcs:raiseNotImplementedError("custom boundary conditions not supported for FaceCenterData2d")
[docs]defrestrict(self,varname,N=2):raiseNotImplementedError("restriction not implemented for FaceCenterData2d")
[docs]defprolong(self,varname):raiseNotImplementedError("prolongation not implemented for FaceCenterData2d")
[docs]defwrite_data(self,f):""" write the data out to an hdf5 file -- here, f is an h5py File pbject """# datagstate=f.create_group("face-centered-state")forninrange(self.nvar):gvar=gstate.create_group(self.names[n])gvar.create_dataset("data",data=self.get_var_by_index(n).v())gvar.attrs["xlb"]=self.BCs[self.names[n]].xlbgvar.attrs["xrb"]=self.BCs[self.names[n]].xrbgvar.attrs["ylb"]=self.BCs[self.names[n]].ylbgvar.attrs["yrb"]=self.BCs[self.names[n]].yrb
[docs]defcell_center_data_clone(old):""" Create a new CellCenterData2d object that is a copy of an existing one Parameters ---------- old : CellCenterData2d object The CellCenterData2d object we wish to copy """ifnotisinstance(old,CellCenterData2d):msg.fail("Can't clone object")# we may be a type derived from CellCenterData2d, so use the same# typemyt=type(old)new=myt(old.grid,dtype=old.dtype)forninrange(old.nvar):new.register_var(old.names[n],old.BCs[old.names[n]])new.create()new.aux=old.aux.copy()new.data=old.data.copy()new.derives=old.derives.copy()returnnew
[docs]defdo_demo():""" show examples of the patch methods / classes """# pylint: disable-next=import-outside-toplevel # required to avoid import loopsimportpyro.util.io_pyroasio# illustrate basic mesh operationsmyg=Grid2d(8,16,xmax=1.0,ymax=2.0)mydata=CellCenterData2d(myg)bc=bnd.BC()mydata.register_var("a",bc)mydata.create()a=mydata.get_var("a")a[:,:]=np.exp(-(myg.x2d-0.5)**2-(myg.y2d-1.0)**2)print(mydata)# outputprint("writing\n")mydata.write("mesh_test")print("reading\n")myd2=io.read("mesh_test")print(myd2)mydata.pretty_print("a")