@Override
  public final void binarySpmv(
      final CartesianProductVector cartesianProductVector, final ChartCell chartCell) {

    final Future<?>[] futures = new Future[grammarThreads];
    final PackedArrayChart.TemporaryChartCell[] temporaryCells =
        threadLocalTemporaryCellArrays.get();

    // Iterate over binary grammar segments
    for (int i = 0; i < grammarThreads; i++) {
      final int segmentStart = binaryRowSegments[i];
      final int segmentEnd = binaryRowSegments[i + 1];
      final PackedArrayChart.TemporaryChartCell tmpCell = temporaryCells[i];

      if (cellSelector.hasCellConstraints()
          && cellSelector.isCellOnlyFactored(chartCell.start(), chartCell.end())) {
        futures[i] =
            threadPool.submit(
                new Runnable() {

                  @Override
                  public void run() {
                    tmpCell.clear();
                    // Multiply by the factored grammar rule matrix
                    binarySpmvMultiply(
                        cartesianProductVector,
                        grammar.factoredCscBinaryPopulatedColumns,
                        grammar.factoredCscBinaryPopulatedColumnOffsets,
                        grammar.factoredCscBinaryRowIndices,
                        grammar.factoredCscBinaryProbabilities,
                        tmpCell.packedChildren,
                        tmpCell.insideProbabilities,
                        tmpCell.midpoints,
                        segmentStart,
                        segmentEnd);
                  }
                });
      } else {
        futures[i] =
            threadPool.submit(
                new Runnable() {

                  @Override
                  public void run() {
                    tmpCell.clear();
                    // Multiply by the full grammar rule matrix
                    binarySpmvMultiply(
                        cartesianProductVector,
                        grammar.cscBinaryPopulatedColumns,
                        grammar.cscBinaryPopulatedColumnOffsets,
                        grammar.cscBinaryRowIndices,
                        grammar.cscBinaryProbabilities,
                        tmpCell.packedChildren,
                        tmpCell.insideProbabilities,
                        tmpCell.midpoints,
                        segmentStart,
                        segmentEnd);
                  }
                });
      }
    }

    final PackedArrayChartCell packedArrayCell = (PackedArrayChartCell) chartCell;
    packedArrayCell.allocateTemporaryStorage();
    try {
      // Wait for the first task to finish and use its arrays as the temporary cell storage
      futures[0].get();

      final TemporaryChartCell tmpCell = packedArrayCell.tmpCell;
      // TODO Eliminate this extra arraycopy
      final int arrayLength = temporaryCells[0].insideProbabilities.length;
      System.arraycopy(
          temporaryCells[0].insideProbabilities, 0, tmpCell.insideProbabilities, 0, arrayLength);
      System.arraycopy(temporaryCells[0].packedChildren, 0, tmpCell.packedChildren, 0, arrayLength);
      System.arraycopy(temporaryCells[0].midpoints, 0, tmpCell.midpoints, 0, arrayLength);

      // Wait for each other task to complete and merge results into the main temporary storage
      for (int i = 1; i < grammarThreads; i++) {
        futures[i].get();
        final PackedArrayChart.TemporaryChartCell threadTmpCell = temporaryCells[i];
        for (int j = 0; j < arrayLength; j++) {
          if (threadTmpCell.insideProbabilities[j] > tmpCell.insideProbabilities[j]) {
            tmpCell.insideProbabilities[j] = threadTmpCell.insideProbabilities[j];
            tmpCell.packedChildren[j] = threadTmpCell.packedChildren[j];
            tmpCell.midpoints[j] = threadTmpCell.midpoints[j];
          }
        }
      }
    } catch (final InterruptedException e) {
      e.printStackTrace();
    } catch (final ExecutionException e) {
      e.printStackTrace();
    }
  }