ActivatedBarrierTrace2D.java

package net.finmath.finitedifference.solvers.adi;

import net.finmath.time.TimeDiscretization;

/**
 * Immutable container for the activated post-hit value traced along the barrier
 * as a function of the second state variable and time.
 *
 * <p>
 * The intended usage is the direct 2D knock-in formulation:
 * first solve the activated vanilla problem, then extract its value on the
 * barrier and use that trace as interface data for the pre-hit PDE.
 * </p>
 *
 * <p>
 * The data layout is values[secondIndex][timeIndex].
 * </p>
 *
 * @author Alessandro Gnoatto
 */
public final class ActivatedBarrierTrace2D {

	/**
	 * The tolerance.
	 */
	private static final double TOLERANCE = 1E-12;

	/**
	 * The barrier value.
	 */
	private final double barrierValue;
	/**
	 * The second state grid.
	 */
	private final double[] secondStateGrid;
	/**
	 * The time grid.
	 */
	private final TimeDiscretization timeGrid;
	/**
	 * The values.
	 */
	private final double[][] values;

	/**
	 * Creates an immutable barrier trace container.
	 *
	 * @param barrierValue The barrier level in the first state variable.
	 * @param secondStateGrid The grid of the second state variable.
	 * @param timeGrid The time discretization corresponding to the trace
	 *     values.
	 * @param values Trace values with layout values[secondIndex][timeIndex].
	 */
	public ActivatedBarrierTrace2D(
			final double barrierValue,
			final double[] secondStateGrid,
			final TimeDiscretization timeGrid,
			final double[][] values) {

		if (secondStateGrid == null || secondStateGrid.length == 0) {
			throw new IllegalArgumentException("secondStateGrid must not be null or empty.");
		}
		if (timeGrid == null) {
			throw new IllegalArgumentException("timeGrid must not be null.");
		}
		if (values == null || values.length != secondStateGrid.length) {
			throw new IllegalArgumentException(
					"values must have one row for each second-state grid point.");
		}

		final int expectedTimeCount = timeGrid.getNumberOfTimes();
		for (int i = 0; i < values.length; i++) {
			if (values[i] == null || values[i].length != expectedTimeCount) {
				throw new IllegalArgumentException(
						"Each values row must have length equal to timeGrid.getNumberOfTimes().");
			}
		}

		this.barrierValue = barrierValue;
		this.secondStateGrid = secondStateGrid.clone();
		this.timeGrid = timeGrid;
		this.values = deepCopy(values);
	}

	/**
	 * Returns the barrier level.
	 *
	 * @return The barrier value.
	 */
	public double getBarrierValue() {
		return barrierValue;
	}

	/**
	 * Returns a defensive copy of the second-state grid.
	 *
	 * @return The second-state grid.
	 */
	public double[] getSecondStateGrid() {
		return secondStateGrid.clone();
	}

	/**
	 * Returns the time grid.
	 *
	 * @return The time discretization.
	 */
	public TimeDiscretization getTimeGrid() {
		return timeGrid;
	}

	/**
	 * Returns a defensive deep copy of the stored trace values.
	 *
	 * @return The full trace matrix with layout values[secondIndex][timeIndex].
	 */
	public double[][] getValues() {
		return deepCopy(values);
	}

	/**
	 * Returns the stored value for a given second-state row and time index.
	 *
	 * @param secondIndex The second-state index.
	 * @param timeIndex The time index.
	 * @return The trace value.
	 */
	public double getValue(final int secondIndex, final int timeIndex) {
		validateSecondIndex(secondIndex);
		validateTimeIndex(timeIndex);
		return values[secondIndex][timeIndex];
	}

	/**
	 * Returns the trace value for a given second-state row and time.
	 *
	 * <p>
	 * If tau lies exactly on a time node, that node value is returned.
	 * Otherwise linear interpolation in time is used between the enclosing
	 * nodes.
	 * Values outside the grid are clamped to the nearest endpoint.
	 * </p>
	 *
	 * @param secondIndex The second-state index.
	 * @param tau The time coordinate.
	 * @return The interpolated trace value.
	 */
	public double getValue(final int secondIndex, final double tau) {
		validateSecondIndex(secondIndex);

		final int numberOfTimes = timeGrid.getNumberOfTimes();
		if (numberOfTimes == 1) {
			return values[secondIndex][0];
		}

		final double firstTime = timeGrid.getTime(0);
		final double lastTime = timeGrid.getTime(numberOfTimes - 1);

		if (tau <= firstTime + TOLERANCE) {
			return values[secondIndex][0];
		}
		if (tau >= lastTime - TOLERANCE) {
			return values[secondIndex][numberOfTimes - 1];
		}

		final int upperIndex = timeGrid.getTimeIndexNearestGreaterOrEqual(tau);
		if (upperIndex < 0) {
			return values[secondIndex][numberOfTimes - 1];
		}

		final double upperTime = timeGrid.getTime(upperIndex);
		if (Math.abs(upperTime - tau) <= TOLERANCE) {
			return values[secondIndex][upperIndex];
		}

		final int lowerIndex = upperIndex - 1;
		if (lowerIndex < 0) {
			return values[secondIndex][upperIndex];
		}

		final double lowerTime = timeGrid.getTime(lowerIndex);
		final double lowerValue = values[secondIndex][lowerIndex];
		final double upperValue = values[secondIndex][upperIndex];

		final double weight = (tau - lowerTime) / (upperTime - lowerTime);
		return (1.0 - weight) * lowerValue + weight * upperValue;
	}

	/**
	 * Returns the number of second-state rows.
	 *
	 * @return The number of second-state grid points.
	 */
	public int getNumberOfSecondStatePoints() {
		return secondStateGrid.length;
	}

	/**
	 * Returns the number of time nodes.
	 *
	 * @return The number of time points.
	 */
	public int getNumberOfTimePoints() {
		return timeGrid.getNumberOfTimes();
	}

	private void validateSecondIndex(final int secondIndex) {
		if (secondIndex < 0 || secondIndex >= secondStateGrid.length) {
			throw new IndexOutOfBoundsException(
					"secondIndex out of bounds: " + secondIndex);
		}
	}

	private void validateTimeIndex(final int timeIndex) {
		if (timeIndex < 0 || timeIndex >= timeGrid.getNumberOfTimes()) {
			throw new IndexOutOfBoundsException(
					"timeIndex out of bounds: " + timeIndex);
		}
	}

	private static double[][] deepCopy(final double[][] source) {
		final double[][] copy = new double[source.length][];
		for (int i = 0; i < source.length; i++) {
			copy[i] = source[i].clone();
		}
		return copy;
	}
}