Modelling
=========

Modelling is the core activity in the Modelverse.
A model is based on a meta-model, or a language, which, for the sake of this tutorial, assume to exist.
Throughout this tutorial, we will focus on how to instantiate a simple Petri Net model.
We assume that our language has a notion of *Place*, *Transition*, *P2T* arc, and *T2P* arc.
The *Place* has a *name* and *tokens*, the *Transition* has a *name*.
Both the *P2T* and *T2P*"arcs have a *weight*.
This information is stored in the metamodel, which we will define later on.
For now, assume that the metamodel is stored in the Modelverse using the name *PetriNets*.

Model Management
----------------

Modellers have access to a variety of model management operations, making it possible to create, read, update, and delete complete models.

Create
^^^^^^

Creating a new instance of PetriNets is simple, and can be done using the *model_add* operation of the wrapper::

    >>> model_add("models/my_pn", "formalisms/PetriNets")

The first parameter of the operation indicates the name that we would like to give to our newly created model.
This name is available to all users, and is therefore a unique identifier.
The second parameter is the metamodel that we want to use to conform to.
The value needs to be a name that is available in the Modelverse, and readable to the current user.

Read
^^^^

To get a list of currently available models, users can query the Modelverse with the *model_list* operation::

    >>> model_list("")
    ["formalisms/", "models/", "administration/", "users/", "type mappings/"]

This list contains the various entries in the specified location.
Note that this operation does not specify anything about the permissions of the supplied entries.
Entries that end with a forward slash (/) are folders, and can subsequently be listed::

    >>> model_list("formalisms")
    ["Bottom", "SimpleClassDiagrams", "Tracability", ...]

    >>> model_list("users/admin")
    []

Depending on permissions, not all folders can be listed::
    
    >>> model_list("administration")
    PermissionDenied("administration")

Update
^^^^^^

Models can be updated in the sense that they can be moved.
This is the operation *model_move*, which takes a model and moves it to the specified location.
There is no output to this operation::

    >>> model_move("models/my_pn", "models/my_new_pn")

Delete
^^^^^^

Finally, models can be deleted using the *model_delete* operations.
Model deletion is permanent and cannot be reverted::

    >>> model_delete("models/my_pn")

Modifying a model
-----------------

Apart from operations at the model management level, where models are considered as atomic, several operations are provided to operate on the model contents.
We categorize them by their effects on the Modelverse: create, read, or delete.
Updates are not allowed, and must be expanded in individual delete and create operation.
Create and delete operations are only allowed when the user has write permission to the model, which users have by default on their own models.

Create
^^^^^^

1. *instantiate* creates a new instance in the model, for example a new PetriNet place::

        >>> instantiate("models/my_pn", "Place")
        __12345

   The operation requires the model on which we are working, and the type of the element you want to instantiate.
   When successful, the operation returns the identifier that can be used in future operations.
   This identifier has no value within the model, and should only be used as a handle to that specific model element.

   When instantiating an edge, the optional *edge* parameter must be passed with the identifiers to connect::
        
        >>> instantiate("models/my_pn", "Place")
        p1
        >>> instantiate("models/my_pn", "Transition")
        t1
        >>> instantiate("models/my_pn", "P2T", edge=("p1", "t1"))
        p2t1

2. *attr_assign* assigns attributes of a specific model element.
   For example, it specifies the name and number of tokens of our PetriNet place::
   
        >>> instantiate("models/my_pn", "Place")
        p1
        >>> attr_assign("models/my_pn", "p1", "name", "place 1")
        >>> attr_assign("models/my_pn", "p1", "tokens", 2)

   The value of the attribute can be any simple primitive: string, integer, float, or boolean.
   When the attribute already exists, its value is overwritten.
   If it doesn't exist, it is created.

Read
^^^^

1. *read_info* reads out basic information about a queried element, such as its type and the source and target (if it is an edge)::

        >>> instantiate("models/my_pn", "Place")
        p1
        >>> read_info("models/my_pn", "p1")
        ("Place, None)
        >>> instantiate("models/my_pn", "Transition")
        t1
        >>> instantiate("models/my_pn", "P2T", edge=("p1", "t1"))
        p2t1
        >>> read_info("models/my_pn", "p2t1")
        ("P2T", ("p1", "t1"))

2. *read_attrs* reads out the attributes of a specific element, in a dictionary form.
   This operation can be used to read out, for example, the number of tokens of a specific place::

        >>> instantiate("models/my_pn", "Place")
        p1
        >>> attr_assign("models/my_pn", "p1", "name", "place 1")
        >>> attr_assign("models/my_pn", "p1", "tokens", 2)
        >>> read_attrs("models/my_pn", "p1")
        {"name": "place 1", "tokens": 2}
   
3. *types* reads out the list of types that can be instantiated in this model.
   All calls to instantiate should act upon one of these types.
   For PetriNets, this returns the concepts of the domain::

        >>> types("models/my_pn")
        ["Place", "Transition", "P2T", "T2P", ...]

4. *element_list_nice* reads out a simple JSON-like representation of the model.
   This includes all information about the model and can be used to fetch the complete model in one go.
   For example, to read out a simple PetriNet::

        >>> element_list_nice("models/my_pn")
        [{"id": "p1", "type": "Place", "name": "place 1", "tokens": 1},
         {"id": "p2", "type": "Place", "name": "place 2", "tokens": 2},
         {"id": "t1", "type": "Transition"},
         {"id": "p2t", "type": "P2T", "__source": "p1", "__target": "t1", "weight": 1},
         {"id": "t2p", "type": "T2P", "__source": "t1", "__target": "p2", "weight": 2},
        ]

5. *read_outgoing* reads the outgoing associations of a certain type, for a specific element.
   This takes into account inheritance relation.
   For example, to read out all outgoing P2T links of a place::

        >>> read_outgoing("models/my_pn", "p1", "P2T")
        ["p2t"]

   It is possible to get all outgoing associations as well, by leaving the type empty (the empty string).

6. *read_incoming* similarly reads out all incoming associations of a certain type, for a specific element.
   For example, to read out all incoming T2P links of a place::

        >>> read_incoming("models/my_pn", "p2", "T2P")
        ["t2p"]

   Again, the type can be set to the empty string to return all incoming associations.

7. *read_association_source* reads out the source of a specific association, and can be used in conjunction with *read_outgoing* and *read_incoming*.
   For example, to read out which is the source of an arc::

        >>> read_association_source("models/my_pn", "p2t")
        p1

8. *read_association_destination* similarly reads out the destination of a specific association.
   For example, to read out the target of an arc::

        >>> read_association_destination("models/my_pn", "p2t")
        t1

9. *connections_between* reads out the set of all association types that can be created between two elements in the model.
   This also takes into account inheritance information.
   If no associations are allowed, the list is empty.
   For example, to find out which association types can connect a place and transition::

        >>> connections_between("models/my_pn", "p1", "t1")
        ["P2T"]

10. *all_instances* read out the set of all instances of a specific type in the model.
    For example, to find all Places in our PetriNet model::

        >>> all_instances("models/my_pn", "Place")
        ["p1", "p2"]

Delete
^^^^^^

1. *delete_element* deletes the element from the model and the type mapping.
   Note that currently, attribute values are not automatically removed from the model, and they will remain dangling.
   Therefore, to prevent strange errors, it is safest to first delete all attributes of an element before deleting it.
   Associations, however, are removed.
   For example, to remove place p1::

        >>> element_list_nice("models/my_pn")
        [{"id": "p1", "type": "Place", "name": "place 1", "tokens": 1},
         {"id": "p2", "type": "Place", "name": "place 2", "tokens": 2},
         {"id": "t1", "type": "Transition"},
         {"id": "p2t", "type": "P2T", "__source": "p1", "__target": "t1", "weight": 1},
         {"id": "t2p", "type": "T2P", "__source": "t1", "__target": "p2", "weight": 2},
        ]
        >>> delete_element("models/my_pn", "p1")
        >>> element_list_nice("models/my_pn")
        [{"id": "p2", "type": "Place", "name": "place 2", "tokens": 2},
         {"id": "t1", "type": "Transition"},
         {"id": "t2p", "type": "T2P", "__source": "t1", "__target": "p2", "weight": 2},
        ]

2. *attr_delete* deletes an attribute from a model element.
   The attribute is removed, basically rendering it undefined.
   Nonetheless, if the attribute is optional, a subsequent *attr_assign* call is required to make sure that the model conforms.
   Note that *attr_assign* automatically updates the attribute, so *attr_delete* is only necessary for optional attributes that must be unset.
