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

Last change on this file since 639 was 639, checked in by dubos, 6 years ago

devel/unstructured : Python interface to ARK time scheme

File size: 16.5 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 unstructured import init_mesh
8
9radian=180/math.pi
10
11#------------------- Hybrid mass-based coordinate -------------
12
13# compute hybrid coefs from distribution of mass
14
15def compute_hybrid_coefs(mass):
16    if mass.ndim==2 :
17        nx,llm=mass.shape
18        mass_dak = np.zeros((nx,llm))
19        mass_dbk = np.zeros((nx,llm))
20        mass_bl = np.zeros((nx,llm+1))+1.
21        for i in range(nx):
22            m_i = mass[i,:]
23            dbk_i = m_i/sum(m_i)
24            mass_dbk[i,:] = dbk_i
25            mass_bl[i,1:]= 1.-dbk_i.cumsum()
26    if mass.ndim==3 :
27        nx,ny,llm=mass.shape
28        mass_dak = np.zeros((nx,ny,llm))
29        mass_dbk = np.zeros((nx,ny,llm))
30        mass_bl = np.zeros((nx,ny,llm+1))+1.
31        for i in range(nx):
32            for j in range(ny):
33                m_ij = mass[i,j,:]
34                dbk_ij = m_ij/sum(m_ij)
35                mass_dbk[i,j,:] = dbk_ij
36                mass_bl[i,j,1:]= 1.-dbk_ij.cumsum()
37
38    return mass_bl, mass_dak, mass_dbk
39
40#----------------------- Cartesian mesh -----------------------
41
42def squeeze(dims): return np.zeros([n for n in dims if n>1])
43
44# arrays is a list of arrays
45# vals is a list of tuples
46# each tuple is stored in each array
47def put(ij, deg, arrays, vals):
48    k=0
49    for vv in vals: # vv is a tuple of values to be stored in arrays
50        for array,v in zip(arrays,vv):
51            array[ij,k]=v
52        k=k+1       
53    deg[ij]=k
54
55class Cartesian_mesh:
56    def __init__(self,nx,ny,llm,nqdyn,Lx,Ly,f):
57        dx,dy = Lx/nx, Ly/ny
58        self.dx, self.dy, self.f = dx,dy,f
59        self.nx, self.ny, self.llm, self.nqdyn = nx,ny,llm,nqdyn
60        self.field_z = self.field_mass
61        # 1D coordinate arrays
62        x=(np.arange(nx)-nx/2.)*Lx/nx
63        y=(np.arange(ny)-ny/2.)*Ly/ny
64        lev=np.arange(llm)
65        levp1=np.arange(llm+1)
66        self.x, self.y, self.lev, self.levp1 = x,y,lev,levp1
67        # 3D coordinate arrays
68        self.xx,self.yy,self.ll = np.meshgrid(x,y,lev, indexing='ij')       
69        self.xxp1,self.yyp1,self.llp1 = np.meshgrid(x,y,levp1, indexing='ij')
70        # beware conventions for indexing
71        # Fortran order : llm,nx*ny,nqdyn  / indices start at 1
72        # Python order : nqdyn,ny,nx,llm   / indices start at 0
73        # indices below follow Fortran while x,y follow Python/C
74        index=lambda x,y : ((x+(nx*(y+2*ny)))%(nx*ny))+1
75        indexu=lambda x,y : 2*index(x,y)-1
76        indexv=lambda x,y : 2*index(x,y)
77        indices = lambda shape : np.zeros(shape,np.int32)
78
79        primal_nb = indices(nx*ny)
80        primal_edge = indices((nx*ny,4))
81        primal_ne = indices((nx*ny,4))
82   
83        dual_nb = indices(nx*ny)
84        dual_edge = indices((nx*ny,4))
85        dual_ne = indices((nx*ny,4))
86        dual_vertex = indices((nx*ny,4))
87        Riv2 = np.zeros((nx*ny,4))
88   
89        left = indices(2*nx*ny)
90        right = indices(2*nx*ny)
91        up = indices(2*nx*ny)
92        down = indices(2*nx*ny)
93        le_de = np.zeros(2*nx*ny)
94   
95        trisk_deg = indices(2*nx*ny)
96        trisk = indices((2*nx*ny,4)) # 4 TRiSK coefs per edge
97        wee = np.zeros((2*nx*ny,4))
98   
99        for x in range(nx):
100            for y in range(ny):
101                # NB : Fortran indices start at 1
102                # primal cells
103                put(index(x,y)-1,primal_nb,(primal_edge,primal_ne),
104                    ((indexu(x,y),1),
105                     (indexv(x,y),1),
106                     (indexu(x-1,y),-1),
107                     (indexv(x,y-1),-1) ))
108                # dual cells
109                put(index(x,y)-1,dual_nb,(dual_edge,dual_vertex,dual_ne,Riv2),
110                   ((indexv(x+1,y),index(x,y),1,.25),
111                    (indexu(x,y+1),index(x+1,y),-1,.25),
112                    (indexv(x,y),index(x+1,y+1),-1,.25),
113                    (indexu(x,y),index(x,y+1),1,.25) ))
114                # edges :
115                # left and right are adjacent primal cells
116                # flux is positive when going from left to right
117                # up and down are adjacent dual cells (vertices)
118                # circulation is positive when going from down to up
119                # u-points
120                ij =indexu(x,y)-1 
121                left[ij]=index(x,y)
122                right[ij]=index(x+1,y)
123                down[ij]=index(x,y-1)
124                up[ij]=index(x,y)
125                le_de[ij]=dy/dx
126                put(ij,trisk_deg,(trisk,wee),(
127                    (indexv(x,y),-.25),
128                    (indexv(x+1,y),-.25),
129                    (indexv(x,y-1),-.25),
130                    (indexv(x+1,y-1),-.25)))
131                # v-points
132                ij = indexv(x,y)-1
133                left[ij]=index(x,y)
134                right[ij]=index(x,y+1)
135                down[ij]=index(x,y)
136                up[ij]=index(x-1,y)
137                le_de[ij]=dx/dy
138                put(ij,trisk_deg,(trisk,wee),(
139                    (indexu(x,y),.25),
140                    (indexu(x-1,y),.25),
141                    (indexu(x,y+1),.25),
142                    (indexu(x-1,y+1),.25)))
143        Aiv=np.zeros(nx*ny)+dx*dy # Ai=Av=dx*dy
144        init_mesh(llm,nqdyn,2*nx*ny,nx*ny,nx*ny,4,4,4,
145                  primal_nb,primal_edge,primal_ne,
146                  dual_nb,dual_edge,dual_ne,dual_vertex,
147                  left,right,down,up,trisk_deg,trisk,
148                  Aiv,Aiv,f+0.*Aiv,le_de,Riv2,-wee)
149
150    def field_theta(self): return squeeze((self.nqdyn,self.ny,self.nx,self.llm))
151    def field_mass(self): return squeeze((self.ny,self.nx,self.llm))
152    def field_z(self): return squeeze((self.ny,self.nx,self.llm))
153    def field_w(self): return squeeze((self.ny,self.nx,self.llm+1))
154    def field_u(self): return np.zeros((self.ny,2*self.nx,self.llm))
155    def field_ps(self): return squeeze((self.ny,self.nx))
156    def ucomp(self,u): return u[:,range(0,2*self.nx,2),:]
157    def set_ucomp(self,uv,u): uv[:,range(0,2*self.nx,2),:]=u
158    def vcomp(self,u): return u[:,range(1,2*self.nx,2),:]
159
160#---------------------- MPAS fully unstructured mesh ------------------------
161
162def compute_ne(num,deg,edges,left,right):
163    ne = np.zeros_like(edges)
164    for cell in range(num):
165        for iedge in range(deg[cell]):
166            edge = edges[cell,iedge]-1
167            if left[edge]==cell+1: ne[cell,iedge]+=1
168            if right[edge]==cell+1: ne[cell,iedge]-=1
169            if ne[cell,iedge]==0 : print 'error at cell,iedge', cell, iedge
170    return ne
171
172def plot(tri,data):
173    plt.figure(figsize=(12,4))
174    plt.gca().set_aspect('equal')
175    plt.tricontourf(tri, data, 20)
176    plt.colorbar()
177    plt.ylim((-90,90))
178    plt.xlim((0,360))
179
180class MPAS_Mesh:
181    def __init__(self, gridfile, llm, nqdyn, radius, f):
182        self.gridfile, self.llm, self.nqdyn = gridfile,llm,nqdyn
183        self.radius, self.f = radius, f
184        # open mesh file, get main dimensions
185        try:
186            nc = cdf.Dataset(gridfile, "r")
187        except RuntimeError:
188            print """
189Unable to open grid file %s, maybe you forgot to download it ?
190To do so, go to the 'Python/' dir and execute './get_MPAS_grids.sh'.
191            """ % gridfile
192            raise
193
194        def getdims(*names): return [len(nc.dimensions[name]) for name in names]
195        def getvars(*names): 
196            for name in names : print "getvar %s ..."%name
197            time1=time.time()
198            ret=[nc.variables[name][:] for name in names]
199            print "... Done in %f seconds"%(time.time()-time1)
200            return ret
201        primal_num, edge_num, dual_num = getdims('nCells','nEdges','nVertices')                                   
202        print 'Number of primal cells, dual cells and edges : %d, %d, %d '%(primal_num,dual_num,edge_num)
203        primal_deg, trisk_deg = getvars('nEdgesOnCell','nEdgesOnEdge')
204        dual_deg = [3 for i in range(dual_num) ]
205        dual_deg = np.ascontiguousarray(dual_deg,dtype=np.int32) # NB : Fortran code expects 32-bit ints
206
207        # get indices for stencils
208        # primal -> vertices (unused)
209        primal_vertex, dual_vertex = getvars('verticesOnCell','cellsOnVertex')
210        # primal <-> edges
211        primal_edge, left_right = getvars('edgesOnCell','cellsOnEdge')
212        left,right = left_right[:,0], left_right[:,1]
213        primal_ne = compute_ne(primal_num,primal_deg,primal_edge,left,right)
214        # dual <-> edges
215        dual_edge, down_up = getvars('edgesOnVertex','verticesOnEdge')
216        down,up = down_up[:,0], down_up[:,1]
217        dual_ne = compute_ne(dual_num,dual_deg,dual_edge,up,down)
218        # primal <-> dual, edges <-> edges (TRiSK)
219        dual_vertex, trisk = getvars('cellsOnVertex','edgesOnEdge')
220        # get positions, lengths, surfaces and weights
221        le,de,Ai,Av = getvars('dvEdge','dcEdge','areaCell','areaTriangle')
222        lat_i,lon_i = getvars('latCell','lonCell')
223        lat_v,lon_v = getvars('latVertex','lonVertex')
224        lat_e,lon_e,angle_e = getvars('latEdge','lonEdge','angleEdge')
225        wee,Riv2 = getvars('weightsOnEdge','kiteAreasOnVertex')
226
227        # fix normalization of wee and Riv2 weights
228        for edge1 in range(edge_num):
229            for i in range(trisk_deg[edge1]):
230                edge2=trisk[edge1,i]-1  # NB Fortran vs C/Python indexing
231                wee[edge1,i] = de[edge1]*wee[edge1,i]/le[edge2]
232        for ivertex in range(dual_num):
233            for j in range(3):
234                Riv2[ivertex,j]=Riv2[ivertex,j]/Av[ivertex]
235            r=Riv2[ivertex,:]
236            r=sum(r)
237            if abs(r-1.)>1e-6 : print 'error with Riv2 at vertex ', ivertex
238           
239        max_primal_deg, max_dual_deg, max_trisk_deg = getdims('maxEdges','vertexDegree','maxEdges2')
240
241        # CRITICAL : force arrays left, etc. to be contiguous in memory:
242        left,right,up,down = [np.ascontiguousarray(x) for x in (left,right,up,down)]
243        trisk,wee,primal_edge = [np.ascontiguousarray(x) for x in (trisk,wee,primal_edge)]
244       
245        print ('Max stencil sizes (div,curl,trisk) : %d, %d, %d' 
246               % (max_primal_deg, max_dual_deg, max_trisk_deg) )
247
248        r2 = radius**2
249        Av = r2*Av
250        fv = f(lon_v,lat_v) # Coriolis parameter
251        self.Ai, self.Av, self.fv = r2*Ai,Av,fv
252        self.le, self.de, self.le_de = radius*le, radius*de, le/de
253        self.trisk_deg, self.trisk, self.wee = trisk_deg, trisk, wee
254        init_mesh(llm, nqdyn, edge_num, primal_num,dual_num,
255                  max_trisk_deg, max_primal_deg, max_dual_deg,
256                  primal_deg,primal_edge,primal_ne,
257                  dual_deg,dual_edge,dual_ne,dual_vertex,
258                  left,right,down,up,trisk_deg,trisk,
259                  self.Ai,self.Av,self.fv,le/de,Riv2,wee)
260       
261        for edge in range(edge_num):
262            iedge = trisk[edge,0:trisk_deg[edge]]
263            if iedge.min()<1 :
264                print 'error'
265                         
266        self.primal_num, self.edge_num, self.dual_num = primal_num, edge_num, dual_num
267        def period(x) : return (x+2*math.pi)%(2*math.pi)
268        lon_i, lon_v, lon_e = map(period, (lon_i,lon_v,lon_e))
269        self.lon_i, self.lat_i = lon_i, lat_i
270        self.lon_v, self.lat_v = lon_v, lat_v
271        self.lon_e, self.lat_e, self.angle_e = lon_e, lat_e, angle_e
272        self.primal_deg, self.primal_vertex = primal_deg, primal_vertex
273        self.primal = tri.Triangulation(lon_i*180./math.pi, lat_i*180./math.pi)
274        self.dual_deg, self.dual_vertex = dual_deg, dual_vertex
275        self.dual = tri.Triangulation(lon_v*180./math.pi, lat_v*180./math.pi)
276        self.triedge = tri.Triangulation(lon_e*180./math.pi, lat_e*180./math.pi)
277        self.dx = de.min()
278        self.lon3D_i, self.ll3D = np.meshgrid(lon_i, range(llm))
279        self.lat3D_i, self.ll3D = np.meshgrid(lat_i, range(llm))
280    def field_theta(self,n=1): return squeeze((n,self.nqdyn,self.primal_num,self.llm))
281    def field_mass(self,n=1):  return squeeze((n,self.primal_num,self.llm))
282    def field_z(self,n=1):     return squeeze((n,self.dual_num,self.llm))
283    def field_w(self,n=1):     return squeeze((n,self.primal_num,self.llm+1))
284    def field_u(self,n=1):     return squeeze((n,self.edge_num,self.llm))
285    def field_ps(self,n=1):    return squeeze((n,self.primal_num,))
286    def ucov2D(self, ulon, ulat): 
287        return self.de*(ulon*np.cos(self.angle_e)+ulat*np.sin(self.angle_e))
288    def ucov3D(self, ulon, ulat):
289        ucov = np.zeros((self.edge_num,ulon.shape[1]))
290        for edge in range(self.edge_num):
291            angle=self.angle_e[edge]
292            ucov[edge,:] = self.de[edge]*(ulon[edge,:]*math.cos(angle)+ulat[edge,:]*math.sin(angle))
293        return ucov
294    def plot_i(self,data):
295        plot(self.primal,data)
296    def plot_v(self,data):
297        plot(self.dual,data)
298    def plot_e(self,data):
299        plot(self.triedge,data)
300
301#-------------------------------------- Mesh partitioning ------------------------------------------#
302
303def partition_from_stencil(owner2, degree, stencil):
304    # 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
305    dim1, dim2= degree.dim, owner2.dim
306    degree, stencil, n = degree.data, stencil.data, dim1.end-dim1.start
307    cells2 = list_stencil(degree, stencil)
308    cells2 = sorted(list(set(list(cells2)))) # list of cells for which we need to know owner2
309    get2 = dim2.getter(cells2)
310    owner1, owner2, glob2loc = np.zeros(n, dtype=np.int32), get2(owner2), get2.dict
311    for i in range(n):
312        owners = [ owner2[glob2loc[stencil[i,j]]] for j in range(degree[i]) ]
313        if i%2 == 0 : owner1[i] = min(owners)
314        else : owner1[i] = max(owners)
315    return owner1
316           
317def find_my_cells(owner): # a PArray1D containing the data returned by partition_mesh
318    dim, comm, owner = owner.dim, owner.dim.comm, owner.data
319    mpi_rank, mpi_size = comm.Get_rank(), comm.Get_size()
320    cells=[set() for i in range(mpi_size)]
321    for i in range(len(owner)):
322        cells[owner[i]].add(dim.start+i)
323    cells = [sorted(list(cells[i])) for i in range(mpi_size)]
324    mycells = comm.alltoall(cells)
325    mycells = sorted(sum(mycells, [])) # concatenate into a single list
326    return mycells
327
328#---------------------------------- Stencil management ---------------------------------------#
329
330# Class Stencil represents an adjacency relationship (e.g. cell=>edges)
331# using adjacency information read from PArrays
332# It computes a list of "edges" adjacent to a given list of "cells"
333# This is used to form the sets E0 -> C0 -> E1 -> V1 -> E2 -> C1
334# which are then used to form lists of global indices for V1,C1,E2 such that
335# C0, E0, E1 form contiguous subsets of C1, E2 starting from 0
336
337def reindex(vertex_dict, degree, bounds):
338    for i in range(degree.size):
339        for j in range(degree[i]):
340            bounds[i,j] = vertex_dict[bounds[i,j]]   
341
342class Stencil_glob:
343    def __init__(self, degree, neigh, weight=None):
344        self.degree, self.neigh, self.weight = degree, neigh, weight
345    def __call__(self, cells, neigh_dict=None):
346        return Stencil(cells, self.degree, self.neigh, neigh_dict, self.weight)
347           
348class Stencil:
349    def __init__(self, cells, degree, neigh, neigh_dict, weight=None):
350        get = degree.dim.getter(cells)
351        mydegree, myneigh = [get(x) for x in (degree, neigh)]
352        if not weight is None : myweight = get(weight)
353        if neigh_dict is None : 
354            keep = lambda n : True
355        else : # if neigh_dict is present, only neighbours in dict are retained
356            keep = lambda n : n in neigh_dict
357        neigh_set = list_stencil(mydegree, myneigh, keep)
358        self.neigh_set = list(set(list(neigh_set)     ))                         
359        rej=0
360        for i in range(len(mydegree)): # keep only elements in neigh_dict, in-place
361            k=0
362            for j in range(mydegree[i]):
363                n=myneigh[i,j]
364                if keep(n):
365                    myneigh[i,k]=n
366                    if not weight is None : myweight[i,k]=myweight[i,j]
367                    k=k+1
368                else:
369                    rej=rej+1
370            mydegree[i]=k
371        if neigh_dict is None:
372            neigh_dict = {j:i for i,j in enumerate(self.neigh_set)}
373        myneigh_loc = reindex(neigh_dict, mydegree, myneigh)
374        self.degree, self.neigh_glob, self.neigh_loc = mydegree, myneigh, myneigh_loc
375
376def progressive_iter(mylist, cell_lists):
377    for thelist in cell_lists: 
378        mylist = mylist + list(set(thelist)-set(mylist))
379        yield mylist
380def progressive_list(*cell_lists):
381    # cell_lists : a tuple of lists of global indices, with each list a subset of the next
382    # returns : a list 'mylist' such that for each list 'thelist' in cell_lists, thelist = mylist[0:len(thelist)]
383    # example : edge_list = progressive_list(E0,E1,E2) with E0,E1,E2 increasing lists of cell edges
384    return list(progressive_iter([], cell_lists))
Note: See TracBrowser for help on using the repository browser.