def test_ppoly(self):
        b = _make_random_spline()
        t, c, k = b.tck
        pp = PPoly.from_spline((t, c, k))

        xx = np.linspace(t[k], t[-k], 100)
        assert_allclose(b(xx), pp(xx), atol=1e-14, rtol=1e-14)
Exemple #2
0
    def test_antiderivative_vs_derivative(self):
        np.random.seed(1234)
        x = np.linspace(0, 1, 30)**2
        y = np.random.rand(len(x))
        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        for dx in range(0, 10):
            ipp = pp.antiderivative(dx)

            # check that derivative is inverse op
            pp2 = ipp.derivative(dx)
            assert_allclose(pp.c, pp2.c)

            # check continuity
            for k in range(dx):
                pp2 = ipp.derivative(k)

                r = 1e-13
                endpoint = r * pp2.x[:-1] + (1 - r) * pp2.x[1:]

                assert_allclose(pp2(pp2.x[1:]),
                                pp2(endpoint),
                                rtol=1e-7,
                                err_msg="dx=%d k=%d" % (dx, k))
Exemple #3
0
    def test_ppoly(self):
        b = _make_random_spline()
        t, c, k = b.tck
        pp = PPoly.from_spline((t, c, k))

        xx = np.linspace(t[k], t[-k], 100)
        assert_allclose(b(xx), pp(xx), atol=1e-14, rtol=1e-14)
    def test_integrate_ppoly(self):
        # test .integrate method to be consistent with PPoly.integrate
        x = [0, 1, 2, 3, 4]
        b = make_interp_spline(x, x)
        b.extrapolate = 'periodic'
        p = PPoly.from_spline(b)

        for x0, x1 in [(-5, 0.5), (0.5, 5), (-4, 13)]:
            assert_allclose(b.integrate(x0, x1), p.integrate(x0, x1))
Exemple #5
0
    def test_integrate_ppoly(self):
        # test .integrate method to be consistent with PPoly.integrate
        x = [0, 1, 2, 3, 4]
        b = make_interp_spline(x, x)
        b.extrapolate = 'periodic'
        p = PPoly.from_spline(b)

        for x0, x1 in [(-5, 0.5), (0.5, 5), (-4, 13)]:
            assert_allclose(b.integrate(x0, x1),
                            p.integrate(x0, x1))
Exemple #6
0
    def test_roots(self):
        x = np.linspace(0, 1, 31)**2
        y = np.sin(30*x)

        spl = splrep(x, y, s=0, k=3)
        pp = PPoly.from_spline(spl)

        r = pp.roots()
        r = r[(r >= 0) & (r <= 1)]
        assert_allclose(r, sproot(spl), atol=1e-15)
Exemple #7
0
    def test_roots(self):
        x = np.linspace(0, 1, 31)**2
        y = np.sin(30 * x)

        spl = splrep(x, y, s=0, k=3)
        pp = PPoly.from_spline(spl)

        r = pp.roots()
        r = r[(r >= 0) & (r <= 1)]
        assert_allclose(r, sproot(spl), atol=1e-15)
Exemple #8
0
    def test_from_spline(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0)
        pp = PPoly.from_spline(spl)

        xi = np.linspace(0, 1, 200)
        assert_allclose(pp(xi), splev(xi, spl))
Exemple #9
0
    def test_from_spline(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0)
        pp = PPoly.from_spline(spl)

        xi = np.linspace(0, 1, 200)
        assert_allclose(pp(xi), splev(xi, spl))
Exemple #10
0
    def test_derivative_eval(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0)
        pp = PPoly.from_spline(spl)

        xi = np.linspace(0, 1, 200)
        for dx in range(0, 3):
            assert_allclose(pp(xi, dx), splev(xi, spl, dx))
Exemple #11
0
    def test_derivative_eval(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0)
        pp = PPoly.from_spline(spl)

        xi = np.linspace(0, 1, 200)
        for dx in range(0, 3):
            assert_allclose(pp(xi, dx), splev(xi, spl, dx))
Exemple #12
0
    def test_derivative(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        xi = np.linspace(0, 1, 200)
        for dx in range(0, 10):
            assert_allclose(pp(xi, dx), pp.derivative(dx)(xi),
                            err_msg="dx=%d" % (dx,))
Exemple #13
0
    def test_derivative(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        xi = np.linspace(0, 1, 200)
        for dx in range(0, 10):
            assert_allclose(pp(xi, dx),
                            pp.derivative(dx)(xi),
                            err_msg="dx=%d" % (dx, ))
Exemple #14
0
    def test_antiderivative_vs_spline(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        for dx in range(0, 10):
            pp2 = pp.antiderivative(dx)
            spl2 = splantider(spl, dx)

            xi = np.linspace(0, 1, 200)
            assert_allclose(pp2(xi), splev(xi, spl2), rtol=1e-7)
Exemple #15
0
    def test_antiderivative_vs_spline(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        for dx in range(0, 10):
            pp2 = pp.antiderivative(dx)
            spl2 = splantider(spl, dx)

            xi = np.linspace(0, 1, 200)
            assert_allclose(pp2(xi), splev(xi, spl2),
                            rtol=1e-7)
Exemple #16
0
def spline(y, dx, degree=3):
    """Spline fit a given scalar function.

  Args:
    y: The values of the scalar function evaluated on points starting at zero
    with the interval dx.
    dx: The interval at which the scalar function is evaluated.
    degree: Polynomial degree of the spline fit.

  Returns:
    A function that computes the spline function.
  """
    num_points = len(y)
    dx = f32(dx)
    x = np.arange(num_points, dtype=f32) * dx
    # Create a spline fit using the scipy function.
    fn = splrep(x, y, s=0,
                k=degree)  # Turn off smoothing by setting s to zero.
    params = PPoly.from_spline(fn)
    # Store the coefficients of the spline fit to an array.
    coeffs = np.array(params.c)

    def spline_fn(x):
        """Evaluates the spline fit for values of x."""
        ind = np.array(x / dx, dtype=np.int64)
        # The spline is defined for x values between 0 and largest value of y. If x
        # is outside this domain, truncate its ind value to within the domain.
        truncated_ind = np.array(
            np.where(ind < num_points, ind, num_points - 1), np.int64)
        truncated_ind = np.array(
            np.where(truncated_ind >= 0, truncated_ind, 0), np.int64)
        result = np.array(0, x.dtype)
        dX = x - np.array(ind, np.float32) * dx
        for i in range(degree +
                       1):  # sum over the polynomial terms up to degree.
            result = result + np.array(coeffs[degree - i, truncated_ind + 2],
                                       x.dtype) * dX**np.array(i, x.dtype)
        # For x values that are outside the domain of the spline fit, return zeros.
        result = np.where(ind < num_points, result, np.array(0.0, x.dtype))
        return result

    return spline_fn
Exemple #17
0
def pp_from_bspline(coeffs, knots, order):
    """
    Return a piecewise polynomial from a BSpline representation.

    Parameters
    ----------
    coeffs : ndarray, shape (M, D)
        Bspline coefficients for the `M` B-splines parameterizing
        `D` dimensions.
    knots : ndarray, shape (N,)
        B-spline knots. The knots must have the full endpoint multiplicity.
        Zero-pad spline coefficients if needed.
    order : int
        Order of the B-spline.

    Returns
    -------
    coeffs_pp : ndarray, shape (K, P, D)
        Coefficients of the piecewise polynomial where `K` is the order
        (``order``), `P` is the number of pieces and `D` is the dimension.
    breaks : ndarray, shape (P+1,)
        Break points of the piecewise polynomial.

    """

    degree = order - 1
    breaks = np.unique(knots)
    pieces = breaks.size - 1
    dim = coeffs.shape[-1]

    coeffs_pp = np.empty((order, pieces, dim))

    # have to do it manually for each dimension
    for d in range(dim):

        bs = BSpline(knots, coeffs[:, d], degree)
        pp = PPoly.from_spline(bs, extrapolate=False)

        # remove endpoint multiplicities
        coeffs_pp[:, :, d] = pp.c[:, degree:(degree+pieces)]

    return coeffs_pp, breaks
Exemple #18
0
    def test_integrate(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        a, b = 0.3, 0.9
        ig = pp.integrate(a, b)

        ipp = pp.antiderivative()
        assert_allclose(ig, ipp(b) - ipp(a))
        assert_allclose(ig, splint(a, b, spl))

        a, b = -0.3, 0.9
        ig = pp.integrate(a, b, extrapolate=True)
        assert_allclose(ig, ipp(b) - ipp(a))

        assert_(np.isnan(pp.integrate(a, b, extrapolate=False)).all())
Exemple #19
0
    def test_integrate(self):
        np.random.seed(1234)
        x = np.sort(np.r_[0, np.random.rand(11), 1])
        y = np.random.rand(len(x))

        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        a, b = 0.3, 0.9
        ig = pp.integrate(a, b)

        ipp = pp.antiderivative()
        assert_allclose(ig, ipp(b) - ipp(a))
        assert_allclose(ig, splint(a, b, spl))

        a, b = -0.3, 0.9
        ig = pp.integrate(a, b, extrapolate=True)
        assert_allclose(ig, ipp(b) - ipp(a))

        assert_(np.isnan(pp.integrate(a, b, extrapolate=False)).all())
Exemple #20
0
def bspline(spl):
    """
    Convert SciPy B-spline to :class:`PiecewisePolynomial` parameters.

    Parameters
    ----------
    spl : tuple or BSpline or UnivariateSpline
        ``scipy.interpolate`` B-spline representation, such as ``splrep()``
        results, ``BSpline`` object (result of ``make_interp_spline()``, for
        example) or ``UnivariateSpline`` object

    Returns
    -------
    ranges : list of tuple
        list of parameters ``(r_min, r_max, coeffs, r_0)`` that can be passed
        directly to :class:`PiecewisePolynomial` or, after “multiplication” by
        :class:`Angular`, to :class:`PiecewiseSPolynomial`
    """
    if isinstance(spl, UnivariateSpline):
        # extract necessary data, convert to compatible format
        knots = spl.get_knots()
        coeffs = spl.get_coeffs()
        k = len(coeffs) - len(knots) + 1
        knots = np.pad(knots, k, 'edge')
        spl = (knots, coeffs, k)

    # convert B-spline representation to piecewise polynomial representation
    ppoly = PPoly.from_spline(spl)
    x = ppoly.x  # breakpoints
    c = ppoly.c.T[:, ::-1]  # coefficients (PPoly degrees are descending)

    # convert to PiecewisePolynomial ranges
    ranges = []
    for i in range(len(x) - 1):
        r_min, r_max = x[i], x[i + 1]
        if r_min != r_max:  # (some PPoly intervals are degenerate)
            ranges.append((r_min, r_max, c[i], r_min))
    return ranges
Exemple #21
0
    def test_antiderivative_vs_derivative(self):
        np.random.seed(1234)
        x = np.linspace(0, 1, 30)**2
        y = np.random.rand(len(x))
        spl = splrep(x, y, s=0, k=5)
        pp = PPoly.from_spline(spl)

        for dx in range(0, 10):
            ipp = pp.antiderivative(dx)

            # check that derivative is inverse op
            pp2 = ipp.derivative(dx)
            assert_allclose(pp.c, pp2.c)

            # check continuity
            for k in range(dx):
                pp2 = ipp.derivative(k)

                r = 1e-13
                endpoint = r*pp2.x[:-1] + (1 - r)*pp2.x[1:]

                assert_allclose(pp2(pp2.x[1:]), pp2(endpoint),
                                rtol=1e-7, err_msg="dx=%d k=%d" % (dx, k))
Exemple #22
0
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import PPoly, splrep, splev

# Data points
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 4, 0, 2, 2]

# Fit B-spline. This is a bunch of cubic polinomials that blend well (C^2)
# Calls fortran's FITPACK under the hood
tck = splrep(x, y)
# Make piecewise polynomial from the B-spline
pp = PPoly.from_spline(tck)

# Some plotting to show how the coefficients are stored in the PPoly.
# Basically columns in pp.c are coefficients of [x^3, x^2, x, 1]
# and pp.x contains the bounds for each polynomial. The complete thing
# consists of evaluating the polynomial in pp.c[:, i] in the range pp.x[i:i+1]

xx = np.linspace(0, max(x))
yy = splev(xx, tck)  # Also evaluates derivatives by setting der=desired order

lw = 20
for i in range(len(pp.c.T)):
    plt.plot(xx, np.poly1d(pp.c[:, i])(xx - pp.x[i]), 'k', alpha=0.3, lw=lw)
    lw -= 2
plt.ylim(-1, 5)
plt.plot(xx, yy)
plt.show()
def bspline_penalty_matrix_optimized(
        linear_operator: LinearDifferentialOperator, basis: BSpline):

    coefs = linear_operator.constant_weights()
    if coefs is None:
        return NotImplemented

    nonzero = np.flatnonzero(coefs)

    # All derivatives above the order of the spline are effectively
    # zero
    nonzero = nonzero[nonzero < basis.order]

    if len(nonzero) == 0:
        return np.zeros((basis.n_basis, basis.n_basis))

    # We will only deal with one nonzero coefficient right now
    if len(nonzero) != 1:
        return NotImplemented

    derivative_degree = nonzero[0]

    if derivative_degree == basis.order - 1:
        # The derivative of the bsplines are constant in the intervals
        # defined between knots
        knots = np.array(basis.knots)
        mid_inter = (knots[1:] + knots[:-1]) / 2
        basis_deriv = basis.derivative(order=derivative_degree)
        constants = basis_deriv(mid_inter)[..., 0].T
        knots_intervals = np.diff(basis.knots)
        # Integration of product of constants
        return constants.T @ np.diag(knots_intervals) @ constants

    # We only deal with the case without zero length intervals
    # for now
    if np.any(np.diff(basis.knots) == 0):
        return NotImplemented

    # Compute exactly using the piecewise polynomial
    # representation of splines

    # Places m knots at the boundaries
    knots = basis._evaluation_knots()

    # c is used the select which spline the function
    # PPoly.from_spline below computes
    c = np.zeros(len(knots))

    # Initialise empty list to store the piecewise polynomials
    ppoly_lst = []

    no_0_intervals = np.where(np.diff(knots) > 0)[0]

    # For each basis gets its piecewise polynomial representation
    for i in range(basis.n_basis):

        # Write a 1 in c in the position of the spline
        # transformed in each iteration
        c[i] = 1

        # Gets the piecewise polynomial representation and gets
        # only the positions for no zero length intervals
        # This polynomial are defined relatively to the knots
        # meaning that the column i corresponds to the ith knot.
        # Let the ith knot be a
        # Then f(x) = pp(x - a)
        pp = PPoly.from_spline((knots, c, basis.order - 1))
        pp_coefs = pp.c[:, no_0_intervals]

        # We have the coefficients for each interval in coordinates
        # (x - a), so we will need to subtract a when computing the
        # definite integral
        ppoly_lst.append(pp_coefs)
        c[i] = 0

    # Now for each pair of basis computes the inner product after
    # applying the linear differential operator
    penalty_matrix = np.zeros((basis.n_basis, basis.n_basis))
    for interval in range(len(no_0_intervals)):
        for i in range(basis.n_basis):
            poly_i = np.trim_zeros(ppoly_lst[i][:, interval], 'f')
            if len(poly_i) <= derivative_degree:
                # if the order of the polynomial is lesser or
                # equal to the derivative the result of the
                # integral will be 0
                continue
            # indefinite integral
            derivative = polyder(poly_i, derivative_degree)
            square = polymul(derivative, derivative)
            integral = polyint(square)

            # definite integral
            penalty_matrix[i, i] += np.diff(
                polyval(
                    integral, basis.knots[interval:interval + 2] -
                    basis.knots[interval]))[0]

            for j in range(i + 1, basis.n_basis):
                poly_j = np.trim_zeros(ppoly_lst[j][:, interval], 'f')
                if len(poly_j) <= derivative_degree:
                    # if the order of the polynomial is lesser
                    # or equal to the derivative the result of
                    # the integral will be 0
                    continue
                    # indefinite integral
                integral = polyint(
                    polymul(polyder(poly_i, derivative_degree),
                            polyder(poly_j, derivative_degree)))
                # definite integral
                penalty_matrix[i, j] += np.diff(
                    polyval(
                        integral, basis.knots[interval:interval + 2] -
                        basis.knots[interval]))[0]
                penalty_matrix[j, i] = penalty_matrix[i, j]
    return penalty_matrix
data_y = 0.2 + np.exp(-(data_x**2)) + 0.1 * np.random.randn(50)

# Smoothing factor for UnivariateSpline. Tuned to give four knots
smoothing_factor = 0.8

# Time vector for simulation
t_sim = np.arange(-2.9, 3.1, 0.1)

###############################################################################
# STATEMENTS

# fit a spline
spl = UnivariateSpline(data_x, data_y, s=smoothing_factor)

# getting a piecewise polynomial from the spline
p_spl = PPoly.from_spline(spl._eval_args)

p_x = p_spl.x[3:-3]
p_c = p_spl.c[:, 3:-3]

# Creating piecewise function from spline
triplet = zip(range(1, p_x.shape[0]), p_x[:-1], np.roll(p_x, -1)[:-1])
body = ""
for i, lb, ub in triplet:
    if i == 1:
        body += f"        if ((x >= {lb}) && (x <= {ub}))\n"
        body += (
            f"            y = piecewise_point(x, {lb}, {i}, k, c_dim0, c_dim1, c);\n"
        )
    elif i == (p_x.shape[0] - 1):
        body += f"        else  // ((x > {lb}) && (x <= {ub}))\n"
Exemple #25
0
    def _gram_matrix(self):
        # Places m knots at the boundaries
        knots = self._evaluation_knots()

        # c is used the select which spline the function
        # PPoly.from_spline below computes
        c = np.zeros(len(knots))

        # Initialise empty list to store the piecewise polynomials
        ppoly_lst = []

        no_0_intervals = np.where(np.diff(knots) > 0)[0]

        # For each basis gets its piecewise polynomial representation
        for i in range(self.n_basis):

            # Write a 1 in c in the position of the spline
            # transformed in each iteration
            c[i] = 1

            # Gets the piecewise polynomial representation and gets
            # only the positions for no zero length intervals
            # This polynomial are defined relatively to the knots
            # meaning that the column i corresponds to the ith knot.
            # Let the ith knot be a
            # Then f(x) = pp(x - a)
            pp = PPoly.from_spline((knots, c, self.order - 1))
            pp_coefs = pp.c[:, no_0_intervals]

            # We have the coefficients for each interval in coordinates
            # (x - a), so we will need to subtract a when computing the
            # definite integral
            ppoly_lst.append(pp_coefs)
            c[i] = 0

        # Now for each pair of basis computes the inner product after
        # applying the linear differential operator
        matrix = np.zeros((self.n_basis, self.n_basis))

        for interval in range(len(no_0_intervals)):
            for i in range(self.n_basis):
                poly_i = np.trim_zeros(ppoly_lst[i][:, interval], 'f')
                # Indefinite integral
                square = polymul(poly_i, poly_i)
                integral = polyint(square)

                # Definite integral
                matrix[i, i] += np.diff(
                    polyval(
                        integral, self.knots[interval:interval + 2] -
                        self.knots[interval]))[0]

                # The Gram matrix is banded, so not all intervals are used
                for j in range(i + 1, min(i + self.order, self.n_basis)):
                    poly_j = np.trim_zeros(ppoly_lst[j][:, interval], 'f')

                    # Indefinite integral
                    integral = polyint(polymul(poly_i, poly_j))

                    # Definite integral
                    matrix[i, j] += np.diff(
                        polyval(
                            integral, self.knots[interval:interval + 2] -
                            self.knots[interval]))[0]

                    # The matrix is symmetric
                    matrix[j, i] = matrix[i, j]

        return matrix
from scipy.interpolate import PPoly
from scipy.interpolate import splev, splrep

W = np.array([-2.0, -2.5, -3.7538003, -5.00760061, -5.50760061])

tvec = np.linspace(0, 1, W.shape[0])

W = np.hstack((W[0], W[0], W[:], W[-1] - 0.1, W[-1] - 0.2))
tvec = np.hstack((-0.1, -0.05, tvec, 1.05, 1.1))

tck = splrep(tvec, W, s=0, k=3)

x2 = np.linspace(0, 1, 100)
# x2 = np.linspace(tvec[0], tvec[-1], 100)
y2 = splev(x2, tck)

print splev(0, tck)
print splev(0, tck, der=1)
print splev(1, tck, der=1)

poly = PPoly.from_spline(tck)
dpoly = poly.derivative(1)
ddpoly = poly.derivative(2)

[B, idx] = np.unique(poly.x, return_index=True)
coeff = poly.c[:, idx]


plt.plot(tvec, W, "o", x2, y2)
plt.show()
Exemple #27
0
    def get_coefficients(self, rcuts=None, powers=None, spl_dr=0.1, rmins=None,
                         mode='exp_spline', atomic_energies={}, kBT=0.1,
                         force_scaling=0.1, plot=True, fit_constant='formula',
                         weight_distribution=1., empirical_formula=True):
        ''' Performs a least-squares fit of polynomial or spline coefficients
        based on the previously registered fitting structure database.

        Returns a dictionary containing the polynomial or spline coefficients,
        the (optional) constants energy shifts for each stoichiometry,
        as well as the residuals and the total residual. Also an 'skf_txt'
        entry is included, which corresponds to the line(s) to be used
        in SKF files. Note that in 'exp_poly' and 'exp_spline' modes,
        the fitted polynomial and spline based potentials are subsequently
        mapped onto (high-resolution) cubic splines for compatibility with
        the SKF format (including the initial exponentially decaying
        repulsion).

        rcuts: dictionary with the cutoff radii for each
               (non-redundant) element pair to be included in the fit.
               If a cutoff is None, the corresponding pair will be
               omitted from the fit (i.e. zero repulsive interaction)
        powers: dictionary with the range of powers for each
               (non-redundant) element pair to be included in the fit.
        spl_dr: desired length of the spline segments (in 'exp_spline'
                mode) in Angstrom.
        rmins: dictionary with distances below which the potentials
               switch to the exponential form (see main.py)
        mode: 'poly', 'exp_poly' or 'exp_spline': the functional form
               of the repulsive potentials.
        atomic_energies: reference DFT and DFTB energies of the
                         isolated atoms. Mandatory.
        kBT: energy to be used in the Boltzmann weighting of the per-atom
             cohesive energies.
        force_scaling: scaling factor (in distance units) to
                       apply to the forces.
        fit_constant: about inclusion of constant energy shifts
                      (see main.py)
        nInt: number of knots for the cubic splines
        plot: whether to plot the resulting repulsive potentials
        weight_distribution: parameter in between 0 and 1, relevant
            when the fitting structures have different stoichiometries.
            If 0, the Boltzmann weights for each stoichiometry are
            calculated independently, so that the most stable structures
            of each stoichiometry will receive the same (and highest)
            weights. If 1, all structures get weighted on the "same"
            cohesive-energy-per-atom scale. Values in between 0 and 1
            are allowed and represent intermediate schemes.
        empirical_formula: whether to divide the stoichiometries
           by their greatest common divisor. If True, a database with
           e.g. 'Ti4O8' and 'Ti6O12' stoichiometries will have
           only one unique stoichiometry (i.e. 'TiO2').
           This influences the calculation of the Boltzmann weights.
        '''
        assert mode in ['exp_spline', 'exp_poly', 'poly']

        for pair in list(rcuts.keys()):
            if rcuts[pair] is None:
                del rcuts[pair]
                continue
            if 'exp' in mode:
                assert pair in self.exp_rep or pair in rmins, \
                       'Missing information on pair: %s' % pair

        pairs = sorted(rcuts.keys())
        assert all([pair in powers for pair in pairs])

        N = len(self.structures)
        N += 3 * sum([len(atoms) for atoms in self.structures])
        N += len(pairs) if 'exp' in mode and rmins is None else 0

        results = {}

        if 'poly' in mode:
            cb = PolynomialCoefficientsBuilder(rcuts, powers, N, mode)
            regularization = 1e-10

        elif 'spline' in mode:
            orders = np.unique([max(powers[pair]) for pair in pairs])
            assert len(orders) == 1
            order = orders[0]

            knots = {} 
            for pair in pairs:
                rmin = self.exp_rep_r0[pair] if rmins is None else rmins[pair]
                knots[pair] = [rmin]
                num = int(np.floor((rcuts[pair] - rmin) / spl_dr))
                knots[pair] += list(np.linspace(rmin, rcuts[pair], num=num,
                                                endpoint=True))
                if self.verbose:
                    items = (pair, order, ' '.join(map(str, knots[pair])))
                    print('Pair %s: order = %d   knots = %s' % items)

            results['order'] = order
            results['knots'] = knots
            cb = SplineCoefficientsBuilder(knots, order, N, mode)
            regularization = 1e-6

        # Fit the polynomial or spline coefficients
        args = (cb, rmins, atomic_energies, kBT, force_scaling, fit_constant,
                weight_distribution)
        kwargs = {'regularization': regularization,
                  'empirical_formula': empirical_formula}
        coeffs, constants, residual, residuals = self._solve(*args, **kwargs)
        results['coeffs'] = coeffs
        results['constants'] = constants
        results['residual'] = residual

        # Construct the text that should either become the 2nd line
        # in the SKF file (in 'poly' mode) or the Spline section (in
        # 'exp_poly' or 'exp_spline' mode)
        results['skf_txt'] = {}

        if mode == 'poly':
            for pair, coeffs in results['coeffs'].items():
                elements = pair.split('-')
                if elements[0] == elements[1]:
                    line = '%.4f ' % atomic_masses[atomic_numbers[elements[0]]]
                else:
                    line = '0.0000 '

                for p in range(2, 10):
                    if p in powers[pair]:
                        index = powers[pair].index(p)
                        c = coeffs[index] 
                        c *= (Bohr ** p) / Hartree
                        line += ' %.8f' % c
                    else:
                        line += ' 0.0'

                line += ' %.8f 10*0.0' % (rcuts[pair] / Bohr)
                results['skf_txt'][pair] = line

        else:
            results['PPoly'] = {}
            if rmins is not None:
                self.exp_rep_r0 = {k: v for k, v in rmins.items()}
                self.exp_rep = {}

            for pair, coeffs in results['coeffs'].items():
                r0 = self.exp_rep_r0[pair]
                rc = rcuts[pair]

                x = np.linspace(r0, rc, endpoint=True, num=1000)
                y = np.zeros_like(x)

                if mode == 'exp_poly':
                    for i, c in enumerate(coeffs):
                        p = powers[pair][i]
                        y += c * ((rc - x) ** p)

                elif mode == 'exp_spline':
                    knots = results['knots'][pair]
                    for k in range(len(knots) - 1):
                        select = knots[k] <= x
                        select *= knots[k + 1] > x
                        rr = x[select] - knots[k]
                        for i, c in enumerate(coeffs[k]):
                            y[select] += c * (rr ** i)

                num = 100
                degree = 3  # SKF format only allows cubic splines
                knots = np.linspace(r0 + 0.05, rcuts[pair] - 0.05,
                                    endpoint=True, num=num)
                tck = splrep(x, y, t=knots, k=degree)
                pp = PPoly.from_spline(tck)
                knots = pp.x[degree:-degree]
                coeffs = pp.c[:, degree:-degree][::-1]

                results['PPoly'][pair] = pp
                elements = pair.split('-')

                if rmins is not None:
                    # fit the exponential parameters to the spline or poly
                    derivs = [pp(r0, nu=nu) for nu in range(3)]
                    if derivs[1] > 0:
                        msg = 'Pair %s -- warning: 1st derivative is ' % pair
                        msg += 'postive at r0=%.3f; setting it to -100' % r0
                        print(msg)
                        derivs[1] = -100.

                    if derivs[2] < 0:
                        msg = 'Pair %s -- warning: 2nd derivative is ' % pair
                        msg += 'negative at r0=%.3f; setting it to 25' % r0
                        print(msg)
                        derivs[2] = 25.

                    a1 = derivs[2] * -1. / derivs[1]
                    xi = derivs[1] * -1. / a1
                    a2 = np.log(xi) + a1 * r0
                    a3 = derivs[0] - xi
                    self.exp_rep[pair] = [a1, a2, a3]

                lines = '\nSpline\n'
                lines += '%d %.8f\n' % (num + 1, rcuts[pair] / Bohr) 

                a1 = self.exp_rep[pair][0] / (Bohr ** -1)
                a2 = self.exp_rep[pair][1] - np.log(Hartree)
                a3 = self.exp_rep[pair][2] / Hartree
                lines += '%.8f %.8f %.8f\n' % (a1, a2, a3)

                line = ''
                order = np.shape(coeffs)[0]
                conversion = np.array([(Bohr ** i) / Hartree
                                       for i in range(order)])
                for i in range(len(knots)-1):
                    coeff = coeffs[:, i] * conversion
                    line += '%.8f %.8f ' % (knots[i] / Bohr, knots[i+1] / Bohr)
                    line += ' '.join(['%.8f' % c for c in coeff])
                    if i == len(knots)-2:
                        dr = (knots[i+1] - knots[i]) / Bohr
                        y0 = sum([c * (dr ** j) for j, c in enumerate(coeff)])
                        y1 = sum([j * c * (dr ** (j - 1))
                                  for j, c in enumerate(coeff)])
                        c4 = (y1 * dr - 5 * y0) / (dr ** 4)
                        c5 = (-y1 * dr + 4 * y0) / (dr ** 5)
                        line += ' %.8f %.8f' % (c4, c5)
                    line += '\n'

                lines += line
                results['skf_txt'][pair] = lines

        # Print out results summary
        if self.verbose:
            print('Residual sum of squares:', results['residual'])
            for k, v in results['constants'].items():
                print('%s %s : fitted constant term = %.6f' % \
                      (fit_constant.title(), k, v))

            for pair, coeff in results['coeffs'].items():
                print('Pair:', pair)
                print('Fitted coefficients:')
                print(coeff)

                if 'poly' in mode:
                    allcoeff = np.zeros(max(powers[pair]) + 1)
                    for i,pwr in enumerate(powers[pair]):
                        allcoeff[pwr] = coeff[i]

                    rc = rcuts[pair]
                    roots = rc - np.roots(allcoeff[::-1])
                    if 'exp' in mode:
                        r0str = ', r0=%.4f' % self.exp_rep_r0[pair]
                    else:
                        r0str = ''

                    print('Roots (cutoff=%.4f%s):' % (rc, r0str))
                    print(roots)

        # Plot the repulsive potentials
        if plot:
            gray = '0.3'
            for pair, coeff in results['coeffs'].items():
                r0 = self.exp_rep_r0[pair] if 'exp' in mode else 0.5
                rc = rcuts[pair]
                output = '%s.pdf' % pair
                startx = r0 - 0.05
                x = np.arange(startx, rc, 0.01)
                y = np.zeros_like(x)

                if 'poly' in mode:
                    label = 'Fitted polynomial'
                    for i, c in enumerate(coeff):
                        pwr = powers[pair][i]
                        y += c * ((rc - x) ** pwr)
                else:
                    label = 'Fitted spline'
                    y = results['PPoly'][pair](x)

                plt.plot(x, y, 'r-', lw=2, label=label)

                xlim = [r0 - 0.1, rc + 0.1]
                ymin = -2. if np.min(y) < 0 else 0
                ymax = np.max(y)
                if 'exp' in mode:
                    er0 = exponential(r0, *self.exp_rep[pair])
                    ymax = max(ymax, er0)
                ymax *= 1.05

                plt.plot([rc, rc], [ymin, ymax], '--', color=gray, lw=2)
                plt.plot(xlim, [0, 0], '--', color=gray, lw=2)
                plt.xlim(xlim)
                plt.ylim([ymin, ymax])

                if 'exp' in mode:
                    if mode == 'exp_poly':
                        y = results['PPoly'][pair](x)
                        label = 'Polynomial mapped on cubic spline'
                        plt.plot(x, y, 'b--', lw=2, label=label)
                    elif mode == 'exp_spline':
                        x = results['knots'][pair]
                        y = list(results['coeffs'][pair][:, 0]) + [0.]
                        label = 'Spline knots'
                        plt.plot(x, y, 'x', color=gray, mew=2, label=label)

                    x = np.arange(startx, r0 + 0.1, 0.01)
                    y = exponential(x, *self.exp_rep[pair])
                    plt.plot(x, y, 'k-', lw=2, label='Exponential')
                    plt.plot([r0, r0], [ymin, ymax], '--', color=gray, lw=2)

                plt.grid()
                plt.xlabel('r (Angstrom)')
                plt.ylabel('Vrep (eV)')
                plt.legend(loc='upper right')
                plt.savefig(output)
                plt.clf()

        return results