@Test(expectedExceptions = IllegalArgumentException.class) public void testDegreeToHigh1() { BasisFunctionKnots.fromUniform(0.0, 10.0, 11, 11); }
/** * Generate the i^th basis function * @param data Container for the knots and degree of the basis function * @param index The index (from zero) of the function. Must be in range 0 to data.getNumSplines() (exclusive) * For example if the degree is 1, and index is 0, this will cover the first three knots. * @return The i^th basis function */ protected Function<Double, Double> generate(BasisFunctionKnots data, final int index) { ArgChecker.notNull(data, "data"); ArgChecker.isTrue(index >= 0 && index < data.getNumSplines(), "index must be in range {} to {} (exclusive)", 0, data.getNumSplines()); return generate(data.getKnots(), data.getDegree(), index); }
@Test public void testThreeD() { BasisFunctionKnots knots1 = BasisFunctionKnots.fromInternalKnots(KNOTS, 2); BasisFunctionKnots knots2 = BasisFunctionKnots.fromInternalKnots(KNOTS, 3); BasisFunctionKnots knots3 = BasisFunctionKnots.fromInternalKnots(KNOTS, 1); List<Function<double[], Double>> set = GENERATOR.generateSet(new BasisFunctionKnots[] {knots1, knots2, knots3 }); //pick of one of the basis functions for testing int index = FunctionUtils.toTensorIndex(new int[] {3, 3, 3 }, new int[] {knots1.getNumSplines(), knots2.getNumSplines(), knots3.getNumSplines() }); Function<double[], Double> func = set.get(index); assertEquals(1. / 3., func.apply(new double[] {2.0, 2.0, 3.0 }), 0.0); }
/** * Generate a set of b-splines with a given polynomial degree on the specified knots. * @param knots holder for the knots and degree * @return a List of functions */ public List<Function<Double, Double>> generateSet(BasisFunctionKnots knots) { ArgChecker.notNull(knots, "knots"); double[] k = knots.getKnots(); List<Function<Double, Double>> set = null; for (int d = 0; d <= knots.getDegree(); d++) { set = generateSet(k, d, set); } return set; }
@Test public void testKnots() { BasisFunctionKnots knots = BasisFunctionKnots.fromKnots(KNOTS, 3); assertEquals(3, knots.getDegree()); assertEquals(11, knots.getNumKnots()); assertEquals(7, knots.getNumSplines()); ArrayAsserts.assertArrayEquals(KNOTS, knots.getKnots(), 1e-15); }
@Test public void testUniform() { BasisFunctionKnots knots = BasisFunctionKnots.fromUniform(1.0, 2.0, 10, 3); assertEquals(3, knots.getDegree()); assertEquals(16, knots.getNumKnots()); assertEquals(12, knots.getNumSplines()); }
@Test public void testInternalKnots() { BasisFunctionKnots knots = BasisFunctionKnots.fromInternalKnots(KNOTS, 2); assertEquals(2, knots.getDegree()); assertEquals(15, knots.getNumKnots()); assertEquals(12, knots.getNumSplines()); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testFunctionIndexOutOfRange2() { BasisFunctionKnots k = BasisFunctionKnots.fromKnots(KNOTS, 5); int nS = k.getNumSplines(); GENERATOR.generate(k, nS); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testNullInternalKnots() { BasisFunctionKnots.fromInternalKnots(null, 2); }
/** * Generate a set of N-dimensional b-splines as the produce of 1-dimensional b-splines with a given polynomial degree. * on the specified knots * @param knots holder for the knots and degree in each dimension * @return a List of functions */ public List<Function<double[], Double>> generateSet(BasisFunctionKnots[] knots) { ArgChecker.noNulls(knots, "knots"); int dim = knots.length; int[] nSplines = new int[dim]; int product = 1; List<List<Function<Double, Double>>> oneDSets = new ArrayList<>(dim); for (int i = 0; i < dim; i++) { oneDSets.add(generateSet(knots[i])); nSplines[i] = knots[i].getNumSplines(); product *= nSplines[i]; } final List<Function<double[], Double>> functions = new ArrayList<>(product); for (int i = 0; i < product; i++) { int[] indices = FunctionUtils.fromTensorIndex(i, nSplines); functions.add(generateMultiDim(oneDSets, indices)); } return functions; }
@Test(expectedExceptions = IllegalArgumentException.class) public void testWrongOrderKnots() { BasisFunctionKnots.fromKnots(WRONG_ORDER_KNOTS, 3); }
/** * 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); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testDegreeToHigh2() { BasisFunctionKnots.fromInternalKnots(KNOTS, 11); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testNullKnots() { BasisFunctionKnots.fromKnots(null, 2); }
/** * 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); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testWrongOrderUniform() { BasisFunctionKnots.fromUniform(2.0, 1.0, 10, 3); }
@Test public void testTwoD() { BasisFunctionKnots knots1 = BasisFunctionKnots.fromInternalKnots(KNOTS, 2); BasisFunctionKnots knots2 = BasisFunctionKnots.fromInternalKnots(KNOTS, 3); List<Function<double[], Double>> set = GENERATOR.generateSet(new BasisFunctionKnots[] {knots1, knots2 }); //pick of one of the basis functions for testing int index = FunctionUtils.toTensorIndex(new int[] {3, 3 }, new int[] {knots1.getNumSplines(), knots2.getNumSplines() }); Function<double[], Double> func = set.get(index); assertEquals(1. / 3., func.apply(new double[] {2.0, 2.0 }), 0.0); assertEquals(1. / 2., func.apply(new double[] {2.5, 2.0 }), 0.0); assertEquals(1. / 8. / 48., func.apply(new double[] {1.5, 3.5 }), 0.0); assertEquals(0.0, func.apply(new double[] {4.0, 2.5 }), 0.0); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testNegDegree2() { BasisFunctionKnots.fromInternalKnots(KNOTS, -1); }
@Test(expectedExceptions = IllegalArgumentException.class) public void testNegDegree() { BasisFunctionKnots.fromKnots(KNOTS, -1); }
/** * 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); }