source: inundation-numpy-branch/pyvolution/general_mesh.py @ 2608

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

Played with custom exceptions for ANUGA

File size: 9.8 KB
Line 
1
2from numpy import array, reshape, zeros, Int, Float, maximum, sqrt, sum, take
3
4
5from 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 pyvolution 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        """
60        Build triangles from x,y coordinates (sequence of 2-tuples or
61        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
62        or Nx3 Numeric array of non-negative integers).
63
64        origin is a 3-tuple consisting of UTM zone, easting and northing.
65        If specified coordinates are assumed to be relative to this origin.
66        """
67
68        self.triangles = array(triangles).astype(Int)
69        self.coordinates = array(coordinates)
70
71        if geo_reference is None:
72            self.geo_reference = Geo_reference() #Use defaults
73        else:
74            self.geo_reference = geo_reference
75
76        #Input checks
77        msg = 'Triangles must an Nx3 Numeric array or a sequence of 3-tuples'
78        assert len(self.triangles.shape) == 2, msg
79
80        msg = 'Coordinates must an Mx2 Numeric array or a sequence of 2-tuples'
81        assert len(self.coordinates.shape) == 2, msg
82
83        msg = 'Vertex indices reference non-existing coordinate sets'
84       
85        assert max(self.triangles.flat[:]) <= self.coordinates.shape[0], msg
86
87        #Register number of elements (N)
88        self.number_of_elements = N = self.triangles.shape[0]
89
90        #FIXME: Maybe move to statistics?
91        xy_extent = [ min(self.coordinates[:,0]), min(self.coordinates[:,1]) ,
92                      max(self.coordinates[:,0]), max(self.coordinates[:,1]) ]
93
94        self.xy_extent = array(xy_extent, Float)
95
96
97        #Allocate space for geometric quantities
98        self.normals = zeros((N, 6), Float)
99        self.areas = zeros(N, Float)
100        self.edgelengths = zeros((N, 3), Float)
101
102        #Get x,y coordinates for all triangles and store
103        self.vertex_coordinates = V = self.compute_vertex_coordinates()
104
105        #Initialise each triangle
106        for i in range(N):
107            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
108
109            x0 = V[i, 0]; y0 = V[i, 1]
110            x1 = V[i, 2]; y1 = V[i, 3]
111            x2 = V[i, 4]; y2 = V[i, 5]
112
113            #Area
114            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
115
116            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
117            msg += ' is degenerate:  area == %f' %self.areas[i]
118            assert self.areas[i] > 0.0, msg
119
120
121            #Normals
122            #The normal vectors
123            # - point outward from each edge
124            # - are orthogonal to the edge
125            # - have unit length
126            # - Are enumerated according to the opposite corner:
127            #   (First normal is associated with the edge opposite
128            #    the first vertex, etc)
129            # - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
130
131            n0 = array([x2 - x1, y2 - y1])
132            l0 = sqrt(sum(n0**2))
133
134            n1 = array([x0 - x2, y0 - y2])
135            l1 = sqrt(sum(n1**2))
136
137            n2 = array([x1 - x0, y1 - y0])
138            l2 = sqrt(sum(n2**2))
139
140            #Normalise
141            n0 /= l0
142            n1 /= l1
143            n2 /= l2
144
145            #Compute and store
146            self.normals[i, :] = [n0[1], -n0[0],
147                                  n1[1], -n1[0],
148                                  n2[1], -n2[0]]
149
150            #Edgelengths
151            self.edgelengths[i, :] = [l0, l1, l2]
152
153        #Build vertex list
154        self.build_vertexlist()
155
156           
157
158    def __len__(self):
159        return self.number_of_elements
160
161    def __repr__(self):
162        return 'Mesh: %d triangles, %d elements'\
163               %(self.coordinates.shape[0], len(self))
164
165    def get_normals(self):
166        """Return all normal vectors.
167        Return normal vectors for all triangles as an Nx6 array
168        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
169        """
170        return self.normals
171
172
173    def get_normal(self, i, j):
174        """Return normal vector j of the i'th triangle.
175        Return value is the numeric array slice [x, y]
176        """
177        return self.normals[i, 2*j:2*j+2]
178
179
180
181    def get_vertex_coordinates(self, obj = False):
182        """Return all vertex coordinates.
183        Return all vertex coordinates for all triangles as an Nx6 array
184        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
185
186        if obj is True, the x/y pairs are returned in a 3*N x 2 array.
187        FIXME, we might make that the default.
188        FIXME Maybe use keyword: continuous = False for this condition?
189        FIXME - Maybe use something referring to unique vertices?
190
191       
192        """
193
194        if obj is True:
195            V = self.vertex_coordinates
196
197            N = V.shape[0]
198            return reshape(V, (3*N, 2))
199        else:   
200            return self.vertex_coordinates
201
202
203    def get_vertex_coordinate(self, i, j):
204        """Return coordinates for vertex j of the i'th triangle.
205        Return value is the numeric array slice [x, y]
206        """
207        return self.vertex_coordinates[i, 2*j:2*j+2]
208
209
210    def compute_vertex_coordinates(self):
211        """Return vertex coordinates for all triangles as an Nx6 array
212        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
213        """
214
215        #FIXME (Ole): Perhaps they should be ordered as in obj files??
216        #See quantity.get_vertex_values
217        #FIXME (Ole) - oh yes they should
218
219
220        N = self.number_of_elements
221        vertex_coordinates = zeros((N, 6), Float)
222
223        for i in range(N):
224            for j in range(3):
225                k = self.triangles[i,j]  #Index of vertex 0
226                v_k = self.coordinates[k]
227                vertex_coordinates[i, 2*j+0] = v_k[0]
228                vertex_coordinates[i, 2*j+1] = v_k[1]
229
230        return vertex_coordinates
231
232    def get_vertices(self, indices=None):
233        """Get connectivity
234        indices is the set of element ids of interest
235        """
236
237        if (indices ==  None):
238            indices = range(len(self))  #len(self)=number of elements
239
240        return  take(self.triangles, indices)
241
242    #FIXME - merge these two (get_vertices and get_triangles)
243    def get_triangles(self, obj = False):
244        """Get connetivity
245        Return triangles (triplets of indices into point coordinates)
246       
247        If obj is True return structure commensurate with replicated
248        points, allowing for discontinuities
249        (FIXME: Need good name for this concept)
250        """
251
252        if obj is True:
253            m = len(self)  #Number of triangles
254            M = 3*m        #Total number of unique vertices
255            T = reshape(array(range(M)).astype(Int), (m,3))
256        else:
257            T = self.triangles
258
259        return T     
260
261   
262
263    def get_unique_vertices(self,  indices=None):
264        triangles = self.get_vertices(indices=indices)
265        unique_verts = {}
266        for triangle in triangles:
267           unique_verts[triangle[0]] = 0
268           unique_verts[triangle[1]] = 0
269           unique_verts[triangle[2]] = 0
270        return unique_verts.keys()
271
272    def build_vertexlist(self):
273        """Build vertexlist index by vertex ids and for each entry (point id)
274        build a list of (triangles, vertex_id) pairs that use the point
275        as vertex.
276
277        Preconditions:
278          self.coordinates and self.triangles are defined
279
280        Postcondition:
281          self.vertexlist is built
282        """
283
284        vertexlist = [None]*len(self.coordinates)
285        for i in range(self.number_of_elements):
286
287            a = self.triangles[i, 0]
288            b = self.triangles[i, 1]
289            c = self.triangles[i, 2]
290
291            #Register the vertices v as lists of
292            #(triangle_id, vertex_id) tuples associated with them
293            #This is used for smoothing
294            for vertex_id, v in enumerate([a,b,c]):
295                if vertexlist[v] is None:
296                    vertexlist[v] = []
297
298                vertexlist[v].append( (i, vertex_id) )
299
300        self.vertexlist = vertexlist
301
302
303    def get_extent(self):
304        """Return min and max of all x and y coordinates
305        """
306
307
308        C = self.get_vertex_coordinates()
309        X = C[:,0:6:2].copy()
310        Y = C[:,1:6:2].copy()
311
312        xmin = min(X.flat)
313        xmax = max(X.flat)
314        ymin = min(Y.flat)
315        ymax = max(Y.flat)
316
317        return xmin, xmax, ymin, ymax
318
319
320    def get_area(self):
321        """Return total area of mesh
322        """
323
324        return sum(self.areas)
325
326       
Note: See TracBrowser for help on using the repository browser.