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
  private void convolveMatrices(Beagle beagle, List<Deque<Integer>> convolutionList) {

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

    while (convolutionList.size() > 0) {
      int[] firstConvolutionBuffers = new int[nodeCount];
      int[] secondConvolutionBuffers = new int[nodeCount];
      int[] resultConvolutionBuffers = new int[nodeCount];
      int operationsCount = 0;

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

      for (Deque<Integer> convolve : convolutionList) {

        if (convolve.size() > 3) {
          firstConvolutionBuffers[operationsCount] = convolve.pop();
          secondConvolutionBuffers[operationsCount] = convolve.pop();

          int buffer;
          boolean done;

          do {
            done = true;

            buffer = popAvailableBuffer();

            if (buffer < 0) {
              // no buffers available
              //                        throw new RuntimeException("All out of buffers");

              // we have run out of buffers, process what we have and continue...
              if (DEBUG) {
                System.out.println("Ran out of buffers for convolving - computing current list.");
                System.out.print("Convolving " + operationsCount + " matrices:");
                for (int i = 0; i < operationsCount; i++) {
                  System.out.print(
                      " "
                          + firstConvolutionBuffers[i]
                          + "*"
                          + secondConvolutionBuffers[i]
                          + "->"
                          + resultConvolutionBuffers[i]);
                }
                System.out.println();
              }

              if (operationsCount > 0) {

                convolveAndRelease(
                    beagle,
                    firstConvolutionBuffers,
                    secondConvolutionBuffers,
                    resultConvolutionBuffers,
                    operationsCount);

                // copy the uncompleted operation back down to the beginning of the operations list
                firstConvolutionBuffers[0] = firstConvolutionBuffers[operationsCount];
                secondConvolutionBuffers[0] = secondConvolutionBuffers[operationsCount];

                // reset the operation count
                operationsCount = 0;
                done = false;

                // there should be enough spare buffers to get a resultConvolutionBuffer for this
                // operation now
              } else {
                // only one partially setup operation so there would be none to free up
                // in this case we will use the reserve buffer
                resultConvolutionBuffers[operationsCount] = getReserveBuffer();
                convolveAndRelease(
                    beagle,
                    firstConvolutionBuffers,
                    secondConvolutionBuffers,
                    resultConvolutionBuffers,
                    1);
                convolve.push(getReserveBuffer());
                done = true; // break out of the do loop
              }
            }
          } while (!done);

          if (buffer >= 0) {
            // if the buffer is still negative then the loop above will have used the reserve buffer
            // to complete the convolution.
            resultConvolutionBuffers[operationsCount] = buffer;
            convolve.push(buffer);
            operationsCount++;
          }

        } else if (convolve.size() == 3) {
          firstConvolutionBuffers[operationsCount] = convolve.pop();
          secondConvolutionBuffers[operationsCount] = convolve.pop();
          resultConvolutionBuffers[operationsCount] = convolve.pop();
          operationsCount++;
        } else {
          throw new RuntimeException("Unexpected convolve list size");
        }

        if (convolve.size() == 0) {
          empty.add(convolve);
        }
      }

      if (DEBUG) {
        System.out.print("Convolving " + operationsCount + " matrices:");
        for (int i = 0; i < operationsCount; i++) {
          System.out.print(
              " "
                  + firstConvolutionBuffers[i]
                  + "*"
                  + secondConvolutionBuffers[i]
                  + "->"
                  + resultConvolutionBuffers[i]);
        }
        System.out.println();
      }

      convolveAndRelease(
          beagle,
          firstConvolutionBuffers,
          secondConvolutionBuffers,
          resultConvolutionBuffers,
          operationsCount);

      convolutionList.removeAll(empty);
    }

    if (MEASURE_RUN_TIME) {
      timer.stop();
      double timeInSeconds = timer.toSeconds();
      convolveTime += timeInSeconds;
    }
  } // END: convolveTransitionMatrices