docs: public double calculateStochasticUpdate(double[] x, double xscale, int[] batch, double gscale) {
      double prob = 0; // the log prob of the sequence given the model, which is the negation of value at this point
      double[] weights = x;
      int[][] wis = getWeightIndices();
  
      // Adjust weight by -gscale*gradient
      // gradient is expected count - empirical count
      // so we adjust by + gscale(empirical count - expected count)
  
      int[] given = new int[window - 1];
      int[][] docCliqueLabels = new int[window][];
      for (int j = 0; j < window; j++) {
        docCliqueLabels[j] = new int[j+1];
      }
      // iterate over all the documents
      for (int m = 0; m < batch.length; m++) {
        int ind = batch[m];
        int[][][] docData = data[ind];
        int[] docLabels = labels[ind];
  
        // make a clique tree for this document
        CRFCliqueTree cliqueTree = CRFCliqueTree.getCalibratedCliqueTree(weights, xscale, wis, docData,
                labelIndices, numClasses, classIndex, backgroundSymbol);
  
        // compute the log probability of the document given the model with the parameters x
        Arrays.fill(given, classIndex.indexOf(backgroundSymbol));
        if (docLabels.length>docData.length) { // only true for self-training
          // fill the given array with the extra docLabels
          System.arraycopy(docLabels, 0, given, 0, given.length);
          // shift the docLabels array left
          int[] newDocLabels = new int[docData.length];
          System.arraycopy(docLabels, docLabels.length-newDocLabels.length, newDocLabels, 0, newDocLabels.length);
          docLabels = newDocLabels;
        }
        // iterate over the positions in this document
        for (int i = 0; i < docData.length; i++) {
          int label = docLabels[i];
          double p = cliqueTree.condLogProbGivenPrevious(i, label, given);
          if (VERBOSE) {
            System.err.println("P(" + label + "|" + ArrayMath.toString(given) + ")=" + p);
          }
          prob += p;
  
          // Empirical count
          for (int j = 0; j < data[ind][i].length; j++) {
            if (j > 0) {
              System.arraycopy(given, window - j - 1, docCliqueLabels[j], 0, j);
            }
            docCliqueLabels[j][j] = label;
            // TODO: We can eliminate this lookup by saving the correctLabelIndex (or marking it in CRFLabel)
            CRFLabel crfLabel = new CRFLabel(docCliqueLabels[j]);
            int correctLabelIndex = labelIndices[j].indexOf(crfLabel);
            for (int n = 0; n < data[ind][i][j].length; n++) {
              // Adjust by gscale (empirical count)
              x[wis[data[ind][i][j][n]][correctLabelIndex]] += gscale;
            }
          }
          // Shift window over
          System.arraycopy(given, 1, given, 0, given.length - 1);
          given[given.length - 1] = label;
        }
  
        // compute the expected counts for this document, which we will need to compute the derivative
        // iterate over the positions in this document
        for (int i = 0; i < data[ind].length; i++) {
          // for each possible clique at this position
          for (int j = 0; j < data[ind][i].length; j++) {
            // Expected count
            Index labelIndex = labelIndices[j];
            // for each possible labeling for that clique
            for (int k = 0; k < labelIndex.size(); k++) {
              int[] label = ((CRFLabel) labelIndex.get(k)).getLabel();
              double p = cliqueTree.prob(i, label); // probability of these labels occurring in this clique with these features
              for (int n = 0; n < data[ind][i][j].length; n++) {
                // Adjust weight by -p*gscale (expected count scaled)
                x[wis[docData[i][j][n]][k]] -= p*gscale;
              }
            }
          }
        }
      }
  
      if (Double.isNaN(prob)) { // shouldn't be the case
        throw new RuntimeException("Got NaN for prob in CRFLogConditionalObjectiveFunction.calculate()");
      }
  
      value = -prob;
      return value;
    }