source: anuga_core/source/anuga/abstract_2d_finite_volumes/general_mesh.py @ 4808

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

bug fix

File size: 19.7 KB
Line 
1
2from Numeric import concatenate, reshape, take, allclose
3from Numeric import array, zeros, Int, Float, sqrt, sum, arange
4
5from anuga.coordinate_transforms.geo_reference import Geo_reference
6
7class General_mesh:
8    """Collection of 2D triangular elements
9
10    A triangular element is defined in terms of three vertex ids,
11    ordered counter clock-wise, each corresponding to a given node
12    which is represented as a coordinate set (x,y).
13    Vertices from different triangles can point to the same node.
14    The nodes are implemented as an Nx2 Numeric array containing the
15    x and y coordinates.
16
17
18    To instantiate:
19       Mesh(nodes, triangles)
20
21    where
22
23      nodes is either a list of 2-tuples or an Nx2 Numeric array of
24      floats representing all x, y coordinates in the mesh.
25
26      triangles is either a list of 3-tuples or an Mx3 Numeric array of
27      integers representing indices of all vertices in the mesh.
28      Each vertex is identified by its index i in [0, N-1].
29
30
31    Example:
32
33        a = [0.0, 0.0]
34        b = [0.0, 2.0]
35        c = [2.0,0.0]
36        e = [2.0, 2.0]
37
38        nodes = [a, b, c, e]
39        triangles = [ [1,0,2], [1,2,3] ]   # bac, bce
40
41        # Create mesh with two triangles: bac and bce   
42        mesh = Mesh(nodes, triangles)
43
44
45
46    Other:
47
48      In addition mesh computes an Mx6 array called vertex_coordinates.
49      This structure is derived from coordinates and contains for each
50      triangle the three x,y coordinates at the vertices.
51
52      See neighbourmesh.py for a specialisation of the general mesh class
53      which includes information about neighbours and the mesh boundary.
54
55      The mesh object is purely geometrical and contains no information
56      about quantities defined on the mesh.
57
58    """
59
60    #FIXME: It would be a good idea to use geospatial data as an alternative
61    #input
62    def __init__(self, nodes, triangles,
63                 geo_reference=None,                 
64                 number_of_full_nodes=None,
65                 number_of_full_triangles=None,                 
66                 verbose=False):
67        """Build triangular 2d mesh from nodes and triangle information
68
69        Input:
70       
71          nodes: x,y coordinates represented as a sequence of 2-tuples or
72                 a Nx2 Numeric array of floats.
73                 
74          triangles: sequence of 3-tuples or Mx3 Numeric array of
75                     non-negative integers representing indices into
76                     the nodes array.
77       
78          georeference (optional): If specified coordinates are
79          assumed to be relative to this origin.
80
81
82        number_of_full_nodes and number_of_full_triangles relate to
83        parallelism when each mesh has an extra layer of ghost points and
84        ghost triangles attached to the end of the two arrays.
85        In this case it is usefull to specify the number of real (called full)
86        nodes and triangles. If omitted they will default to all.
87         
88        """
89
90        if verbose: print 'General_mesh: Building basic mesh structure' 
91
92        self.triangles = array(triangles, Int)
93        self.nodes = array(nodes, Float)
94
95
96        # Register number of elements and nodes
97        self.number_of_triangles = N = self.triangles.shape[0]
98        self.number_of_nodes = self.nodes.shape[0]       
99
100       
101
102        if number_of_full_nodes is None:
103            self.number_of_full_nodes = self.number_of_nodes
104        else:
105            assert int(number_of_full_nodes)
106            self.number_of_full_nodes = number_of_full_nodes           
107
108
109        if number_of_full_triangles is None:
110            self.number_of_full_triangles = self.number_of_triangles           
111        else:
112            assert int(number_of_full_triangles)           
113            self.number_of_full_triangles = number_of_full_triangles
114       
115       
116        #print self.number_of_full_nodes, self.number_of_nodes
117        #print self.number_of_full_triangles, self.number_of_triangles
118       
119           
120
121        # FIXME: this stores a geo_reference, but when coords are returned
122        # This geo_ref is not taken into account!
123        if geo_reference is None:
124            self.geo_reference = Geo_reference() #Use defaults
125        else:
126            self.geo_reference = geo_reference
127
128        # Input checks
129        msg = 'Triangles must an Mx3 Numeric array or a sequence of 3-tuples. '
130        msg += 'The supplied array has the shape: %s'\
131               %str(self.triangles.shape)
132        assert len(self.triangles.shape) == 2, msg
133
134        msg = 'Nodes must an Nx2 Numeric array or a sequence of 2-tuples'
135        msg += 'The supplied array has the shape: %s'\
136               %str(self.nodes.shape)
137        assert len(self.nodes.shape) == 2, msg
138
139        msg = 'Vertex indices reference non-existing coordinate sets'
140        assert max(max(self.triangles)) <= self.nodes.shape[0], msg
141
142
143        # FIXME: Maybe move to statistics?
144        # Or use with get_extent
145        xy_extent = [ min(self.nodes[:,0]), min(self.nodes[:,1]) ,
146                      max(self.nodes[:,0]), max(self.nodes[:,1]) ]
147
148        self.xy_extent = array(xy_extent, Float)
149
150
151        # Allocate space for geometric quantities
152        self.normals = zeros((N, 6), Float)
153        self.areas = zeros(N, Float)
154        self.edgelengths = zeros((N, 3), Float)
155
156        # Get x,y coordinates for all triangles and store
157        self.vertex_coordinates = V = self.compute_vertex_coordinates()
158
159
160        # Initialise each triangle
161        if verbose:
162            print 'General_mesh: Computing areas, normals and edgelenghts'
163           
164        for i in range(N):
165            if verbose and i % ((N+10)/10) == 0: print '(%d/%d)' %(i, N)
166           
167            x0, y0 = V[3*i, :]
168            x1, y1 = V[3*i+1, :]
169            x2, y2 = V[3*i+2, :]           
170
171            # Area
172            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
173
174            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
175            msg += ' is degenerate:  area == %f' %self.areas[i]
176            assert self.areas[i] > 0.0, msg
177
178
179            # Normals
180            # The normal vectors
181            #   - point outward from each edge
182            #   - are orthogonal to the edge
183            #   - have unit length
184            #   - Are enumerated according to the opposite corner:
185            #     (First normal is associated with the edge opposite
186            #     the first vertex, etc)
187            #   - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
188
189            n0 = array([x2 - x1, y2 - y1])
190            l0 = sqrt(sum(n0**2))
191
192            n1 = array([x0 - x2, y0 - y2])
193            l1 = sqrt(sum(n1**2))
194
195            n2 = array([x1 - x0, y1 - y0])
196            l2 = sqrt(sum(n2**2))
197
198            # Normalise
199            n0 /= l0
200            n1 /= l1
201            n2 /= l2
202
203            # Compute and store
204            self.normals[i, :] = [n0[1], -n0[0],
205                                  n1[1], -n1[0],
206                                  n2[1], -n2[0]]
207
208            # Edgelengths
209            self.edgelengths[i, :] = [l0, l1, l2]
210
211       
212        # Build structure listing which trianglse belong to which nodet.
213        if verbose: print 'Building inverted triangle structure'         
214        self.build_inverted_triangle_structure()
215
216           
217
218    def __len__(self):
219        return self.number_of_triangles
220   
221
222    def __repr__(self):
223        return 'Mesh: %d vertices, %d triangles'\
224               %(self.nodes.shape[0], len(self))
225
226    def get_normals(self):
227        """Return all normal vectors.
228        Return normal vectors for all triangles as an Nx6 array
229        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
230        """
231        return self.normals
232
233
234    def get_normal(self, i, j):
235        """Return normal vector j of the i'th triangle.
236        Return value is the numeric array slice [x, y]
237        """
238        return self.normals[i, 2*j:2*j+2]
239
240
241    def get_nodes(self, absolute=False):
242        """Return all nodes in mesh.
243
244        The nodes are ordered in an Nx2 array where N is the number of nodes.
245        This is the same format they were provided in the constructor
246        i.e. without any duplication.
247
248        Boolean keyword argument absolute determines whether coordinates
249        are to be made absolute by taking georeference into account
250        Default is False as many parts of ANUGA expects relative coordinates.
251        (To see which, switch to default absolute=True and run tests).       
252        """
253
254        N = self.number_of_full_nodes
255        V = self.nodes[:N,:]
256        if absolute is True:
257            if not self.geo_reference.is_absolute():
258                V = self.geo_reference.get_absolute(V)
259               
260        return V
261   
262    def get_node(self, i,
263                 absolute=False):
264        """Return node coordinates.
265
266        The nodes are ordered in an Nx2 array where N is the number of nodes.
267        This is the same format they were provided in the constructor
268        i.e. without any duplication.
269
270        Boolean keyword argument absolute determines whether coordinates
271        are to be made absolute by taking georeference into account
272        Default is False as many parts of ANUGA expects relative coordinates.
273        (To see which, switch to default absolute=True and run tests).       
274        """
275
276       
277        V = self.nodes[i,:]
278        if absolute is True:
279            if not self.geo_reference.is_absolute():
280                return V + array([self.geo_reference.get_xllcorner(),
281                                  self.geo_reference.get_yllcorner()])
282            else:
283                return V
284        else:
285            return V
286   
287       
288
289    def get_vertex_coordinates(self,
290                               triangle_id=None,
291                               absolute=False):
292        """Return vertex coordinates for all triangles.
293       
294        Return all vertex coordinates for all triangles as a 3*M x 2 array
295        where the jth vertex of the ith triangle is located in row 3*i+j and
296        M the number of triangles in the mesh.
297
298        if triangle_id is specified (an integer) the 3 vertex coordinates
299        for triangle_id are returned.
300       
301        Boolean keyword argument absolute determines whether coordinates
302        are to be made absolute by taking georeference into account
303        Default is False as many parts of ANUGA expects relative coordinates.
304        """
305       
306        V = self.vertex_coordinates
307        if absolute is True:
308            if not self.geo_reference.is_absolute():
309                V = self.geo_reference.get_absolute(V)
310
311        if triangle_id is None:       
312            return V
313        else:
314            i = triangle_id
315            msg = 'triangle_id must be an integer'
316            assert int(i) == i, msg
317            assert 0 <= i < self.number_of_triangles
318           
319            i3 = 3*i
320            return array([V[i3,:], V[i3+1,:], V[i3+2,:]])
321
322
323
324    def get_vertex_coordinate(self, i, j, absolute=False):
325        """Return coordinates for vertex j of the i'th triangle.
326        Return value is the numeric array slice [x, y]
327        """
328
329        msg = 'vertex id j must be an integer in [0,1,2]'
330        assert j in [0,1,2], msg
331       
332        V = self.get_vertex_coordinates(triangle_id=i,
333                                        absolute=absolute)
334        return V[j,:]
335   
336
337
338    def compute_vertex_coordinates(self):
339        """Return all vertex coordinates for all triangles as a 3*M x 2 array
340        where the jth vertex of the ith triangle is located in row 3*i+j.
341
342        This function is used to precompute this important structure. Use
343        get_vertex coordinates to retrieve the points.
344        """
345
346        M = self.number_of_triangles
347        vertex_coordinates = zeros((3*M, 2), Float)
348
349        for i in range(M):
350            for j in range(3):
351                k = self.triangles[i,j] # Index of vertex j in triangle i
352                vertex_coordinates[3*i+j,:] = self.nodes[k]
353
354        return vertex_coordinates
355
356
357
358    def get_triangles(self, indices=None):
359        """Get mesh triangles.
360
361        Return Mx3 integer array where M is the number of triangles.
362        Each row corresponds to one triangle and the three entries are
363        indices into the mesh nodes which can be obtained using the method
364        get_nodes()
365
366        Optional argument, indices is the set of triangle ids of interest.
367        """
368
369        M = self.number_of_full_triangles
370
371        if indices is None:
372            indices = range(M)
373
374        return take(self.triangles, indices)
375   
376
377
378    def get_disconnected_triangles(self):
379        """Get mesh based on nodes obtained from get_vertex_coordinates.
380
381        Return array Mx3 array of integers where each row corresponds to
382        a triangle. A triangle is a triplet of indices into
383        point coordinates obtained from get_vertex_coordinates and each
384        index appears only once
385
386        This provides a mesh where no triangles share nodes
387        (hence the name disconnected triangles) and different
388        nodes may have the same coordinates.
389
390        This version of the mesh is useful for storing meshes with
391        discontinuities at each node and is e.g. used for storing
392        data in sww files.
393
394        The triangles created will have the format
395
396        [[0,1,2],
397         [3,4,5],
398         [6,7,8],
399         ...
400         [3*M-3 3*M-2 3*M-1]]         
401        """
402
403        M = len(self) # Number of triangles
404        K = 3*M       # Total number of unique vertices
405       
406        #T = reshape(array(range(K)).astype(Int), (M,3))
407        T = reshape(arange(K).astype(Int), (M,3))  # Faster
408       
409        return T     
410
411   
412
413    def get_unique_vertices(self,  indices=None):
414        """FIXME(Ole): This function needs a docstring
415        """
416        triangles = self.get_triangles(indices=indices)
417        unique_verts = {}
418        for triangle in triangles:
419           unique_verts[triangle[0]] = 0
420           unique_verts[triangle[1]] = 0
421           unique_verts[triangle[2]] = 0
422        return unique_verts.keys()
423
424
425    def get_triangles_and_vertices_per_node(self, node=None):
426        """Get triangles associated with given node.
427
428        Return list of triangle_ids, vertex_ids for specified node.
429        If node in None or absent, this information will be returned
430        for all (full) nodes in a list L where L[v] is the triangle
431        list for node v.
432        """
433
434        triangle_list = []
435        if node is not None:
436            # Get index for this node
437            first = sum(self.number_of_triangles_per_node[:node])
438           
439            # Get number of triangles for this node
440            count = self.number_of_triangles_per_node[node]
441
442            for i in range(count):
443                index = self.vertex_value_indices[first+i]
444
445                volume_id = index / 3
446                vertex_id = index % 3
447
448                triangle_list.append( (volume_id, vertex_id) )
449
450            triangle_list = array(triangle_list)   
451        else:
452            # Get info for all nodes recursively.
453            # If need be, we can speed this up by
454            # working directly with the inverted triangle structure
455            for i in range(self.number_of_full_nodes):
456               
457                L = self.get_triangles_and_vertices_per_node(node=i)
458
459                triangle_list.append(L)
460
461        return triangle_list
462       
463
464    def build_inverted_triangle_structure(self):
465        """Build structure listing triangles belonging to each node
466
467        Two arrays are created and store as mesh attributes
468
469        number_of_triangles_per_node: An integer array of length N
470        listing for each node how many triangles use it. N is the number of
471        nodes in mesh.
472       
473        vertex_value_indices: An array of length M listing indices into
474        triangles ordered by node number. The (triangle_id, vertex_id)
475        pairs are obtained from each index as (index/3, index%3) or each
476        index can be used directly into a flattened triangles array. This
477        is for example the case in the quantity.c where this structure is
478        used to average vertex values efficiently.
479
480       
481        Example:
482       
483        a = [0.0, 0.0] # node 0
484        b = [0.0, 2.0] # node 1
485        c = [2.0, 0.0] # node 2
486        d = [0.0, 4.0] # node 3
487        e = [2.0, 2.0] # node 4
488        f = [4.0, 0.0] # node 5
489
490        nodes = array([a, b, c, d, e, f])
491       
492        #bac, bce, ecf, dbe, daf, dae
493        triangles = array([[1,0,2], [1,2,4], [4,2,5], [3,1,4]])       
494
495
496        For this structure
497
498        number_of_triangles_per_node = [1 3 3 1 3 1]
499        which means that node a has 1 triangle associated with it, node b
500        has 3, node has 3 and so on.
501       
502        vertex_value_indices = [ 1  0  3 10  2  4  7  9  5  6 11  8]
503        which reflects the fact that
504        node 0 is used by triangle 0, vertex 1 (index = 1)
505        node 1 is used by triangle 0, vertex 0 (index = 0)
506                   and by triangle 1, vertex 0 (index = 3)
507                   and by triangle 3, vertex 1 (index = 10)
508        node 2 is used by triangle 0, vertex 2 (index = 2)
509                   and by triangle 1, vertex 1 (index = 4)
510                   and by triangle 2, vertex 1 (index = 7)
511        node 3 is used by triangle 3, vertex 0 (index = 9)
512        node 4 is used by triangle 1, vertex 2 (index = 5)
513                   and by triangle 2, vertex 0 (index = 6)
514                   and by triangle 3, vertex 2 (index = 11)
515        node 5 is used by triangle 2, vertex 2 (index = 8)                   
516       
517
518        Preconditions:
519          self.nodes and self.triangles are defined
520
521        Postcondition:
522          self.number_of_triangles_per_node is built
523          self.vertex_value_indices is built         
524        """
525
526        # Count number of triangles per node
527        number_of_triangles_per_node = zeros(self.number_of_full_nodes)
528        for volume_id, triangle in enumerate(self.get_triangles()):
529            for vertex_id in triangle:
530                number_of_triangles_per_node[vertex_id] += 1
531
532        # Allocate space for inverted structure
533        number_of_entries = sum(number_of_triangles_per_node)
534        vertex_value_indices = zeros(number_of_entries)
535
536        # Register (triangle, vertex) indices for each node
537        vertexlist = [None]*self.number_of_full_nodes
538        for volume_id in range(self.number_of_full_triangles):
539
540            a = self.triangles[volume_id, 0]
541            b = self.triangles[volume_id, 1]
542            c = self.triangles[volume_id, 2]
543
544            for vertex_id, node_id in enumerate([a,b,c]):
545                if vertexlist[node_id] is None:
546                    vertexlist[node_id] = []
547       
548                vertexlist[node_id].append( (volume_id, vertex_id) )
549
550
551        # Build inverted triangle index array
552        k = 0
553        for vertices in vertexlist:
554            if vertices is not None:
555                for volume_id, vertex_id in vertices:
556                    vertex_value_indices[k] = 3*volume_id + vertex_id
557                                       
558                    k += 1
559
560        # Save structure
561        self.number_of_triangles_per_node = number_of_triangles_per_node
562        self.vertex_value_indices = vertex_value_indices
563
564
565       
566
567    def get_extent(self, absolute=False):
568        """Return min and max of all x and y coordinates
569
570        Boolean keyword argument absolute determines whether coordinates
571        are to be made absolute by taking georeference into account
572        """
573       
574
575
576        C = self.get_vertex_coordinates(absolute=absolute)
577        X = C[:,0:6:2].copy()
578        Y = C[:,1:6:2].copy()
579
580        xmin = min(X.flat)
581        xmax = max(X.flat)
582        ymin = min(Y.flat)
583        ymax = max(Y.flat)
584        #print "C",C
585        return xmin, xmax, ymin, ymax
586
587    def get_areas(self):
588        """Get areas of all individual triangles.
589        """
590        return self.areas       
591
592    def get_area(self):
593        """Return total area of mesh
594        """
595
596        return sum(self.areas)
597
598       
Note: See TracBrowser for help on using the repository browser.