source: branches/inundation-numpy-branch/pyvolution/mesh.py @ 8697

Last change on this file since 8697 was 3514, checked in by duncan, 18 years ago

Hi all,
I'm doing a change in the anuga structure, moving the code to

\anuga_core\source\anuga

After you have done an svn update, the PYTHONPATH has to be changed to;
PYTHONPATH = anuga_core/source/

This is part of changes required to make installation of anuga quicker and reducing the size of our sandpits.

If any imports are broken, try fixing them. With adding anuga. to them for example. If this seems to have really broken things, email/phone me.

Cheers
Duncan

File size: 22.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 numpy import array, zeros, Int, Float, minimum, maximum, sum, arange
8from math import sqrt
9from general_mesh import General_mesh
10
11
12class Mesh(General_mesh):
13    """Collection of triangular elements (purely geometric)
14
15    A triangular element is defined in terms of three vertex ids,
16    ordered counter clock-wise,
17    each corresponding to a given coordinate set.
18    Vertices from different elements can point to the same
19    coordinate set.
20
21    Coordinate sets are implemented as an N x 2 Numeric array containing
22    x and y coordinates.
23
24
25    To instantiate:
26       Mesh(coordinates, triangles)
27
28    where
29
30      coordinates is either a list of 2-tuples or an Mx2 Numeric array of
31      floats representing all x, y coordinates in the mesh.
32
33      triangles is either a list of 3-tuples or an Nx3 Numeric array of
34      integers representing indices of all vertices in the mesh.
35      Each vertex is identified by its index i in [0, M-1].
36
37
38    Example:
39        a = [0.0, 0.0]
40        b = [0.0, 2.0]
41        c = [2.0,0.0]
42        e = [2.0, 2.0]
43
44        points = [a, b, c, e]
45        triangles = [ [1,0,2], [1,2,3] ]   #bac, bce
46        mesh = Mesh(points, triangles)
47
48        #creates two triangles: bac and bce
49
50
51    Mesh takes the optional third argument boundary which is a
52    dictionary mapping from (element_id, edge_id) to boundary tag.
53    The default value is None which will assign the default_boundary_tag
54    as specified in config.py to all boundary edges.
55    """
56
57    #FIXME: Maybe rename coordinates to points (as in a poly file)
58    #But keep 'vertex_coordinates'
59
60    #FIXME: Put in check for angles less than a set minimum
61
62
63    def __init__(self, coordinates, triangles, boundary = None,
64                 tagged_elements = None, geo_reference = None,
65                 use_inscribed_circle = False):
66        """
67        Build triangles from x,y coordinates (sequence of 2-tuples or
68        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
69        or Nx3 Numeric array of non-negative integers).
70        """
71
72
73        from math import sqrt
74
75
76        General_mesh.__init__(self, coordinates, triangles, geo_reference)
77
78        N = self.number_of_elements
79
80        self.use_inscribed_circle = use_inscribed_circle
81
82        #Allocate space for geometric quantities
83        self.centroid_coordinates = zeros((N, 2), Float)
84
85        self.radii = zeros(N, Float)
86
87        self.neighbours = zeros((N, 3), Int)
88        self.neighbour_edges = zeros((N, 3), Int)
89        self.number_of_boundaries = zeros(N, Int)
90        self.surrogate_neighbours = zeros((N, 3), Int)
91
92        #Get x,y coordinates for all triangles and store
93        V = self.vertex_coordinates
94
95        #Initialise each triangle
96        for i in range(N):
97            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
98
99            x0 = V[i, 0]; y0 = V[i, 1]
100            x1 = V[i, 2]; y1 = V[i, 3]
101            x2 = V[i, 4]; y2 = V[i, 5]
102
103            #Compute centroid
104            centroid = array([(x0 + x1 + x2)/3, (y0 + y1 + y2)/3])
105            self.centroid_coordinates[i] = centroid
106
107
108            if self.use_inscribed_circle == False:
109                #OLD code. Computed radii may exceed that of an
110                #inscribed circle
111
112                #Midpoints
113                m0 = array([(x1 + x2)/2, (y1 + y2)/2])
114                m1 = array([(x0 + x2)/2, (y0 + y2)/2])
115                m2 = array([(x1 + x0)/2, (y1 + y0)/2])
116
117                #The radius is the distance from the centroid of
118                #a triangle to the midpoint of the side of the triangle
119                #closest to the centroid
120                d0 = sqrt(sum( (centroid-m0)**2 ))
121                d1 = sqrt(sum( (centroid-m1)**2 ))
122                d2 = sqrt(sum( (centroid-m2)**2 ))
123
124                self.radii[i] = min(d0, d1, d2)
125
126            else:
127                #NEW code added by Peter Row. True radius
128                #of inscribed circle is computed
129
130                a = sqrt((x0-x1)**2+(y0-y1)**2)
131                b = sqrt((x1-x2)**2+(y1-y2)**2)
132                c = sqrt((x2-x0)**2+(y2-y0)**2)
133
134                self.radii[i]=2.0*self.areas[i]/(a+b+c)
135
136
137            #Initialise Neighbours (-1 means that it is a boundary neighbour)
138            self.neighbours[i, :] = [-1, -1, -1]
139
140            #Initialise edge ids of neighbours
141            #In case of boundaries this slot is not used
142            self.neighbour_edges[i, :] = [-1, -1, -1]
143
144
145        #Build neighbour structure
146        self.build_neighbour_structure()
147
148        #Build surrogate neighbour structure
149        self.build_surrogate_neighbour_structure()
150
151        #Build boundary dictionary mapping (id, edge) to symbolic tags
152        self.build_boundary_dictionary(boundary)
153
154        #Build tagged element  dictionary mapping (tag) to array of elements
155        self.build_tagged_elements_dictionary(tagged_elements)
156
157        #Update boundary indices FIXME: OBSOLETE
158        #self.build_boundary_structure()
159
160        #FIXME check integrity?
161
162    def __repr__(self):
163        return 'Mesh: %d triangles, %d elements, %d boundary segments'\
164               %(self.coordinates.shape[0], len(self), len(self.boundary))
165
166
167    def set_to_inscribed_circle(self,safety_factor = 1):
168        #FIXME phase out eventually
169        N = self.number_of_elements
170        V = self.vertex_coordinates
171
172        #initialising min and max ratio
173        i=0
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        ratio = old_rad/self.radii[i]
182        max_ratio = ratio
183        min_ratio = ratio
184
185        for i in range(N):
186            old_rad = self.radii[i]
187            x0 = V[i, 0]; y0 = V[i, 1]
188            x1 = V[i, 2]; y1 = V[i, 3]
189            x2 = V[i, 4]; y2 = V[i, 5]
190            a = sqrt((x0-x1)**2+(y0-y1)**2)
191            b = sqrt((x1-x2)**2+(y1-y2)**2)
192            c = sqrt((x2-x0)**2+(y2-y0)**2)
193            self.radii[i]=self.areas[i]/(2*(a+b+c))*safety_factor
194            ratio = old_rad/self.radii[i]
195            if ratio >= max_ratio: max_ratio = ratio
196            if ratio <= min_ratio: min_ratio = ratio
197        return max_ratio,min_ratio
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
213        N = self.number_of_elements
214        neighbourdict = {}
215        for i in range(N):
216
217            #Register all segments as keys mapping to current triangle
218            #and segment id
219            a = self.triangles[i, 0]
220            b = self.triangles[i, 1]
221            c = self.triangles[i, 2]
222            if neighbourdict.has_key((a,b)):
223                    msg = "Edge 2 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[a,b][1],neighbourdict[a,b][0])
224                    raise msg
225            if neighbourdict.has_key((b,c)):
226                    msg = "Edge 0 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[b,c][1],neighbourdict[b,c][0])
227                    raise msg
228            if neighbourdict.has_key((c,a)):
229                    msg = "Edge 1 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[c,a][1],neighbourdict[c,a][0])
230                    raise msg
231
232            neighbourdict[a,b] = (i, 2) #(id, edge)
233            neighbourdict[b,c] = (i, 0) #(id, edge)
234            neighbourdict[c,a] = (i, 1) #(id, edge)
235
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.triangles[i, 0]
242            b = self.triangles[i, 1]
243            c = self.triangles[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    def build_surrogate_neighbour_structure(self):
263        """Build structure where each triangle edge points to its neighbours
264        if they exist. Otherwise point to the triangle itself.
265
266        The surrogate neighbour structure is useful for computing gradients
267        based on centroid values of neighbours.
268
269        Precondition: Neighbour structure is defined
270        Postcondition:
271          Surrogate neighbour structure is defined:
272          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
273          triangles.
274
275        """
276
277        N = self.number_of_elements
278        for i in range(N):
279            #Find all neighbouring volumes that are not boundaries
280            for k in range(3):
281                if self.neighbours[i, k] < 0:
282                    self.surrogate_neighbours[i, k] = i #Point this triangle
283                else:
284                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
285
286
287
288    def build_boundary_dictionary(self, boundary = None):
289        """Build or check the dictionary of boundary tags.
290         self.boundary is a dictionary of tags,
291         keyed by volume id and edge:
292         { (id, edge): tag, ... }
293
294         Postconditions:
295            self.boundary is defined.
296        """
297
298        from config import default_boundary_tag
299
300        if boundary is None:
301            boundary = {}
302            for vol_id in range(self.number_of_elements):
303                for edge_id in range(0, 3):
304                    if self.neighbours[vol_id, edge_id] < 0:
305                        boundary[(vol_id, edge_id)] = default_boundary_tag
306        else:
307            #Check that all keys in given boundary exist
308            for vol_id, edge_id in boundary.keys():
309                msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
310                a, b = self.neighbours.shape
311                assert vol_id < a and edge_id < b, msg
312
313                #FIXME: This assert violates internal boundaries (delete it)
314                #msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
315                #assert self.neighbours[vol_id, edge_id] < 0, msg
316
317            #Check that all boundary segments are assigned a tag
318            for vol_id in range(self.number_of_elements):
319                for edge_id in range(0, 3):
320                    if self.neighbours[vol_id, edge_id] < 0:
321                        if not boundary.has_key( (vol_id, edge_id) ):
322                            msg = 'WARNING: Given boundary does not contain '
323                            msg += 'tags for edge (%d, %d). '\
324                                   %(vol_id, edge_id)
325                            msg += 'Assigning default tag (%s).'\
326                                   %default_boundary_tag
327
328                            #FIXME: Print only as per verbosity
329                            #print msg
330
331                            #FIXME: Make this situation an error in the future
332                            #and make another function which will
333                            #enable default boundary-tags where
334                            #tags a not specified
335                            boundary[ (vol_id, edge_id) ] =\
336                                      default_boundary_tag
337
338
339
340        self.boundary = boundary
341
342
343    def build_tagged_elements_dictionary(self, tagged_elements = None):
344        """Build the dictionary of element tags.
345         self.tagged_elements is a dictionary of element arrays,
346         keyed by tag:
347         { (tag): [e1, e2, e3..] }
348
349         Postconditions:
350            self.element_tag is defined
351        """
352
353        if tagged_elements is None:
354            tagged_elements = {}
355        else:
356            #Check that all keys in given boundary exist
357            for tag in tagged_elements.keys():
358                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
359
360                msg = 'Not all elements exist. '
361                assert max(tagged_elements[tag]) < self.number_of_elements, msg
362        #print "tagged_elements", tagged_elements
363        self.tagged_elements = tagged_elements
364
365    def build_boundary_structure(self):
366        """Traverse boundary and
367        enumerate neighbour indices from -1 and
368        counting down.
369
370        Precondition:
371            self.boundary is defined.
372        Post condition:
373            neighbour array has unique negative indices for boundary
374            boundary_segments array imposes an ordering on segments
375            (not otherwise available from the dictionary)
376
377        Note: If a segment is listed in the boundary dictionary
378        it *will* become a boundary - even if there is a neighbouring triangle.
379        This would be the case for internal boundaries
380        """
381
382        #FIXME: Now Obsolete - maybe use some comments from here in
383        #domain.set_boundary
384
385        if self.boundary is None:
386            msg = 'Boundary dictionary must be defined before '
387            msg += 'building boundary structure'
388            raise msg
389
390
391        self.boundary_segments = self.boundary.keys()
392        self.boundary_segments.sort()
393
394        index = -1
395        for id, edge in self.boundary_segments:
396
397            #FIXME: One would detect internal boundaries as follows
398            #if self.neighbours[id, edge] > -1:
399            #    print 'Internal boundary'
400
401            self.neighbours[id, edge] = index
402            index -= 1
403
404
405    def get_boundary_tags(self):
406        """Return list of available boundary tags
407        """
408
409        tags = {}
410        for v in self.boundary.values():
411            tags[v] = 1
412
413        return tags.keys()
414
415
416    def get_boundary_polygon(self):
417        """Return bounding polygon as a list of points
418
419        FIXME: If triangles are listed as discontinuous
420        (e.g vertex coordinates listed multiple times),
421        this may not work as expected.
422        """
423
424        #V = self.get_vertex_coordinates()
425        segments = {}
426
427        #pmin = (min(self.coordinates[:,0]), min(self.coordinates[:,1]))
428        #pmax = (max(self.coordinates[:,0]), max(self.coordinates[:,1]))
429
430        #FIXME:Can this be written more compactly, e.g.
431        #using minimum and maximium?
432        pmin = array( [min(self.coordinates[:,0]),
433                       min(self.coordinates[:,1]) ] )
434
435        pmax = array( [max(self.coordinates[:,0]),
436                       max(self.coordinates[:,1]) ] )
437
438        mindist = sqrt(sum( (pmax-pmin)**2 ))
439        for i, edge_id in self.boundary.keys():
440            #Find vertex ids for boundary segment
441            if edge_id == 0: a = 1; b = 2
442            if edge_id == 1: a = 2; b = 0
443            if edge_id == 2: a = 0; b = 1
444
445            A = tuple(self.get_vertex_coordinate(i, a))
446            B = tuple(self.get_vertex_coordinate(i, b))
447
448            #Take the point closest to pmin as starting point
449            #Note: Could be arbitrary, but nice to have
450            #a unique way of selecting
451            dist_A = sqrt(sum( (A-pmin)**2 ))
452            dist_B = sqrt(sum( (B-pmin)**2 ))
453
454            #Find minimal point
455            if dist_A < mindist:
456                mindist = dist_A
457                p0 = A
458            if dist_B < mindist:
459                mindist = dist_B
460                p0 = B
461
462
463            if p0 is None:
464                raise 'Weird'
465                p0 = A #We need a starting point (FIXME)
466
467                print 'A', A
468                print 'B', B
469                print 'pmin', pmin
470                print
471
472            segments[A] = B
473
474
475        #Start with smallest point and follow boundary (counter clock wise)
476        polygon = [p0]
477        while len(polygon) < len(self.boundary):
478            p1 = segments[p0]
479            polygon.append(p1)
480            p0 = p1
481
482        return polygon
483
484
485    def check_integrity(self):
486        """Check that triangles are internally consistent e.g.
487        that area corresponds to edgelengths, that vertices
488        are arranged in a counter-clockwise order, etc etc
489        Neighbour structure will be checked by class Mesh
490        """
491
492        from config import epsilon
493        from math import pi
494        from anuga.utilities.numerical_tools import anglediff
495
496        N = self.number_of_elements
497
498        #Get x,y coordinates for all vertices for all triangles
499        V = self.get_vertex_coordinates()
500
501        #Check each triangle
502        for i in range(N):
503            x0 = V[i, 0]; y0 = V[i, 1]
504            x1 = V[i, 2]; y1 = V[i, 3]
505            x2 = V[i, 4]; y2 = V[i, 5]
506
507            #Check that area hasn't been compromised
508            area = self.areas[i]
509            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
510            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
511                  %(x0,y0,x1,y1,x2,y2)
512            assert abs((area - ref)/area) < epsilon, msg
513
514            #Check that points are arranged in counter clock-wise order
515            v0 = [x1-x0, y1-y0]
516            v1 = [x2-x1, y2-y1]
517            v2 = [x0-x2, y0-y2]
518
519            a0 = anglediff(v1, v0)
520            a1 = anglediff(v2, v1)
521            a2 = anglediff(v0, v2)
522
523            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
524            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
525            assert a0 < pi and a1 < pi and a2 < pi, msg
526
527            #Check that normals are orthogonal to edge vectors
528            #Note that normal[k] lies opposite vertex k
529
530            normal0 = self.normals[i, 0:2]
531            normal1 = self.normals[i, 2:4]
532            normal2 = self.normals[i, 4:6]
533
534            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
535
536                #Normalise
537                l_u = sqrt(u[0]*u[0] + u[1]*u[1])
538                l_v = sqrt(v[0]*v[0] + v[1]*v[1])               
539
540                x = (u[0]*v[0] + u[1]*v[1])/l_u/l_v #Inner product
541               
542                msg = 'Normal vector (%f,%f) is not perpendicular to' %tuple(v)
543                msg += ' edge (%f,%f) in triangle %d.' %(tuple(u) + (i,))
544                msg += ' Inner product is %e.' %x               
545                assert x < epsilon, msg
546
547
548        #Check that all vertices have been registered
549        for v_id, v in enumerate(self.vertexlist):
550
551            msg = 'Vertex %s does not belong to an element.'
552            #assert v is not None, msg
553            if v is None:
554                print msg%v_id
555
556        #Check integrity of neighbour structure
557        for i in range(N):
558            for v in self.triangles[i, :]:
559                #Check that all vertices have been registered
560                assert self.vertexlist[v] is not None
561
562                #Check that this triangle is listed with at least one vertex
563                assert (i, 0) in self.vertexlist[v] or\
564                       (i, 1) in self.vertexlist[v] or\
565                       (i, 2) in self.vertexlist[v]
566
567
568
569            #Check neighbour structure
570            for k, neighbour_id in enumerate(self.neighbours[i,:]):
571
572                #Assert that my neighbour's neighbour is me
573                #Boundaries need not fulfill this
574                if neighbour_id >= 0:
575                    edge = self.neighbour_edges[i, k]
576                    msg = 'Triangle %d has neighbour %d but it does not point back. \n' %(i,neighbour_id)
577                    msg += 'Only points to (%s)' %(self.neighbours[neighbour_id,:])
578                    assert self.neighbours[neighbour_id, edge] == i ,msg
579
580
581
582        #Check that all boundaries have
583        # unique, consecutive, negative indices
584
585        #L = len(self.boundary)
586        #for i in range(L):
587        #    id, edge = self.boundary_segments[i]
588        #    assert self.neighbours[id, edge] == -i-1
589
590
591        #NOTE: This assert doesn't hold true if there are internal boundaries
592        #FIXME: Look into this further.
593        #FIXME (Ole): In pyvolution mark 3 this is OK again
594        #NOTE: No longer works because neighbour structure is modified by
595        #      domain set_boundary.
596        #for id, edge in self.boundary:
597        #    assert self.neighbours[id,edge] < 0
598        #
599        #NOTE (Ole): I reckon this was resolved late 2004?
600        #
601        #See domain.set_boundary
602
603
604    def get_centroid_coordinates(self):
605        """Return all centroid coordinates.
606        Return all centroid coordinates for all triangles as an Nx2 array
607        (ordered as x0, y0 for each triangle)
608        """
609        return self.centroid_coordinates
610
611
612
613
614    def statistics(self):
615        """Output statistics about mesh
616        """
617
618        from anuga.utilities.numerical_tools import histogram
619
620        vertex_coordinates = self.vertex_coordinates
621        areas = self.areas
622        x = vertex_coordinates[:,0]
623        y = vertex_coordinates[:,1]
624
625
626        #Setup 10 bins for area histogram
627        m = max(areas)
628        bins = arange(0., m, m/10)
629        hist = histogram(areas, bins)
630
631        str =  '------------------------------------------------\n'
632        str += 'Mesh statistics:\n'
633        str += '  Number of triangles = %d\n' %self.number_of_elements
634        str += '  Extent:\n'
635        str += '    x in [%f, %f]\n' %(min(x), max(x))
636        str += '    y in [%f, %f]\n' %(min(y), max(y))
637        str += '  Areas:\n'
638        str += '    A in [%f, %f]\n' %(min(areas), max(areas))
639        str += '    Histogram:\n'
640
641        hi = bins[0]
642        for i, count in enumerate(hist):
643            lo = hi
644            if i+1 < len(bins):
645                #Open upper interval               
646                hi = bins[i+1]
647                str += '      [%f, %f[: %d\n' %(lo, hi, count)               
648            else:
649                #Closed upper interval
650                hi = m
651                str += '      [%f, %f]: %d\n' %(lo, hi, count)               
652               
653                     
654        str += 'Boundary:\n'
655        str += '  Number of boundary segments == %d\n' %(len(self.boundary))
656        str += '  Boundary tags == %s\n' %self.get_boundary_tags() 
657        str += '------------------------------------------------\n'
658       
659
660        return str
661
Note: See TracBrowser for help on using the repository browser.