/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.math.impl.interpolation; import com.opengamma.strata.collect.ArgChecker; /** * Helper class to hold the knots and polynomial degree that specify a set of basis functions */ public final class BasisFunctionKnots { private final double[] _knots; private final int _degree; private final int _nSplines; /** * Generate knots uniformly in the range xa and xb and knots outside this range to support the basis functions on * the edge of the range * @param xa start of the range * @param xb end of the range * @param nKnots number of knots in the range (internal knots) * @param degree the polynomial degree of the basis functions (this will determine how many external knots are required) * @return a BasisFunctionKnots instance */ public static BasisFunctionKnots fromUniform(double xa, double xb, int nKnots, int degree) { ArgChecker.isTrue(xb > xa, "Require xb > xa, values are xa = {}, xb = {}", xa, xb); ArgChecker.notNegative(degree, "degree"); ArgChecker.isTrue(nKnots - degree > 0, "Require at least {} knots for degree {}, only given {}", degree + 1, degree, nKnots); int nTotalKnots = nKnots + 2 * degree; // this is the total number of knots, including those outside the range int nSplines = nKnots + degree - 1; double[] knots = new double[nTotalKnots]; double dx = (xb - xa) / (nKnots - 1); // knots to the left and right of the range for (int i = 0; i < degree; i++) { knots[i] = (i - degree) * dx + xa; knots[degree + nKnots + i] = xb + dx * (i + 1); } // knots in the main range for (int i = 0; i < nKnots - 1; i++) { knots[i + degree] = xa + i * dx; } knots[nKnots + degree - 1] = xb; return new BasisFunctionKnots(knots, degree, nSplines); } /** * Generate a set of knots capable of supporting the given degree of basis functions. The given knots are used inside * the range, with knots generated outside this range to support the basis functions on the edge of the range * @param internalKnots the internal knots. The start of the range is the first knot and the end is the last. * @param degree the polynomial degree of the basis functions (this will determine how many external knots are required) * @return a BasisFunctionKnots instance */ public static BasisFunctionKnots fromInternalKnots(double[] internalKnots, int degree) { ArgChecker.notEmpty(internalKnots, "knots"); ArgChecker.notNegative(degree, "degree"); int nInterKnots = internalKnots.length; ArgChecker.isTrue(nInterKnots - degree > 0, "Require at least {} knots for degree {}, only given {}", degree + 1, degree, nInterKnots); // check knots are ascending for (int i = 1; i < nInterKnots; i++) { ArgChecker.isTrue(internalKnots[i] - internalKnots[i - 1] > 0, "knots are not ascending"); } int nSplines = nInterKnots + degree - 1; int nTotalKnots = nInterKnots + 2 * degree; // add in extra knots outside the range to handle basis functions on the edge double[] knots = new double[nTotalKnots]; double dxa = internalKnots[1] - internalKnots[0]; double dxb = internalKnots[nInterKnots - 1] - internalKnots[nInterKnots - 2]; // knots to the left and right of the range for (int i = 0; i < degree; i++) { knots[i] = (i - degree) * dxa + internalKnots[0]; knots[degree + nInterKnots + i] = internalKnots[nInterKnots - 1] + dxb * (i + 1); } // knots in the main range System.arraycopy(internalKnots, 0, knots, degree, nInterKnots); return new BasisFunctionKnots(knots, degree, nSplines); } /** * Generate a set of knots capable of supporting the given degree of basis functions. All the knots, including those * outside the range must be supplied - the first and last degree knots are outside the range (e.g. for degree = 2, the * first and last two knots are out side the range and exist to support the basis functions on the edge of the range. * @param knots The total set of knots - must be strictly acceding * @param degree the polynomial degree of the basis functions * @return a BasisFunctionKnots instance */ public static BasisFunctionKnots fromKnots(double[] knots, int degree) { ArgChecker.notEmpty(knots, "knots"); ArgChecker.notNegative(degree, "degree"); int nKnots = knots.length; ArgChecker.isTrue(nKnots - 3 * degree > 0, "Require at least {} knots for degree {}, only given {}", 3 * degree + 1, degree, nKnots); // check knots are ascending for (int i = 1; i < nKnots; i++) { ArgChecker.isTrue(knots[i] - knots[i - 1] > 0, "knots are not ascending"); } int nSplines = nKnots - degree - 1; return new BasisFunctionKnots(knots, degree, nSplines); } private BasisFunctionKnots(double[] knots, int degree, int nSplines) { _knots = knots; _degree = degree; _nSplines = nSplines; } /** * Get the full set of knots * @return the knots */ public double[] getKnots() { return _knots.clone(); } /** * The number of knots * @return number of knots */ public int getNumKnots() { return _knots.length; } /** * the polynomial degree of the basis functions * @return the degree */ public int getDegree() { return _degree; } /** * The number of basis splines of the degree this set of knots will support * @return number of splines */ public int getNumSplines() { return _nSplines; } }