/* Copyright 2009-2016 David Hadka * * This file is part of the MOEA Framework. * * The MOEA Framework is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * The MOEA Framework is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>. */ package org.moeaframework; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import org.moeaframework.analysis.collector.Accumulator; import org.moeaframework.analysis.collector.AdaptiveMultimethodVariationCollector; import org.moeaframework.analysis.collector.AdaptiveTimeContinuationCollector; import org.moeaframework.analysis.collector.ApproximationSetCollector; import org.moeaframework.analysis.collector.Collector; import org.moeaframework.analysis.collector.ElapsedTimeCollector; import org.moeaframework.analysis.collector.EpsilonProgressCollector; import org.moeaframework.analysis.collector.IndicatorCollector; import org.moeaframework.analysis.collector.InstrumentedAlgorithm; import org.moeaframework.analysis.collector.PopulationSizeCollector; import org.moeaframework.core.Algorithm; import org.moeaframework.core.EpsilonBoxDominanceArchive; import org.moeaframework.core.NondominatedPopulation; import org.moeaframework.core.Problem; import org.moeaframework.core.Settings; import org.moeaframework.core.indicator.AdditiveEpsilonIndicator; import org.moeaframework.core.indicator.Contribution; import org.moeaframework.core.indicator.GenerationalDistance; import org.moeaframework.core.indicator.Hypervolume; import org.moeaframework.core.indicator.InvertedGenerationalDistance; import org.moeaframework.core.indicator.R1Indicator; import org.moeaframework.core.indicator.R2Indicator; import org.moeaframework.core.indicator.R3Indicator; import org.moeaframework.core.indicator.Spacing; import org.moeaframework.core.spi.ProblemFactory; /** * Instruments algorithms with {@link Collector}s which record information about * the runtime behavior of algorithms. First, the instrumenter walks the object * graph of an algorithm to determine its composition. Upon finding objects * which can be instrumented, it attaches the corresponding collector. Next, * the instrumenter returns an {@link InstrumentedAlgorithm}, which orchestrates * the collection of runtime information as the algorithm is executed. Lastly, * the {@code InstrumentedAlgorithm} stores the runtime information, which can * subsequently be accessed and analyzed. * <pre> * Instrumenter instrumenter = new Instrumenter() * .withProblem(problemName) * .attachAll(); * * Executor executor = new Executor() * .withProblem(problemName) * .withAlgorithm(algorithmName) * .withMaxEvaluations(numberOfEvaluations) * .withInstrumenter(instrumenter) * .run(); * * Accumulator accumulator = instrumenter.getLastAccumulator(); * </pre> */ public class Instrumenter extends ProblemBuilder { /** * {@code true} if the hypervolume collector is included; {@code false} * otherwise. */ private boolean includeHypervolume; /** * {@code true} if the generational distance collector is included; * {@code false} otherwise. */ private boolean includeGenerationalDistance; /** * {@code true} if the inverted generational distance collector is included; * {@code false} otherwise. */ private boolean includeInvertedGenerationalDistance; /** * {@code true} if the spacing collector is included; {@code false} * otherwise. */ private boolean includeSpacing; /** * {@code true} if the additive ε-indicator collector is included; * {@code false} otherwise. */ private boolean includeAdditiveEpsilonIndicator; /** * {@code true} if the contribution collector is included; {@code false} * otherwise. */ private boolean includeContribution; /** * {@code true} if the R1 collector is included; {@code false} otherwise. */ private boolean includeR1; /** * {@code true} if the R2 collector is included; {@code false} otherwise. */ private boolean includeR2; /** * {@code true} if the R3 collector is included; {@code false} otherwise. */ private boolean includeR3; /** * {@code true} if the ε-progress collector is included; * {@code false} otherwise. */ private boolean includeEpsilonProgress; /** * {@code true} if the adaptive multimethod variation collector is included; * {@code false} otherwise. */ private boolean includeAdaptiveMultimethodVariation; /** * {@code true} if the adaptive time continuation collector is included; * {@code false} otherwise. */ private boolean includeAdaptiveTimeContinuation; /** * {@code true} if the elapsed time collector is included; {@code false} * otherwise. */ private boolean includeElapsedTime; /** * {@code true} if the approximation set collector is included; * {@code false} otherwise. */ private boolean includeApproximationSet; /** * {@code true} if the population size collector is included; {@code false} * otherwise. */ private boolean includePopulationSize; /** * The frequency, in evaluations, that data is collected. */ private int frequency; /** * The collection of custom collectors added through the * {@link #attach(Collector)} method. This does not include built-in * collectors. */ private final List<Collector> customCollectors; /** * The accumulator from the last instrumented algorithm. */ private Accumulator lastAccumulator; /** * The packages that this instrumenter is allowed to access. */ private final List<String> allowedPackages; /** * Constructs a new instrumenter instance, initially with no collectors. */ public Instrumenter() { super(); frequency = 100; customCollectors = new ArrayList<Collector>(); allowedPackages = new ArrayList<String>(); allowedPackages.add("org.moeaframework"); allowedPackages.addAll(Arrays.asList(Settings.getAllowedPackages())); } /** * Returns the accumulator from the last instrumented algorithm. The * accumulator will be filled with the runtime information as the algorithm * is executed. * * @return the accumulator from the last instrumented algorithm */ public Accumulator getLastAccumulator() { return lastAccumulator; } /** * Allows this instrumenter to visit classes in the given package. Some * Java classes can not be readily visited/discovered by this instrumenter, * possibly resulting in a {@code NullPointerException} or a * {@code SecurityException}. Therefore, the instrumenter can only visit * classes explicitly allowed by the user. By default, * {@code org.moeaframework} is allowed. * * @param packageName a package name or the package name prefix * @return a reference to this instrumenter */ public Instrumenter addAllowedPackage(String packageName) { allowedPackages.add(packageName); return this; } /** * Removes one of the packages this instrumenter is allowed to visit. * Note that the given string must match the string passed to * {@link #addAllowedPackage(String)}. * * @param packageName the package name or package name prefix to remove * @return a reference to this instrumenter */ public Instrumenter removeAllowedPackage(String packageName) { allowedPackages.remove(packageName); return this; } /** * Returns all packages this instrumenter is allowed to visit. * * @return the list of allowed package names */ public List<String> getAllowedPackages() { return allowedPackages; } /** * Sets the frequency, in evaluations, that data is collected. * * @param frequency the frequency * @return a reference to this instrumenter */ public Instrumenter withFrequency(int frequency) { this.frequency = frequency; return this; } /** * Includes the specified collector when instrumenting algorithms. * * @param collector the collector to include when instrumenting algorithms * @return a reference to this instrumenter */ public Instrumenter attach(Collector collector) { customCollectors.add(collector); return this; } /** * Includes the hypervolume collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachHypervolumeCollector() { includeHypervolume = true; return this; } /** * Includes the generational distance collector when instrumenting * algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachGenerationalDistanceCollector() { includeGenerationalDistance = true; return this; } /** * Includes the inverted generational distance collector when instrumenting * algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachInvertedGenerationalDistanceCollector() { includeInvertedGenerationalDistance = true; return this; } /** * Includes the spacing collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachSpacingCollector() { includeSpacing = true; return this; } /** * Includes the additive ε-indicator collector when instrumenting * algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachAdditiveEpsilonIndicatorCollector() { includeAdditiveEpsilonIndicator = true; return this; } /** * Includes the contribution collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachContributionCollector() { includeContribution = true; return this; } /** * Includes the R1 collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachR1Collector() { includeR1 = true; return this; } /** * Includes the R2 collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachR2Collector() { includeR2 = true; return this; } /** * Includes the R3 collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachR3Collector() { includeR3 = true; return this; } /** * Includes all indicator collectors when instrumenting algorithms. This * includes hypervolume, generational distance, inverted generational * distance, spacing, additive ε-indicator, contribution, and the * R1, R2, and R3 indicators. * * @return a reference to this instrumenter */ public Instrumenter attachAllMetricCollectors() { attachHypervolumeCollector(); attachGenerationalDistanceCollector(); attachInvertedGenerationalDistanceCollector(); attachSpacingCollector(); attachAdditiveEpsilonIndicatorCollector(); attachContributionCollector(); attachR1Collector(); attachR2Collector(); attachR3Collector(); return this; } /** * Includes the ε-progress collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachEpsilonProgressCollector() { includeEpsilonProgress = true; return this; } /** * Includes the adaptive multimethod variation collector when instrumenting * algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachAdaptiveMultimethodVariationCollector() { includeAdaptiveMultimethodVariation = true; return this; } /** * Includes the adaptive time continuation collector when instrumenting * algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachAdaptiveTimeContinuationCollector() { includeAdaptiveTimeContinuation = true; return this; } /** * Includes the elapsed time collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachElapsedTimeCollector() { includeElapsedTime = true; return this; } /** * Includes the approximation set collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachApproximationSetCollector() { includeApproximationSet = true; return this; } /** * Includes the population size collector when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachPopulationSizeCollector() { includePopulationSize = true; return this; } /** * Includes all collectors when instrumenting algorithms. * * @return a reference to this instrumenter */ public Instrumenter attachAll() { attachAllMetricCollectors(); attachEpsilonProgressCollector(); attachAdaptiveMultimethodVariationCollector(); attachAdaptiveTimeContinuationCollector(); attachElapsedTimeCollector(); attachApproximationSetCollector(); attachPopulationSizeCollector(); return this; } @Override public Instrumenter withSameProblemAs(ProblemBuilder builder) { return (Instrumenter)super.withSameProblemAs(builder); } @Override public Instrumenter usingProblemFactory(ProblemFactory problemFactory) { return (Instrumenter)super.usingProblemFactory(problemFactory); } @Override public Instrumenter withProblem(String problemName) { return (Instrumenter)super.withProblem(problemName); } @Override public Instrumenter withProblemClass(Class<?> problemClass, Object... problemArguments) { return (Instrumenter)super.withProblemClass(problemClass, problemArguments); } @Override public Instrumenter withProblemClass(String problemClassName, Object... problemArguments) throws ClassNotFoundException { return (Instrumenter)super.withProblemClass(problemClassName, problemArguments); } @Override public Instrumenter withReferenceSet(File referenceSetFile) { return (Instrumenter)super.withReferenceSet(referenceSetFile); } @Override public Instrumenter withEpsilon(double... epsilon) { return (Instrumenter)super.withEpsilon(epsilon); } @Override public NondominatedPopulation getReferenceSet() { return super.getReferenceSet(); } /** * Recursively walks the object graph to 1) determine the nesting of objects * to help determine which objects are to be instrumented; and 2) attach the * {@code collector}s to any matching objects. * <p> * In order to avoid cycles in the object graph, objects are only traversed * the first time they are encountered. If an object appears multiple * times in the object graph, the {@code instrument} method will only be * invoked once. * <p> * When generating the nesting of objects, anonymous classes are given the * placeholder type {@code "(Anonymous)"}, without quotes. While the * contents of arrays and {@link Collection}s are listed in the nesting, * the array/collection object itself is not listed. For example, * the nesting will show {@code CompoundVariation >> PM} instead of * {@code CompoundVariation >> ArrayList >> Object[] >> PM}. * <p> * This method is reentrant. * * @param algorithm the instrumented algorithm * @param collectors the collectors to be attached * @param visited the set of visited objects, which may include the current * object when traversing its superclasses * @param parents the objects in which the current object is contained * @param object the current object undergoing reflection * @param type the superclass whose members are being reflected; or * {@code null} if the base type is to be used */ protected void instrument(InstrumentedAlgorithm algorithm, List<Collector> collectors, Set<Object> visited, Stack<Object> parents, Object object, Class<?> type) { if (object == null) { return; } else if ((type == null) || (type.equals(object.getClass()))) { if (visited.contains(object)) { return; } type = object.getClass(); } if (type.isAnnotation() || type.isEnum() || type.isPrimitive()) { //ignore objects which are not classes or arrays return; } else if (object instanceof Instrumenter) { //well this is embarrassing return; } else if (type.isArray()) { //recursively walk the elements in the array for (int i=0; i<Array.getLength(object); i++) { instrument(algorithm, collectors, visited, parents, Array.get(object, i), null); } } else if (object instanceof Collection) { //recursively walk the elements in the array for (Object element : (Collection<?>)object) { instrument(algorithm, collectors, visited, parents, element, null); } } else if (type.getPackage() != null) { //do not visit objects that are in inaccessible packages boolean allowed = false; for (String packageName : allowedPackages) { if (type.getPackage().getName().startsWith(packageName)) { allowed = true; break; } } if (!allowed) { return; } } if (!visited.contains(object)) { //attach any matching collectors for (Collector collector : collectors) { if (collector.getAttachPoint().matches(parents, object)) { algorithm.addCollector(collector.attach(object)); } } visited.add(object); } //recursively walk superclass to enumerate all non-public fields Class<?> superclass = type.getSuperclass(); if (superclass != null) { instrument(algorithm, collectors, visited, parents, object, superclass); } //recursively walk fields parents.push(object); for (Field field : type.getDeclaredFields()) { field.setAccessible(true); try { instrument(algorithm, collectors, visited, parents, field.get(object), null); } catch (IllegalArgumentException e) { //should never occur since object is of the specified type e.printStackTrace(); } catch (IllegalAccessException e) { //should never occur after setting field.setAccessible(true) e.printStackTrace(); } } parents.pop(); } /** * Instruments the specified algorithm, returning an * {@link InstrumentedAlgorithm} to be used to execute the algorithm and * store the data produced by any attached instruments. * * @param algorithm the algorithm to instrument * @return the instrumented algorithm * @throws IllegalArgumentException if no reference set is available or * could not be loaded */ public InstrumentedAlgorithm instrument(Algorithm algorithm) { List<Collector> collectors = new ArrayList<Collector>(); if (includeHypervolume || includeGenerationalDistance || includeInvertedGenerationalDistance || includeSpacing || includeAdditiveEpsilonIndicator || includeContribution || includeR1 || includeR2 || includeR3) { Problem problem = algorithm.getProblem(); NondominatedPopulation referenceSet = getReferenceSet(); EpsilonBoxDominanceArchive archive = null; if (epsilon != null) { archive = (EpsilonBoxDominanceArchive)newArchive(); } if (includeHypervolume) { collectors.add(new IndicatorCollector( new Hypervolume(problem, referenceSet), archive)); } if (includeGenerationalDistance) { collectors.add(new IndicatorCollector( new GenerationalDistance(problem, referenceSet), archive)); } if (includeInvertedGenerationalDistance) { collectors.add(new IndicatorCollector( new InvertedGenerationalDistance(problem, referenceSet), archive)); } if (includeSpacing) { collectors.add(new IndicatorCollector(new Spacing(problem), archive)); } if (includeAdditiveEpsilonIndicator) { collectors.add(new IndicatorCollector( new AdditiveEpsilonIndicator(problem, referenceSet), archive)); } if (includeContribution) { collectors.add(new IndicatorCollector( archive == null ? new Contribution(referenceSet) : new Contribution(referenceSet, archive.getComparator()), archive)); } if (includeR1) { collectors.add(new IndicatorCollector(new R1Indicator( problem, R1Indicator.getDefaultSubdivisions(problem), referenceSet), archive)); } if (includeR2) { collectors.add(new IndicatorCollector(new R2Indicator( problem, R2Indicator.getDefaultSubdivisions(problem), referenceSet), archive)); } if (includeR3) { collectors.add(new IndicatorCollector(new R3Indicator( problem, R3Indicator.getDefaultSubdivisions(problem), referenceSet), archive)); } } if (includeEpsilonProgress) { collectors.add(new EpsilonProgressCollector()); } if (includeAdaptiveMultimethodVariation) { collectors.add(new AdaptiveMultimethodVariationCollector()); } if (includeAdaptiveTimeContinuation) { collectors.add(new AdaptiveTimeContinuationCollector()); } if (includeElapsedTime) { collectors.add(new ElapsedTimeCollector()); } if (includeApproximationSet) { if (epsilon == null) { collectors.add(new ApproximationSetCollector()); } else { collectors.add(new ApproximationSetCollector(epsilon)); } } if (includePopulationSize) { collectors.add(new PopulationSizeCollector()); } collectors.addAll(customCollectors); InstrumentedAlgorithm instrumentedAlgorithm = new InstrumentedAlgorithm( algorithm, frequency); instrument(instrumentedAlgorithm, collectors, new HashSet<Object>(), new Stack<Object>(), algorithm, null); lastAccumulator = instrumentedAlgorithm.getAccumulator(); return instrumentedAlgorithm; } }