/**
   * This methods makes the GCIM results in a string for output to either console or external window
   */
  public String getGcimResultsString() {

    for (int i = 0; i < numIMi; i++) {

      HashMap<TectonicRegionType, ScalarIMR> imriMap = imiAttenRels.get(i);
      ScalarIMR firstIMRiFromMap = TRTUtils.getFirstIMR(imriMap);

      GcimResultsString += "----------------------" + "\n";
      // Get the IM name for printing
      if (firstIMRiFromMap.getIntensityMeasure().getName() == SA_Param.NAME) {
        GcimResultsString +=
            "Results for SA period: "
                + ((SA_Param) firstIMRiFromMap.getIntensityMeasure()).getPeriodParam().getValue()
                + "\n";
      } else if (firstIMRiFromMap.getIntensityMeasure().getName() == SA_InterpolatedParam.NAME) {
        GcimResultsString +=
            "Results for SA Interpolated period: "
                + ((SA_InterpolatedParam) firstIMRiFromMap.getIntensityMeasure())
                    .getPeriodInterpolatedParam()
                    .getValue()
                + "\n";
      } else {
        GcimResultsString +=
            "Results for " + firstIMRiFromMap.getIntensityMeasure().getName() + "\n";
      }
      for (int j = 0; j < zApprox.length; j++) {
        GcimResultsString += imiArray[j][i] + "\t" + cdfIMi_IMjArray[j][i] + "\n";
      }
      GcimResultsString += "----------------------" + "\n";
      GcimResultsString += "\n";
    }
    return GcimResultsString;
  }
  /**
   * This method gets the contribution of each rupture in the ERF toward the probability of IML=iml
   *
   * @throws java.rmi.RemoteException
   * @throws IOException
   */
  public void getRuptureContributions(
      double iml,
      Site site,
      HashMap<TectonicRegionType, ScalarIMR> imrjMap,
      AbstractERF eqkRupForecast,
      double maxDist,
      ArbitrarilyDiscretizedFunc magDistFilter)
      throws java.rmi.RemoteException {

    // IMj the GCIM is to be conditioned on
    this.imrjMap = imrjMap;
    ScalarIMR firstIMRFromMap = TRTUtils.getFirstIMR(imrjMap);

    // Site and earthquake rupture forecast
    this.site = site;
    this.eqkRupForecast = eqkRupForecast;

    // Call DisaggregationCalculator twice for IMj = imj and imj + deltaimj
    DisaggregationCalculator disaggCalc = new DisaggregationCalculator();
    disaggCalc.setStoreRupProbEpsilons(true);
    disaggCalc.disaggregate(iml, site, imrjMap, eqkRupForecast, maxDist, magDistFilter);
    double disaggRupDetails1[][][] = disaggCalc.getRupProbEpsilons();
    double trate_imj = disaggCalc.getTotalRate();

    disaggCalc.disaggregate(iml * 1.01, site, imrjMap, eqkRupForecast, maxDist, magDistFilter);
    double disaggRupDetails2[][][] = disaggCalc.getRupProbEpsilons();
    double trate_imj2 = disaggCalc.getTotalRate();

    // get some initial details from the ERF and sanity check
    int numSources = eqkRupForecast.getNumSources();
    if (numSources != disaggRupDetails1.length || numSources != disaggRupDetails2.length)
      throw new IllegalArgumentException("Error: Num Sources != that from disagg calc");

    pRup_IMj = new double[numSources][];
    epsilonIMj = new double[numSources][];

    // compute the probability given IMj==iml (Equation 7 in Bradley)
    double dtrate_imj = trate_imj - trate_imj2;

    for (int i = 0; i < numSources; ++i) {
      int numRup = eqkRupForecast.getSource(i).getNumRuptures();
      pRup_IMj[i] = new double[numRup];
      epsilonIMj[i] = new double[numRup];
      if (disaggRupDetails1[i] == null) {
        for (int j = 0; j < numRup; j++) {
          pRup_IMj[i][j] = 0;
          epsilonIMj[i][j] = 0;
        }
        continue;
      }
      for (int j = 0; j < numRup; j++) {
        double pRup_imj = disaggRupDetails1[i][j][0];
        double pRup_imj2 = disaggRupDetails2[i][j][0];
        pRup_IMj[i][j] = (pRup_imj * trate_imj - pRup_imj2 * trate_imj2) / dtrate_imj;
        epsilonIMj[i][j] = disaggRupDetails1[i][j][1];
      }
    }
  }
  /**
   * 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;
  }