Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix: Reset force coords when refitting splines #191

Merged
merged 1 commit into from
Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions verde/spline.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ class SplineCV(BaseGridder):
----------
force_ : array
The estimated forces that fit the observed data.
force_coords_ : tuple of arrays
The easting and northing coordinates of the point forces. Same as *force_coords*
if it is not None. Otherwise, same as the data locations used to fit the spline.
region_ : tuple
The boundaries (``[W, E, S, N]``) of the data used to fit the
interpolator. Used as the default region for the
Expand Down Expand Up @@ -176,7 +179,6 @@ def fit(self, coordinates, data, weights=None):
best = np.argmax(self.scores_)
self._best = Spline(**parameter_sets[best])
self._best.fit(coordinates, data, weights=weights)
self.force_coords = self._best.force_coords
return self

@property
Expand All @@ -199,6 +201,11 @@ def mindist_(self):
"The optimal mindist parameter"
return self._best.mindist

@property
def force_coords_(self):
"The optimal force locations"
return self._best.force_coords_

def predict(self, coordinates):
"""
Evaluate the best estimated spline on the given set of points.
Expand Down Expand Up @@ -273,8 +280,7 @@ class Spline(BaseGridder):
imposed on the estimated forces. If None, no regularization is used.
force_coords : None or tuple of arrays
The easting and northing coordinates of the point forces. If None (default),
then will be set to the data coordinates the first time
:meth:`~verde.Spline.fit` is called.
then will be set to the data coordinates used to fit the spline.
engine : str
Computation engine for the Jacobian matrix and prediction. Can be ``'auto'``,
``'numba'``, or ``'numpy'``. If ``'auto'``, will use numba if it is installed or
Expand All @@ -285,6 +291,9 @@ class Spline(BaseGridder):
----------
force_ : array
The estimated forces that fit the observed data.
force_coords_ : tuple of arrays
The easting and northing coordinates of the point forces. Same as *force_coords*
if it is not None. Otherwise, same as the data locations used to fit the spline.
region_ : tuple
The boundaries (``[W, E, S, N]``) of the data used to fit the
interpolator. Used as the default region for the
Expand Down Expand Up @@ -336,8 +345,10 @@ def fit(self, coordinates, data, weights=None):
# Capture the data region to use as a default when gridding.
self.region_ = get_region(coordinates[:2])
if self.force_coords is None:
self.force_coords = tuple(i.copy() for i in n_1d_arrays(coordinates, n=2))
jacobian = self.jacobian(coordinates[:2], self.force_coords)
self.force_coords_ = tuple(i.copy() for i in n_1d_arrays(coordinates, n=2))
else:
self.force_coords_ = self.force_coords
jacobian = self.jacobian(coordinates[:2], self.force_coords_)
self.force_ = least_squares(jacobian, data, weights, self.damping)
return self

Expand All @@ -363,7 +374,7 @@ def predict(self, coordinates):
"""
check_is_fitted(self, ["force_"])
shape = np.broadcast(*coordinates[:2]).shape
force_east, force_north = n_1d_arrays(self.force_coords, n=2)
force_east, force_north = n_1d_arrays(self.force_coords_, n=2)
east, north = n_1d_arrays(coordinates, n=2)
data = np.empty(east.size, dtype=east.dtype)
if parse_engine(self.engine) == "numba":
Expand Down
15 changes: 8 additions & 7 deletions verde/tests/test_spline.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ def test_spline_cv():
# Can't test on many configurations because it takes too long for regular testing
spline = SplineCV(
dampings=[None],
mindists=[1e-5, 1e-3],
mindists=[1e-7, 1e-5],
cv=ShuffleSplit(n_splits=2, random_state=0),
).fit(coords, data.scalars)
# The interpolation should be perfect on top of the data points
npt.assert_allclose(spline.predict(coords), data.scalars, rtol=1e-5)
npt.assert_allclose(spline.score(coords, data.scalars), 1)
assert spline.mindist_ == 1e-3
npt.assert_allclose(spline.force_coords_, coords)
assert spline.mindist_ == 1e-7
assert spline.damping_ is None
npt.assert_allclose(spline.force_coords, coords)
# There should be 1 force per data point
assert data.scalars.size == spline.force_.size
npt.assert_allclose(
Expand Down Expand Up @@ -66,16 +66,17 @@ def test_spline_cv_parallel():
# Can't test on many configurations because it takes too long for regular testing
# Use ShuffleSplit instead of KFold to test it out and make this run faster
spline = SplineCV(
dampings=[None],
mindists=[1e-5, 1e-3],
dampings=[None, 1e-8],
mindists=[1e-7, 1e-5],
client=client,
cv=ShuffleSplit(n_splits=1, random_state=0),
).fit(coords, data.scalars)
client.close()
# The interpolation should be perfect on top of the data points
npt.assert_allclose(spline.predict(coords), data.scalars, rtol=1e-5)
npt.assert_allclose(spline.score(coords, data.scalars), 1)
assert spline.mindist_ == 1e-5
npt.assert_allclose(spline.force_coords_, coords)
assert spline.mindist_ == 1e-7
assert spline.damping_ is None
shape = (5, 5)
region = (270, 320, -770, -720)
Expand All @@ -100,7 +101,7 @@ def test_spline():
npt.assert_allclose(spline.score(coords, data.scalars), 1)
# There should be 1 force per data point
assert data.scalars.size == spline.force_.size
npt.assert_allclose(spline.force_coords, coords)
npt.assert_allclose(spline.force_coords_, coords)
shape = (5, 5)
region = (270, 320, -770, -720)
# Tolerance needs to be kind of high to allow for error due to small
Expand Down