private void convolveAndRelease(
      Beagle beagle,
      int[] firstConvolutionBuffers,
      int[] secondConvolutionBuffers,
      int[] resultConvolutionBuffers,
      int operationsCount) {

    if (RUN_IN_SERIES) {
      if (operationsCount > 1) {
        throw new RuntimeException("Unable to convolve matrices in series");
      }
    }

    beagle.convolveTransitionMatrices(
        firstConvolutionBuffers, // A
        secondConvolutionBuffers, // B
        resultConvolutionBuffers, // C
        operationsCount // count
        );

    for (int i = 0; i < operationsCount; i++) {
      if (firstConvolutionBuffers[i] >= matrixBufferHelper.getBufferCount()
          && firstConvolutionBuffers[i] != reserveBufferIndex) {
        pushAvailableBuffer(firstConvolutionBuffers[i]);
      }
      if (secondConvolutionBuffers[i] >= matrixBufferHelper.getBufferCount()
          && secondConvolutionBuffers[i] != reserveBufferIndex) {
        pushAvailableBuffer(secondConvolutionBuffers[i]);
      }
    }
  } // END: convolveAndRelease
  public void updateSubstitutionModels(Beagle beagle) {
    for (int i = 0; i < eigenCount; i++) {
      eigenBufferHelper.flipOffset(i);

      EigenDecomposition ed = substitutionModelList.get(i).getEigenDecomposition();

      beagle.setEigenDecomposition(
          eigenBufferHelper.getOffsetIndex(i),
          ed.getEigenVectors(),
          ed.getInverseEigenVectors(),
          ed.getEigenValues());
    }
  }
  public SubstitutionModelDelegate(Tree tree, BranchModel branchModel, int bufferPoolSize) {

    if (MEASURE_RUN_TIME) {
      updateTime = 0;
      convolveTime = 0;
    }

    this.tree = tree;

    this.substitutionModelList = branchModel.getSubstitutionModels();

    this.branchModel = branchModel;

    eigenCount = substitutionModelList.size();
    nodeCount = tree.getNodeCount();

    // two eigen buffers for each decomposition for store and restore.
    eigenBufferHelper = new BufferIndexHelper(eigenCount, 0);

    // two matrices for each node less the root
    matrixBufferHelper = new BufferIndexHelper(nodeCount, 0);

    this.extraBufferCount =
        branchModel.requiresMatrixConvolution()
            ? (bufferPoolSize > 0 ? bufferPoolSize : BUFFER_POOL_SIZE_DEFAULT)
            : 0;

    if (branchModel.requiresMatrixConvolution() && this.extraBufferCount < eigenCount) {
      throw new RuntimeException(
          "SubstitutionModelDelegate requires at least "
              + eigenCount
              + " extra buffers to convolve matrices");
    }

    for (int i = 0; i < extraBufferCount; i++) {
      pushAvailableBuffer(i + matrixBufferHelper.getBufferCount());
    }

    // one extra created as a reserve
    // which is used to free up buffers when the avail stack is empty.
    reserveBufferIndex = matrixBufferHelper.getBufferCount() + extraBufferCount;

    if (DEBUG) {
      System.out.println("Creating reserve buffer with index: " + reserveBufferIndex);
    }
  } // END: Constructor
  private void computeTransitionMatrices(
      Beagle beagle, int[][] probabilityIndices, double[][] edgeLengths, int[] counts) {

    Timer timer;
    if (MEASURE_RUN_TIME) {
      timer = new Timer();
      timer.start();
    }

    if (DEBUG) {
      System.out.print("Computing matrices:");
    }

    for (int i = 0; i < eigenCount; i++) {
      if (DEBUG) {
        for (int j = 0; j < counts[i]; j++) {
          //                    System.out.print(" " + probabilityIndices[i][j]);
          System.out.print(" " + probabilityIndices[i][j] + " (" + edgeLengths[i][j] + ")");
        }
      }
      if (counts[i] > 0) {
        beagle.updateTransitionMatrices(
            eigenBufferHelper.getOffsetIndex(i),
            probabilityIndices[i],
            null, // firstDerivativeIndices
            null, // secondDerivativeIndices
            edgeLengths[i],
            counts[i]);
      }
    }

    if (DEBUG) {
      System.out.println();
    }

    if (MEASURE_RUN_TIME) {
      timer.stop();
      double timeInSeconds = timer.toSeconds();
      updateTime += timeInSeconds;
    }
  } // END: computeTransitionMatrices
 public void restoreState() {
   eigenBufferHelper.restoreState();
   matrixBufferHelper.restoreState();
 }
 public int getMatrixIndex(int branchIndex) {
   return matrixBufferHelper.getOffsetIndex(branchIndex);
 }
 public void flipMatrixBuffer(int branchIndex) {
   matrixBufferHelper.flipOffset(branchIndex);
 }
  public void updateTransitionMatrices(
      Beagle beagle, int[] branchIndices, double[] edgeLength, int updateCount) {

    int[][] probabilityIndices = new int[eigenCount][updateCount];
    double[][] edgeLengths = new double[eigenCount][updateCount];

    int[] counts = new int[eigenCount];

    List<Deque<Integer>> convolutionList = new ArrayList<Deque<Integer>>();

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

      BranchModel.Mapping mapping =
          branchModel.getBranchModelMapping(tree.getNode(branchIndices[i]));
      int[] order = mapping.getOrder();
      double[] weights = mapping.getWeights();

      if (order.length == 1) {
        int k = order[0];
        probabilityIndices[k][counts[k]] = matrixBufferHelper.getOffsetIndex(branchIndices[i]);
        edgeLengths[k][counts[k]] = edgeLength[i];
        counts[k]++;
      } else {
        double sum = 0.0;
        for (double w : weights) {
          sum += w;
        }

        if (getAvailableBufferCount() < order.length) {
          // too few buffers available, process what we have and continue...
          computeTransitionMatrices(beagle, probabilityIndices, edgeLengths, counts);
          convolveMatrices(beagle, convolutionList);

          // reset the counts
          for (int k = 0; k < eigenCount; k++) {
            counts[k] = 0;
          }
        }

        Deque<Integer> bufferIndices = new ArrayDeque<Integer>();
        for (int j = 0; j < order.length; j++) {

          int buffer = popAvailableBuffer();

          if (buffer < 0) {
            // no buffers available
            throw new RuntimeException(
                "Ran out of buffers for transition matrices - computing current list.");
          }

          int k = order[j];
          probabilityIndices[k][counts[k]] = buffer;
          edgeLengths[k][counts[k]] = weights[j] * edgeLength[i] / sum;
          //                    edgeLengths[k][counts[k]] = weights[j] ;
          counts[k]++;

          bufferIndices.add(buffer);
        }
        bufferIndices.add(matrixBufferHelper.getOffsetIndex(branchIndices[i]));

        convolutionList.add(bufferIndices);
      } // END: if convolution needed
    } // END: i loop

    computeTransitionMatrices(beagle, probabilityIndices, edgeLengths, counts);
    convolveMatrices(beagle, convolutionList);
  } // END: updateTransitionMatrices
 public int getMatrixBufferCount() {
   // plus one for the reserve buffer
   return matrixBufferHelper.getBufferCount() + extraBufferCount + 1;
 }
 public int getEigenBufferCount() {
   return eigenBufferHelper.getBufferCount();
 }