DiscreteMonitoringSupport.java

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

import java.util.Set;
import java.util.TreeSet;

import net.finmath.modelling.products.MonitoringType;
import net.finmath.time.TimeDiscretization;
import net.finmath.time.TimeDiscretizationFromArray;

/**
 * Utility methods for products with continuous or discrete monitoring.
 *
 * <p>
 * This class centralizes common monitoring-related functionality used by
 * finite-difference products. It provides helpers to identify discrete
 * monitoring, test whether a time belongs to a monitoring schedule, validate
 * monitoring inputs, and refine a solver time discretization so that monitoring
 * dates are represented exactly as backward-time grid points.
 * </p>
 *
 * <p>
 * The finite-difference solvers use the backward time variable
 * {@code tau = maturity - time}. Consequently, when monitoring dates are merged
 * into a solver time grid, the inserted grid points are {@code maturity -
 * monitoringTime}.
 * </p>
 *
 * @author Alessandro Gnoatto
 */
public final class DiscreteMonitoringSupport {

	/**
	 * Default absolute tolerance used when comparing monitoring times.
	 */
	public static final double DEFAULT_MONITORING_TIME_TOLERANCE = 1E-12;

	private DiscreteMonitoringSupport() {
	}

	/**
	 * Returns whether the given monitoring type represents discrete monitoring.
	 *
	 * @param monitoringType The monitoring type.
	 * @return {@code true} if {@code monitoringType} is
	 *         {@link MonitoringType#DISCRETE}; {@code false} otherwise.
	 */
	public static boolean usesDiscreteMonitoring(final MonitoringType monitoringType) {
		return monitoringType == MonitoringType.DISCRETE;
	}

	/**
	 * Tests whether a time belongs to a discrete monitoring schedule.
	 *
	 * <p>
	 * A time is considered a monitoring time if it differs from one of the
	 * supplied monitoring times by at most the supplied absolute tolerance.
	 * </p>
	 *
	 * @param time The time to test.
	 * @param monitoringTimes The monitoring schedule.
	 * @param tolerance The absolute tolerance used for time comparison.
	 * @return {@code true} if {@code time} matches one of the monitoring times;
	 *         {@code false} otherwise.
	 */
	public static boolean isMonitoringTime(
			final double time,
			final double[] monitoringTimes,
			final double tolerance) {

		if (monitoringTimes == null) {
			return false;
		}

		for (final double monitoringTime : monitoringTimes) {
			if (Math.abs(monitoringTime - time) <= tolerance) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Validates a monitoring specification.
	 *
	 * <p>
	 * Continuous monitoring must not provide monitoring times. Discrete
	 * monitoring must provide a non-empty, strictly increasing array of
	 * monitoring times lying in the interval {@code [0, maturity]}, up to the
	 * supplied tolerance.
	 * </p>
	 *
	 * @param monitoringType The monitoring type.
	 * @param monitoringTimes The monitoring schedule.
	 * @param maturity The product maturity.
	 * @param tolerance The absolute tolerance used for validation.
	 * @throws IllegalArgumentException Thrown if the monitoring type is
	 * {@code null}, if continuous monitoring specifies monitoring times,
	 *         or if discrete monitoring has missing, unordered, or out-of-range
	 *         monitoring times.
	 */
	public static void validateMonitoringSpecification(
			final MonitoringType monitoringType,
			final double[] monitoringTimes,
			final double maturity,
			final double tolerance) {

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

		if (monitoringType == MonitoringType.CONTINUOUS) {
			if (monitoringTimes != null && monitoringTimes.length > 0) {
				throw new IllegalArgumentException(
						"Continuous monitoring must not specify monitoringTimes."
						);
			}
			return;
		}

		if (monitoringTimes == null || monitoringTimes.length == 0) {
			throw new IllegalArgumentException(
					"Discrete monitoring requires a non-empty monitoringTimes array."
					);
		}

		double previousTime = -Double.MAX_VALUE;
		for (final double monitoringTime : monitoringTimes) {
			if (monitoringTime < -tolerance || monitoringTime > maturity + tolerance) {
				throw new IllegalArgumentException(
						"Monitoring times must lie in [0,maturity]."
						);
			}

			if (monitoringTime <= previousTime + tolerance) {
				throw new IllegalArgumentException(
						"Monitoring times must be strictly increasing."
						);
			}

			previousTime = monitoringTime;
		}
	}

	/**
	 * Refines a backward-time discretization with monitoring dates.
	 *
	 * <p>
	 * The returned time discretization contains all original times from
	 * {@code baseTimeDiscretization} and, for each monitoring date
	 * {@code t_i}, the corresponding backward-time point
	 * {@code maturity - t_i}. Duplicate points are removed by the
	 * {@link TreeSet} merge.
	 * </p>
	 *
	 * @param baseTimeDiscretization The original solver time discretization.
	 * @param maturity The product maturity.
	 * @param monitoringTimes The monitoring schedule.
	 * @return A refined time discretization containing the original grid and
	 *     the
	 *         monitoring dates converted to backward time, or the original
	 * discretization if the monitoring schedule is {@code null} or empty.
	 */
	public static TimeDiscretization refineTimeDiscretizationWithMonitoring(
			final TimeDiscretization baseTimeDiscretization,
			final double maturity,
			final double[] monitoringTimes) {

		if (monitoringTimes == null || monitoringTimes.length == 0) {
			return baseTimeDiscretization;
		}

		final Set<Double> mergedTauTimes = new TreeSet<>();

		for (int i = 0; i < baseTimeDiscretization.getNumberOfTimes(); i++) {
			mergedTauTimes.add(baseTimeDiscretization.getTime(i));
		}

		for (final double monitoringTime : monitoringTimes) {
			mergedTauTimes.add(maturity - monitoringTime);
		}

		final double[] refinedTimes = new double[mergedTauTimes.size()];
		int index = 0;
		for (final Double time : mergedTauTimes) {
			refinedTimes[index++] = time;
		}

		return new TimeDiscretizationFromArray(refinedTimes);
	}
}