/** {@inheritDoc} */
  @Override
  public <E extends Entity> List<E> crossover(List<E> parentCollection) {
    checkState(
        parentCollection.size() >= 3,
        "There must be a minimum of three parents to perform UNDX crossover.");
    checkState(
        numberOfOffspring > 0,
        "At least one offspring must be generated. Check 'numberOfOffspring'.");

    List<Vector> solutions = Entities.<Vector>getCandidateSolutions(parentCollection);
    List<E> offspring = Lists.newArrayListWithCapacity(numberOfOffspring);
    UniformDistribution randomParent = new UniformDistribution();
    final int k = solutions.size();
    final int n = solutions.get(0).size();

    for (int os = 0; os < numberOfOffspring; os++) {
      // get index of main parent and put its solution at the end of the list
      int parent = (int) randomParent.getRandomNumber(0.0, k);
      Collections.swap(solutions, parent, k - 1);

      List<Vector> e_zeta = new ArrayList<Vector>();

      // calculate mean of parents except main parent
      Vector g = Vectors.mean(solutions.subList(0, k - 1));

      // basis vectors defined by parents
      for (int i = 0; i < k - 1; i++) {
        Vector d = solutions.get(i).subtract(g);

        if (!d.isZero()) {
          double dbar = d.length();
          Vector e = d.orthogonalize(e_zeta);

          if (!e.isZero()) {
            e_zeta.add(e.normalize().multiply(dbar));
          }
        }
      }

      final double D = solutions.get(k - 1).subtract(g).length();

      // create the remaining basis vectors
      List<Vector> e_eta = Lists.newArrayList();
      e_eta.add(solutions.get(k - 1).subtract(g));

      for (int i = 0; i < n - e_zeta.size() - 1; i++) {
        Vector d = Vector.newBuilder().copyOf(g).buildRandom();
        e_eta.add(d);
      }

      e_eta = Vectors.orthonormalize(e_eta);

      // construct the offspring
      Vector variables = Vector.copyOf(g);

      if (!useIndividualProviders) {
        for (int i = 0; i < e_zeta.size(); i++) {
          variables =
              variables.plus(
                  e_zeta.get(i).multiply(random.getRandomNumber(0.0, sigma1.getParameter())));
        }

        for (int i = 0; i < e_eta.size(); i++) {
          variables =
              variables.plus(
                  e_eta
                      .get(i)
                      .multiply(
                          D * random.getRandomNumber(0.0, sigma2.getParameter() / Math.sqrt(n))));
        }
      } else {
        for (int i = 0; i < e_zeta.size(); i++) {
          variables =
              variables.plus(
                  e_zeta
                      .get(i)
                      .multiply(
                          new Supplier<Number>() {

                            @Override
                            public Number get() {
                              return random.getRandomNumber(0.0, sigma1.getParameter());
                            }
                          }));
        }

        for (int i = 0; i < e_eta.size(); i++) {
          variables =
              variables.plus(
                  e_eta
                      .get(i)
                      .multiply(
                          new Supplier<Number>() {

                            @Override
                            public Number get() {
                              return D
                                  * random.getRandomNumber(
                                      0.0, sigma2.getParameter() / Math.sqrt(n));
                            }
                          }));
        }
      }

      E child = (E) parentCollection.get(parent).getClone();
      child.setCandidateSolution(variables);

      offspring.add(child);
    }

    return offspring;
  }