source: inundation/ga/storm_surge/pyvolution-parallel/mesh.py @ 1452

Last change on this file since 1452 was 1237, checked in by steve, 20 years ago
File size: 21.0 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 default_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
60    def __init__(self, coordinates, triangles, boundary = None,
61                 tagged_elements = None, geo_reference = None,
62                 use_inscribed_circle = False):
63        """
64        Build triangles from x,y coordinates (sequence of 2-tuples or
65        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
66        or Nx3 Numeric array of non-negative integers).
67        """
68
69
70
71        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
72
73        General_mesh.__init__(self, coordinates, triangles, geo_reference)
74
75        N = self.number_of_elements
76
77        #Allocate space for geometric quantities
78        self.centroid_coordinates = zeros((N, 2), Float)
79
80        self.radii = zeros(N, Float)
81
82        self.neighbours = zeros((N, 3), Int)
83        self.neighbour_edges = zeros((N, 3), Int)
84        self.number_of_boundaries = zeros(N, Int)
85        self.surrogate_neighbours = zeros((N, 3), Int)
86
87        #Get x,y coordinates for all triangles and store
88        V = self.vertex_coordinates
89
90        #Initialise each triangle
91        for i in range(N):
92            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
93
94            x0 = V[i, 0]; y0 = V[i, 1]
95            x1 = V[i, 2]; y1 = V[i, 3]
96            x2 = V[i, 4]; y2 = V[i, 5]
97
98            #Compute centroid
99            centroid = array([(x0 + x1 + x2)/3, (y0 + y1 + y2)/3])
100            self.centroid_coordinates[i] = centroid
101
102
103            if use_inscribed_circle == False:
104                #OLD code. Computed radii may exceed that of an
105                #inscribed circle
106
107                #Midpoints
108                m0 = array([(x1 + x2)/2, (y1 + y2)/2])
109                m1 = array([(x0 + x2)/2, (y0 + y2)/2])
110                m2 = array([(x1 + x0)/2, (y1 + y0)/2])
111
112                #The radius is the distance from the centroid of
113                #a triangle to the midpoint of the side of the triangle
114                #closest to the centroid
115                d0 = sqrt(sum( (centroid-m0)**2 ))
116                d1 = sqrt(sum( (centroid-m1)**2 ))
117                d2 = sqrt(sum( (centroid-m2)**2 ))
118
119                self.radii[i] = min(d0, d1, d2)
120
121            else:
122                #NEW code added by Peter Row. True radius
123                #of inscribed circle is computed
124
125                from math import sqrt
126                a = sqrt((x0-x1)**2+(y0-y1)**2)
127                b = sqrt((x1-x2)**2+(y1-y2)**2)
128                c = sqrt((x2-x0)**2+(y2-y0)**2)
129
130                self.radii[i]=self.areas[i]/(2*(a+b+c))
131
132
133            #Initialise Neighbours (-1 means that it is a boundary neighbour)
134            self.neighbours[i, :] = [-1, -1, -1]
135
136            #Initialise edge ids of neighbours
137            #In case of boundaries this slot is not used
138            self.neighbour_edges[i, :] = [-1, -1, -1]
139
140
141        #Build neighbour structure
142        self.build_neighbour_structure()
143
144        #Build surrogate neighbour structure
145        self.build_surrogate_neighbour_structure()
146
147        #Build boundary dictionary mapping (id, edge) to symbolic tags
148        self.build_boundary_dictionary(boundary)
149
150        #Build tagged element  dictionary mapping (tag) to array of elements
151        self.build_tagged_elements_dictionary(tagged_elements)
152
153        #Update boundary indices FIXME: OBSOLETE
154        #self.build_boundary_structure()
155
156        #FIXME check integrity?
157
158    def __repr__(self):
159        return 'Mesh: %d triangles, %d elements, %d boundary segments'\
160               %(self.coordinates.shape[0], len(self), len(self.boundary))
161
162
163    def set_to_inscribed_circle(self,safety_factor = 1):
164        #FIXME phase out eventually
165        from math import sqrt
166        N = self.number_of_elements
167        V = self.vertex_coordinates
168
169        #initialising min and max ratio
170        i=0
171        old_rad = self.radii[i]
172        x0 = V[i, 0]; y0 = V[i, 1]
173        x1 = V[i, 2]; y1 = V[i, 3]
174        x2 = V[i, 4]; y2 = V[i, 5]
175        a = sqrt((x0-x1)**2+(y0-y1)**2)
176        b = sqrt((x1-x2)**2+(y1-y2)**2)
177        c = sqrt((x2-x0)**2+(y2-y0)**2)
178        ratio = old_rad/self.radii[i]
179        max_ratio = ratio
180        min_ratio = ratio
181
182        for i in range(N):
183            old_rad = self.radii[i]
184            x0 = V[i, 0]; y0 = V[i, 1]
185            x1 = V[i, 2]; y1 = V[i, 3]
186            x2 = V[i, 4]; y2 = V[i, 5]
187            a = sqrt((x0-x1)**2+(y0-y1)**2)
188            b = sqrt((x1-x2)**2+(y1-y2)**2)
189            c = sqrt((x2-x0)**2+(y2-y0)**2)
190            self.radii[i]=self.areas[i]/(2*(a+b+c))*safety_factor
191            ratio = old_rad/self.radii[i]
192            if ratio >= max_ratio: max_ratio = ratio
193            if ratio <= min_ratio: min_ratio = ratio
194        return max_ratio,min_ratio
195
196    def build_neighbour_structure(self):
197        """Update all registered triangles to point to their neighbours.
198
199        Also, keep a tally of the number of boundaries for each triangle
200
201        Postconditions:
202          neighbours and neighbour_edges is populated
203          number_of_boundaries integer array is defined.
204        """
205
206        #Step 1:
207        #Build dictionary mapping from segments (2-tuple of points)
208        #to left hand side edge (facing neighbouring triangle)
209
210        N = self.number_of_elements
211        neighbourdict = {}
212        for i in range(N):
213
214            #Register all segments as keys mapping to current triangle
215            #and segment id
216            a = self.triangles[i, 0]
217            b = self.triangles[i, 1]
218            c = self.triangles[i, 2]
219            if neighbourdict.has_key((a,b)):
220                    msg = "Edge 2 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[a,b][1],neighbourdict[a,b][0])
221                    raise msg
222            if neighbourdict.has_key((b,c)):
223                    msg = "Edge 0 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[b,c][1],neighbourdict[b,c][0])
224                    raise msg
225            if neighbourdict.has_key((c,a)):
226                    msg = "Edge 1 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[c,a][1],neighbourdict[c,a][0])
227                    raise msg
228
229            neighbourdict[a,b] = (i, 2) #(id, edge)
230            neighbourdict[b,c] = (i, 0) #(id, edge)
231            neighbourdict[c,a] = (i, 1) #(id, edge)
232
233
234        #Step 2:
235        #Go through triangles again, but this time
236        #reverse direction of segments and lookup neighbours.
237        for i in range(N):
238            a = self.triangles[i, 0]
239            b = self.triangles[i, 1]
240            c = self.triangles[i, 2]
241
242            self.number_of_boundaries[i] = 3
243            if neighbourdict.has_key((b,a)):
244                self.neighbours[i, 2] = neighbourdict[b,a][0]
245                self.neighbour_edges[i, 2] = neighbourdict[b,a][1]
246                self.number_of_boundaries[i] -= 1
247
248            if neighbourdict.has_key((c,b)):
249                self.neighbours[i, 0] = neighbourdict[c,b][0]
250                self.neighbour_edges[i, 0] = neighbourdict[c,b][1]
251                self.number_of_boundaries[i] -= 1
252
253            if neighbourdict.has_key((a,c)):
254                self.neighbours[i, 1] = neighbourdict[a,c][0]
255                self.neighbour_edges[i, 1] = neighbourdict[a,c][1]
256                self.number_of_boundaries[i] -= 1
257
258
259    def build_surrogate_neighbour_structure(self):
260        """Build structure where each triangle edge points to its neighbours
261        if they exist. Otherwise point to the triangle itself.
262
263        The surrogate neighbour structure is useful for computing gradients
264        based on centroid values of neighbours.
265
266        Precondition: Neighbour structure is defined
267        Postcondition:
268          Surrogate neighbour structure is defined:
269          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
270          triangles.
271
272        """
273
274        N = self.number_of_elements
275        for i in range(N):
276            #Find all neighbouring volumes that are not boundaries
277            for k in range(3):
278                if self.neighbours[i, k] < 0:
279                    self.surrogate_neighbours[i, k] = i #Point this triangle
280                else:
281                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
282
283
284
285    def build_boundary_dictionary(self, boundary = None):
286        """Build or check the dictionary of boundary tags.
287         self.boundary is a dictionary of tags,
288         keyed by volume id and edge:
289         { (id, edge): tag, ... }
290
291         Postconditions:
292            self.boundary is defined.
293        """
294
295        from config import default_boundary_tag
296
297        if boundary is None:
298            boundary = {}
299            for vol_id in range(self.number_of_elements):
300                for edge_id in range(0, 3):
301                    if self.neighbours[vol_id, edge_id] < 0:
302                        boundary[(vol_id, edge_id)] = default_boundary_tag
303        else:
304            #Check that all keys in given boundary exist
305            for vol_id, edge_id in boundary.keys():
306                msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
307                a, b = self.neighbours.shape
308                assert vol_id < a and edge_id < b, msg
309
310                #FIXME: This assert violates internal boundaries (delete it)
311                #msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
312                #assert self.neighbours[vol_id, edge_id] < 0, msg
313
314            #Check that all boundary segments are assigned a tag
315            for vol_id in range(self.number_of_elements):
316                for edge_id in range(0, 3):
317                    if self.neighbours[vol_id, edge_id] < 0:
318                        if not boundary.has_key( (vol_id, edge_id) ):
319                            msg = 'WARNING: Given boundary does not contain '
320                            msg += 'tags for edge (%d, %d). '\
321                                   %(vol_id, edge_id)
322                            msg += 'Assigning default tag (%s).'\
323                                   %default_boundary_tag
324
325                            #FIXME: Print only as per verbosity
326                            #print msg
327
328                            #FIXME: Make this situation an error in the future
329                            #and make another function which will
330                            #enable default boundary-tags where
331                            #tags a not specified
332                            boundary[ (vol_id, edge_id) ] =\
333                                      default_boundary_tag
334
335
336
337        self.boundary = boundary
338
339
340    def build_tagged_elements_dictionary(self, tagged_elements = None):
341        """Build the dictionary of element tags.
342         self.tagged_elements is a dictionary of element arrays,
343         keyed by tag:
344         { (tag): [e1, e2, e3..] }
345
346         Postconditions:
347            self.element_tag is defined
348        """
349        from Numeric import array, Int
350
351        if tagged_elements is None:
352            tagged_elements = {}
353        else:
354            #Check that all keys in given boundary exist
355            for tag in tagged_elements.keys():
356                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
357
358                msg = 'Not all elements exist. '
359                assert max(tagged_elements[tag]) < self.number_of_elements, msg
360        #print "tagged_elements", tagged_elements
361        self.tagged_elements = tagged_elements
362
363    def build_boundary_structure(self):
364        """Traverse boundary and
365        enumerate neighbour indices from -1 and
366        counting down.
367
368        Precondition:
369            self.boundary is defined.
370        Post condition:
371            neighbour array has unique negative indices for boundary
372            boundary_segments array imposes an ordering on segments
373            (not otherwise available from the dictionary)
374
375        Note: If a segment is listed in the boundary dictionary
376        it *will* become a boundary - even if there is a neighbouring triangle.
377        This would be the case for internal boundaries
378        """
379
380        #FIXME: Now Obsolete - maybe use some comments from here in
381        #domain.set_boundary
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
395            #FIXME: One would detect internal boundaries as follows
396            #if self.neighbours[id, edge] > -1:
397            #    print 'Internal boundary'
398
399            self.neighbours[id, edge] = index
400            index -= 1
401
402
403    def get_boundary_tags(self):
404        """Return list of available boundary tags
405        """
406
407        tags = {}
408        for v in self.boundary.values():
409            tags[v] = 1
410
411        return tags.keys()
412
413
414    def get_boundary_polygon(self):
415        """Return bounding polygon as a list of points
416        """
417        from Numeric import allclose, sqrt, array, minimum, maximum
418
419
420
421        V = self.get_vertex_coordinates()
422        segments = {}
423
424        #pmin = (min(self.coordinates[:,0]), min(self.coordinates[:,1]))
425        #pmax = (max(self.coordinates[:,0]), max(self.coordinates[:,1]))
426
427        #FIXME:Can this be written more compactly, e.g.
428        #using minimum and maximium?
429        pmin = array( [min(self.coordinates[:,0]),
430                       min(self.coordinates[:,1]) ] )
431
432        pmax = array( [max(self.coordinates[:,0]),
433                       max(self.coordinates[:,1]) ] )
434
435        mindist = sqrt(sum( (pmax-pmin)**2 ))
436        for i, edge_id in self.boundary.keys():
437            #Find vertex ids for boundary segment
438            if edge_id == 0: a = 1; b = 2
439            if edge_id == 1: a = 2; b = 0
440            if edge_id == 2: a = 0; b = 1
441
442            A = tuple(self.get_vertex_coordinate(i, a))
443            B = tuple(self.get_vertex_coordinate(i, b))
444
445            #Take the point closest to pmin as starting point
446            #Note: Could be arbitrary, but nice to have
447            #a unique way of selecting
448            dist_A = sqrt(sum( (A-pmin)**2 ))
449            dist_B = sqrt(sum( (B-pmin)**2 ))
450
451            #Find minimal point
452            if dist_A < mindist:
453                mindist = dist_A
454                p0 = A
455            if dist_B < mindist:
456                mindist = dist_B
457                p0 = B
458
459
460            if p0 is None:
461                raise 'Weird'
462                p0 = A #We need a starting point (FIXME)
463
464                print 'A', A
465                print 'B', B
466                print 'pmin', pmin
467                print
468
469            segments[A] = B
470
471
472        #Start with smallest point and follow boundary (counter clock wise)
473        polygon = [p0]
474        while len(polygon) < len(self.boundary):
475            p1 = segments[p0]
476            polygon.append(p1)
477            p0 = p1
478
479        return polygon
480
481
482    def check_integrity(self):
483        """Check that triangles are internally consistent e.g.
484        that area corresponds to edgelengths, that vertices
485        are arranged in a counter-clockwise order, etc etc
486        Neighbour structure will be checked by class Mesh
487        """
488
489        from config import epsilon
490        from math import pi
491        from util import anglediff
492
493        N = self.number_of_elements
494
495        #Get x,y coordinates for all vertices for all triangles
496        V = self.get_vertex_coordinates()
497
498        #Check each triangle
499        for i in range(N):
500            x0 = V[i, 0]; y0 = V[i, 1]
501            x1 = V[i, 2]; y1 = V[i, 3]
502            x2 = V[i, 4]; y2 = V[i, 5]
503
504            #Check that area hasn't been compromised
505            area = self.areas[i]
506            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
507            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
508                  %(x0,y0,x1,y1,x2,y2)
509            assert abs((area - ref)/area) < epsilon, msg
510
511            #Check that points are arranged in counter clock-wise order
512            v0 = [x1-x0, y1-y0]
513            v1 = [x2-x1, y2-y1]
514            v2 = [x0-x2, y0-y2]
515
516            a0 = anglediff(v1, v0)
517            a1 = anglediff(v2, v1)
518            a2 = anglediff(v0, v2)
519
520            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
521            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
522            assert a0 < pi and a1 < pi and a2 < pi, msg
523
524            #Check that normals are orthogonal to edge vectors
525            #Note that normal[k] lies opposite vertex k
526
527            normal0 = self.normals[i, 0:2]
528            normal1 = self.normals[i, 2:4]
529            normal2 = self.normals[i, 4:6]
530
531            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
532                assert u[0]*v[0] + u[1]*v[1] < epsilon
533
534
535        #Check that all vertices have been registered
536        for v_id, v in enumerate(self.vertexlist):
537
538            msg = 'Vertex %s does not belong to an element.'
539            #assert v is not None, msg
540            if v is None:
541                print msg%v_id
542
543        #Check integrity of neighbour structure
544        for i in range(N):
545            for v in self.triangles[i, :]:
546                #Check that all vertices have been registered
547                assert self.vertexlist[v] is not None
548
549                #Check that this triangle is listed with at least one vertex
550                assert (i, 0) in self.vertexlist[v] or\
551                       (i, 1) in self.vertexlist[v] or\
552                       (i, 2) in self.vertexlist[v]
553
554
555
556            #Check neighbour structure
557            for k, neighbour_id in enumerate(self.neighbours[i,:]):
558
559                #Assert that my neighbour's neighbour is me
560                #Boundaries need not fulfill this
561                if neighbour_id >= 0:
562                    edge = self.neighbour_edges[i, k]
563                    msg = 'Triangle %d has neighbour %d but it does not point back. \n' %(i,neighbour_id)
564                    msg += 'Only points to (%s)' %(self.neighbours[neighbour_id,:])
565                    assert self.neighbours[neighbour_id, edge] == i ,msg
566
567
568
569        #Check that all boundaries have
570        # unique, consecutive, negative indices
571
572        #L = len(self.boundary)
573        #for i in range(L):
574        #    id, edge = self.boundary_segments[i]
575        #    assert self.neighbours[id, edge] == -i-1
576
577
578        #NOTE: This assert doesn't hold true if there are internal boundaries
579        #FIXME: Look into this further.
580        #FIXME (Ole): In pyvolution mark 3 this is OK again
581        #NOTE: No longer works because neighbour structure is modified by
582        #      domain set_boundary.
583        #for id, edge in self.boundary:
584        #    assert self.neighbours[id,edge] < 0
585        #
586        #NOTE (Ole): I reckon this was resolved late 2004?
587        #
588        #See domain.set_boundary
589
590
591    def get_centroid_coordinates(self):
592        """Return all centroid coordinates.
593        Return all centroid coordinates for all triangles as an Nx2 array
594        (ordered as x0, y0 for each triangle)
595        """
596        return self.centroid_coordinates
Note: See TracBrowser for help on using the repository browser.