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.base 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). In the future
ResourceSet may be 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). 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.
Let's say a pattern named myPattern with two parameters is already available and can be used as a LHS.
Note 1: A rule can also have a name via a specific constructor.
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 IncQuery keyword find can avoid code duplication.
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.
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.
To use an objective one must implement the
IObjective 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.
Since VIATRA-DSE v0.12.
ConstraintsObjective can be used both as a hard and as a soft objective. 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 will be 1 in case of a valid solution and 0 in case of an invalid.
Soft constraints can be added with the
.withSoftConstraint(querySpecification, weigth)<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]). Hard and soft constraints can be used simoultanously with the same objective.
ModelQueriesHardObjective 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:
.withType(ModelQueryType.NO_MATCH) method will modify the behavior of the objective: it considers a solution valid if there are no matches of the given patterns.
Other built-in objective implementations are:
WeightedQueriesSoftObjective(deprecated) - 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:
fitness = sum(pattern[i].countMatches() * weight[i])
CompositHardObjective- 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.
CompositSoftObjective- can be configured with a set of soft objectives. Returns the sum of the calculated fitness by the the sub objectives.
TrajectoryCostSoftObjective- calculates fitness from the trajectory. Different weight for the transformation rules can be assigned.
NoRuleActivationsHardObjective- 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:
The default comparator of the built in objective is Comparators.HIGHER_IS_BETTER.
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 state coder is required in order to use the framework.
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. For more on these strategies see VIATRA2/DSE/UserGuide/Strategy.
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:
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:
- Add the org.junit as a dependency.
- Annotate the method with @Test annotation
- 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.
Bpmn example: VIATRA/DSE/UserGuide/BPMNExample