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

VIATRA/DSE/UserGuide/API

< VIATRA‎ | DSE
Revision as of 12:55, 26 July 2016 by Nasz013.gmail.com (Talk | contribs) (Starting the exploration)

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.base 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.

EObject root = createInitialModel();
dse.setInitialModel(root);


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). 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.

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:

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.


For version 0.12 and before:

DSETransformationRule<MyPatternMatch, MyPatternMatcher> rule = 
    new DSETransformationRule<MyPatternMatch, MyPatternMatcher>(
        MyPatternQuerySpecification.instance(), new MyPatternMatchProcessor() {
 
        @Override
        public void process(ExampleEObject p1, ExampleEObject p2) {
            // RHS manipulates the EMF model
            p1.setFriend(p2);
        }
    });
 
dse.addTransformationRule(rule);

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.

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.

ConstraintsObjective can be used both as a hard and as a soft objective. 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 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, 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.

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.

Methods for configuration:

  • acceptAnySolutions() - if an exploration strategy (e.g. the evolutionary strategy) tries to save an invalid solution, the solution store will 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.

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));


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.

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.setModel(theInitialModel);
    solutionTrajectory.doTransformation();
}
 
// To get an arbitrary solution trajectory with simpler way, use the next method:
SolutionTrajectory solutionTrajectory = dse.getArbitrarySolution();
 
// 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);

Run configuration

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.
  • In case of Mars or later Eclipse just run it as JUnit plug-in test.
  • 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

Back to the top