In the past decades, the domain of cyber-physical systems (CPSs) has made enormous advances. Systems engineers pushed for the creation of sophisticated formalisms, intricate modelling languages and powerful modelling platforms that support them in their daily work. As a result, it is nowadays possible to model, simulate and verify larger and more complex systems than ever before.
One problem that comes with these tools and languages is that the complexity of the modelling approach grows at the same rate. System builders have to choose amongst a plethora of available modelling solutions and weigh each one’s benefits and downsides to select the most appropriate choice. While this task is usually seen as “part of the game” for modelling experts, it often deters the creators of small-scale CPSs such as smart-home applications or smaller office automation installations.
The reaction to this issue is the move towards domain-specific languages (DSLs). These (modelling) languages attempt to close the semantic gap between a model and the system’s domain and its users. They overcome the genericity of the employed tools, by providing dedicated domain concepts as a frontend to mask the underlying complexity. Based on these insights, I developed CREST, a domain-specific formalism for the modelling of continuous resource flows (e.g. water, heat, electricity) in CPSs. CREST’s goal is to provide a coherent means to expressing a CPS’s structure alongside its reactive and continuous behaviour. Contrary to complex modelling solutions, CREST aims to be easily learned and used, even by novices to the modelling domain or non-professional creators of systems such as smart homes. Despite CREST being a novel formalism, its features and modelling methodology are inspired by existing formalisms, languages and tools that are actively used for CPS modelling.
Usually, languages like CREST are created using language workbenches that promise to simplify the development and provide compatibility with tools that fit neatly into the language eco-system. This approach leads to out-of-the-box features such as standardised model serialisation, readily available model-transformation tools and the integration into other model-based tools. Despite these advantages, the development and maintenance of DSLs still requires significant effort. In this blog post, I want to draw attention to internal DSLs. These kinds of languages are integrated into a host language (typically a programming language) and build upon its syntax, runtime and development environment. A well-known internal DSL is the SystemC language, which is has been successfully used for the development of hardware such as system-on-a-chip components. The idea of embedding a DSL inside a more general language is based on the expected reduction of development effort, reuse of syntax and existing runtime, and availability of third-party libraries reduce development effort.
CREST & crestdsl
CREST is implemented as an internal DSL, using Python as a host language. This means that all aspects and domain concepts are implemented and expressed using standard Python constructs.
I chose Python for its many convincing benefits. Amongst other strongpoints, it provides an easy to learn syntax, comes pre-installed on most modern operating systems and experiences continuously growing popularity. From a language engineering viewpoint, Python is highly flexible, supports various programming paradigms (e.g. object oriented, functional), a customizable meta-class system and various means to adjust execution flows (e.g. using method/class annotations, object introspection, etc). Additionally, its popularity led to the development of a very large number of readily available third-party libraries, development environments, bindings to and from other languages (see e.g. C & C++, Swift) and tools, and even a browser-based interactive runtime environment (Jupyter). The following screenshot shows a short excerpt of a CREST model code developed a Jupyter notebook.
The development of CREST models is straightforward. All functionality is distributed in the form of the crestdsl library, whose functionality is split in submodules. The modelling module can be accessed as follows:
import crestdsl.model as crest
CREST models the flow of resources. Thus, before defining components and their communication interfaces, it is necessary to define these resource types and the value domains:
CREST components are defined as
Entity objects, their internal structural and behavioural components are specified as object attributes. Usually though, instead of creating entities directly, they are instantiated from entity types (subclasses of Entity base class).
Dynamic behaviour is also defined using native Python mechanisms. The following example shows the definition of a lamp entity with a state automaton and guarded transitions. In the example, two different forms of transition specification are shown: The first one creates a
Transition object and specifies the guard using a lambda-expression. The latter one uses the
@transition annotation. The specification styles are equivalent since the crestdsl engine aligns their functionality “behind the scene”.
So-called “update” functions are used to modify port values during model execution. Each update specifies an automaton state in which it is active. This means that during simulation, the update’s
dt parameter will hold the amount of time that has passed since the update was last executed. Theoretically, updates are executed in infinitesimal intervals. Practically, the simulator is responsible for executing the updates as often as required. Similar to transitions, updates can be modelled as either lambda expressions or annotated class methods.
crestdsl further supports classical Python development best practices. Thus, class inheritance can be used to extend and specialise existing entity types, while
__init__ constructors can increase code reusability, as shown in the following example.
crestdsl models can be simulated and analysed. In general, the models support two types of interaction:
- Modification of input port values, and
- The advance of time.
Either one requires the system to be stabilised afterwards. Stabilisation is the process of propagating the information throughout the system, such that the new port values are relayed to all update functions that depend on them.
The code below shows the information an extension of the
DynamicLamp that automatically shuts off after 30 time units and then remains off for 15 time units before it can be turned on again.
To simulate this entity, the
Simulator class can be instantiated with a model entity.
Next to the reuse of Python’s runtime and execution platform, crestdsl experiences even further advantages by building on top of a widespread programming language. For example, the combination with Jupyter enables the use of existing libraries to enhance the use of Python.
Additionally, it is also possible to make use of Jupyter’s native integration of the Pandas library, which is a de-facto standard for data analysis and machine learning applications in Python. In concert with the Plotly graph plotting library, crestdsl’s tracing information can be displayed and interactively explored and evaluated in a user-friendly way.
Another advanced use case that my colleagues and I are currently exploring is the application of machine learning and artificial intelligence techniques to move towards a data-driven modelling process.
Live demo and installation
By building upon Juptyer notebooks, it is possible to make crestdsl accessible directly on the internet, without the need of installation. Try it!
The link leads to a few notebooks that showcase some of crestdsl’s functionality. (You might have to wait for a few of minutes for the server to launch in the background).
The loaded model code is hosted on Github, the crestdsl library and dependencies are packaged as a docker image. Alternatively, the docker image can also be installed locally.
For more information about the use and installation of crestdsl, visit the project’s webpage at https://crestdsl.github.io.
When I started the work on CREST, I faced the difficult choice between language workbenches and the use of internal DSLs. Before this point I already had (a little) experience with the development of DSLs using Xtext and the Eclipse modelling eco-system. In these projects I often overcame the positive parts too quickly (early results, third party plugins) and soon moved to the “unfortunate” situations (sleepless nights, fear of “workbench lock-in”, missing/incomplete documentation).
With some positive encouragement found in the DSL book, I decided against using language workbenches for CREST and instead chose Python. I very quickly realised that my initial worries of having to manually recreate the features that are already provided by the workbenches were unwarranted. In fact, I was positively surprised by how straightforward the development was.
Obviously, Python is no silver bullet and there is still development effort necessary – just like in any other project. However, for the use cases that CREST tries to solve (model creation, simulation, formal verification) Python offers exactly what I needed: A dynamic language with clear syntax, stable runtime and established development practices, and a large and supportive developer base.
Give it a try! And please share your experience with me…