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, we assume exists already. 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 as formalisms/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. Note that this operation creates a new model, and therefore the target location should be writable, as otherwise a PermissionDenied exception is raised. 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 (with or without trailing forward slash):

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

>>> 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. When invoked on a folder, the complete folder, including all subfolders, is moved to the new 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 directly. We again categorize them by their effects on the Modelverse: create, read, or delete. Updates are not allowed, and must thus 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 models they created themselves.

As will become clear, all these operations take the model in which they operate as first argument. Subsequent operations on the same model are optimized, offering a noticeable performance improvement.

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. Note that this type is specified by the ID of the model that is being instantiated. 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
    

    Optionally, users can suggest an ID themselves, through the ID parameter. Note that there is no guarantee that this will actually be the returned ID (e.g., if the ID is already in use):

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

    >>> p1 = instantiate("models/my_pn", "Place")
    >>> 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):

    >>> read_outgoing("models/my_pn", "p1", "")
    ["p2t"]
    
  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:

    >>> read_incoming("models/my_pn", "p2", "")
    ["t2p"]
    
  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 reads out the set of all instances of a specific type in the model. Again, inheritance information is taken into account. 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.