source: codes/icosagcm/devel/Python/dynamico/meshes.py @ 799

Last change on this file since 799 was 799, checked in by dubos, 5 years ago

devel/Python : shift positions in Cartesian meshes by dx/2, dy/2

File size: 37.8 KB
Line 
1import time
2import math
3import numpy as np
4import netCDF4 as cdf
5import matplotlib.tri as tri
6import matplotlib.pyplot as plt
7from matplotlib.patches import Polygon
8from matplotlib.collections import PatchCollection
9
10import unstructured as unst
11import parallel
12from util import list_stencil, Base_class, inverse_list
13from precision import zeros
14
15radian=180/math.pi
16 
17#------------------- Hybrid mass-based coordinate -------------
18
19# compute hybrid coefs from distribution of mass
20
21def compute_hybrid_coefs(mass):
22    if mass.ndim==2 :
23        nx,llm=mass.shape
24        mass_dak = np.zeros((nx,llm))
25        mass_dbk = np.zeros((nx,llm))
26        mass_bl = np.zeros((nx,llm+1))+1.
27        for i in range(nx):
28            m_i = mass[i,:]
29            dbk_i = m_i/sum(m_i)
30            mass_dbk[i,:] = dbk_i
31            mass_bl[i,1:]= 1.-dbk_i.cumsum()
32    if mass.ndim==3 :
33        nx,ny,llm=mass.shape
34        mass_dak = np.zeros((nx,ny,llm))
35        mass_dbk = np.zeros((nx,ny,llm))
36        mass_bl = np.zeros((nx,ny,llm+1))+1.
37        for i in range(nx):
38            for j in range(ny):
39                m_ij = mass[i,j,:]
40                dbk_ij = m_ij/sum(m_ij)
41                mass_dbk[i,j,:] = dbk_ij
42                mass_bl[i,j,1:]= 1.-dbk_ij.cumsum()
43 
44    return mass_bl, mass_dak, mass_dbk
45
46# from theta.k90       : rhodz(l,ij) = mass_dak(l,ij) + mass_col(ij)*mass_dbk(l,ij)
47# from caldyn_wflux.k90: wflux(l,ij) = mass_bl(l,ij) * dmass_col(ij) - convm(l,ij)
48def mass_coefs_from_pressure_coefs(g, ap_il, bp_il):
49    # p = Mg + ptop = a + b.ps
50    # ps = Mcol.g+ptop
51    # M = mass_a + mass_b.Mcol
52    # M = (a+b.ps-ptop)/g
53    #   = (a+b.ptop-ptop)/g + b.Mcol
54    # mass_a  = (a+b.ptop)/g
55    # mass_da = (da+db.ptop)/g
56    # mass_b  = b
57    # mass_db = db
58    n, llm, ptop = ap_il.shape[0], ap_il.shape[1]-1, ap_il[0,-1]
59    print 'hybrid ptop : ', ptop
60    mass_al = (ap_il+ptop*bp_il)/g
61    mass_dak, mass_dbk = np.zeros((n,llm)), np.zeros((n,llm))
62    for k in range(llm):
63        mass_dak[:,k] = mass_al[:,k]-mass_al[:,k+1]
64        mass_dbk[:,k] = bp_il[:,k]-bp_il[:,k+1]
65    print mass_al[0,:]
66    print mass_dak[0,:]
67    print mass_dbk[0,:]
68    return [ np.asarray(x) for x in bp_il, mass_dak, mass_dbk ]
69
70#----------------------- Cartesian mesh -----------------------
71
72# arrays is a list of arrays
73# vals is a list of tuples
74# each tuple is stored in each array
75def put(ij, deg, arrays, vals):
76    k=0
77    for vv in vals: # vv is a tuple of values to be stored in arrays
78        for array,v in zip(arrays,vv):
79            array[ij,k]=v
80        k=k+1       
81    deg[ij]=k
82
83def concat(x,y):
84    z = np.asarray([x,y])
85    return z.transpose()
86
87class Cartesian_mesh(Base_class):
88    def __init__(self,nx,ny,llm,nqdyn,Lx,Ly,f):
89        dx,dy = Lx/nx, Ly/ny
90        self.dx, self.dy, self.f = dx,dy,f
91        self.nx, self.ny, self.llm, self.nqdyn = nx,ny,llm,nqdyn
92        self.field_z = self.field_mass
93        # 1D coordinate arrays
94        x=(np.arange(nx)-nx/2.)*Lx/nx
95        y=(np.arange(ny)-ny/2.)*Ly/ny
96        lev=np.arange(llm)
97        levp1=np.arange(llm+1)
98        self.x, self.y, self.lev, self.levp1 = x,y,lev,levp1
99        # 3D coordinate arrays
100        self.yy,self.xx,self.ll = np.meshgrid(y,x,lev, indexing='ij')       
101        self.yyp1,self.xxp1,self.llp1 = np.meshgrid(y,x,levp1, indexing='ij')
102        # beware conventions for indexing
103        # Fortran order : llm,nx*ny,nqdyn  / indices start at 1
104        # Python order : nqdyn,ny,nx,llm   / indices start at 0
105        # indices below follow Fortran while x,y follow Python/C
106        def periodize(z,nz): return (z+2*nz)%nz
107        def index(x,y):      return 1+periodize(x,nx)+nx*periodize(y,ny)
108        def indexu(x,y):     return 2*index(x,y)-1
109        def indexv(x,y):     return 2*index(x,y)
110        def zeros(shape,n=1): return [np.zeros(shape) for i in range(n)] if n>1 else np.zeros(shape)
111        def indices(shape,n=1): return [np.zeros(shape,np.int32) for i in range(n)] if n>1 else np.zeros(shape,np.int32)
112
113        Aiv, lon_i,lon_v,lat_i,lat_v   = zeros( nx*ny,   5)
114        lon_e,lat_e,le,de,angle_e      = zeros( 2*nx*ny, 5)
115        Riv2                           = zeros((nx*ny,4)  )
116        wee                            = zeros((2*nx*ny,4))
117
118        primal_deg,dual_deg                 = indices( nx*ny,   2)
119        primal_edge,primal_ne,primal_vertex = indices((nx*ny,4),3)
120        dual_edge,dual_ne,dual_vertex       = indices((nx*ny,4),3)
121        trisk_deg,left,right,up,down        = indices( 2*nx*ny, 5)
122        trisk                               = indices((2*nx*ny,4)) # 4 TRiSK coefs per edge
123   
124        def putval(ij, arrays, vals): 
125            for array,v in zip(arrays,vals): array[ij]=v
126        for x in range(nx):
127            for y in range(ny):
128                # NB : Fortran indices start at 1
129                # primal cells
130                ij=index(x,y)-1
131                lon_i[ij], lat_i[ij] = x, y
132                put(index(x,y)-1, primal_deg,(primal_edge,primal_vertex,primal_ne),
133                    ((indexu(x,y),index(x,y),1), 
134                    (indexv(x,y),index(x-1,y),1),
135                    (indexu(x-1,y),index(x-1,y-1),-1),
136                    (indexv(x,y-1),index(x,y-1),-1) ))
137                # dual cells
138                ij=index(x,y)-1
139                lon_v[ij], lat_v[ij] = x+.5, y+.5
140                put(ij,dual_deg,(dual_edge,dual_vertex,dual_ne,Riv2),
141                   ((indexv(x+1,y),index(x,y),1,.25),
142                    (indexu(x,y+1),index(x+1,y),-1,.25),
143                    (indexv(x,y),index(x+1,y+1),-1,.25),
144                    (indexu(x,y),index(x,y+1),1,.25) ))
145                # edges :
146                # left and right are adjacent primal cells
147                # flux is positive when going from left to right
148                # up and down are adjacent dual cells (vertices)
149                # circulation is positive when going from down to up
150                # u-points
151                ij =indexu(x,y)-1 
152                putval(ij, (left, right, down, up, le, de, angle_e, lon_e, lat_e),
153                       (index(x,y), index(x+1,y), index(x,y-1), index(x,y), dy, dx, 0., x+.5, y ) )
154                put(ij,trisk_deg,(trisk,wee),(
155                    (indexv(x,y),.25),
156                    (indexv(x+1,y),.25),
157                    (indexv(x,y-1),.25),
158                    (indexv(x+1,y-1),.25)))
159                # v-points
160                ij = indexv(x,y)-1
161                putval(ij, (left, right, down, up, le, de, angle_e, lon_e, lat_e),
162                       (index(x,y), index(x,y+1), index(x,y), index(x-1,y), dx, dy, .5*math.pi, x, y+.5 ) )
163                put(ij,trisk_deg,(trisk,wee),(
164                    (indexu(x,y),-.25),
165                    (indexu(x-1,y),-.25),
166                    (indexu(x,y+1),-.25),
167                    (indexu(x-1,y+1),-.25)))
168
169        primal_i, primal_j = [ x.astype(np.int32) for x in lon_i, lat_i]
170        edge_i, edge_j = [ x.astype(np.int32) for x in lon_e, lat_e]
171        lon_i, lon_v, lon_e = [(x+.5)*dx-.5*Lx for x in lon_i, lon_v, lon_e]
172        lat_i, lat_v, lat_e = [(y+.5)*dy-.5*Ly for y in lat_i, lat_v, lat_e]
173
174        Aiv[:]=dx*dy # Ai=Av=dx*dy
175        le_de=le/de
176        unst.init_mesh(llm,nqdyn,2*nx*ny,nx*ny,nx*ny,4,4,4,
177                  primal_deg,primal_edge,primal_ne,
178                  dual_deg,dual_edge,dual_ne,dual_vertex,
179                  left,right,down,up,trisk_deg,trisk,
180                  Aiv,Aiv,f+0.*Aiv,le_de,Riv2,wee)
181        self.set_members(locals(), 'primal_deg', 'primal_edge', 'primal_ne', 'primal_vertex',
182                         'dual_deg', 'dual_edge', 'dual_ne', 'dual_vertex',
183                         'left','right','down','up',
184                         'trisk_deg','trisk','Riv2','wee',
185                         'lon_i','lat_i','lon_v','lat_v',
186                         'le_de','le','de','lon_e','lat_e','angle_e',
187                         'primal_i','primal_j','edge_i','edge_j')
188        self.Ai, self.Av = Aiv, Aiv
189    def ncwrite(self, name):
190        """The method writes Cartesian mesh on the disc.
191            Args: Mesh parameters"""
192
193        #----------------OPENING NETCDF OUTPUT FILE------------
194
195        try:
196            f = cdf.Dataset(name, 'w', format='NETCDF4')
197        except:
198            print("Cartesian_mesh.ncwrite : Error occurred while opening new netCDF mesh file")
199            raise
200
201        #----------------DEFINING DIMENSIONS--------------------
202
203        for dimname, dimsize in [("primal_cell", self.primal_deg.size),
204                                 ("dual_cell", self.dual_deg.size),
205                                 ("edge", self.left.size),
206                                 ("primal_edge_or_vertex", self.primal_edge.shape[1]),
207                                 ("dual_edge_or_vertex", self.dual_edge.shape[1]),
208                                 ("TWO", 2),
209                                 ("trisk_edge", 4)]:
210            f.createDimension(dimname,dimsize)
211
212        #----------------DEFINING VARIABLES---------------------
213       
214        f.description = "Cartesian_mesh"
215        f.meshtype, f.nx, f.ny = 'curvilinear', self.nx, self.ny
216
217        def create_vars(dimname, info):
218            for vname, vtype, vdata in info:
219                var = f.createVariable(vname,vtype,dimname)
220                var[:] = vdata
221               
222        create_vars("primal_cell", 
223                    [("primal_deg","i4",self.primal_deg), 
224                     ("primal_i","i4",self.primal_i),
225                     ("primal_j","i4",self.primal_j),
226                     ("Ai","f8",self.Ai),
227                     ("lon_i","f8",self.lon_i),
228                     ("lat_i","f8",self.lat_i)] )
229
230        create_vars("dual_cell", 
231                    [("dual_deg","i4",self.dual_deg), 
232                     ("Av","f8",self.Av),
233                     ("lon_v","f8",self.lon_v),
234                     ("lat_v","f8",self.lat_v),
235                     ] )
236
237        create_vars( ("primal_cell","primal_edge_or_vertex"), 
238                     [("primal_edge", "i4", self.primal_edge),
239                      ("primal_ne", "i4", self.primal_ne),
240                      ("primal_vertex", "i4", self.primal_vertex)] )
241
242        create_vars( ("dual_cell","dual_edge_or_vertex"), 
243                     [("dual_edge", "i4", self.dual_edge),
244                      ("dual_vertex","i4",self.dual_vertex),
245                      ("dual_ne","i4",self.dual_ne),
246                      ("Riv2","f8",self.Riv2)] )
247
248        create_vars("edge",
249                    [("trisk_deg","i4",self.trisk_deg),
250                     ("le","f8",self.le),
251                     ("de","f8",self.de),
252                     ("lon_e","f8", self.lon_e),
253                     ("lat_e","f8", self.lat_e),
254                     ("angle_e","f8", self.angle_e),
255                     ("edge_i","i4",self.edge_i),
256                     ("edge_j","i4",self.edge_j)] )
257
258        create_vars( ("edge","TWO"),
259                     [("edge_left_right","i4", concat(self.left,self.right)),
260                      ("edge_down_up","i4", concat(self.down,self.up))] )
261                     
262
263        create_vars( ("edge","trisk_edge"),
264                    [("trisk","i4",self.trisk),
265                     ("wee","f8",self.wee)] )
266
267        f.close()
268
269    def field_theta(self,n=1): return zeros((n,self.nqdyn,self.ny,self.nx,self.llm))
270    def field_mass(self,n=1): return zeros((n,self.ny,self.nx,self.llm))
271    def field_z(self,n=1): return zeros((n,self.ny,self.nx,self.llm))
272    def field_w(self,n=1): return zeros((n,self.ny,self.nx,self.llm+1))
273    def field_u(self,n=1): return zeros((n,self.ny,2*self.nx,self.llm))
274    def field_ps(self,n=1): return zeros((n,self.ny,self.nx))
275    def field_ucomp(self,n=1): return zeros((n,self.ny,self.nx,self.llm))
276    def ucomp(self,u): 
277        return u[range(0,2*self.nx,2),:] if self.ny==1 else u[:,range(0,2*self.nx,2),:]
278    def set_ucomp(self,uv,u):
279        if self.ny==1 : uv[range(0,2*self.nx,2),:]=u
280        else : uv[:,range(0,2*self.nx,2),:]=u
281    def vcomp(self,u):
282        return u[range(1,2*self.nx,2),:] if self.ny==1 else u[:,range(1,2*self.nx,2),:]
283    def set_vcomp(self,uv,v):
284        if self.ny==1 : uv[range(1,2*self.nx,2),:]=v
285        else : uv[:,range(1,2*self.nx,2),:]=v
286
287#---------------------- DYNAMICO format for fully unstructured mesh and curvilinear meshes ----------------------
288
289class Mesh_Format(Base_class):
290    def getdims(self,*names): return [len(self.nc.dimensions[name]) for name in names]
291    def get_pdims(self,comm,*names): return [self.get_pdim(comm,name) for name in names]
292    def get_pvars(self,pdim,*names): return [self.get_pvar(pdim,name) for name in names]
293    def getvars(self,*names): 
294            for name in names : print "getvar %s ..."%name
295            time1=time.time()
296            ret=[self.getvar(name) for name in names]
297            print "... Done in %f seconds"%(time.time()-time1)
298            return ret
299   
300class DYNAMICO_Format(Mesh_Format):
301    """ Helps class Unstructured_Mesh to read a DYNAMICO mesh file."""
302    start_index=1 # indexing starts at 1 as in Fortran
303    def __init__(self,gridfilename):
304        nc = cdf.Dataset(gridfilename, "r")
305        self.nc, self.meshtype = nc, nc.meshtype
306        if self.meshtype == 'curvilinear':
307            self.nx, self.ny = nc.nx, nc.ny
308        print('#NC4: Opening file:',gridfilename)
309    def get_pdim(self,comm,name): return parallel.PDim(self.nc.dimensions[name], comm)
310    def getvar(self,name): return self.nc.variables[name][:]
311    def get_pvar(self,pdim,name):
312        # provides parallel access to a NetCDF variable, with name translation
313        # first deal with special cases
314        if name == 'edge_deg': return parallel.CstPArray1D(pdim, np.int32, 2)
315        # general case
316        return parallel.PArray(pdim, self.nc.variables[name])
317    def normalize(self, mesh, radius): pass
318
319#---------------------- MPAS fully unstructured mesh ------------------------
320
321class MPAS_Format(Mesh_Format):
322    """ Helps class Unstructured_Mesh to read a MPAS mesh file."""
323    start_index=1 # indexing starts at 1 as in Fortran
324    meshtype='unstructured'
325    translate= {
326        'primal_cell':'nCells', 'edge':'nEdges', 'dual_cell':'nVertices',
327        'primal_deg':'nEdgesOnCell', 'trisk_deg':'nEdgesOnEdge',
328        'primal_edge':'edgesOnCell', 'dual_edge':'edgesOnVertex', 
329        'primal_vertex':'verticesOnCell', 'dual_vertex':'cellsOnVertex',
330        'trisk':'edgesOnEdge', 'edge_down_up':'verticesOnEdge', 'edge_left_right':'cellsOnEdge',
331        'le':'dvEdge', 'de':'dcEdge', 'Ai':'areaCell', 'Av':'areaTriangle',
332        'lat_i':'latCell','lon_i':'lonCell','lat_v':'latVertex','lon_v':'lonVertex',
333        'lat_e':'latEdge','lon_e':'lonEdge','angle_e':'angleEdge',
334        'wee':'weightsOnEdge','Riv2':'kiteAreasOnVertex',
335        'primal_ne':'signOnCell', 'dual_ne':'signOnVertex'}
336    def __init__(self,gridfilename):
337        try:
338            self.nc = cdf.Dataset(gridfilename, "r")
339        except RuntimeError:
340            print """
341Unable to open grid file %s, maybe you forgot to download it ?
342To do so, go to the 'Python/' dir and execute './get_MPAS_grids.sh'.
343            """ % gridfile
344            raise
345    def get_pdim(self,comm,name): 
346        name = self.translate[name]
347        return parallel.PDim(self.nc.dimensions[name], comm)
348    def getvar(self,name):
349        # first deal with special cases
350        if name == 'dual_deg':           
351            dual_deg = [3 for i in range(self.dual_num) ]
352            dual_deg = np.ascontiguousarray(dual_deg,dtype=np.int32) 
353            # NB : Fortran code expects 32-bit ints
354            return dual_deg
355        # general case
356        name=self.translate[name]
357        return self.nc.variables[name][:]
358    def get_pvar(self,pdim,name): 
359        # provides parallel access to a NetCDF variable, with name translation
360        # first deal with special cases
361        if name == 'dual_deg': return parallel.CstPArray1D(pdim, np.int32, 3)
362        if name == 'edge_deg': return parallel.CstPArray1D(pdim, np.int32, 2)
363        # general case
364        name=self.translate[name]
365        return parallel.PArray(pdim, self.nc.variables[name])
366    def normalize(self, mesh, radius):
367        (trisk_deg, trisk, Ai,
368         de, le, wee, Av, Riv2) = (mesh.trisk_deg, mesh.trisk, mesh.Ai, 
369                                       mesh.de, mesh.le, mesh.wee, mesh.Av, mesh.Riv2)
370        edge_num, dual_num, r2 = de.size, Av.size, radius**2
371        # fix normalization of wee and Riv2 weights
372        for edge1 in range(edge_num):
373            for i in range(trisk_deg[edge1]):
374                edge2=trisk[edge1,i]-1  # NB Fortran vs C/Python indexing
375                wee[edge1,i] = de[edge1]*wee[edge1,i]/le[edge2]
376        for ivertex in range(dual_num):
377            for j in range(3):
378                Riv2[ivertex,j]=Riv2[ivertex,j]/Av[ivertex]
379            r=Riv2[ivertex,:]
380            r=sum(r)
381            if abs(r-1.)>1e-6 : print 'error with Riv2 at vertex ', ivertex
382        # scale lengths and areas
383        mesh.le, mesh.de, mesh.Av, mesh.Ai = radius*le, radius*de, r2*Av, r2*Ai
384       
385def compute_ne(num,deg,edges,left,right):
386    ne = np.zeros_like(edges)
387    for cell in range(num):
388        for iedge in range(deg[cell]):
389            edge = edges[cell,iedge]-1
390            if left[edge]==cell+1: ne[cell,iedge]+=1
391            if right[edge]==cell+1: ne[cell,iedge]-=1
392#            if ne[cell,iedge]==0 : print 'compute_ne error at cell,iedge', cell, iedge
393    return ne
394
395class Abstract_Mesh(Base_class):
396    def field_theta(self,n=1): return zeros((n,self.nqdyn,self.primal_num,self.llm))
397    def field_mass(self,n=1):  return zeros((n,self.primal_num,self.llm))
398    def field_z(self,n=1):     return zeros((n,self.dual_num,self.llm))
399    def field_w(self,n=1):     return zeros((n,self.primal_num,self.llm+1))
400    def field_u(self,n=1):     return zeros((n,self.edge_num,self.llm))
401    def field_ps(self,n=1):    return zeros((n,self.primal_num,))
402    def ucov2D(self, ulon, ulat): 
403        return self.de*(ulon*np.cos(self.angle_e)+ulat*np.sin(self.angle_e))
404    def ucov3D(self, ulon, ulat):
405        ucov = zeros((self.edge_num,ulon.shape[1]))
406        for edge in range(self.edge_num):
407            angle=self.angle_e[edge]
408            ucov[edge,:] = self.de[edge]*(ulon[edge,:]*math.cos(angle)+ulat[edge,:]*math.sin(angle))
409        return ucov
410    def plot(self, tri,data, **kwargs):
411        plt.figure(figsize=(10,4))
412        plt.gca().set_aspect('equal')
413        plt.tricontourf(tri, data, 20, **kwargs)
414        plt.colorbar()
415        plt.ylim((-90,90))
416        plt.xlim((-180,180))
417    def plot_i(self,data, **kwargs):
418        self.plot(self.primal,data, **kwargs)
419    def plot_v(self,data, **kwargs):
420        self.plot(self.dual,data, **kwargs)
421    def plot_e(self,data, **kwargs):
422        self.plot(self.triedge,data, **kwargs)
423   
424class Unstructured_Mesh(Abstract_Mesh):
425    def __init__(self, gridfile, llm, nqdyn, radius, f):
426        getvars = gridfile.getvars
427        # get positions, lengths, surfaces and weights
428        le,de,Ai,Av,wee,Riv2 = getvars('le','de','Ai','Av','wee','Riv2')
429        lat_i,lon_i,lat_v,lon_v = getvars('lat_i','lon_i','lat_v','lon_v')
430        lat_e,lon_e,angle_e = getvars('lat_e','lon_e','angle_e')
431        fv = f(lon_v,lat_v) # Coriolis parameter
432        primal_num, dual_num, edge_num = [ x.size for x in Ai, Av, le ]
433        gridfile.primal_num, gridfile.dual_num = primal_num, dual_num
434        primal_deg, dual_deg, trisk_deg = getvars('primal_deg','dual_deg','trisk_deg')       
435        # get indices for stencils
436        # primal <-> edges
437        primal_edge, left_right = getvars('primal_edge','edge_left_right')
438        left,right = left_right[:,0], left_right[:,1]
439        primal_ne = compute_ne(primal_num,primal_deg,primal_edge,left,right)
440        # dual <-> edges
441        dual_edge, down_up = getvars('dual_edge','edge_down_up')
442        down,up = down_up[:,0], down_up[:,1]
443        dual_ne = compute_ne(dual_num,dual_deg,dual_edge,up,down)
444        # primal <-> dual, edges <-> edges (TRiSK)
445        primal_vertex, dual_vertex, trisk = getvars('primal_vertex','dual_vertex','trisk')
446        # CRITICAL : force arrays left, etc. to be contiguous in memory:
447        left,right,up,down = [np.ascontiguousarray(x) for x in (left,right,up,down)]
448        trisk,wee,primal_edge = [np.ascontiguousarray(x) for x in (trisk,wee,primal_edge)]
449        self.set_members(locals(), 'gridfile','llm','nqdyn','radius','f',
450                         'primal_num', 'dual_num', 'edge_num', 'primal_deg', 'primal_vertex', 'primal_ne',
451                         'lat_i', 'lon_i', 'lat_v', 'lon_v', 'lat_e', 'lon_e', 'angle_e',
452                         'Ai', 'Av', 'fv', 'le', 'de',
453                         'trisk_deg', 'trisk', 'wee', 'Riv2', 'dual_ne')
454        gridfile.normalize(self, radius)
455        max_primal_deg, max_dual_deg, max_trisk_deg = [ x.shape[1] for x in primal_edge, dual_edge, trisk]
456        unst.init_mesh(llm, nqdyn, edge_num, primal_num,dual_num,
457                  max_trisk_deg, max_primal_deg, max_dual_deg,
458                  primal_deg,primal_edge,primal_ne,
459                  dual_deg,dual_edge,dual_ne,dual_vertex,
460                  left,right,down,up,trisk_deg,trisk,
461                  self.Ai,self.Av,self.fv,le/de,Riv2,wee)
462        self.primal  = tri.Triangulation(lon_i*radian, lat_i*radian)
463        self.dual    = tri.Triangulation(lon_v*radian, lat_v*radian)
464        self.triedge = tri.Triangulation(lon_e*radian, lat_e*radian)       
465       
466class Unstructured_PMesh(Abstract_Mesh): # Mesh data distributed across MPI processes
467    def __init__(self,comm, gridfile):
468        self.comm, self.gridfile, get_pvars = comm, gridfile, gridfile.get_pvars
469        dim_primal, dim_edge, dim_dual = gridfile.get_pdims(comm,
470            'primal_cell','edge','dual_cell')
471        primal_deg, primal_vertex, primal_edge, primal_ne, lon_i, lat_i, Ai = get_pvars(
472            dim_primal, 'primal_deg', 'primal_vertex', 'primal_edge', 'primal_ne', 'lon_i', 'lat_i', 'Ai' )
473        edge_deg, trisk_deg, left_right, down_up, trisk, le, de, wee, lon_e, lat_e, angle_e = get_pvars(
474             dim_edge, 'edge_deg', 'trisk_deg', 'edge_left_right', 'edge_down_up', 
475             'trisk', 'le', 'de', 'wee', 'lon_e', 'lat_e', 'angle_e')
476        dual_deg, dual_vertex, dual_edge, dual_ne, Riv2, lon_v, lat_v, Av = get_pvars(
477            dim_dual, 'dual_deg', 'dual_vertex', 'dual_edge', 'dual_ne', 'Riv2', 'lon_v', 'lat_v', 'Av')
478        if gridfile.meshtype == 'curvilinear':
479            self.primal_i, self.primal_j = get_pvars(dim_primal, 'primal_i','primal_j')
480            self.dual_i, self.dual_j = get_pvars(dim_dual, 'primal_i','primal_j')
481            self.edge_i, self.edge_j = get_pvars(dim_edge, 'edge_i','edge_j')
482
483        # Let indices start at 0
484        for x in (primal_vertex, primal_edge, dual_vertex,dual_edge,
485            left_right, down_up, trisk ) : x.data = x.data-gridfile.start_index
486        self.edge2cell   = Stencil_glob(edge_deg, left_right)
487        self.cell2edge   = Stencil_glob(primal_deg, primal_edge, primal_ne)
488        self.cell2vertex = Stencil_glob(primal_deg, primal_vertex)
489        self.edge2vertex = Stencil_glob(edge_deg, down_up)
490        self.vertex2edge = Stencil_glob(dual_deg, dual_edge, dual_ne)
491        self.vertex2cell = Stencil_glob(dual_deg, dual_vertex, Riv2)
492        self.edge2edge   = Stencil_glob(trisk_deg, trisk, wee)
493        self.set_members(locals(), 'dim_primal', 'dim_dual', 'dim_edge',
494                         'primal_deg', 'primal_vertex', 'primal_edge', 'trisk_deg', 'trisk',
495                         'lon_v', 'lat_v', 'Av', 'lon_i', 'lat_i', 'Ai', 
496                         'le', 'de', 'lon_e', 'lat_e', 'angle_e')
497    def partition_metis(self):
498        print 'Partitioning with ParMetis...'
499        edge_owner = unst.partition_mesh(self.trisk_deg, self.trisk, self.comm.Get_size())
500        self.edge_owner = parallel.LocPArray1D(self.dim_edge, edge_owner)
501        primal_owner = partition_from_stencil(self.edge_owner, self.primal_deg, self.primal_edge)
502        self.primal_owner = parallel.LocPArray1D(self.dim_primal, primal_owner)
503        dual_owner = partition_from_stencil(self.edge_owner, self.dual_deg, self.dual_edge)
504        self.dual_owner = parallel.LocPArray1D(self.dim_dual, dual_owner)
505
506    def partition_curvilinear(self, ni,nj):
507        def owner(dim,i,j):
508            owner_i, owner_j =  (ni*i.data)/nx, (nj*j.data)/ny
509            return parallel.LocPArray1D(dim, owner_i + ni*owner_j)
510        nx, ny = self.gridfile.nx, self.gridfile.ny
511        print 'Partitioning %d x %d cells in %d x %d blocks ...'%(nx,ny,ni,nj)
512        n = self.comm.Get_size()
513        assert n == ni*nj, 'Mismatch between ni x nj = %d and MPI_SIZE = %d.'%(ni*nj, n)
514        self.edge_owner   = owner(self.dim_edge,   self.edge_i,   self.edge_j)
515        self.primal_owner = owner(self.dim_primal, self.primal_i, self.primal_j)
516        self.dual_owner = self.primal_owner
517        print 'partition_curvilinear %d :'%(self.comm.Get_rank()), self.primal_owner.data
518
519    def get(self, getter, *names): return [ getter(self.__dict__[x]) for x in names ]
520
521def chain(start, links):
522    for link in links:
523        start = link(start).neigh_set
524        yield start
525
526def patches(degree, bounds, lon, lat):
527    for i in range(degree.size):
528        nb_edge=degree[i]
529        bounds_cell = bounds[i,0:nb_edge]
530        lat_cell    = lat[bounds_cell]
531        lon_cell    = lon[bounds_cell]
532        orig=lon_cell[0]
533        lon_cell    = lon_cell-orig+180.
534        lon_cell    = np.mod(lon_cell,360.)
535        lon_cell    = lon_cell+orig-180.
536#        if np.abs(lon_cell-orig).max()>10. :
537#            print '%d patches :'%mpi_rank, lon_cell
538        lonlat_cell = np.zeros((nb_edge,2))
539        lonlat_cell[:,0],lonlat_cell[:,1] = lon_cell,lat_cell
540        polygon = Polygon(lonlat_cell, True)
541        yield polygon
542
543class Local_Mesh(Abstract_Mesh):
544    def __init__(self, pmesh, llm, nqdyn, radius, f):
545        comm, dim_primal, primal_owner, dim_edge, edge_owner, dim_dual, dual_owner = pmesh.members(
546            'comm', 'dim_primal', 'primal_owner', 'dim_edge', 'edge_owner', 'dim_dual', 'dual_owner' )
547        print 'Constructing halos ...'
548        edges_E0 = find_my_cells(edge_owner)
549        cells_C0, edges_E1, vertices_V1, edges_E2, cells_C1 = chain(
550            edges_E0, ( pmesh.edge2cell, pmesh.cell2edge, pmesh.edge2vertex, pmesh.vertex2edge, pmesh.edge2cell) )
551        edges_E0, edges_E1, edges_E2 = progressive_list(edges_E0, edges_E1, edges_E2)
552        cells_C0, cells_C1 = progressive_list(cells_C0, cells_C1)
553        print 'E2,E1,E0 ; C1,C0 : ', map(len, (edges_E2, edges_E1, edges_E0, cells_C1, cells_C0))
554        get_own_cells, get_all_cells = dim_primal.getter(cells_C0), dim_primal.getter(cells_C1)
555        get_all_duals, get_all_edges = dim_dual.getter(vertices_V1), dim_edge.getter(edges_E2)
556       
557        self.lon_i, self.lat_i, self.Ai = pmesh.get(get_all_cells, 'lon_i', 'lat_i', 'Ai')
558        self.lon_v, self.lat_v, self.Av = pmesh.get(get_all_duals, 'lon_v', 'lat_v', 'Av')
559        self.fv = f(self.lon_v,self.lat_v) # Coriolis parameter
560        self.le, self.de, self.lon_e, self.lat_e, self.angle_e = pmesh.get(
561            get_all_edges, 'le', 'de', 'lon_e', 'lat_e', 'angle_e')
562        primal_num, dual_num, edge_num = map(len, (cells_C1, vertices_V1, edges_E2))
563
564        self.meshtype = pmesh.gridfile.meshtype
565        if self.meshtype == 'curvilinear' :
566            self.nx, self.ny = pmesh.gridfile.nx, pmesh.gridfile.ny
567            self.primal_i, self.primal_j = pmesh.get(get_all_cells, 'primal_i', 'primal_j')
568            self.dual_i, self.dual_j = pmesh.get(get_all_duals, 'dual_i', 'dual_j')
569
570
571        # construct local stencils from global stencils
572        dict_E2, dict_C1, dict_V1 = map(inverse_list, (edges_E2, cells_C1, vertices_V1))
573        down_up       = pmesh.edge2vertex( edges_E2,    dict_V1)
574        left_right    = pmesh.edge2cell(   edges_E2,    dict_C1)
575        primal_edge   = pmesh.cell2edge(   cells_C1,    dict_E2)
576        dual_edge     = pmesh.vertex2edge( vertices_V1, dict_E2)
577        trisk         = pmesh.edge2edge(   edges_E2,    dict_E2)
578        Riv2          = pmesh.vertex2cell( vertices_V1, dict_C1)
579        print 'Edge-vertex degree min/max %d %d'%(down_up.degree.min(), down_up.degree.max() )
580        print 'Edge-cell degree min/max %d %d'%(left_right.degree.min(), left_right.degree.max() )
581        print 'Primal degree min/max : %d %d'%(primal_edge.degree.min(), primal_edge.degree.max() )
582        print 'Dual degree min/max : %d %d'%(dual_edge.degree.min(), dual_edge.degree.max() )
583        print 'TRiSK degree min/max : %d %d'%(trisk.degree.min(), trisk.degree.max() )
584        print 'Riv2 degree min/max : %d %d'%(Riv2.degree.min(), Riv2.degree.max() )
585        primal_deg, primal_edge, primal_ne = primal_edge.degree,   primal_edge.neigh_loc, primal_edge.weight
586        dual_deg,   dual_edge, dual_ne  = dual_edge.degree, dual_edge.neigh_loc, dual_edge.weight
587        trisk_deg,  trisk, wee      = trisk.degree,         trisk.neigh_loc, trisk.weight
588        dual_deg, dual_vertex, Riv2 = Riv2.degree,          Riv2.neigh_loc,  Riv2.weight
589        edge_deg, left, right = left_right.degree, left_right.neigh_loc[:,0], left_right.neigh_loc[:,1]
590        edge_deg, down, up    = down_up.degree,    down_up.neigh_loc[:,0],    down_up.neigh_loc[:,1]
591        for edge in range(edge_num): 
592            if edge_deg[edge]<2: up[edge]=down[edge]
593        max_primal_deg, max_dual_deg, max_trisk_deg = [x.shape[1] for x in primal_edge, dual_edge, trisk]
594
595        # construct own primal mesh for XIOS output
596        primal_own_glo = find_my_cells(primal_owner)
597        primal_vertex = pmesh.cell2vertex(primal_own_glo, dict_V1)
598        primal_own_deg, primal_vertex = primal_vertex.degree, primal_vertex.neigh_loc
599        primal_own_glo = np.asarray(primal_own_glo, dtype=np.int32)
600        primal_own_loc = [dict_C1[i] for i in primal_own_glo]
601        primal_own_num = len(primal_own_glo)
602        primal_own_all = comm.allgather(primal_own_num)
603        displ = sum(primal_own_all[0:comm.Get_rank()]) # required by XIOS
604        print 'local primal mesh :', primal_vertex.min(), primal_vertex.max(), primal_own_all, displ
605
606        # construct own dual mesh for XIOS output
607        dual_own_glo = find_my_cells(dual_owner)
608        dual_own_glo = np.asarray(primal_own_glo, dtype=np.int32)
609        dual_own_loc = [dict_V1[i] for i in dual_own_glo]
610       
611        # compute_ne(...) and normalize(...) expect indices starting at 1
612        trisk, dual_vertex, primal_edge, dual_edge =  trisk+1, dual_vertex+1, primal_edge+1, dual_edge+1
613        left, right, down, up = left+1, right+1, down+1, up+1
614
615        # store what we have computed
616        le_de = self.le/self.de
617        self.set_members(locals(), 'nqdyn', 'llm', 'primal_num', 'dual_num', 'edge_num',
618                         'primal_own_glo', 'primal_own_loc', 'primal_own_deg',
619                         'primal_deg', 'primal_vertex', 'displ', 'dual_own_loc', 
620                         'trisk_deg', 'trisk', 'wee', 
621                         'dual_deg', 'dual_edge', 'dual_ne', 'dual_vertex', 'Riv2',
622                         'left','right','primal_deg','primal_edge','primal_ne','le_de',
623                         'vertices_V1', 'edges_E2')
624
625        # normalize and propagate info to Fortran side
626        pmesh.gridfile.normalize(self,radius)
627        unst.init_mesh(llm, nqdyn, self.edge_num, self.primal_num, self.dual_num,
628                  max_trisk_deg, max_primal_deg, max_dual_deg,
629                  primal_deg,primal_edge,primal_ne,
630                  dual_deg,dual_edge,dual_ne,dual_vertex,
631                  left,right,down,up,trisk_deg,trisk,
632                  self.Ai,self.Av,self.fv,le_de,Riv2,wee)
633
634        # setup halo transfers - NB : llm must be set before we call set_dynamico_transfer
635        self.com_primal = parallel.Halo_Xchange(
636            42, dim_primal, cells_C1, get_all_cells(primal_owner))
637        self.com_edges = parallel.Halo_Xchange(
638            73, dim_edge, edges_E2, get_all_edges(edge_owner))
639        self.com_primal.set_dynamico_transfer('primal')
640        self.com_edges.set_dynamico_transfer('edge')
641
642        self.primal  = tri.Triangulation(self.lon_i*radian, self.lat_i*radian)
643        self.dual    = tri.Triangulation(self.lon_v*radian, self.lat_v*radian)
644        self.triedge = tri.Triangulation(self.lon_e*radian, self.lat_e*radian)       
645
646    def plot_patches(self, ax, clim, degree, bounds, lon, lat, data):
647        nb_vertex = lon.size # global
648        p = list(patches(degree, bounds, lon, lat))
649        p = PatchCollection(p, linewidth=0.01)
650        p.set_array(data) # set values at each polygon (cell)
651        p.set_clim(clim)
652        ax.add_collection(p)
653
654#--------------------------------------  Mesh reordering  ------------------------------------------#
655
656# NB : Indices start at 0
657# permutations map an old index to a new index : perm[old_index]=new_index
658# enumerate(perm) is the list [ (0,perm[0]) , (1,perm[1]), ...] = [(old, new) ... ]
659
660def reorder_values(perm, *datas): 
661    # data in datas : ndarray containing indices ; perm : permutation to apply to values
662    for data in datas:
663        for x in np.nditer(data, op_flags=['readwrite']):
664            x[...]=perm[x]
665
666def reorder_indices(perm, *datas): 
667    # data in datas : ndarray containing values ; perm : permutation to apply to indices
668    for data in datas:
669        data_out, ndim = data.copy(), data.ndim
670        if ndim == 1:
671            for old,new in enumerate(perm):
672                data_out[new]=data[old]
673        if ndim == 2:
674            for old,new in enumerate(perm):
675                data_out[new,:]=data[old,:]
676        data[:]=data_out[:]
677
678def key_to_perm(keys): 
679    # keys : maps an old index to a key
680    # returns : a list mapping old indices to new indices obtained by sorting keys in ascending order
681    ordered = [old for key,old in sorted(zip(keys,range(len(keys))))] 
682    # ordered maps new indices to old indices
683    # but we want to map old indices to new indices
684    perm = list(range(len(keys)))
685    for new,old in enumerate(ordered):
686        perm[old]=new
687    return perm  # maps old indices to new indices
688
689def toint(x): return np.int32((x+1.)*65536)
690   
691def morton_index(x,y,z):
692    nn, xx, yy, zz = x.size, toint(x), toint(y), toint(z)
693    idx=np.zeros(nn, dtype=np.int64)
694    unst.ker.dynamico_morton_encode(nn, xx,yy,zz, idx)
695    return idx
696
697#-------------------------------------- Mesh partitioning ------------------------------------------#
698
699# NB : Indices start at 0
700
701def partition_from_stencil(owner2, degree, stencil):
702    # given a stencil dim1->dim2 and owner2 on dim2, define owner[i] on dim1 as min(stencil[i,:] if i is even, max if odd
703    dim1, dim2= degree.dim, owner2.dim
704    degree, stencil, n = degree.data, stencil.data, dim1.end-dim1.start
705    cells2 = list_stencil(degree, stencil)
706    cells2 = sorted(list(set(list(cells2)))) # list of cells for which we need to know owner2
707    get2 = dim2.getter(cells2)
708    owner1, owner2, glob2loc = np.zeros(n, dtype=np.int32), get2(owner2), get2.dict
709    for i in range(n):
710        owners = [ owner2[glob2loc[stencil[i,j]]] for j in range(degree[i]) ]
711        if i%2 == 0 : owner1[i] = min(owners)
712        else : owner1[i] = max(owners)
713    return owner1
714           
715def find_my_cells(owner): # owner : a PArray1D containing the data returned by partition_mesh
716    dim, comm, owner = owner.dim, owner.dim.comm, owner.data
717    mpi_rank, mpi_size = comm.Get_rank(), comm.Get_size()
718    cells=[set() for i in range(mpi_size)]
719    for i in range(len(owner)):
720        cells[owner[i]].add(dim.start+i)
721    cells = [sorted(list(cells[i])) for i in range(mpi_size)]
722    mycells = comm.alltoall(cells)
723    mycells = sorted(sum(mycells, [])) # concatenate into a single list
724    return mycells
725
726#---------------------------------- Stencil management ---------------------------------------#
727
728# NB : Indices start at 0
729
730# Class Stencil represents an adjacency relationship (e.g. cell=>edges)
731# using adjacency information read from PArrays
732# It computes a list of "edges" adjacent to a given list of "cells"
733# This is used to form the sets E0 -> C0 -> E1 -> V1 -> E2 -> C1
734# which are then used to form lists of global indices for V1,C1,E2 such that
735# C0, E0, E1 form contiguous subsets of C1, E2 starting from 0
736
737def reindex(vertex_dict, degree, bounds):
738    for i in range(degree.size):
739        for j in range(degree[i]):
740            bounds[i,j] = vertex_dict[bounds[i,j]]
741    return bounds
742
743class Stencil_glob:
744    def __init__(self, degree, neigh, weight=None):
745        self.degree, self.neigh, self.weight = degree, neigh, weight
746    def __call__(self, cells, neigh_dict=None):
747        return Stencil(cells, self.degree, self.neigh, neigh_dict, self.weight)
748           
749class Stencil:
750    def __init__(self, cells, degree, neigh, neigh_dict, weight=None):
751        get = degree.dim.getter(cells)
752        mydegree, myneigh = [get(x) for x in (degree, neigh)]
753        if not weight is None : myweight = get(weight)
754        if neigh_dict is None : 
755            keep = lambda n : True
756        else : # if neigh_dict is present, only neighbours in dict are retained
757            keep = lambda n : n in neigh_dict
758        neigh_set = list_stencil(mydegree, myneigh, keep)
759        self.neigh_set = list(set(list(neigh_set)     ))                         
760        rej=0
761        for i in range(len(mydegree)): # keep only elements in neigh_dict, in-place
762            k=0
763            for j in range(mydegree[i]):
764                n=myneigh[i,j]
765                if keep(n):
766                    myneigh[i,k]=n
767                    if not weight is None : myweight[i,k]=myweight[i,j]
768                    k=k+1
769                else:
770                    rej=rej+1
771            mydegree[i]=k
772        if neigh_dict is None:
773            neigh_dict = {j:i for i,j in enumerate(self.neigh_set)}
774        myneigh_loc = reindex(neigh_dict, mydegree, myneigh)
775        self.degree, self.neigh_glob, self.neigh_loc = mydegree, myneigh, myneigh_loc
776        if weight is not None: self.weight = myweight
777           
778def progressive_iter(mylist, cell_lists):
779    for thelist in cell_lists: 
780        mylist = mylist + list(set(thelist)-set(mylist))
781        yield mylist
782def progressive_list(*cell_lists):
783    # cell_lists : a tuple of lists of global indices, with each list a subset of the next
784    # returns : a list 'mylist' such that for each list 'thelist' in cell_lists, thelist = mylist[0:len(thelist)]
785    # example : edge_list = progressive_list(E0,E1,E2) with E0,E1,E2 increasing lists of cell edges
786    return list(progressive_iter([], cell_lists))
Note: See TracBrowser for help on using the repository browser.