1818 from .utils import dummy_jit as jit
1919
2020
21+ # Default arguments for numba.jit
22+ JIT_ARGS = dict(nopython=True, target="cpu", fastmath=True, parallel=True)
23+
24+
2125class Spline(BaseGridder):
2226 r"""
2327 Biharmonic spline interpolation using Green's functions.
@@ -71,14 +75,14 @@ class Spline(BaseGridder):
7175 then will be set to the data coordinates the first time
7276 :meth:`~verde.Spline.fit` is called.
7377 engine : str
74- Computation engine for the Jacobian matrix. Can be ``'auto'``, ``'numba'``, or
75- ``'numpy'``. If ``'auto'``, will use numba if it is installed or numpy
76- otherwise. The numba version is multi-threaded and considerably faster, which
78+ Computation engine for the Jacobian matrix and prediction . Can be ``'auto'``,
79+ ``'numba'``, or ``' numpy'``. If ``'auto'``, will use numba if it is installed or
80+ numpy otherwise. The numba version is multi-threaded and usually faster, which
7781 makes fitting and predicting faster.
7882
7983 Attributes
8084 ----------
81- forces_ : array
85+ force_ : array
8286 The estimated forces that fit the observed data.
8387 region_ : tuple
8488 The boundaries (``[W, E, S, N]``) of the data used to fit the
@@ -152,9 +156,19 @@ def predict(self, coordinates):
152156
153157 """
154158 check_is_fitted(self, ["force_"])
155- jac = self.jacobian(coordinates[:2], self.force_coords)
156159 shape = np.broadcast(*coordinates[:2]).shape
157- return jac.dot(self.force_).reshape(shape)
160+ force_east, force_north = n_1d_arrays(self.force_coords, n=2)
161+ east, north = n_1d_arrays(coordinates, n=2)
162+ data = np.empty(east.size, dtype=east.dtype)
163+ if parse_engine(self.engine) == "numba":
164+ data = predict_numba(
165+ east, north, force_east, force_north, self.mindist, self.force_, data
166+ )
167+ else:
168+ data = predict_numpy(
169+ east, north, force_east, force_north, self.mindist, self.force_, data
170+ )
171+ return data.reshape(shape)
158172
159173 def jacobian(self, coordinates, force_coords, dtype="float64"):
160174 """
@@ -183,12 +197,14 @@ def jacobian(self, coordinates, force_coords, dtype="float64"):
183197 """
184198 force_east, force_north = n_1d_arrays(force_coords, n=2)
185199 east, north = n_1d_arrays(coordinates, n=2)
200+ jac = np.empty((east.size, force_east.size), dtype=dtype)
186201 if parse_engine(self.engine) == "numba":
187- jac = np.empty((east.size, force_east.size), dtype=dtype)
188- jacobian_numba(east, north, force_east, force_north, self.mindist, jac)
202+ jac = jacobian_numba(
203+ east, north, force_east, force_north, self.mindist, jac
204+ )
189205 else:
190206 jac = jacobian_numpy(
191- east, north, force_east, force_north, self.mindist, dtype
207+ east, north, force_east, force_north, self.mindist, jac
192208 )
193209 return jac
194210
@@ -214,34 +230,59 @@ def warn_weighted_exact_solution(spline, weights):
214230 )
215231
216232
217- @jit(nopython=True, target="cpu", fastmath=True, parallel=True)
218- def jacobian_numba(east, north, force_east, force_north, mindist, jac):
219- """
220- Calculate the Jacobian matrix using numba to speed things up.
221- """
233+ def greens_func(east, north, mindist):
234+ "Calculate the Green's function for the Bi-Harmonic Spline"
235+ distance = np.sqrt(east ** 2 + north ** 2)
236+ # The mindist factor helps avoid singular matrices when the force and
237+ # computation point are too close
238+ distance += mindist
239+ return (distance ** 2) * (np.log(distance) - 1)
240+
241+
242+ def predict_numpy(east, north, force_east, force_north, mindist, forces, result):
243+ "Calculate the predicted data using numpy."
244+ result[:] = 0
245+ for j in range(forces.size):
246+ green = greens_func(east - force_east[j], north - force_north[j], mindist)
247+ result += green * forces[j]
248+ return result
249+
250+
251+ def jacobian_numpy(east, north, force_east, force_north, mindist, jac):
252+ "Calculate the Jacobian using numpy broadcasting."
253+ # Reshaping the data to a column vector will automatically build a distance matrix
254+ # between each data point and force.
255+ jac[:] = greens_func(
256+ east.reshape((east.size, 1)) - force_east,
257+ north.reshape((north.size, 1)) - force_north,
258+ mindist,
259+ )
260+ return jac
261+
262+
263+ @jit(**JIT_ARGS)
264+ def predict_numba(east, north, force_east, force_north, mindist, forces, result):
265+ "Calculate the predicted data using numba to speed things up."
222266 for i in numba.prange(east.size): # pylint: disable=not-an-iterable
267+ result[i] = 0
268+ for j in range(forces.size):
269+ green = GREENS_FUNC_JIT(
270+ east[i] - force_east[j], north[i] - force_north[j], mindist
271+ )
272+ result[i] += green * forces[j]
273+ return result
274+
275+
276+ @jit(**JIT_ARGS)
277+ def jacobian_numba(east, north, force_east, force_north, mindist, jac):
278+ "Calculate the Jacobian matrix using numba to speed things up."
279+ for i in range(east.size):
223280 for j in range(force_east.size):
224- distance = np.sqrt (
225- ( east[i] - force_east[j]) ** 2 + ( north[i] - force_north[j]) ** 2
281+ jac[i, j] = GREENS_FUNC_JIT (
282+ east[i] - force_east[j], north[i] - force_north[j], mindist
226283 )
227- distance += mindist
228- jac[i, j] = (distance ** 2) * (np.log(distance) - 1)
229284 return jac
230285
231286
232- def jacobian_numpy(east, north, force_east, force_north, mindist, dtype):
233- """
234- Calculate the Jacobian using numpy broadcasting. Is slightly slower than the numba
235- version.
236- """
237- # Reshaping the data to a column vector will automatically build a
238- # distance matrix between each data point and force.
239- distance = np.hypot(
240- east.reshape((east.size, 1)) - force_east,
241- north.reshape((north.size, 1)) - force_north,
242- dtype=dtype,
243- )
244- # The mindist factor helps avoid singular matrices when the force and
245- # computation point are too close
246- distance += mindist
247- return (distance ** 2) * (np.log(distance) - 1)
287+ # Jit compile the Green's functions for use in the numba functions
288+ GREENS_FUNC_JIT = jit(**JIT_ARGS)(greens_func)
0 commit comments