- 1 Usage of the VIATRA-DSE API
- 1.1 API
- 1.2 Defining the domain
- 1.3 Transformation rules
- 1.4 Objectives
- 1.5 Solution store
- 1.6 State coding
- 1.7 Starting the exploration
- 1.8 Using the results
- 1.9 Other functions of the API
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.
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:
Defining the domain
The first thing required is a metamodel or ecore model created with EMF and an initial model. See the code below.
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
ResourceSet is supported as well.
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:
Then the following Xtend code creates a rule, and then adds it to the configuration:
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:
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
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.
ConstraintsObjective uses VIATRA Query to specify both hard and as a soft constraints. Example for using it as a hard objective:
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:
The default comparator of built-in objectives is
A custom objective will be required for most problems. For that either implement the
IObjective interface or preferably the
BaseObjective abstract class.
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:
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.
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.
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:
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.
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:
org.eclipse.viatra.dse.genetic plug-in, fully customizable evolutionary algorithms are also available.
For configuration examples see the
Stop the exploration
If exploration is started asynchronously, the following methods can be used to stop the exploration and/or wait for termination.
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.
Other functions of the API
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.
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: