IndependentIncrementsFromICDF.java

/*
 * (c) Copyright Christian P. Fries, Germany. Contact: email@christian-fries.de.
 *
 * Created on 09.02.2004
 */
package net.finmath.montecarlo;

import java.io.IOException;
import java.io.Serializable;
import java.util.function.DoubleUnaryOperator;
import java.util.function.IntFunction;

import net.finmath.randomnumbers.MersenneTwister;
import net.finmath.stochastic.RandomVariable;
import net.finmath.time.TimeDiscretization;

/**
 * Implementation of a time-discrete n-dimensional sequence of independent increments
 * <i>W = (W<sub>1</sub>,...,W<sub>n</sub>)</i> form a given set of inverse
 * cumulative distribution functions.
 *
 * Independent increments is a sequence of independent random variables index
 * by the time index associated with the time discretization. At each time step
 * the increment is a d-dimensional random variable \( Z(t_{i}) \), where d is <code>numberOfFactors</code>.
 * where each component of \( Z_{j}(t_{i}) \) is given by
 * \[
 * 	Z_{j}(t_{i}) = ICDF_{i,j}(U_{i,j})
 * \]
 * for a sequence of independent uniform distributes random variables U_{i,j}.
 *
 * The inverse cumulative distribution functions \( ICDF_{i,j} \) are given by
 * <code>inverseCumulativeDistributionFunctions</code> as the
 * map \( i \mapsto ( j \mapsto ICDF_{i,j} ) \) (here i is the time index and j is the factor (component).
 *
 * Each \( U_{i,j} \) is samples using <code>numberOfPaths</code>.
 *
 * The class is immutable and thread safe. It uses lazy initialization.
 *
 * @author Christian Fries
 * @version 1.6
 */
public class IndependentIncrementsFromICDF implements IndependentIncrements, Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = 6270884840989559532L;

	private final TimeDiscretization						timeDiscretization;

	private final int			numberOfFactors;
	private final int			numberOfPaths;
	private final int			seed;

	private final RandomVariableFactory randomVariableFactory;

	private transient	RandomVariable[][]	increments;
	private	transient	Object						incrementsLazyInitLock = new Object();

	private final IntFunction<IntFunction<DoubleUnaryOperator>> inverseCumulativeDistributionFunctions;

	/**
	 * Construct  the simulation of independent increments.
	 *
	 * Independent increments is a sequence of independent random variables index
	 * by the time index associated with the time discretization. At each time step
	 * the increment is a d-dimensional random variable \( Z(t_{i}) \), where d is <code>numberOfFactors</code>.
	 * where each component of \( Z_{j}(t_{i}) \) is given by
	 * \[
	 * 	Z_{j}(t_{i}) = ICDF_{i,j}(U_{i,j})
	 * \]
	 * for a sequence of independent uniform distributes random variables U_{i,j}.
	 *
	 * The inverse cumulative distribution functions \( ICDF_{i,j} \) are given by
	 * <code>inverseCumulativeDistributionFunctions</code> as the
	 * map \( i \mapsto ( j \mapsto ICDF_{i,j} ) \) (here i is the time index and j is the factor (component).
	 *
	 * Each \( U_{i,j} \) is samples using <code>numberOfPaths</code>.
	 *
	 * The constructor allows to set the factory to be used for the construction of
	 * random variables. This allows to generate increments represented
	 * by different implementations of the RandomVariable
	 * (e.g. the RandomVariableFromFloatArray internally using float representations).
	 *
	 * @param timeDiscretization The time discretization used for the increments.
	 * @param numberOfFactors Number of factors.
	 * @param numberOfPaths Number of paths to simulate.
	 * @param seed The seed of the random number generator.
	 * @param inverseCumulativeDistributionFunctions A map from the timeIndices to a map from the from the factors to the corresponding inverse cumulative distribution function.
	 * @param randomVariableFactory Factory to be used to create random variable.
	 */
	public IndependentIncrementsFromICDF(
			final TimeDiscretization timeDiscretization,
			final int numberOfFactors,
			final int numberOfPaths,
			final int seed,
			final IntFunction<IntFunction<DoubleUnaryOperator>> inverseCumulativeDistributionFunctions,
			final RandomVariableFactory randomVariableFactory) {
		super();
		this.timeDiscretization = timeDiscretization;
		this.numberOfFactors	= numberOfFactors;
		this.numberOfPaths		= numberOfPaths;
		this.seed				= seed;

		this.inverseCumulativeDistributionFunctions = inverseCumulativeDistributionFunctions;
		this.randomVariableFactory = randomVariableFactory;

		increments	= null; 	// Lazy initialization
	}

	/**
	 * Construct  the simulation of independet increments.
	 *
	 * The independent increments is a sequence of independent random variables index
	 * by the time index associated with the time discretization. At each time step
	 * the increment is a d-dimensional random variable \( Z(t_{i}) \), where d is <code>numberOfFactors</code>.
	 * where each component of \( Z_{j}(t_{i}) \) is given by
	 * \[
	 * 	Z_{j}(t_{i}) = ICDF_{i,j}(U_{i,j})
	 * \]
	 * for a sequence of independent uniform distributes random variables U_{i,j}.
	 *
	 * The inverse cumulative distribution functions \( ICDF_{i,j} \) are given by
	 * <code>inverseCumulativeDistributionFunctions</code> as the
	 * map \( i \mapsto ( j \mapsto ICDF_{i,j} ) \) (here i is the time index and j is the factor (component).
	 *
	 * Each \( U_{i,j} \) is samples using <code>numberOfPaths</code>.
	 *
	 * @param timeDiscretization The time discretization used for the increments.
	 * @param numberOfFactors Number of factors.
	 * @param numberOfPaths Number of paths to simulate.
	 * @param seed The seed of the random number generator.
	 * @param inverseCumulativeDistributionFunctions A map from the timeIndices to a map from the from the factors to the corresponding inverse cumulative distribution function.
	 */
	public IndependentIncrementsFromICDF(
			final TimeDiscretization timeDiscretization,
			final int numberOfFactors,
			final int numberOfPaths,
			final int seed,
			final IntFunction<IntFunction<DoubleUnaryOperator>> inverseCumulativeDistributionFunctions) {
		this(timeDiscretization, numberOfFactors, numberOfPaths, seed, inverseCumulativeDistributionFunctions, new RandomVariableFromArrayFactory());
	}

	@Override
	public IndependentIncrements getCloneWithModifiedSeed(final int seed) {
		return new IndependentIncrementsFromICDF(getTimeDiscretization(), getNumberOfFactors(), getNumberOfPaths(), seed, inverseCumulativeDistributionFunctions, randomVariableFactory);
	}

	@Override
	public IndependentIncrements getCloneWithModifiedTimeDiscretization(final TimeDiscretization newTimeDiscretization) {
		return new IndependentIncrementsFromICDF(newTimeDiscretization, getNumberOfFactors(), getNumberOfPaths(), getSeed(), inverseCumulativeDistributionFunctions, randomVariableFactory);
	}

	@Override
	public RandomVariable getIncrement(final int timeIndex, final int factor) {

		// Thread safe lazy initialization
		synchronized(incrementsLazyInitLock) {
			if(increments == null) {
				doGenerateIncrements();
			}
		}

		/*
		 *  We return an immutable object which ensures that the receiver does not alter the data.
		 */
		return increments[timeIndex][factor];
	}

	/**
	 * Lazy initialization of brownianIncrement. Synchronized to ensure thread safety of lazy init.
	 */
	private void doGenerateIncrements() {
		if(increments != null) {
			return;	// Nothing to do
		}

		// Create random number sequence generator
		final MersenneTwister			mersenneTwister		= new MersenneTwister(seed);

		// Allocate memory
		final double[][][] incrementsArray = new double[timeDiscretization.getNumberOfTimeSteps()][numberOfFactors][numberOfPaths];

		// Pre-fetch icdfs
		final DoubleUnaryOperator[][] inverseCumulativeDistributionFunctions = new DoubleUnaryOperator[timeDiscretization.getNumberOfTimeSteps()][numberOfFactors];
		for(int timeIndex=0; timeIndex<timeDiscretization.getNumberOfTimeSteps(); timeIndex++) {
			for(int factor=0; factor<numberOfFactors; factor++) {
				inverseCumulativeDistributionFunctions[timeIndex][factor] = this.inverseCumulativeDistributionFunctions.apply(timeIndex).apply(factor);
			}
		}

		/*
		 * Generate independent increments, each having distribution specified by ICDF.
		 *
		 * The inner loop goes over time and factors.
		 * MersenneTwister is known to generate "independent" increments in 623 dimensions.
		 * Since we want to generate independent streams (paths), the loop over path is the outer loop.
		 */
		for(int path=0; path<numberOfPaths; path++) {
			for(int timeIndex=0; timeIndex<timeDiscretization.getNumberOfTimeSteps(); timeIndex++) {
				// Generate uncorrelated Brownian increment
				for(int factor=0; factor<numberOfFactors; factor++) {
					final double uniformIncrement = mersenneTwister.nextDouble();
					incrementsArray[timeIndex][factor][path] = inverseCumulativeDistributionFunctions[timeIndex][factor].applyAsDouble(uniformIncrement);
				}
			}
		}

		// Allocate memory for RandomVariableFromDoubleArray wrapper objects.
		increments = new RandomVariable[timeDiscretization.getNumberOfTimeSteps()][numberOfFactors];

		// Wrap the values in RandomVariableFromDoubleArray objects
		for(int timeIndex=0; timeIndex<timeDiscretization.getNumberOfTimeSteps(); timeIndex++) {
			final double time = timeDiscretization.getTime(timeIndex+1);
			for(int factor=0; factor<numberOfFactors; factor++) {
				increments[timeIndex][factor] =
						randomVariableFactory.createRandomVariable(time, incrementsArray[timeIndex][factor]);
			}
		}
	}

	@Override
	public TimeDiscretization getTimeDiscretization() {
		return timeDiscretization;
	}

	@Override
	public int getNumberOfFactors() {
		return numberOfFactors;
	}

	@Override
	public int getNumberOfPaths() {
		return numberOfPaths;
	}

	@Override
	public RandomVariable getRandomVariableForConstant(final double value) {
		return randomVariableFactory.createRandomVariable(value);
	}

	/**
	 * @return Returns the seed.
	 */
	public int getSeed() {
		return seed;
	}

	@Override
	public String toString() {
		return super.toString()
				+ "\n" + "timeDiscretizationFromArray: " + timeDiscretization.toString()
				+ "\n" + "numberOfPaths: " + numberOfPaths
				+ "\n" + "numberOfFactors: " + numberOfFactors
				+ "\n" + "seed: " + seed;
	}

	@Override
	public boolean equals(final Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}

		final IndependentIncrementsFromICDF that = (IndependentIncrementsFromICDF) o;

		if ((numberOfFactors != that.numberOfFactors) || (numberOfPaths != that.numberOfPaths) || (seed != that.seed)) {
			return false;
		}
		return timeDiscretization.equals(that.timeDiscretization);
	}

	@Override
	public int hashCode() {
		int result = timeDiscretization.hashCode();
		result = 31 * result + numberOfFactors;
		result = 31 * result + numberOfPaths;
		result = 31 * result + seed;
		return result;
	}

	private void readObject(final java.io.ObjectInputStream in) throws ClassNotFoundException, IOException {
		in.defaultReadObject();
		// initialization of transients
		incrementsLazyInitLock = new Object();
	}
}