123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- Internal workings
- =================
- For more detailed information on the Modelverse specification, which this project is implementing, we refer to the `Modelverse Specification <http://msdl.cs.mcgill.ca/people/yentl/files/Modelverse.pdf>`_.
- Information on the implementation can be found below.
- Modelverse State
- ----------------
- The Modelverse State is basically just an implementation of a graph library.
- As we have a particular kind of graph, this implementation is mostly done by hand at the moment.
- The notable exception to this is the RDF backend, proving that other implementations can also be used.
- The basic implementation just stores everything as dictionaries.
- All operations are then defined by doing operations on these dictionaries.
- The most interesting operations here are dictionary operations, which need to traverse these dictionaries in complex ways.
- To overcome performance problems for these operations, all results are cached (and validated afterwards).
- RDF backend
- ^^^^^^^^^^^
- The RDF backend requires the *rdflib* module in Python.
- The Modelverse graph is then stored in RDF representation and all operations on it are done using SPARQL queries.
- Due to this level of indirection, performance is extremely slow.
- To increase performance, we would likely have to make more *composite* operations, or even group different requests together internally.
- Status codes
- ^^^^^^^^^^^^
- The MvS returns, apart from its actual return value, a status code for the request.
- This value is not used by the MvK at all, since sometimes a request is expected to give an error (*e.g.*, checking whether an element is present).
- When debugging the MvS, however, these status codes can come in handy.
- Modelverse Kernel
- -----------------
- The Modelverse Kernel is basically a graph transformation kernel.
- It consists of two parts: a small transformation engine (only a few lines in Python), and a copy of all rules it knows.
- Most code is an encoding of these transformation rules, and can (theoretically) be automatically generated by the Modelverse itself.
- This is currently not top priority, but will probably be implemented in the future.
- The transformation rules are an encoding of the rules presented in the specification mentioned at the top of this page.
- In each rule, the MvK needs to have tight communication with the MvS.
- For this, the rules are encoded using *yield* operations in Python.
- Rules therefore act as generators, outputting commands to the MvS, and immediately getting a reply.
- Each individual yield contains a list of operations to send to the MvS simultaneously.
- The result will therefore also be a list of results for each operation.
- As you can see in these rules, each entry in the list has a specific form.
- An entry is a tuple containing two elements: the code of the operation to execute, followed by a list of all parameters to pass.
- The code is a shortened name for the operation, such as *CN* for *create_node*.
- A full mapping can be found in the MvS, the most important ones are shown below.
- ==== ================
- Code Function
- ==== ================
- CN create_node
- CNV create_nodevalue
- CE create_edge
- CD create_dict
- RV read_value
- RE read_edge
- RD read_dict
- RDN read_dict_node
- DN delete_node
- DE delete_edge
- ==== ================
- .. note::
- Since each yield operation works on lists even a single operation needs to be wrapped in a list.
- Worse, however, is that the return value will also be a list.
- Instead of having to take the first element each time, Python allows you to write a comma after the variable, to make it expand it automatically.
- Your syntax thus becomes::
- a, = yield [("CN", [])]
- Where a will now already contain the created node, and not a list with as first (and only) element the created node.
- Primitives
- ^^^^^^^^^^
- The Modelverse Kernel also defines the primitives users can use.
- Primitives are necessary since we can't go deeper at some point.
- It is the MvK's responsibility to implement each and every primitive defined in the bootstrap file.
- Primitives are implemented in the bootstrap file as having a body with an empty node.
- When execution goes to this node, the MvK must execute the associated primitive instead.
- To do this, the MvK must map all these nodes to their corresponding primitive implementation during startup.
- Precompiled functions
- ^^^^^^^^^^^^^^^^^^^^^
- Similar to primitives, the MvK also supports precompiled functions.
- A precompiled function is similar to a primitive in terms of implementation, but they do also have an implementation inside of the Modelverse itself.
- This means that there are two implementations: one hardcoded, and one in action language.
- Both should obviously be consistent.
- Whereas primitives are required for a functional Modelverse, precompiled functions are only a performance optimization, and can be disabled by the user for debugging purposes.
- This is interesting, since with precompiled functions, no intermediate values will be visible.
- Additionally, precompiled functions cannot be changed, as they are Python code instead of being explicitly modelled.
- Modelverse Interface
- --------------------
- Semantics visitor
- ^^^^^^^^^^^^^^^^^
- Constructors visitor
- ^^^^^^^^^^^^^^^^^^^^
- Primitives visitor
- ^^^^^^^^^^^^^^^^^^
- Bootstrap visitor
- ^^^^^^^^^^^^^^^^^
- Model visitor
- ^^^^^^^^^^^^^
- Bootstrapping
- -------------
- To bootstrap, you just have to run the file *bootstrap.py*.
- Here, we explain what this file actually does...
- The bootstrap script primarily creates the initial graph manually.
- This manual creation is done by writing data to a file, which is later read by the MvS.
- The initial graph consists of several parts:
- * The Modelverse Root node;
- * A global definition of all primitives;
- * The stack frame of the initial user *user_manager*, which manages all other users;
- * The code for the *user_manager* user;
- * The code for all new users, as assigned by the *user_manager*;
- * Bindings in the compilation manager for bootstrap files.
- These are all fairly simple in themselves.
- For some parts, such as the code, the HUTN compiler is invoked on a temporary piece of code.
- All bootstrap files are also compiled and made available to the compilation manager with their MD5 hash.
- How to add a primitive
- ----------------------
- Probably the most important reason why you would want to know about the Modelverse internals, is if you want to add a Modelverse primitive.
- Primitives are functions implemented in the MvK core, and thus become hardcoded.
- .. warning::
- While these functions are hardcoded, their implementation needs to follow strict rules, such that their semantics is identical in all possible programming languages.
- For example, a primitive *getTime()* cannot simply be implemented as *time.time()* in Python, as this gives different results on Linux and Windows.
- To add a primitive, follow these steps:
- 1. Add the code of the primitive to the MvK implementation by adding it in the file *primitives.py*.
- a. Name the function identically to how the primitive will be invoked later on.
- b. Take a set of parameters. Parameters for primitives are always positional and start from *a* onwards.
- c. The final parameter should be a catch-all element, as it is possible that a high number of additional information is passed. In Python, this is done with a parameter prepended with \*\*.
- d. When asking the MvS for information, use yields, just like the implementation of transformation rules in the MvK.
- e. Instead of return, use a *raise* with the exception *PrimitiveFinished*. This exception takes one argument: the returnvalue.
- 2. Add the signature to *bootstrap.py* in the topmost dictionary *primitives*. The key is the name of the function, and the value is a list starting with the return type, followed by all parameter types (as string).
- 3. Add the declaration to *includes/primitives.alh* to make them available everywhere.
- 4. Add the definition to *primitives.alc* and assign the Modelverse reference *?primitives/function_name*.
- 5. Recreate the bootstrap file by running the script *generate_bootstrap.py*.
- 6. Restart the Modelverse to reload the bootstrap file.
- 7. From now on, all files including *primitives.alh* have access to the defined function.
- Adding a precompiled function
- -----------------------------
- Adding a precompiled function is way easier: only step 1 of the addition of a primitive should be done, but in the file *compiled.py* instead of *primitives.py*.
- All other steps are done automatically since there is an action language implementation present.
- The MvK will then automatically pick the precompiled function or the explicitly modelled operation, depending on preferences.
- A restart of the MvK is needed for Python to pick up the new functions.
|