diff --git a/JAICore/jaicore-basic/src/main/java/jaicore/basic/algorithm/AOptimizer.java b/JAICore/jaicore-basic/src/main/java/jaicore/basic/algorithm/AOptimizer.java index 77eb841ba1..dbbe35c245 100644 --- a/JAICore/jaicore-basic/src/main/java/jaicore/basic/algorithm/AOptimizer.java +++ b/JAICore/jaicore-basic/src/main/java/jaicore/basic/algorithm/AOptimizer.java @@ -11,18 +11,39 @@ import jaicore.basic.algorithm.events.SolutionCandidateFoundEvent; import jaicore.basic.algorithm.exceptions.AlgorithmException; +/** + * The AOptimizer represents an algorithm that is meant to optimize for a single best solution. + * While it may observe multiple candidates for the best solution and report them via events + * when running, eventually it will return only the single best observed one. + * + * @author fmohr, wever + * + * @param The type of input instances (problems) to be solved by the algorithm. + * @param The type of output that is obtained by running the algorithm on the input. + * @param The type performance values will have to compare different solutions. + */ public abstract class AOptimizer, V extends Comparable> extends ASolutionCandidateIterator implements IOptimizationAlgorithm { /* Logger variables */ private Logger logger = LoggerFactory.getLogger(AOptimizer.class); private String loggerName; + /* currently best solution candidate observed so far */ private O bestSeenSolution; + /** + * C'tor taking only an input as a parameter. + * @param input The input of the algorithm. + */ public AOptimizer(final I input) { super(input); } + /** + * C'tor taking a configuration of the algorithm and an input for the algorithm as arguments. + * @param config The parameterization of the algorithm. + * @param input The input to the algorithm (the problem to solve). + */ protected AOptimizer(final IAlgorithmConfig config, final I input) { super(config, input); } @@ -30,10 +51,11 @@ protected AOptimizer(final IAlgorithmConfig config, final I input) { /** * Updates the best seen solution if the new solution is better. Returns true iff the best seen solution has been updated. * - * @param candidate - * @return + * @param candidate A candidate for a new best seen solution. It is only updated iff the candidate performs better than the bestSeenSolution observed so far. + * @return Returns true iff the candidate is the best seen solution. */ protected boolean updateBestSeenSolution(final O candidate) { + assert (candidate != null) : "Cannot update best solution with null."; if (this.bestSeenSolution == null || (candidate.getScore() != null && candidate.getScore().compareTo(this.bestSeenSolution.getScore()) < 0)) { this.bestSeenSolution = candidate; return true; @@ -61,6 +83,9 @@ public SolutionCandidateFoundEvent nextSolutionCandidateEvent() throws Interr throw new NoSuchElementException(); } + /** + * @return The best seen solution, yet. + */ public O getBestSeenSolution() { return this.bestSeenSolution; } diff --git a/softwareconfiguration/hasco/src/main/java/hasco/core/HASCO.java b/softwareconfiguration/hasco/src/main/java/hasco/core/HASCO.java index 0a79273447..337c0a9e1d 100644 --- a/softwareconfiguration/hasco/src/main/java/hasco/core/HASCO.java +++ b/softwareconfiguration/hasco/src/main/java/hasco/core/HASCO.java @@ -1,4 +1,5 @@ package hasco.core; + import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -56,8 +57,7 @@ * @param * @param */ -public class HASCO, N, A, V extends Comparable> - extends SoftwareConfigurationAlgorithm, HASCOSolutionCandidate, V> { +public class HASCO, N, A, V extends Comparable> extends SoftwareConfigurationAlgorithm, HASCOSolutionCandidate, V> { private Logger logger = LoggerFactory.getLogger(HASCO.class); private String loggerName; @@ -74,13 +74,13 @@ public class HASCO, N, A, V extends Compa private final List> listOfAllRecognizedSolutions = new ArrayList<>(); private int numUnparametrizedSolutions = -1; private final Set returnedUnparametrizedComponentInstances = new HashSet<>(); - + private Map, HASCOSolutionEvent> hascoSolutionEventCache = new HashMap<>(); + /* runtime variables of algorithm */ private final TimeRecordingEvaluationWrapper timeGrabbingEvaluationWrapper; public HASCO(final RefinementConfiguredSoftwareConfigurationProblem configurationProblem, final IHASCOPlanningGraphGeneratorDeriver planningGraphGeneratorDeriver, - final IOptimalPathInORGraphSearchFactory searchFactory, - final AlgorithmProblemTransformer, ISearch> searchProblemTransformer) { + final IOptimalPathInORGraphSearchFactory searchFactory, final AlgorithmProblemTransformer, ISearch> searchProblemTransformer) { super(ConfigFactory.create(HASCOConfig.class), configurationProblem); if (configurationProblem == null) { throw new IllegalArgumentException("Cannot work with configuration problem NULL"); @@ -103,21 +103,17 @@ public HASCO(final RefinementConfiguredSoftwareConfigurationProblem configura /* derive planning problem and search problem */ this.logger.debug("Deriving search problem"); RefinementConfiguredSoftwareConfigurationProblem refConfigSoftwareConfigurationProblem = new RefinementConfiguredSoftwareConfigurationProblem<>( - new SoftwareConfigurationProblem(this.getInput().getComponents(), this.getInput().getRequiredInterface(), this.timeGrabbingEvaluationWrapper), - this.getInput().getParamRefinementConfig()); + new SoftwareConfigurationProblem(this.getInput().getComponents(), this.getInput().getRequiredInterface(), this.timeGrabbingEvaluationWrapper), this.getInput().getParamRefinementConfig()); this.planningProblem = new HASCOReduction().transform(refConfigSoftwareConfigurationProblem); if (this.logger.isDebugEnabled()) { - String operations = this.planningProblem.getCorePlanningProblem().getDomain().getOperations().stream().map( - o -> "\n\t\t" + o.getName() + "(" + o.getParams() + ")\n\t\t\tPre: " + o.getPrecondition() + "\n\t\t\tAdd List: " + o.getAddLists() + "\n\t\t\tDelete List: " + o.getDeleteLists()) - .collect(Collectors.joining()); - String methods = this.planningProblem - .getCorePlanningProblem().getDomain().getMethods().stream().map(m -> "\n\t\t" + m.getName() + "(" + m.getParameters() + ") for task " + m.getTask() + "\n\t\t\tPre: " - + m.getPrecondition() + "\n\t\t\tPre Eval: " + m.getEvaluablePrecondition() + "\n\t\t\tNetwork: " + m.getNetwork().getLineBasedStringRepresentation()) - .collect(Collectors.joining()); + String operations = this.planningProblem.getCorePlanningProblem().getDomain().getOperations().stream() + .map(o -> "\n\t\t" + o.getName() + "(" + o.getParams() + ")\n\t\t\tPre: " + o.getPrecondition() + "\n\t\t\tAdd List: " + o.getAddLists() + "\n\t\t\tDelete List: " + o.getDeleteLists()).collect(Collectors.joining()); + String methods = this.planningProblem.getCorePlanningProblem().getDomain().getMethods().stream().map(m -> "\n\t\t" + m.getName() + "(" + m.getParameters() + ") for task " + m.getTask() + "\n\t\t\tPre: " + m.getPrecondition() + + "\n\t\t\tPre Eval: " + m.getEvaluablePrecondition() + "\n\t\t\tNetwork: " + m.getNetwork().getLineBasedStringRepresentation()).collect(Collectors.joining()); this.logger.debug("Derived the following HTN planning problem:\n\tOperations:{}\n\tMethods:{}", operations, methods); } this.searchProblem = new CostSensitivePlanningToSearchProblemTransformer(this.planningGraphGeneratorDeriver).transform(this.planningProblem); - + /* create search object */ this.logger.debug("Creating and initializing the search object"); this.searchFactory.setProblemInput(this.searchProblem, this.searchProblemTransformer); @@ -128,47 +124,75 @@ public HASCO(final RefinementConfiguredSoftwareConfigurationProblem configura public AlgorithmEvent nextWithException() throws InterruptedException, AlgorithmExecutionCanceledException, TimeoutException, AlgorithmException { /* check on termination */ - logger.trace("Conducting next step in {}.", this.getId()); + this.logger.trace("Conducting next step in {}.", this.getId()); try { this.checkAndConductTermination(); - } catch (DelayedTimeoutCheckException e1) { - e1.printStackTrace(); - throw e1.getException(); - } catch (DelayedCancellationCheckException e1) { - e1.printStackTrace(); - throw e1.getException(); + } catch (DelayedTimeoutCheckException e) { + this.logger.warn("Delayed timeout exception has been thrown", e); + throw e.getException(); + } catch (DelayedCancellationCheckException e) { + this.logger.warn("HASCO was cancelled with significant delay.", e); + throw e.getException(); } - logger.trace("No stop criteria have caused HASCO to stop up to now. Proceeding ..."); + this.logger.trace("No stop criteria have caused HASCO to stop up to now. Proceeding ..."); /* act depending on state */ switch (this.getState()) { case created: { this.logger.info("Starting HASCO run."); AlgorithmInitializedEvent event = this.activate(); - + /* analyze problem */ - numUnparametrizedSolutions = Util.getNumberOfUnparametrizedCompositions(getInput().getComponents(), getInput().getRequiredInterface()); - logger.info("Search space contains {} unparametrized solutions.", numUnparametrizedSolutions); - + this.numUnparametrizedSolutions = Util.getNumberOfUnparametrizedCompositions(this.getInput().getComponents(), this.getInput().getRequiredInterface()); + this.logger.info("Search space contains {} unparametrized solutions.", this.numUnparametrizedSolutions); + /* setup search algorithm */ this.search.setNumCPUs(this.getNumCPUs()); this.search.setTimeout(this.getTimeout()); if (this.loggerName != null && this.loggerName.length() > 0 && this.search instanceof ILoggingCustomizable) { - this.logger.info("Setting logger name of {} to {}", this.search.getId(), this.loggerName + ".search"); + this.logger.info("Setting logger name of {} to {}.search", this.search.getId(), this.loggerName); ((ILoggingCustomizable) this.search).setLoggerName(this.loggerName + ".search"); } else { - this.logger.info("Not setting the logger name of the search. Logger name of HASCO is {}. Search loggingCustomizable: {}", this.loggerName, - (this.search instanceof ILoggingCustomizable)); + this.logger.info("Not setting the logger name of the search. Logger name of HASCO is {}. Search loggingCustomizable: {}", this.loggerName, (this.search instanceof ILoggingCustomizable)); } /* register a listener on the search that will forward all events to HASCO's event bus */ this.search.registerListener(new Object() { - + + @Subscribe + public void receiveSearchEvent(final AlgorithmEvent event) { + if (!(event instanceof AlgorithmInitializedEvent || event instanceof AlgorithmFinishedEvent)) { + HASCO.this.post(event); + } + } + @Subscribe - public void receiveSearchEvent(AlgorithmEvent event) { - if (!(event instanceof AlgorithmInitializedEvent || event instanceof AlgorithmFinishedEvent)) - post(event); + public void receiveSolutionCandidateFoundEvent(final EvaluatedSearchSolutionCandidateFoundEvent solutionEvent) throws InterruptedException, TimeoutException, AlgorithmException { + EvaluatedSearchGraphPath searchPath = solutionEvent.getSolutionCandidate(); + Plan plan = HASCO.this.planningGraphGeneratorDeriver.getPlan(searchPath.getNodes()); + ComponentInstance objectInstance = Util.getSolutionCompositionForPlan(HASCO.this.getInput().getComponents(), HASCO.this.planningProblem.getCorePlanningProblem().getInit(), plan, true); + HASCO.this.returnedUnparametrizedComponentInstances.add(new UnparametrizedComponentInstance(objectInstance)); + V score; + try { + boolean scoreInCache = HASCO.this.timeGrabbingEvaluationWrapper.hasEvaluationForComponentInstance(objectInstance); + if (scoreInCache) { + score = solutionEvent.getSolutionCandidate().getScore(); + } else { + score = HASCO.this.timeGrabbingEvaluationWrapper.evaluate(objectInstance); + } + } catch (ObjectEvaluationFailedException e) { + throw new AlgorithmException(e, "Could not evaluator component instance"); + } + HASCO.this.logger.info("Received new solution with score {} from search, communicating this solution to the HASCO listeners. Number of returned unparametrized solutions is now {}/{}.", score, + HASCO.this.returnedUnparametrizedComponentInstances.size(), HASCO.this.numUnparametrizedSolutions); + EvaluatedSearchGraphBasedPlan evaluatedPlan = new EvaluatedSearchGraphBasedPlan<>(plan, score, searchPath); + HASCOSolutionCandidate solution = new HASCOSolutionCandidate<>(objectInstance, evaluatedPlan, HASCO.this.timeGrabbingEvaluationWrapper.getEvaluationTimeForComponentInstance(objectInstance)); + HASCO.this.updateBestSeenSolution(solution); + HASCO.this.listOfAllRecognizedSolutions.add(solution); + HASCOSolutionEvent hascoSolutionEvent = new HASCOSolutionEvent<>(HASCO.this.getId(), solution); + HASCO.this.post(hascoSolutionEvent); } + }); /* now initialize the search */ @@ -177,65 +201,41 @@ public void receiveSearchEvent(AlgorithmEvent event) { AlgorithmEvent searchEvent; do { searchEvent = this.search.nextWithException(); - logger.debug("Observing search event {}", searchEvent); + this.logger.debug("Observing search event {}", searchEvent); searchInitializationObserved = (searchEvent instanceof AlgorithmInitializedEvent); } while (this.search.hasNext() && !searchInitializationObserved); if (!searchInitializationObserved) { throw new IllegalStateException("The search underlying HASCO could not be initialized successully."); } - logger.debug("Search has been initialized."); - logger.info("HASCO initialization completed."); + this.logger.debug("Search has been initialized."); + this.logger.info("HASCO initialization completed."); return event; } case active: { - /* step search */ AlgorithmEvent searchEvent = this.search.nextWithException(); if (searchEvent instanceof AlgorithmFinishedEvent) { - logger.info("The search algorithm has finished. Terminating HASCO."); + this.logger.info("The search algorithm has finished. Terminating HASCO."); return this.terminate(); } /* otherwise, if a solution has been found, we announce this finding to our listeners and memorize if it is a new best candidate */ else if (searchEvent instanceof EvaluatedSearchSolutionCandidateFoundEvent) { - @SuppressWarnings("unchecked") - EvaluatedSearchSolutionCandidateFoundEvent solutionEvent = (EvaluatedSearchSolutionCandidateFoundEvent) searchEvent; - EvaluatedSearchGraphPath searchPath = solutionEvent.getSolutionCandidate(); - Plan plan = this.planningGraphGeneratorDeriver.getPlan(searchPath.getNodes()); - ComponentInstance objectInstance = Util.getSolutionCompositionForPlan(this.getInput().getComponents(), this.planningProblem.getCorePlanningProblem().getInit(), plan, true); - returnedUnparametrizedComponentInstances.add(new UnparametrizedComponentInstance(objectInstance)); - V score; - try { - boolean scoreInCache = this.timeGrabbingEvaluationWrapper.hasEvaluationForComponentInstance(objectInstance); -// assert (scoreInCache || solutionEvent.getSolutionCandidate().getScore() == null) : "Solution candidate has a score but is not in cache of the timeGrabbingEvaluationWrapper"; - if (scoreInCache) { - score = solutionEvent.getSolutionCandidate().getScore(); - } else { - score = this.timeGrabbingEvaluationWrapper.evaluate(objectInstance); - } - } catch (ObjectEvaluationFailedException e) { - throw new AlgorithmException(e, "Could not evaluate component instance."); - } - this.logger.info("Received new solution with score {} from search, communicating this solution to the HASCO listeners. Number of returned unparametrized solutions is now {}/{}.", score, returnedUnparametrizedComponentInstances.size(), numUnparametrizedSolutions); - EvaluatedSearchGraphBasedPlan evaluatedPlan = new EvaluatedSearchGraphBasedPlan<>(plan, score, searchPath); - HASCOSolutionCandidate solution = new HASCOSolutionCandidate<>(objectInstance, evaluatedPlan, - this.timeGrabbingEvaluationWrapper.getEvaluationTimeForComponentInstance(objectInstance)); - this.updateBestSeenSolution(solution); - this.listOfAllRecognizedSolutions.add(solution); - HASCOSolutionEvent hascoSolutionEvent = new HASCOSolutionEvent<>(getId(), solution); + HASCOSolutionEvent hascoSolutionEvent = this.hascoSolutionEventCache.remove(searchEvent); + assert (hascoSolutionEvent != null) : "Hasco solution event has not been seen yet or cannot be retrieved from cache."; + this.logger.info("Received new solution with score {} from search, communicating this solution to the HASCO listeners. Number of returned unparametrized solutions is now {}/{}.", hascoSolutionEvent.getScore(), + this.returnedUnparametrizedComponentInstances.size(), this.numUnparametrizedSolutions); this.post(hascoSolutionEvent); return hascoSolutionEvent; - } - - /* if this is an ordinary graph search event, just return it */ - else + } else { return searchEvent; + } } default: throw new IllegalStateException("HASCO cannot do anything in state " + this.getState()); } } - + public GraphGenerator getGraphGenerator() { return this.searchProblem.getGraphGenerator(); } @@ -246,17 +246,17 @@ public CostSensitiveHTNPlanningProblem getPlanningP @Override public void cancel() { - if (isCanceled()) { - logger.debug("Ignoring cancel, because cancel has been triggered in the past already."); + if (this.isCanceled()) { + this.logger.debug("Ignoring cancel, because cancel has been triggered in the past already."); return; } - logger.info("Received cancel, first processing the cancel locally, then forwarding to search."); + this.logger.info("Received cancel, first processing the cancel locally, then forwarding to search."); super.cancel(); if (this.search != null) { - logger.info("Trigger cancel on search."); + this.logger.info("Trigger cancel on search."); this.search.cancel(); } - logger.info("Finished, now terminating"); + this.logger.info("Finished, now terminating"); this.terminate(); } @@ -274,7 +274,7 @@ public HASCORunReport getReport() { @Override protected void shutdown() { - if (isShutdownInitialized()) { + if (this.isShutdownInitialized()) { this.logger.debug("Shutdown has already been initialized, ignoring new shutdown request."); return; } @@ -291,11 +291,11 @@ public HASCOConfig getConfig() { } public IOptimalPathInORGraphSearchFactory getSearchFactory() { - return searchFactory; + return this.searchFactory; } public IOptimalPathInORGraphSearch getSearch() { - return search; + return this.search; } @Override @@ -311,11 +311,11 @@ public void setLoggerName(final String name) { this.logger.info("Activated logger for {} with name {}", this.getId(), name); super.setLoggerName(this.loggerName + "._swConfigAlgo"); if (this.getInput().getCompositionEvaluator() instanceof ILoggingCustomizable) { - this.logger.info("Setting logger of HASCO solution evaluator {} to {}.", getInput().getCompositionEvaluator().getClass().getName(), name + ".solutionevaluator"); - ((ILoggingCustomizable)getInput().getCompositionEvaluator()).setLoggerName(name + ".solutionevaluator"); + this.logger.info("Setting logger of HASCO solution evaluator {} to {}.solutionevaluator.", this.getInput().getCompositionEvaluator().getClass().getName(), name); + ((ILoggingCustomizable) this.getInput().getCompositionEvaluator()).setLoggerName(name + ".solutionevaluator"); + } else { + this.logger.info("The solution evaluator {} does not implement ILoggingCustomizable, so no customization possible.", this.getInput().getCompositionEvaluator().getClass().getName()); } - else - this.logger.info("The solution evaluator {} does not implement ILoggingCustomizable, so no customization possible.", getInput().getCompositionEvaluator().getClass().getName()); } @Override