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

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

added test for fit_to_mesh_file, removed bugs from loadASCII.

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