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

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

Got flagstaff to work with parallel API

File size: 11.4 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 triangles, %d elements'\
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, 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        V = self.vertex_coordinates
216        if absolute is True:
217            if not self.geo_reference.is_absolute():
218           
219                V0 = self.geo_reference.get_absolute(V[:,0:2])
220                V1 = self.geo_reference.get_absolute(V[:,2:4])
221                V2 = self.geo_reference.get_absolute(V[:,4:6])
222
223                # This does double the memory need
224                V = concatenate( (V0, V1, V2), axis=1 )
225
226               
227        if obj is True:
228
229            N = V.shape[0]
230            return reshape(V, (3*N, 2))
231        else:   
232            return V
233
234
235    def get_vertex_coordinate(self, i, j, absolute=False):
236        """Return coordinates for vertex j of the i'th triangle.
237        Return value is the numeric array slice [x, y]
238        """
239
240        V = self.get_vertex_coordinates(absolute=absolute)
241        return V[i, 2*j:2*j+2]
242   
243        ##return self.vertex_coordinates[i, 2*j:2*j+2]
244
245
246    def compute_vertex_coordinates(self):
247        """Return vertex coordinates for all triangles as an Nx6 array
248        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
249        """
250
251        #FIXME (Ole): Perhaps they should be ordered as in obj files??
252        #See quantity.get_vertex_values
253        #FIXME (Ole) - oh yes they should
254
255        N = self.number_of_elements
256        vertex_coordinates = zeros((N, 6), Float)
257
258        for i in range(N):
259            for j in range(3):
260                k = self.triangles[i,j]  #Index of vertex 0
261                v_k = self.coordinates[k]
262                vertex_coordinates[i, 2*j+0] = v_k[0]
263                vertex_coordinates[i, 2*j+1] = v_k[1]
264
265        return vertex_coordinates
266
267    def get_vertices(self, indices=None):
268        """Get connectivity
269        indices is the set of element ids of interest
270        """
271
272        if (indices ==  None):
273            indices = range(len(self))  #len(self)=number of elements
274
275        return  take(self.triangles, indices)
276
277    #FIXME - merge these two (get_vertices and get_triangles)
278    def get_triangles(self, obj=False):
279        """Get connetivity
280        Return triangles (triplets of indices into point coordinates)
281       
282        If obj is True return structure commensurate with replicated
283        points, allowing for discontinuities
284        (FIXME: Need good name for this concept)
285        """
286
287        if obj is True:
288            m = len(self)  #Number of triangles
289            M = 3*m        #Total number of unique vertices
290            T = reshape(array(range(M)).astype(Int), (m,3))
291        else:
292            T = self.triangles
293
294        return T     
295
296   
297
298    def get_unique_vertices(self,  indices=None):
299        triangles = self.get_vertices(indices=indices)
300        unique_verts = {}
301        for triangle in triangles:
302           unique_verts[triangle[0]] = 0
303           unique_verts[triangle[1]] = 0
304           unique_verts[triangle[2]] = 0
305        return unique_verts.keys()
306
307    def build_vertexlist(self):
308        """Build vertexlist index by vertex ids and for each entry (point id)
309        build a list of (triangles, vertex_id) pairs that use the point
310        as vertex.
311
312        Preconditions:
313          self.coordinates and self.triangles are defined
314
315        Postcondition:
316          self.vertexlist is built
317        """
318
319        vertexlist = [None]*len(self.coordinates)
320        for i in range(self.number_of_elements):
321
322            a = self.triangles[i, 0]
323            b = self.triangles[i, 1]
324            c = self.triangles[i, 2]
325
326            #Register the vertices v as lists of
327            #(triangle_id, vertex_id) tuples associated with them
328            #This is used for averaging multiple vertex values.
329            for vertex_id, v in enumerate([a,b,c]):
330                if vertexlist[v] is None:
331                    vertexlist[v] = []
332
333                vertexlist[v].append( (i, vertex_id) )
334
335        self.vertexlist = vertexlist
336
337
338    def get_extent(self, absolute=False):
339        """Return min and max of all x and y coordinates
340
341        Boolean keyword argument absolute determines whether coordinates
342        are to be made absolute by taking georeference into account
343        """
344       
345
346
347        C = self.get_vertex_coordinates(absolute=absolute)
348        X = C[:,0:6:2].copy()
349        Y = C[:,1:6:2].copy()
350
351        xmin = min(X.flat)
352        xmax = max(X.flat)
353        ymin = min(Y.flat)
354        ymax = max(Y.flat)
355
356        return xmin, xmax, ymin, ymax
357
358
359    def get_area(self):
360        """Return total area of mesh
361        """
362
363        return sum(self.areas)
364
365       
Note: See TracBrowser for help on using the repository browser.