/******************************************************************************* * Copyright (c) 2010-2014, Miklos Foldenyi, Andras Szabolcs Nagy, Abel Hegedus, Akos Horvath, Zoltan Ujhelyi and Daniel Varro * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-v20.html. * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ package org.eclipse.viatra.dse.api; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.viatra.dse.api.strategy.interfaces.IStrategy; import org.eclipse.viatra.dse.base.DesignSpaceManager; import org.eclipse.viatra.dse.base.GlobalContext; import org.eclipse.viatra.dse.designspace.api.DesignSpace; import org.eclipse.viatra.dse.designspace.api.IDesignSpace; import org.eclipse.viatra.dse.objectives.IGlobalConstraint; import org.eclipse.viatra.dse.objectives.IObjective; import org.eclipse.viatra.dse.solutionstore.ISolutionNameProvider; import org.eclipse.viatra.dse.solutionstore.IdBasedSolutionNameProvider; import org.eclipse.viatra.dse.solutionstore.SolutionStore; import org.eclipse.viatra.dse.statecode.IStateCoder; import org.eclipse.viatra.dse.statecode.IStateCoderFactory; import org.eclipse.viatra.dse.statecoding.simple.SimpleStateCoderFactory; import org.eclipse.viatra.dse.visualizer.IDesignSpaceVisualizer; import org.eclipse.viatra.query.runtime.matchers.util.Preconditions; import org.eclipse.viatra.transformation.evm.api.resolver.ConflictResolver; import org.eclipse.viatra.transformation.runtime.emf.rules.batch.BatchTransformationRule; /** *
* The {@link DesignSpaceExplorer} is the main API of the Design Space Exploration engine. *
* ** To parameterize the algorithm one must use the following methods after instantiating: *
* Designs Space Exploration is the process of finding a sequence (or sequences) of predefined transformation * rules ("transitions") that, if applied in order on the starting model, results in a new model state that fulfills the * hard (or goal) constraints and is near optimal with respect to the objectives. *
* ** An extension to this paradigm is the introduction of global constraints, which guarantees, that no sequence will be * returned, which if executed, results in an intermediate model state that violates the specified global constraints, * including the final state. You can add constraints by invoking {@link #addGlobalConstraint(IGlobalConstraint)}. *
* * @author Andras Szabolcs Nagy & Miklos Foldenyi * */ public class DesignSpaceExplorer { private Notifier model; private GlobalContext globalContext = new GlobalContext(); private final Logger logger = Logger.getLogger(this.getClass()); private Set* Creates a {@link DesignSpaceExplorer} object that is able to execute a design space exploration process. *
* *
* By default the state coder used is the generic (not meta-model specific) {@link GraphHash}. You can provide your
* custom state coder by implementing the {@link IStateCoderFactory} and {@link IStateCoder} interfaces, and passing
* the former to the {@link #setStateCoderFactory(IStateCoderFactory)} method.
*
*/
public DesignSpaceExplorer() {
setDesignspace(new DesignSpace());
}
/**
* Adds a metamodel in the form of {@link EPackage}, which is needed for certain guidance.
*
* @param metaModelPackage
*/
public void addMetaModelPackage(EPackage metaModelPackage) {
metaModelPackages.add(metaModelPackage);
}
/**
* Defines the initial model of the exploration, and whether it is supposed to be used to execute the DSE process or
* it should be cloned. Please note, that in multithreaded mode any subsequent threads will be working on cloned
* models.
*
* @param model
* The root object of the EMF model.
* @param deepCopyModel
* If it is set to true, the exploration will run on a cloned model.
*/
public void setInitialModel(Notifier model, boolean deepCopyModel) {
this.model = model;
this.deepCopyModel = deepCopyModel;
}
/**
* Defines the initial model of the exploration. The model will be cloned, which is desired in most cases as the
* given model won't be changed.
*
* @param model
* The root object of the EMF model.
*/
public void setInitialModel(Notifier model) {
setInitialModel(model, true);
}
/**
* Defines the initial model of the exploration. The given model won't be cloned, thus the exploration will modify
* it.
*
* @param model
* The root object of the EMF model. It won't be cloned.
*/
public void setInitialModelUncloned(Notifier model) {
setInitialModel(model, false);
}
/**
* Adds a {@link BatchTransformationRule}.
*
* @param rule
* The transformationRule.
*/
public void addTransformationRule(BatchTransformationRule, ?> rule) {
Preconditions.checkArgument(rule != null);
for (BatchTransformationRule, ?> rule2 : globalContext.getTransformations()) {
if (rule.getPrecondition().equals(rule2.getPrecondition())) {
throw new DSEException(
"Two transformation rule ("
+ rule.getName()
+ "; "
+ rule2.getName()
+ ") uses the same LHS VIATRA Query pattern ("
+ rule.getPrecondition().getFullyQualifiedName()
+ "), which may lead to hash collision."
+ " Please wrap the pattern with an other pattern with the 'find' keyword (or duplicate the code), and use that for one of the rules LHS.");
}
}
globalContext.getTransformations().add(rule);
}
/**
* Adds a global constraint to the exploration process. Please see the {@link IGlobalConstraint} interface and its
* implementations for details.
*
* @param constraint
* The global constraint.
* @see IGlobalConstraint
*/
public void addGlobalConstraint(IGlobalConstraint constraint) {
globalContext.getGlobalConstraints().add(constraint);
}
/**
* Adds an objective the the exploration process. Please see the {@link IObjective} interface and its
* implementations for details.
*
* @param objective
* The objective.
* @see IObjective
*/
public void addObjective(IObjective objective) {
for (IObjective o : globalContext.getObjectives()) {
if (o.getName().equals(objective.getName())) {
throw new DSEException("Two objectives with the same name cannot be registered:" + o.getName());
}
}
globalContext.getObjectives().add(objective);
}
/**
* Sets a {@link IStateCoderFactory} for which will be used for creating {@link IStateCoder}s. The default
* implementation is the {@link SimpleStateCoderFactory}, which works well in most of the cases.
*
* @param stateCoderFactory
* The factory.
*/
public final void setStateCoderFactory(IStateCoderFactory stateCoderFactory) {
globalContext.setStateCoderFactory(stateCoderFactory);
}
/**
* Defines the maximum processing threads that the design space exploration can use. Note, that this is only
* limiting the threads doing the actual calculation. By default this value will be set to the number of logical
* processors (including HyperThreading) in the computer, reported by {@link Runtime#availableProcessors()}.
*
* @param maxNumberOfThreads
* The number of maximum processing threads available to the design space exploration process.
*/
public void setMaxNumberOfThreads(int maxNumberOfThreads) {
globalContext.getThreadPool().setMaximumPoolSize(maxNumberOfThreads);
}
/**
* Sets the {@link IDesignSpace} implementation that is to be used during the design space exploration process. By
* default, the {@link DesignSpace} implementation is used.
*
* @param designspace
* The {@link IDesignSpace} implementation.
*/
public final void setDesignspace(IDesignSpace designspace) {
globalContext.setDesignSpace(designspace);
}
/**
* Sets the solution store for strategies. Please see the {@link SolutionStore} for how to configure it.
*
* @param solutionStore
* The parameterized {@link SolutionStore} implementation.
*/
public void setSolutionStore(SolutionStore solutionStore) {
globalContext.setSolutionStore(solutionStore);
}
/**
* Starts the design space exploration. It returns only when the strategy decides to stop the execution.
*
* @param strategy
* The strategy of the exploration.
*/
public void startExploration(IStrategy strategy) {
startExploration(strategy, true, -1);
}
/**
* Starts the design space exploration asynchronously. Completion of the process can be verified by calling
* {@link DesignSpaceExplorer#isDone()}.
*
* @param strategy
* The strategy of the exploration.
*/
public void startExplorationAsync(IStrategy strategy) {
startExploration(strategy, false, -1);
}
/**
* Starts the design space exploration with a timeout. It returns only when the strategy decides to stop the
* execution or the given timeout is elapsed.
*
* @param strategy
* The strategy of the exploration.
* @param timeout
* The number of milliseconds before the exploration is forced to stop.
* @return Returns true if the exploration stopped by the timeout.
*/
public boolean startExplorationWithTimeout(IStrategy strategy, long timeout) {
return startExploration(strategy, true, timeout);
}
/**
* Starts the design space exploration asynchronously with a timeout. Completion of the process can be verified by
* calling {@link DesignSpaceExplorer#isDone()}.
*
* @param strategy
* The strategy of the exploration.
* @param timeout
* The number of milliseconds before the exploration is forced to stop.
* @return Returns true if the exploration stopped by the timeout.
*/
public boolean startExplorationAsyncWithTimeout(IStrategy strategy, long timeout) {
return startExploration(strategy, false, timeout);
}
/**
* Starts the design space exploration. If {@code waitForTermination} is true, then it returns only when the
* strategy decides to stop the execution or there was a timeout, otherwise when the exploration process is started
* it returns immediately. In this case, process completion can be verified by calling
* {@link DesignSpaceExplorer#isDone()}.
*
* @param strategy
* The strategy of the exploration.
* @param waitForTermination
* True if the method must wait for the engine to stop, i.e. whether to start synchronously.
* @param timeout
* The number of milliseconds before the exploration is forced to stop.
* @return Returns true if the exploration stopped by the timeout.
*/
public boolean startExploration(IStrategy strategy, boolean waitForTermination, final long timeout) {
initExploration(strategy);
Timer timer = new Timer();
final AtomicBoolean wasTimeout = new AtomicBoolean(false);
if (timeout > 0) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
logger.info("Timeout, stopping threads...");
globalContext.stopAllThreads();
wasTimeout.set(true);
}
};
timer.schedule(timerTask, timeout);
}
if (waitForTermination) {
waitForTerminaition();
timer.cancel();
} else {
logger.info("Design space exploration started asynchronously.");
}
return wasTimeout.get();
}
private void initExploration(IStrategy strategy) {
Preconditions.checkArgument(model != null, MODEL_NOT_YET_GIVEN);
Preconditions.checkArgument(strategy != null, "A strategy must be given. Use the Strategies helper class.");
Preconditions.checkState(!globalContext.getTransformations().isEmpty(),
"At least one transformation rule must be added to start the exploration.");
if (globalContext.getStateCoderFactory() == null) {
if (getMetaModelPackages() == null || getMetaModelPackages().isEmpty()) {
throw new DSEException("Cannot initialize state coder."
+ " Please specifiy the EPackages your model uses with addMetaModelPackage(EPackage)");
}
globalContext.setStateCoderFactory(new SimpleStateCoderFactory(getMetaModelPackages()));
}
logger.info("DesignSpaceExplorer started exploration.");
if (deepCopyModel) {
globalContext.startFirstThread(strategy, model);
} else {
globalContext.startFirstThreadWithoutModelClone(strategy, model);
}
}
/**
* Returns all of the found {@link Solution}s, trajectories. Call it after
* {@link DesignSpaceExplorer#startExploration()}. Calling this while the process is running returns the solutions
* that have been found so far. The returned {@link Solution} objects may change internal state after they
* have been returned, if a shorter trajectory has been found to the referred state.
*
* @return The found solutions.
*/
public Collection
*
*
* @author Andras Szabolcs Nagy
*
*/
public enum DseLoggingLevel {
OFF, WARN, BASIC, VERBOSE_STRATEGY, VERBOSE_FULL
}
/**
* Changes the level of logging. See {@link DseLoggingLevel} for details.
*
* @param dseLoggingLevel
*/
public static void turnOnLogging(DseLoggingLevel dseLoggingLevel) {
switch (dseLoggingLevel) {
case OFF:
Logger.getLogger(DesignSpaceExplorer.class).setLevel(Level.OFF);
Logger.getLogger(IStrategy.class).setLevel(Level.OFF);
Logger.getLogger(DesignSpaceManager.class).setLevel(Level.OFF);
break;
case WARN:
Logger.getLogger(DesignSpaceExplorer.class).setLevel(Level.WARN);
Logger.getLogger(IStrategy.class).setLevel(Level.WARN);
Logger.getLogger(DesignSpaceManager.class).setLevel(Level.WARN);
break;
case BASIC:
Logger.getLogger(DesignSpaceExplorer.class).setLevel(Level.INFO);
Logger.getLogger(IStrategy.class).setLevel(Level.INFO);
Logger.getLogger(DesignSpaceManager.class).setLevel(Level.WARN);
break;
case VERBOSE_STRATEGY:
Logger.getLogger(DesignSpaceExplorer.class).setLevel(Level.DEBUG);
Logger.getLogger(IStrategy.class).setLevel(Level.DEBUG);
Logger.getLogger(DesignSpaceManager.class).setLevel(Level.WARN);
break;
case VERBOSE_FULL:
Logger.getLogger(DesignSpaceExplorer.class).setLevel(Level.DEBUG);
Logger.getLogger(IStrategy.class).setLevel(Level.DEBUG);
Logger.getLogger(DesignSpaceManager.class).setLevel(Level.DEBUG);
break;
default:
throw new DSEException("Not supported logging level.");
}
}
/**
* Changes the level of logging. See {@link DseLoggingLevel} for details.
*
* Also configures a basic console appender for log4j.
*
* @param dseLoggingLevel
*/
public static void turnOnLoggingWithBasicConfig(DseLoggingLevel dseLoggingLevel) {
BasicConfigurator.configure();
Logger.getRootLogger().setLevel(Level.WARN);
turnOnLogging(dseLoggingLevel);
}
/**
* Stops the exploration and waits for termination. It has no effect if the exploration is already terminated or not
* even started.
*/
public void stopExploration() {
if (globalContext.isDone()) {
logger.info("Cannot stop exploration - design space exploration has already finished.");
} else if (globalContext.isNotStarted()) {
logger.info("Cannot stop exploration - design space exploration has not been started.");
} else {
globalContext.stopAllThreads();
waitForTerminaition();
}
}
/**
* Stops the exploration asynchronously. It has no effect if the exploration is already terminated or not even
* started.
*/
public void stopExplorationAsync() {
if (globalContext.isDone()) {
logger.info("Cannot stop exploration - design space exploration has already finished.");
} else if (globalContext.isNotStarted()) {
logger.info("Cannot stop exploration - design space exploration has not been started.");
} else {
globalContext.stopAllThreads();
}
}
/**
* Waits for termination.
*/
public void waitForTerminaition() {
globalContext.waitForTermination();
}
/**
* Serializes all the found solutions by transforming the given initial model.
*
solution[id].xmi
.
* @param model The initial model.
*/
public void saveModels(Notifier model) {
this.saveModels(model, "solution", "xmi");
}
/**
* Serializes all the found solutions by transforming the given initial model.
* Files will be named solution[id].[extension]
.
* @param model The initial model.
* @param extension The extension of the omitted file.
*/
public void saveModels(Notifier model, String extension) {
this.saveModels(model, "solution", extension);
}
/**
* Serializes all the found solutions by transforming the given initial model.
* Files will be named [fileNamePrefix][id].[extension]
.
* @param model The initial model.
* @param fileNamePrefix The prefix (optionally including a file path) of the omitted file.
* @param extension The extension of the omitted file.
*/
public void saveModels(Notifier model, String fileNamePrefix, String extension) {
globalContext.getSolutionStore().saveModels(model, new IdBasedSolutionNameProvider(fileNamePrefix, extension));
}
/**
* Serializes all the found solutions by transforming the given initial model.
* Files will be named using the {@link ISolutionNameProvider}.
* @param model The initial model.
*/
public void saveModels(Notifier model, ISolutionNameProvider solutionNameProvider) {
globalContext.getSolutionStore().saveModels(model, solutionNameProvider);
}
}