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

Last change on this file since 291 was 291, checked in by ole, 20 years ago

Cleaned up in get_vertex_values

File size: 19.0 KB
Line 
1"""Classes implementing general 2D geometrical mesh of triangles.
2
3   Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
4   Geoscience Australia, 2004   
5"""
6
7
8class Mesh:
9    """Collection of triangular elements (purely geometric)
10
11    A triangular element is defined in terms of three vertex ids,
12    ordered counter clock-wise,
13    each corresponding to a given coordinate set.
14    Vertices from different elements can point to the same
15    coordinate set.
16
17    Coordinate sets are implemented as an N x 2 Numeric array containing
18    x and y coordinates.
19   
20
21    To instantiate:
22       Mesh(points, vertices)
23
24    where
25
26      points is either a list of 2-tuples or an Mx2 Numeric array of
27      floats representing all x, y coordinates in the mesh.
28
29      vertices is either a list of 3-tuples or an Nx3 Numeric array of
30      integers representing indices of all vertices in the mesh.
31      Each vertex is identified by its index i in [0, M-1].
32
33
34    Example:
35        a = [0.0, 0.0]
36        b = [0.0, 2.0]
37        c = [2.0,0.0]
38        e = [2.0, 2.0]
39       
40        points = [a, b, c, e]
41        vertices = [ [1,0,2], [1,2,3] ]   #bac, bce
42        mesh = Mesh(points, vertices)
43   
44        #creates two triangles: bac and bce
45
46
47    Mesh takes the optional third argument boundary which is a
48    dictionary mapping from (element_id, edge_id) to boundary tag.
49    The default value is None which will assign the defualt_boundary_tag
50    as specified in config.py to all boundary edges.
51    """
52
53    def __init__(self, coordinates, vertices, boundary = None):
54        """
55        Build triangles from x,y coordinates (sequence of 2-tuples or
56        Mx2 Numeric array of floats) and vertices (sequence of 3-tuples
57        or Nx3 Numeric array of non-negative integers).
58        """
59
60        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
61
62        self.vertices = array(vertices).astype(Int)
63        self.coordinates = array(coordinates)
64
65        #Input checks
66        msg = 'Vertices must an Nx2 Numeric array or a sequence of 2-tuples'
67        assert len(self.vertices.shape) == 2, msg
68
69        msg = 'Coordinates must an Mx2 Numeric array or a sequence of 2-tuples'
70        assert len(self.coordinates.shape) == 2, msg       
71
72        msg = 'Vertex indices reference non-existing coordinate sets'
73        assert max(max(self.vertices)) <= self.coordinates.shape[0], msg
74
75
76        #Register number of elements (N)
77        self.number_of_elements = N = self.vertices.shape[0]
78
79        #Allocate space for geometric quantities
80        self.centroids = zeros((N, 2), Float)
81        #FIXME: Should be renamed to centroid_coordinates
82       
83        self.areas = zeros(N, Float)
84        self.radii = zeros(N, Float)
85        self.edgelengths = zeros((N, 3), 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        self.normals = zeros((N, 6), Float)
93
94        #Get x,y coordinates for all vertices for all triangles
95        #and store
96        self.vertex_coordinates = V = self.compute_vertex_coordinates()
97       
98
99        ##print 'Initialise mesh'
100        #Initialise each triangle
101        for i in range(N):
102            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
103           
104            x0 = V[i, 0]; y0 = V[i, 1]
105            x1 = V[i, 2]; y1 = V[i, 3]
106            x2 = V[i, 4]; y2 = V[i, 5]           
107
108            #Compute centroid
109            centroid = array([(x0 + x1 + x2)/3, (y0 + y1 + y2)/3])
110            self.centroids[i] = centroid
111
112            #Area
113            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
114
115            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
116            msg += ' is degenerate:  area == %f' %self.areas[i]
117            assert self.areas[i] > 0.0, msg
118       
119            #Midpoints       
120            m0 = array([(x1 + x2)/2, (y1 + y2)/2])
121            m1 = array([(x0 + x2)/2, (y0 + y2)/2])
122            m2 = array([(x1 + x0)/2, (y1 + y0)/2])
123
124            #The radius is the distance from the centroid of
125            #a triangle to the midpoint of the side of the triangle
126            #closest to the centroid
127            d0 = sqrt(sum( (centroid-m0)**2 )) 
128            d1 = sqrt(sum( (centroid-m1)**2 ))       
129            d2 = sqrt(sum( (centroid-m2)**2 ))
130       
131            self.radii[i] = min(d0, d1, d2)
132
133            #Normals
134            #The normal vectors
135            # - point outward from each edge
136            # - are orthogonal to the edge
137            # - have unit length
138            # - Are enumerated according to the opposite corner:
139            #   (First normal is associated with the edge opposite
140            #    the first vertex, etc)
141            # - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
142
143            n0 = array([x2 - x1, y2 - y1])
144            l0 = sqrt(sum(n0**2))
145
146            n1 = array([x0 - x2, y0 - y2])
147            l1 = sqrt(sum(n1**2))
148
149            n2 = array([x1 - x0, y1 - y0])
150            l2 = sqrt(sum(n2**2))
151
152            #Normalise
153            n0 /= l0
154            n1 /= l1
155            n2 /= l2
156
157            #Compute and store
158            self.normals[i, :] = [n0[1], -n0[0],
159                                  n1[1], -n1[0],
160                                  n2[1], -n2[0]]             
161
162            #Edgelengths
163            self.edgelengths[i, :] = [l0, l1, l2]       
164
165            #Initialise Neighbours (-1 means that it is a boundary neighbour)
166            self.neighbours[i, :] = [-1, -1, -1]
167
168            #Initialise edge ids of neighbours
169            #In case of boundaries this slot is not used
170            self.neighbour_edges[i, :] = [-1, -1, -1]
171
172
173        #Build neighbour structure   
174        self.build_neighbour_structure()
175
176        #Build vertex list
177        self.build_vertexlist()       
178
179        #Build surrogate neighbour structure   
180        self.build_surrogate_neighbour_structure()       
181
182        #Build boundary dictionary mapping (id, edge) to symbolic tags       
183        self.build_boundary_dictionary(boundary)
184
185        #Update boundary indices
186        self.build_boundary_structure()       
187
188       
189    def __len__(self):
190        return self.number_of_elements
191
192    def __repr__(self):
193        return 'Mesh: %d vertices, %d elements, %d boundary segments'\
194               %(self.coordinates.shape[0], len(self), len(self.boundary))
195
196           
197    def build_neighbour_structure(self):
198        """Update all registered triangles to point to their neighbours.
199       
200        Also, keep a tally of the number of boundaries for each triangle
201
202        Postconditions:
203          neighbours and neighbour_edges is populated
204          number_of_boundaries integer array is defined.
205        """
206       
207        #Step 1:
208        #Build dictionary mapping from segments (2-tuple of points)
209        #to left hand side edge (facing neighbouring triangle)
210
211        N = self.number_of_elements       
212        neighbourdict = {}
213        for i in range(N):
214
215            #Register all segments as keys mapping to current triangle
216            #and segment id
217            a = self.vertices[i, 0]
218            b = self.vertices[i, 1]
219            c = self.vertices[i, 2]           
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.vertices[i, 0]
230            b = self.vertices[i, 1]
231            c = self.vertices[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   
251    def build_vertexlist(self):
252        """Build vertexlist index by vertex ids and for each entry (point id)
253        build a list of (triangles, vertex_id) pairs that use the point
254        as vertex.
255
256        Preconditions:
257          self.coordinates and self.vertices are defined 
258       
259        Postcondition:
260          self.vertexlist is build
261        """
262
263        vertexlist = [None]*len(self.coordinates)
264        for i in range(self.number_of_elements):
265
266            a = self.vertices[i, 0]
267            b = self.vertices[i, 1]
268            c = self.vertices[i, 2]           
269
270            #Register the vertices v as lists of
271            #(triangle_id, vertex_id) tuples associated with them
272            #This is used for smoothing
273            for vertex_id, v in enumerate([a,b,c]):
274                if vertexlist[v] is None:
275                    vertexlist[v] = []
276
277                vertexlist[v].append( (i, vertex_id) )   
278
279        self.vertexlist = vertexlist
280   
281
282    def build_surrogate_neighbour_structure(self):
283        """Build structure where each triangle edge points to its neighbours
284        if they exist. Otherwise point to the triangle itself.
285
286        The surrogate neighbour structure is useful for computing gradients
287        based on centroid values of neighbours.
288
289        Precondition: Neighbour structure is defined
290        Postcondition:
291          Surrogate neighbour structure is defined:
292          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
293          triangles.
294
295        """
296
297        N = self.number_of_elements
298        for i in range(N):
299            #Find all neighbouring volumes that are not boundaries
300            for k in range(3):
301                if self.neighbours[i, k] < 0:
302                    self.surrogate_neighbours[i, k] = i #Point this triangle
303                else:
304                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
305
306
307
308    def build_boundary_dictionary(self, boundary = None):
309        """Build or input the dictionary of boundary tags.
310         self.boundary is a dictionary of tags,
311         keyed by volume id and edge:
312         { (id, edge): tag, ... }
313         
314         Postconditions:
315            self.boundary is defined.
316        """
317
318        from config import default_boundary_tag
319       
320        if boundary is None:
321            boundary = {}
322            for vol_id in range(self.number_of_elements):
323                for edge_id in range(0, 3):
324                    if self.neighbours[vol_id, edge_id] < 0:
325                        boundary[(vol_id, edge_id)] = default_boundary_tag
326        else:
327            #Check that all keys in given boundary exist
328            for vol_id, edge_id in boundary.keys():
329                msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
330                a, b = self.neighbours.shape
331                assert vol_id < a and edge_id < b, msg
332               
333                msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
334                assert self.neighbours[vol_id, edge_id] < 0, msg
335
336            #Check that all boundary segments are assigned a value
337            for vol_id in range(self.number_of_elements):
338                for edge_id in range(0, 3):
339                    if self.neighbours[vol_id, edge_id] < 0:
340                        if not boundary.has_key( (vol_id, edge_id) ):
341                            msg = 'WARNING: Given boundary does not contain '
342                            msg += 'tags for edge (%d, %d). '\
343                                   %(vol_id, edge_id)
344                            msg += 'Assigning default tag (%s).'\
345                                   %default_boundary_tag
346
347                            #FIXME: Print only as per verbosity
348                            #print msg
349                            boundary[ (vol_id, edge_id) ] =\
350                                      default_boundary_tag
351           
352
353           
354        self.boundary = boundary
355           
356
357    def build_boundary_structure(self):
358        """Traverse boundary and
359        enumerate neighbour indices from -1 and
360        counting down.
361
362        Precondition:
363            self.boundary is defined.
364        Post condition:
365            neighbour array has unique negative indices for boundary
366            boundary_segments array imposes an ordering on segments
367            (not otherwise available from the dictionary)             
368        """
369
370        if self.boundary is None:
371            msg = 'Boundary dictionary must be defined before '
372            msg += 'building boundary structure'
373            raise msg
374
375       
376        self.boundary_segments = self.boundary.keys()
377        self.boundary_segments.sort()
378
379        index = -1       
380        for id, edge in self.boundary_segments:
381            self.neighbours[id, edge] = index
382            index -= 1
383
384   
385    def get_boundary_tags(self):
386        """Return list of available boundary tags
387        """
388       
389        tags = {}
390        for v in self.boundary.values():
391            tags[v] = 1
392
393        return tags.keys()   
394           
395   
396
397    def check_integrity(self):
398        """Check that triangles are internally consistent e.g.
399        that area corresponds to edgelengths, that vertices
400        are arranged in a counter-clockwise order, etc etc
401        Neighbour structure will be checked by class Mesh
402        """
403       
404        from config import epsilon
405        from math import pi
406        from util import anglediff
407
408        N = self.number_of_elements
409       
410        #Get x,y coordinates for all vertices for all triangles
411        V = self.get_vertex_coordinates()
412
413        #Check each triangle
414        for i in range(N):
415            x0 = V[i, 0]; y0 = V[i, 1]
416            x1 = V[i, 2]; y1 = V[i, 3]
417            x2 = V[i, 4]; y2 = V[i, 5]           
418
419            #Check that area hasn't been compromised
420            area = self.areas[i]
421            ref = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
422            msg = 'Wrong area for vertex coordinates: %f %f %f %f %f %f'\
423                  %(x0,y0,x1,y1,x2,y2)
424            assert abs((area - ref)/area) < epsilon, msg
425       
426            #Check that points are arranged in counter clock-wise order
427            v0 = [x1-x0, y1-y0]
428            v1 = [x2-x1, y2-y1]
429            v2 = [x0-x2, y0-y2]
430
431            a0 = anglediff(v1, v0)
432            a1 = anglediff(v2, v1)
433            a2 = anglediff(v0, v2)       
434
435            msg = '''Vertices (%s,%s), (%s,%s), (%s,%s) are not arranged
436            in counter clockwise order''' %(x0, y0, x1, y1, x2, y2)
437            assert a0 < pi and a1 < pi and a2 < pi, msg
438
439            #Check that normals are orthogonal to edge vectors
440            #Note that normal[k] lies opposite vertex k
441
442            normal0 = self.normals[i, 0:2]
443            normal1 = self.normals[i, 2:4]
444            normal2 = self.normals[i, 4:6]           
445
446            for u, v in [ (v0, normal2), (v1, normal0), (v2, normal1) ]:
447                assert u[0]*v[0] + u[1]*v[1] < epsilon
448
449
450        #Check integrity of neighbour structure
451        for i in range(N):
452            for v in self.vertices[i, :]:
453                #Check that all vertices have been registered
454                assert self.vertexlist[v] is not None
455
456                #Check that this triangle is listed with at least one vertex
457                assert (i, 0) in self.vertexlist[v] or\
458                       (i, 1) in self.vertexlist[v] or\
459                       (i, 2) in self.vertexlist[v]
460               
461               
462
463            #Check neighbour structure
464            for k, neighbour_id in enumerate(self.neighbours[i,:]):
465
466                #Assert that my neighbour's neighbour is me
467                #Boundaries need not fulfill this
468                if neighbour_id >= 0:
469                    edge = self.neighbour_edges[i, k]
470                    assert self.neighbours[neighbour_id, edge] == i
471
472
473
474        #Check that all boundaries have
475        # unique, consecutive, negative indices                   
476        L = len(self.boundary)
477        for i in range(L):
478            id, edge = self.boundary_segments[i]
479            assert self.neighbours[id, edge] == -i-1
480
481
482        #NOTE: This assert doesn't hold true if there are internal boundaries
483        #FIXME: Look into this further.
484        #for id, edge in self.boundary:
485        #    assert self.neighbours[id,edge] < 0
486       
487
488   
489
490    def get_normals(self):
491        """Return all normal vectors.
492        Return normal vectors for all triangles as an Nx6 array
493        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)       
494        """
495        return self.normals
496
497
498    def get_normal(self, i, j):
499        """Return normal vector j of the i'th triangle.
500        Return value is the numeric array slice [x, y]
501        """
502        return self.normals[i, 2*j:2*j+2]     
503   
504
505           
506    def get_vertex_coordinates(self):
507        """Return all vertex coordinates.
508        Return all vertex coordinates for all triangles as an Nx6 array
509        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)       
510        """
511        return self.vertex_coordinates
512
513
514    def get_vertex_coordinate(self, i, j):
515        """Return coordinates for vertex j of the i'th triangle.
516        Return value is the numeric array slice [x, y]
517        """
518        return self.vertex_coordinates[i, 2*j:2*j+2]     
519
520
521    def compute_vertex_coordinates(self):
522        """Return vertex coordinates for all triangles as an Nx6 array
523        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
524        """
525
526        #FIXME: Perhaps they should be ordered as in obj files??
527        #See quantity.get_vertex_values
528       
529        from Numeric import zeros, Float
530
531        N = self.number_of_elements
532        vertex_coordinates = zeros((N, 6), Float) 
533
534        for i in range(N):
535            for j in range(3):
536                k = self.vertices[i,j]  #Index of vertex 0
537                v_k = self.coordinates[k]
538                vertex_coordinates[i, 2*j+0] = v_k[0]
539                vertex_coordinates[i, 2*j+1] = v_k[1]
540
541        return vertex_coordinates
542 
543    def get_vertices(self, unique=False):
544        """Get connectivity
545        If unique is True give them only once as stored internally.
546        If unique is False return
547        """
548
549        if unique is True:
550            return self.vertices
551        else:
552            from Numeric import reshape, array, Int
553               
554            m = len(self)  #Number of volumes
555            M = 3*m        #Total number of unique vertices
556            return reshape(array(range(M)).astype(Int), (m,3))
557
558#FIXME: May get rid of
559def rectangular_mesh(m, n, len1=1.0, len2=1.0, origin = (0.0, 0.0)):
560    from mesh_factory import rectangular
561    points, vertices, boundary = rectangular(m, n, len1, len2, origin)
562
563    return Mesh(points, vertices, boundary)
564
565
566#FIXME: Get oblique and contracting and circular meshes from Chris
567
Note: See TracBrowser for help on using the repository browser.