source: inundation/ga/storm_surge/pyvolution/least_squares.py @ 342

Last change on this file since 342 was 342, checked in by duncan, 21 years ago

added 1st iteration of loading a .tsh and .xya file and returning a .tsh with fitted attribute values

File size: 12.2 KB
Line 
1"""Least squares smooting and interpolation.
2
3   Implements a penalised least-squares fit and associated interpolations.
4
5   The panalty term (or smoothing term) is controlled by the smoothing
6   parameter alpha.
7   With a value of alpha=0, the fit function will attempt
8   to interpolate as closely as possible in the least-squares sense.
9   With values alpha > 0, a certain amount of smoothing will be applied.
10   A positive alpha is essential in cases where there are too few
11   data points.
12   A negative alpha is not allowed.
13   A typical value of alpha is 1.0e-6
14         
15
16   Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
17   Geoscience Australia, 2004.   
18"""
19
20#FIXME: Current implementation uses full matrices and a general solver.
21#Later on we may consider using sparse techniques
22
23from general_mesh import General_Mesh
24from Numeric import zeros, array, Float, Int, dot, transpose
25from LinearAlgebra import solve_linear_equations
26
27
28def fit_to_mesh(vertex_coordinates,
29                              triangles,
30                              point_coordinates,
31                              point_attributes,
32                              alpha = 0.0):
33    """
34    Fit a smooth surface to a trianglulation,
35    given data points with attributes.
36
37         
38        Inputs:
39       
40          vertex_coordinates: List of coordinate pairs [xi, eta] of points
41          constituting mesh (or a an m x 2 Numeric array)
42       
43          triangles: List of 3-tuples (or a Numeric array) of
44          integers representing indices of all vertices in the mesh.
45
46          point_coordinates: List of coordinate pairs [x, y] of data points
47          (or an nx2 Numeric array)
48
49          alpha: Smoothing parameter.
50
51          point_attributes: Vector or array of data at the point_coordinates.
52    """
53    interp = Interpolation(vertex_coordinates,
54                           triangles,
55                           point_coordinates,
56                           alpha = alpha)
57   
58    vertex_attributes = interp.fit(point_attributes)
59    return vertex_attributes
60
61
62class Interpolation:
63
64    def __init__(self,
65                 vertex_coordinates,
66                 triangles,
67                 point_coordinates = None,
68                 alpha = 0.001):
69
70       
71        """ Build interpolation matrix mapping from
72        function values at vertices to function values at data points
73
74        Inputs:
75       
76          vertex_coordinates: List of coordinate pairs [xi, eta] of points
77          constituting mesh (or a an m x 2 Numeric array)
78       
79          triangles: List of 3-tuples (or a Numeric array) of
80          integers representing indices of all vertices in the mesh.
81
82          point_coordinates: List of coordinate pairs [x, y] of data points
83          (or an nx2 Numeric array)
84
85          alpha: Smoothing parameter
86         
87        """
88
89
90        #Convert input to Numeric arrays
91        vertex_coordinates = array(vertex_coordinates).astype(Float)
92        triangles = array(triangles).astype(Int)               
93       
94        #Build underlying mesh
95        self.mesh = General_Mesh(vertex_coordinates, triangles)
96
97        #Smoothing parameter
98        self.alpha = alpha
99
100        #Build coefficient matrices
101        self.build_coefficient_matrix_B(point_coordinates)   
102
103
104       
105    def build_coefficient_matrix_B(self, point_coordinates=None):
106        """Build final coefficient matrix
107        """
108
109        self.build_smoothing_matrix_D()
110       
111        if point_coordinates:
112            self.build_interpolation_matrix_A(point_coordinates)
113            AtA = dot(self.At, self.A)
114            self.B = AtA + self.alpha*self.D
115
116       
117    def build_interpolation_matrix_A(self, point_coordinates):
118        """Build n x m interpolation matrix, where
119        n is the number of data points and
120        m is the number of basis functions phi_k (one per vertex)
121        """ 
122
123        #Convert input to Numeric arrays
124        point_coordinates = array(point_coordinates).astype(Float)
125       
126        #Build n x m interpolation matrix       
127        m = self.mesh.coordinates.shape[0] #Nbr of basis functions (1/vertex)
128        n = point_coordinates.shape[0]     #Nbr of data points         
129
130        self.A = zeros((n,m), Float)
131
132        #Compute matrix elements
133        for i in range(n):
134            #For each data_coordinate point
135
136            x = point_coordinates[i]
137            for k in range(len(self.mesh)):
138                #For each triangle (brute force)
139                #FIXME: Real algorithm should only visit relevant triangles
140
141                #Get the three vertex_points
142                xi0 = self.mesh.get_vertex_coordinate(k, 0)
143                xi1 = self.mesh.get_vertex_coordinate(k, 1)
144                xi2 = self.mesh.get_vertex_coordinate(k, 2)                 
145
146                #Get the three normals
147                n0 = self.mesh.get_normal(k, 0)
148                n1 = self.mesh.get_normal(k, 1)
149                n2 = self.mesh.get_normal(k, 2)               
150
151                #Compute interpolation
152                sigma2 = dot((x-xi0), n2)/dot((xi2-xi0), n2)
153                sigma0 = dot((x-xi1), n0)/dot((xi0-xi1), n0)
154                sigma1 = dot((x-xi2), n1)/dot((xi1-xi2), n1)
155
156                #FIXME: Maybe move out to test or something
157                epsilon = 1.0e-13
158                assert abs(sigma0 + sigma1 + sigma2 - 1.0) < epsilon
159
160                #Check that this triangle contains data point
161                if sigma0 >= 0 and sigma1 >= 0 and sigma2 >= 0:
162
163                    #Assign values to matrix A
164                    j = self.mesh.triangles[k,0] #Global vertex id
165                    self.A[i, j] = sigma0
166
167                    j = self.mesh.triangles[k,1] #Global vertex id
168                    self.A[i, j] = sigma1
169
170                    j = self.mesh.triangles[k,2] #Global vertex id
171                    self.A[i, j] = sigma2
172
173   
174        #Precompute
175        self.At = transpose(self.A)
176
177
178        #FIXME: Remember to re-introduce the 1/n factor in the
179        #interpolation term
180       
181    def build_smoothing_matrix_D(self):
182        """Build m x m smoothing matrix, where
183        m is the number of basis functions phi_k (one per vertex)
184
185        The smoothing matrix is defined as
186
187        D = D1 + D2
188
189        where
190
191        [D1]_{k,l} = \int_\Omega
192           \frac{\partial \phi_k}{\partial x}
193           \frac{\partial \phi_l}{\partial x}\,
194           dx dy
195
196        [D2]_{k,l} = \int_\Omega
197           \frac{\partial \phi_k}{\partial y}
198           \frac{\partial \phi_l}{\partial y}\,
199           dx dy
200
201
202        The derivatives \frac{\partial \phi_k}{\partial x},
203        \frac{\partial \phi_k}{\partial x} for a particular triangle
204        are obtained by computing the gradient a_k, b_k for basis function k
205        """
206
207        #FIXME: algorithm might be optimised by computing local 9x9
208        #"element stiffness matrices:
209
210        from util import gradient
211       
212        m = self.mesh.coordinates.shape[0] #Nbr of basis functions (1/vertex)
213
214        self.D = zeros((m,m), Float)
215
216        #For each triangle compute contributions to D = D1+D2       
217        for i in range(len(self.mesh)):
218
219            #Get area
220            area = self.mesh.areas[i]
221
222            #Get global vertex indices
223            v0 = self.mesh.triangles[i,0]
224            v1 = self.mesh.triangles[i,1]
225            v2 = self.mesh.triangles[i,2]
226           
227            #Get the three vertex_points
228            xi0 = self.mesh.get_vertex_coordinate(i, 0)
229            xi1 = self.mesh.get_vertex_coordinate(i, 1)
230            xi2 = self.mesh.get_vertex_coordinate(i, 2)                 
231
232            #Compute gradients for each vertex
233            a0, b0 = gradient(xi0[0], xi0[1], xi1[0], xi1[1], xi2[0], xi2[1],
234                              1, 0, 0)
235
236            a1, b1 = gradient(xi0[0], xi0[1], xi1[0], xi1[1], xi2[0], xi2[1],
237                              0, 1, 0)
238
239            a2, b2 = gradient(xi0[0], xi0[1], xi1[0], xi1[1], xi2[0], xi2[1],
240                              0, 0, 1)           
241
242            #Compute diagonal contributions
243            self.D[v0,v0] += (a0*a0 + b0*b0)*area
244            self.D[v1,v1] += (a1*a1 + b1*b1)*area
245            self.D[v2,v2] += (a2*a2 + b2*b2)*area           
246
247            #Compute contributions for basis functions sharing edges
248            e01 = (a0*a1 + b0*b1)*area
249            self.D[v0,v1] += e01
250            self.D[v1,v0] += e01
251
252            e12 = (a1*a2 + b1*b2)*area
253            self.D[v1,v2] += e12
254            self.D[v2,v1] += e12
255
256            e20 = (a2*a0 + b2*b0)*area
257            self.D[v2,v0] += e20
258            self.D[v0,v2] += e20             
259           
260           
261    def fit(self, z):
262        """Fit a smooth surface to given data points z.
263
264        The smooth surface is computed at each vertex in the underlying
265        mesh using the formula given in the module doc string.
266
267        Pre Condition:
268          self.A, self.At and self.B have been initialised
269         
270        Inputs:
271          z: Vector or array of data at the point_coordinates.
272          If z is an array, smoothing will be done for each column
273        """
274
275        #Convert input to Numeric arrays
276        z = array(z).astype(Float)
277       
278        #Compute right hand side based on data
279        Atz = dot(self.At, z)
280
281        #Check sanity
282        n, m = self.A.shape
283        if n<m and self.alpha == 0.0:
284            msg = 'ERROR (least_squares): Too few data points\n'
285            msg += 'There only %d data points. Need at least %d\n' %(n,m)
286            msg += 'Alternatively, increase smoothing parameter alpha' 
287            raise msg
288
289
290        #Solve and return
291        return solve_linear_equations(self.B, Atz)
292
293        #FIXME: Should we store the result here for later use? (ON)
294   
295
296    def interpolate(self, f):
297        """Compute predicted values at data points implied in self.A.
298
299        The mesh values representing a smooth surface are
300        assumed to be specified in f. This argument could,
301        for example have been obtained from the method self.fit()
302       
303        Pre Condition:
304          self.A has been initialised
305       
306        Inputs:
307          f: Vector or array of data at the mesh vertices.
308          If f is an array, interpolation will be done for each column
309        """
310       
311        return dot(self.A, f)
312
313     
314    #FIXME: We will need a method 'evaluate(self):' that will interpolate
315    #a computed surface living on the mesh onto a collection of
316    #arbitrary data points
317    #
318    #Precondition: self.fit(z) has stored its result in self.f.
319    #
320    #Input: data_points
321    #Algorithm:
322    #  1 Build a new temporary A matrix based on mesh and new data points
323    #  2 Apply it to self.f (return A*self.f)
324    #
325    # ON
326
327
328
329#-------------------------------------------------------------
330if __name__ == "__main__":
331   
332    import os, sys
333    from load_mesh.loadASCII import mesh_file_to_mesh_dictionary, \
334                 load_xya_file, export_trianglulation_file
335    usage = "usage: %s mesh_input.tsh point.xya  mesh_output.tsh" %         os.path.basename(sys.argv[0])
336
337    if len(sys.argv) < 4:
338        print usage
339    else:
340        mesh_file = sys.argv[1]
341        point_file = sys.argv[2]
342        mesh_output_file = sys.argv[3]
343
344        # load in the .tsh file
345        mesh_dic = mesh_file_to_mesh_dictionary(mesh_file)
346        vertex_coordinates = mesh_dic['generatedpointlist']
347        triangles = mesh_dic['generatedtrianglelist']
348
349        # load in the .xya file
350        point_dict = load_xya_file(point_file)
351        point_coordinates = point_dict['pointlist']
352        point_attributes = point_dict['pointattributelist']
353       
354       
355        f = fit_to_mesh(vertex_coordinates,
356                                      triangles,
357                                      point_coordinates,
358                                      point_attributes)
359        # convert array to list of lists
360        mesh_dic['generatedpointattributelist'] = f.tolist()
361        export_trianglulation_file(mesh_output_file, mesh_dic)
362        #FIXME  do unit test
363       
364       
365
366
367
368
369
370
371
372
373
374
375
Note: See TracBrowser for help on using the repository browser.