source: trunk/anuga_core/source/anuga/fit_interpolate/interpolate2d.py @ 8969

Last change on this file since 8969 was 8969, checked in by steve, 11 years ago

Got rid of comparison warnings with nans in test_interpolate2d.py

File size: 10.2 KB
Line 
1"""Bilinear and piecewise constant interpolation.
2
3* Interpolate from a regular numpy array to arbitrary points.
4* NaN's are taken care of sensibly.
5* Algorithm works both for cartesian (x, y) and geographic (lon, lat)
6  coordinates.
7* Guarantees interpolated values within extrema of grid points, i.e.
8  no overshoot.
9
10See docstring for details on usage and the mathematical derivation.
11
12To test, run the accompanying script test_interpolate2d.py
13
14Author: Ole Nielsen 2011
15
16"""
17__author__ = 'Ole Nielsen <ole.moller.nielsen@gmail.com>'
18__revision__ = '$Format:%H$'
19__date__ = '01/11/2011'
20__license__ = 'GPL v3'
21__copyright__ = 'Copyright 2012, Australia Indonesia Facility for '
22__copyright__ += 'Disaster Reduction'
23
24import numpy
25from anuga.anuga_exceptions import ANUGAError
26
27
28def interpolate2d(x, y, Z, points, mode='linear', bounds_error=False):
29    """Fundamental 2D interpolation routine
30
31    Input
32        x: 1D array of x-coordinates of the mesh on which to interpolate
33        y: 1D array of y-coordinates of the mesh on which to interpolate
34        Z: 2D array of values for each x, y pair
35        points: Nx2 array of coordinates where interpolated values are sought
36        mode: Determines the interpolation order. Options are
37            * 'constant' - piecewise constant nearest neighbour interpolation
38            * 'linear' - bilinear interpolation using the four
39                         nearest neighbours (default)
40        bounds_error: Boolean flag. If True (default) a BoundsError exception
41            will be raised when interpolated values are requested
42            outside the domain of the input data. If False, nan
43            is returned for those values
44
45    Output
46        1D array of length N (same length as points) with interpolated values
47
48    Notes
49        Input coordinates x and y are assumed to be monotonically increasing,
50        but need not be equidistantly spaced.
51
52        Z is assumed to have dimension M x N, where M = len(x) and N = len(y).
53        In other words it is assumed that the x values follow the first
54        (vertical) axis downwards and y values the second (horizontal) axis
55        from left to right.
56
57        If this routine is to be used for interpolation of raster grids where
58        data is typically organised with longitudes (x) going from left to
59        right and latitudes (y) from left to right then user
60        interpolate_raster in this module
61
62    Derivation
63
64        Bilinear interpolation is based on the standard 1D linear interpolation
65        formula:
66
67        Given points (x0, y0) and (x1, y1) and a value of x where
68        x0 <= x <= x1, the linearly interpolated value y at x is given as
69
70        alpha*(y1-y0) + y0
71
72        or
73
74        alpha*y1 + (1-alpha)*y0                (1)
75
76        where alpha = (x-x0)/(x1-x0)           (1a)
77
78
79        2D bilinear interpolation aims at obtaining an interpolated value z at
80        a point (x,y) which lies inside a square formed by points (x0, y0),
81        (x1, y0), (x0, y1) and (x1, y1) for which values z00, z10, z01 and
82        z11 are known.
83
84        This is obtained by applying equation (1) twice  in the
85        x-direction to obtain interpolated points q0 and q1 for (x, y0) and
86        (x, y1), respectively.
87
88        q0 = alpha*z10 + (1-alpha)*z00         (2)
89
90        and
91
92        q1 = alpha*z11 + (1-alpha)*z01         (3)
93
94
95        Then using equation (1) in the y-direction on the results from (2)
96        and (3)
97
98        z = beta*q1 + (1-beta)*q0              (4)
99
100        where beta = (y-y0)/(y1-y0)            (4a)
101
102
103        Substituting (2) and (3) into (4) yields
104
105        z = alpha*beta*z11 + beta*z01 - alpha*beta*z01 +
106            alpha*z10 + z00 - alpha*z00 - alpha*beta*z10 - beta*z00 +
107            alpha*beta*z00
108          = alpha*beta*(z11 - z01 - z10 + z00) +
109            alpha*(z10 - z00) + beta*(z01 - z00) + z00
110
111        which can be further simplified to
112
113        z = alpha*beta*(z11 - dx - dy - z00) + alpha*dx + beta*dy + z00  (5)
114
115        where
116        dx = z10 - z00
117        dy = z01 - z00
118
119        Equation (5) is what is implemented in the function interpolate2d.
120
121
122        Piecewise constant interpolation can be implemented using the same
123        coefficients (1a) and (4a) that are used for bilinear interpolation
124        as they are a measure of the relative distance to the left and lower
125        neigbours. A value of 0 will pick the left or lower bound whereas a
126        value of 1 will pick the right or higher bound. Hence z can be
127        assigned to its nearest neigbour as follows
128
129            | z00   alpha < 0.5 and beta < 0.5    # lower left corner
130            |
131            | z10   alpha >= 0.5 and beta < 0.5   # lower right corner
132        z = |
133            | z01   alpha < 0.5 and beta >= 0.5   # upper left corner
134            |
135            | z11   alpha >= 0.5 and beta >= 0.5  # upper right corner
136
137    """
138
139    # Check inputs and provide xi, eta as x and y coordinates from
140    # points vector
141    x, y, Z, xi, eta = check_inputs(x, y, Z, points, mode, bounds_error)
142
143    # Identify elements that are outside interpolation domain or NaN
144    oldset = numpy.seterr(invalid='ignore')  # Suppress comparison with nan warning
145    outside = (xi < x[0]) + (eta < y[0]) + (xi > x[-1]) + (eta > y[-1])
146    outside += numpy.isnan(xi) + numpy.isnan(eta)
147    numpy.seterr(**oldset)  # Restore warnings
148   
149    # Restrict interpolation points to those that are inside the grid
150    inside = -outside  # Invert boolean array to find elements inside
151    xi = xi[inside]
152    eta = eta[inside]
153
154    # Find upper neighbours for each interpolation point
155    # ('left' means first occurrence)
156    idx = numpy.searchsorted(x, xi, side='left')
157    idy = numpy.searchsorted(y, eta, side='left')
158
159    # Get the four neighbours for each interpolation point
160    x0 = x[idx - 1]  # Left
161    x1 = x[idx]      # Right
162    y0 = y[idy - 1]  # Lower
163    y1 = y[idy]      # Upper
164
165    # And the corresponding four grid values
166    z00 = Z[idx - 1, idy - 1]
167    z01 = Z[idx - 1, idy]
168    z10 = Z[idx, idy - 1]
169    z11 = Z[idx, idy]
170
171    # Coefficients for weighting between lower and upper bounds
172    oldset = numpy.seterr(invalid='ignore')  # Suppress zero division warning
173    alpha = (xi - x0) / (x1 - x0)
174    beta = (eta - y0) / (y1 - y0)
175    numpy.seterr(**oldset)  # Restore warnings
176
177    if mode == 'linear':
178        # Bilinear interpolation formula as per equation (5) above
179        dx = z10 - z00
180        dy = z01 - z00
181        z = z00 + alpha * dx + beta * dy + alpha * beta * (z11 - dx - dy - z00)
182    else:
183        # Piecewise constant (as verified in input_check)
184
185        # Set up masks for the quadrants
186        left = alpha < 0.5
187        right = -left
188        lower = beta < 0.5
189        upper = -lower
190
191        lower_left = lower * left
192        lower_right = lower * right
193        upper_left = upper * left
194
195        # Initialise result array with all elements set to upper right
196        z = z11
197
198        # Then set the other quadrants
199        z[lower_left] = z00[lower_left]
200        z[lower_right] = z10[lower_right]
201        z[upper_left] = z01[upper_left]
202
203    # Populate result with interpolated values for points inside domain
204    # and NaN for values outside
205    r = numpy.zeros(len(points))
206    r[inside] = z
207    r[outside] = numpy.nan
208
209    # Return interpolated points
210    return r
211
212
213#------------------------
214# Auxiliary functionality
215#------------------------
216def check_inputs(x, y, Z, points, mode, bounds_error):
217    """Check inputs for interpolate2d function
218    """
219
220    msg = ('Only mode "linear" and "constant" are implemented. '
221           'I got "%s"' % mode)
222    if mode not in ['linear', 'constant']:
223        raise ANUGAError(msg)
224
225    x = numpy.array(x)
226
227    try:
228        y = numpy.array(y)
229    except Exception, e:
230        msg = ('Input vector y could not be converted to numpy array: '
231               '%s' % str(e))
232        raise Exception(msg)
233
234    msg = ('Input vector x must be monotonically increasing. I got '
235           'min(x) == %.15f, but x[0] == %.15f' % (min(x), x[0]))
236    if not min(x) == x[0]:
237        raise ANUGAError(msg)
238
239    msg = ('Input vector y must be monotoneously increasing. '
240           'I got min(y) == %.15f, but y[0] == %.15f' % (min(y), y[0]))
241    if not min(y) == y[0]:
242        raise ANUGAError(msg)
243
244    msg = ('Input vector x must be monotoneously increasing. I got '
245           'max(x) == %.15f, but x[-1] == %.15f' % (max(x), x[-1]))
246    if not max(x) == x[-1]:
247        raise ANUGAError(msg)
248
249    msg = ('Input vector y must be monotoneously increasing. I got '
250           'max(y) == %.15f, but y[-1] == %.15f' % (max(y), y[-1]))
251    if not max(y) == y[-1]:
252        raise ANUGAError(msg)
253
254    try:
255        Z = numpy.array(Z)
256        m, n = Z.shape
257    except Exception, e:
258        msg = 'Z must be a 2D numpy array: %s' % str(e)
259        raise Exception(msg)
260
261    Nx = len(x)
262    Ny = len(y)
263    msg = ('Input array Z must have dimensions %i x %i corresponding to the '
264           'lengths of the input coordinates x and y. However, '
265           'Z has dimensions %i x %i.' % (Nx, Ny, m, n))
266    if not (Nx == m and Ny == n):
267        raise ANUGAError(msg)
268
269    # Get interpolation points
270    points = numpy.array(points)
271    xi = points[:, 0]
272    eta = points[:, 1]
273
274    if bounds_error:
275        xi0 = numpy.nanmin(xi)
276        xi1 = numpy.nanmax(xi)
277        eta0 = numpy.nanmin(eta)
278        eta1 = numpy.nanmax(eta)
279
280        msg = ('Interpolation point xi=%f was less than the smallest '
281               'value in domain (x=%f) and bounds_error was requested.'
282               % (xi0, x[0]))
283        if xi0 < x[0]:
284            raise BoundsError(msg)
285
286        msg = ('Interpolation point xi=%f was greater than the largest '
287               'value in domain (x=%f) and bounds_error was requested.'
288               % (xi1, x[-1]))
289        if xi1 > x[-1]:
290            raise BoundsError(msg)
291
292        msg = ('Interpolation point eta=%f was less than the smallest '
293               'value in domain (y=%f) and bounds_error was requested.'
294               % (eta0, y[0]))
295        if eta0 < y[0]:
296            raise BoundsError(msg)
297
298        msg = ('Interpolation point eta=%f was greater than the largest '
299               'value in domain (y=%f) and bounds_error was requested.'
300               % (eta1, y[-1]))
301        if eta1 > y[-1]:
302            raise BoundsError(msg)
303
304    return x, y, Z, xi, eta
Note: See TracBrowser for help on using the repository browser.