Operations
==========

While model transformations are in many tools considered to be the only types of operations on models, this is not necessarily the case in the Modelverse.
Indeed, MPM advocates to use the most appropriate formalism, and, as model transformations are models themselves, this should also apply to specifying operations.
As such, we extend the notion of model transformations to more general model operations.

Model operations come in three types, depending on how they are ideally specified: model transformations, manual operations, and action language.
In the remainder of this section, we will show all types, and what situation they are ideally suited for.

Model Transformations
---------------------

When model matching and rewriting is involved, model transformations are clearly the ideal solution in most cases.
The LHS is used to match elements in the host model, and the RHS is put in its place.
As this could also be combined with a visual concrete syntax, it could even become possible for domain experts to create a model transformation without too much programming knowledge.

Nonetheless, model transformations are a completely different paradigm, based solely on matching and rewriting.
Some operations have a long history in computer science, and have therefore efficient or simple procedural algorithms.
For example, computing the reachability graph of a PetriNets instance can certainly be done with model transformations, but will be overly difficult.
On the other hand, procedural algorithms are relatively easy (when implemented naively).

The functions on model transformations are exposed using the *transformation_add_MT* and *transformation_execute_MT* operations.
Examples as shown in the previous section::

    >>> def callback():
    ...     instantiate(None, "Association", edge=("PetriNets/Transition", "ReachabilityGraph/Transition"), ID="PN2RG_Transition")
    >>> transformation_add_MT({"PetriNets": "formalisms/PetriNets"}, {"ReachabilityGraph": "formalisms/ReachabilityGraph"}, "models/pn_analyse", open("models/pn_analyse.mvc", "r").read(), callback)

A situation in which model transformations are effective, is when the transformation is to map a Domain Specific Language (DSL) to a General Purpose Language (GPL).
For example, when defining a mapping between a RPGame DSL and the PetriNets formalism, to facilitate formal analysis::

    >>> def callback():
    ...     instantiate(None, "Association", edge=("RPGame/Tile", "PetriNets/Place"), ID="Tracability_link")
    >>> rule = \
    ...     """
    ...     Composite composite {
    ...         {Contains} ForAll create_places_for_tiles {
    ...             LHS {
    ...                 Pre_RPGame/Tile pre_t1 {
    ...                     label = "tile"
    ...                 }
    ...             }
    ...             RHS {
    ...                 Post_RPGame/Tile post_t1 {
    ...                     label = "tile"
    ...                 }
    ...                 Post_PetriNets/Place post_p1 {
    ...                     label = "place"
    ...                 }
    ...                 Post_Tracability_link (post_t1, post_p1) {
    ...                     label = "tracability"
    ...                 }
    ...             }
    ...         }
    ...         ...
    ...     }
    ...     Initial (composite, create_places_for_tiles) {}
    ...     OnSuccess (create_places_for_tiles, ...) {}
    ...     OnFailure (create_places_for_tiles, ...) {}
    ...     ...
    ...     """
    >>> transformation_add_MT({"RPGame": "formalisms/RPGame"}, {"PetriNets": "formalisms/PetriNets"}, "models/rpg_to_pn", rule, callback)
    >>> transformation_execute_MT("models/rpg_to_pn", {"RPGame": "models/my_rpg"}, {"PetriNets": "models/exported_rpg"})

Action Language
---------------

Doing operations in a procedural way is supported in the Modelverse through the use of action language models.
As action language is explicitly modelled, we can create models consisting solely of action language.
They are therefore similar to model transformations, which are also models, but only define a different operational semantics.
Instead of having a model transformation interpreter apply the schedule and rules, we merely call the action language functions enclosed in the model.

The functions on action language are exposed using the *transformation_add_AL* and *transformation_execute_AL* operations.
Their signature is identical to that of model transformations.

The action language model itself requires a single *main* function, or it will execute the topmost function in the specified code.
This function should take a single argument, being the merged model.
From this model, the merged metamodel can easily be accessed.
As with model transformations, the names of model entities are prefixed with their tags.

For example, when defining the PetriNets analysis in action language, which is a much better match than model transformations::

    >>> def callback():
    ...     instantiate(None, "Association", edge=("PetriNets/Transition", "ReachabilityGraph/Transition"), ID="PN2RG_Transition")
    >>> code = \
    ...     """
    ...     include "primitives.alh"
    ...     Element function explore_state(model : Element, state : Element):
    ...         ...
    ...     
    ...     Element function main(model : Element):
    ...         String initial
    ...         initial = instantiate_node(model, "ReachabilityGraph/Initial")
    ...         instantiate_attribute(model, initial, "name", "state_0")
    ...         ...
    ...         return model!
    ...     """
    >>> transformation_add_AL({"PetriNets": "formalisms/PetriNets"}, {"ReachabilityGraph": "formalisms/ReachabilityGraph"}, "models/pn_analyse", code, callback)
    >>> transformation-execute_AL("models/pn_analyse", {"PetriNets": "models/my_pn"}, {"ReachabilityGraph": "models/generated_reachability"})

Manual Operations
-----------------

Some other operations simply cannot be automated.
For example, translating natural language descriptions of requirements of a model to the actual model is not something that can be automated, at least for now.
The only solution, therefore, is to do these operations manually, thereby requiring user intervention.

Even though they are manual, they can still be partially automated: merging and splitting based on tags can still be done before and after the manual operation.

The functions on action language are exposed using the *transformation_add_MANUAL* and *transformation_execute_MANUAL* operations.
Their signature is identical to that of model transformations, except that now there is no model to provide with the *transformation_add_MANUAL*.
As with model transformations, the names of model entities are prefixed with their tags.

For example, when defining an operation to refine or revise a model in a DSL with respect to the requirements, such as the RPGame language from before::

    >>> def callback():
    ...     instantiate(None, "Association", edge=("Requirements/Actor", "RPGame/Player"))
    >>> transformation_add_MANUAL({"RPGame": "formalisms/RPGame", "Requirements": "formalisms/Requirements"}, {"RPGame": "formalisms/RPGame"}, "revise_rpg", callback)

Its execution, however, differs significantly from before, as we will see next.

Execution callbacks
^^^^^^^^^^^^^^^^^^^

As specified before, manual operations require user intervention during execution.
The *transformation_execute_MANUAL* operation is therefore not a simple function that is executed and returns after some time: user input must be given.
For this, an additional argument is provided as callback function.
This callback function can be any sequence of operations again, such that it is possible to create or alter the model.
Certainly, if it were to be possible automatically, we could have done it that way in the Modelverse.
Therefore, the callback function can do much more than just these calls: all expressive power of Python is available, such as the possibility for user input (*raw_input()*).
Based on this, the callback can define whatever structure is desired, possibly even going as far as starting up a dialog box or using a graphical interface.
As before, the first parameter of the operations (*model_name*) should be set to *None*.

For example, to specify the operations that have to be done in a manual operation, all in Python syntax::

    >>> def callback():
    ...     tiles = [instantiate(None, "RPGame/Tile") for _ in range(100)]
    ...     left_links = [instantiate(None, "RPGame/left", edge=(tiles[i], tiles[i+1])) for i in range(len(tiles) - 1)]
    ...     ...
    >>> transformation_execute_MANUAL("revise_rpg", {"RPGame": "models/my_rpg", "Requirements": "models/rpg_requirements"}, {"RPGame": "models/my_rpg"}, callback)

Alternatively, the operations might only be known at runtime, thereby requiring user interaction.
Note that, in this case, the code in the callback is effectively the user interface offered to the user in this specific context.
As such, the code can be completely tailored to the domain and problem at hand (e.g., a *create random level* operation).

    >>> def callback():
    ...     while True:
    ...         print("Please perform operation on RPGame!)"
    ...         inp = raw_input()
    ...         if inp == "new tile":
    ...             instantiate(None, "RPGame/Tile")
    ...         elif inp == "create random level":
    ...             ...
    ...         ...
    >>> transformation_execute_MANUAL("revise_rpg", {"RPGame": "models/my_rpg", "Requirements": "models/rpg_requirements"}, {"RPGame": "models/my_rpg"}, callback)

Manual operations are not the only operations which might require user input.
Model transformations and action language can just as well query the users for input.
This is done the same way: by defining a callback function.
Note that they are slightly different, in the sense that their callback functions have no option to call any of the Modelverse operations.
Instead, the callback is invoked when the action language, either standalone or in the actions/constraints of model transformations, executes the *input* or *output* operations.
The return value of the callback is made available to the action language (fragment) when it executes the *input* operations, and can be a single primitive type, or a list.
In case it is a list, the primitive types are offered to the action language individually, requiring multiple *input* calls.

Process Model
-------------

A next logical step is the chaining of these various operations, or activities.
For this reason, the FTG+PM was previously developed.
The Modelverse provides both modelling and enactment support for the FTG+PM.

When modelling, the FTG+PM is just a metamodel like all others, and instances can easily be created of it.
There are notions of activities, decision nodes, forks, joins, and so on.
Apart from control flow, an FTG+PM also specifies data flow: which models are used in this scope, and how are they interrelated.
Indeed, we could define the sequence of activities to execute, but we still need information on what are the input models of the various activities.
For example, the same activity might execute multiple times, but each time on different input models and generating different output models.

A simple process model is shown, which executes the operations stored in *models/A* and *models/B* in parallel.
Both operations use some data, where *models/A* modifies one of its data inputs in-place, and *models/B* generates new data::

    Start start {}
    Finish finish {}

    Fork fork1 {}

    Exec activity_A {
        name = "models/A"
    }

    Exec activity_B {
        name = "models/B"
    }

    Join join1{}

    Data data_a1 {
        name = "models/data_a1"
        type = "formalisms/Bottom"
    }

    Data data_a2 {
        name = "models/data_a2"
        type = "formalisms/Bottom"
    }

    Data data_b1 {
        name = "models/data_b1"
        type = "formalisms/SimpleClassDiagrams"
    }

    Data data_b2 {
        name = "models/data_b2"
        type = "formalisms/Bottom"
    }

    Next (start, fork1) {}
    Next (fork1, activity_A) {}
    Next (fork1, activity_B) {}
    Next (activity_A, join1) {}
    Next (activity_B, join1) {}
    Next (join1, finish) {}

    Consumes (activity_A, data_a1) {
        name = "First"
    }
    Consumes (activity_A, data_a2) {
        name = "Second"
    }
    Produces (activity_A, data_a2) {
        name = "Second"
    }

    Consumes (activity_B, data_b1) {
        name = "input"
    }
    Produces (activity_B, data_b2) {
        name = "output"
    }

This simple Process Model is equivalent to the following statements in the code.
Note that we do not know the type of the activity in the operations, and therefore put an asterisk (\*) there.
Also, the order in which these operations execute is undefined, as either order is fine.
We therefore leave it up to the Modelverse to decide upon an order::

    >>> execute_transformation_*("models/A", {"First": "my_models/data_a1", "Second": "my_models/data_a2"}, {"Second": "my_models/data_a2"})
    >>> execute_transformation_*("models/B", {"input": "my_models/data_b1"}, {"output": "my_models/data_b2"})

In this case, it doesn't seem useful to use processes, though it is possible for such processes to contain *Decision* nodes, which can be used for arbitrary control flows.
As everything runs on the Modelverse, instead of constantly requiring communication with the client, performance should be higher due to reduced network latency.

When enacting, the FTG+PM is executed by starting at the initial node.
The first element it points to is executed, thereby branching, deciding, or executing an activity.
Due to the branching, it is possible for multiple activities to be scheduled concurrently.
As for now, the Modelverse sequentializes the various activities internally.
When executing an activity, which can either be a model transformation, action language, or manual operation, the operation is executed with the required operation.
More information on enactment was previously given.
