DiscreteKnockInActivationSupport.java

package net.finmath.finitedifference.assetderivativevaluation.products.internal;

import net.finmath.finitedifference.assetderivativevaluation.models.FiniteDifferenceEquityModel;
import net.finmath.finitedifference.grids.SpaceTimeDiscretization;
import net.finmath.interpolation.RationalFunctionInterpolation;
import net.finmath.interpolation.RationalFunctionInterpolation.ExtrapolationMethod;
import net.finmath.interpolation.RationalFunctionInterpolation.InterpolationMethod;

/**
 * Utility methods for discretely monitored knock-in products.
 *
 * <p>
 * Discretely monitored knock-in products are commonly priced by solving a
 * pre-hit continuation problem and replacing values by an activated
 * continuation value on monitoring dates where the barrier has been breached.
 * This class provides shared helpers for extracting the activated continuation
 * vector from a previously computed activated value surface.
 * </p>
 *
 * <p>
 * The activated surface is assumed to be indexed by space point in the first
 * array dimension and by finite-difference time index in the second array
 * dimension. The time index is inferred from {@code maturity - time}, matching
 * the backward-time convention used by the finite-difference solvers.
 * </p>
 *
 * <p>
 * One-dimensional activated vectors may be interpolated from the activated
 * grid to the target grid if the grids differ. Two-dimensional activated
 * vectors currently require the activated and target grids to match in both
 * state variables.
 * </p>
 *
 * @author Alessandro Gnoatto
 */
public final class DiscreteKnockInActivationSupport {

	private DiscreteKnockInActivationSupport() {
	}

	/**
	 * Tests whether two one-dimensional grids are equal within a given absolute
	 * tolerance.
	 *
	 * @param first The first grid.
	 * @param second The second grid.
	 * @param tolerance The absolute tolerance used pointwise.
	 * @return {@code true} if the grids have the same length and all
	 *     corresponding
	 * grid points differ by at most {@code tolerance}; {@code false} otherwise.
	 */
	public static boolean hasSameGrid(
			final double[] first,
			final double[] second,
			final double tolerance) {

		if (first.length != second.length) {
			return false;
		}

		for (int i = 0; i < first.length; i++) {
			if (Math.abs(first[i] - second[i]) > tolerance) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Builds a zero terminal value vector matching the spatial size of a
	 * discretization.
	 *
	 * <p>
	 * For one-dimensional discretizations, the returned vector has the length
	 * of
	 * the first spatial grid. For two-dimensional discretizations, the returned
	 * vector has the product length of the first and second spatial grids,
	 * using
	 * the same flattened indexing convention as {@link #flatten(int, int,
	 * int)}.
	 * </p>
	 *
	 * @param discretization The space-time discretization.
	 * @return A zero-filled terminal value vector.
	 * @throws IllegalArgumentException Thrown if the discretization is neither
	 *         one-dimensional nor two-dimensional.
	 */
	public static double[] buildZeroTerminalValues(final SpaceTimeDiscretization discretization) {
		final int dims = discretization.getNumberOfSpaceGrids();

		if (dims == 1) {
			final double[] grid = discretization.getSpaceGrid(0).getGrid();
			return new double[grid.length];
		}

		if (dims == 2) {
			final double[] grid0 = discretization.getSpaceGrid(0).getGrid();
			final double[] grid1 = discretization.getSpaceGrid(1).getGrid();
			return new double[grid0.length * grid1.length];
		}

		throw new IllegalArgumentException("Only 1D and 2D grids are supported.");
	}

	/**
	 * Extracts an activated continuation vector at a given event time.
	 *
	 * <p>
	 * The method converts the event time into a solver time index using
	 * {@code maturity - time}. If the resulting time is not an exact time-grid
	 * point, the nearest previous time index is used. The extracted vector is
	 * represented on the spatial grid of {@code targetModel}.
	 * </p>
	 *
	 * @param time The event time.
	 * @param maturity The product maturity.
	 * @param activatedSurface The activated continuation surface, indexed by
	 *        flattened space point and time index.
	 * @param activatedDiscretization The discretization on which the activated
	 *        surface was computed.
	 * @param targetModel The target model whose spatial discretization
	 *     determines
	 *        the returned vector.
	 * @param gridTolerance The absolute tolerance used when comparing grids.
	 * @return The activated continuation vector represented on the target model
	 *     grid.
	 * @throws IllegalArgumentException Thrown if the target discretization is
	 *     neither
	 * one-dimensional nor two-dimensional, or if a two-dimensional activated
	 *         surface is requested on a non-matching target grid.
	 */
	public static double[] getActivatedVectorAt(
			final double time,
			final double maturity,
			final double[][] activatedSurface,
			final SpaceTimeDiscretization activatedDiscretization,
			final FiniteDifferenceEquityModel targetModel,
			final double gridTolerance) {

		final double tau = maturity - time;
		int timeIndex = activatedDiscretization.getTimeDiscretization().getTimeIndex(tau);

		if (timeIndex < 0) {
			timeIndex = activatedDiscretization.getTimeDiscretization()
					.getTimeIndexNearestLessOrEqual(tau);
		}

		final SpaceTimeDiscretization targetDiscretization = targetModel.getSpaceTimeDiscretization();
		final int dims = targetDiscretization.getNumberOfSpaceGrids();

		if (dims == 1) {
			return getActivatedVectorAt1D(
					timeIndex,
					activatedSurface,
					activatedDiscretization,
					targetDiscretization,
					gridTolerance
					);
		}

		if (dims == 2) {
			return getActivatedVectorAt2D(
					timeIndex,
					activatedSurface,
					activatedDiscretization,
					targetDiscretization,
					gridTolerance
					);
		}

		throw new IllegalArgumentException("Only 1D and 2D grids are supported.");
	}

	/**
	 * Extracts a one-dimensional activated vector at a given time index.
	 *
	 * <p>
	 * If source and target grids match within the supplied tolerance, values
	 * are
	 * copied directly. Otherwise, the activated surface column is linearly
	 * interpolated from the source grid to the target grid with constant
	 * extrapolation.
	 * </p>
	 *
	 * @param timeIndex The activated surface time index.
	 * @param activatedSurface The activated continuation surface.
	 * @param sourceDiscretization The discretization of the activated surface.
	 * @param targetDiscretization The target discretization.
	 * @param gridTolerance The absolute tolerance used when comparing grids.
	 * @return The activated vector represented on the target grid.
	 */
	private static double[] getActivatedVectorAt1D(
			final int timeIndex,
			final double[][] activatedSurface,
			final SpaceTimeDiscretization sourceDiscretization,
			final SpaceTimeDiscretization targetDiscretization,
			final double gridTolerance) {

		final double[] targetGrid = targetDiscretization.getSpaceGrid(0).getGrid();
		final double[] sourceGrid = sourceDiscretization.getSpaceGrid(0).getGrid();

		final double[] activatedVector = new double[targetGrid.length];

		if (hasSameGrid(sourceGrid, targetGrid, gridTolerance)) {
			for (int i = 0; i < targetGrid.length; i++) {
				activatedVector[i] = activatedSurface[i][timeIndex];
			}
			return activatedVector;
		}

		final RationalFunctionInterpolation interpolator = new RationalFunctionInterpolation(
				sourceGrid,
				getColumn(activatedSurface, timeIndex),
				InterpolationMethod.LINEAR,
				ExtrapolationMethod.CONSTANT
				);

		for (int i = 0; i < targetGrid.length; i++) {
			activatedVector[i] = interpolator.getValue(targetGrid[i]);
		}

		return activatedVector;
	}

	/**
	 * Extracts a two-dimensional activated vector at a given time index.
	 *
	 * <p>
	 * The method assumes flattened two-dimensional indexing, where
	 * {@code flatten(i, j, n0)} maps the first-state index {@code i} and
	 * second-state index {@code j} into the one-dimensional value vector.
	 * Two-dimensional interpolation is not performed; source and target grids
	 * must match in both state variables within the supplied tolerance.
	 * </p>
	 *
	 * @param timeIndex The activated surface time index.
	 * @param activatedSurface The activated continuation surface.
	 * @param sourceDiscretization The discretization of the activated surface.
	 * @param targetDiscretization The target discretization.
	 * @param gridTolerance The absolute tolerance used when comparing grids.
	 * @return The activated vector represented on the target grid.
	 * @throws IllegalArgumentException Thrown if the source and target grids do
	 *         not match in both state variables.
	 */
	private static double[] getActivatedVectorAt2D(
			final int timeIndex,
			final double[][] activatedSurface,
			final SpaceTimeDiscretization sourceDiscretization,
			final SpaceTimeDiscretization targetDiscretization,
			final double gridTolerance) {

		final double[] targetX0 = targetDiscretization.getSpaceGrid(0).getGrid();
		final double[] targetX1 = targetDiscretization.getSpaceGrid(1).getGrid();
		final double[] sourceX0 = sourceDiscretization.getSpaceGrid(0).getGrid();
		final double[] sourceX1 = sourceDiscretization.getSpaceGrid(1).getGrid();

		if (!hasSameGrid(sourceX0, targetX0, gridTolerance)
				|| !hasSameGrid(sourceX1, targetX1, gridTolerance)) {
			throw new IllegalArgumentException(
					"Discrete 2D knock-in currently requires activated and target grids to match."
					);
		}

		final int n0 = targetX0.length;
		final int n1 = targetX1.length;
		final double[] activatedVector = new double[n0 * n1];

		for (int j = 0; j < n1; j++) {
			for (int i = 0; i < n0; i++) {
				final int k = flatten(i, j, n0);
				activatedVector[k] = activatedSurface[k][timeIndex];
			}
		}

		return activatedVector;
	}

	/**
	 * Flattens a two-dimensional grid index into a one-dimensional vector
	 * index.
	 *
	 * @param i0 The index in the first spatial dimension.
	 * @param i1 The index in the second spatial dimension.
	 * @param n0 The number of grid points in the first spatial dimension.
	 * @return The flattened vector index.
	 */
	public static int flatten(final int i0, final int i1, final int n0) {
		return i0 + i1 * n0;
	}

	/**
	 * Extracts a column from a rectangular matrix.
	 *
	 * @param matrix The matrix.
	 * @param columnIndex The column index to extract.
	 * @return The requested matrix column.
	 */
	public static double[] getColumn(final double[][] matrix, final int columnIndex) {
		final double[] column = new double[matrix.length];
		for (int i = 0; i < matrix.length; i++) {
			column[i] = matrix[i][columnIndex];
		}
		return column;
	}
}