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

com.linkedin.dagli.dag.DAG7x6 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.Preparer7;
import com.linkedin.dagli.preparer.AbstractBatchPreparer7;
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.AbstractPreparableTransformer7;
import com.linkedin.dagli.transformer.AbstractPreparedTransformer7;
import com.linkedin.dagli.transformer.PreparedTransformer7;
import com.linkedin.dagli.util.array.AutoCloseableArray;

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

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

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;


/**
 * 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).
 *
 * DAG7x6 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).
 *
 * DAG7x6 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 fourth input
 * @param  the type of the fifth input
 * @param  the type of the sixth input
 * @param  the type of the seventh 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
 */
@ValueEquality
public class DAG7x6
    extends
    AbstractPreparableTransformer7, DAG7x6.Prepared, DAG7x6>
    implements
    PreparableDAGTransformer, DAG7x6.Prepared, DAG7x6> {

  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
   * @param input4 the {@link Producer} providing the fourth input to the transformer
   * @param input5 the {@link Producer} providing the fifth input to the transformer
   * @param input6 the {@link Producer} providing the sixth input to the transformer
   * @param input7 the {@link Producer} providing the seventh input to the transformer
   */
  DAG7x6(DAGStructure> dag, Producer input1, Producer input2,
      Producer input3, Producer input4, Producer input5,
      Producer input6, Producer input7) {
    super(input1, input2, input3, input4, input5, input6, input7);
    _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 DAG7x6 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
      AbstractPreparableTransformer7, DAG7x6.Prepared, DAG7x6>.InternalAPI
      implements
      PreparableDAGTransformer.InternalAPI, DAG7x6.Prepared, DAG7x6> {
    @Override
    public DAGStructure> getDAGStructure() {
      return _dag;
    }

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

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

    @Override
    public DAG7x6 getInstance() {
      return DAG7x6.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 DAG7x6 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
   * @param input4 the {@link Producer} providing the fourth input to the transformer
   * @param input5 the {@link Producer} providing the fifth input to the transformer
   * @param input6 the {@link Producer} providing the sixth input to the transformer
   * @param input7 the {@link Producer} providing the seventh input to the transformer
   * @return a copy of this instance that will have the specified parents
   */
  public DAG7x6 withInputs(Producer input1,
      Producer input2, Producer input3, Producer input4,
      Producer input5, Producer input6, Producer input7) {
    return super.withAllInputs(input1, input2, input3, input4, input5, input6, input7);
  }

  /**
   * 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 6 (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 DAG6x6 withGeneratorAsInput1(Generator generator) {
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
    Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
    Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
    Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
    DAG7x6 dag =
        this.withInputs(generator, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7);

    return DAG
        .withPlaceholders(nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput2(), getInput3(), getInput4(), getInput5(), getInput6(), getInput7());
  }

  /**
   * 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 6 (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 DAG6x6 withGeneratorAsInput2(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
    Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
    Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
    Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
    DAG7x6 dag =
        this.withInputs(nestedPlaceholder1, generator, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput1(), getInput3(), getInput4(), getInput5(), getInput6(), getInput7());
  }

  /**
   * 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 6 (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 DAG6x6 withGeneratorAsInput3(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
    Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
    Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
    Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
    DAG7x6 dag =
        this.withInputs(nestedPlaceholder1, nestedPlaceholder2, generator, nestedPlaceholder4, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder4, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput1(), getInput2(), getInput4(), getInput5(), getInput6(), getInput7());
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the fourth input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
   * provided as the fourth 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 fourth input
   * @return a new DAG that invokes this transformer with the same inputs, except the fourth, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG6x6 withGeneratorAsInput4(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
    Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
    Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
    DAG7x6 dag =
        this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, generator, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder5,
            nestedPlaceholder6, nestedPlaceholder7)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput1(), getInput2(), getInput3(), getInput5(), getInput6(), getInput7());
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the fifth input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
   * provided as the fifth 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 fifth input
   * @return a new DAG that invokes this transformer with the same inputs, except the fifth, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG6x6 withGeneratorAsInput5(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
    Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
    Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
    DAG7x6 dag =
        this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4, generator,
            nestedPlaceholder6, nestedPlaceholder7);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
            nestedPlaceholder6, nestedPlaceholder7)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput1(), getInput2(), getInput3(), getInput4(), getInput6(), getInput7());
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the sixth input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
   * provided as the sixth 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 sixth input
   * @return a new DAG that invokes this transformer with the same inputs, except the sixth, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG6x6 withGeneratorAsInput6(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
    Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
    Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
    DAG7x6 dag =
        this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
            nestedPlaceholder5, generator, nestedPlaceholder7);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
            nestedPlaceholder5, nestedPlaceholder7)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput1(), getInput2(), getInput3(), getInput4(), getInput5(), getInput7());
  }

  /**
   * Creates a new DAG that invokes this transformer, except that the seventh input is replaced by the specified {@link Generator}.
   * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
   * provided as the seventh 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 seventh input
   * @return a new DAG that invokes this transformer with the same inputs, except the seventh, whose value is replaced by
   *         the produced by the provided generator
   */
  public DAG6x6 withGeneratorAsInput7(Generator generator) {
    Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
    Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
    Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
    Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
    Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
    Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
    DAG7x6 dag =
        this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
            nestedPlaceholder5, nestedPlaceholder6, generator);

    return DAG
        .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
            nestedPlaceholder5, nestedPlaceholder6)
        .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
            new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag)).withExecutor(_executor)
        .withInputs(getInput1(), getInput2(), getInput3(), getInput4(), getInput5(), getInput6());
  }

  /**
   * 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 fourth input
   * @param  the type of the fifth input
   * @param  the type of the sixth input
   * @param  the type of the seventh 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
   */
  private static class Preparer
      extends
      AbstractBatchPreparer7, DAG7x6.Prepared> {
    // the DAG being prepared
    private final DAG7x6 _dag;

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

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

    @Override
    public void process(A value1, B value2, C value3, D value4, E value5, F value6, G value7) {
      // noop
    }
  }

  @Override
  protected Preparer7, DAG7x6.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.
   * @param values4 the sequence of values for the fourth
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values5 the sequence of values for the fifth
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values6 the sequence of values for the sixth
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values7 the sequence of values for the seventh
   * {@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, Iterable values4,
      Iterable values5, Iterable values6, Iterable values7) {
    DAGExecutionResult, DAG7x6.Prepared> res =
        _executor
            .internalAPI()
            .prepareAndApplyUnsafe(
                this,
                new ObjectReader[] { ObjectReader.wrap(values1), ObjectReader.wrap(values2), ObjectReader.wrap(values3), ObjectReader
                    .wrap(values4), ObjectReader.wrap(values5), ObjectReader.wrap(values6), ObjectReader.wrap(values7) });
    return new Result<>((DAG7x6.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.
   * @param values4 the sequence of values for the fourth
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values5 the sequence of values for the fifth
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values6 the sequence of values for the sixth
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @param values7 the sequence of values for the seventh
   * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
   * @return a prepared (trained) DAG that can be applied to new examples (inference)
   */
  public DAG7x6.Prepared prepare(Iterable values1,
      Iterable values2, Iterable values3, Iterable values4,
      Iterable values5, Iterable values6, Iterable values7) {
    return (DAG7x6.Prepared) _executor
        .internalAPI()
        .prepareUnsafe(
            this,
            new ObjectReader[] { ObjectReader.wrap(values1), ObjectReader.wrap(values2), ObjectReader.wrap(values3), ObjectReader
                .wrap(values4), ObjectReader.wrap(values5), ObjectReader.wrap(values6), ObjectReader.wrap(values7) })
        .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 fourth input
  * @param  the type of the fifth input
  * @param  the type of the sixth input
  * @param  the type of the seventh 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
   */
  public static final class Result extends
      AbstractDAGResult6 {
    // the prepared DAG
    private final DAG7x6.Prepared _preparedDAG;

    /**
     * @return the {@link Prepared} DAG that was prepared (trained) on the provided preparation (training) data.
     */
    public DAG7x6.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(DAG7x6.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 fourth input
  * @param  the type of the fifth input
  * @param  the type of the sixth input
  * @param  the type of the seventh 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
   */
  @ValueEquality
  public static class Prepared
      extends
      AbstractPreparedTransformer7, DAG7x6.Prepared>
      implements
      PreparedDAGTransformer, DAG7x6.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
    * @param input4 the {@link Producer} providing the fourth input to the transformer
    * @param input5 the {@link Producer} providing the fifth input to the transformer
    * @param input6 the {@link Producer} providing the sixth input to the transformer
    * @param input7 the {@link Producer} providing the seventh input to the transformer
     */
    Prepared(DAGStructure> dag, Producer input1,
        Producer input2, Producer input3, Producer input4,
        Producer input5, Producer input6, Producer input7) {
      super(input1, input2, input3, input4, input5, input6, input7);
      _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 DAG7x6.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
        AbstractPreparedTransformer7, DAG7x6.Prepared>.InternalAPI
        implements
        PreparedDAGTransformer.InternalAPI, DAG7x6.Prepared> {
      @Override
      public DAGStructure> getDAGStructure() {
        return _dag;
      }

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

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

      @Override
      public DAG7x6.Prepared getInstance() {
        return DAG7x6.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 DAG7x6.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
     * @param input4 the {@link Producer} providing the fourth input to the transformer
     * @param input5 the {@link Producer} providing the fifth input to the transformer
     * @param input6 the {@link Producer} providing the sixth input to the transformer
     * @param input7 the {@link Producer} providing the seventh input to the transformer
     * @return a copy of this instance that will have the specified parents
     */
    public DAG7x6.Prepared withInputs(Producer input1,
        Producer input2, Producer input3, Producer input4,
        Producer input5, Producer input6, Producer input7) {
      return super.withAllInputs(input1, input2, input3, input4, input5, input6, input7);
    }

    /**
     * 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 6 (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 DAG6x6.Prepared withGeneratorAsInput1(Generator generator) {
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
      Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
      Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
      Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
      DAG7x6.Prepared dag =
          this.withInputs(generator, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput2(), getInput3(), getInput4(), getInput5(), getInput6(), getInput7());
    }

    /**
     * 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 6 (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 DAG6x6.Prepared withGeneratorAsInput2(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
      Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
      Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
      Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
      DAG7x6.Prepared dag =
          this.withInputs(nestedPlaceholder1, generator, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder3, nestedPlaceholder4, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput1(), getInput3(), getInput4(), getInput5(), getInput6(), getInput7());
    }

    /**
     * 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 6 (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 DAG6x6.Prepared withGeneratorAsInput3(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
      Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
      Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
      Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
      DAG7x6.Prepared dag =
          this.withInputs(nestedPlaceholder1, nestedPlaceholder2, generator, nestedPlaceholder4, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder4, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput1(), getInput2(), getInput4(), getInput5(), getInput6(), getInput7());
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the fourth input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
     * provided as the fourth 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 fourth input
     * @return a new DAG that invokes this transformer with the same inputs, except the fourth, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG6x6.Prepared withGeneratorAsInput4(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
      Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
      Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
      DAG7x6.Prepared dag =
          this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, generator, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder5,
              nestedPlaceholder6, nestedPlaceholder7)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput1(), getInput2(), getInput3(), getInput5(), getInput6(), getInput7());
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the fifth input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
     * provided as the fifth 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 fifth input
     * @return a new DAG that invokes this transformer with the same inputs, except the fifth, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG6x6.Prepared withGeneratorAsInput5(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
      Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
      Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
      DAG7x6.Prepared dag =
          this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4, generator,
              nestedPlaceholder6, nestedPlaceholder7);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
              nestedPlaceholder6, nestedPlaceholder7)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput1(), getInput2(), getInput3(), getInput4(), getInput6(), getInput7());
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the sixth input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
     * provided as the sixth 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 sixth input
     * @return a new DAG that invokes this transformer with the same inputs, except the sixth, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG6x6.Prepared withGeneratorAsInput6(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
      Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
      Placeholder nestedPlaceholder7 = new Placeholder<>("Original Input 7");
      DAG7x6.Prepared dag =
          this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
              nestedPlaceholder5, generator, nestedPlaceholder7);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
              nestedPlaceholder5, nestedPlaceholder7)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput1(), getInput2(), getInput3(), getInput4(), getInput5(), getInput7());
    }

    /**
     * Creates a new DAG that invokes this transformer, except that the seventh input is replaced by the specified {@link Generator}.
     * The resultant DAG will thus have an arity of 6 (one less than this transformer), and values that were formerly
     * provided as the seventh 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 seventh input
     * @return a new DAG that invokes this transformer with the same inputs, except the seventh, whose value is replaced by
     *         the produced by the provided generator
     */
    public DAG6x6.Prepared withGeneratorAsInput7(Generator generator) {
      Placeholder nestedPlaceholder1 = new Placeholder<>("Original Input 1");
      Placeholder nestedPlaceholder2 = new Placeholder<>("Original Input 2");
      Placeholder nestedPlaceholder3 = new Placeholder<>("Original Input 3");
      Placeholder nestedPlaceholder4 = new Placeholder<>("Original Input 4");
      Placeholder nestedPlaceholder5 = new Placeholder<>("Original Input 5");
      Placeholder nestedPlaceholder6 = new Placeholder<>("Original Input 6");
      DAG7x6.Prepared dag =
          this.withInputs(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
              nestedPlaceholder5, nestedPlaceholder6, generator);

      return DAG.Prepared
          .withPlaceholders(nestedPlaceholder1, nestedPlaceholder2, nestedPlaceholder3, nestedPlaceholder4,
              nestedPlaceholder5, nestedPlaceholder6)
          .withOutputs(new Value0FromTuple<>(dag), new Value1FromTuple<>(dag), new Value2FromTuple<>(dag),
              new Value3FromTuple<>(dag), new Value4FromTuple<>(dag), new Value5FromTuple<>(dag))
          .withExecutor(_executor)
          .withInputs(getInput1(), getInput2(), getInput3(), getInput4(), getInput5(), getInput6());
    }

    /**
     * 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.
     * @param values4 the sequence of values for the fourth
     * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
     * @param values5 the sequence of values for the fifth
     * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
     * @param values6 the sequence of values for the sixth
     * {@link com.linkedin.dagli.placeholder.Placeholder} in the DAG.
     * @param values7 the sequence of values for the seventh
     * {@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, Iterable values4, Iterable values5,
        Iterable values6, Iterable values7) {
      return new Result(
          _executor.internalAPI()
              .applyUnsafe(
                  this,
                  new ObjectReader[] { ObjectReader.wrap(values1), ObjectReader.wrap(values2), ObjectReader
                      .wrap(values3), ObjectReader.wrap(values4), ObjectReader.wrap(values5), ObjectReader
                      .wrap(values6), ObjectReader.wrap(values7) }));
    }

    @Override
    public Tuple6 apply(A value1, B value2, C value3, D value4, E value5, F value6, G value7) {
      try (
          AutoCloseableArray> res =
              new AutoCloseableArray<>(_executor.internalAPI().applyUnsafe(
                  this,
                  new ObjectReader[] { ObjectReader.singleton(value1), ObjectReader.singleton(value2), ObjectReader
                      .singleton(value3), ObjectReader.singleton(value4), ObjectReader.singleton(value5), ObjectReader
                      .singleton(value6), ObjectReader.singleton(value7) }));

          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()) {
        return (Tuple6) Tuple6.of(resultIterator0.next(), resultIterator1.next(),
            resultIterator2.next(), resultIterator3.next(), resultIterator4.next(), resultIterator5.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
     */
    public static final class Result extends AbstractDAGResult6 {
      /**
       * 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);
      }
    }
  }
}