Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "VIATRA/DSE/UserGuide/API"

< VIATRA‎ | DSE
(Transformation rules)
(Objectives)
 
(37 intermediate revisions by the same user not shown)
Line 3: Line 3:
 
This page presents the basic API of the VIATRA-DSE framework. After reading this you will be able to define a DSE problem including transformation rules and objectives, run it with different built in strategies and use the solutions the exploration found.
 
This page presents the basic API of the VIATRA-DSE framework. After reading this you will be able to define a DSE problem including transformation rules and objectives, run it with different built in strategies and use the solutions the exploration found.
  
It is highly recommended to get familiar with the [[EMF|EMF]] and [[EMFIncQuery|EMF-IncQuery]] frameworks first as they are essential for using VIATRA-DSE.
+
It is highly recommended to get familiar with the [[EMF|EMF]] and [[VIATRA/Query|VIATRA Query]] frameworks first as they are essential for using VIATRA-DSE.
  
 
== API ==
 
== API ==
  
VIATRA-DSE depends on the Eclipse platform thus using it one should create a plug-in project and add the '''org.eclipse.viatra.dse.base''' plug-in as a dependency for the plug-in project.
+
VIATRA-DSE depends on the Eclipse platform thus using it one should create a plug-in project and add the '''org.eclipse.viatra.dse''' plug-in as a dependency for the plug-in project.
 
To start using the framework type this line of code:
 
To start using the framework type this line of code:
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
DesignSpaceExplorer dse = new DesignSpaceExplorer();
 
DesignSpaceExplorer dse = new DesignSpaceExplorer();
 
</source>
 
</source>
Line 18: Line 18:
 
The first thing required is a metamodel or ecore model created with EMF and an initial model. See the code below.
 
The first thing required is a metamodel or ecore model created with EMF and an initial model. See the code below.
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
EObject root = createInitialModel();
+
Notifier theInitialModel = createInitialModel();
dse.setInitialModel(root);
+
dse.setInitialModel(theInitialModel);
 
</source>
 
</source>
  
  
The initial model must be set as the root <code>EObject</code> of the model (i.e.: it contains all other objects of the model via the containment hierarchy). In the future <code>Resource</code> and <code>ResourceSet</code> may be supported as well.
+
The initial model must be set as the root <code>EObject</code> of the model (i.e.: it contains all other objects of the model via the containment hierarchy). From v0.13M1 <code>Resource</code> and <code>ResourceSet</code> is supported as well.
  
 
== Transformation rules ==
 
== Transformation rules ==
  
Rules tell the engine how the initial model can be modified and treated as atomic steps. They consist of a left hand side (LHS or condition) and a right hand side (RHS). The LHS is always an IncQuery pattern (i.e. a model query) while the RHS is simple Java code. To define such a rule an instance of the DSETransformationRule class must be created.
+
Rules tell the engine how the initial model can be modified and treated as atomic steps. They consist of a left hand side (LHS or condition) and a right hand side (RHS or operation). The LHS is always a Viatra Query pattern (i.e. a model query) while the RHS is simple Java code. To define such a rule an instance of the <code>BatchTransformationRule</code> class must be created. While it is also possible to do this in the Java language, it is strongly recommended to use [https://www.eclipse.org/xtend/ Xtend] (which essentially compiles to Java code).  
  
Let's say a pattern named myPattern with two parameters is already available and can be used as a LHS.
+
Let's say a pattern named myPattern with two parameters is already available and can be used as a LHS:
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
DSETransformationRule<MyPatternMatch, MyPatternMatcher> rule =  
+
pattern myPattern(param1 : Type1, param2 : Type2) {
 +
    // Constraints on param1 and param2
 +
}
 +
</source>
 +
 
 +
Then the following Xtend code creates a rule, and then adds it to the configuration:
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
val factory = new BatchTransformationRuleFactory
 +
val myRule = factory.createRule
 +
            .name("MyRule")
 +
            .precondition(MyPatternQuerySpecification.instance)
 +
            .action[
 +
                // RHS manipulates the EMF model, e.g.:
 +
                param1.target = param2
 +
            ]
 +
            .build
 +
 
 +
dse.addTransformationRule(myRule);
 +
</source>
 +
 
 +
Note 1:  It's a good practice to use a separate "RuleProvider" class and create the rules in the constructor, for easier readability of the DSE configuration. [http://git.eclipse.org/c/viatra/org.eclipse.viatra.examples.git/tree/dse/bpmn/org.eclipse.viatra.dse.examples.bpmn.dse/src/org/eclipse/viatra/dse/examples/bpmn/rules/BpmnRuleProvider.xtend See the rules of the BPMN example.]
 +
 
 +
Note 2: It's forbidden to apply two rules with the same LHS to be able to distinguish them based on name of the pattern. If two rules would use the same LHS, creating a new pattern and using the Viatra Query language keyword ''find'' can avoid code duplication.
 +
 
 +
=== Id generation for new objects ===
 +
 
 +
It may be important that an identifier is generated for every newly created object, either semantically (it is need for the problem at hand) or it is important for the state coder in order to distinguish different model states. There is a built-in helper for generating identifiers:
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
int id = DseIdPoolHelper.INSTANCE.getId(myRule);
 +
</source>
 +
 
 +
The generated id is equivalent to how many times the rule was executed in a given trajectory (sequence of transformation rules), hence after backtracking and then re-executing the rule, it will return the same id.
 +
 
 +
=== For version 0.12 and before ===
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
DSETransformationRule<MyPatternMatch, MyPatternMatcher> myRule =  
 
     new DSETransformationRule<MyPatternMatch, MyPatternMatcher>(
 
     new DSETransformationRule<MyPatternMatch, MyPatternMatcher>(
 
         MyPatternQuerySpecification.instance(), new MyPatternMatchProcessor() {
 
         MyPatternQuerySpecification.instance(), new MyPatternMatchProcessor() {
 
          
 
          
 
         @Override
 
         @Override
         public void process(ExampleEObject p1, ExampleEObject p2) {
+
         public void process(Type param1, Type2 param2) {
 
             // RHS manipulates the EMF model
 
             // RHS manipulates the EMF model
             p1.setFriend(p2);
+
             param1.setTarget(param2);
 
         }
 
         }
 
     });
 
     });
  
dse.addTransformation(rule);
+
dse.addTransformationRule(myRule);
 
</source>
 
</source>
  
 +
== Objectives ==
  
Note 1: A rule can also have a name via the "setName(String)" method.
+
Objectives determine the quality of a solution represented in double values. The goal of a DSE problem can be a single objective with a binary check (valid or invalid model) or it can consist of multiple numerical objectives giving a multi-objective optimization problem. There are two types of objective: hard and soft. Hard objectives are used to decide whether the solution is valid and can be considered as a usable solution, while soft objectives determine the quality and enables the ordering of the solutions. Soft and hard objectives can be use in any combination.
  
Note 2: It's forbidden to apply two rules with the same LHS to be able to distinguish them based on the pattern. If two rules would use the same LHS, creating a new pattern and using the IncQuery keyword ''find'' can avoid code duplication.
+
The <code>ConstraintsObjective</code> uses VIATRA Query to specify both hard and as a soft constraints. Example for using it as a hard objective:
  
Note 3: It's a good practice to use static factory methods in a separate class for creating rules, as the above code can worsen the readability.
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
dse.addObjective(Objectives.createConstraintsObjective("MyHardObjective")
 +
    .withHardConstraint(Pattern1QuerySpecification.instance()
 +
    .withHardConstraint(Pattern2QuerySpecification.instance()));
 +
</source>
  
== Objectives ==
+
For each hard constraint a name and type can also be specified. The default type is <code>ModelQueryType.MUST_HAVE_MATCH</code> and it means that the given pattern must have a match in the actual model. The other type is <code>ModelQueryType.NO_MATCH</code>, which means that the given pattern shouldn't have a match. The fitness value is unaffected whether the constraints are satisfied or not.
  
Objectives determine the quality of a solution represented in double values. The goal can be a single objective with a binary check (valid or invalid model) or it can consist of multiple numerical objectives giving a multi-objective optimization problem. There are two types of objective: hard and soft. Hard objectives are used to decide whether the solution is valid and can be considered as a usable solution, while soft objectives determine the quality and enables the ordering of the solutions. It is possible to use both types or only one of them.
+
Note that if no hard constraint is given, the framework will consider the hard constraints unsatisfied. Use the <code>DummyHardObjective</code> (see below) to consider all model states valid.
 +
 
 +
Soft constraints can be added with the <code>.withSoftConstraint(querySpecification, weight)</code> method (a name can be also specified). In this case the fitness value will be determined in the following way: <code>fitness = sum(pattern[i].countMatches() * weight[i])</code>. Hard and soft constraints can be used simultaneously with the same objective.
 +
 
 +
Any objective that derives from the <code>BaseObjective</code> (e.g. the <code>ConstraintsObjective</code>) can define a hard constraint on the fitness value, i.e. a solution will be valid if its fitness value is better than the given value. For this, the <code>.withHardConstraintOnFitness(0.5)</code> method can be used. A custom Comparator implementation can also be defined.
 +
 
 +
 
 +
Other built-in objective implementations (which can be instantiated with the static methods on <code>Objectives</code>) are :
 +
* <code>CompositeObjective</code> - can be configured with a list of objectives. It considers a solution valid, if all the contained hard objectives assess it as a valid solution. The fitness value will be the sum of each objective functions' result. Weights can be defined for each objective.
 +
* <code>TrajectoryCostSoftObjective</code> - calculates fitness from the trajectory. Fitness can be derived from fixed rules costs, rule activation costs or length of the trajectory.
 +
* <code>NoRuleActivationsHardObjective</code> - this hard objective is satisfied only if there are no more rule activations from the current state.
 +
* <code>MinimalDepthHardObjective</code> - this hard objective is satisfied only if the particular trajectory is longer than a predefined length.
 +
* <code>DummyHardObjective</code> - this hard objective considers all trajectories as a valid solution.
  
To use an objective one must implement the <code>IObjective</code> interface, the BaseObjective abstract class or use one of the built in implementations. Objectives are mainly defined by IncQuery patterns, hence the built in implementations are focused on them. <code>ModelQueriesHardObjective</code> is a hard objective implementation, which can be configured with IncQuery patterns. It checks if all of the given patterns have a match in the actual model, and considers it a valid solution only if they have (returning a fitness value 1 and returning 0 if it is invalid). See the following example:
+
Objectives also have a comparator, which decides the relation between two solutions (which one is better) corresponding to the particular objective. For example, an objective could be a value to maximize, or a value to minimize the difference from a given number. This is dependent of the objective's comparator, which can be set in the following way:
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
dse.addObjective(new ModelQueriesHardObjective("MyHardObjective")
+
objective.withComparator(Comparators.LOWER_IS_BETTER);
    .withConstraint(Pattern1QuerySpecification.instance()
+
    .withConstraint(Pattern2QuerySpecification.instance()));
+
 
</source>
 
</source>
  
  
Calling the <code>.withType(ModelQueryType.NO_MATCH)</code> method will modify the behavior of the objective: it considers a solution valid if there are no matches of the given patterns.
+
The default comparator of built-in objectives is <code>Comparators.HIGHER_IS_BETTER</code>.
  
Other built-in objective implementations are:
+
A custom objective will be required for most problems. For that either implement the <code>IObjective</code> interface or preferably the <code>BaseObjective</code> abstract class.
* <code>WeightedQueriesSoftObjective</code> - is a soft objective, which can be configured with a list of patterns and weights assigned to them. It calculates a fitness value in the following way: <code>fitness = sum(pattern[i].countMatches() * weight[i])</code>
+
* <code>CompositHardObjective</code> - can be configured with a set of hard objectives. It considers a solution valid, if all the hard objectives assess it as a valid solution.
+
* <code>CompositSoftObjective</code> - can be configured with a set of soft objectives. Returns the sum of the calculated fitness by the the sub objectives.
+
* <code>TrajectoryCostSoftObjective</code> - calculates fitness from the trajectory. Different weight for the transformation rules can be assigned.
+
* <code>NoRuleActivationsHardObjective</code> - this hard objective is satisfied only if there are no more rule activations from the current state.
+
  
Objectives also have a comparator, which decides the relation between two solutions (which one is better) corresponding to the particular objective. For example, an objective could be a value to maximize, or a value to minimize a difference from a given number. This is dependent of the objective's comparator, which can be set in the following way:
+
== Solution store ==
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
Solutions (that satisfy the hard objectives) can be handled in different way. The default configuration is that it stores all of them and that it will try to stop the exploration after the first solution is found.
objective.setComparator(Comparators.LOWER_IS_BETTER);
+
 
 +
The solution store can be configured by creating a new one:
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
dse.setSolutionStore(new SolutionStore(1)); // the default configuration
 
</source>
 
</source>
  
 +
The number in the constructor means that it will try to stop the exploration after that many solution found. Zero or negative number means no such stop conditions.
 +
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
dse.setSolutionStore(new SolutionStore(0)); // Find all solutions
 +
</source>
  
The default comparator of the built in objective is Comparators.HIGHER_IS_BETTER.
+
Methods for configuration:
 +
* <code>acceptAnySolutions()</code> - if an exploration strategy (e.g. the evolutionary strategy) tries to save an invalid solution, the solution store will still accept it.
 +
* <code>storeBestSolutionsOnly()</code> - if a new solution is found during exploration, all dominated solutions will be removed from the solution store.
 +
* <code>logSolutionsWhenFound()</code> - logs any solution immediately if one is found.
 +
* <code>registerSolutionFoundHandler(ISolutionFoundHandler)</code> - solutions can be processed on the fly with a callback method. The context of the exploration is available, e.g. the exploration can be stopped.
  
 
== State coding ==
 
== State coding ==
  
A state coder is also necessary for the engine to start the exploration. By default there is a built-in general state coder which will be enough in most of the times but it can fail for certain problems and a custom state coder is required in order to use the framework.
+
A state coder is also necessary for the engine to start the exploration. By default there is a built-in general state coder which will be enough in most of the times but it can fail for certain problems and in that case a custom domain-specific state coder is required in order to use the framework. Domain-specific state coders usually also outperforms the general purpose state coder.
  
 
To use the built-in state coder, the metamodel must be given with the following code:
 
To use the built-in state coder, the metamodel must be given with the following code:
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
dse.addMetaModelPackage(MetaModelEPackage.eINSTANCE);
 
dse.addMetaModelPackage(MetaModelEPackage.eINSTANCE);
  
Line 102: Line 163:
 
== Starting the exploration ==
 
== Starting the exploration ==
  
To start the exploration, a strategy must also be defined. Static methods of the ''Strategies'' class can be used to create a built-in strategy such as depth first search. For more on these strategies see [[VIATRA2/DSE/UserGuide/Strategy]].
+
To start the exploration, a strategy must also be defined. Static methods of the ''Strategies'' class can be used to create a built-in strategy such as depth-first search, breadth-first search, fixed priority and hill climbing exploration strategies.
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
// Starting the exploration process with a depth first search limited to 5 step depth.
 
// Starting the exploration process with a depth first search limited to 5 step depth.
dse.startExploration(Strategies.createDFSStrategy(5));
+
dse.startExploration(Strategies.createDfsStrategy(5));
 +
 
 +
// The exploration can also be started with a timeout or/and asynchronously, e.g.:
 +
dse.startExplorationWithTimeout(Strategies.createDfsStrategy(5), 30000); // milisec
 +
dse.startExplorationAsync(Strategies.createDfsStrategy(5));
 +
</source>
 +
 
 +
=== Depth-first search ===
 +
 
 +
By default it will not explore the state space further if a valid solution is found (i.e., satisfies the hard objectives). It can be configured to explore further:
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
dse.startExploration(Strategies.createDfsStrategy(5).continueIfHardObjectivesFulfilled());
 +
</source>
 +
 
 +
=== Evolutionary algorithms ===
 +
 
 +
With the <code>org.eclipse.viatra.dse.genetic</code> plug-in, fully customizable evolutionary algorithms are also available.
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
// Starting the exploration process with an NSGA-II exploration strategy with population size of 20.
 +
dse.startExploration(EvolutionaryStrategyBuilder.createNsga2Strategy(20));
 +
</source>
 +
 
 +
For configuration examples see the <code>EvolutionaryStrategyBuilder</code> class.
 +
 
 +
=== Stop the exploration ===
 +
 
 +
If exploration is started asynchronously, the following methods can be used to stop the exploration and/or wait for termination.
 +
 
 +
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
dse.stopExploration();
 +
dse.stopExplorationAsync();
 +
dse.waitForTerminaition();
 
</source>
 
</source>
  
Line 113: Line 207:
 
A solution of the exploration is a trajectory, a sequence of rules which if applied to the initial model, it satisfies the goals. It's important that the same goal state can be reached by different trajectories and this is also represented by the results: an instance of ''Solution'' class can have multiple ''SolutionTrajectory'' instances. A solution has at least one solution trajectory, but nothing more can be expected, as it is heavily depend on the traversal strategy and the actual traversal of the state space. The ''SolutionTrajectory'' can be used to transform the given model (should be the initial model) based on the trajectory.
 
A solution of the exploration is a trajectory, a sequence of rules which if applied to the initial model, it satisfies the goals. It's important that the same goal state can be reached by different trajectories and this is also represented by the results: an instance of ''Solution'' class can have multiple ''SolutionTrajectory'' instances. A solution has at least one solution trajectory, but nothing more can be expected, as it is heavily depend on the traversal strategy and the actual traversal of the state space. The ''SolutionTrajectory'' can be used to transform the given model (should be the initial model) based on the trajectory.
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
Collection<Solution> solutions = dse.getSolutions();
 
Collection<Solution> solutions = dse.getSolutions();
 
if (!solutions.isEmpty()) {
 
if (!solutions.isEmpty()) {
Line 119: Line 213:
 
     SolutionTrajectory solutionTrajectory = solution.getArbitraryTrajectory();
 
     SolutionTrajectory solutionTrajectory = solution.getArbitraryTrajectory();
 
     // Transform the model
 
     // Transform the model
     solutionTrajectory.setModel(theInitialModel);
+
     solutionTrajectory.doTransformation(theInitialModel);
    solutionTrajectory.doTransformation();
+
 
}
 
}
  
 
// To get an arbitrary solution trajectory with simpler way, use the next method:
 
// To get an arbitrary solution trajectory with simpler way, use the next method:
 
SolutionTrajectory solutionTrajectory = dse.getArbitrarySolution();
 
SolutionTrajectory solutionTrajectory = dse.getArbitrarySolution();
 +
 +
// To undo a transformation (e.g. for serializing all the solutions)
 +
solutionTrajectory.doTransformationUndoable(theInitialModel);
 +
// ... serialize "theInitialModel"
 +
solutionTrajectory.undoTransformation();
 +
 +
// To easily print the solutions, use the following code:
 +
System.out.println(dse.toStringSolutions());
 
</source>
 
</source>
  
Line 133: Line 234:
 
If there are any global constraints added they must be satisfied in all of the intermediate model state of trajectory. If a global constraint is not satisfied the engine won't allow the exploration to go further from that state. It can be useful to reduce the state space, but it can also be a criteria for a usable solution.
 
If there are any global constraints added they must be satisfied in all of the intermediate model state of trajectory. If a global constraint is not satisfied the engine won't allow the exploration to go further from that state. It can be useful to reduce the state space, but it can also be a criteria for a usable solution.
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
dse.addGlobalConstraint(new ModelQueriesGlobalConstraint()
 
dse.addGlobalConstraint(new ModelQueriesGlobalConstraint()
 
     .withConstraint(MyGlobalConstraintQuerySpecifiaction.instance());
 
     .withConstraint(MyGlobalConstraintQuerySpecifiaction.instance());
Line 142: Line 243:
 
The engine allows the traversal strategy to explore the state space with multiple threads. By default it allows a number of threads equal to the number of logical cores in the processor. It can be overridden with the following code:
 
The engine allows the traversal strategy to explore the state space with multiple threads. By default it allows a number of threads equal to the number of logical cores in the processor. It can be overridden with the following code:
  
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
dse.setMaxNumberOfThreads(1);
 
dse.setMaxNumberOfThreads(1);
 
</source>
 
</source>
  
= Run configuration =
+
=== Logging, debugging ===
  
Running the DSE engine needs an Eclipse platform, thus it is recommended to use a '''JUnit plug-in test''' run configuration in headless mode. Here are some basic steps to configure it properly:
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
+
DesignSpaceExplorer.turnOnLogging(DseLoggingLevel.BASIC);
* Add the org.junit as a dependency.
+
// If log4j is not configured properly, just call
* Annotate the method with @Test annotation
+
DesignSpaceExplorer.turnOnLoggingWithBasicConfig(DseLoggingLevel.BASIC);
* Create a JUnit plug-in test run configuration with the corresponding class
+
</source>
* Locate the Run configuration / Main tab and change the selection at "Run an application" to "[No Application] – Headless Mode".
+
* On the Plug-ins tab select "plug-ins selected below only" from the drop down list and select the plugin which contains the class with the annotated method.
+
* Hit "Add Required Plug-ins" 2-3 times.
+
 
+
= Examples =
+
 
+
Bpmn example: [[VIATRA/DSE/UserGuide/BPMNExample]]
+

Latest revision as of 12:28, 28 November 2016

Usage of the VIATRA-DSE API

This page presents the basic API of the VIATRA-DSE framework. After reading this you will be able to define a DSE problem including transformation rules and objectives, run it with different built in strategies and use the solutions the exploration found.

It is highly recommended to get familiar with the EMF and VIATRA Query frameworks first as they are essential for using VIATRA-DSE.

API

VIATRA-DSE depends on the Eclipse platform thus using it one should create a plug-in project and add the org.eclipse.viatra.dse plug-in as a dependency for the plug-in project. To start using the framework type this line of code:

DesignSpaceExplorer dse = new DesignSpaceExplorer();

Defining the domain

The first thing required is a metamodel or ecore model created with EMF and an initial model. See the code below.

Notifier theInitialModel = createInitialModel();
dse.setInitialModel(theInitialModel);


The initial model must be set as the root EObject of the model (i.e.: it contains all other objects of the model via the containment hierarchy). From v0.13M1 Resource and ResourceSet is supported as well.

Transformation rules

Rules tell the engine how the initial model can be modified and treated as atomic steps. They consist of a left hand side (LHS or condition) and a right hand side (RHS or operation). The LHS is always a Viatra Query pattern (i.e. a model query) while the RHS is simple Java code. To define such a rule an instance of the BatchTransformationRule class must be created. While it is also possible to do this in the Java language, it is strongly recommended to use Xtend (which essentially compiles to Java code).

Let's say a pattern named myPattern with two parameters is already available and can be used as a LHS:

pattern myPattern(param1 : Type1, param2 : Type2) {
    // Constraints on param1 and param2
}

Then the following Xtend code creates a rule, and then adds it to the configuration:

val factory = new BatchTransformationRuleFactory
val myRule = factory.createRule
            .name("MyRule")
            .precondition(MyPatternQuerySpecification.instance)
            .action[
                // RHS manipulates the EMF model, e.g.:
                param1.target = param2
            ]
            .build
 
dse.addTransformationRule(myRule);

Note 1: It's a good practice to use a separate "RuleProvider" class and create the rules in the constructor, for easier readability of the DSE configuration. See the rules of the BPMN example.

Note 2: It's forbidden to apply two rules with the same LHS to be able to distinguish them based on name of the pattern. If two rules would use the same LHS, creating a new pattern and using the Viatra Query language keyword find can avoid code duplication.

Id generation for new objects

It may be important that an identifier is generated for every newly created object, either semantically (it is need for the problem at hand) or it is important for the state coder in order to distinguish different model states. There is a built-in helper for generating identifiers:

int id = DseIdPoolHelper.INSTANCE.getId(myRule);

The generated id is equivalent to how many times the rule was executed in a given trajectory (sequence of transformation rules), hence after backtracking and then re-executing the rule, it will return the same id.

For version 0.12 and before

DSETransformationRule<MyPatternMatch, MyPatternMatcher> myRule = 
    new DSETransformationRule<MyPatternMatch, MyPatternMatcher>(
        MyPatternQuerySpecification.instance(), new MyPatternMatchProcessor() {
 
        @Override
        public void process(Type param1, Type2 param2) {
            // RHS manipulates the EMF model
            param1.setTarget(param2);
        }
    });
 
dse.addTransformationRule(myRule);

Objectives

Objectives determine the quality of a solution represented in double values. The goal of a DSE problem can be a single objective with a binary check (valid or invalid model) or it can consist of multiple numerical objectives giving a multi-objective optimization problem. There are two types of objective: hard and soft. Hard objectives are used to decide whether the solution is valid and can be considered as a usable solution, while soft objectives determine the quality and enables the ordering of the solutions. Soft and hard objectives can be use in any combination.

The ConstraintsObjective uses VIATRA Query to specify both hard and as a soft constraints. Example for using it as a hard objective:

dse.addObjective(Objectives.createConstraintsObjective("MyHardObjective")
    .withHardConstraint(Pattern1QuerySpecification.instance()
    .withHardConstraint(Pattern2QuerySpecification.instance()));

For each hard constraint a name and type can also be specified. The default type is ModelQueryType.MUST_HAVE_MATCH and it means that the given pattern must have a match in the actual model. The other type is ModelQueryType.NO_MATCH, which means that the given pattern shouldn't have a match. The fitness value is unaffected whether the constraints are satisfied or not.

Note that if no hard constraint is given, the framework will consider the hard constraints unsatisfied. Use the DummyHardObjective (see below) to consider all model states valid.

Soft constraints can be added with the .withSoftConstraint(querySpecification, weight) method (a name can be also specified). In this case the fitness value will be determined in the following way: fitness = sum(pattern[i].countMatches() * weight[i]). Hard and soft constraints can be used simultaneously with the same objective.

Any objective that derives from the BaseObjective (e.g. the ConstraintsObjective) can define a hard constraint on the fitness value, i.e. a solution will be valid if its fitness value is better than the given value. For this, the .withHardConstraintOnFitness(0.5) method can be used. A custom Comparator implementation can also be defined.


Other built-in objective implementations (which can be instantiated with the static methods on Objectives) are :

  • CompositeObjective - can be configured with a list of objectives. It considers a solution valid, if all the contained hard objectives assess it as a valid solution. The fitness value will be the sum of each objective functions' result. Weights can be defined for each objective.
  • TrajectoryCostSoftObjective - calculates fitness from the trajectory. Fitness can be derived from fixed rules costs, rule activation costs or length of the trajectory.
  • NoRuleActivationsHardObjective - this hard objective is satisfied only if there are no more rule activations from the current state.
  • MinimalDepthHardObjective - this hard objective is satisfied only if the particular trajectory is longer than a predefined length.
  • DummyHardObjective - this hard objective considers all trajectories as a valid solution.

Objectives also have a comparator, which decides the relation between two solutions (which one is better) corresponding to the particular objective. For example, an objective could be a value to maximize, or a value to minimize the difference from a given number. This is dependent of the objective's comparator, which can be set in the following way:

objective.withComparator(Comparators.LOWER_IS_BETTER);


The default comparator of built-in objectives is Comparators.HIGHER_IS_BETTER.

A custom objective will be required for most problems. For that either implement the IObjective interface or preferably the BaseObjective abstract class.

Solution store

Solutions (that satisfy the hard objectives) can be handled in different way. The default configuration is that it stores all of them and that it will try to stop the exploration after the first solution is found.

The solution store can be configured by creating a new one:

dse.setSolutionStore(new SolutionStore(1)); // the default configuration

The number in the constructor means that it will try to stop the exploration after that many solution found. Zero or negative number means no such stop conditions.

dse.setSolutionStore(new SolutionStore(0)); // Find all solutions

Methods for configuration:

  • acceptAnySolutions() - if an exploration strategy (e.g. the evolutionary strategy) tries to save an invalid solution, the solution store will still accept it.
  • storeBestSolutionsOnly() - if a new solution is found during exploration, all dominated solutions will be removed from the solution store.
  • logSolutionsWhenFound() - logs any solution immediately if one is found.
  • registerSolutionFoundHandler(ISolutionFoundHandler) - solutions can be processed on the fly with a callback method. The context of the exploration is available, e.g. the exploration can be stopped.

State coding

A state coder is also necessary for the engine to start the exploration. By default there is a built-in general state coder which will be enough in most of the times but it can fail for certain problems and in that case a custom domain-specific state coder is required in order to use the framework. Domain-specific state coders usually also outperforms the general purpose state coder.

To use the built-in state coder, the metamodel must be given with the following code:

dse.addMetaModelPackage(MetaModelEPackage.eINSTANCE);
 
// The framework will implicitly instantiate the built-in state coder if it is not set. 
dse.setStateCoderFactory(new SimpleStateCoderFactory(dse.getMetaModelPacakges()));

For any more detail about state coders please see the state coding section of this wiki.

Starting the exploration

To start the exploration, a strategy must also be defined. Static methods of the Strategies class can be used to create a built-in strategy such as depth-first search, breadth-first search, fixed priority and hill climbing exploration strategies.

// Starting the exploration process with a depth first search limited to 5 step depth.
dse.startExploration(Strategies.createDfsStrategy(5));
 
// The exploration can also be started with a timeout or/and asynchronously, e.g.:
dse.startExplorationWithTimeout(Strategies.createDfsStrategy(5), 30000); // milisec
dse.startExplorationAsync(Strategies.createDfsStrategy(5));

Depth-first search

By default it will not explore the state space further if a valid solution is found (i.e., satisfies the hard objectives). It can be configured to explore further:

dse.startExploration(Strategies.createDfsStrategy(5).continueIfHardObjectivesFulfilled());

Evolutionary algorithms

With the org.eclipse.viatra.dse.genetic plug-in, fully customizable evolutionary algorithms are also available.

// Starting the exploration process with an NSGA-II exploration strategy with population size of 20.
dse.startExploration(EvolutionaryStrategyBuilder.createNsga2Strategy(20));

For configuration examples see the EvolutionaryStrategyBuilder class.

Stop the exploration

If exploration is started asynchronously, the following methods can be used to stop the exploration and/or wait for termination.

dse.stopExploration();
dse.stopExplorationAsync();
dse.waitForTerminaition();

Using the results

A solution of the exploration is a trajectory, a sequence of rules which if applied to the initial model, it satisfies the goals. It's important that the same goal state can be reached by different trajectories and this is also represented by the results: an instance of Solution class can have multiple SolutionTrajectory instances. A solution has at least one solution trajectory, but nothing more can be expected, as it is heavily depend on the traversal strategy and the actual traversal of the state space. The SolutionTrajectory can be used to transform the given model (should be the initial model) based on the trajectory.

Collection<Solution> solutions = dse.getSolutions();
if (!solutions.isEmpty()) {
    Solution solution = dse.getAllSolutions().iterator().next();
    SolutionTrajectory solutionTrajectory = solution.getArbitraryTrajectory();
    // Transform the model
    solutionTrajectory.doTransformation(theInitialModel);
}
 
// To get an arbitrary solution trajectory with simpler way, use the next method:
SolutionTrajectory solutionTrajectory = dse.getArbitrarySolution();
 
// To undo a transformation (e.g. for serializing all the solutions)
solutionTrajectory.doTransformationUndoable(theInitialModel);
// ... serialize "theInitialModel"
solutionTrajectory.undoTransformation();
 
// To easily print the solutions, use the following code:
System.out.println(dse.toStringSolutions());

Other functions of the API

Global constraints

If there are any global constraints added they must be satisfied in all of the intermediate model state of trajectory. If a global constraint is not satisfied the engine won't allow the exploration to go further from that state. It can be useful to reduce the state space, but it can also be a criteria for a usable solution.

dse.addGlobalConstraint(new ModelQueriesGlobalConstraint()
    .withConstraint(MyGlobalConstraintQuerySpecifiaction.instance());

Parallel execution

The engine allows the traversal strategy to explore the state space with multiple threads. By default it allows a number of threads equal to the number of logical cores in the processor. It can be overridden with the following code:

dse.setMaxNumberOfThreads(1);

Logging, debugging

DesignSpaceExplorer.turnOnLogging(DseLoggingLevel.BASIC);
// If log4j is not configured properly, just call
DesignSpaceExplorer.turnOnLoggingWithBasicConfig(DseLoggingLevel.BASIC);

Back to the top