All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.linkedin.dagli.dag.DAG3x7 Maven / Gradle / Ivy

Go to download

DAG-oriented machine learning framework for bug-resistant, readable, efficient, maintainable and trivially deployable models in Java and other JVM languages

There is a newer version: 15.0.0-beta9
Show newest version
// AUTOGENERATED CODE.  DO NOT MODIFY DIRECTLY!  Instead, please modify the dag/DAGXxY.ftl file.
// See the README in the module's src/template directory for details.
package com.linkedin.dagli.dag;

import java.util.Collection;
import java.util.Collections;

import com.linkedin.dagli.annotation.equality.IgnoredByValueEquality;
import com.linkedin.dagli.annotation.equality.ValueEquality;
import com.linkedin.dagli.objectio.ObjectIterator;
import com.linkedin.dagli.objectio.ObjectReader;
import com.linkedin.dagli.preparer.Preparer3;
import com.linkedin.dagli.preparer.AbstractBatchPreparer3;
import com.linkedin.dagli.preparer.PreparerContext;
import com.linkedin.dagli.preparer.PreparerResultMixed;
import com.linkedin.dagli.producer.Producer;
import com.linkedin.dagli.reducer.Reducer;
import com.linkedin.dagli.transformer.AbstractPreparableTransformer3;
import com.linkedin.dagli.transformer.AbstractPreparedTransformer3;
import com.linkedin.dagli.transformer.PreparedTransformer3;
import com.linkedin.dagli.util.array.AutoCloseableArray;

import com.linkedin.dagli.generator.Generator;
import com.linkedin.dagli.placeholder.Placeholder;

import com.linkedin.dagli.tuple.Tuple7;
import com.linkedin.dagli.tuple.Tuple3;

import com.linkedin.dagli.transformer.Value0FromTuple;
import com.linkedin.dagli.transformer.Value1FromTuple;
import com.linkedin.dagli.transformer.Value2FromTuple;
import com.linkedin.dagli.transformer.Value3FromTuple;
import com.linkedin.dagli.transformer.Value4FromTuple;
import com.linkedin.dagli.transformer.Value5FromTuple;
import com.linkedin.dagli.transformer.Value6FromTuple;


/**
 * DAGs, directed acyclic graphs, contain root nodes ({@link com.linkedin.dagli.placeholder.Placeholder}s and {@link com.linkedin.dagli.generator.Generator}s) and child nodes
 * ({@link com.linkedin.dagli.transformer.PreparedTransformer}s,
 * {@link com.linkedin.dagli.transformer.PreparableTransformer}s, and {@link com.linkedin.dagli.view.TransformerView}s).
 *
 * DAG3x7 is a preparable DAG.
 *
 * Before it can be used to transform new examples, it must be prepared with training data (e.g. by calling the
 * prepare() method).  The prepareAndApply() method both trains the DAG and applies it to the training data, yielding
 * both the prepared DAG and the results of applying the DAG to the training data (this can be useful in autoevaluation,
 * e.g. how well a classification model performs when predicting labels for its own training data).
 *
 * DAG3x7 is also a {@link com.linkedin.dagli.transformer.PreparableTransformer} and can be used as
 * a transformer within other DAGs.
 *
 * @param  the type of the first input
 * @param  the type of the second input
 * @param  the type of the third input
 * @param  the type of the first result
 * @param  the type of the second result
 * @param  the type of the third result
 * @param  the type of the fourth result
 * @param  the type of the fifth result
 * @param  the type of the sixth result
 * @param  the type of the seventh result
 */
@ValueEquality
public class DAG3x7
    extends
    AbstractPreparableTransformer3, DAG3x7.Prepared, DAG3x7>
    implements
    PreparableDAGTransformer, DAG3x7.Prepared, DAG3x7> {

  private static final long serialVersionUID = 1;

  // the DAGStructure stores the actual graph
  private DAGStructure> _dag;

  // keep track of what level of reduction we used to construct this DAG; this will be carried over to the prepared DAG
  @IgnoredByValueEquality
  // irrelevant given _dag and not (directly) externally observable
  private Reducer.Level _reductionLevel = null; // null -> no reductions applied

  // remember what executor should be used to prepare the DAG; this will also be "inherited" by the prepared DAG and
  // used for inference
  @IgnoredByValueEquality
  // not a factor in the semantics of the DAG and not (directly) externally observable
  private DAGExecutor _executor;

  /**
   * Creates a new DAG instance from the specified {@link DAGStructure} graph with the specified inputs.  DAG objects
   * have inputs because they can also serve as transformers within other DAGs.  In the common case where the DAG is
   * used directly and not embedded within another DAG, these inputs don't matter (and can simply be left as
   * {@link com.linkedin.dagli.producer.MissingInput}s).
   *
   * By default, this DAG will use a {@link LocalDAGExecutor}; to use a different executor, use the
   * {@link #withExecutor(DAGExecutor)} method.  When this DAG is prepared, the DAG executor will be "inherited" by the
   * resultant prepared DAG.
   *
   * @param dag the {@link DAGStructure} representing the actual graph of nodes and directed (parent/child) edges
   * @param input1 the {@link Producer} providing the first input to the transformer
   * @param input2 the {@link Producer} providing the second input to the transformer
   * @param input3 the {@link Producer} providing the third input to the transformer
   */
  DAG3x7(DAGStructure> dag, Producer input1,
      Producer input2, Producer input3) {
    super(input1, input2, input3);
    _dag = dag;
    _executor = new LocalDAGExecutor();
  }

  /**
   * Returns a DAG derived from this one that has been reduced to at least the specified level.
   *
   * If level is less than this DAG's current reduction level, {@code this} DAG is returned.
   *
   * Otherwise, this DAG is reduced, and the new, reduced DAG is returned.
   *
   * @param level the level of reduction desired; all reducers at or above this level will be applied
   * @return a DAG that has been reduced to at least the specified level (possibly this same DAG)
   */
  @Override
  public DAG3x7 withReduction(Reducer.Level level) {
    if (Reducer.Level.compare(level, _reductionLevel) >= 0) {
      return this;
    }

    DeduplicatedDAG reduced = DAGReducer.reduce(new DeduplicatedDAG(_dag), level);
    return clone(c -> {
      c._dag = new DAGStructure<>(reduced);
      c._reductionLevel = level;
    });
  }

  @Override
  public InternalAPI internalAPI() {
    return new InternalAPI();
  }

  public class InternalAPI
      extends
      AbstractPreparableTransformer3, DAG3x7.Prepared, DAG3x7>.InternalAPI
      implements
      PreparableDAGTransformer.InternalAPI, DAG3x7.Prepared, DAG3x7> {
    @Override
    public DAGStructure> getDAGStructure() {
      return _dag;
    }

    @Override
    public Reducer.Level getReductionLevel() {
      return _reductionLevel;
    }

    @Override
    public DAGExecutor getDAGExecutor() {
      return _executor;
    }

    @Override
    public DAG3x7 getInstance() {
      return DAG3x7.this;
    }
  }

  @Override
  protected Collection>> getGraphReducers() {
    return Collections.singletonList(DAGTransformerReducer.INSTANCE);
  }

  @Override
  protected boolean hasAlwaysConstantResult() {
    return _dag._isAlwaysConstant;
  }

  /**
   * Returns a copy of this DAG that will use the given {@link DAGExecutor} that will be used to execute this DAG.
   *
   * This executor will also be "inherited" by the resultant prepared DAG.
   *
   * @param executor the {@link DAGExecutor} to use
   * @return a copy of this instance that will use the provided executor
   */
  public DAG3x7 withExecutor(DAGExecutor executor) {
    return clone(r -> r._executor = executor);
  }

  @Override
  protected boolean hasIdempotentPreparer() {
    return _dag._hasIdempotentPreparer;
  }

  /**
   * Sets the parents of this transformer.  The results of the parent nodes will be the inputs to the transformer.
   *
   * @param input1 the {@link Producer} providing the first input to the transformer
   * @param input2 the {@link Producer} providing the second input to the transformer
   * @param input3 the {@link Producer} providing the third input to the transformer
   * @return a copy of this instance that will have the specified parents
   */
  public DAG3x7 withInputs(Producer input1,
      Producer input2, Producer input3) {
    return super.withAllInputs(input1, input2, input3);
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the first input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 2 (one less than this transformer), and values that were formerly
   * provided as the first input to the transformer will now instead be generated by the {@link Generator}.
   *
   * This method is useful for eliding inputs to a transformer.  For example, a prepared classifier might have a
   * "vestigial" label input (inherited from a corresponding preparable classifier from which it was obtained) that can
   * be replaced by Constant.nullValue().  Since the label input isn't used by the
   * prepared classifier (instead, it predicts a label from its other inputs and produces this as its result) replacing
   * it with a null has no effect and eliminates an unnecessary input.
   *
   * @param generator the generator that will provide a value for the first input
   * @return a new DAG that invokes this transformer with the same inputs, except the first, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG2x7 withGeneratorAsInput1(Generator generator) {
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    DAG3x7 dag =
        this.withInputs(generator, nestedPlaceholder2, nestedPlaceholder3);

    return DAG
        .withPlaceholders(nestedPlaceholder2, nestedPlaceholder3)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag),
            new Value6FromTuple<>(dag)).withExecutor(_executor).withInputs(getInput2(), getInput3());
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the second input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 2 (one less than this transformer), and values that were formerly
   * provided as the second input to the transformer will now instead be generated by the {@link Generator}.
   *
   * This method is useful for eliding inputs to a transformer.  For example, a prepared classifier might have a
   * "vestigial" label input (inherited from a corresponding preparable classifier from which it was obtained) that can
   * be replaced by Constant.nullValue().  Since the label input isn't used by the
   * prepared classifier (instead, it predicts a label from its other inputs and produces this as its result) replacing
   * it with a null has no effect and eliminates an unnecessary input.
   *
   * @param generator the generator that will provide a value for the second input
   * @return a new DAG that invokes this transformer with the same inputs, except the second, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG2x7 withGeneratorAsInput2(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    DAG3x7 dag =
        this.withInputs(nestedPlaceholder1, generator, nestedPlaceholder3);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder3)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag),
            new Value6FromTuple<>(dag)).withExecutor(_executor).withInputs(getInput1(), getInput3());
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the third input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 2 (one less than this transformer), and values that were formerly
   * provided as the third input to the transformer will now instead be generated by the {@link Generator}.
   *
   * This method is useful for eliding inputs to a transformer.  For example, a prepared classifier might have a
   * "vestigial" label input (inherited from a corresponding preparable classifier from which it was obtained) that can
   * be replaced by Constant.nullValue().  Since the label input isn't used by the
   * prepared classifier (instead, it predicts a label from its other inputs and produces this as its result) replacing
   * it with a null has no effect and eliminates an unnecessary input.
   *
   * @param generator the generator that will provide a value for the third input
   * @return a new DAG that invokes this transformer with the same inputs, except the third, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG2x7 withGeneratorAsInput3(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    DAG3x7 dag =
        this.withInputs(nestedPlaceholder1, nestedPlaceholder2, generator);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag),
            new Value6FromTuple<>(dag)).withExecutor(_executor).withInputs(getInput1(), getInput2());
  }

  /**
   * The {@link com.linkedin.dagli.preparer.Preparer} that prepares (trains) the DAG.
   *
   * @param  the type of the first input
   * @param  the type of the second input
   * @param  the type of the third input
   * @param  the type of the first result
   * @param  the type of the second result
   * @param  the type of the third result
   * @param  the type of the fourth result
   * @param  the type of the fifth result
   * @param  the type of the sixth result
   * @param  the type of the seventh result
   */
  private static class Preparer
      extends
      AbstractBatchPreparer3, DAG3x7.Prepared> {
    // the DAG being prepared
    private final DAG3x7 _dag;

    /**
     * Creates a new instance.
     *
     * @param dag the DAG that will be prepared
     */
    Preparer(DAG3x7 dag) {
      _dag = dag;
    }

    @Override
    @SuppressWarnings("unchecked")
    // we know the returned type is correct by the semantics of executors
    public PreparerResultMixed>, DAG3x7.Prepared> finish(
        ObjectReader> inputs) {
      return (PreparerResultMixed) _dag._executor.internalAPI().prepareUnsafe(_dag,
          ObjectReader.split(3, inputs.lazyMap(tuple -> tuple.toArray())));
    }

    @Override
    public void process(A value1, B value2, C value3) {
      // noop
    }
  }

  @Override
  protected Preparer3, DAG3x7.Prepared> getPreparer(
      PreparerContext context) {
    return new Preparer<>(this);
  }

  /**
   * Prepares (trains) the DAG on the provided input data (examples), and applies it to this same data.  The returned
   * {@link Result} instance contains both the prepared DAG as well as the results from applying the DAG on the
   * preparation data.
   *
   * The inputs to this method take the form of parallel sequences of values.  Each sequence will provide the values
   * for a particular {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG (the ordering of the
   * {@link com.linkedin.dagli.placeholder.Placeholder}s in a DAG is determined by their
   * ordering in the call to "DAG.withPlaceholders(...)...." that created it).  They are "parallel" because, e.g. all the
   * values corresponding to the fourth example will be the fourth value in their respective input sequences (all
   * sequences must, of course, be equal in size).
   *
   * Note that the results from applying the DAG to the preparation data with this method are not necessarily the same
   * results that would be obtained from calling the apply(...) method on prepared DAG with the same data.  This is
   * because certain transformers (e.g. KFoldCrossTrained and PreparedByGroup) intentionally transform training data in
   * a different way than inference data.  Please see the documentation of these transformers for details.
   *
   * @param values1 the sequence of values for the first
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values2 the sequence of values for the second
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values3 the sequence of values for the third
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @return a {@link Result} instance containing both the prepared DAG and the result of applying the DAG on the
   *         preparation data
   */
  public Result prepareAndApply(Iterable values1,
      Iterable values2, Iterable values3) {
    DAGExecutionResult, DAG3x7.Prepared> res =
        _executor.internalAPI().prepareAndApplyUnsafe(this,
            new ObjectReader[] { ObjectReader.wrap(values1), ObjectReader.wrap(values2), ObjectReader.wrap(values3) });
    return new Result<>((DAG3x7.Prepared) res.getPreparerResult()
        .getPreparedTransformerForNewData(), res.getOutputs());
  }

  /**
   * Prepares (trains) the DAG on the provided input data (examples).  The resultant prepared (trained) DAG can then be
   * applied to new examples (inference).
   *
   * The inputs to this method take the form of parallel sequences of values.  Each sequence will provide the values
   * for a particular {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG (the ordering of the
   * {@link com.linkedin.dagli.placeholder.Placeholder}s in a DAG is determined by their
   * ordering in the call to "DAG.withPlaceholders(...)...." that created it).  They are "parallel" because, e.g. all the
   * values corresponding to the fourth example will be the fourth value in their respective input sequences (all
   * sequences must, of course, be equal in size).
   *
   * @param values1 the sequence of values for the first
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values2 the sequence of values for the second
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values3 the sequence of values for the third
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @return a prepared (trained) DAG that can be applied to new examples (inference)
   */
  public DAG3x7.Prepared prepare(Iterable values1,
      Iterable values2, Iterable values3) {
    return (DAG3x7.Prepared) _executor
        .internalAPI()
        .prepareUnsafe(this,
            new ObjectReader[] { ObjectReader.wrap(values1), ObjectReader.wrap(values2), ObjectReader.wrap(values3) })
        .getPreparedTransformerForNewData();
  }

  /**
   * {@link Result}s are returned by the DAG's prepareAndApply(...) methods and contain both the prepared (trained) DAG
   * and the results of applying that DAG to the preparation (training) data.
   *
  * @param  the type of the first input
  * @param  the type of the second input
  * @param  the type of the third input
  * @param  the type of the first result
  * @param  the type of the second result
  * @param  the type of the third result
  * @param  the type of the fourth result
  * @param  the type of the fifth result
  * @param  the type of the sixth result
  * @param  the type of the seventh result
   */
  public static final class Result extends
      AbstractDAGResult7 {
    // the prepared DAG
    private final DAG3x7.Prepared _preparedDAG;

    /**
     * @return the {@link Prepared} DAG that was prepared (trained) on the provided preparation (training) data.
     */
    public DAG3x7.Prepared getPreparedDAG() {
      return _preparedDAG;
    }

    /**
     * Creates a new instance.
     *
     * @param preparedDAG the prepared DAG to be stored in this instance
     * @param results an array of {@link ObjectReader}s that contain each resultant output value of applying the DAG to
     *                the preparation (training) data
     */
    Result(DAG3x7.Prepared preparedDAG, ObjectReader[] results) {
      super(results);
      _preparedDAG = preparedDAG;
    }
  }

  /**
   * Prepared DAGs (directed acyclic graphs) contain root nodes ({@link com.linkedin.dagli.placeholder.Placeholder}s and
   * {@link com.linkedin.dagli.generator.Generator}s) and child nodes
   * ({@link com.linkedin.dagli.transformer.PreparedTransformer}s)  Unlike preparable DAGs, prepared DAGs do not contain
   * {@link com.linkedin.dagli.transformer.PreparableTransformer}s nor {@link com.linkedin.dagli.view.TransformerView}s
   * and can be applied to examples to transform them to some desired result (often an inference, such as a
   * classification or regression) using the apply(...) (for single examples) or applyAll(...) (for multiple examples)
   * methods.
   *
   * Prepared DAGs are also themselves {@link com.linkedin.dagli.transformer.PreparedTransformer}s, and can be used as
   * such within another DAG.
   *
  * @param  the type of the first input
  * @param  the type of the second input
  * @param  the type of the third input
  * @param  the type of the first result
  * @param  the type of the second result
  * @param  the type of the third result
  * @param  the type of the fourth result
  * @param  the type of the fifth result
  * @param  the type of the sixth result
  * @param  the type of the seventh result
   */
  @ValueEquality
  public static class Prepared
      extends
      AbstractPreparedTransformer3, DAG3x7.Prepared>
      implements
      PreparedDAGTransformer, DAG3x7.Prepared> {

    private static final long serialVersionUID = 1;

    // the DAGStructure stores the actual graph
    private DAGStructure> _dag;

    // keep track of what level of reduction we used to construct this DAG
    @IgnoredByValueEquality
    // irrelevant given _dag and not (directly) externally observable
    private Reducer.Level _reductionLevel = null; // null -> no reductions applied

    // the executor that will be used to apply the DAG to new examples
    @IgnoredByValueEquality
    // not a factor in the semantics of the DAG and not (directly) externally observable
    private PreparedDAGExecutor _executor;

    /**
     * Creates a new DAG instance from the specified {@link DAGStructure} graph with the specified inputs.  DAG objects
     * have inputs because they can also serve as transformers within other DAGs.  In the common case where the DAG is
     * used directly and not embedded within another DAG, these inputs don't matter (and can simply be left as
     * {@link com.linkedin.dagli.producer.MissingInput}s).
     *
     * By default, this DAG will use a {@link LocalDAGExecutor}; to use a different executor, use the
     * {@link #withExecutor(DAGExecutor)} method.
     *
     * @param dag the {@link DAGStructure} representing the actual graph of nodes and directed (parent/child) edges
    * @param input1 the {@link Producer} providing the first input to the transformer
    * @param input2 the {@link Producer} providing the second input to the transformer
    * @param input3 the {@link Producer} providing the third input to the transformer
     */
    Prepared(DAGStructure> dag, Producer input1,
        Producer input2, Producer input3) {
      super(input1, input2, input3);
      _dag = dag;
      _executor = new LocalDAGExecutor();
    }

    /**
     * Returns a DAG derived from this one that has been reduced to at least the specified level.
     *
     * If level is less than this DAG's current reduction level, {@code this} DAG is returned.
     *
     * Otherwise, this DAG is reduced, and the new, reduced DAG is returned.
     *
     * @param level the level of reduction desired; all reducers at or above this level will be applied
     * @return a DAG that has been reduced to at least the specified level (possibly this same DAG)
     */
    @Override
    public DAG3x7.Prepared withReduction(Reducer.Level level) {
      if (Reducer.Level.compare(level, _reductionLevel) >= 0) {
        return this;
      }

      DeduplicatedDAG reduced = DAGReducer.reduce(new DeduplicatedDAG(_dag), level);
      return clone(c -> {
        c._dag = new DAGStructure<>(reduced);
        c._reductionLevel = level;
      });
    }

    @Override
    public InternalAPI internalAPI() {
      return new InternalAPI();
    }

    public class InternalAPI
        extends
        AbstractPreparedTransformer3, DAG3x7.Prepared>.InternalAPI
        implements
        PreparedDAGTransformer.InternalAPI, DAG3x7.Prepared> {
      @Override
      public DAGStructure> getDAGStructure() {
        return _dag;
      }

      @Override
      public Reducer.Level getReductionLevel() {
        return _reductionLevel;
      }

      @Override
      public PreparedDAGExecutor getDAGExecutor() {
        return _executor;
      }

      @Override
      public DAG3x7.Prepared getInstance() {
        return DAG3x7.Prepared.this;
      }
    }

    @Override
    protected Collection>> getGraphReducers() {
      return Collections.singletonList(DAGTransformerReducer.INSTANCE);
    }

    @Override
    protected boolean hasAlwaysConstantResult() {
      return _dag._isAlwaysConstant;
    }

    /**
     * Returns a copy of this DAG that will use the given {@link PreparedDAGExecutor} that will be used to execute this DAG.

     *
     * @param executor the {@link DAGExecutor} to use
     * @return a copy of this instance that will use the provided executor
     */
    public DAG3x7.Prepared withExecutor(PreparedDAGExecutor executor) {
      return clone(r -> r._executor = executor);
    }

    /**
     * Sets the parents of this transformer.  The results of the parent nodes will be the inputs to the transformer.
     *
     * @param input1 the {@link Producer} providing the first input to the transformer
     * @param input2 the {@link Producer} providing the second input to the transformer
     * @param input3 the {@link Producer} providing the third input to the transformer
     * @return a copy of this instance that will have the specified parents
     */
    public DAG3x7.Prepared withInputs(Producer input1,
        Producer input2, Producer input3) {
      return super.withAllInputs(input1, input2, input3);
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the first input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 2 (one less than this transformer), and values that were formerly
     * provided as the first input to the transformer will now instead be generated by the {@link Generator}.
     *
     * This method is useful for eliding inputs to a transformer.  For example, a prepared classifier might have a
     * "vestigial" label input (inherited from a corresponding preparable classifier from which it was obtained) that can
     * be replaced by Constant.nullValue().  Since the label input isn't used by the
     * prepared classifier (instead, it predicts a label from its other inputs and produces this as its result) replacing
     * it with a null has no effect and eliminates an unnecessary input.
     *
     * @param generator the generator that will provide a value for the first input
     * @return a new DAG that invokes this transformer with the same inputs, except the first, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG2x7.Prepared withGeneratorAsInput1(Generator generator) {
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      DAG3x7.Prepared dag =
          this.withInputs(generator, nestedPlaceholder2, nestedPlaceholder3);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder2, nestedPlaceholder3)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag),
              new Value6FromTuple<>(dag)).withExecutor(_executor).withInputs(getInput2(), getInput3());
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the second input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 2 (one less than this transformer), and values that were formerly
     * provided as the second input to the transformer will now instead be generated by the {@link Generator}.
     *
     * This method is useful for eliding inputs to a transformer.  For example, a prepared classifier might have a
     * "vestigial" label input (inherited from a corresponding preparable classifier from which it was obtained) that can
     * be replaced by Constant.nullValue().  Since the label input isn't used by the
     * prepared classifier (instead, it predicts a label from its other inputs and produces this as its result) replacing
     * it with a null has no effect and eliminates an unnecessary input.
     *
     * @param generator the generator that will provide a value for the second input
     * @return a new DAG that invokes this transformer with the same inputs, except the second, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG2x7.Prepared withGeneratorAsInput2(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      DAG3x7.Prepared dag =
          this.withInputs(nestedPlaceholder1, generator, nestedPlaceholder3);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder3)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag),
              new Value6FromTuple<>(dag)).withExecutor(_executor).withInputs(getInput1(), getInput3());
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the third input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 2 (one less than this transformer), and values that were formerly
     * provided as the third input to the transformer will now instead be generated by the {@link Generator}.
     *
     * This method is useful for eliding inputs to a transformer.  For example, a prepared classifier might have a
     * "vestigial" label input (inherited from a corresponding preparable classifier from which it was obtained) that can
     * be replaced by Constant.nullValue().  Since the label input isn't used by the
     * prepared classifier (instead, it predicts a label from its other inputs and produces this as its result) replacing
     * it with a null has no effect and eliminates an unnecessary input.
     *
     * @param generator the generator that will provide a value for the third input
     * @return a new DAG that invokes this transformer with the same inputs, except the third, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG2x7.Prepared withGeneratorAsInput3(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      DAG3x7.Prepared dag =
          this.withInputs(nestedPlaceholder1, nestedPlaceholder2, generator);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag),
              new Value6FromTuple<>(dag)).withExecutor(_executor).withInputs(getInput1(), getInput2());
    }

    /**
     * Applies the DAG to the provided input data (examples), running the model on the data and producing the results as a
     * {@link Result} instance.
     *
     * The inputs to this method take the form of parallel sequences of values.  Each sequence will provide the values
     * for a particular {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG (the ordering of the
     * {@link com.linkedin.dagli.placeholder.Placeholder}s in a DAG is determined by their
     * ordering in the call to "DAG.withPlaceholders(...)...." that created it).  They are "parallel" because, e.g. all the
     * values corresponding to the fourth example will be the fourth value in their respective input sequences (all
     * sequences must, of course, be equal in size).
     *
     * @param values1 the sequence of values for the first
     * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
     * @param values2 the sequence of values for the second
     * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
     * @param values3 the sequence of values for the third
     * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
     * @return a {@link Result} instance containing the results of applying the DAG on the provided data
     */
    public Result applyAll(Iterable values1, Iterable values2,
        Iterable values3) {
      return new Result(_executor.internalAPI().applyUnsafe(this,
          new ObjectReader[] { ObjectReader.wrap(values1), ObjectReader.wrap(values2), ObjectReader.wrap(values3) }));
    }

    @Override
    public Tuple7 apply(A value1, B value2, C value3) {
      try (
          AutoCloseableArray> res =
              new AutoCloseableArray<>(_executor.internalAPI().applyUnsafe(
                  this,
                  new ObjectReader[] { ObjectReader.singleton(value1), ObjectReader.singleton(value2), ObjectReader
                      .singleton(value3) }));

          ObjectIterator resultIterator0 = res.get(0).iterator();
          ObjectIterator resultIterator1 = res.get(1).iterator();
          ObjectIterator resultIterator2 = res.get(2).iterator();
          ObjectIterator resultIterator3 = res.get(3).iterator();
          ObjectIterator resultIterator4 = res.get(4).iterator();
          ObjectIterator resultIterator5 = res.get(5).iterator();
          ObjectIterator resultIterator6 = res.get(6).iterator()) {
        return (Tuple7) Tuple7.of(resultIterator0.next(), resultIterator1.next(),
            resultIterator2.next(), resultIterator3.next(), resultIterator4.next(), resultIterator5.next(),
            resultIterator6.next());
      }
    }

    /**
     * Contains the results of applying the DAG to one or more examples, as produced by a call to applyAll(...).
     *
     * @param  the type of the first result
     * @param  the type of the second result
     * @param  the type of the third result
     * @param  the type of the fourth result
     * @param  the type of the fifth result
     * @param  the type of the sixth result
     * @param  the type of the seventh result
     */
    public static final class Result extends AbstractDAGResult7 {
      /**
       * Creates a new instance.
       *
       * @param results an array of {@link ObjectReader}s that contain each resultant output value of applying the DAG to
       *                the provided examples
       */
      Result(ObjectReader[] results) {
        super(results);
      }
    }
  }
}