code: public VectorFactor addFactor(int[] neighborIndices, int[] neighborDimensions, Function<int[], ConcatVector> assignmentFeaturizer) {
      ConcatVectorTable features = new ConcatVectorTable(neighborDimensions);
      for (int[] assignment : features) {
        features.setAssignmentValue(assignment, () -> assignmentFeaturizer.apply(assignment));
      }
  
      return addFactor(features, neighborIndices);
    }
docs: This is the preferred way to add factors to a graphical model. Specify the neighbors, their dimensions, and a
  function that maps from variable assignments to ConcatVector's of features, and this function will handle the
  data flow of constructing and populating a factor matching those specifications.
  <p>
  IMPORTANT: assignmentFeaturizer must be REPEATABLE and NOT HAVE SIDE EFFECTS
  This is because it is actually stored as a lazy closure until the full featurized vector is needed, and then it
  is created, used, and discarded. It CAN BE CALLED MULTIPLE TIMES, and must always return the same value in order
  for behavior of downstream systems to be defined.
  
  @param neighborIndices      the names of the variables, as indices
  @param neighborDimensions   the sizes of the neighbor variables, corresponding to the order in neighborIndices
  @param assignmentFeaturizer a function that maps from an assignment to the variables, represented as an array of
  assignments in the same order as presented in neighborIndices, to a ConcatVector of
  features for that assignment.
  
  @return a reference to the created factor. This can be safely ignored, as the factor is already saved in the model