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_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))
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))
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))
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)
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)
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))
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))
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))
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))
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,))
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, ))
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)
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)
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
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
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())
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())
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
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))
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"
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()
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