source: inundation/pyvolution/mesh.py @ 2852

Last change on this file since 2852 was 2778, checked in by steve, 19 years ago

On our 64 bit machine, ran into problem in pmesh/mesh.py where seg[0], seg[1], triangle,
and neighbor where not seen as integers. Had to wrap them in int(..)

File size: 25.9 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
8from math import pi, sqrt
9       
10
11class Mesh(General_mesh):
12    """Collection of triangular elements (purely geometric)
13
14    A triangular element is defined in terms of three vertex ids,
15    ordered counter clock-wise,
16    each corresponding to a given coordinate set.
17    Vertices from different elements can point to the same
18    coordinate set.
19
20    Coordinate sets are implemented as an N x 2 Numeric array containing
21    x and y coordinates.
22
23
24    To instantiate:
25       Mesh(coordinates, triangles)
26
27    where
28
29      coordinates is either a list of 2-tuples or an Mx2 Numeric array of
30      floats representing all x, y coordinates in the mesh.
31
32      triangles is either a list of 3-tuples or an Nx3 Numeric array of
33      integers representing indices of all vertices in the mesh.
34      Each vertex is identified by its index i in [0, M-1].
35
36
37    Example:
38        a = [0.0, 0.0]
39        b = [0.0, 2.0]
40        c = [2.0,0.0]
41        e = [2.0, 2.0]
42
43        points = [a, b, c, e]
44        triangles = [ [1,0,2], [1,2,3] ]   #bac, bce
45        mesh = Mesh(points, triangles)
46
47        #creates two triangles: bac and bce
48
49
50    Mesh takes the optional third argument boundary which is a
51    dictionary mapping from (element_id, edge_id) to boundary tag.
52    The default value is None which will assign the default_boundary_tag
53    as specified in config.py to all boundary edges.
54    """
55
56    #FIXME: Maybe rename coordinates to points (as in a poly file)
57    #But keep 'vertex_coordinates'
58
59    #FIXME: Put in check for angles less than a set minimum
60
61
62    def __init__(self, coordinates, triangles, boundary = None,
63                 tagged_elements = None, geo_reference = None,
64                 use_inscribed_circle = False):
65        """
66        Build triangles from x,y coordinates (sequence of 2-tuples or
67        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
68        or Nx3 Numeric array of non-negative integers).
69        """
70
71
72
73        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
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        from Numeric import array, Int
353
354        if tagged_elements is None:
355            tagged_elements = {}
356        else:
357            #Check that all keys in given boundary exist
358            for tag in tagged_elements.keys():
359                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
360
361                msg = 'Not all elements exist. '
362                assert max(tagged_elements[tag]) < self.number_of_elements, msg
363        #print "tagged_elements", tagged_elements
364        self.tagged_elements = tagged_elements
365
366    def build_boundary_structure(self):
367        """Traverse boundary and
368        enumerate neighbour indices from -1 and
369        counting down.
370
371        Precondition:
372            self.boundary is defined.
373        Post condition:
374            neighbour array has unique negative indices for boundary
375            boundary_segments array imposes an ordering on segments
376            (not otherwise available from the dictionary)
377
378        Note: If a segment is listed in the boundary dictionary
379        it *will* become a boundary - even if there is a neighbouring triangle.
380        This would be the case for internal boundaries
381        """
382
383        #FIXME: Now Obsolete - maybe use some comments from here in
384        #domain.set_boundary
385
386        if self.boundary is None:
387            msg = 'Boundary dictionary must be defined before '
388            msg += 'building boundary structure'
389            raise msg
390
391
392        self.boundary_segments = self.boundary.keys()
393        self.boundary_segments.sort()
394
395        index = -1
396        for id, edge in self.boundary_segments:
397
398            #FIXME: One would detect internal boundaries as follows
399            #if self.neighbours[id, edge] > -1:
400            #    print 'Internal boundary'
401
402            self.neighbours[id, edge] = index
403            index -= 1
404
405
406    def get_boundary_tags(self):
407        """Return list of available boundary tags
408        """
409
410        tags = {}
411        for v in self.boundary.values():
412            tags[v] = 1
413
414        return tags.keys()
415
416
417    def get_boundary_polygon(self, verbose = False):
418        """Return bounding polygon for mesh (counter clockwise)
419
420        Using the mesh boundary, derive a bounding polygon for this mesh.
421        If multiple vertex values are present, the algorithm will select the
422        path that contains the mesh.
423        """
424       
425        from Numeric import allclose, sqrt, array, minimum, maximum
426        from utilities.numerical_tools import angle, ensure_numeric       
427
428
429        # Get mesh extent
430        xmin, xmax, ymin, ymax = self.get_extent()
431        pmin = ensure_numeric([xmin, ymin])
432        pmax = ensure_numeric([xmax, ymax])       
433
434
435        # Assemble dictionary of boundary segments and choose starting point
436        segments = {}
437        inverse_segments = {}
438        mindist = sqrt(sum((pmax-pmin)**2)) #Start value across entire mesh
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 = self.get_vertex_coordinate(i, a) # Start
446            B = self.get_vertex_coordinate(i, b) # End
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 lower leftmost 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            # Sanity check
464            if p0 is None:
465                raise Exception('Impossible')
466
467
468            # Register potential paths from A to B
469            if not segments.has_key(tuple(A)):
470                segments[tuple(A)] = [] # Empty list for candidate points               
471               
472            segments[tuple(A)].append(B)               
473
474
475
476
477        #Start with smallest point and follow boundary (counter clock wise)
478        polygon = [p0]      # Storage for final boundary polygon
479        point_registry = {} # Keep track of storage to avoid multiple runs around boundary
480                            # This will only be the case if there are more than one candidate
481                            # FIXME (Ole): Perhaps we can do away with polygon and use
482                            # only point_registry to save space.
483
484        point_registry[tuple(p0)] = 0                           
485                           
486        #while len(polygon) < len(self.boundary):
487        while len(point_registry) < len(self.boundary):
488
489            candidate_list = segments[tuple(p0)]
490            if len(candidate_list) > 1:
491                # Multiple points detected
492                # Take the candidate that is furthest to the clockwise direction,
493                # as that will follow the boundary.
494
495
496                if verbose:
497                    print 'Point %s has multiple candidates: %s' %(str(p0), candidate_list)
498
499
500                # Choose vector against which all angles will be measured
501                if len(polygon) > 1:   
502                    v_prev = p0 - polygon[-2] # Vector that leads to p0
503                else:
504                    # FIXME (Ole): What do we do if the first point has multiple candidates?
505                    # Being the lower left corner, perhaps we can use the
506                    # vector [1, 0], but I really don't know if this is completely
507                    # watertight.
508                    # Another option might be v_prev = [1.0, 0.0]
509                    v_prev = [1.0, 0.0]
510                   
511
512                   
513                # Choose candidate with minimum angle   
514                minimum_angle = 2*pi
515                for pc in candidate_list:
516                    vc = pc-p0  # Candidate vector
517                   
518                    # Angle between each candidate and the previous vector in [-pi, pi]
519                    ac = angle(vc, v_prev)
520                    if ac > pi: ac = pi-ac
521
522                    # take the minimal angle corresponding to the rightmost vector
523                    if ac < minimum_angle:
524                        minimum_angle = ac
525                        p1 = pc             # Best candidate
526                       
527
528                if verbose is True:
529                    print '  Best candidate %s, angle %f' %(p1, minimum_angle*180/pi)
530               
531            else:
532                p1 = candidate_list[0]
533
534            if point_registry.has_key(tuple(p1)):
535                # We have completed the boundary polygon - yeehaa
536                break 
537            else:
538                point_registry[tuple(p1)] = len(point_registry)
539           
540            polygon.append(p1)
541            p0 = p1
542
543
544        return polygon
545
546
547    def check_integrity(self):
548        """Check that triangles are internally consistent e.g.
549        that area corresponds to edgelengths, that vertices
550        are arranged in a counter-clockwise order, etc etc
551        Neighbour structure will be checked by class Mesh
552        """
553
554        from config import epsilon
555        from utilities.numerical_tools import anglediff
556
557        N = self.number_of_elements
558        #Get x,y coordinates for all vertices for all triangles
559        V = self.get_vertex_coordinates()
560        #Check each triangle
561        for i in range(N):
562           
563            x0 = V[i, 0]; y0 = V[i, 1]
564            x1 = V[i, 2]; y1 = V[i, 3]
565            x2 = V[i, 4]; y2 = V[i, 5]
566
567            #Check that area hasn't been compromised
568            area = self.areas[i]
569            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
570            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
571                  %(x0,y0,x1,y1,x2,y2)
572            assert abs((area - ref)/area) < epsilon, msg
573
574            #Check that points are arranged in counter clock-wise order
575            v0 = [x1-x0, y1-y0]
576            v1 = [x2-x1, y2-y1]
577            v2 = [x0-x2, y0-y2]
578
579            a0 = anglediff(v1, v0)
580            a1 = anglediff(v2, v1)
581            a2 = anglediff(v0, v2)
582
583            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
584            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
585            assert a0 < pi and a1 < pi and a2 < pi, msg
586
587            #Check that normals are orthogonal to edge vectors
588            #Note that normal[k] lies opposite vertex k
589
590            normal0 = self.normals[i, 0:2]
591            normal1 = self.normals[i, 2:4]
592            normal2 = self.normals[i, 4:6]
593
594            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
595
596                #Normalise
597                l_u = sqrt(u[0]*u[0] + u[1]*u[1])
598                l_v = sqrt(v[0]*v[0] + v[1]*v[1])               
599
600                x = (u[0]*v[0] + u[1]*v[1])/l_u/l_v #Inner product
601               
602                msg = 'Normal vector (%f,%f) is not perpendicular to' %tuple(v)
603                msg += ' edge (%f,%f) in triangle %d.' %(tuple(u) + (i,))
604                msg += ' Inner product is %e.' %x               
605                assert x < epsilon, msg
606
607
608        #Check that all vertices have been registered
609        for v_id, v in enumerate(self.vertexlist):
610
611            msg = 'Vertex %s does not belong to an element.'
612            #assert v is not None, msg
613            if v is None:
614                print msg%v_id
615
616        #Check integrity of neighbour structure
617        for i in range(N):
618#            print i
619            for v in self.triangles[i, :]:
620                #Check that all vertices have been registered
621                assert self.vertexlist[v] is not None
622
623                #Check that this triangle is listed with at least one vertex
624                assert (i, 0) in self.vertexlist[v] or\
625                       (i, 1) in self.vertexlist[v] or\
626                       (i, 2) in self.vertexlist[v]
627
628
629
630            #Check neighbour structure
631            for k, neighbour_id in enumerate(self.neighbours[i,:]):
632
633                #Assert that my neighbour's neighbour is me
634                #Boundaries need not fulfill this
635                if neighbour_id >= 0:
636                    edge = self.neighbour_edges[i, k]
637                    msg = 'Triangle %d has neighbour %d but it does not point back. \n' %(i,neighbour_id)
638                    msg += 'Only points to (%s)' %(self.neighbours[neighbour_id,:])
639                    assert self.neighbours[neighbour_id, edge] == i ,msg
640
641
642
643        #Check that all boundaries have
644        # unique, consecutive, negative indices
645
646        #L = len(self.boundary)
647        #for i in range(L):
648        #    id, edge = self.boundary_segments[i]
649        #    assert self.neighbours[id, edge] == -i-1
650
651
652        #NOTE: This assert doesn't hold true if there are internal boundaries
653        #FIXME: Look into this further.
654        #FIXME (Ole): In pyvolution mark 3 this is OK again
655        #NOTE: No longer works because neighbour structure is modified by
656        #      domain set_boundary.
657        #for id, edge in self.boundary:
658        #    assert self.neighbours[id,edge] < 0
659        #
660        #NOTE (Ole): I reckon this was resolved late 2004?
661        #
662        #See domain.set_boundary
663
664
665    def get_centroid_coordinates(self):
666        """Return all centroid coordinates.
667        Return all centroid coordinates for all triangles as an Nx2 array
668        (ordered as x0, y0 for each triangle)
669        """
670        return self.centroid_coordinates
671
672
673
674
675    def statistics(self):
676        """Output statistics about mesh
677        """
678
679        from Numeric import arange
680        from utilities.numerical_tools import histogram
681
682        vertex_coordinates = self.vertex_coordinates
683        areas = self.areas
684        x = vertex_coordinates[:,0]
685        y = vertex_coordinates[:,1]
686
687
688        #Setup 10 bins for area histogram
689        m = max(areas)
690        bins = arange(0., m, m/10)
691        hist = histogram(areas, bins)
692
693        str =  '------------------------------------------------\n'
694        str += 'Mesh statistics:\n'
695        str += '  Number of triangles = %d\n' %self.number_of_elements
696        str += '  Extent:\n'
697        str += '    x in [%f, %f]\n' %(min(x), max(x))
698        str += '    y in [%f, %f]\n' %(min(y), max(y))
699        str += '  Areas:\n'
700        str += '    A in [%f, %f]\n' %(min(areas), max(areas))
701        str += '    number of distinct areas: %d\n' %(len(areas))       
702        str += '    Histogram:\n'
703
704        hi = bins[0]
705        for i, count in enumerate(hist):
706            lo = hi
707            if i+1 < len(bins):
708                #Open upper interval               
709                hi = bins[i+1]
710                str += '      [%f, %f[: %d\n' %(lo, hi, count)               
711            else:
712                #Closed upper interval
713                hi = m
714                str += '      [%f, %f]: %d\n' %(lo, hi, count)
715
716        N = len(areas)
717        if N > 10:
718            str += '    Percentiles (10%):\n'
719            areas = areas.tolist()
720            areas.sort()
721
722            k = 0
723            lower = min(areas)
724            for i, a in enumerate(areas):       
725                if i % (N/10) == 0 and i != 0: #For every 10% of the sorted areas               
726                    str += '      %d triangles in [%f, %f]\n' %(i-k, lower, a)
727                    lower = a
728                    k = i
729                   
730            str += '      %d triangles in [%f, %f]\n'\
731                   %(N-k, lower, max(areas))                   
732               
733                     
734        str += 'Boundary:\n'
735        str += '  Number of boundary segments == %d\n' %(len(self.boundary))
736        str += '  Boundary tags == %s\n' %self.get_boundary_tags() 
737        str += '------------------------------------------------\n'
738       
739
740        return str
741
Note: See TracBrowser for help on using the repository browser.