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

io.jenetics.ext.moea.MOEA Maven / Gradle / Ivy

The newest version!
/*
 * Java Genetic Algorithm Library (jenetics-8.1.0).
 * Copyright (c) 2007-2024 Franz Wilhelmstötter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author:
 *    Franz Wilhelmstötter ([email protected])
 */
package io.jenetics.ext.moea;

import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static io.jenetics.ext.moea.Pareto.front;

import java.util.Comparator;
import java.util.Objects;
import java.util.function.ToIntFunction;
import java.util.stream.Collector;

import io.jenetics.Gene;
import io.jenetics.Optimize;
import io.jenetics.Phenotype;
import io.jenetics.engine.EvolutionResult;
import io.jenetics.util.ISeq;
import io.jenetics.util.IntRange;

/**
 * Collectors for collecting final pareto-set for multi-objective
 * optimization.
 *
 * {@snippet lang="java":
 *  final Problem> problem = Problem.of(
 *      v -> Vec.of(v[0]*cos(v[1]), v[0]*sin(v[1])),
 *      Codecs.ofVector(
 *          DoubleRange.of(0, 1),
 *          DoubleRange.of(0, 2*PI)
 *      )
 *  );
 *
 *  final Engine> engine = Engine.builder(problem)
 *      .alterers(
 *          new Mutator<>(0.1),
 *          new MeanAlterer<>())
 *      .offspringSelector(new TournamentSelector<>(2))
 *      .survivorsSelector(UFTournamentSelector.ofVec())
 *      .build();
 *
 *  final ISeq>> result = engine.stream()
 *      .limit(Limits.byFixedGeneration(50))
 *      .collect(MOEA.toParetoSet());
 * }
 *
 * @author Franz Wilhelmstötter
 * @version 5.1
 * @since 4.1
 */
public final class MOEA {

	private static final IntRange DEFAULT_SET_RANGE = IntRange.of(75, 100);

	private MOEA() {
	}

	/**
	 * Collector of {@link Phenotype} objects, who's (multi-objective) fitness
	 * value is part of the 
	 *     pareto front.
	 *
	 * @param  the gene type
	 * @param  the array type, e.g. {@code double[]}
	 * @param  the multi object result type vector
	 * @return the pareto set collector
	 * @throws IllegalArgumentException if the minimal pareto set {@code size}
	 *         is smaller than one
	 */
	public static , T, V extends Vec>
	Collector, ?, ISeq>>
	toParetoSet() {
		return toParetoSet(DEFAULT_SET_RANGE);
	}

	/**
	 * Collector of {@link Phenotype} objects, who's (multi-objective) fitness
	 * value is part of the 
	 *     pareto front.
	 *
	 * @param size the allowed size range of the returned pareto set. If the
	 *        size of the pareto set is bigger than {@code size.getMax()},
	 *        during the collection, it is reduced to {@code size.getMin()}.
	 *        Pareto set elements which are close to each other are removed first.
	 * @param  the gene type
	 * @param  the array type, e.g. {@code double[]}
	 * @param  the multi object result type vector
	 * @return the pareto set collector
	 * @throws NullPointerException if one the {@code size} is {@code null}
	 * @throws IllegalArgumentException if the minimal pareto set {@code size}
	 *         is smaller than one
	 */
	public static , T, V extends Vec>
	Collector, ?, ISeq>>
	toParetoSet(final IntRange size) {
		return toParetoSet(
			size,
			Vec::dominance,
			Vec::compare,
			Vec::distance,
			Vec::length
		);
	}

	/**
	 * Collector of {@link Phenotype} objects, who's (multi-objective) fitness
	 * value is part of the 
	 *     pareto front.
	 *
	 * @see #toParetoSet(IntRange)
	 *
	 * @param size the allowed size range of the returned pareto set. If the
	 *        size of the pareto set is bigger than {@code size.getMax()},
	 *        during the collection, it is reduced to {@code size.getMin()}.
	 *        Pareto set elements which are close to each other are removed first.
	 * @param dominance the pareto dominance measure of the fitness result type
	 *        {@code C}
	 * @param comparator the comparator of the elements of the vector type
	 *        {@code C}
	 * @param distance the distance function of two elements of the vector
	 *        type {@code C}
	 * @param dimension the dimensionality of the result vector {@code C}.
	 *        Usually {@code Vec::length}.
	 * @param  the gene type
	 * @param  the multi object result vector. E.g. {@code Vec}
	 * @return the pareto set collector
	 * @throws NullPointerException if one the arguments is {@code null}
	 * @throws IllegalArgumentException if the minimal pareto set {@code size}
	 *         is smaller than one
	 */
	public static , C extends Comparable>
	Collector, ?, ISeq>>
	toParetoSet(
		final IntRange size,
		final Comparator dominance,
		final ElementComparator comparator,
		final ElementDistance distance,
		final ToIntFunction dimension
	) {
		requireNonNull(size);
		requireNonNull(dominance);
		requireNonNull(distance);

		if (size.min() < 1) {
			throw new IllegalArgumentException(format(
				"Minimal pareto set size must be greater than zero: %d",
				size.min()
			));
		}

		return Collector.of(
			() -> new Front(
				size, dominance, comparator, distance, dimension
			),
			Front::add,
			Front::merge,
			Front::toISeq
		);
	}

	private static final class Front<
		G extends Gene,
		C extends Comparable
	> {

		final IntRange _size;
		final Comparator _dominance;
		final ElementComparator _comparator;
		final ElementDistance _distance;
		final ToIntFunction _dimension;

		private Optimize _optimize;
		private ParetoFront> _front;

		Front(
			final IntRange size,
			final Comparator dominance,
			final ElementComparator comparator,
			final ElementDistance distance,
			final ToIntFunction dimension
		) {
			_size = size;
			_dominance = dominance;
			_comparator = comparator;
			_distance = distance;
			_dimension = dimension;
		}

		void add(final EvolutionResult result) {
			if (_front == null) {
				_optimize = result.optimize();
				_front = new ParetoFront<>(this::dominance, this::equals);
			}

			final ISeq> front = front(
				result.population(),
				this::dominance
			);
			_front.addAll(front.asList());
			trim();
		}

		private int dominance(final Phenotype a, final Phenotype b) {
			return _optimize == Optimize.MAXIMUM
				? _dominance.compare(a.fitness(), b.fitness())
				: _dominance.compare(b.fitness(), a.fitness());
		}

		private boolean equals(final Phenotype a, final Phenotype b) {
			return Objects.equals(a.genotype(), b.genotype());
		}

		private void trim() {
			assert _front != null;
			assert _optimize != null;

			if (_front.size() > _size.max() - 1) {
				_front.trim(
					_size.min(),
					this::compare,
					_distance.map(Phenotype::fitness),
					v -> _dimension.applyAsInt(v.fitness())
				);
			}
		}

		private int compare(
			final Phenotype a,
			final Phenotype b,
			final int i
		) {
			return _optimize == Optimize.MAXIMUM
				? _comparator.compare(a.fitness(), b.fitness(), i)
				: _comparator.compare(b.fitness(), a.fitness(), i);
		}

		Front merge(final Front front) {
			_front.merge(front._front);
			trim();
			return this;
		}

		ISeq> toISeq() {
			return _front != null ? _front.toISeq() : ISeq.empty();
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy