"""An array class that has methods supporting the type of stenciloperations we see in finite-difference methods, like i+1, i-1, etc."""importnumbersimportnumpyasnpdef_buf_split(b):""" take an integer or iterable and break it into a -x, +x, -y, +y value representing a ghost cell buffer """try:bxlo,bxhi,bylo,byhi=bexcept(ValueError,TypeError):try:blo,bhi=bexcept(ValueError,TypeError):blo=bbhi=bbxlo=bylo=blobxhi=byhi=bhireturnbxlo,bxhi,bylo,byhi
[docs]classArrayIndexer(np.ndarray):"""a class that wraps the data region of a single cell-centered data array (d) and allows us to easily do array operations like d[i+1,j] using the ip() method. """def__new__(cls,d,grid=None):obj=np.asarray(d).view(cls)obj.g=gridobj.c=len(d.shape)returnobjdef__array_finalize__(self,obj):ifobjisNone:returnself.g=getattr(obj,"g",None)self.c=getattr(obj,"c",None)
[docs]defv(self,buf=0,n=0,s=1):"""return a view of the valid data region for component n, with stride s, and a buffer of ghost cells given by buf """returnself.ip_jp(0,0,buf=buf,n=n,s=s)
[docs]defip(self,shift,buf=0,n=0,s=1):"""return a view of the data shifted by shift in the x direction. By default the view is the same size as the valid region, but the buf can specify how many ghost cells on each side to include. The component is n and s is the stride """returnself.ip_jp(shift,0,buf=buf,n=n,s=s)
[docs]defjp(self,shift,buf=0,n=0,s=1):"""return a view of the data shifted by shift in the y direction. By default the view is the same size as the valid region, but the buf can specify how many ghost cells on each side to include. The component is n and s is the stride """returnself.ip_jp(0,shift,buf=buf,n=n,s=s)
[docs]defip_jp(self,ishift,jshift,buf=0,n=0,s=1):"""return a view of the data shifted by ishift in the x direction and jshift in the y direction. By default the view is the same size as the valid region, but the buf can specify how many ghost cells on each side to include. The component is n and s is the stride """bxlo,bxhi,bylo,byhi=_buf_split(buf)c=len(self.shape)ifc==2:returnnp.asarray(self[self.g.ilo-bxlo+ishift:self.g.ihi+1+bxhi+ishift:s,self.g.jlo-bylo+jshift:self.g.jhi+1+byhi+jshift:s])returnnp.asarray(self[self.g.ilo-bxlo+ishift:self.g.ihi+1+bxhi+ishift:s,self.g.jlo-bylo+jshift:self.g.jhi+1+byhi+jshift:s,n])
[docs]deflap(self,n=0,buf=0):"""return the 5-point Laplacian"""l=(self.ip(-1,n=n,buf=buf)-2*self.v(n=n,buf=buf)+self.ip(1,n=n,buf=buf))/self.g.dx**2+ \
(self.jp(-1,n=n,buf=buf)-2*self.v(n=n,buf=buf)+self.jp(1,n=n,buf=buf))/self.g.dy**2returnl
[docs]defnorm(self,n=0):""" find the norm of the quantity (index n) defined on the same grid, in the domain's valid region """c=len(self.shape)ifc==2:returnnp.sqrt(self.g.dx*self.g.dy*np.sum((self[self.g.ilo:self.g.ihi+1,self.g.jlo:self.g.jhi+1]**2).flat))_tmp=self[:,:,n]returnnp.sqrt(self.g.dx*self.g.dy*np.sum((_tmp[self.g.ilo:self.g.ihi+1,self.g.jlo:self.g.jhi+1]**2).flat))
[docs]defcopy(self,order='C'):"""make a copy of the array, defined on the same grid"""returnArrayIndexer(np.asarray(self).copy(order=order),grid=self.g)
[docs]defis_symmetric(self,nodal=False,tol=1.e-14,asymmetric=False):"""return True is the data is left-right symmetric (to the tolerance tol) For node-centered data, set nodal=True """# prefactor to convert from symmetric to asymmetric tests=1ifasymmetric:s=-1ifnotnodal:L=self[self.g.ilo:self.g.ilo+self.g.nx//2,self.g.jlo:self.g.jhi+1]R=self[self.g.ilo+self.g.nx//2:self.g.ihi+1,self.g.jlo:self.g.jhi+1]else:L=self[self.g.ilo:self.g.ilo+self.g.nx//2+1,self.g.jlo:self.g.jhi+1]print(self.g.ilo+self.g.nx//2,self.g.ihi+2)R=self[self.g.ilo+self.g.nx//2:self.g.ihi+2,self.g.jlo:self.g.jhi+1]e=abs(L-s*np.flipud(R)).max()returne<tol
[docs]defis_asymmetric(self,nodal=False,tol=1.e-14):"""return True is the data is left-right asymmetric (to the tolerance tol)---e.g, the sign flips. For node-centered data, set nodal=True """returnself.is_symmetric(nodal=nodal,tol=tol,asymmetric=True)
[docs]deffill_ghost(self,n=0,bc=None):"""Fill the boundary conditions. This operates on a single component, n. We do periodic, reflect-even, reflect-odd, and outflow We need a BC object to tell us what BC type on each boundary. """# there is only a single grid, so every boundary is on# a physical boundary (except if we are periodic)# Note: we piggy-back on outflow and reflect-odd for# Neumann and Dirichlet homogeneous BCs respectively, but# this only works for a single ghost cell# -x boundaryifbc.xlbin["outflow","neumann"]:ifbc.xl_valueisNone:foriinrange(self.g.ilo):self[i,:,n]=self[self.g.ilo,:,n]else:self[self.g.ilo-1,:,n]= \
self[self.g.ilo,:,n]-self.g.dx*bc.xl_value[:]elifbc.xlb=="reflect-even":foriinrange(self.g.ilo):self[i,:,n]=self[2*self.g.ng-i-1,:,n]elifbc.xlbin["reflect-odd","dirichlet"]:ifbc.xl_valueisNone:foriinrange(self.g.ilo):self[i,:,n]=-self[2*self.g.ng-i-1,:,n]else:self[self.g.ilo-1,:,n]= \
2*bc.xl_value[:]-self[self.g.ilo,:,n]elifbc.xlb=="periodic":foriinrange(self.g.ilo):self[i,:,n]=self[self.g.ihi-self.g.ng+i+1,:,n]# +x boundaryifbc.xrbin["outflow","neumann"]:ifbc.xr_valueisNone:foriinrange(self.g.ihi+1,self.g.nx+2*self.g.ng):self[i,:,n]=self[self.g.ihi,:,n]else:self[self.g.ihi+1,:,n]= \
self[self.g.ihi,:,n]+self.g.dx*bc.xr_value[:]elifbc.xrb=="reflect-even":foriinrange(self.g.ng):i_bnd=self.g.ihi+1+ii_src=self.g.ihi-iself[i_bnd,:,n]=self[i_src,:,n]elifbc.xrbin["reflect-odd","dirichlet"]:ifbc.xr_valueisNone:foriinrange(self.g.ng):i_bnd=self.g.ihi+1+ii_src=self.g.ihi-iself[i_bnd,:,n]=-self[i_src,:,n]else:self[self.g.ihi+1,:,n]= \
2*bc.xr_value[:]-self[self.g.ihi,:,n]elifbc.xrb=="periodic":foriinrange(self.g.ihi+1,2*self.g.ng+self.g.nx):self[i,:,n]=self[i-self.g.ihi-1+self.g.ng,:,n]# -y boundaryifbc.ylbin["outflow","neumann"]:ifbc.yl_valueisNone:forjinrange(self.g.jlo):self[:,j,n]=self[:,self.g.jlo,n]else:self[:,self.g.jlo-1,n]= \
self[:,self.g.jlo,n]-self.g.dy*bc.yl_value[:]elifbc.ylb=="reflect-even":forjinrange(self.g.jlo):self[:,j,n]=self[:,2*self.g.ng-j-1,n]elifbc.ylbin["reflect-odd","dirichlet"]:ifbc.yl_valueisNone:forjinrange(self.g.jlo):self[:,j,n]=-self[:,2*self.g.ng-j-1,n]else:self[:,self.g.jlo-1,n]= \
2*bc.yl_value[:]-self[:,self.g.jlo,n]elifbc.ylb=="periodic":forjinrange(self.g.jlo):self[:,j,n]=self[:,self.g.jhi-self.g.ng+j+1,n]# +y boundaryifbc.yrbin["outflow","neumann"]:ifbc.yr_valueisNone:forjinrange(self.g.jhi+1,self.g.ny+2*self.g.ng):self[:,j,n]=self[:,self.g.jhi,n]else:self[:,self.g.jhi+1,n]= \
self[:,self.g.jhi,n]+self.g.dy*bc.yr_value[:]elifbc.yrb=="reflect-even":forjinrange(self.g.ng):j_bnd=self.g.jhi+1+jj_src=self.g.jhi-jself[:,j_bnd,n]=self[:,j_src,n]elifbc.yrbin["reflect-odd","dirichlet"]:ifbc.yr_valueisNone:forjinrange(self.g.ng):j_bnd=self.g.jhi+1+jj_src=self.g.jhi-jself[:,j_bnd,n]=-self[:,j_src,n]else:self[:,self.g.jhi+1,n]= \
2*bc.yr_value[:]-self[:,self.g.jhi,n]elifbc.yrb=="periodic":forjinrange(self.g.jhi+1,2*self.g.ng+self.g.ny):self[:,j,n]=self[:,j-self.g.jhi-1+self.g.ng,n]
[docs]defpretty_print(self,n=0,fmt=None,show_ghost=True):""" Print out a small dataset to the screen with the ghost cells a different color, to make things stand out """iffmtisNone:ifissubclass(self.dtype.type,numbers.Integral):fmt="%4d"elifself.dtype==np.float64:fmt="%10.5g"else:raiseValueError("ERROR: dtype not supported")# print j descending, so it looks like a grid (y increasing# with height)ifshow_ghost:ilo=0ihi=self.g.qx-1jlo=0jhi=self.g.qy-1else:ilo=self.g.iloihi=self.g.ihijlo=self.g.jlojhi=self.g.jhiforjinreversed(range(jlo,jhi+1)):foriinrange(ilo,ihi+1):if(j<self.g.jloorj>self.g.jhiori<self.g.iloori>self.g.ihi):gc=1else:gc=0ifself.c==2:val=self[i,j]else:try:val=self[i,j,n]exceptIndexError:val=self[i,j]ifgc:print("\033[31m"+fmt%(val)+"\033[0m",end="")else:print(fmt%(val),end="")print(" ")leg=""" ^ y | +---> x """print(leg)
[docs]classArrayIndexerFC(ArrayIndexer):"""a class that wraps the data region of a single face-centered data array (d) and allows us to easily do array operations like d[i+1,j] using the ip() method. """def__new__(cls,d,idir,grid=None):obj=np.asarray(d).view(cls)obj.g=gridobj.idir=idirobj.c=len(d.shape)returnobjdef__array_finalize__(self,obj):ifobjisNone:returnself.g=getattr(obj,"g",None)self.idir=getattr(obj,"idir",None)self.c=getattr(obj,"c",None)
[docs]defip_jp(self,ishift,jshift,buf=0,n=0,s=1):"""return a view of the data shifted by ishift in the x direction and jshift in the y direction. By default the view is the same size as the valid region, but the buf can specify how many ghost cells on each side to include. The component is n and s is the stride """bxlo,bxhi,bylo,byhi=_buf_split(buf)c=len(self.shape)ifself.idir==1:# pylint: disable=no-else-return# face-centered in xifc==2:returnnp.asarray(self[self.g.ilo-bxlo+ishift:self.g.ihi+2+bxhi+ishift:s,self.g.jlo-bylo+jshift:self.g.jhi+1+byhi+jshift:s])returnnp.asarray(self[self.g.ilo-bxlo+ishift:self.g.ihi+2+bxhi+ishift:s,self.g.jlo-bylo+jshift:self.g.jhi+1+byhi+jshift:s,n])else:# idir == 2# face-centered in yifc==2:returnnp.asarray(self[self.g.ilo-bxlo+ishift:self.g.ihi+1+bxhi+ishift:s,self.g.jlo-bylo+jshift:self.g.jhi+2+byhi+jshift:s])returnnp.asarray(self[self.g.ilo-bxlo+ishift:self.g.ihi+1+bxhi+ishift:s,self.g.jlo-bylo+jshift:self.g.jhi+2+byhi+jshift:s,n])
[docs]deflap(self,n=0,buf=0):raiseNotImplementedError("lap not implemented for ArrayIndexerFC")
[docs]defnorm(self,n=0):""" find the norm of the quantity (index n) defined on the same grid, in the domain's valid region """c=len(self.shape)ifself.idir==1:ifc==2:returnnp.sqrt(self.g.dx*self.g.dy*np.sum((self[self.g.ilo:self.g.ihi+2,self.g.jlo:self.g.jhi+1]**2).flat))_tmp=self[:,:,n]returnnp.sqrt(self.g.dx*self.g.dy*np.sum((_tmp[self.g.ilo:self.g.ihi+2,self.g.jlo:self.g.jhi+1]**2).flat))# idir == 2ifc==2:returnnp.sqrt(self.g.dx*self.g.dy*np.sum((self[self.g.ilo:self.g.ihi+1,self.g.jlo:self.g.jhi+2]**2).flat))_tmp=self[:,:,n]returnnp.sqrt(self.g.dx*self.g.dy*np.sum((_tmp[self.g.ilo:self.g.ihi+1,self.g.jlo:self.g.jhi+2]**2).flat))
[docs]defcopy(self,order='C'):"""make a copy of the array, defined on the same grid"""returnArrayIndexerFC(np.asarray(self).copy(order=order),self.idir,grid=self.g)
[docs]defis_symmetric(self,nodal=False,tol=1.e-14,asymmetric=False):"""return True is the data is left-right symmetric (to the tolerance tol) For node-centered data, set nodal=True """raiseNotImplementedError()
[docs]defis_asymmetric(self,nodal=False,tol=1.e-14):"""return True is the data is left-right asymmetric (to the tolerance tol)---e.g, the sign flips. For node-centered data, set nodal=True """raiseNotImplementedError()
[docs]deffill_ghost(self,n=0,bc=None):"""Fill the boundary conditions. This operates on a single component, n. We do periodic, reflect-even, reflect-odd, and outflow We need a BC object to tell us what BC type on each boundary. """# there is only a single grid, so every boundary is on# a physical boundary (except if we are periodic)# Note: we piggy-back on outflow and reflect-odd for# Neumann and Dirichlet homogeneous BCs respectively, but# this only works for a single ghost cell# -x boundaryifbc.xlbin["outflow","neumann","reflect-even","reflect-odd","dirichlet"]:raiseNotImplementedError("boundary condition not implemented for -x")ifbc.xlb=="periodic":ifself.idir==1:# face-centered in xforiinrange(self.g.ilo):self[i,:,n]=self[self.g.ihi-self.g.ng+i+1,:,n]elifself.idir==2:# face-centered in yforiinrange(self.g.ilo):self[i,:,n]=self[self.g.ihi-self.g.ng+i+1,:,n]# +x boundaryifbc.xrbin["outflow","neumann","reflect-even","reflect-odd","dirichlet"]:raiseNotImplementedError("boundary condition not implemented for +x")ifbc.xrb=="periodic":ifself.idir==1:# face-centered in xforiinrange(self.g.ihi+2,2*self.g.ng+self.g.nx+1):self[i,:,n]=self[i-self.g.ihi-1+self.g.ng,:,n]elifself.idir==2:# face-centered in yforiinrange(self.g.ihi+1,2*self.g.ng+self.g.nx):self[i,:,n]=self[i-self.g.ihi-1+self.g.ng,:,n]# -y boundaryifbc.ylbin["outflow","neumann","reflect-even","reflect-odd","dirichlet"]:raiseNotImplementedError("boundary condition not implemented for -y")ifbc.ylb=="periodic":ifself.idir==1:# face-centered in xforjinrange(self.g.jlo):self[:,j,n]=self[:,self.g.jhi-self.g.ng+j+1,n]elifself.idir==2:# face-centered in yforjinrange(self.g.jlo):self[:,j,n]=self[:,self.g.jhi-self.g.ng+j+1,n]# +y boundaryifbc.yrbin["outflow","neumann","reflect-even","reflect-odd","dirichlet"]:raiseNotImplementedError("boundary condition not implemented for +y")ifbc.yrb=="periodic":ifself.idir==1:# face-centered in xforjinrange(self.g.jhi+1,2*self.g.ng+self.g.ny):self[:,j,n]=self[:,j-self.g.jhi-1+self.g.ng,n]elifself.idir==2:forjinrange(self.g.jhi+2,2*self.g.ng+self.g.ny+1):self[:,j,n]=self[:,j-self.g.jhi-1+self.g.ng,n]
[docs]defpretty_print(self,n=0,fmt=None,show_ghost=True):""" Print out a small dataset to the screen with the ghost cells a different color, to make things stand out """iffmtisNone:ifissubclass(self.dtype.type,numbers.Integral):fmt="%4d"elifself.dtype==np.float64:fmt="%10.5g"else:raiseValueError("ERROR: dtype not supported")# print j descending, so it looks like a grid (y increasing# with height)ilo=ihi=jlo=jhi=-1ifshow_ghost:ifself.idir==1:ilo=0ihi=self.g.qxjlo=0jhi=self.g.qy-1elifself.idir==2:ilo=0ihi=self.g.qx-1jlo=0jhi=self.g.qyelse:ifself.idir==1:ilo=self.g.iloihi=self.g.ihi+1jlo=self.g.jlojhi=self.g.jhielifself.idir==2:ilo=self.g.iloihi=self.g.ihijlo=self.g.jlojhi=self.g.jhi+1forjinreversed(range(jlo,jhi+1)):foriinrange(ilo,ihi+1):gc=Noneifself.idir==1:if(j<self.g.jloorj>self.g.jhiori<self.g.iloori>self.g.ihi+1):gc=1else:gc=0elifself.idir==2:if(j<self.g.jloorj>self.g.jhi+1ori<self.g.iloori>self.g.ihi):gc=1else:gc=0ifself.c==2:val=self[i,j]else:val=self[i,j,n]ifgc:print("\033[31m"+fmt%(val)+"\033[0m",end="")else:print(fmt%(val),end="")print(" ")leg=""" ^ y | +---> x """print(leg)