/**
   * this function performs computes the linear correlation between IMi and IMk given IMj which is
   * needed to form the full covariance/correlation matrix of IM|IMj Returns true if it was
   * successfully else return false
   *
   * @param imiNumber - the imi counter used for storing results in array
   * @param imkNumber - the imk counter used for storing results in array
   * @param imriMap: selected IMRi object (that for which the distribution is desired i.e. IMi)
   * @param imijCorrRelMap: selected correlation object for IMi and IMj
   * @param imrkMap: selected IMRk object (that for which the distribution is desired i.e. IMk)
   * @param imkjCorrRelMap: selected correlation object for IMk and IMj
   * @param maxDist: maxDist of sources to consider
   * @param magDistFilter: Magnitude-Distance filter for sources
   * @return boolean
   */
  public boolean getIMiIMk_IMj_Correlation(
      int imiNumber,
      int imkNumber,
      //			HashMap<TectonicRegionType, ScalarIntensityMeasureRelationshipAPI> imriMap,
      HashMap<TectonicRegionType, ImCorrelationRelationship> imijCorrRelMap,
      //			HashMap<TectonicRegionType, ScalarIntensityMeasureRelationshipAPI> imrkMap,
      HashMap<TectonicRegionType, ImCorrelationRelationship> imkjCorrRelMap,
      double maxDist,
      ArbitrarilyDiscretizedFunc magDistFilter) {

    // Set the site in imri
    //		for (ScalarIntensityMeasureRelationshipAPI imri:imriMap.values()) {
    //			imri.resetParameterEventListeners();
    //			imri.setUserMaxDistance(maxDist);
    //			imri.setSite(site);
    //		}
    //
    //		//Set the site in imrk
    //		for (ScalarIntensityMeasureRelationshipAPI imrk:imrkMap.values()) {
    //			imrk.resetParameterEventListeners();
    //			imrk.setUserMaxDistance(maxDist);
    //			imrk.setSite(site);
    //		}

    // Get total number of sources
    int numSources = eqkRupForecast.getNumSources();

    //		double[][] mulnIMi_RupIMj = new double[numSources][];
    //		double[][] stdlnIMi_RupIMj = new double[numSources][];
    //		double mulnIMi_Rup, stdlnIMi_Rup, mulnIMi_IMj, varlnIMi_IMj, stdlnIMi_IMj, cdfIMi_RupIMj;

    //		double[][] mulnIMk_RupIMj = new double[numSources][];
    //		double[][] stdlnIMk_RupIMj = new double[numSources][];
    //		double mulnIMk_Rup, stdlnIMk_Rup, mulnIMk_IMj, varlnIMk_IMj, stdlnIMk_IMj, cdfIMk_RupIMj;

    boolean includeMagDistFilter;
    if (magDistFilter == null) includeMagDistFilter = false;
    else includeMagDistFilter = true;
    double magThresh = 0.0;

    int numRupRejected = 0;
    // loop over all of the ruptures
    for (int i = 0; i < numSources; i++) {
      // get source and all its details
      ProbEqkSource source = eqkRupForecast.getSource(i);

      int numRuptures = eqkRupForecast.getNumRuptures(i);
      //			mulnIMi_RupIMj[i] = new double[numRuptures];
      //			stdlnIMi_RupIMj[i] = new double[numRuptures];

      //			mulnIMk_RupIMj[i] = new double[numRuptures];
      //			stdlnIMk_RupIMj[i] = new double[numRuptures];

      // check the distance of the source
      double distance = source.getMinDistance(site);
      if (distance > maxDist) {
        continue;
      }

      // set the IMR according to the tectonic region of the source (if there is more than one)
      TectonicRegionType trt = source.getTectonicRegionType();
      //			ScalarIntensityMeasureRelationshipAPI imri = TRTUtils.getIMRForTRT(imriMap, trt);
      ImCorrelationRelationship imijCorrRel = Utils.getIMCorrRelForTRT(imijCorrRelMap, trt);
      //
      //			ScalarIntensityMeasureRelationshipAPI imrk = TRTUtils.getIMRForTRT(imrkMap, trt);
      ImCorrelationRelationship imkjCorrRel = Utils.getIMCorrRelForTRT(imkjCorrRelMap, trt);

      // compute the correlation coefficient between lnIMi and lnIMj for the given source
      double rho_lnIMilnIMj = imijCorrRel.getImCorrelation();

      // compute the correlation coefficient between lnIMk and lnIMj for the given source
      double rho_lnIMklnIMj = imkjCorrRel.getImCorrelation();

      // loop over ruptures
      for (int j = 0; j < numRuptures; j++) {

        ProbEqkRupture rupture = source.getRupture(j);

        // apply magThreshold if we're to use the mag-dist cutoff filter
        if (includeMagDistFilter && rupture.getMag() < magThresh) {
          numRupRejected += 1;
          continue;
        }

        // set the rupture in the imr
        //				imri.setEqkRupture(rupture);
        //				imrk.setEqkRupture(rupture);

        // get the unconditional mean, stdDev of lnIMi for the given rupture
        //				mulnIMi_Rup = imri.getMean();
        //				stdlnIMi_Rup = imri.getStdDev();

        //				mulnIMk_Rup = imrk.getMean();
        //				stdlnIMk_Rup = imrk.getStdDev();

        // get the conditional mean, stdDev of lnIMi for the given rupture
        //				mulnIMi_RupIMj[i][j] = mulnIMi_Rup + stdlnIMi_Rup * rho_lnIMilnIMj *epsilonIMj[i][j];
        //				stdlnIMi_RupIMj[i][j] = stdlnIMi_Rup * Math.sqrt(1-Math.pow(rho_lnIMilnIMj,2.0));

        // get the conditional mean, stdDev of lnIMk for the given rupture
        //				mulnIMk_RupIMj[i][j] = mulnIMk_Rup + stdlnIMk_Rup * rho_lnIMklnIMj *epsilonIMj[i][j];
        //				stdlnIMk_RupIMj[i][j] = stdlnIMk_Rup * Math.sqrt(1-Math.pow(rho_lnIMklnIMj,2.0));

        // compute the correlation between IMi and IMj given IMk

      }
    }

    // compute the conditional mean and variance of lnIMi and lnIMk (independent of Rup)
    //		mulnIMi_IMj=0;
    //		mulnIMk_IMj=0;

    // loop over all of the ruptures
    for (int i = 0; i < numSources; i++) {
      int numRuptures = eqkRupForecast.getNumRuptures(i);
      for (int j = 0; j < numRuptures; j++) {
        //				mulnIMi_IMj += mulnIMi_RupIMj[i][j] * pRup_IMj[i][j];
      }
    }

    //		varlnIMi_IMj=0;
    // loop over all of the ruptures
    for (int i = 0; i < numSources; i++) {
      int numRuptures = eqkRupForecast.getNumRuptures(i);
      for (int j = 0; j < numRuptures; j++) {
        //				varlnIMi_IMj += (Math.pow(stdlnIMi_RupIMj[i][j],2.0) +
        // Math.pow(mulnIMi_RupIMj[i][j]-mulnIMi_IMj, 2.0) ) * pRup_IMj[i][j];
      }
    }
    //		stdlnIMi_IMj = Math.sqrt(varlnIMi_IMj);

    rhoIMiIMk_IMj[imiNumber][imkNumber] = 0.0;
    return true;
  }
  /**
   * this function performs the GCIM computations to obtain the conditional distribution of a single
   * input intensity measure. Returns true if it was successfully else return false
   *
   * @param imiNumber - the imi counter used for storing results in array
   * @param imri: selected IMRi object (that for which the distribution is desired i.e. IMi)
   * @param imCorrelationRelationship: selected correlation object for IMi and IMj
   * @param maxDist: maxDist of sources to consider
   * @param magDistFilter: Magnitude-Distance filter for sources
   * @return boolean
   */
  public boolean getSingleGcim(
      int imiNumber,
      HashMap<TectonicRegionType, ScalarIMR> imriMap,
      HashMap<TectonicRegionType, ImCorrelationRelationship> imijCorrRelMap,
      double maxDist,
      ArbitrarilyDiscretizedFunc magDistFilter) {

    // Set the site in imri
    for (ScalarIMR imri : imriMap.values()) {
      imri.resetParameterEventListeners();
      imri.setUserMaxDistance(maxDist);
      imri.setSite(site);
    }

    // Get total number of sources
    int numSources = eqkRupForecast.getNumSources();

    double[][] mulnIMi_RupIMj = new double[numSources][];
    double[][] stdlnIMi_RupIMj = new double[numSources][];
    double mulnIMi_Rup, stdlnIMi_Rup, mulnIMi_IMj, varlnIMi_IMj, stdlnIMi_IMj, cdfIMi_RupIMj;

    mulnIMi_RupIMjArray[imiNumber] = new double[numSources][];
    stdlnIMi_RupIMjArray[imiNumber] = new double[numSources][];

    boolean includeMagDistFilter;
    if (magDistFilter == null) includeMagDistFilter = false;
    else includeMagDistFilter = true;
    double magThresh = 0.0;

    int numRupRejected = 0;
    // loop over all of the ruptures
    for (int i = 0; i < numSources; i++) {
      // get source and all its details
      ProbEqkSource source = eqkRupForecast.getSource(i);

      int numRuptures = eqkRupForecast.getNumRuptures(i);
      mulnIMi_RupIMj[i] = new double[numRuptures];
      stdlnIMi_RupIMj[i] = new double[numRuptures];

      mulnIMi_RupIMjArray[imiNumber][i] = new double[numRuptures];
      stdlnIMi_RupIMjArray[imiNumber][i] = new double[numRuptures];

      // check the distance of the source
      double distance = source.getMinDistance(site);
      if (distance > maxDist) {
        continue;
      }

      // set the IMR according to the tectonic region of the source (if there is more than one)
      TectonicRegionType trt = source.getTectonicRegionType();
      ScalarIMR imri = TRTUtils.getIMRforTRT(imriMap, trt);
      ImCorrelationRelationship imijCorrRel = Utils.getIMCorrRelForTRT(imijCorrRelMap, trt);

      // compute the correlation coefficient between lnIMi and lnIMj for the given source
      double rho_lnIMilnIMj = imijCorrRel.getImCorrelation();

      // loop over ruptures
      for (int j = 0; j < numRuptures; j++) {

        ProbEqkRupture rupture = source.getRupture(j);

        // apply magThreshold if we're to use the mag-dist cutoff filter
        if (includeMagDistFilter && rupture.getMag() < magThresh) {
          numRupRejected += 1;
          continue;
        }

        // set the rupture in the imr
        imri.setEqkRupture(rupture);

        // get the unconditional mean, stdDev of lnIMi for the given rupture
        mulnIMi_Rup = imri.getMean();
        stdlnIMi_Rup = imri.getStdDev();

        // get the conditional mean, stdDev of lnIMi for the given rupture
        mulnIMi_RupIMj[i][j] = mulnIMi_Rup + stdlnIMi_Rup * rho_lnIMilnIMj * epsilonIMj[i][j];
        stdlnIMi_RupIMj[i][j] = stdlnIMi_Rup * Math.sqrt(1 - Math.pow(rho_lnIMilnIMj, 2.0));

        // store in array
        mulnIMi_RupIMjArray[imiNumber][i][j] = mulnIMi_RupIMj[i][j];
        stdlnIMi_RupIMjArray[imiNumber][i][j] = stdlnIMi_RupIMj[i][j];
      }
    }

    // compute the conditional mean and variance of lnIMi (independent of Rup)
    mulnIMi_IMj = 0;
    // loop over all of the ruptures
    for (int i = 0; i < numSources; i++) {
      int numRuptures = eqkRupForecast.getNumRuptures(i);
      for (int j = 0; j < numRuptures; j++) {
        mulnIMi_IMj += mulnIMi_RupIMj[i][j] * pRup_IMj[i][j];
      }
    }

    varlnIMi_IMj = 0;
    // loop over all of the ruptures
    for (int i = 0; i < numSources; i++) {
      int numRuptures = eqkRupForecast.getNumRuptures(i);
      for (int j = 0; j < numRuptures; j++) {
        varlnIMi_IMj +=
            (Math.pow(stdlnIMi_RupIMj[i][j], 2.0)
                    + Math.pow(mulnIMi_RupIMj[i][j] - mulnIMi_IMj, 2.0))
                * pRup_IMj[i][j];
      }
    }
    stdlnIMi_IMj = Math.sqrt(varlnIMi_IMj);

    // Initially assuming that that lnIMi|IMj=imj is lognormal determine the range of
    // IMi values to compute the CDF of lnIMi|IMj=imj for, then compute this cdf
    for (int n = 0; n < zApprox.length; n++) {
      imiArray[n][imiNumber] = Math.exp(mulnIMi_IMj + zApprox[n] * stdlnIMi_IMj);

      cdfIMi_IMjArray[n][imiNumber] = 0.0;
      // loop over all of the ruptures
      for (int i = 0; i < numSources; i++) {
        int numRuptures = eqkRupForecast.getNumRuptures(i);
        for (int j = 0; j < numRuptures; j++) {
          double z =
              (Math.log(imiArray[n][imiNumber]) - mulnIMi_RupIMj[i][j]) / stdlnIMi_RupIMj[i][j];
          cdfIMi_RupIMj = GaussianDistCalc.getCDF(z);
          cdfIMi_IMjArray[n][imiNumber] += cdfIMi_RupIMj * pRup_IMj[i][j];
        }
      }
    }

    return true;
  }