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

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

adding in Ole's 2d interpolation code

File size: 10.0 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    outside = (xi < x[0]) + (eta < y[0]) + (xi > x[-1]) + (eta > y[-1])
145    outside += numpy.isnan(xi) + numpy.isnan(eta)
146
147    # Restrict interpolation points to those that are inside the grid
148    inside = -outside  # Invert boolean array to find elements inside
149    xi = xi[inside]
150    eta = eta[inside]
151
152    # Find upper neighbours for each interpolation point
153    # ('left' means first occurrence)
154    idx = numpy.searchsorted(x, xi, side='left')
155    idy = numpy.searchsorted(y, eta, side='left')
156
157    # Get the four neighbours for each interpolation point
158    x0 = x[idx - 1]  # Left
159    x1 = x[idx]      # Right
160    y0 = y[idy - 1]  # Lower
161    y1 = y[idy]      # Upper
162
163    # And the corresponding four grid values
164    z00 = Z[idx - 1, idy - 1]
165    z01 = Z[idx - 1, idy]
166    z10 = Z[idx, idy - 1]
167    z11 = Z[idx, idy]
168
169    # Coefficients for weighting between lower and upper bounds
170    oldset = numpy.seterr(invalid='ignore')  # Suppress zero division warning
171    alpha = (xi - x0) / (x1 - x0)
172    beta = (eta - y0) / (y1 - y0)
173    numpy.seterr(**oldset)  # Restore warnings
174
175    if mode == 'linear':
176        # Bilinear interpolation formula as per equation (5) above
177        dx = z10 - z00
178        dy = z01 - z00
179        z = z00 + alpha * dx + beta * dy + alpha * beta * (z11 - dx - dy - z00)
180    else:
181        # Piecewise constant (as verified in input_check)
182
183        # Set up masks for the quadrants
184        left = alpha < 0.5
185        right = -left
186        lower = beta < 0.5
187        upper = -lower
188
189        lower_left = lower * left
190        lower_right = lower * right
191        upper_left = upper * left
192
193        # Initialise result array with all elements set to upper right
194        z = z11
195
196        # Then set the other quadrants
197        z[lower_left] = z00[lower_left]
198        z[lower_right] = z10[lower_right]
199        z[upper_left] = z01[upper_left]
200
201    # Populate result with interpolated values for points inside domain
202    # and NaN for values outside
203    r = numpy.zeros(len(points))
204    r[inside] = z
205    r[outside] = numpy.nan
206
207    # Return interpolated points
208    return r
209
210
211#------------------------
212# Auxiliary functionality
213#------------------------
214def check_inputs(x, y, Z, points, mode, bounds_error):
215    """Check inputs for interpolate2d function
216    """
217
218    msg = ('Only mode "linear" and "constant" are implemented. '
219           'I got "%s"' % mode)
220    if mode not in ['linear', 'constant']:
221        raise ANUGAError(msg)
222
223    x = numpy.array(x)
224
225    try:
226        y = numpy.array(y)
227    except Exception, e:
228        msg = ('Input vector y could not be converted to numpy array: '
229               '%s' % str(e))
230        raise Exception(msg)
231
232    msg = ('Input vector x must be monotonically increasing. I got '
233           'min(x) == %.15f, but x[0] == %.15f' % (min(x), x[0]))
234    if not min(x) == x[0]:
235        raise ANUGAError(msg)
236
237    msg = ('Input vector y must be monotoneously increasing. '
238           'I got min(y) == %.15f, but y[0] == %.15f' % (min(y), y[0]))
239    if not min(y) == y[0]:
240        raise ANUGAError(msg)
241
242    msg = ('Input vector x must be monotoneously increasing. I got '
243           'max(x) == %.15f, but x[-1] == %.15f' % (max(x), x[-1]))
244    if not max(x) == x[-1]:
245        raise ANUGAError(msg)
246
247    msg = ('Input vector y must be monotoneously increasing. I got '
248           'max(y) == %.15f, but y[-1] == %.15f' % (max(y), y[-1]))
249    if not max(y) == y[-1]:
250        raise ANUGAError(msg)
251
252    try:
253        Z = numpy.array(Z)
254        m, n = Z.shape
255    except Exception, e:
256        msg = 'Z must be a 2D numpy array: %s' % str(e)
257        raise Exception(msg)
258
259    Nx = len(x)
260    Ny = len(y)
261    msg = ('Input array Z must have dimensions %i x %i corresponding to the '
262           'lengths of the input coordinates x and y. However, '
263           'Z has dimensions %i x %i.' % (Nx, Ny, m, n))
264    if not (Nx == m and Ny == n):
265        raise ANUGAError(msg)
266
267    # Get interpolation points
268    points = numpy.array(points)
269    xi = points[:, 0]
270    eta = points[:, 1]
271
272    if bounds_error:
273        xi0 = min(xi)
274        xi1 = max(xi)
275        eta0 = min(eta)
276        eta1 = max(eta)
277
278        msg = ('Interpolation point xi=%f was less than the smallest '
279               'value in domain (x=%f) and bounds_error was requested.'
280               % (xi0, x[0]))
281        if xi0 < x[0]:
282            raise BoundsError(msg)
283
284        msg = ('Interpolation point xi=%f was greater than the largest '
285               'value in domain (x=%f) and bounds_error was requested.'
286               % (xi1, x[-1]))
287        if xi1 > x[-1]:
288            raise BoundsError(msg)
289
290        msg = ('Interpolation point eta=%f was less than the smallest '
291               'value in domain (y=%f) and bounds_error was requested.'
292               % (eta0, y[0]))
293        if eta0 < y[0]:
294            raise BoundsError(msg)
295
296        msg = ('Interpolation point eta=%f was greater than the largest '
297               'value in domain (y=%f) and bounds_error was requested.'
298               % (eta1, y[-1]))
299        if eta1 > y[-1]:
300            raise BoundsError(msg)
301
302    return x, y, Z, xi, eta
Note: See TracBrowser for help on using the repository browser.