docs: @SuppressWarnings("unchecked")
    public List<CfgParseTree> beamSearch(List<?> terminals, int beamSize) {
      if (terminals.size() == 0) {
        // Zero size inputs occur because unknown words may be automatically skipped.
        return Collections.emptyList();
      }
      
      BeamSearchCfgParseChart chart = new BeamSearchCfgParseChart((List<Object>) terminals, beamSize);
  
      // Construct an encoding for mapping partial parse trees to longs. Using an
      // encoding improves parsing efficiency.
      long[] treeKeyOffsets = new long[6];
      // Index of rule type.
      treeKeyOffsets[5] = 1;
      // Index of nonterminal root symbol.
      treeKeyOffsets[4] = treeKeyOffsets[5] * ruleVariableType.numValues();
      // Starting index for the right subtree, ending at spanEnd for the current
      // tree.
      treeKeyOffsets[3] = treeKeyOffsets[4] * nonterminalVariableType.numValues();
      // Ending index for the left subtree, starting at spanStart of the current
      // tree.
      treeKeyOffsets[2] = treeKeyOffsets[3] * (chart.chartSize() + 1);
      // Index into the beam of right subtrees.
      treeKeyOffsets[1] = treeKeyOffsets[2] * (chart.chartSize() + 1);
      // Index into the beam of left subtrees.
      treeKeyOffsets[0] = treeKeyOffsets[1] * beamSize;
  
      // Handle the terminal rules, mapping the observed terminals to possible
      // nonterminals.
      initializeBeamSearchChart((List<Object>) terminals, chart, treeKeyOffsets);
  
      // Construct a tree from the nonterminals.
      for (int spanSize = 1; spanSize < chart.chartSize(); spanSize++) {
        for (int spanStart = 0; spanStart + spanSize < chart.chartSize(); spanStart++) {
          int spanEnd = spanStart + spanSize;
          calculateInsideBeam(spanStart, spanEnd, chart, treeKeyOffsets);
        }
      }
  
      // This block of code only exists for logging purposes.
      /*
       * int numTruncatedEntries = 0; for (int spanSize = 0; spanSize <
       * chart.chartSize() - 1; spanSize++) { for (int spanStart = 0; spanStart +
       * spanSize < chart.chartSize(); spanStart++) { if
       * (chart.getNumParseTreeKeysForSpan(spanStart, spanStart + spanSize) ==
       * beamSize) { numTruncatedEntries++; } } } System.out.println("Truncated "
       * + numTruncatedEntries + " chart entries");
       */
  
      // Map the integer encodings of trees back to tree objects.
      List<CfgParseTree> trees;
      trees = Lists.newArrayList();
      int numRootTrees = chart.getNumParseTreeKeysForSpan(0, terminals.size() - 1);
      long[] rootKeys = chart.getParseTreeKeysForSpan(0, terminals.size() - 1);
      double[] rootProbs = chart.getParseTreeProbsForSpan(0, terminals.size() - 1);
      for (int i = 0; i < numRootTrees; i++) {
        CfgParseTree tree = mapTreeKeyToParseTree(rootKeys[i], rootProbs[i], 0, terminals.size() - 1, chart, treeKeyOffsets);
        tree = tree.multiplyProbability(rootDistribution.getUnnormalizedProbability(tree.getRoot()));
        trees.add(tree);
      }
  
      Collections.sort(trees);
      Collections.reverse(trees);
      return trees;
    }