source: inundation/ga/storm_surge/pyvolution/mesh.py @ 262

Last change on this file since 262 was 262, checked in by ole, 20 years ago

extrapolate 2 order - c implementation

File size: 18.9 KB
RevLine 
[258]1"""Classes implementing general 2D geometrical mesh of triangles.
2
3   Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
4   Geoscience Australia, 2004   
5"""
6
7
8class Mesh:
9    """Collection of triangular elements (purely geometric)
10
11    A triangular element is defined in terms of three vertex ids,
12    ordered counter clock-wise,
13    each corresponding to a given coordinate set.
14    Vertices from different elements can point to the same
15    coordinate set.
16
17    Coordinate sets are implemented as an N x 2 Numeric array containing
18    x and y coordinates.
19   
20
21    To instantiate:
22       Mesh(points, vertices)
23
24    where
25
26      points is either a list of 2-tuples or an Mx2 Numeric array of
27      floats representing all x, y coordinates in the mesh.
28
29      vertices is either a list of 3-tuples or an Nx3 Numeric array of
30      integers representing indices of all vertices in the mesh.
31      Each vertex is identified by its index i in [0, M-1].
32
33
34    Example:
35        a = [0.0, 0.0]
36        b = [0.0, 2.0]
37        c = [2.0,0.0]
38        e = [2.0, 2.0]
39       
40        points = [a, b, c, e]
41        vertices = [ [1,0,2], [1,2,3] ]   #bac, bce
42        mesh = Mesh(points, vertices)
43   
44        #creates two triangles: bac and bce
45
46
47    Mesh takes the optional third argument boundary which is a
48    dictionary mapping from (element_id, edge_id) to boundary tag.
49    The default value is None which will assign the defualt_boundary_tag
50    as specified in config.py to all boundary edges.
51    """
52
53    def __init__(self, coordinates, vertices, boundary = None):
54        """
55        Build triangles from x,y coordinates (sequence of 2-tuples or
56        Mx2 Numeric array of floats) and vertices (sequence of 3-tuples
57        or Nx3 Numeric array of non-negative integers).
58        """
59
60        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
61
62        #Only one of them
63        self.vertices = array(vertices).astype(Int)
64        self.coordinates = array(coordinates)
65
66
67        #Input checks
68        msg = 'Vertices must an Nx2 Numeric array or a sequence of 2-tuples'
69        assert len(self.vertices.shape) == 2, msg
70
71        msg = 'Coordinates must an Mx2 Numeric array or a sequence of 2-tuples'
72        assert len(self.coordinates.shape) == 2, msg       
73
74        msg = 'Vertex indices reference non-existing coordinate sets'
75        assert max(max(self.vertices)) <= self.coordinates.shape[0], msg
76
77
78        #Register number of elements (N)
79        self.number_of_elements = N = self.vertices.shape[0]
80
81        #Allocate space for geometric quantities
82        self.centroids = zeros((N, 2), Float)
[262]83        #FIXME: Should be renamed to centroid_coordinates
84       
[258]85        self.areas = zeros(N, Float)
86        self.radii = zeros(N, Float)
87        self.edgelengths = zeros((N, 3), Float)
88
89        self.neighbours = zeros((N, 3), Int)
90        self.neighbour_edges = zeros((N, 3), Int)
91        self.number_of_boundaries = zeros(N, Int) 
92        self.surrogate_neighbours = zeros((N, 3), Int)       
93       
94        self.normals = zeros((N, 6), Float)
95
96        #Get x,y coordinates for all vertices for all triangles
97        #and store
98        self.vertex_coordinates = V = self.compute_vertex_coordinates()
99       
100
101        ##print 'Initialise mesh'
102        #Initialise each triangle
103        for i in range(N):
104            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
105           
106            x0 = V[i, 0]; y0 = V[i, 1]
107            x1 = V[i, 2]; y1 = V[i, 3]
108            x2 = V[i, 4]; y2 = V[i, 5]           
109
110            #Compute centroid
111            centroid = array([(x0 + x1 + x2)/3, (y0 + y1 + y2)/3])
112            self.centroids[i] = centroid
113
114            #Area
115            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
116
117            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
118            msg += ' is degenerate:  area == %f' %self.areas[i]
119            assert self.areas[i] > 0.0, msg
120       
121            #Midpoints       
122            m0 = array([(x1 + x2)/2, (y1 + y2)/2])
123            m1 = array([(x0 + x2)/2, (y0 + y2)/2])
124            m2 = array([(x1 + x0)/2, (y1 + y0)/2])
125
126            #The radius is the distance from the centroid of
127            #a triangle to the midpoint of the side of the triangle
128            #closest to the centroid
129            d0 = sqrt(sum( (centroid-m0)**2 )) 
130            d1 = sqrt(sum( (centroid-m1)**2 ))       
131            d2 = sqrt(sum( (centroid-m2)**2 ))
132       
133            self.radii[i] = min(d0, d1, d2)
134
135            #Normals
136            #The normal vectors
137            # - point outward from each edge
138            # - are orthogonal to the edge
139            # - have unit length
140            # - Are enumerated according to the opposite corner:
141            #   (First normal is associated with the edge opposite
142            #    the first vertex, etc)
143            # - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
144
145            n0 = array([x2 - x1, y2 - y1])
146            l0 = sqrt(sum(n0**2))
147
148            n1 = array([x0 - x2, y0 - y2])
149            l1 = sqrt(sum(n1**2))
150
151            n2 = array([x1 - x0, y1 - y0])
152            l2 = sqrt(sum(n2**2))
153
154            #Normalise
155            n0 /= l0
156            n1 /= l1
157            n2 /= l2
158
159            #Compute and store
160            self.normals[i, :] = [n0[1], -n0[0],
161                                  n1[1], -n1[0],
162                                  n2[1], -n2[0]]             
163
164            #Edgelengths
165            self.edgelengths[i, :] = [l0, l1, l2]       
166
167            #Initialise Neighbours (-1 means that it is a boundary neighbour)
168            self.neighbours[i, :] = [-1, -1, -1]
169
170            #Initialise edge ids of neighbours
171            #In case of boundaries this slot is not used
172            self.neighbour_edges[i, :] = [-1, -1, -1]
173
174
175        #Build neighbour structure   
176        self.build_neighbour_structure()
177
178        #Build vertex list
179        self.build_vertexlist()       
180
181        #Build surrogate neighbour structure   
182        self.build_surrogate_neighbour_structure()       
183
184        #Build boundary dictionary mapping (id, edge) to symbolic tags       
185        self.build_boundary_dictionary(boundary)
186
187        #Update boundary indices
188        self.build_boundary_structure()       
189
190       
191    def __len__(self):
192        return self.number_of_elements
193
194    def __repr__(self):
195        return 'Mesh: %d vertices, %d elements, %d boundary segments'\
196               %(self.coordinates.shape[0], len(self), len(self.boundary))
197
198           
199    def build_neighbour_structure(self):
200        """Update all registered triangles to point to their neighbours.
201       
202        Also, keep a tally of the number of boundaries for each triangle
203
204        Postconditions:
205          neighbours and neighbour_edges is populated
206          number_of_boundaries integer array is defined.
207        """
208       
209        #Step 1:
210        #Build dictionary mapping from segments (2-tuple of points)
211        #to left hand side edge (facing neighbouring triangle)
212        #Also build list of vertices containing indices of triangles 
213
214        N = self.number_of_elements       
215        neighbourdict = {}
216        vertexlist = [None]*len(self.coordinates)
217        for i in range(N):
218
219            #Register all segments as keys mapping to current triangle
220            #and segment id
221            a = self.vertices[i, 0]
222            b = self.vertices[i, 1]
223            c = self.vertices[i, 2]           
224            neighbourdict[a,b] = (i, 2) #(id, edge)
225            neighbourdict[b,c] = (i, 0) #(id, edge)
226            neighbourdict[c,a] = (i, 1) #(id, edge)
227
228            #Register the vertices as keys mapping to
229            #(triangle, edge) tuples associated with them
230            #This is used for smoothing
231            for vertex_id, v in enumerate([a,b,c]):
232                if vertexlist[v] is None:
233                    vertexlist[v] = []
234
235                vertexlist[v].append( (i, vertex_id) )   
236
237        #Step 2:
238        #Go through triangles again, but this time
239        #reverse direction of segments and lookup neighbours.
240        for i in range(N):
241            a = self.vertices[i, 0]
242            b = self.vertices[i, 1]
243            c = self.vertices[i, 2]
244
245            self.number_of_boundaries[i] = 3
246            if neighbourdict.has_key((b,a)):
247                self.neighbours[i, 2] = neighbourdict[b,a][0]
248                self.neighbour_edges[i, 2] = neighbourdict[b,a][1]
249                self.number_of_boundaries[i] -= 1
250               
251            if neighbourdict.has_key((c,b)):
252                self.neighbours[i, 0] = neighbourdict[c,b][0]
253                self.neighbour_edges[i, 0] = neighbourdict[c,b][1]
254                self.number_of_boundaries[i] -= 1               
255               
256            if neighbourdict.has_key((a,c)):
257                self.neighbours[i, 1] = neighbourdict[a,c][0]
258                self.neighbour_edges[i, 1] = neighbourdict[a,c][1]
259                self.number_of_boundaries[i] -= 1             
260
261
262        self.vertexlist = vertexlist
263   
264    def build_vertexlist(self):
265        """Build vertexlist index by vertex ids and for each entry (point id)
266        build a list of (triangles, vertex_id) pairs that use the point
267        as vertex.
268
269        Preconditions:
270          self.coordinates and self.vertices are defined 
271       
272        Postcondition:
273          self.vertexlist is build
274        """
275
276        vertexlist = [None]*len(self.coordinates)
277        for i in range(self.number_of_elements):
278
279            a = self.vertices[i, 0]
280            b = self.vertices[i, 1]
281            c = self.vertices[i, 2]           
282
283            #Register the vertices as keys mapping to
284            #(triangle, edge) tuples associated with them
285            #This is used for smoothing
286            for vertex_id, v in enumerate([a,b,c]):
287                if vertexlist[v] is None:
288                    vertexlist[v] = []
289
290                vertexlist[v].append( (i, vertex_id) )   
291
292        self.vertexlist = vertexlist
293   
294
295    def build_surrogate_neighbour_structure(self):
296        """Build structure where each triangle edge points to its neighbours
297        if they exist. Otherwise point to the triangle itself.
298
299        The surrogate neighbour structure is useful for computing gradients
300        based on centroid values of neighbours.
301
302        Precondition: Neighbour structure is defined
303        Postcondition:
304          Surrogate neighbour structure is defined:
305          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
306          triangles.
307
308        """
309
310        N = self.number_of_elements
311        for i in range(N):
312            #Find all neighbouring volumes that are not boundaries
313            for k in range(3):
314                if self.neighbours[i, k] < 0:
315                    self.surrogate_neighbours[i, k] = i #Point this triangle
316                else:
317                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
318
319
320
321    def build_boundary_dictionary(self, boundary = None):
322        """Build or input the dictionary of boundary tags.
323         self.boundary is a dictionary of tags,
324         keyed by volume id and edge:
325         { (id, edge): tag, ... }
326         
327         Postconditions:
328            self.boundary is defined.
329        """
330
331        from config import default_boundary_tag
332       
333        if boundary is None:
334            boundary = {}
335            for vol_id in range(self.number_of_elements):
336                for edge_id in range(0, 3):
337                    if self.neighbours[vol_id, edge_id] < 0:
338                        boundary[(vol_id, edge_id)] = default_boundary_tag
339        else:
340            #Check that all keys in given boundary exist
341            for vol_id, edge_id in boundary.keys():
342                msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
343                a, b = self.neighbours.shape
344                assert vol_id < a and edge_id < b, msg
345               
346                msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
347                assert self.neighbours[vol_id, edge_id] < 0, msg
348
349            #Check that all boundary segments are assigned a value
350            for vol_id in range(self.number_of_elements):
351                for edge_id in range(0, 3):
352                    if self.neighbours[vol_id, edge_id] < 0:
353                        if not boundary.has_key( (vol_id, edge_id) ):
354                            msg = 'WARNING: Given boundary does not contain '
355                            msg += 'tags for edge (%d, %d). '\
356                                   %(vol_id, edge_id)
357                            msg += 'Assigning default tag (%s).'\
358                                   %default_boundary_tag
359
360                            #FIXME: Print only as per verbosity
361                            #print msg
362                            boundary[ (vol_id, edge_id) ] =\
363                                      default_boundary_tag
364           
365
366           
367        self.boundary = boundary
368           
369
370    def build_boundary_structure(self):
371        """Traverse boundary and
372        enumerate neighbour indices from -1 and
373        counting down.
374
375        Precondition:
376            self.boundary is defined.
377        Post condition:
378            neighbour array has unique negative indices for boundary
379            boundary_segments array imposes an ordering on segments
380            (not otherwise available from the dictionary)             
381        """
382
383        if self.boundary is None:
384            msg = 'Boundary dictionary must be defined before '
385            msg += 'building boundary structure'
386            raise msg
387
388       
389        self.boundary_segments = self.boundary.keys()
390        self.boundary_segments.sort()
391
392        index = -1       
393        for id, edge in self.boundary_segments:
394            self.neighbours[id, edge] = index
395            index -= 1
396
397   
398    def get_boundary_tags(self):
399        """Return list of available boundary tags
400        """
401       
402        tags = {}
403        for v in self.boundary.values():
404            tags[v] = 1
405
406        return tags.keys()   
407           
408   
409
410    def check_integrity(self):
411        """Check that triangles are internally consistent e.g.
412        that area corresponds to edgelengths, that vertices
413        are arranged in a counter-clockwise order, etc etc
414        Neighbour structure will be checked by class Mesh
415        """
416       
417        from config import epsilon
418        from math import pi
419        from util import anglediff
420
421        N = self.number_of_elements
422       
423        #Get x,y coordinates for all vertices for all triangles
424        V = self.get_vertex_coordinates()
425
426        #Check each triangle
427        for i in range(N):
428            x0 = V[i, 0]; y0 = V[i, 1]
429            x1 = V[i, 2]; y1 = V[i, 3]
430            x2 = V[i, 4]; y2 = V[i, 5]           
431
432            #Check that area hasn't been compromised
433            area = self.areas[i]
434            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
435            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
436                  %(x0,y0,x1,y1,x2,y2)
437            assert abs((area - ref)/area) < epsilon, msg
438       
439            #Check that points are arranged in counter clock-wise order
440            v0 = [x1-x0, y1-y0]
441            v1 = [x2-x1, y2-y1]
442            v2 = [x0-x2, y0-y2]
443
444            a0 = anglediff(v1, v0)
445            a1 = anglediff(v2, v1)
446            a2 = anglediff(v0, v2)       
447
448            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
449            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
450            assert a0 < pi and a1 < pi and a2 < pi, msg
451
452            #Check that normals are orthogonal to edge vectors
453            #Note that normal[k] lies opposite vertex k
454
455            normal0 = self.normals[i, 0:2]
456            normal1 = self.normals[i, 2:4]
457            normal2 = self.normals[i, 4:6]           
458
459            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
460                assert u[0]*v[0] + u[1]*v[1] < epsilon
461
462
463        #Check integrity of neighbour structure
464        for i in range(N):
465            for v in self.vertices[i, :]:
466                #Check that all vertices have been registered
467                assert self.vertexlist[v] is not None
468
469                #Check that this triangle is listed with at least one vertex
470                assert (i, 0) in self.vertexlist[v] or\
471                       (i, 1) in self.vertexlist[v] or\
472                       (i, 2) in self.vertexlist[v]
473               
474               
475
476            #Check neighbour structure
477            for k, neighbour_id in enumerate(self.neighbours[i,:]):
478
479                #Assert that my neighbour's neighbour is me
480                #Boundaries need not fulfill this
481                if neighbour_id >= 0:
482                    edge = self.neighbour_edges[i, k]
483                    assert self.neighbours[neighbour_id, edge] == i
484
485
486
487        #Check that all boundaries have
488        # unique, consecutive, negative indices                   
489        L = len(self.boundary)
490        for i in range(L):
491            id, edge = self.boundary_segments[i]
492            assert self.neighbours[id, edge] == -i-1
493
494
495        #NOTE: This assert doesn't hold true if there are internal boundaries
496        #FIXME: Look into this further.
497        #for id, edge in self.boundary:
498        #    assert self.neighbours[id,edge] < 0
499       
500
501   
502
503    def get_normals(self):
504        """Return all normal vectors.
505        Return normal vectors for all triangles as an Nx6 array
506        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)       
507        """
508        return self.normals
509
510
511    def get_normal(self, i, j):
512        """Return normal vector j of the i'th triangle.
513        Return value is the numeric array slice [x, y]
514        """
515        return self.normals[i, 2*j:2*j+2]     
516   
517
518           
519    def get_vertex_coordinates(self):
520        """Return all vertex coordinates.
521        Return all vertex coordinates for all triangles as an Nx6 array
522        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)       
523        """
524        return self.vertex_coordinates
525
526
527    def get_vertex_coordinate(self, i, j):
528        """Return coordinates for vertex j of the i'th triangle.
529        Return value is the numeric array slice [x, y]
530        """
531        return self.vertex_coordinates[i, 2*j:2*j+2]     
532
533
534    def compute_vertex_coordinates(self):
535        """Return vertex coordinates for all triangles as an Nx6 array
536        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
537        """
538
539        from Numeric import zeros, Float
540
541        N = self.number_of_elements
542        vertex_coordinates = zeros((N, 6), Float) 
543
544        for i in range(N):
545            for j in range(3):
546                k = self.vertices[i,j]  #Index of vertex 0
547                v_k = self.coordinates[k]
548                vertex_coordinates[i, 2*j+0] = v_k[0]
549                vertex_coordinates[i, 2*j+1] = v_k[1]
550
551        return vertex_coordinates
552 
553
554
555
556def rectangular_mesh(m, n, len1=1.0, len2=1.0, origin = (0.0, 0.0)):
557    from mesh_factory import rectangular
558    points, vertices, boundary = rectangular(m, n, len1, len2, origin)
559
560    return Mesh(points, vertices, boundary)
561
562
563#FIXME: Get oblique and contracting and circular meshes from Chris
564
Note: See TracBrowser for help on using the repository browser.