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)
 
(20 intermediate revisions by the same user not shown)
Line 7: Line 7:
 
== 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">
+
<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">
+
<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>
  
Line 28: Line 28:
 
== 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 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.  
+
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. Then the following Xtend code creates a rule, and then adds it to the configuration:
+
Let's say a pattern named myPattern with two parameters is already available and can be used as a LHS:
  
<source lang="java">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 +
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 factory = new BatchTransformationRuleFactory
 
val myRule = factory.createRule
 
val myRule = factory.createRule
Line 50: Line 58:
 
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.
 
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 ===
  
For version 0.12 and before:
+
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">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
DSETransformationRule<MyPatternMatch, MyPatternMatcher> rule =  
+
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.addTransformationRule(rule);
+
dse.addTransformationRule(myRule);
 
</source>
 
</source>
  
 
== Objectives ==
 
== 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. It is possible to use both types or only one of them.
+
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.
  
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 VIATRA Query patterns, hence the built in implementations are focused on them.
+
The <code>ConstraintsObjective</code> uses VIATRA Query to specify both hard and as a soft constraints. Example for using it as a hard objective:
  
<code>ConstraintsObjective</code> can be used both as a hard and as a soft objective. Example for using it as a hard objective:
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
+
dse.addObjective(Objectives.createConstraintsObjective("MyHardObjective")
<source lang="java">
+
dse.addObjective(new ConstraintsObjective("MyHardObjective")
+
 
     .withHardConstraint(Pattern1QuerySpecification.instance()
 
     .withHardConstraint(Pattern1QuerySpecification.instance()
 
     .withHardConstraint(Pattern2QuerySpecification.instance()));
 
     .withHardConstraint(Pattern2QuerySpecification.instance()));
 
</source>
 
</source>
  
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 will be 1 in case of a valid solution and 0 in case of an invalid.
+
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.
 +
 
 +
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.
 
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 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 <code>.withHardConstraintOnFitness(0.5)</code> method can be used. A custom Comparator implementation can also be defined.
+
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 are:
+
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>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>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>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.
  
 
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:
 
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">
+
<source lang="java" style="border-left:1px solid; padding:8px; border-color:#cccccc; margin-left:30px>
 
objective.withComparator(Comparators.LOWER_IS_BETTER);
 
objective.withComparator(Comparators.LOWER_IS_BETTER);
 
</source>
 
</source>
Line 102: Line 121:
  
 
The default comparator of built-in objectives is <code>Comparators.HIGHER_IS_BETTER</code>.
 
The default comparator of built-in objectives is <code>Comparators.HIGHER_IS_BETTER</code>.
 +
 +
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.
 +
 +
== 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:
 +
 +
<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>
 +
 +
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>
 +
 +
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 ==
Line 109: Line 152:
 
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">
+
<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 120: 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 [[VIATRA/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">
+
<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 131: 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">
+
<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 137: 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:
 
// To easily print the solutions, use the following code:
Line 154: 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">
+
<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 163: 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">
+
<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);
* In case of Mars or later Eclipse just run it as JUnit plug-in test.
+
</source>
 
+
* In case of erlier versions of Eclipse you may have to set the Headless mode in the run config:
+
** Create a JUnit plug-in test run configuration with the corresponding class
+
** 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