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

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

Work towards spatio-temporal boundary
Also changed naming from 'z' to 'elevation' in data manager (kept 'z' for backwards compatibility, though)

File size: 16.4 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
[482]7from general_mesh import General_mesh
[258]8
[482]9class Mesh(General_mesh):
[258]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:
[305]23       Mesh(coordinates, triangles)
[258]24
25    where
26
[303]27      coordinates is either a list of 2-tuples or an Mx2 Numeric array of
[258]28      floats representing all x, y coordinates in the mesh.
29
[305]30      triangles is either a list of 3-tuples or an Nx3 Numeric array of
[258]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]
[305]42        triangles = [ [1,0,2], [1,2,3] ]   #bac, bce
43        mesh = Mesh(points, triangles)
[258]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
[320]54    #FIXME: Maybe rename coordinates to points (as in a poly file)
[317]55    #But keep 'vertex_coordinates'
[320]56
57    #FIXME: Put in check for angles less than a set minimum
[317]58   
[415]59    def __init__(self, coordinates, triangles, boundary = None,
60                 tagged_elements = None):
[258]61        """
62        Build triangles from x,y coordinates (sequence of 2-tuples or
[305]63        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
[258]64        or Nx3 Numeric array of non-negative integers).
65        """
66
67        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
68
[636]69        General_mesh.__init__(self, coordinates, triangles)
[258]70
[322]71        N = self.number_of_elements
72       
[258]73        #Allocate space for geometric quantities
[305]74        self.centroid_coordinates = zeros((N, 2), Float)
75
[258]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       
[305]83        #Get x,y coordinates for all triangles and store
[322]84        V = self.vertex_coordinates
[258]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])
[305]96            self.centroid_coordinates[i] = centroid
[258]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       
[322]110            self.radii[i] = min(d0, d1, d2)               
[258]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 surrogate neighbour structure   
124        self.build_surrogate_neighbour_structure()       
125
126        #Build boundary dictionary mapping (id, edge) to symbolic tags       
127        self.build_boundary_dictionary(boundary)
128
[415]129        #Build tagged element  dictionary mapping (tag) to array of elements
130        self.build_tagged_elements_dictionary(tagged_elements)
131       
[655]132        #Update boundary indices FIXME: OBSOLETE
[648]133        #self.build_boundary_structure()       
[258]134
135       
136    def __repr__(self):
[305]137        return 'Mesh: %d triangles, %d elements, %d boundary segments'\
[258]138               %(self.coordinates.shape[0], len(self), len(self.boundary))
139
140           
141    def build_neighbour_structure(self):
142        """Update all registered triangles to point to their neighbours.
143       
144        Also, keep a tally of the number of boundaries for each triangle
145
146        Postconditions:
147          neighbours and neighbour_edges is populated
148          number_of_boundaries integer array is defined.
149        """
150       
151        #Step 1:
152        #Build dictionary mapping from segments (2-tuple of points)
153        #to left hand side edge (facing neighbouring triangle)
154
155        N = self.number_of_elements       
156        neighbourdict = {}
157        for i in range(N):
158
159            #Register all segments as keys mapping to current triangle
160            #and segment id
[305]161            a = self.triangles[i, 0]
162            b = self.triangles[i, 1]
163            c = self.triangles[i, 2]           
[258]164            neighbourdict[a,b] = (i, 2) #(id, edge)
165            neighbourdict[b,c] = (i, 0) #(id, edge)
166            neighbourdict[c,a] = (i, 1) #(id, edge)
167
168
169        #Step 2:
170        #Go through triangles again, but this time
171        #reverse direction of segments and lookup neighbours.
172        for i in range(N):
[305]173            a = self.triangles[i, 0]
174            b = self.triangles[i, 1]
175            c = self.triangles[i, 2]
[258]176
177            self.number_of_boundaries[i] = 3
178            if neighbourdict.has_key((b,a)):
179                self.neighbours[i, 2] = neighbourdict[b,a][0]
180                self.neighbour_edges[i, 2] = neighbourdict[b,a][1]
181                self.number_of_boundaries[i] -= 1
182               
183            if neighbourdict.has_key((c,b)):
184                self.neighbours[i, 0] = neighbourdict[c,b][0]
185                self.neighbour_edges[i, 0] = neighbourdict[c,b][1]
186                self.number_of_boundaries[i] -= 1               
187               
188            if neighbourdict.has_key((a,c)):
189                self.neighbours[i, 1] = neighbourdict[a,c][0]
190                self.neighbour_edges[i, 1] = neighbourdict[a,c][1]
191                self.number_of_boundaries[i] -= 1             
192
193
194    def build_surrogate_neighbour_structure(self):
195        """Build structure where each triangle edge points to its neighbours
196        if they exist. Otherwise point to the triangle itself.
197
198        The surrogate neighbour structure is useful for computing gradients
199        based on centroid values of neighbours.
200
201        Precondition: Neighbour structure is defined
202        Postcondition:
203          Surrogate neighbour structure is defined:
204          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
205          triangles.
206
207        """
208
209        N = self.number_of_elements
210        for i in range(N):
211            #Find all neighbouring volumes that are not boundaries
212            for k in range(3):
213                if self.neighbours[i, k] < 0:
214                    self.surrogate_neighbours[i, k] = i #Point this triangle
215                else:
216                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
217
218
219
220    def build_boundary_dictionary(self, boundary = None):
[297]221        """Build or check the dictionary of boundary tags.
[258]222         self.boundary is a dictionary of tags,
223         keyed by volume id and edge:
224         { (id, edge): tag, ... }
225         
226         Postconditions:
227            self.boundary is defined.
228        """
229
230        from config import default_boundary_tag
231       
232        if boundary is None:
233            boundary = {}
234            for vol_id in range(self.number_of_elements):
235                for edge_id in range(0, 3):
236                    if self.neighbours[vol_id, edge_id] < 0:
237                        boundary[(vol_id, edge_id)] = default_boundary_tag
238        else:
239            #Check that all keys in given boundary exist
240            for vol_id, edge_id in boundary.keys():
241                msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
242                a, b = self.neighbours.shape
243                assert vol_id < a and edge_id < b, msg
244
[601]245                #FIXME: This assert violates internal boundaries (delete it)
246                #msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
247                #assert self.neighbours[vol_id, edge_id] < 0, msg
248
249            #Check that all boundary segments are assigned a tag
[258]250            for vol_id in range(self.number_of_elements):
251                for edge_id in range(0, 3):
252                    if self.neighbours[vol_id, edge_id] < 0:
253                        if not boundary.has_key( (vol_id, edge_id) ):
254                            msg = 'WARNING: Given boundary does not contain '
255                            msg += 'tags for edge (%d, %d). '\
256                                   %(vol_id, edge_id)
257                            msg += 'Assigning default tag (%s).'\
258                                   %default_boundary_tag
259
260                            #FIXME: Print only as per verbosity
261                            #print msg
[601]262
263                            #FIXME: Make this situation an error in the future
264                            #and make another function which will
265                            #enable default boundary-tags where
266                            #tags a not specified
[258]267                            boundary[ (vol_id, edge_id) ] =\
268                                      default_boundary_tag
269           
270
271           
272        self.boundary = boundary
273           
274
[415]275    def build_tagged_elements_dictionary(self, tagged_elements = None):
276        """Build the dictionary of element tags.
277         self.tagged_elements is a dictionary of element arrays,
278         keyed by tag:
279         { (tag): [e1, e2, e3..] }
280         
281         Postconditions:
282            self.element_tag is defined
283        """
284        from Numeric import array, Int
285       
286        if tagged_elements is None:
287            tagged_elements = {}
288        else:
289            #Check that all keys in given boundary exist
290            for tag in tagged_elements.keys():
291                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
292               
293                msg = 'Not all elements exist. '
294                assert max(tagged_elements[tag]) < self.number_of_elements, msg
295        #print "tagged_elements", tagged_elements               
296        self.tagged_elements = tagged_elements
297           
[258]298    def build_boundary_structure(self):
299        """Traverse boundary and
300        enumerate neighbour indices from -1 and
301        counting down.
302
303        Precondition:
304            self.boundary is defined.
305        Post condition:
306            neighbour array has unique negative indices for boundary
307            boundary_segments array imposes an ordering on segments
[636]308            (not otherwise available from the dictionary)
309
310        Note: If a segment is listed in the boundary dictionary
311        it *will* become a boundary - even if there is a neighbouring triangle.
312        This would be the case for internal boundaries
[258]313        """
314
[655]315        #FIXME: Now Obsolete - maybe use some comments from here in
316        #domain.set_boundary
[648]317
[258]318        if self.boundary is None:
319            msg = 'Boundary dictionary must be defined before '
320            msg += 'building boundary structure'
321            raise msg
322
323       
324        self.boundary_segments = self.boundary.keys()
325        self.boundary_segments.sort()
326
327        index = -1       
328        for id, edge in self.boundary_segments:
[636]329
330            #FIXME: One would detect internal boundaries as follows
331            #if self.neighbours[id, edge] > -1:
332            #    print 'Internal boundary'
333           
[258]334            self.neighbours[id, edge] = index
335            index -= 1
336
337   
338    def get_boundary_tags(self):
339        """Return list of available boundary tags
340        """
341       
342        tags = {}
343        for v in self.boundary.values():
344            tags[v] = 1
345
346        return tags.keys()   
347           
348   
349
350    def check_integrity(self):
351        """Check that triangles are internally consistent e.g.
352        that area corresponds to edgelengths, that vertices
353        are arranged in a counter-clockwise order, etc etc
354        Neighbour structure will be checked by class Mesh
355        """
356       
357        from config import epsilon
358        from math import pi
359        from util import anglediff
360
361        N = self.number_of_elements
362       
363        #Get x,y coordinates for all vertices for all triangles
364        V = self.get_vertex_coordinates()
365
366        #Check each triangle
367        for i in range(N):
368            x0 = V[i, 0]; y0 = V[i, 1]
369            x1 = V[i, 2]; y1 = V[i, 3]
370            x2 = V[i, 4]; y2 = V[i, 5]           
371
372            #Check that area hasn't been compromised
373            area = self.areas[i]
374            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
375            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
376                  %(x0,y0,x1,y1,x2,y2)
377            assert abs((area - ref)/area) < epsilon, msg
378       
379            #Check that points are arranged in counter clock-wise order
380            v0 = [x1-x0, y1-y0]
381            v1 = [x2-x1, y2-y1]
382            v2 = [x0-x2, y0-y2]
383
384            a0 = anglediff(v1, v0)
385            a1 = anglediff(v2, v1)
386            a2 = anglediff(v0, v2)       
387
388            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
389            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
390            assert a0 < pi and a1 < pi and a2 < pi, msg
391
392            #Check that normals are orthogonal to edge vectors
393            #Note that normal[k] lies opposite vertex k
394
395            normal0 = self.normals[i, 0:2]
396            normal1 = self.normals[i, 2:4]
397            normal2 = self.normals[i, 4:6]           
398
399            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
400                assert u[0]*v[0] + u[1]*v[1] < epsilon
401
402
[297]403        #Check that all vertices have been registered
404        for v in self.vertexlist:
405            msg = 'Some points do not belong to an element.\n'
406            msg += 'Make sure all points appear as element vertices!'
407            if v is None:
408                print 'WARNING (mesh.py): %s' %msg
409                break
410
411
412               
[258]413        #Check integrity of neighbour structure
414        for i in range(N):
[305]415            for v in self.triangles[i, :]:
[258]416                #Check that all vertices have been registered
417                assert self.vertexlist[v] is not None
418
419                #Check that this triangle is listed with at least one vertex
420                assert (i, 0) in self.vertexlist[v] or\
421                       (i, 1) in self.vertexlist[v] or\
422                       (i, 2) in self.vertexlist[v]
423               
424               
425
426            #Check neighbour structure
427            for k, neighbour_id in enumerate(self.neighbours[i,:]):
428
429                #Assert that my neighbour's neighbour is me
430                #Boundaries need not fulfill this
431                if neighbour_id >= 0:
432                    edge = self.neighbour_edges[i, k]
433                    assert self.neighbours[neighbour_id, edge] == i
434
435
436
437        #Check that all boundaries have
438        # unique, consecutive, negative indices                   
439
[648]440        #L = len(self.boundary)
441        #for i in range(L):
442        #    id, edge = self.boundary_segments[i]
443        #    assert self.neighbours[id, edge] == -i-1
[258]444
[648]445
[258]446        #NOTE: This assert doesn't hold true if there are internal boundaries
447        #FIXME: Look into this further.
[645]448        #FIXME (Ole): In pyvolution mark 3 this is OK again
[648]449        #NOTE: No longer works because neighbour structure is modified by
450        #      domain set_boundary.
451        #for id, edge in self.boundary:
452        #    assert self.neighbours[id,edge] < 0
[849]453        #
454        #NOTE (Ole): I reckon this was resolved late 2004?
455        #
456        #See domain.set_boundary
[601]457       
458           
[518]459    def get_centroid_coordinates(self):
460        """Return all centroid coordinates.
461        Return all centroid coordinates for all triangles as an Nx2 array
462        (ordered as x0, y0 for each triangle)       
463        """
464        return self.centroid_coordinates
Note: See TracBrowser for help on using the repository browser.