source: anuga_core/source/anuga/abstract_2d_finite_volumes/neighbour_mesh.py @ 4780

Last change on this file since 4780 was 4780, checked in by duncan, 17 years ago

Bug fix

File size: 30.3 KB
Line 
1"""Classes implementing general 2D triangular mesh with neighbour structure.
2
3   This structure is purely geometrical. Anything relating to quantities
4   or timestepping is implemented in subclass domain.py.
5
6   Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
7   Geoscience Australia, 2004
8"""
9
10from general_mesh import General_mesh
11from math import pi, sqrt
12       
13
14class Mesh(General_mesh):
15    """Collection of triangular elements (purely geometric)
16
17    A triangular element is defined in terms of three vertex ids,
18    ordered counter clock-wise,
19    each corresponding to a given coordinate set.
20    Vertices from different elements can point to the same
21    coordinate set.
22
23    Coordinate sets are implemented as an N x 2 Numeric array containing
24    x and y coordinates.
25
26
27    To instantiate:
28       Mesh(coordinates, triangles)
29
30    where
31
32      coordinates is either a list of 2-tuples or an Mx2 Numeric array of
33      floats representing all x, y coordinates in the mesh.
34
35      triangles is either a list of 3-tuples or an Nx3 Numeric array of
36      integers representing indices of all vertices in the mesh.
37      Each vertex is identified by its index i in [0, M-1].
38
39
40    Example:
41        a = [0.0, 0.0]
42        b = [0.0, 2.0]
43        c = [2.0,0.0]
44        e = [2.0, 2.0]
45
46        points = [a, b, c, e]
47        triangles = [ [1,0,2], [1,2,3] ]   #bac, bce
48        mesh = Mesh(points, triangles)
49
50        #creates two triangles: bac and bce
51
52
53    Mesh takes the optional third argument boundary which is a
54    dictionary mapping from (element_id, edge_id) to boundary tag.
55    The default value is None which will assign the default_boundary_tag
56    as specified in config.py to all boundary edges.
57    """
58
59    #FIXME: Maybe rename coordinates to points (as in a poly file)
60    #But keep 'vertex_coordinates'
61
62    #FIXME: Put in check for angles less than a set minimum
63
64
65    def __init__(self, coordinates, triangles,
66                 boundary=None,
67                 tagged_elements=None,
68                 geo_reference=None,
69                 number_of_full_nodes=None,
70                 number_of_full_triangles=None,
71                 use_inscribed_circle=False,
72                 verbose=False):
73        """
74        Build triangles from x,y coordinates (sequence of 2-tuples or
75        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
76        or Nx3 Numeric array of non-negative integers).
77        """
78
79
80
81        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
82
83        General_mesh.__init__(self, coordinates, triangles,
84                              number_of_full_nodes=\
85                              number_of_full_nodes,
86                              number_of_full_triangles=\
87                              number_of_full_triangles,
88                              geo_reference=geo_reference,
89                              verbose=verbose)
90
91        if verbose: print 'Initialising mesh'         
92
93        N = len(self) #Number_of_triangles
94
95        self.use_inscribed_circle = use_inscribed_circle
96
97        #Allocate space for geometric quantities
98        self.centroid_coordinates = zeros((N, 2), Float)
99
100        self.radii = zeros(N, Float)
101
102        self.neighbours = zeros((N, 3), Int)
103        self.neighbour_edges = zeros((N, 3), Int)
104        self.number_of_boundaries = zeros(N, Int)
105        self.surrogate_neighbours = zeros((N, 3), Int)
106
107        #Get x,y coordinates for all triangles and store
108        V = self.vertex_coordinates # Relative coordinates
109
110        #Initialise each triangle
111        if verbose: print 'Mesh: Computing centroids and radii'       
112        for i in range(N):
113            if verbose and i % ((N+10)/10) == 0: print '(%d/%d)' %(i, N)
114
115            x0, y0 = V[3*i, :]
116            x1, y1 = V[3*i+1, :]
117            x2, y2 = V[3*i+2, :]                       
118
119            #x0 = V[i, 0]; y0 = V[i, 1]
120            #x1 = V[i, 2]; y1 = V[i, 3]
121            #x2 = V[i, 4]; y2 = V[i, 5]
122
123            #Compute centroid
124            centroid = array([(x0 + x1 + x2)/3, (y0 + y1 + y2)/3])
125            self.centroid_coordinates[i] = centroid
126
127
128            if self.use_inscribed_circle == False:
129                #OLD code. Computed radii may exceed that of an
130                #inscribed circle
131
132                #Midpoints
133                m0 = array([(x1 + x2)/2, (y1 + y2)/2])
134                m1 = array([(x0 + x2)/2, (y0 + y2)/2])
135                m2 = array([(x1 + x0)/2, (y1 + y0)/2])
136
137                #The radius is the distance from the centroid of
138                #a triangle to the midpoint of the side of the triangle
139                #closest to the centroid
140                d0 = sqrt(sum( (centroid-m0)**2 ))
141                d1 = sqrt(sum( (centroid-m1)**2 ))
142                d2 = sqrt(sum( (centroid-m2)**2 ))
143
144                self.radii[i] = min(d0, d1, d2)
145
146            else:
147                #NEW code added by Peter Row. True radius
148                #of inscribed circle is computed
149
150                a = sqrt((x0-x1)**2+(y0-y1)**2)
151                b = sqrt((x1-x2)**2+(y1-y2)**2)
152                c = sqrt((x2-x0)**2+(y2-y0)**2)
153
154                self.radii[i]=2.0*self.areas[i]/(a+b+c)
155
156
157            #Initialise Neighbours (-1 means that it is a boundary neighbour)
158            self.neighbours[i, :] = [-1, -1, -1]
159
160            #Initialise edge ids of neighbours
161            #In case of boundaries this slot is not used
162            self.neighbour_edges[i, :] = [-1, -1, -1]
163
164
165        #Build neighbour structure
166        if verbose: print 'Mesh: Building neigbour structure'               
167        self.build_neighbour_structure()
168
169        #Build surrogate neighbour structure
170        if verbose: print 'Mesh: Building surrogate neigbour structure'
171        self.build_surrogate_neighbour_structure()
172
173        #Build boundary dictionary mapping (id, edge) to symbolic tags
174        if verbose: print 'Mesh: Building boundary dictionary'
175        self.build_boundary_dictionary(boundary)
176
177        #Build tagged element  dictionary mapping (tag) to array of elements
178        if verbose: print 'Mesh: Building tagged elements dictionary'       
179        self.build_tagged_elements_dictionary(tagged_elements)
180
181        # Build a list of vertices that are not connected to any triangles
182        self.lone_vertices = []
183        #Check that all vertices have been registered
184        for node, count in enumerate(self.number_of_triangles_per_node):   
185            #msg = 'Node %d does not belong to an element.' %node
186            #assert count > 0, msg
187            if count == 0:
188                self.lone_vertices.append(node)
189               
190        #Update boundary indices FIXME: OBSOLETE
191        #self.build_boundary_structure()
192
193        #FIXME check integrity?
194        if verbose: print 'Mesh: Done'               
195
196    def __repr__(self):
197        return General_mesh.__repr__(self) + ', %d boundary segments'\
198               %(len(self.boundary))
199
200
201    def set_to_inscribed_circle(self,safety_factor = 1):
202        #FIXME phase out eventually
203        N = self.number_of_triangles
204        V = self.vertex_coordinates
205
206        #initialising min and max ratio
207        i=0
208        old_rad = self.radii[i]
209        x0 = V[i, 0]; y0 = V[i, 1]
210        x1 = V[i, 2]; y1 = V[i, 3]
211        x2 = V[i, 4]; y2 = V[i, 5]
212        a = sqrt((x0-x1)**2+(y0-y1)**2)
213        b = sqrt((x1-x2)**2+(y1-y2)**2)
214        c = sqrt((x2-x0)**2+(y2-y0)**2)
215        ratio = old_rad/self.radii[i]
216        max_ratio = ratio
217        min_ratio = ratio
218
219        for i in range(N):
220            old_rad = self.radii[i]
221            x0 = V[i, 0]; y0 = V[i, 1]
222            x1 = V[i, 2]; y1 = V[i, 3]
223            x2 = V[i, 4]; y2 = V[i, 5]
224            a = sqrt((x0-x1)**2+(y0-y1)**2)
225            b = sqrt((x1-x2)**2+(y1-y2)**2)
226            c = sqrt((x2-x0)**2+(y2-y0)**2)
227            self.radii[i]=self.areas[i]/(2*(a+b+c))*safety_factor
228            ratio = old_rad/self.radii[i]
229            if ratio >= max_ratio: max_ratio = ratio
230            if ratio <= min_ratio: min_ratio = ratio
231        return max_ratio,min_ratio
232
233    def build_neighbour_structure(self):
234        """Update all registered triangles to point to their neighbours.
235
236        Also, keep a tally of the number of boundaries for each triangle
237
238        Postconditions:
239          neighbours and neighbour_edges is populated
240          number_of_boundaries integer array is defined.
241        """
242
243        #Step 1:
244        #Build dictionary mapping from segments (2-tuple of points)
245        #to left hand side edge (facing neighbouring triangle)
246
247        N = len(self) #Number_of_triangles
248        neighbourdict = {}
249        for i in range(N):
250
251            #Register all segments as keys mapping to current triangle
252            #and segment id
253            a = self.triangles[i, 0]
254            b = self.triangles[i, 1]
255            c = self.triangles[i, 2]
256            if neighbourdict.has_key((a,b)):
257                    msg = "Edge 2 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[a,b][1],neighbourdict[a,b][0])
258                    raise Exception, msg
259            if neighbourdict.has_key((b,c)):
260                    msg = "Edge 0 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[b,c][1],neighbourdict[b,c][0])
261                    raise msg
262            if neighbourdict.has_key((c,a)):
263                    msg = "Edge 1 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[c,a][1],neighbourdict[c,a][0])
264                    raise msg
265
266            neighbourdict[a,b] = (i, 2) #(id, edge)
267            neighbourdict[b,c] = (i, 0) #(id, edge)
268            neighbourdict[c,a] = (i, 1) #(id, edge)
269
270
271        #Step 2:
272        #Go through triangles again, but this time
273        #reverse direction of segments and lookup neighbours.
274        for i in range(N):
275            a = self.triangles[i, 0]
276            b = self.triangles[i, 1]
277            c = self.triangles[i, 2]
278
279            self.number_of_boundaries[i] = 3
280            if neighbourdict.has_key((b,a)):
281                self.neighbours[i, 2] = neighbourdict[b,a][0]
282                self.neighbour_edges[i, 2] = neighbourdict[b,a][1]
283                self.number_of_boundaries[i] -= 1
284
285            if neighbourdict.has_key((c,b)):
286                self.neighbours[i, 0] = neighbourdict[c,b][0]
287                self.neighbour_edges[i, 0] = neighbourdict[c,b][1]
288                self.number_of_boundaries[i] -= 1
289
290            if neighbourdict.has_key((a,c)):
291                self.neighbours[i, 1] = neighbourdict[a,c][0]
292                self.neighbour_edges[i, 1] = neighbourdict[a,c][1]
293                self.number_of_boundaries[i] -= 1
294
295
296    def build_surrogate_neighbour_structure(self):
297        """Build structure where each triangle edge points to its neighbours
298        if they exist. Otherwise point to the triangle itself.
299
300        The surrogate neighbour structure is useful for computing gradients
301        based on centroid values of neighbours.
302
303        Precondition: Neighbour structure is defined
304        Postcondition:
305          Surrogate neighbour structure is defined:
306          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
307          triangles.
308
309        """
310
311        N = len(self) #Number of triangles
312        for i in range(N):
313            #Find all neighbouring volumes that are not boundaries
314            for k in range(3):
315                if self.neighbours[i, k] < 0:
316                    self.surrogate_neighbours[i, k] = i #Point this triangle
317                else:
318                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
319
320
321
322    def build_boundary_dictionary(self, boundary = None):
323        """Build or check the dictionary of boundary tags.
324         self.boundary is a dictionary of tags,
325         keyed by volume id and edge:
326         { (id, edge): tag, ... }
327
328         Postconditions:
329            self.boundary is defined.
330        """
331
332        from anuga.config import default_boundary_tag
333
334        if boundary is None:
335            boundary = {}
336            for vol_id in range(len(self)):
337                for edge_id in range(0, 3):
338                    if self.neighbours[vol_id, edge_id] < 0:
339                        boundary[(vol_id, edge_id)] = default_boundary_tag
340        else:
341            #Check that all keys in given boundary exist
342            for vol_id, edge_id in boundary.keys():
343                msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
344                a, b = self.neighbours.shape
345                assert vol_id < a and edge_id < b, msg
346
347                #FIXME: This assert violates internal boundaries (delete it)
348                #msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
349                #assert self.neighbours[vol_id, edge_id] < 0, msg
350
351            #Check that all boundary segments are assigned a tag
352            for vol_id in range(len(self)):
353                for edge_id in range(0, 3):
354                    if self.neighbours[vol_id, edge_id] < 0:
355                        if not boundary.has_key( (vol_id, edge_id) ):
356                            msg = 'WARNING: Given boundary does not contain '
357                            msg += 'tags for edge (%d, %d). '\
358                                   %(vol_id, edge_id)
359                            msg += 'Assigning default tag (%s).'\
360                                   %default_boundary_tag
361
362                            #FIXME: Print only as per verbosity
363                            #print msg
364
365                            #FIXME: Make this situation an error in the future
366                            #and make another function which will
367                            #enable default boundary-tags where
368                            #tags a not specified
369                            boundary[ (vol_id, edge_id) ] =\
370                                      default_boundary_tag
371
372
373
374        self.boundary = boundary
375
376
377    def build_tagged_elements_dictionary(self, tagged_elements = None):
378        """Build the dictionary of element tags.
379         self.tagged_elements is a dictionary of element arrays,
380         keyed by tag:
381         { (tag): [e1, e2, e3..] }
382
383         Postconditions:
384            self.element_tag is defined
385        """
386        from Numeric import array, Int
387
388        if tagged_elements is None:
389            tagged_elements = {}
390        else:
391            #Check that all keys in given boundary exist
392            for tag in tagged_elements.keys():
393                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
394
395                msg = 'Not all elements exist. '
396                assert max(tagged_elements[tag]) < len(self), msg
397        #print "tagged_elements", tagged_elements
398        self.tagged_elements = tagged_elements
399
400    def build_boundary_structure(self):
401        """Traverse boundary and
402        enumerate neighbour indices from -1 and
403        counting down.
404
405        Precondition:
406            self.boundary is defined.
407        Post condition:
408            neighbour array has unique negative indices for boundary
409            boundary_segments array imposes an ordering on segments
410            (not otherwise available from the dictionary)
411
412        Note: If a segment is listed in the boundary dictionary
413        it *will* become a boundary - even if there is a neighbouring triangle.
414        This would be the case for internal boundaries
415        """
416
417        #FIXME: Now Obsolete - maybe use some comments from here in
418        #domain.set_boundary
419
420        if self.boundary is None:
421            msg = 'Boundary dictionary must be defined before '
422            msg += 'building boundary structure'
423            raise msg
424
425
426        self.boundary_segments = self.boundary.keys()
427        self.boundary_segments.sort()
428
429        index = -1
430        for id, edge in self.boundary_segments:
431
432            #FIXME: One would detect internal boundaries as follows
433            #if self.neighbours[id, edge] > -1:
434            #    print 'Internal boundary'
435
436            self.neighbours[id, edge] = index
437            index -= 1
438
439
440    def get_boundary_tags(self):
441        """Return list of available boundary tags
442        """
443
444        tags = {}
445        for v in self.boundary.values():
446            tags[v] = 1
447
448        return tags.keys()
449
450
451    def get_boundary_polygon(self, verbose=False):
452        """Return bounding polygon for mesh (counter clockwise)
453
454        Using the mesh boundary, derive a bounding polygon for this mesh.
455        If multiple vertex values are present (vertices stored uniquely),
456        the algorithm will select the path that contains the entire mesh.
457
458        All points are in absolute UTM coordinates
459        """
460       
461        from Numeric import allclose, sqrt, array, minimum, maximum
462        from anuga.utilities.numerical_tools import angle, ensure_numeric     
463
464
465        # Get mesh extent
466        xmin, xmax, ymin, ymax = self.get_extent(absolute=True)
467        pmin = ensure_numeric([xmin, ymin])
468        pmax = ensure_numeric([xmax, ymax])       
469
470
471        # Assemble dictionary of boundary segments and choose starting point
472        segments = {}
473        inverse_segments = {}
474        p0 = None
475        mindist = sqrt(sum((pmax-pmin)**2)) # Start value across entire mesh
476        for i, edge_id in self.boundary.keys():
477            # Find vertex ids for boundary segment
478            if edge_id == 0: a = 1; b = 2
479            if edge_id == 1: a = 2; b = 0
480            if edge_id == 2: a = 0; b = 1
481
482            A = self.get_vertex_coordinate(i, a, absolute=True) # Start
483            B = self.get_vertex_coordinate(i, b, absolute=True) # End
484
485
486            # Take the point closest to pmin as starting point
487            # Note: Could be arbitrary, but nice to have
488            # a unique way of selecting
489            dist_A = sqrt(sum((A-pmin)**2))
490            dist_B = sqrt(sum((B-pmin)**2))
491
492            # Find lower leftmost point
493            if dist_A < mindist:
494                mindist = dist_A
495                p0 = A
496            if dist_B < mindist:
497                mindist = dist_B
498                p0 = B
499
500
501            # Sanity check
502            if p0 is None:
503                raise Exception('Impossible')
504
505
506            # Register potential paths from A to B
507            if not segments.has_key(tuple(A)):
508                segments[tuple(A)] = [] # Empty list for candidate points
509
510            segments[tuple(A)].append(B)               
511
512
513        # Start with smallest point and follow boundary (counter clock wise)
514        polygon = [list(p0)]# Storage for final boundary polygon
515        point_registry = {} # Keep track of storage to avoid multiple runs
516                            # around boundary. This will only be the case if
517                            # there are more than one candidate.
518                            # FIXME (Ole): Perhaps we can do away with polygon
519                            # and use only point_registry to save space.
520
521        point_registry[tuple(p0)] = 0                           
522                           
523        while len(point_registry) < len(self.boundary):
524
525            candidate_list = segments[tuple(p0)]
526            if len(candidate_list) > 1:
527                # Multiple points detected (this will be the case for meshes
528                # with duplicate points as those used for discontinuous
529                # triangles with vertices stored uniquely).
530                # Take the candidate that is furthest to the clockwise
531                # direction, as that will follow the boundary.
532                #
533                # This will also be the case for pathological triangles
534                # that have no neighbours.
535
536                if verbose:
537                    print 'Point %s has multiple candidates: %s'\
538                          %(str(p0), candidate_list)
539
540                # Check that previous are not in candidate list
541                #for p in candidate_list:
542                #    assert not allclose(p0, p)
543
544                # Choose vector against which all angles will be measured
545                if len(polygon) > 1:   
546                    v_prev = p0 - polygon[-2] # Vector that leads to p0
547                                              # from previous point
548                else:
549                    # FIXME (Ole): What do we do if the first point has
550                    # multiple candidates?
551                    # Being the lower left corner, perhaps we can use the
552                    # vector [1, 0], but I really don't know if this is
553                    # completely watertight.
554                    v_prev = [1.0, 0.0]
555                   
556
557                # Choose candidate with minimum angle   
558                minimum_angle = 2*pi
559                for pc in candidate_list:
560
561                    vc = pc-p0  # Candidate vector (from p0 to candidate pt)
562                   
563                    # Angle between each candidate and the previous vector
564                    # in [-pi, pi]
565                    ac = angle(vc, v_prev)
566                    if ac > pi:
567                        # Give preference to angles on the right hand side
568                        # of v_prev
569                        # print 'pc = %s, changing angle from %f to %f'\
570                        # %(pc, ac*180/pi, (ac-2*pi)*180/pi)
571                        ac = ac-2*pi
572
573                    # Take the minimal angle corresponding to the
574                    # rightmost vector
575                    if ac < minimum_angle:
576                        minimum_angle = ac
577                        p1 = pc             # Best candidate
578                       
579
580                if verbose is True:
581                    print '  Best candidate %s, angle %f'\
582                          %(p1, minimum_angle*180/pi)
583                   
584            else:
585                p1 = candidate_list[0]
586
587               
588            if point_registry.has_key(tuple(p1)):
589                # We have reached a point already visited.
590               
591                if allclose(p1, polygon[0]):
592                    # If it is the initial point, the polygon is complete.
593                   
594                    if verbose is True:
595                        print '  Stop criterion fulfilled at point %s' %p1
596                        print polygon               
597                       
598                    # We have completed the boundary polygon - yeehaa
599                    break
600                else:   
601                    # The point already visited is not the initial point
602                    # This would be a pathological triangle, but the
603                    # algorithm must be able to deal with this
604                    pass
605   
606            else:
607                # We are still finding new points on the boundary
608                point_registry[tuple(p1)] = len(point_registry)
609           
610            polygon.append(list(p1)) # De-Numeric each point :-)
611            p0 = p1
612
613
614        return polygon
615
616
617    def check_integrity(self):
618        """Check that triangles are internally consistent e.g.
619        that area corresponds to edgelengths, that vertices
620        are arranged in a counter-clockwise order, etc etc
621        Neighbour structure will be checked by class Mesh
622        """
623
624        from anuga.config import epsilon
625        from anuga.utilities.numerical_tools import anglediff
626
627        from Numeric import sort, allclose
628
629        N = len(self)
630        #Get x,y coordinates for all vertices for all triangles
631        V = self.get_vertex_coordinates()
632        #Check each triangle
633        for i in range(N):
634
635            x0, y0 = V[3*i, :]
636            x1, y1 = V[3*i+1, :]
637            x2, y2 = V[3*i+2, :]
638           
639            #Check that area hasn't been compromised
640            area = self.areas[i]
641            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
642            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
643                  %(x0,y0,x1,y1,x2,y2)
644            assert abs((area - ref)/area) < epsilon, msg
645
646            #Check that points are arranged in counter clock-wise order
647            v0 = [x1-x0, y1-y0]
648            v1 = [x2-x1, y2-y1]
649            v2 = [x0-x2, y0-y2]
650            a0 = anglediff(v1, v0)
651            a1 = anglediff(v2, v1)
652            a2 = anglediff(v0, v2)
653
654            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
655            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
656            assert a0 < pi and a1 < pi and a2 < pi, msg
657
658            #Check that normals are orthogonal to edge vectors
659            #Note that normal[k] lies opposite vertex k
660
661            normal0 = self.normals[i, 0:2]
662            normal1 = self.normals[i, 2:4]
663            normal2 = self.normals[i, 4:6]
664
665            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
666
667                #Normalise
668                l_u = sqrt(u[0]*u[0] + u[1]*u[1])
669                l_v = sqrt(v[0]*v[0] + v[1]*v[1])               
670
671                x = (u[0]*v[0] + u[1]*v[1])/l_u/l_v #Inner product
672               
673                msg = 'Normal vector (%f,%f) is not perpendicular to' %tuple(v)
674                msg += ' edge (%f,%f) in triangle %d.' %(tuple(u) + (i,))
675                msg += ' Inner product is %e.' %x               
676                assert x < epsilon, msg
677
678
679
680
681        #Check neighbour structure
682        for i in range(N):
683            # For each triangle
684           
685            for k, neighbour_id in enumerate(self.neighbours[i,:]):
686
687                #Assert that my neighbour's neighbour is me
688                #Boundaries need not fulfill this
689                if neighbour_id >= 0:
690                    edge = self.neighbour_edges[i, k]
691                    msg = 'Triangle %d has neighbour %d but it does not point back. \n' %(i,neighbour_id)
692                    msg += 'Only points to (%s)' %(self.neighbours[neighbour_id,:])
693                    assert self.neighbours[neighbour_id, edge] == i ,msg
694
695
696
697        #Check that all boundaries have
698        # unique, consecutive, negative indices
699
700        #L = len(self.boundary)
701        #for i in range(L):
702        #    id, edge = self.boundary_segments[i]
703        #    assert self.neighbours[id, edge] == -i-1
704
705
706        #NOTE: This assert doesn't hold true if there are internal boundaries
707        #FIXME: Look into this further.
708        #FIXME (Ole): In pyvolution mark 3 this is OK again
709        #NOTE: No longer works because neighbour structure is modified by
710        #      domain set_boundary.
711        #for id, edge in self.boundary:
712        #    assert self.neighbours[id,edge] < 0
713        #
714        #NOTE (Ole): I reckon this was resolved late 2004?
715        #
716        #See domain.set_boundary
717
718
719
720        #Check integrity of inverted triangle structure
721
722        V = self.vertex_value_indices[:] #Take a copy
723        V = sort(V)
724        assert allclose(V, range(3*N))
725
726        assert sum(self.number_of_triangles_per_node) ==\
727               len(self.vertex_value_indices)
728
729        # Check number of triangles per node
730        count = [0]*self.number_of_nodes
731        for triangle in self.triangles:
732            for i in triangle:
733                count[i] += 1
734
735        assert allclose(count, self.number_of_triangles_per_node)
736
737
738        # Check integrity of vertex_value_indices
739        current_node = 0
740        k = 0 # Track triangles touching on node
741        for index in self.vertex_value_indices:
742
743            if self.number_of_triangles_per_node[current_node] == 0:
744                # Node is lone - i.e. not part of the mesh
745                continue
746           
747            k += 1
748           
749            volume_id = index / 3
750            vertex_id = index % 3
751           
752            msg = 'Triangle %d, vertex %d points to %d. Should have been %d'\
753                  %(volume_id, vertex_id, self.triangles[volume_id, vertex_id], current_node)
754            assert self.triangles[volume_id, vertex_id] == current_node, msg
755                       
756            if self.number_of_triangles_per_node[current_node] == k:
757                # Move on to next node
758                k = 0
759                current_node += 1
760
761
762    def get_lone_vertices(self):
763        """Return a list of vertices that are not connected to any triangles.
764
765        """
766        return self.lone_vertices
767
768    def get_centroid_coordinates(self, absolute=False):
769        """Return all centroid coordinates.
770        Return all centroid coordinates for all triangles as an Nx2 array
771        (ordered as x0, y0 for each triangle)
772
773        Boolean keyword argument absolute determines whether coordinates
774        are to be made absolute by taking georeference into account
775        Default is False as many parts of ANUGA expects relative coordinates.
776        """
777
778        V = self.centroid_coordinates
779        if absolute is True:
780            if not self.geo_reference.is_absolute():
781                V = self.geo_reference.get_absolute(V)
782           
783        return V
784
785       
786    def get_radii(self):
787        """Return all radii.
788        Return radius of inscribed cirle for all triangles
789        """
790        return self.radii       
791
792
793
794    def statistics(self):
795        """Output statistics about mesh
796        """
797
798        from Numeric import arange
799        from anuga.utilities.numerical_tools import histogram, create_bins
800
801        vertex_coordinates = self.vertex_coordinates # Relative coordinates
802        areas = self.areas
803        x = vertex_coordinates[:,0]
804        y = vertex_coordinates[:,1]
805
806
807        #Setup 10 bins for area histogram
808        bins = create_bins(areas, 10)
809        #m = max(areas)
810        #bins = arange(0., m, m/10)
811        hist = histogram(areas, bins)
812
813        str =  '------------------------------------------------\n'
814        str += 'Mesh statistics:\n'
815        str += '  Number of triangles = %d\n' %len(self)
816        str += '  Extent [m]:\n'
817        str += '    x in [%f, %f]\n' %(min(x), max(x))
818        str += '    y in [%f, %f]\n' %(min(y), max(y))
819        str += '  Areas [m^2]:\n'
820        str += '    A in [%f, %f]\n' %(min(areas), max(areas))
821        str += '    number of distinct areas: %d\n' %(len(areas))       
822        str += '    Histogram:\n'
823
824        hi = bins[0]
825        for i, count in enumerate(hist):
826            lo = hi
827            if i+1 < len(bins):
828                #Open upper interval               
829                hi = bins[i+1]
830                str += '      [%f, %f[: %d\n' %(lo, hi, count)               
831            else:
832                #Closed upper interval
833                hi = max(areas)
834                str += '      [%f, %f]: %d\n' %(lo, hi, count)
835
836        N = len(areas)
837        if N > 10:
838            str += '    Percentiles (10%):\n'
839            areas = areas.tolist()
840            areas.sort()
841
842            k = 0
843            lower = min(areas)
844            for i, a in enumerate(areas):       
845                if i % (N/10) == 0 and i != 0: #For every 10% of the sorted areas               
846                    str += '      %d triangles in [%f, %f]\n' %(i-k, lower, a)
847                    lower = a
848                    k = i
849                   
850            str += '      %d triangles in [%f, %f]\n'\
851                   %(N-k, lower, max(areas))                   
852               
853                     
854        str += '  Boundary:\n'
855        str += '    Number of boundary segments == %d\n' %(len(self.boundary))
856        str += '    Boundary tags == %s\n' %self.get_boundary_tags() 
857        str += '------------------------------------------------\n'
858       
859
860        return str
861
Note: See TracBrowser for help on using the repository browser.