Repository: fatiando/verde
Pull request #103 (2018-08-01 00:35:11)

Rename Vector2D to VectorSpline2D
The name Vector2D doesn't really tell anyone what it does. Also renamed
the `fudge` parameter to `mindist` which is what it really is.


Patch:
diff --git a/doc/api/index.rst b/doc/api/index.rst
index c6e89cb..20885c1 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -14,7 +14,7 @@ Interpolators
    :toctree: generated/
 
     Spline
-    Vector2D
+    VectorSpline2D
     ScipyGridder
 
 Data Processing
diff --git a/examples/vector2d.py b/examples/vectorspline2d.py
similarity index 92%
rename from examples/vector2d.py
rename to examples/vectorspline2d.py
index c9680f9..6e671a3 100644
--- a/examples/vector2d.py
+++ b/examples/vectorspline2d.py
@@ -4,8 +4,8 @@ Coupled gridding of 2-component vector data
 
 One way of gridding vector data would be grid each component separately using
 :class:`verde.Spline` and :class:`verde.Components`. Alternatively,
-:class:`verde.Vector2D` can grid two components simultaneously in a way that couples
-them through elastic deformation theory. This is particularly suited, though not
+:class:`verde.VectorSpline2D` can grid two components simultaneously in a way that
+couples them through elastic deformation theory. This is particularly suited, though not
 exclusive, to data that represent elastic/semi-elastic deformation, like horizontal GPS
 velocities.
 """
@@ -21,7 +21,7 @@ import verde as vd
 data = vd.datasets.fetch_california_gps()
 coordinates = (data.longitude.values, data.latitude.values)
 region = vd.get_region(coordinates)
-# Use a Mercator projection because Vector2D is a Cartesian gridder
+# Use a Mercator projection because VectorSpline2D is a Cartesian gridder
 projection = pyproj.Proj(proj="merc", lat_ts=data.latitude.mean())
 
 # Split the data into a training and testing set. We'll fit the gridder on the training
@@ -40,7 +40,7 @@ chain = vd.Chain(
     [
         ("mean", vd.BlockReduce(np.mean, spacing * 111e3)),
         ("trend", vd.Components([vd.Trend(degree=5) for i in range(2)])),
-        ("spline", vd.Vector2D(poisson=0.5)),
+        ("spline", vd.VectorSpline2D(poisson=0.5, mindist=10e3)),
     ]
 )
 # Fit on the training data
diff --git a/verde/__init__.py b/verde/__init__.py
index c6bde10..9e00a8c 100644
--- a/verde/__init__.py
+++ b/verde/__init__.py
@@ -21,7 +21,7 @@ from .chain import Chain
 from .components import Components
 from .spline import Spline
 from .model_selection import cross_val_score, train_test_split
-from .vector import Vector2D
+from .vector import VectorSpline2D
 
 
 def test(doctest=True, verbose=True, coverage=False, figures=True):
diff --git a/verde/spline.py b/verde/spline.py
index 0fd89a2..5bd6ee9 100644
--- a/verde/spline.py
+++ b/verde/spline.py
@@ -16,61 +16,60 @@ class Spline(BaseGridder):
     r"""
     Biharmonic spline interpolation using Green's functions.
 
-    Implements the 2D splines of [Sandwell1987]_. The Green's function for the
-    spline corresponds to the elastic deflection of a thin sheet subject to a
-    vertical force. For an observation point at the origin and a force at the
-    coordinates given by the vector :math:`\mathbf{x}`, the Green's function
-    is:
+    This gridder assumes Cartesian coordinates.
+
+    Implements the 2D splines of [Sandwell1987]_. The Green's function for the spline
+    corresponds to the elastic deflection of a thin sheet subject to a vertical force.
+    For an observation point at the origin and a force at the coordinates given by the
+    vector :math:`\mathbf{x}`, the Green's function is:
 
     .. math::
 
         g(\mathbf{x}) = \|\mathbf{x}\|^2 \left(\log \|\mathbf{x}\| - 1\right)
 
-    In practice, this function is not defined for data points that coincide
-    with a force. To prevent this, a fudge factor is added to
-    :math:`\|\mathbf{x}\|`.
+    In practice, this function is not defined for data points that coincide with a
+    force. To prevent this, a fudge factor is added to :math:`\|\mathbf{x}\|`.
 
-    The interpolation is performed by estimating forces that produce
-    deflections that fit the observed data (using least-squares). Then, the
-    interpolated points can be evaluated at any location.
+    The interpolation is performed by estimating forces that produce deflections that
+    fit the observed data (using least-squares). Then, the interpolated points can be
+    evaluated at any location.
 
-    By default, the forces will be placed at the same points as the input data
-    given to :meth:`~verde.Spline.fit`. This configuration provides an exact
-    solution on top of the data points. However, this solution can be unstable
-    for certain configurations of data points.
+    By default, the forces will be placed at the same points as the input data given to
+    :meth:`~verde.Spline.fit`. This configuration provides an exact solution on top of
+    the data points. However, this solution can be unstable for certain configurations
+    of data points.
 
     Approximate (and more stable) solutions can be obtained by applying damping
-    regularization to smooth the estimated forces (and interpolated values) or
-    by distributing the forces on a regular grid instead (using arguments
-    *spacing*, *shape*, and/or *region*).
+    regularization to smooth the estimated forces (and interpolated values) or by
+    distributing the forces on a regular grid instead (using arguments *spacing*,
+    *shape*, and/or *region*).
 
-    Data weights can be used during fitting but only have an any effect when
-    using the approximate solutions.
+    Data weights can be used during fitting but only have an any effect when using the
+    approximate solutions.
 
-    The Jacobian (design, sensitivity, feature, etc) matrix for the spline
-    is normalized using :class:`sklearn.preprocessing.StandardScaler` without
-    centering the mean so that the transformation can be undone in the
-    estimated forces.
+    The Jacobian (design, sensitivity, feature, etc) matrix for the spline is normalized
+    using :class:`sklearn.preprocessing.StandardScaler` without centering the mean so
+    that the transformation can be undone in the estimated forces.
 
     Parameters
     ----------
-    fudge : float
-        The positive fudge factor applied to the Green's function to avoid
-        singularities.
+    mindist : float
+        A minimum distance between the point forces and data points. Needed because the
+        Green's functions are singular when forces and data points coincide. Acts as a
+        fudge factor.
     damping : None or float
-        The positive damping regularization parameter. Controls how much
-        smoothness is imposed on the estimated forces. If None, no
-        regularization is used.
+        The positive damping regularization parameter. Controls how much smoothness is
+        imposed on the estimated forces. If None, no regularization is used.
     shape : None or tuple
-        If not None, then should be the shape of the regular grid of forces.
-        See :func:`verde.grid_coordinates` for details.
+        If not None, then should be the shape of the regular grid of forces. See
+        :func:`verde.grid_coordinates` for details.
     spacing : None or float or tuple
-        If not None, then should be the spacing of the regular grid of forces.
-        See :func:`verde.grid_coordinates` for details.
+        If not None, then should be the spacing of the regular grid of forces. See
+        :func:`verde.grid_coordinates` for details.
     region : None or tuple
-        If not None, then the boundaries (``[W, E, S, N]``) used to generate a
-        regular grid of forces. If None is given, then will use the boundaries
-        of data given to the first call to :meth:`~verde.Spline.fit`.
+        If not None, then the boundaries (``[W, E, S, N]``) used to generate a regular
+        grid of forces. If None is given, then will use the boundaries of data given to
+        the first call to :meth:`~verde.Spline.fit`.
 
     Attributes
     ----------
@@ -85,11 +84,13 @@ class Spline(BaseGridder):
 
     """
 
-    def __init__(self, fudge=1e-5, damping=None, shape=None, spacing=None, region=None):
+    def __init__(
+        self, mindist=1e-5, damping=None, shape=None, spacing=None, region=None
+    ):
         self.damping = damping
         self.shape = shape
         self.spacing = spacing
-        self.fudge = fudge
+        self.mindist = mindist
         self.region = region
 
     def fit(self, coordinates, data, weights=None):
@@ -252,7 +253,7 @@ class Spline(BaseGridder):
             northing.reshape((northing.size, 1)) - force_northing.ravel(),
             dtype=dtype,
         )
-        # The fudge factor helps avoid singular matrices when the force and
+        # The mindist factor helps avoid singular matrices when the force and
         # computation point are too close
-        distance += self.fudge
+        distance += self.mindist
         return (distance ** 2) * (np.log(distance) - 1)
diff --git a/verde/vector.py b/verde/vector.py
index ccaf7fa..5049d9b 100644
--- a/verde/vector.py
+++ b/verde/vector.py
@@ -10,64 +10,65 @@ from .spline import Spline
 from .coordinates import get_region
 
 
-class Vector2D(Spline):
+class VectorSpline2D(Spline):
     r"""
     Elastically coupled interpolation of 2-component vector data.
 
-    Uses the Green's functions based on elastic deformation from
-    [SandwellWessel2016]_. The interpolation is done by estimating point forces
-    that generate an elastic deformation that fits the observed vector data.
-    The deformation equations are based on a 2D elastic sheet with a constant
-    Poisson's ratio. The data can then be predicted at any desired location.
-
-    The east and north data components are coupled through the elastic
-    deformation equations. This coupling is controlled by the Poisson's ratio,
-    which is usually between -1 and 1. The special case of Poisson's ratio -1
-    leads to an uncoupled interpolation, meaning that the east and north
-    components don't interfere with each other.
-
-    The point forces are traditionally placed under each data point. The force
-    locations are set the first time :meth:`~verde.Vector2D.fit` is called.
-    Subsequent calls will fit using the same force locations as the first call.
-    This configuration results in an exact prediction at the data points but
-    can be unstable.
-
-    [SandwellWessel2016]_ stabilize the solution using Singular Value
-    Decomposition but we use ridge regression instead. The regularization can
-    be controlled using the *damping* argument. Alternatively, we also allow
-    forces to be placed on a regular grid using the *spacing*, *shape*, and/or
-    *region* arguments. Regularization or forces on a grid will result in a
-    least-squares estimate at the data points, not an exact solution. Note that
-    the least-squares solution is required for data weights to have any effect.
-
-    The Jacobian (design, sensitivity, feature, etc) matrix for the spline
-    is normalized using :class:`sklearn.preprocessing.StandardScaler` without
-    centering the mean so that the transformation can be undone in the
-    estimated forces.
+    This gridder assumes Cartesian coordinates.
+
+    Uses the Green's functions based on elastic deformation from [SandwellWessel2016]_.
+    The interpolation is done by estimating point forces that generate an elastic
+    deformation that fits the observed vector data. The deformation equations are based
+    on a 2D elastic sheet with a constant Poisson's ratio. The data can then be
+    predicted at any desired location.
+
+    The east and north data components are coupled through the elastic deformation
+    equations. This coupling is controlled by the Poisson's ratio, which is usually
+    between -1 and 1. The special case of Poisson's ratio -1 leads to an uncoupled
+    interpolation, meaning that the east and north components don't interfere with each
+    other.
+
+    The point forces are traditionally placed under each data point. The force locations
+    are set the first time :meth:`~verde.VectorSpline2D.fit` is called. Subsequent calls
+    will fit using the same force locations as the first call. This configuration
+    results in an exact prediction at the data points but can be unstable.
+
+    [SandwellWessel2016]_ stabilize the solution using Singular Value Decomposition but
+    we use ridge regression instead. The regularization can be controlled using the
+    *damping* argument. Alternatively, we also allow forces to be placed on a regular
+    grid using the *spacing*, *shape*, and/or *region* arguments. Regularization or
+    forces on a grid will result in a least-squares estimate at the data points, not an
+    exact solution. Note that the least-squares solution is required for data weights to
+    have any effect.
+
+    The Jacobian (design, sensitivity, feature, etc) matrix for the spline is normalized
+    using :class:`sklearn.preprocessing.StandardScaler` without centering the mean so
+    that the transformation can be undone in the estimated forces.
 
     Parameters
     ----------
     poisson : float
-        The Poisson's ratio for the elastic deformation Green's functions.
-        Default is 0.5. A value of -1 will lead to uncoupled interpolation of
-        the east and north data components.
-    fudge : float
-        The positive fudge factor applied to the Green's function to avoid
-        singularities.
+        The Poisson's ratio for the elastic deformation Green's functions. Default is
+        0.5. A value of -1 will lead to uncoupled interpolation of the east and north
+        data components.
+    mindist : float
+        A minimum distance between the point forces and data points. Needed because the
+        Green's functions are singular when forces and data points coincide. Acts as a
+        fudge factor. A good rule of thumb is to use the average spacing between data
+        points.
     damping : None or float
-        The positive damping regularization parameter. Controls how much
-        smoothness is imposed on the estimated forces. If None, no
-        regularization is used.
+        The positive damping regularization parameter. Controls how much smoothness is
+        imposed on the estimated forces. If None, no regularization is used.
     shape : None or tuple
-        If not None, then should be the shape of the regular grid of forces.
-        See :func:`verde.grid_coordinates` for details.
+        If not None, then should be the shape of the regular grid of forces. See
+        :func:`verde.grid_coordinates` for details.
     spacing : None or float or tuple
-        If not None, then should be the spacing of the regular grid of forces.
-        See :func:`verde.grid_coordinates` for details.
+        If not None, then should be the spacing of the regular grid of forces. See
+        :func:`verde.grid_coordinates` for details.
     region : None or tuple
-        If not None, then the boundaries (``[W, E, S, N]``) used to generate a
-        regular grid of forces. If None is given, then will use the boundaries
-        of data given to the first call to :meth:`~verde.Vector2D.fit`.
+        If not None, then the boundaries (``[W, E, S, N]``) used to generate a regular
+        grid of forces. If None is given, then will use the boundaries of data given to
+        the first call to :meth:`~verde.VectorSpline2D.fit`.
 
     Attributes
     ----------
@@ -78,7 +79,7 @@ class Vector2D(Spline):
     region_ : tuple
         The boundaries (``[W, E, S, N]``) of the data used to fit the
         interpolator. Used as the default region for the
-        :meth:`~verde.Vector2D.grid` and :meth:`~verde.Vector2D.scatter`
+        :meth:`~verde.VectorSpline2D.grid` and :meth:`~verde.VectorSpline2D.scatter`
         methods.
 
     """
@@ -86,7 +87,7 @@ class Vector2D(Spline):
     def __init__(
         self,
         poisson=0.5,
-        fudge=1e-5,
+        mindist=10e3,
         damping=None,
         shape=None,
         spacing=None,
@@ -94,7 +95,11 @@ class Vector2D(Spline):
     ):
         self.poisson = poisson
         super().__init__(
-            fudge=fudge, damping=damping, shape=shape, spacing=spacing, region=region
+            mindist=mindist,
+            damping=damping,
+            shape=shape,
+            spacing=spacing,
+            region=region,
         )
 
     def fit(self, coordinates, data, weights=None):
@@ -102,7 +107,7 @@ class Vector2D(Spline):
         Fit the gridder to the given 2-component vector data.
 
         The data region is captured and used as default for the
-        :meth:`~verde.Vector2D.grid` and :meth:`~verde.Vector2D.scatter`
+        :meth:`~verde.VectorSpline2D.grid` and :meth:`~verde.VectorSpline2D.scatter`
         methods.
 
         All input arrays must have the same shape.
@@ -154,7 +159,7 @@ class Vector2D(Spline):
         """
         Evaluate the fitted gridder on the given set of points.
 
-        Requires a fitted estimator (see :meth:`~verde.Vector2D.fit`).
+        Requires a fitted estimator (see :meth:`~verde.VectorSpline2D.fit`).
 
         Parameters
         ----------
@@ -219,9 +224,9 @@ class Vector2D(Spline):
             for datac, forcec in zip(coordinates, force_coordinates)
         )
         distance = np.hypot(east, north, dtype=dtype)
-        # The fudge factor helps avoid singular matrices when the force and
+        # The mindist factor helps avoid singular matrices when the force and
         # computation point are too close
-        distance += self.fudge
+        distance += self.mindist
         # Pre-compute common terms for the Green's functions of each component
         ln_r = (3 - self.poisson) * np.log(distance)
         over_r2 = (1 + self.poisson) / distance ** 2


Test patch:
diff --git a/verde/tests/test_vector.py b/verde/tests/test_vector.py
index 01a9097..b956388 100644
--- a/verde/tests/test_vector.py
+++ b/verde/tests/test_vector.py
@@ -8,7 +8,7 @@ import numpy.testing as npt
 
 from ..datasets.synthetic import CheckerBoard
 from ..coordinates import grid_coordinates
-from ..vector import Vector2D
+from ..vector import VectorSpline2D
 
 
 @pytest.fixture
@@ -23,7 +23,7 @@ def data2d():
 def test_vector2d(data2d):
     "See if the exact solution works"
     coords, data = data2d
-    spline = Vector2D().fit(coords, data)
+    spline = VectorSpline2D().fit(coords, data)
     npt.assert_allclose(spline.score(coords, data), 1)
     npt.assert_allclose(spline.predict(coords), data, rtol=1e-3)
     # There should be 1 force per data point
@@ -35,7 +35,7 @@ def test_vector2d(data2d):
 def test_vector2d_twice(data2d):
     "Check that the force coordinates are updated if fitting a second time"
     coords, data = data2d
-    spline = Vector2D().fit(coords, data)
+    spline = VectorSpline2D().fit(coords, data)
     npt.assert_allclose(spline.force_coords_, coords)
     coords2 = tuple(i * 1000 for i in coords)
     spline.fit(coords2, data)
@@ -47,7 +47,7 @@ def test_vector2d_weights(data2d):
     "Use unit weights and a regular grid solution"
     coords, data = data2d
     weights = tuple(np.ones_like(data[0]) for i in range(2))
-    spline = Vector2D(shape=(11, 11)).fit(coords, data, weights)
+    spline = VectorSpline2D(shape=(11, 11)).fit(coords, data, weights)
     npt.assert_allclose(spline.score(coords, data), 1, rtol=1e-3)
     npt.assert_allclose(spline.predict(coords), data, rtol=5e-2)
     assert spline.force_coords_[0].size == 11 * 11
@@ -57,7 +57,7 @@ def test_vector2d_weights(data2d):
 def test_vector2d_fails(data2d):
     "Should fail if not given 2 data components"
     coords, data = data2d
-    spline = Vector2D()
+    spline = VectorSpline2D()
     with pytest.raises(ValueError):
         spline.fit(coords, data[0])
     with pytest.raises(ValueError):