/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.math.impl.interpolation; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.math.impl.FunctionUtils; /** * Generator for a set of basis functions */ public class BasisFunctionGenerator { /** * Generates a set of b-splines with knots a fixed distance apart * @param xa minimum value of the function domain * @param xb maximum value of the function domain * @param nKnots number of internal knots (minimum of degree + 1) * @param degree The order of the polynomial splines * @return a List of functions * @deprecated Use generateSet(BasisFunctionKnots knots) */ @Deprecated public List<Function<Double, Double>> generateSet(final double xa, final double xb, final int nKnots, final int degree) { // args are checked in the constructor below BasisFunctionKnots k = BasisFunctionKnots.fromUniform(xa, xb, nKnots, degree); return generateSet(k); } /** * Generates a set of b-splines a given polynomial degree on the specified knots * @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 List of functions * @deprecated Use generateSet(BasisFunctionKnots knots) */ @Deprecated public List<Function<Double, Double>> generateSet(final double[] internalKnots, final int degree) { // args are checked in the constructor below BasisFunctionKnots k = BasisFunctionKnots.fromInternalKnots(internalKnots, degree); return generateSet(k); } /** * 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; } /** * 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; } /** * Generate a set of N-dimensional b-splines as the produce of 1-dimensional b-splines with a given polynomial degree * @param xa minimum value of the function domain in each dimension * @param xb maximum value of the function domain in each dimension * @param nKnots number of internal knots (minimum of degree + 1) in each dimension * @param degree The order of the polynomial splines in each dimension * @return a List of functions * @deprecated use generateSet(BasisFunctionKnots[] knots) */ @Deprecated public List<Function<double[], Double>> generateSet(final double[] xa, final double[] xb, final int[] nKnots, final int[] degree) { final int dim = xa.length; ArgChecker.isTrue(dim == xb.length, "xb wrong dimension"); ArgChecker.isTrue(dim == nKnots.length, "nKnots wrong dimension"); ArgChecker.isTrue(dim == degree.length, "degree wrong dimension"); BasisFunctionKnots[] knots = new BasisFunctionKnots[dim]; for (int i = 0; i < dim; i++) { knots[i] = BasisFunctionKnots.fromUniform(xa[i], xb[i], nKnots[i], degree[i]); } return generateSet(knots); } /** * 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); } /** * Generate the n-dimensional basis function for the given index position. This is formed as the product of 1-d basis * functions. * @param oneDSets The sets of basis functions in each dimension * @param index index (from zero) of the basis function in each dimension. * @return A n-dimensional basis function */ private Function<double[], Double> generateMultiDim(List<List<Function<Double, Double>>> oneDSets, int[] index) { final int dim = index.length; final List<Function<Double, Double>> funcs = new ArrayList<>(dim); for (int i = 0; i < dim; i++) { funcs.add(oneDSets.get(i).get(index[i])); } return new Function<double[], Double>() { @Override public Double apply(double[] x) { double product = 1.0; ArgChecker.isTrue(dim == x.length, "length of x {} was not equal to dimension {}", x.length, dim); for (int i = 0; i < dim; i++) { product *= funcs.get(i).apply(x[i]); } return product; } }; } private List<Function<Double, Double>> generateSet(final double[] knots, final int degree, final List<Function<Double, Double>> degreeM1Set) { int nSplines = knots.length - degree - 1; final List<Function<Double, Double>> functions = new ArrayList<>(nSplines); for (int i = 0; i < nSplines; i++) { functions.add(generate(knots, degree, i, degreeM1Set)); } return functions; } /** * Generate a basis function of the required degree either by using the set of functions one degree lower, or by recursion * @param knots The knots that support the basis functions * @param degree The required degree * @param index The index of the required function * @param degreeM1Set Set of basis functions one degree lower than required (can be null) * @return The basis function */ private Function<Double, Double> generate(final double[] knots, final int degree, final int index, final List<Function<Double, Double>> degreeM1Set) { if (degree == 0) { return new Function<Double, Double>() { private final double _l = knots[index]; private final double _h = knots[index + 1]; @Override public Double apply(final Double x) { return (x >= _l && x < _h) ? 1.0 : 0.0; } }; } if (degree == 1) { return new Function<Double, Double>() { private final double _l = knots[index]; private final double _m = knots[index + 1]; private final double _h = knots[index + 2]; private final double _inv1 = 1.0 / (knots[index + 1] - knots[index]); private final double _inv2 = 1.0 / (knots[index + 2] - knots[index + 1]); @Override public Double apply(final Double x) { return (x <= _l || x >= _h) ? 0.0 : (x <= _m ? (x - _l) * _inv1 : (_h - x) * _inv2); } }; } // if degreeM1Set is unavailable, use the recursion final Function<Double, Double> fa = degreeM1Set == null ? generate(knots, degree - 1, index) : degreeM1Set.get(index); final Function<Double, Double> fb = degreeM1Set == null ? generate(knots, degree - 1, index + 1) : degreeM1Set.get(index + 1); return new Function<Double, Double>() { private final double _inv1 = 1.0 / (knots[index + degree] - knots[index]); private final double _inv2 = 1.0 / (knots[index + degree + 1] - knots[index + 1]); private final double _xa = knots[index]; private final double _xb = knots[index + degree + 1]; @Override public Double apply(final Double x) { return (x - _xa) * _inv1 * fa.apply(x) + (_xb - x) * _inv2 * fb.apply(x); } }; } /** * Generate a basis function of the required degree by recursion * @param knots The knots that support the basis functions * @param degree The required degree * @param index The index of the required function * @return The basis function */ private Function<Double, Double> generate(final double[] knots, final int degree, final int index) { return generate(knots, degree, index, null); } }