-
Notifications
You must be signed in to change notification settings - Fork 4
The Geppetto model explained
The Geppetto Model abstraction is used as a base to define structured data in Geppetto, typed related objects that can be displayed and specified as needed by the application. The Geppetto model may sound complex in the beginning; it's complexity comes from its ability to describe experimental bases which allow to describe computational neuroscience experiments, with typed variables which can be fetched, instantiated to describe a given state of the simulation and its visual aspects. On the other hand, most of the applications can use directly Simple Instances, a more plain way to specify data. Using the Geppetto model is not mandatory to use all Geppetto components, but some (e.g. the Canvas) rely on it in different ways.
The Geppetto Meta-Model is defined in a declarative way using Ecore, a tool for building meta-models from the Eclipse Modeling Framework (EMF). Introductory documentation for EMF can be found at the eclipse project website.
Every project that is loaded in Geppetto has a Geppetto model associated to it. The Geppetto Model is a dynamic typed abstraction of the data structures we need for our project. In other words, Geppetto imports the project specific data into a model with fully typed object descriptions.
When the frontends loads a project, the model (coming from the backend) is loaded; while loading the model, the declared variables are instantiated with the default values which are specified in the model. The model comes with a full-featured reflection mechanism, which allows to inspect and instantiate values through the Meta-Model specification.
Geppetto was born from the need of describing computational models coming from neuroscience. In this context, each model has its own structure and values that can evolve during the experiments we are running upon the model.
We can represent the model with the usual tools that we use in programming to represent data structures: variables, classes, values, parameters within a life cycle in which variable values can change and can be represented visually.
Nothing that we cannot do with any programming language, except the fact that all of this definitions in Geppetto are made dynamically: when we load a model, we are defining a complete new set of typed data structures based on the Geppetto Meta-Model. We have no limits on the models we can define, still having a fully typed model system.
The Geppetto Model can be serialized and is shared and synced between the backend and the frontend of the application.
There are simpler ways than the Geppetto Model to defined dynamic structures. For instance, we could define variables with any structure with a JSON markup. JSON markup is not typed though. When we parse a JSON markup we must implicitly know its semantics and write a specific manipulation logic consequently. By defining the types we are kind of declaratively programming the visualization and behaviours which can be automatically associated to the data through Geppetto apis and components. For example, a visual type can be visualized on the canvas, time series can be plotted, etc. On the frontend, instances of different types can have different capabilities, i.e. actions that can be activated from the variables of that types.
The Geppetto Meta-Model is a description of what can be found inside a Geppetto model, hence the "meta". The main concepts to understand from the Geppetto Meta-Model are Type, Variable and Value, and they will be familiar to pretty much every developer. A Geppetto model defines variables, which at some point will be instantiated; each variable has a value and a type. With the use of composite types, we can define complex data structures.
A Type represents the structure of an entity and therefore defines something that can be associated to one of multiple variables. In modern programming languages this same concept is often referred to as Class.
There are different built-in types defined in the Geppetto Meta-Model; the CompositeType allows the developer to specify structured types with one or more variables.
Every type belongs to the Geppetto library which contains it.
A variable represents an instance of a given type. This concept exists in every programming language. Every variable knows its type and initial value.
A value is something that can be assigned to a variable or to a type (the default value) a concept that once again exists in every programming language. There are different kinds of values defined in the Geppetto meta-model and every existing type has pretty much one or more corresponding values defined.
The Geppetto model is created on the backend usually from an EMF specification file (xmi). Domain-specific Geppetto types and values are created dynamically at runtime from possibly any file format provided a Model Interpreter is available for that format.
Upon receiving a Geppetto Model from the backend, when loading a given Geppetto Project, the frontend will instantiate it.
Instantiated Geppetto Types are mapped to JavaScript objects (e.g. a population of one cell Type would become a JavaScript array containing Instances of that Type) and augmented with specific Capabilities which confer on them the ability to be accessed via a specific API.
Variables are defined with a type and a default value. When the Geppetto model is loaded on the frontend its top-level variables are instantiated (the model is only instantiated in the client). Every instance knows its value and variable, each variable knows its type(s). A type in turn may contain multiple variables (as is the case of CompositeType) that can be instantiated. The instantiation of inner variables is done on-demand at runtime when needed.
For example, let's start from a model having a top-level variable x of type T. The
type T is a CompositeType defining the variables t1 and t2.
When the model is loaded we will have an instance called x
on our Geppetto runtime.
So we can:
- Use that instance
x
in the Geppetto console - Access the instance through the global Javascript variable
x
- Create the instance from Javascript through
Instances.getInstance('x')
- Create the instance from Javascript through
Instances.getInstance('x.t1')
- Use the instance sub variables through
x.t1
,x.t2
from the Geppetto console
Note that we are not guaranteed to access x.t1
from Javascript until
Instances.getInstance('x.t1')
is invoked: sub instances are created on demand.
Let's have a look at some examples that will show how the model abstraction can be used in practice. In these examples, we will use the JAVA API that is generated for us by EMF to create the models.
Let's say we want to create a type that represents a biological cell. What would we need to do?
SimpleType cellType = TypesFactory.eINSTANCE.createSimpleType();
cellType.setId("cell");
cellType.setName("Cell");
GeppettoLibrary myLibrary = GeppettoFactory.eINSTANCE.createGeppettoLibrary();
myLibrary.setId("myLibrary");
myLibrary.getTypes().add(cellType);
With these three lines, we are creating a new simple type in Geppetto. A simple type has a name, what will be displayed in the UI, and an id which is what will be used every time we want to access the type. We have then added our brand new type to a brand new library we created. If we were to feed this model to Geppetto, the type with its two fields contained inside "myLibrary" is what we'd see. Let's see how difficult it is to see a Sphere in the Geppetto 3D canvas when we instantiate our cell.
The first thing we want to get a handle on is what we call a VisualType. A VisualType tells Geppetto that something can be visualised and comes from inside the Geppetto Common Library. The Geppetto Common Library is a collection of types that Geppetto instantiates by default and that can be used by every domain and application. It is no different from the "myLibrary" we created above, only this one comes from Geppetto.
VisualType visualType = (VisualType) commonLibrary.getType(TypesPackage.Literals.VISUAL_TYPE);
Variable soma = VariablesFactory.eINSTANCE.createVariable();
soma.setId("soma");
soma.getTypes().add(visualType);
Sphere sphere = ValuesFactory.eINSTANCE.createSphere();
sphere.setRadius(10);
Point origin = ValuesFactory.eINSTANCE.createPoint(); //x=0,y=0,z=0 by default
sphere.setPosition(origin);
soma.getInitialValues().put(visualType, sphere);
CompositeVisualType morphology = TypesFactory.eINSTANCE.createCompositeVisualType();
morphology.setId("morphology");
morphology.getVariables.add(soma);
cellType.setVisualType(morphology);
So what have we done? We have created a variable we called "soma" and we have made it of type VisualType, reusing what comes from the commonLibrary. We have created a Sphere which is one of the VisualValues available through the Geppetto Model, we gave it a radius of 10, and we placed it at the origin of our scene. We have then assigned this sphere as the initial value of our variable soma. What we created next is "morphology", a CompositeVisualType to which we have added the soma we just created. The last line assigns this morphology to the cell we created. What would we see if we were to feed this type to the frontend?
In order to use and visualize the cell, you need to instantiate you need to add a new variable of that type to the model:
Variable myCell = VariablesFactory.eINSTANCE.createVariable();
myCell.setId("myCell");
myCell.getTypes().add(cellType)
geppettoModel.getLibraries().add(myLibrary);
geppettoModel.getVariables().add(myCell);
So this is how you instantiate something, just as you'd expect. We create a variable of the type that we want, cellType in this case, and we add it at the root level in the geppettoModel, which in this case represents the Java object of our Geppetto Model.
Instances is a global service which allows to extract instances from the model. An instance is a wrapper for available with inspection capabilities: from an instance, we can start the inspection of everything that is below.
-
Instances.getInstance(variablePath)
: allows to instantiate a variable if not already instantiated and returns it. After an instance is created it can be accessed from the global scope through its variable name -
myInstance.getVariable()
: returns the variable definition for the instance, starting inspection. A typical use ismyInstance.getVariable().getType().getVariables()
, which gives all the available fields for that instance. -
myInstance.getPath()
: gets the full path within the model
-
myInstance.getValue()
: returns the actual value for that instance
-
myValue.resolve(callback)
: resolves the current value on runtime
...
...
...
-
GEPPETTO.ModelFactory.allPaths
: gets all paths available on the current model
Why EMF? The Eclipse Modelling Framework is an industry-grade technology which has been around for more than 15 years and is currently used in thousands of professional software and tools. Ecore allows the developer to specify all the entities (called EClass) and relationships that exist in a given meta-model allowing the developer to define all the constraints (e.g. containment, hierarchy, boundary conditions, etc.) that exist in the model in a declarative way. EMF adds the ability to generate, from the model definition, the code to use the model in a multitude of languages, making pretty much every line of model-related code bug-free. EMF supports XMI, a dialect of XML, as the default serialization standard, making it easy to serialize and deserialize models in a robust way, performing validation against the schema through every step of the process. Geppetto also takes advantage of EMF-JSON, an extension that makes it possible to serialize the models to JSON as well.
This is the top-level package and it contains many of the Geppetto abstractions.
GeppettoModel is the EClass that represents the top-level node of a Geppetto Model. Node is an abstract EClass, extended by many entities, which gives the ability to associate an id, a name and a set of Tags to every entity. The Geppetto Library is simply a container for types. In a Geppetto Model there can be one or multiple libraries defined.
This package contains the definition of all the types defined in the Geppetto Meta-model.
An abstract type, simply called Type, is defined and is extended by every existing type. Every Type can have zero or many superTypes (multiple hierarchies that is), an optional VisualType (which specifies how that type can be visualised in the 3D environment) and an optional DomainModel (to specify what domain is declaring that particular type).
The Geppetto Meta-model defines a set of types to represent dynamic systems. These types can be used by every developer that wishes to extend Geppetto to add support for a particular modelling specification.
StateVariableType and ParameterType define, respectively, a state variable and a parameter of a system. Dynamics describes the dynamics of the system specifying a Function and a PhysicalQuantity as initalCondition. A Function is defined as an Expression and a list of Arguments.
An ArrayType defines a type that when instantiated will result in multiple instances of the type of the array to be created. After instantiation, each element of the array can be accessed through an index. The type of the Array can be any Geppetto Type. A VisualType is an abstract EClass that defines a particular kind of type that can be visualised in the 3D environment. A VisualType only allows for a VisualValue to be associated to it (e.g. a Cylinder, a Sphere, an OBJ, etc.).
This package contains the definition of all the values that can be associated to variables and types.
A Quantity defines the result of a measure. When we associate a Unit to a Quantity we obtain a PhysicalQuantity. A special mention to CompositeValue that defines a structure value that can be assigned to a variable of type CompositeType. VisualValues can be assigned to variables of type VisualType. ArrayValues can be assigned to variables of type ArrayType and specifies the index for each one of the individual values.
This package contains the definition of the variable EClass.
Geppetto allows a variable to instantiate one or multiple types, a feature that makes it possible to support multi-scale definitions (imagine a variable that at one scale is defined simply as a parameter and at another scale is mapped to a whole computational model with sophisticated dynamics).