GridWithMandatoryPoint.java

package net.finmath.finitedifference.grids;

import java.util.Arrays;

/**
 * Piecewise-uniform grid on {@code [minimumValue, maximumValue]} with one
 * mandatory interior node.
 *
 * <p>
 * The interval is split into two sub-intervals:
 * </p>
 * <ul>
 *   <li>{@code [minimumValue, mandatoryPoint]}</li>
 *   <li>{@code [mandatoryPoint, maximumValue]}</li>
 * </ul>
 *
 * <p>
 * On each side, nodes are distributed uniformly. The mandatory point is forced
 * to be an exact grid node, while the lower and upper bounds are preserved.
 * </p>
 *
 * <p>
 * This is useful for finite-difference pricing of products with a payoff
 * discontinuity or kink at a known location, for example digitals with
 * discontinuity at the strike.
 * </p>
 *
 * @author Alessandro Gnoatto
 */
public class GridWithMandatoryPoint extends AbstractGrid {

	/**
	 * The number of steps.
	 */
	private final int numberOfSteps;
	/**
	 * The minimum value.
	 */
	private final double minimumValue;
	/**
	 * The maximum value.
	 */
	private final double maximumValue;
	/**
	 * The mandatory point.
	 */
	private final double mandatoryPoint;

	/**
	 * The grid.
	 */
	private double[] grid;
	/**
	 * The delta.
	 */
	private double[] delta;

	/**
	 * Creates a grid with one mandatory interior node.
	 *
	 * @param numberOfSteps Number of grid intervals.
	 * @param minimumValue Lower grid boundary.
	 * @param maximumValue Upper grid boundary.
	 * @param mandatoryPoint Point that must appear exactly as a grid node.
	 */
	public GridWithMandatoryPoint(
			final int numberOfSteps,
			final double minimumValue,
			final double maximumValue,
			final double mandatoryPoint) {

		validateInputs(numberOfSteps, minimumValue, maximumValue, mandatoryPoint);

		this.numberOfSteps = numberOfSteps;
		this.minimumValue = minimumValue;
		this.maximumValue = maximumValue;
		this.mandatoryPoint = mandatoryPoint;
	}

	/**
	 * Generates the grid.
	 *
	 * <p>
	 * The mandatory point is assigned an interior node index chosen according
	 * to
	 * its relative position inside the interval. The left and right parts are
	 * then filled uniformly.
	 * </p>
	 */
	private void generateGrid() {

		final int mandatoryIndex = chooseMandatoryIndex(
				numberOfSteps,
				minimumValue,
				maximumValue,
				mandatoryPoint
				);

		grid = new double[numberOfSteps + 1];

		final double dxLeft = (mandatoryPoint - minimumValue) / mandatoryIndex;
		final double dxRight = (maximumValue - mandatoryPoint) / (numberOfSteps - mandatoryIndex);

		for (int i = 0; i <= mandatoryIndex; i++) {
			grid[i] = minimumValue + i * dxLeft;
		}

		for (int i = mandatoryIndex + 1; i <= numberOfSteps; i++) {
			grid[i] = mandatoryPoint + (i - mandatoryIndex) * dxRight;
		}

		grid[0] = minimumValue;
		grid[mandatoryIndex] = mandatoryPoint;
		grid[numberOfSteps] = maximumValue;
	}

	/**
	 * Generates the spacing array.
	 *
	 * @param vector Array of values at grid points, usually the grid itself.
	 */
	private void generateDelta(final double[] vector) {

		if (grid == null) {
			generateGrid();
		}

		if (vector == null) {
			throw new IllegalArgumentException("vector must not be null.");
		}

		if (vector.length != grid.length) {
			throw new IllegalArgumentException("vector length must match grid length.");
		}

		delta = new double[numberOfSteps];

		for (int i = 0; i < vector.length - 1; i++) {
			delta[i] = vector[i + 1] - vector[i];
		}
	}

	/**
	 * Returns the grid points, generating them if necessary.
	 *
	 * @return The grid points.
	 */
	@Override
	public double[] getGrid() {

		if (grid == null) {
			generateGrid();
		}

		return Arrays.copyOf(grid, grid.length);
	}

	/**
	 * Returns the spacing between consecutive grid points.
	 *
	 * @param vector Array of values at grid points, usually the grid itself.
	 * @return The spacing array.
	 */
	@Override
	public double[] getDelta(final double[] vector) {

		if (delta == null) {
			generateDelta(vector);
		}

		return Arrays.copyOf(delta, delta.length);
	}

	/**
	 * Returns the mandatory point.
	 *
	 * @return The mandatory point.
	 */
	public double getMandatoryPoint() {
		return mandatoryPoint;
	}

	/**
	 * Returns the node index corresponding to the mandatory point.
	 *
	 * @return The mandatory node index.
	 */
	public int getMandatoryIndex() {
		return chooseMandatoryIndex(numberOfSteps, minimumValue, maximumValue, mandatoryPoint);
	}

	/**
	 * Finds the index of a mandatory point in a previously generated grid.
	 *
	 * @param grid The grid array.
	 * @param mandatoryPoint The mandatory point.
	 * @return The index of the mandatory point.
	 */
	public static int findMandatoryIndex(final double[] grid, final double mandatoryPoint) {
		for (int i = 0; i < grid.length; i++) {
			if (Math.abs(grid[i] - mandatoryPoint) < 1E-12) {
				return i;
			}
		}
		throw new IllegalArgumentException("Mandatory point is not a grid node.");
	}

	/**
	 * Convenience overload.
	 *
	 * @param grid The grid.
	 * @param mandatoryPoint The mandatory point.
	 * @return The index of the mandatory point.
	 */
	public static int findMandatoryIndex(final Grid grid, final double mandatoryPoint) {
		return findMandatoryIndex(grid.getGrid(), mandatoryPoint);
	}

	/**
	 * Chooses the interior node index assigned to the mandatory point.
	 *
	 * <p>
	 * The allocation is chosen to roughly match the geometric position of the
	 * mandatory point inside the interval.
	 * </p>
	 *
	 * @param numberOfSteps Number of grid intervals.
	 * @param minimumValue Lower grid boundary.
	 * @param maximumValue Upper grid boundary.
	 * @param mandatoryPoint Mandatory point.
	 * @return The grid node index assigned to the mandatory point.
	 */
	private static int chooseMandatoryIndex(
			final int numberOfSteps,
			final double minimumValue,
			final double maximumValue,
			final double mandatoryPoint) {

		final double relativePosition =
				(mandatoryPoint - minimumValue) / (maximumValue - minimumValue);

		int mandatoryIndex = (int)Math.round(relativePosition * numberOfSteps);

		mandatoryIndex = Math.max(1, mandatoryIndex);
		mandatoryIndex = Math.min(numberOfSteps - 1, mandatoryIndex);

		return mandatoryIndex;
	}

	/**
	 * Validates constructor inputs.
	 *
	 * @param numberOfSteps Number of grid intervals.
	 * @param minimumValue Lower grid boundary.
	 * @param maximumValue Upper grid boundary.
	 * @param mandatoryPoint Mandatory point.
	 */
	private static void validateInputs(
			final int numberOfSteps,
			final double minimumValue,
			final double maximumValue,
			final double mandatoryPoint) {

		if (numberOfSteps < 2) {
			throw new IllegalArgumentException("numberOfSteps must be at least 2.");
		}

		if (!(minimumValue < maximumValue)) {
			throw new IllegalArgumentException("Require minimumValue < maximumValue.");
		}

		if (!(mandatoryPoint > minimumValue && mandatoryPoint < maximumValue)) {
			throw new IllegalArgumentException(
					"mandatoryPoint must lie strictly inside (minimumValue, maximumValue)."
					);
		}
	}
}