/**
   * Calculates the delta of a call option under a Black-Scholes model
   *
   * <p>The method also handles cases where the forward and/or option strike is negative and some
   * limit cases where the forward or the option strike is zero. In the case forward = option strike
   * = 0 the method returns 1.0.
   *
   * @param initialStockValue The initial value of the underlying, i.e., the spot.
   * @param riskFreeRate The risk free rate of the bank account numerarie.
   * @param volatility The Black-Scholes volatility.
   * @param optionMaturity The option maturity T.
   * @param optionStrike The option strike.
   * @return The delta of the option
   */
  public static RandomVariableInterface blackScholesOptionDelta(
      RandomVariableInterface initialStockValue,
      RandomVariableInterface riskFreeRate,
      RandomVariableInterface volatility,
      double optionMaturity,
      RandomVariableInterface optionStrike) {
    if (optionMaturity < 0) {
      return initialStockValue.mult(0.0);
    } else {
      // Calculate delta
      RandomVariableInterface dPlus =
          initialStockValue
              .div(optionStrike)
              .log()
              .add(volatility.squared().mult(0.5).add(riskFreeRate).mult(optionMaturity))
              .div(volatility)
              .div(Math.sqrt(optionMaturity));

      UnivariateFunction cummulativeNormal =
          new UnivariateFunction() {
            public double value(double x) {
              return NormalDistribution.cumulativeDistribution(x);
            }
          };
      RandomVariableInterface delta = dPlus.apply(cummulativeNormal);

      return delta;
    }
  }
  /**
   * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0) P, where S
   * follows a log-normal process with constant log-volatility.
   *
   * <p>The model specific quantities are considered to be random variable, i.e., the function may
   * calculate an per-path valuation in a single call.
   *
   * @param forward The forward of the underlying.
   * @param volatility The Black-Scholes volatility.
   * @param optionMaturity The option maturity T.
   * @param optionStrike The option strike. If the option strike is &le; 0.0 the method returns the
   *     value of the forward contract paying S(T)-K in T.
   * @param payoffUnit The payoff unit (e.g., the discount factor)
   * @return Returns the value of a European call option under the Black-Scholes model.
   */
  public static RandomVariableInterface blackScholesGeneralizedOptionValue(
      RandomVariableInterface forward,
      RandomVariableInterface volatility,
      double optionMaturity,
      double optionStrike,
      RandomVariableInterface payoffUnit) {
    if (optionMaturity < 0) {
      return forward.mult(0.0);
    } else {
      RandomVariableInterface dPlus =
          forward
              .div(optionStrike)
              .log()
              .add(volatility.squared().mult(0.5 * optionMaturity))
              .div(volatility)
              .div(Math.sqrt(optionMaturity));
      RandomVariableInterface dMinus = dPlus.sub(volatility.mult(Math.sqrt(optionMaturity)));

      UnivariateFunction cumulativeNormal =
          new UnivariateFunction() {
            public double value(double x) {
              return NormalDistribution.cumulativeDistribution(x);
            }
          };

      RandomVariableInterface valueAnalytic =
          dPlus
              .apply(cumulativeNormal)
              .mult(forward)
              .sub(dMinus.apply(cumulativeNormal).mult(optionStrike))
              .mult(payoffUnit);

      return valueAnalytic;
    }
  }
  /* (non-Javadoc)
   * @see net.finmath.stochastic.RandomVariableInterface#equals(net.finmath.montecarlo.RandomVariable)
   */
  @Override
  public boolean equals(RandomVariableInterface randomVariable) {
    if (this.time != randomVariable.getFiltrationTime()) return false;
    if (this.isDeterministic() && randomVariable.isDeterministic()) {
      return this.valueIfNonStochastic == randomVariable.get(0);
    }

    if (this.isDeterministic() != randomVariable.isDeterministic()) return false;

    for (int i = 0; i < realizations.length; i++)
      if (realizations[i] != randomVariable.get(i)) return false;

    return true;
  }
  /**
   * Calculates the option value of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a
   * normal process with constant volatility, i.e., a Bachelier model.
   *
   * @param forward The forward of the underlying.
   * @param volatility The Bachelier volatility.
   * @param optionMaturity The option maturity T.
   * @param optionStrike The option strike.
   * @param payoffUnit The payoff unit (e.g., the discount factor)
   * @return Returns the value of a European call option under the Bachelier model.
   */
  public static RandomVariableInterface bachelierOptionValue(
      RandomVariableInterface forward,
      RandomVariableInterface volatility,
      double optionMaturity,
      double optionStrike,
      RandomVariableInterface payoffUnit) {
    if (optionMaturity < 0) {
      return forward.mult(0.0);
    } else {
      RandomVariableInterface integratedVolatility = volatility.mult(Math.sqrt(optionMaturity));
      RandomVariableInterface dPlus = forward.sub(optionStrike).div(integratedVolatility);

      UnivariateFunction cummulativeNormal =
          new UnivariateFunction() {
            public double value(double x) {
              return NormalDistribution.cumulativeDistribution(x);
            }
          };
      UnivariateFunction densityNormal =
          new UnivariateFunction() {
            public double value(double x) {
              return NormalDistribution.density(x);
            }
          };
      RandomVariableInterface valueAnalytic =
          dPlus
              .apply(cummulativeNormal)
              .mult(forward.sub(optionStrike))
              .add(dPlus.apply(densityNormal).mult(integratedVolatility))
              .mult(payoffUnit);

      return valueAnalytic;
    }
  }
 public RandomVariableInterface barrier(
     RandomVariableInterface trigger,
     RandomVariableInterface valueIfTriggerNonNegative,
     double valueIfTriggerNegative) {
   return this.barrier(
       trigger,
       valueIfTriggerNonNegative,
       new RandomVariable(valueIfTriggerNonNegative.getFiltrationTime(), valueIfTriggerNegative));
 }
  /* (non-Javadoc)
   * @see net.finmath.stochastic.RandomVariableInterface#subRatio(net.finmath.stochastic.RandomVariableInterface, net.finmath.stochastic.RandomVariableInterface)
   */
  public RandomVariableInterface subRatio(
      RandomVariableInterface numerator, RandomVariableInterface denominator) {
    // Set time of this random variable to maximum of time with respect to which measurability is
    // known.
    double newTime =
        Math.max(Math.max(time, numerator.getFiltrationTime()), denominator.getFiltrationTime());

    if (isDeterministic() && numerator.isDeterministic() && denominator.isDeterministic()) {
      double newValueIfNonStochastic =
          valueIfNonStochastic - (numerator.get(0) / denominator.get(0));
      return new RandomVariable(newTime, newValueIfNonStochastic);
    } else {
      double[] newRealizations =
          new double[Math.max(Math.max(size(), numerator.size()), denominator.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = get(i) - numerator.get(i) / denominator.get(i);
      return new RandomVariable(newTime, newRealizations);
    }
  }
  /**
   * This static method calculated the gamma of a call option under a Black-Scholes model
   *
   * @param initialStockValue The initial value of the underlying, i.e., the spot.
   * @param riskFreeRate The risk free rate of the bank account numerarie.
   * @param volatility The Black-Scholes volatility.
   * @param optionMaturity The option maturity T.
   * @param optionStrike The option strike.
   * @return The gamma of the option
   */
  public static RandomVariableInterface blackScholesOptionGamma(
      RandomVariableInterface initialStockValue,
      RandomVariableInterface riskFreeRate,
      RandomVariableInterface volatility,
      double optionMaturity,
      double optionStrike) {
    if (optionStrike <= 0.0 || optionMaturity <= 0.0) {
      // The Black-Scholes model does not consider it being an option
      return initialStockValue.mult(0.0);
    } else {
      // Calculate gamma
      RandomVariableInterface dPlus =
          initialStockValue
              .div(optionStrike)
              .log()
              .add(volatility.squared().mult(0.5).add(riskFreeRate).mult(optionMaturity))
              .div(volatility)
              .div(Math.sqrt(optionMaturity));

      RandomVariableInterface gamma =
          dPlus
              .squared()
              .mult(-0.5)
              .exp()
              .div(
                  initialStockValue
                      .mult(volatility)
                      .mult(Math.sqrt(2.0 * Math.PI * optionMaturity)));

      return gamma;
    }
  }
  @Override
  public double getAverage(RandomVariableInterface probabilities) {
    if (isDeterministic()) return valueIfNonStochastic;
    if (size() == 0) return Double.NaN;

    /*
     * Kahan summation on (realizations[i] * probabilities.get(i))
     */
    double sum = 0.0;
    double error = 0.0; // Running error compensation
    for (int i = 0; i < realizations.length; i++) {
      double value = realizations[i] * probabilities.get(i) - error; // Error corrected value
      double newSum = sum + value; // New sum
      error = (newSum - sum) - value; // New numerical error
      sum = newSum;
    }
    return sum / realizations.length;
  }
  /* (non-Javadoc)
   * @see net.finmath.stochastic.RandomVariableInterface#floor(net.finmath.stochastic.RandomVariableInterface)
   */
  public RandomVariableInterface floor(RandomVariableInterface randomVariable) {
    // Set time of this random variable to maximum of time with respect to which measurability is
    // known.
    double newTime = Math.max(time, randomVariable.getFiltrationTime());

    if (isDeterministic() && randomVariable.isDeterministic()) {
      double newValueIfNonStochastic = FastMath.max(valueIfNonStochastic, randomVariable.get(0));
      return new RandomVariable(newTime, newValueIfNonStochastic);
    } else if (isDeterministic()) return randomVariable.floor(this);
    else {
      double[] newRealizations = new double[Math.max(size(), randomVariable.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = FastMath.max(realizations[i], randomVariable.get(i));
      return new RandomVariable(newTime, newRealizations);
    }
  }
  @Override
  public double getVariance(RandomVariableInterface probabilities) {
    if (isDeterministic()) return 0.0;
    if (size() == 0) return Double.NaN;

    double average = getAverage(probabilities);

    /*
     * Kahan summation on (realizations[i] - average)^2 * probabilities.get(i)
     */
    double sum = 0.0;
    double errorOfSum = 0.0;
    for (int i = 0; i < realizations.length; i++) {
      double value =
          (realizations[i] - average) * (realizations[i] - average) * probabilities.get(i)
              - errorOfSum;
      double newSum = sum + value;
      errorOfSum = (newSum - sum) - value;
      sum = newSum;
    }
    return sum;
  }
  /* (non-Javadoc)
   * @see net.finmath.stochastic.RandomVariableInterface#addProduct(net.finmath.stochastic.RandomVariableInterface, double)
   */
  public RandomVariableInterface addProduct(RandomVariableInterface factor1, double factor2) {
    // Set time of this random variable to maximum of time with respect to which measurability is
    // known.
    double newTime = Math.max(time, factor1.getFiltrationTime());

    if (isDeterministic() && factor1.isDeterministic()) {
      double newValueIfNonStochastic = valueIfNonStochastic + (factor1.get(0) * factor2);
      return new RandomVariable(newTime, newValueIfNonStochastic);
    } else if (isDeterministic() && !factor1.isDeterministic()) {
      double[] factor1Realizations = factor1.getRealizations();
      double[] newRealizations = new double[Math.max(size(), factor1.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = valueIfNonStochastic + factor1Realizations[i] * factor2;
      return new RandomVariable(newTime, newRealizations);
    } else if (!isDeterministic() && factor1.isDeterministic()) {
      double factor1Value = factor1.get(0);
      double[] newRealizations = new double[Math.max(size(), factor1.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = realizations[i] + factor1Value * factor2;
      return new RandomVariable(newTime, newRealizations);
    } else {
      double[] factor1Realizations = factor1.getRealizations();
      double[] newRealizations = new double[Math.max(size(), factor1.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = realizations[i] + factor1Realizations[i] * factor2;
      return new RandomVariable(newTime, newRealizations);
    }
  }
  /* (non-Javadoc)
   * @see net.finmath.stochastic.RandomVariableInterface#barrier(net.finmath.stochastic.RandomVariableInterface, net.finmath.stochastic.RandomVariableInterface, net.finmath.stochastic.RandomVariableInterface)
   */
  public RandomVariableInterface barrier(
      RandomVariableInterface trigger,
      RandomVariableInterface valueIfTriggerNonNegative,
      RandomVariableInterface valueIfTriggerNegative) {
    // Set time of this random variable to maximum of time with respect to which measurability is
    // known.
    double newTime = Math.max(time, trigger.getFiltrationTime());
    newTime = Math.max(newTime, valueIfTriggerNonNegative.getFiltrationTime());
    newTime = Math.max(newTime, valueIfTriggerNegative.getFiltrationTime());

    if (isDeterministic()
        && trigger.isDeterministic()
        && valueIfTriggerNonNegative.isDeterministic()
        && valueIfTriggerNegative.isDeterministic()) {
      double newValueIfNonStochastic =
          trigger.get(0) >= 0 ? valueIfTriggerNonNegative.get(0) : valueIfTriggerNegative.get(0);
      return new RandomVariable(newTime, newValueIfNonStochastic);
    } else {
      int numberOfPaths =
          Math.max(
              Math.max(trigger.size(), valueIfTriggerNonNegative.size()),
              valueIfTriggerNegative.size());
      double[] newRealizations = new double[numberOfPaths];
      for (int i = 0; i < newRealizations.length; i++) {
        newRealizations[i] =
            trigger.get(i) >= 0.0
                ? valueIfTriggerNonNegative.get(i)
                : valueIfTriggerNegative.get(i);
      }
      return new RandomVariable(newTime, newRealizations);
    }
  }
  /* (non-Javadoc)
   * @see net.finmath.stochastic.RandomVariableInterface#discount(net.finmath.stochastic.RandomVariableInterface, double)
   */
  public RandomVariableInterface discount(RandomVariableInterface rate, double periodLength) {
    // Set time of this random variable to maximum of time with respect to which measurability is
    // known.
    double newTime = Math.max(time, rate.getFiltrationTime());

    if (isDeterministic() && rate.isDeterministic()) {
      double newValueIfNonStochastic = valueIfNonStochastic / (1 + rate.get(0) * periodLength);
      return new RandomVariable(newTime, newValueIfNonStochastic);
    } else if (isDeterministic() && !rate.isDeterministic()) {
      double[] rateRealizations = rate.getRealizations();
      double[] newRealizations = new double[Math.max(size(), rate.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = valueIfNonStochastic / (1.0 + rateRealizations[i] * periodLength);
      return new RandomVariable(newTime, newRealizations);
    } else if (!isDeterministic() && rate.isDeterministic()) {
      double rateValue = rate.get(0);
      double[] newRealizations = new double[Math.max(size(), rate.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = realizations[i] / (1.0 + rateValue * periodLength);
      return new RandomVariable(newTime, newRealizations);
    } else {
      double[] rateRealizations = rate.getRealizations();
      double[] newRealizations = new double[Math.max(size(), rate.size())];
      for (int i = 0; i < newRealizations.length; i++)
        newRealizations[i] = realizations[i] / (1.0 + rateRealizations[i] * periodLength);
      return new RandomVariable(newTime, newRealizations);
    }
  }
 /**
  * Create a random variable from a given other implementation of <code>RandomVariableInterface
  * </code>.
  *
  * @param value Object implementing <code>RandomVariableInterface</code>.
  */
 public RandomVariable(RandomVariableInterface value) {
   super();
   this.time = value.getFiltrationTime();
   this.realizations = value.isDeterministic() ? null : value.getRealizations();
   this.valueIfNonStochastic = value.isDeterministic() ? value.get(0) : Double.NaN;
 }
 @Override
 public RandomVariableInterface applyStateSpaceTransform(
     int componentIndex, RandomVariableInterface randomVariable) {
   return randomVariable.exp();
 }