public double merge(
      int index1,
      int index2,
      int clusterIndex1,
      int clusterIndex2,
      int[] cluster1Sites,
      int[] cluster2Sites) {

    double logqMerge = 0.0;

    HashMap<Integer, Integer> siteMap = new HashMap<Integer, Integer>();

    // The value of the merged cluster will have that of cluster 2 before the merge.
    QuietRealParameter mergedParam = paramList.getParameter(clusterIndex2);
    QuietRealParameter mergedModel = modelList.getParameter(clusterIndex2);
    QuietRealParameter mergedFreqs = freqsList.getParameter(clusterIndex2);
    QuietRealParameter mergedRates = ratesList.getParameter(clusterIndex2);

    // Create a vector that combines the site indices of the two clusters
    int[] mergedClusterSites = new int[cluster1Sites.length + cluster2Sites.length - 2];

    int k = 0;
    for (int i = 0; i < cluster1Sites.length; i++) {
      // Point every member in cluster 1 to cluster 2
      // paramPointers.point(cluster1Sites[i],mergedParam);
      // modelPointers.point(cluster1Sites[i],mergedModel);
      // freqsPointers.point(cluster1Sites[i],mergedFreqs);
      // ratesPointers.point(cluster1Sites[i],mergedRates);

      if (cluster1Sites[i] != index1) {
        // For all members that are not index 1,
        // record the cluster in which they have been before the merge,
        // and assign them to the combined vector.
        siteMap.put(cluster1Sites[i], clusterIndex1);
        mergedClusterSites[k++] = cluster1Sites[i];
      }
    }

    for (int i = 0; i < cluster2Sites.length; i++) {
      // All members in cluster 2 remains in cluster2 so no new pointer assignments
      if (cluster2Sites[i] != index2) {
        // For all members that are not index 2,
        // record the cluster in which they have been before the merge,
        // and assign them to the combined vector.
        siteMap.put(cluster2Sites[i], clusterIndex2);
        mergedClusterSites[k++] = cluster2Sites[i];
      }
    }

    try {

      // Create a weight vector of patterns to inform the temporary tree likelihood
      // which set of pattern likelihoods are to be computed.
      // int[] tempWeights = dpTreeLikelihood.getClusterWeights(clusterIndex1);
      /*int[] tempWeights = new int[tempLikelihood.m_data.get().getPatternCount()];
      for(int i = 0; i < cluster1Sites.length; i++){
          int patIndex = tempLikelihood.m_data.get().getPatternIndex(cluster1Sites[i]);
          tempWeights[patIndex] = 1;
      }
      tempLikelihood.setPatternWeights(tempWeights);
      double[] cluster1SitesCluster2ParamLogLik = tempLikelihood.calculateLogP(
              mergedParam,
              mergedModel,
              mergedFreqs,
              mergedRates,
              cluster1Sites,
              index1
      ); */
      k = 0;
      int[] sCluster1Sites = new int[cluster1Sites.length - 1];
      for (int i = 0; i < cluster1Sites.length; i++) {
        if (cluster1Sites[i] != index1) {
          sCluster1Sites[k++] = cluster1Sites[i];
        }
      }
      tempLikelihood.setupPatternWeightsFromSites(sCluster1Sites);
      double[] cluster1SitesCluster2ParamLogLik =
          tempLikelihood.calculateLogP(
              mergedParam, mergedModel, mergedFreqs, mergedRates, sCluster1Sites);

      // tempWeights = dpTreeLikelihood.getClusterWeights(clusterIndex2);
      /*tempWeights = new int[tempLikelihood.m_data.get().getPatternCount()];
      for(int i = 0; i < cluster2Sites.length; i++){
          int patIndex = tempLikelihood.m_data.get().getPatternIndex(cluster2Sites[i]);
          tempWeights[patIndex] = 1;
      }
      tempLikelihood.setPatternWeights(tempWeights);
      RealParameter removedParam = paramList.getParameter(clusterIndex1);
      RealParameter removedModel = modelList.getParameter(clusterIndex1);
      RealParameter removedFreqs = freqsList.getParameter(clusterIndex1);
      RealParameter removedRates = ratesList.getParameter(clusterIndex1);
      double[] cluster2SitesCluster1ParamLogLik = tempLikelihood.calculateLogP(
              removedParam,
              removedModel,
              removedFreqs,
              removedRates,
              cluster2Sites,
              index2
      );*/
      k = 0;
      int[] sCluster2Sites = new int[cluster2Sites.length - 1];
      for (int i = 0; i < cluster2Sites.length; i++) {
        if (cluster2Sites[i] != index2) {
          sCluster2Sites[k++] = cluster2Sites[i];
        }
      }
      tempLikelihood.setupPatternWeightsFromSites(sCluster2Sites);
      RealParameter removedParam = paramList.getParameter(clusterIndex1);
      RealParameter removedModel = modelList.getParameter(clusterIndex1);
      RealParameter removedFreqs = freqsList.getParameter(clusterIndex1);
      RealParameter removedRates = ratesList.getParameter(clusterIndex1);
      double[] cluster2SitesCluster1ParamLogLik =
          tempLikelihood.calculateLogP(
              removedParam, removedModel, removedFreqs, removedRates, sCluster2Sites);

      // System.out.println("populate logLik1:");
      double[] logLik1 = new double[mergedClusterSites.length];
      for (int i = 0; i < (cluster1Sites.length - 1); i++) {
        // System.out.println(clusterIndex1+" "+mergedClusterSites[i]);

        logLik1[i] = dpTreeLikelihood.getSiteLogLikelihood(clusterIndex1, mergedClusterSites[i]);
      }
      System.arraycopy(
          cluster2SitesCluster1ParamLogLik,
          0,
          logLik1,
          cluster1Sites.length - 1,
          cluster2SitesCluster1ParamLogLik.length);

      double[] logLik2 = new double[mergedClusterSites.length];
      System.arraycopy(
          cluster1SitesCluster2ParamLogLik, 0, logLik2, 0, cluster1SitesCluster2ParamLogLik.length);

      // System.out.println("populate logLik2:");
      for (int i = cluster1SitesCluster2ParamLogLik.length; i < logLik2.length; i++) {
        // System.out.println(clusterIndex2+"
        // "+mergedClusterSites[i-cluster1SitesCluster2ParamLogLik.length]);
        logLik2[i] = dpTreeLikelihood.getSiteLogLikelihood(clusterIndex2, mergedClusterSites[i]);
      }

      double[] lik1 = new double[logLik1.length];
      double[] lik2 = new double[logLik2.length];

      // scale it so it may be more accuate
      double minLog;
      /*for(int i = 0; i < logLik1.length; i++){
          minLog = Math.min(logLik1[i],logLik2[i]);
          if(minLog == logLik1[i]){
              lik1[i] = 1.0;
              lik2[i] = Math.exp(logLik2[i] - minLog);
          }else{
              lik1[i] = Math.exp(logLik1[i] - minLog);
              lik2[i] = 1.0;
          }

      }*/

      for (int i = 0; i < logLik1.length; i++) {

        lik1[i] = Math.exp(logLik1[i]);
        lik2[i] = Math.exp(logLik2[i]);
        // System.out.println(lik1[i]+" "+lik2[i]);
      }

      // Create a set of indices for random permutation
      int[] shuffle = new int[mergedClusterSites.length];
      for (int i = 0; i < shuffle.length; i++) {
        shuffle[i] = i;
      }
      Randomizer.shuffle(shuffle);

      int cluster1Count = 1;
      int cluster2Count = 1;
      int cluster;
      double psi1, psi2, cluster1Prob;
      for (int i = 0; i < mergedClusterSites.length; i++) {

        cluster = siteMap.get(mergedClusterSites[shuffle[i]]);
        psi1 = cluster1Count * lik1[shuffle[i]];
        psi2 = cluster2Count * lik2[shuffle[i]];

        /*testCorrectness(i,cluster,
        clusterIndex1,clusterIndex2,shuffle, mergedClusterSites,
         lik1,lik2);*/

        cluster1Prob = psi1 / (psi1 + psi2);
        // System.out.println(cluster1Prob);
        if (cluster == clusterIndex1) {
          logqMerge += Math.log(cluster1Prob);
          cluster1Count++;

        } else if (cluster == clusterIndex2) {
          logqMerge += Math.log(1 - cluster1Prob);
          cluster2Count++;

        } else {
          throw new RuntimeException("Something is wrong.");
        }
      }

      logqMerge += // paramBaseDistr.calcLogP(removedParam)
          mergeValue(removedParam, mergedParam, paramBaseDistr)
              // + modelBaseDistr.calcLogP(removedModel)
              + mergeDiscreteValue(removedModel, mergedModel, modelDistrInput.get())
              + freqsBaseDistr.calcLogP(removedFreqs)
              // + ratesBaseDistr.calcLogP(removedRates);
              + mergeValueInLogSpace(removedRates, mergedRates, ratesBaseDistr);
      if (logqMerge > Double.NEGATIVE_INFINITY) {
        paramList.mergeParameter(clusterIndex1, clusterIndex2);
        modelList.mergeParameter(clusterIndex1, clusterIndex2);
        freqsList.mergeParameter(clusterIndex1, clusterIndex2);
        ratesList.mergeParameter(clusterIndex1, clusterIndex2);
        for (int i = 0; i < cluster1Sites.length; i++) {
          // Point every member in cluster 1 to cluster 2
          paramPointers.point(cluster1Sites[i], mergedParam);
          modelPointers.point(cluster1Sites[i], mergedModel);
          freqsPointers.point(cluster1Sites[i], mergedFreqs);
          ratesPointers.point(cluster1Sites[i], mergedRates);
        }
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    return logqMerge;
  }