/*
* (c) Copyright Christian P. Fries, Germany. All rights reserved. Contact: email@christian-fries.de.
*
* Created on 24.12.2016
*/
package net.finmath.montecarlo.interestrate.modelplugins;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.finmath.exception.CalculationException;
import net.finmath.montecarlo.BrownianMotion;
import net.finmath.montecarlo.BrownianMotionInterface;
import net.finmath.montecarlo.interestrate.TermStructureModelInterface;
import net.finmath.montecarlo.interestrate.TermStructureModelMonteCarloSimulation;
import net.finmath.montecarlo.interestrate.products.AbstractLIBORMonteCarloProduct;
import net.finmath.montecarlo.process.ProcessEulerScheme;
import net.finmath.optimizer.OptimizerFactoryInterface;
import net.finmath.optimizer.OptimizerFactoryLevenbergMarquardt;
import net.finmath.optimizer.OptimizerInterface;
import net.finmath.optimizer.OptimizerInterface.ObjectiveFunction;
import net.finmath.optimizer.SolverException;
/**
* A base class and interface description for the instantaneous covariance of
* an forward rate interest rate model.
*
* @author Christian Fries
*/
public abstract class TermStructureCovarianceModelParametric implements TermStructureCovarianceModelInterface, TermStructureTenorTimeScalingInterface, TermStructureFactorLoadingsModelParametricInterface {
private static final Logger logger = Logger.getLogger("net.finmath");
/**
* Get the parameters of determining this parametric
* covariance model. The parameters are usually free parameters
* which may be used in calibration.
*
* @return Parameter vector.
*/
public abstract double[] getParameter();
@Override
public abstract TermStructureCovarianceModelParametric clone();
/**
* Return an instance of this model using a new set of parameters.
* Note: To improve performance it is admissible to return the same instance of the object given that the parameters have not changed. Models should be immutable.
*
* @param parameters The new set of parameters.
* @return An instance of AbstractLIBORCovarianceModelParametric with modified parameters.
*/
public abstract TermStructureCovarianceModelParametric getCloneWithModifiedParameters(double[] parameters);
/**
* Return a calibrated clone of the covariance model.
*
* @param calibrationModel Model to be used for the calibration.
* @param calibrationProducts Vector of calibration products.
* @param calibrationTargetValues Vector of corresponding target values.
* @param calibrationWeights Vector of corresponding weights.
* @param calibrationParameters Property map of calibration parameters.
* @return A clone of this model, using the calibrated parameters.
* @throws CalculationException Exception indicating failure in calibration.
*/
public TermStructureCovarianceModelParametric getCloneCalibrated(final TermStructureModelInterface calibrationModel, final AbstractLIBORMonteCarloProduct[] calibrationProducts, final double[] calibrationTargetValues, final double[] calibrationWeights, Map<String, Object> calibrationParameters) throws CalculationException {
if(calibrationParameters == null) calibrationParameters = new HashMap<String,Object>();
Integer numberOfPathsParameter = (Integer)calibrationParameters.get("numberOfPaths");
Integer seedParameter = (Integer)calibrationParameters.get("seed");
Integer maxIterationsParameter = (Integer)calibrationParameters.get("maxIterations");
Double parameterStepParameter = (Double)calibrationParameters.get("parameterStep");
Double accuracyParameter = (Double)calibrationParameters.get("accuracy");
BrownianMotionInterface brownianMotionParameter = (BrownianMotionInterface)calibrationParameters.get("brownianMotion");
double[] initialParameters = this.getParameter();
double[] lowerBound = new double[initialParameters.length];
double[] upperBound = new double[initialParameters.length];
double[] parameterStep = new double[initialParameters.length];
double[] zero = new double[calibrationTargetValues.length];
Arrays.fill(lowerBound, 0);
Arrays.fill(upperBound, Double.POSITIVE_INFINITY);
Arrays.fill(parameterStep, parameterStepParameter != null ? parameterStepParameter.doubleValue() : 1E-4);
Arrays.fill(zero, 0);
/*
* We allow for 2 simultaneous calibration models.
* Note: In the case of a Monte-Carlo calibration, the memory requirement is that of
* one model with 2 times the number of paths. In the case of an analytic calibration
* memory requirement is not the limiting factor.
*/
int numberOfThreads = 2;
OptimizerFactoryInterface optimizerFactoryParameter = (OptimizerFactoryInterface)calibrationParameters.get("optimizerFactory");
int numberOfPaths = numberOfPathsParameter != null ? numberOfPathsParameter.intValue() : 2000;
int seed = seedParameter != null ? seedParameter.intValue() : 31415;
int maxIterations = maxIterationsParameter != null ? maxIterationsParameter.intValue() : 400;
double accuracy = accuracyParameter != null ? accuracyParameter.doubleValue() : 1E-7;
final BrownianMotionInterface brownianMotion = brownianMotionParameter != null ? brownianMotionParameter : new BrownianMotion(calibrationModel.getProcess().getStochasticDriver().getTimeDiscretization(), getNumberOfFactors(), numberOfPaths, seed);
OptimizerFactoryInterface optimizerFactory = optimizerFactoryParameter != null ? optimizerFactoryParameter : new OptimizerFactoryLevenbergMarquardt(maxIterations, accuracy, numberOfThreads);
int numberOfThreadsForProductValuation = 2 * Math.max(2, Runtime.getRuntime().availableProcessors());
final ExecutorService executor = null;//Executors.newFixedThreadPool(numberOfThreadsForProductValuation);
ObjectiveFunction calibrationError = new ObjectiveFunction() {
// Calculate model values for given parameters
@Override
public void setValues(double[] parameters, double[] values) throws SolverException {
TermStructureCovarianceModelParametric calibrationCovarianceModel = TermStructureCovarianceModelParametric.this.getCloneWithModifiedParameters(parameters);
// Create a term structure model with the new covariance structure.
HashMap<String, Object> data = new HashMap<String, Object>();
data.put("covarianceModel", calibrationCovarianceModel);
TermStructureModelInterface model;
try {
model = calibrationModel.getCloneWithModifiedData(data);
} catch (CalculationException e) {
throw new SolverException(e);
}
ProcessEulerScheme process = new ProcessEulerScheme(brownianMotion);
final TermStructureModelMonteCarloSimulation termStructureModelMonteCarloSimulation = new TermStructureModelMonteCarloSimulation(model, process);
ArrayList<Future<Double>> valueFutures = new ArrayList<Future<Double>>(calibrationProducts.length);
for(int calibrationProductIndex=0; calibrationProductIndex<calibrationProducts.length; calibrationProductIndex++) {
final int workerCalibrationProductIndex = calibrationProductIndex;
Callable<Double> worker = new Callable<Double>() {
public Double call() throws SolverException {
try {
return calibrationWeights[workerCalibrationProductIndex] * (calibrationProducts[workerCalibrationProductIndex].getValue(termStructureModelMonteCarloSimulation) - calibrationTargetValues[workerCalibrationProductIndex]);
} catch (CalculationException e) {
// We do not signal exceptions to keep the solver working and automatically exclude non-working calibration products.
return new Double(0.0);
} catch (Exception e) {
// We do not signal exceptions to keep the solver working and automatically exclude non-working calibration products.
return new Double(0.0);
}
}
};
if(executor != null) {
Future<Double> valueFuture = executor.submit(worker);
valueFutures.add(calibrationProductIndex, valueFuture);
}
else {
FutureTask<Double> valueFutureTask = new FutureTask<Double>(worker);
valueFutureTask.run();
valueFutures.add(calibrationProductIndex, valueFutureTask);
}
}
for(int calibrationProductIndex=0; calibrationProductIndex<calibrationProducts.length; calibrationProductIndex++) {
try {
double value = valueFutures.get(calibrationProductIndex).get();
values[calibrationProductIndex] = value;
}
catch (InterruptedException e) {
throw new SolverException(e);
} catch (ExecutionException e) {
throw new SolverException(e);
}
}
double error = 0.0;
for (int valueIndex = 0; valueIndex < values.length; valueIndex++) {
double deviation = values[valueIndex];
error += deviation * deviation;
}
System.out.println(Math.sqrt(error/values.length));
}
};
OptimizerInterface optimizer = optimizerFactory.getOptimizer(calibrationError, initialParameters, lowerBound, upperBound, parameterStep, zero);
try {
optimizer.run();
}
catch(SolverException e) {
throw new CalculationException(e);
}
finally {
if(executor != null) {
executor.shutdown();
}
}
// Get covariance model corresponding to the best parameter set.
double[] bestParameters = optimizer.getBestFitParameters();
TermStructureCovarianceModelParametric calibrationCovarianceModel = this.getCloneWithModifiedParameters(bestParameters);
// Diagnostic output
if (logger.isLoggable(Level.FINE)) {
logger.fine("The solver required " + optimizer.getIterations() + " iterations. The best fit parameters are:");
String logString = "Best parameters:";
for(int i=0; i<bestParameters.length; i++) {
logString += "\tparameter["+i+"]: " + bestParameters[i];
}
logger.fine(logString);
}
return calibrationCovarianceModel;
}
}