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

Last change on this file since 3689 was 3689, checked in by ole, 18 years ago

Added functionality for getting arbitrary interpolated values in Quantity as well as calculating inundation height and location. This work was done at SUT during the last week of September 2006.

File size: 11.7 KB
Line 
1
2from Numeric import concatenate, reshape, take, allclose
3from Numeric import array, zeros, Int, Float, sqrt, sum
4
5from anuga.coordinate_transforms.geo_reference import Geo_reference
6
7class General_mesh:
8    """Collection of triangular elements (purely geometric)
9
10    A triangular element is defined in terms of three vertex ids,
11    ordered counter clock-wise,
12    each corresponding to a given coordinate set.
13    Vertices from different elements can point to the same
14    coordinate set.
15
16    Coordinate sets are implemented as an N x 2 Numeric array containing
17    x and y coordinates.
18
19
20    To instantiate:
21       Mesh(coordinates, triangles)
22
23    where
24
25      coordinates is either a list of 2-tuples or an Mx2 Numeric array of
26      floats representing all x, y coordinates in the mesh.
27
28      triangles is either a list of 3-tuples or an Nx3 Numeric array of
29      integers representing indices of all vertices in the mesh.
30      Each vertex is identified by its index i in [0, M-1].
31
32
33    Example:
34        a = [0.0, 0.0]
35        b = [0.0, 2.0]
36        c = [2.0,0.0]
37        e = [2.0, 2.0]
38
39        points = [a, b, c, e]
40        triangles = [ [1,0,2], [1,2,3] ]   #bac, bce
41        mesh = Mesh(points, triangles)
42
43        #creates two triangles: bac and bce
44
45    Other:
46
47      In addition mesh computes an Nx6 array called vertex_coordinates.
48      This structure is derived from coordinates and contains for each
49      triangle the three x,y coordinates at the vertices.
50
51
52        This is a cut down version of mesh from mesh.py
53    """
54
55    #FIXME: It would be a good idea to use geospatial data as an alternative
56    #input
57    def __init__(self, coordinates, triangles,
58                 geo_reference=None,
59                 verbose=False):
60        """
61        Build triangles from x,y coordinates (sequence of 2-tuples or
62        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
63        or Nx3 Numeric array of non-negative integers).
64
65        origin is a 3-tuple consisting of UTM zone, easting and northing.
66        If specified coordinates are assumed to be relative to this origin.
67        """
68
69        if verbose: print 'General_mesh: Building basic mesh structure' 
70
71        self.triangles = array(triangles,Int)
72        self.coordinates = array(coordinates,Float)
73       
74
75        # FIXME: this stores a geo_reference, but when coords are returned
76        # This geo_ref is not taken into account!
77        if geo_reference is None:
78            self.geo_reference = Geo_reference() #Use defaults
79        else:
80            self.geo_reference = geo_reference
81
82        # Input checks
83        msg = 'Triangles must an Nx3 Numeric array or a sequence of 3-tuples. '
84        msg += 'The supplied array has the shape: %s'\
85               %str(self.triangles.shape)
86        assert len(self.triangles.shape) == 2, msg
87
88        msg = 'Coordinates must an Mx2 Numeric array or a sequence of 2-tuples'
89        msg += 'The supplied array has the shape: %s'\
90               %str(self.coordinates.shape)
91        assert len(self.coordinates.shape) == 2, msg
92
93        msg = 'Vertex indices reference non-existing coordinate sets'
94        assert max(max(self.triangles)) <= self.coordinates.shape[0], msg
95
96
97        # Register number of elements (N)
98        self.number_of_elements = N = self.triangles.shape[0]
99
100        # FIXME: Maybe move to statistics?
101        # Or use with get_extent
102        xy_extent = [ min(self.coordinates[:,0]), min(self.coordinates[:,1]) ,
103                      max(self.coordinates[:,0]), max(self.coordinates[:,1]) ]
104
105        self.xy_extent = array(xy_extent, Float)
106
107
108        # Allocate space for geometric quantities
109        self.normals = zeros((N, 6), Float)
110        self.areas = zeros(N, Float)
111        self.edgelengths = zeros((N, 3), Float)
112
113        # Get x,y coordinates for all triangles and store
114        self.vertex_coordinates = V = self.compute_vertex_coordinates()
115
116
117        # Initialise each triangle
118        if verbose:
119            print 'General_mesh: Computing areas, normals and edgelenghts'
120           
121        for i in range(N):
122            if verbose and i % ((N+10)/10) == 0: print '(%d/%d)' %(i, N)
123           
124
125            x0 = V[i, 0]; y0 = V[i, 1]
126            x1 = V[i, 2]; y1 = V[i, 3]
127            x2 = V[i, 4]; y2 = V[i, 5]
128
129            # Area
130            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
131
132            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
133            msg += ' is degenerate:  area == %f' %self.areas[i]
134            assert self.areas[i] > 0.0, msg
135
136
137            # Normals
138            # The normal vectors
139            #   - point outward from each edge
140            #   - are orthogonal to the edge
141            #   - have unit length
142            #   - Are enumerated according to the opposite corner:
143            #     (First normal is associated with the edge opposite
144            #     the first vertex, etc)
145            #   - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
146
147            n0 = array([x2 - x1, y2 - y1])
148            l0 = sqrt(sum(n0**2))
149
150            n1 = array([x0 - x2, y0 - y2])
151            l1 = sqrt(sum(n1**2))
152
153            n2 = array([x1 - x0, y1 - y0])
154            l2 = sqrt(sum(n2**2))
155
156            # Normalise
157            n0 /= l0
158            n1 /= l1
159            n2 /= l2
160
161            # Compute and store
162            self.normals[i, :] = [n0[1], -n0[0],
163                                  n1[1], -n1[0],
164                                  n2[1], -n2[0]]
165
166            # Edgelengths
167            self.edgelengths[i, :] = [l0, l1, l2]
168
169       
170        # Build vertex list
171        if verbose: print 'Building vertex list'         
172        self.build_vertexlist()
173
174           
175
176    def __len__(self):
177        return self.number_of_elements
178
179    def __repr__(self):
180        return 'Mesh: %d vertices, %d triangles'\
181               %(self.coordinates.shape[0], len(self))
182
183    def get_normals(self):
184        """Return all normal vectors.
185        Return normal vectors for all triangles as an Nx6 array
186        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
187        """
188        return self.normals
189
190
191    def get_normal(self, i, j):
192        """Return normal vector j of the i'th triangle.
193        Return value is the numeric array slice [x, y]
194        """
195        return self.normals[i, 2*j:2*j+2]
196
197
198
199    def get_vertex_coordinates(self, unique=False, obj=False, absolute=False):
200        """Return all vertex coordinates.
201        Return all vertex coordinates for all triangles as an Nx6 array
202        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
203
204        if obj is True, the x/y pairs are returned in a 3*N x 2 array.
205        FIXME, we might make that the default.
206        FIXME Maybe use keyword: continuous = False for this condition?
207        FIXME - Maybe use something referring to unique vertices?
208
209        Boolean keyword argument absolute determines whether coordinates
210        are to be made absolute by taking georeference into account
211        Default is False as many parts of ANUGA expects relative coordinates.
212        (To see which, switch to default absolute=True and run tests).
213        """
214
215        if unique is True:
216            V = self.coordinates
217            if absolute is True:
218                if not self.geo_reference.is_absolute():
219                    V = self.geo_reference.get_absolute(V)
220
221            return V
222
223               
224
225        V = self.vertex_coordinates
226        if absolute is True:
227            if not self.geo_reference.is_absolute():
228           
229                V0 = self.geo_reference.get_absolute(V[:,0:2])
230                V1 = self.geo_reference.get_absolute(V[:,2:4])
231                V2 = self.geo_reference.get_absolute(V[:,4:6])
232
233                # This does double the memory need
234                V = concatenate( (V0, V1, V2), axis=1 )
235
236               
237        if obj is True:
238            N = V.shape[0]
239            return reshape(V, (3*N, 2))
240        else:   
241            return V
242
243
244    def get_vertex_coordinate(self, i, j, absolute=False):
245        """Return coordinates for vertex j of the i'th triangle.
246        Return value is the numeric array slice [x, y]
247        """
248
249        V = self.get_vertex_coordinates(absolute=absolute)
250        return V[i, 2*j:2*j+2]
251   
252        ##return self.vertex_coordinates[i, 2*j:2*j+2]
253
254
255    def compute_vertex_coordinates(self):
256        """Return vertex coordinates for all triangles as an Nx6 array
257        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
258        """
259
260        #FIXME (Ole): Perhaps they should be ordered as in obj files??
261        #See quantity.get_vertex_values
262        #FIXME (Ole) - oh yes they should
263
264        N = self.number_of_elements
265        vertex_coordinates = zeros((N, 6), Float)
266
267        for i in range(N):
268            for j in range(3):
269                k = self.triangles[i,j]  #Index of vertex 0
270                v_k = self.coordinates[k]
271                vertex_coordinates[i, 2*j+0] = v_k[0]
272                vertex_coordinates[i, 2*j+1] = v_k[1]
273
274        return vertex_coordinates
275
276    def get_vertices(self, indices=None):
277        """Get connectivity
278        indices is the set of element ids of interest
279        """
280
281        if (indices ==  None):
282            indices = range(len(self))  #len(self)=number of elements
283
284        return  take(self.triangles, indices)
285
286    #FIXME - merge these two (get_vertices and get_triangles)
287    def get_triangles(self, obj=False):
288        """Get connetivity
289        Return triangles (triplets of indices into point coordinates)
290       
291        If obj is True return structure commensurate with replicated
292        points, allowing for discontinuities
293        (FIXME: Need good name for this concept)
294        """
295
296        if obj is True:
297            m = len(self)  #Number of triangles
298            M = 3*m        #Total number of unique vertices
299            T = reshape(array(range(M)).astype(Int), (m,3))
300        else:
301            T = self.triangles
302
303        return T     
304
305   
306
307    def get_unique_vertices(self,  indices=None):
308        triangles = self.get_vertices(indices=indices)
309        unique_verts = {}
310        for triangle in triangles:
311           unique_verts[triangle[0]] = 0
312           unique_verts[triangle[1]] = 0
313           unique_verts[triangle[2]] = 0
314        return unique_verts.keys()
315
316    def build_vertexlist(self):
317        """Build vertexlist index by vertex ids and for each entry (point id)
318        build a list of (triangles, vertex_id) pairs that use the point
319        as vertex.
320
321        Preconditions:
322          self.coordinates and self.triangles are defined
323
324        Postcondition:
325          self.vertexlist is built
326        """
327
328        vertexlist = [None]*len(self.coordinates)
329        for i in range(self.number_of_elements):
330
331            a = self.triangles[i, 0]
332            b = self.triangles[i, 1]
333            c = self.triangles[i, 2]
334
335            #Register the vertices v as lists of
336            #(triangle_id, vertex_id) tuples associated with them
337            #This is used for averaging multiple vertex values.
338            for vertex_id, v in enumerate([a,b,c]):
339                if vertexlist[v] is None:
340                    vertexlist[v] = []
341
342                vertexlist[v].append( (i, vertex_id) )
343
344        self.vertexlist = vertexlist
345
346
347    def get_extent(self, absolute=False):
348        """Return min and max of all x and y coordinates
349
350        Boolean keyword argument absolute determines whether coordinates
351        are to be made absolute by taking georeference into account
352        """
353       
354
355
356        C = self.get_vertex_coordinates(absolute=absolute)
357        X = C[:,0:6:2].copy()
358        Y = C[:,1:6:2].copy()
359
360        xmin = min(X.flat)
361        xmax = max(X.flat)
362        ymin = min(Y.flat)
363        ymax = max(Y.flat)
364
365        return xmin, xmax, ymin, ymax
366
367
368    def get_area(self):
369        """Return total area of mesh
370        """
371
372        return sum(self.areas)
373
374       
Note: See TracBrowser for help on using the repository browser.