Terminations.java

package net.bmahe.genetics4j.core.termination;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.Validate;

import net.bmahe.genetics4j.core.Genotype;
import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;

public class Terminations {

	public static <T extends Comparable<T>> Termination<T> ofMaxGeneration(final long maxGeneration) {
		Validate.isTrue(maxGeneration > 0);

		return new Termination<T>() {

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				Validate.isTrue(generation >= 0);

				return generation >= maxGeneration;
			}
		};
	}

	public static <T extends Comparable<T>> Termination<T> ofMaxTime(final Duration duration) {
		Validate.notNull(duration);

		return new Termination<T>() {

			private final long durationNanos = duration.get(ChronoUnit.NANOS);
			private Long startTime = null;

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				Validate.isTrue(generation >= 0);

				final long nowNanos = System.nanoTime();

				if (startTime == null) {
					startTime = nowNanos;
				}

				return nowNanos - startTime >= durationNanos;
			}
		};
	}

	@SafeVarargs
	public static <T extends Comparable<T>> Termination<T> and(final Termination<T>... terminations) {
		Validate.notNull(terminations);
		Validate.isTrue(terminations.length > 0);

		return new Termination<T>() {

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				return Arrays.stream(terminations)
						.allMatch((termination) -> termination.isDone(eaConfiguration, generation, population, fitness));
			}

		};
	}

	@SafeVarargs
	public static <T extends Comparable<T>> Termination<T> or(final Termination<T>... terminations) {
		Validate.notNull(terminations);
		Validate.isTrue(terminations.length > 0);

		return new Termination<T>() {

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				return Arrays.stream(terminations)
						.anyMatch((termination) -> termination.isDone(eaConfiguration, generation, population, fitness));
			}

		};
	}

	public static <T extends Comparable<T>> Termination<T> ofFitnessAtLeast(final T threshold) {
		Validate.notNull(threshold);
		return new Termination<T>() {

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				Validate.isTrue(generation >= 0);

				return fitness.stream()
						.anyMatch((fitnessValue) -> threshold.compareTo(fitnessValue) <= 0);
			}
		};
	}

	public static <T extends Comparable<T>> Termination<T> ofFitnessAtMost(final T threshold) {
		Validate.notNull(threshold);
		return new Termination<T>() {

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				Validate.isTrue(generation >= 0);

				return fitness.stream()
						.anyMatch((fitnessValue) -> threshold.compareTo(fitnessValue) >= 0);
			}
		};
	}

	/**
	 * Will terminate if the fitness does not improve over a specified number of
	 * generations
	 *
	 * @param <T>
	 * @param stableGenerationsCount
	 * @return
	 */
	public static <T extends Comparable<T>> Termination<T> ofStableFitness(final int stableGenerationsCount) {
		Validate.isTrue(stableGenerationsCount > 0);

		return new Termination<T>() {

			private long lastImprovedGeneration = -1;
			private T lastBestFitness = null;

			@Override
			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
					final List<Genotype> population, final List<T> fitness) {
				Validate.isTrue(generation >= 0);

				final Comparator<T> fitnessComparator = eaConfiguration.fitnessComparator();

				final Optional<T> bestFitnessOpt = fitness.stream()
						.max(fitnessComparator);

				if (lastImprovedGeneration < 0
						|| bestFitnessOpt.map(bestFitness -> fitnessComparator.compare(bestFitness, lastBestFitness) > 0)
								.orElse(false)) {
					lastImprovedGeneration = generation;
					lastBestFitness = bestFitnessOpt.get();
				}

				if (generation - lastImprovedGeneration > stableGenerationsCount) {
					return true;
				}
				return false;
			}
		};
	}
}