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

Last change on this file since 480 was 415, checked in by duncan, 20 years ago

added element tags to Domain

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