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

Last change on this file since 1192 was 1192, checked in by prow, 20 years ago

set_to_inc. now returns min and max ratio between new and old radii.

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