├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── Simulation.ipynb ├── Syntax-Semantics.ipynb ├── ThreeMasses.ipynb ├── crestdsl ├── __init__.py ├── caching.py ├── config.py ├── ml │ ├── __init__.py │ ├── decorators.py │ ├── functionlearn.py │ └── learners.py ├── model │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── api.py │ │ └── convenienceAPI.py │ ├── datatypes.py │ ├── decorators.py │ ├── entity.py │ ├── meta.py │ ├── model.py │ ├── ports.py │ ├── resource.py │ └── systemcheck.py ├── planning │ └── __init__.py ├── simulation │ ├── __init__.py │ ├── basesimulator.py │ ├── conditiontimedchangecalculator.py │ ├── contextconditiontimedchangecalculator.py │ ├── dependencyOrder.py │ ├── epsilon.py │ ├── fastconditiontimedchangecalculator.py │ ├── interactivesimulator.py │ ├── plansimulator.py │ ├── simulator.py │ ├── to_z3.py │ ├── tracestore.py │ ├── transitiontime.py │ ├── z3calculator.py │ ├── z3conditionchangecalculator.py │ └── z3simulator.py ├── sourcehelper.py ├── ui │ ├── __init__.py │ ├── dotter.py │ ├── draw_statespace.py │ ├── elk.py │ ├── icons │ │ ├── add.png │ │ ├── press32.png │ │ ├── print32.png │ │ ├── view_1_132.png │ │ ├── wrench.png │ │ ├── zoom_in32.png │ │ └── zoom_out32.png │ ├── index.html │ ├── index.js │ ├── index_with_comments.html │ └── statespace.html └── verification │ ├── __init__.py │ ├── alpha_beta.py │ ├── checklib.py │ ├── continuous.py │ ├── modelchecker.py │ ├── modelchecking.py │ ├── normalform.py │ ├── pointwise.py │ ├── reachabilitycalculator.py │ ├── simplify.py │ ├── small_normalform.py │ ├── statespace.py │ ├── tctl.py │ └── verifier.py ├── docs ├── Makefile ├── assets │ ├── crestlogo.png │ ├── crestlogo.svg │ ├── crestlogo_rectangle.png │ ├── crestlogo_rounded_square.png │ └── crestlogo_square.png ├── index.html ├── make.bat ├── readthedocs.requirements.txt └── source │ ├── api │ ├── crestdsl.config.rst │ ├── crestdsl.model.api.rst │ ├── crestdsl.model.datatypes.rst │ ├── crestdsl.model.rst │ ├── crestdsl.model.systemcheck.rst │ ├── crestdsl.rst │ ├── crestdsl.simulation.rst │ ├── crestdsl.ui.rst │ └── crestdsl.verification.rst │ ├── conf.py │ ├── content │ ├── contact.rst │ ├── install.rst │ ├── modelling.rst │ ├── simulation.rst │ └── verification.rst │ ├── index.rst │ └── publications.bib ├── requirements.txt ├── scripts ├── docker-build.sh ├── docker-run-tests.sh ├── docker-run.sh └── install-run-tests.sh ├── setup.py └── tests ├── __init__.py ├── model ├── __init__.py ├── api │ ├── __init__.py │ └── test_convenience.py ├── test_entity_creation.py ├── test_entity_get_XY.py └── test_ports.py ├── simulator ├── __init__.py ├── test_dependencyOrder.py ├── test_epsilon.py ├── test_plansimulator.py ├── test_resolve_type.py ├── test_rewrite_if.py ├── test_sourcehelper_ast.py ├── test_to_z3.py └── test_z3calculator.py ├── ui ├── __init__.py └── test_elk.py └── verification ├── __init__.py ├── test_checklib.py ├── test_continuous.py ├── test_modelchecking.py ├── test_pointwiseModelChecker.py ├── test_simplify.py ├── test_tctl.py └── test_verifier.py /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore ipynb files 2 | *.ipynb 3 | 4 | led / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # mxgraph 105 | mxgraph 106 | 107 | # mac stuff 108 | .DS_Store 109 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: python 4 | python: 5 | - "3.6" 6 | 7 | services: 8 | - docker 9 | 10 | # build the docker 11 | # install the codecov python tool to upload the things to the correct places 12 | before_install: 13 | - docker build -t stklik/crest . 14 | 15 | install: true # don't let pip install requirements.txt ... we did that manually in the dockerfile 16 | 17 | script: 18 | # pass travis environment info into the docker container 19 | - ci_env=`bash <(curl -s https://codecov.io/env)` 20 | # run the tests inside the docker using coverage, also print coverage 21 | - docker run $ci_env stklik/crest 22 | /bin/bash -c 'pwd;ls;python -m unittest;pip install coverage;coverage run --source crestdsl --branch -m unittest discover -v;coverage report;bash <(curl -s https://codecov.io/bash)' 23 | 24 | deploy: 25 | provider: pypi 26 | user: $PYPI_USER 27 | password: $PYPI_PASSWORD 28 | distributions: "sdist bdist_wheel" 29 | on: 30 | tags: true 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # this Dockerfile is so we can try CREST without having to install anything 2 | # we build upon a jupyter/scipy installation that was extended with z3 3 | # within this docker only fast things happen (install graphviz, copy files, do pip things) 4 | 5 | FROM jupyter/scipy-notebook:latest 6 | 7 | LABEL maintainer="Stefan Klikovits " 8 | 9 | USER root 10 | 11 | # install (patched) z3 12 | RUN git clone https://github.com/stklik/z3.git 13 | WORKDIR z3 14 | RUN python scripts/mk_make.py --python 15 | RUN cd build && make 16 | RUN cd build && make install 17 | WORKDIR .. 18 | RUN rm -rf z3 # cleanup 19 | 20 | # install graphviz and curl 21 | RUN mkdir /var/lib/apt/lists/partial && \ 22 | apt-get update && \ 23 | apt-get install -y --no-install-recommends graphviz libgraphviz-dev curl dvipng && \ 24 | apt-get clean && \ 25 | rm -rf /var/lib/apt/lists/* 26 | 27 | # install these through conda 28 | RUN conda update -n base conda 29 | RUN conda install numpy 30 | RUN conda install pandas 31 | RUN conda install matplotlib 32 | RUN conda install scipy 33 | RUN conda update networkx 34 | RUN conda install -c conda-forge importnb 35 | 36 | # let's also update everything while we're at it! 37 | RUN conda update --all 38 | 39 | RUN pip install --no-cache-dir --upgrade pip 40 | RUN pip install --no-cache-dir --upgrade importlib_resources 41 | RUN pip install --no-cache-dir --upgrade methoddispatch 42 | RUN pip install --no-cache-dir --upgrade plotly 43 | RUN pip install --no-cache-dir --upgrade cufflinks 44 | RUN pip install --no-cache-dir --upgrade astor 45 | RUN pip install --no-cache-dir --upgrade pwlf 46 | RUN pip install --no-cache-dir --upgrade graphviz pygraphviz 47 | # RUN pip install --no-cache-dir --upgrade networkx 48 | RUN pip install --no-cache-dir --upgrade colored 49 | 50 | # install jupyter extensions 51 | RUN pip install --no-cache-dir jupyter_contrib_nbextensions 52 | RUN jupyter contrib nbextension install --system 53 | 54 | RUN jupyter labextension update @jupyterlab/hub-extension 55 | RUN jupyter labextension install @jupyter-widgets/jupyterlab-manager 56 | RUN jupyter labextension update jupyterlab_bokeh 57 | 58 | RUN jupyter labextension install @jupyterlab/plotly-extension 59 | RUN jupyter labextension install jupyter-matplotlib 60 | 61 | RUN jupyter nbextension enable init_cell/main 62 | RUN jupyter nbextension enable hide_input/main 63 | RUN jupyter nbextension enable python-markdown/main 64 | 65 | RUN jupyter nbextension enable code_prettify/code_prettify 66 | RUN jupyter nbextension enable toc2/main 67 | RUN jupyter nbextension enable codefolding/main 68 | 69 | # get mxgraph into the docker 70 | # RUN git clone https://github.com/jgraph/mxgraph.git 71 | 72 | # Add Live slideshows with RISE 73 | # RUN conda install -c damianavila82 rise 74 | 75 | # copy CREST and tests into the container so we can use it 76 | COPY crestdsl ${HOME}/crestdsl/ 77 | COPY tests ${HOME}/tests/ 78 | 79 | # copy the notebooks, so we have some inital stuff 80 | COPY *.ipynb ${HOME}/ 81 | 82 | # some cleanup 83 | RUN rmdir work 84 | 85 | USER $NB_USER 86 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stefan Klikovits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include *.md 3 | include crestdsl/ui/*.js 4 | include crestdsl/ui/*.html 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | # **CREST** - A Continuous REactive SysTems DSL 7 | 8 | [![Build Status](https://travis-ci.org/crestdsl/CREST.svg?branch=master)](https://travis-ci.org/crestdsl/CREST) 9 | [![PyPI](https://img.shields.io/pypi/v/crestdsl.svg?color=blue)](https://pypi.org/project/crestdsl/) 10 | [![Documentation Status](https://readthedocs.org/projects/crestdsl/badge/?version=latest)](https://crestdsl.readthedocs.io/?badge=latest) 11 | [![codecov](https://codecov.io/gh/crestdsl/CREST/branch/master/graph/badge.svg)](https://codecov.io/gh/crestdsl/CREST) 12 | (I know, I know, I'm really busy though...) 13 | 14 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/crestdsl/CREST/master) 15 | <-- Launch this repository and play with CREST directly in your browser! 16 | 17 | --- 18 | 19 | ## Introduction 20 | 21 | CREST is a novel modelling language for the definition of Continuous-time, REactive SysTems. 22 | It is a domain-specific language (DSL) targets small cyber-physical systems (CPS) such as home automation systems. 23 | Specifically, it focusses on the flow and transfer of resources within a CPS. 24 | While CREST is a graphical language and its systems can be visualised as CREST diagrams, 25 | the main form of use is as internal DSL for the Python general purpose programming language. 26 | 27 | 28 | ## Try me ! 29 | 30 | CREST uses [Docker](https://www.docker.com/), [Jupyter](https://jupyter.org) notebooks and [Binder](https://mybinder.readthedocs.io/en/latest/) to create, edit and simulate interactive models online. 31 | 32 | You can try CREST yourself by clicking on [this link](https://mybinder.org/v2/gh/crestdsl/CREST/master) (or on the "launch binder" badge above). 33 | 34 | You will find several notebooks that will introduce CREST's 35 | [Syntax & Semantics](https://mybinder.org/v2/gh/crestdsl/CREST/master?filepath=Syntax-Semantics.ipynb) and [Simulation](https://mybinder.org/v2/gh/stklik/CREST/master?filepath=Simulation.ipynb). 36 | You can also just launch the docker container on binder (click the badge) and create a new notebook. 37 | You can then create and simulate your own models. 38 | 39 | 40 | ## Installation 41 | 42 | **Recommended:** Download/clone this repository and use the sources. 43 | The easiest way is to use the latest version of CREST is to either launch it on Binder (see above), 44 | or create a local Docker image (`scripts/docker-build.sh`) and then run it (`scripts/docker-run.sh`). 45 | Alternatively you can use [`repo2docker`](https://github.com/jupyter/repo2docker). 46 | 47 | **Local install:** You can also use CREST locally and install the dependencies manually. See the [Dockerfile](./Dockerfile) for information about the tools and libraries that are used. CREST also requires Microsoft's [Z3Prover](https://github.com/Z3Prover) to be installed (including the Python API). 48 | 49 | **pip** If you have an installation of Z3 ready, you can install with `pip install crestdsl`. 50 | 51 | 52 | ## Contribute 53 | 54 | Please let me know if you want to contribute, then I'll create a proper description here. 55 | For now, its some information for myself so I don't forget. 56 | 57 | **Auto-Build** There are several "magic" things going on here. 58 | First, travis builds every push and runs the (smoke-) tests to make sure we're not failing too hard. 59 | The tests also trigger a code coverage report. 60 | 61 | **Auto-Deploy** Deployments happen automatically on every GitHub release. All you have to do is write a nice description and it will automatically try to deploy to pypi. 62 | **REMEMBER TO BUMP THE VERSION IN setup.py!!!!** 63 | 64 | **Auto-DockerBuild** The docker image should be built automatically every night from the latest pip-deployed crestdsl version. 65 | This makes sure that the latest package-dependencies are taken into account. Its probably not the smartest idea, since these dependencies can break things, but for now it works. Once we have more users, we can maybe create nightly-versions and stable releases. But that's stuff for the future. 66 | 67 | 68 | --- 69 | 70 | ## Publications 71 | 72 |
73 | 74 | Stefan Klikovits, Auélien Coet and Didier Buchs: 75 | ML4CREST: Machine Learning for CPS Models . 76 | 2nd International Workshop on Model Driven Engineering for the Internet-of-Things (MDE4IOT), Copenhagen, 2018 77 | 78 |
 79 | @InProceedings{Klikovits:MDE4IOT:ML4CREST,
 80 |     title = {{ML4CREST}: Machine Learning for CPS Models},
 81 |     author = {Stefan Klikovits and Aur\'{e}lien Coet and Didier Buchs},
 82 |     booktitle = {2nd International Workshop on Model Driven Engineering for the Internet-of-Things (MDE4IOT), Copenhagen, Denmark, October 15, 2018. Proceedings},
 83 |     year = {2018},
 84 | }
 85 |     
86 |
87 | 88 |
89 | 90 | Stefan Klikovits, Alban Linard and Didier Buchs: 91 | CREST - A DSL for Reactive Cyber-Physical Systems. 92 | 10th System Analysis and Modeling Conference (SAM) 2018 93 | 94 |
 95 | @InProceedings{Klikovits:SAM18:CREST,
 96 |     title = {{CREST} - A {DSL} for Reactive Cyber-Physical Systems},
 97 |     author = {Stefan Klikovits and Alban Linard and Didier Buchs},
 98 |     booktitle = {10th International System Analysis and Modeling Conference (SAM 2018), Copenhagen, Denmark, October 15-16, 2018. Proceedings},
 99 |     year = {2018},
100 |     pages = {29-45},
101 |     isbn = {978-3-030-01041-6}
102 | }
103 |     
104 |
105 | 106 |
107 | 108 | Stefan Klikovits, Alban Linard, and Didier Buchs: 109 | CREST Formalization. 110 | Technical Report. Software Modeling and Verification Group, University of Geneva. 2018 111 | 112 |
113 | @techreport{Klikovits:CRESTFormalization:2018,
114 |     author = {Stefan Klikovits and Alban Linard and Didier Buchs},
115 |     title = {{CREST} Formalization},
116 |     institution = {Software Modeling and Verification Group, University of Geneva},
117 |     doi = {10.5281/zenodo.1284561},
118 |     year = {2018}
119 | }
120 | 
121 |
122 | 123 | 124 |
125 | 126 | Stefan Klikovits, Alban Linard, Didier Buchs: 127 | CREST - A Continuous, REactive SysTems DSL. 128 | MODELS (Satellite Events) 2017: 286-291 129 | 130 |
131 | @inproceedings{Klikovits:CREST:Gemoc:2017,
132 |   author    = {Stefan Klikovits and
133 |                Alban Linard and
134 |                Didier Buchs},
135 |   title     = {{CREST} - {A} Continuous, REactive SysTems {DSL}},
136 |   booktitle = {Proceedings of {MODELS} 2017 Satellite Event: Workshops (ModComp,
137 |                ME, EXE, COMMitMDE, MRT, MULTI, GEMOC, MoDeVVa, MDETools, FlexMDE,
138 |                MDEbug), Posters, Doctoral Symposium, Educator Symposium, {ACM} Student
139 |                Research Competition, and Tools and Demonstrations co-located with
140 |                {ACM/IEEE} 20th International Conference on Model Driven Engineering
141 |                Languages and Systems {(MODELS} 2017), Austin, TX, USA, September,
142 |                17, 2017.},
143 |   pages     = {286--291},
144 |   year      = {2017},
145 |   url       = {http://ceur-ws.org/Vol-2019/gemoc\_2.pdf}
146 | }
147 | 
148 |
149 | 150 | --- 151 | 152 | ### Thanks 153 | * to Prof. Didier Buchs and the University of Geneva or enabling me to do this research project 154 | * to the [Jupyterhub](https://github.com/orgs/jupyterhub/people) and [Binder](https://mybinder.org) teams for providing their amazing service 155 | -------------------------------------------------------------------------------- /crestdsl/__init__.py: -------------------------------------------------------------------------------- 1 | name = "crestdsl" 2 | -------------------------------------------------------------------------------- /crestdsl/caching.py: -------------------------------------------------------------------------------- 1 | import crestdsl.model as model 2 | import functools 3 | 4 | class Cache(object): 5 | """ 6 | Create a cache for certain model API functions, so that we can work a bit faster. 7 | Remember to activate/deactivate. 8 | """ 9 | 10 | def __init__(self): 11 | # save originals 12 | self.orig_get_entities = model.get_entities 13 | self.orig_get_all_entities = model.get_all_entities 14 | self.orig_get_all_ports = model.get_all_ports 15 | self.orig_get_by_klass = model.entity.get_by_klass 16 | 17 | self._created = False 18 | 19 | def __enter__(self): 20 | self._cache = Cache() 21 | self._cache.activate() 22 | return self._cache 23 | 24 | def __exit__(self, type, value, traceback): 25 | self._cache.deactivate() 26 | 27 | def create_cached(self): 28 | def create_cached_function(func): 29 | @functools.lru_cache(maxsize=1024) 30 | def new_func(entity, func=func): 31 | # print("cache miss", func, entity) 32 | return func(entity) 33 | return new_func 34 | 35 | def create_cached_get_by_klass(func): 36 | @functools.lru_cache(maxsize=1024) 37 | def new_func(class_or_entity, klass, as_dict=False, func=func): 38 | # print("cache miss", func, class_or_entity, klass, as_dict) 39 | return func(class_or_entity, klass, as_dict) 40 | return new_func 41 | 42 | self.cached_get_entities = create_cached_function(model.get_entities) 43 | self.cached_get_all_entities = create_cached_function(model.get_all_entities) 44 | self.cached_get_all_ports = create_cached_function(model.get_all_ports) 45 | self.cached_entity_get_by_klass = create_cached_get_by_klass(model.entity.get_by_klass) 46 | 47 | self._created = True 48 | 49 | def activate(self): 50 | if not self._created: 51 | self.create_cached() 52 | 53 | # replace originals 54 | model.get_entities = self.cached_get_entities 55 | model.get_all_entities = self.cached_get_all_entities 56 | model.get_all_ports = self.cached_get_all_ports 57 | model.entity.get_by_klass = self.cached_entity_get_by_klass 58 | 59 | def deactivate(self): 60 | # reset to orginals 61 | model.get_entities = self.orig_get_entities 62 | model.get_all_entities = self.orig_get_all_entities 63 | model.get_all_ports = self.orig_get_all_ports 64 | model.entity.get_by_klass = self.orig_get_by_klass 65 | -------------------------------------------------------------------------------- /crestdsl/config.py: -------------------------------------------------------------------------------- 1 | import logging # basic loggingconfig 2 | logging.basicConfig(level=logging.WARNING) 3 | logger = logging.getLogger(__name__) 4 | 5 | # only export config, by default 6 | __all__ = ['config'] 7 | 8 | class ConfigObject(object): 9 | """ 10 | An object that holds various configuration settings that 11 | are used throughout crestdsl. 12 | """ 13 | 14 | approx: int = 100 15 | """precision for when we have to round z3's fractions to actual numbers""" 16 | use_integer_and_real:bool = True 17 | """Should we replace Python's int and float with Z3's Integer and Real for simulation?""" 18 | epsilon:float = 10 ** -10 19 | """ When converting from infinitesimal values to actual float numbers, 20 | what value should epsilon take? (Should be very small)""" 21 | ui_display_round:int = 4 22 | """When showing numbers in plots, to what precision should they be rounded?""" 23 | 24 | record_traces:bool = True 25 | """Should the simulator record traces by default? 26 | (needs a small amount of more performance & memory)""" 27 | 28 | consider_behaviour_changes: bool = True 29 | """This should be True if you're using conditinoal statements 30 | in your updates/influences/guards, etc.""" 31 | 32 | remove_epsilon_transition_after: int = 5 33 | """If we detect the same transition doing lots of epsilon steps, 34 | we block it after this many detections.""" 35 | 36 | plotformat: str = 'png' 37 | """When we're in commandline and plotting graphviz to a file, we'll use this format. 38 | Possible output values are listed here: https://www.graphviz.org/doc/info/output.html. 39 | """ 40 | 41 | def __init__(self): 42 | # z3 Definitions 43 | self._default_plotter = None 44 | 45 | 46 | @property 47 | def interactive(self): 48 | """This value tells us if we're running in Jupyter or not.""" 49 | try: 50 | __IPYTHON__ 51 | return True 52 | except NameError: 53 | return False 54 | 55 | """ simulator """ 56 | 57 | @property 58 | def default_plotter(self): 59 | """The default plotting library used. 60 | 61 | What is the default plotting output library. 62 | Currently the choice is between elk and graphviz dot 63 | """ 64 | if self._default_plotter is not None: 65 | return self._default_plotter 66 | if self.interactive: 67 | from crestdsl.ui import elk 68 | return elk 69 | else: 70 | from crestdsl.ui import dotter 71 | return dotter 72 | 73 | @default_plotter.setter 74 | def set_default_plotter(self, plotter): 75 | self._default_plotter = plotter 76 | 77 | 78 | config: ConfigObject = ConfigObject() 79 | """A global singleton object that holds various settings, 80 | e.g. for global choice of the default plotting library or the rounding precision of the output values. 81 | Import it with ``from crestdsl.config import config`` to access the properties. 82 | 83 | .. seealso:: :class:`crestdsl.config.ConfigObject` 84 | """ 85 | 86 | 87 | 88 | 89 | import numbers 90 | try: 91 | import z3 92 | except ModuleNotFoundError: 93 | logger.warning("There was a problem when importing z3. Please make sure it is correctly installed") 94 | 95 | def to_python(some_number): 96 | if isinstance(some_number, numbers.Number): 97 | return some_number 98 | 99 | some_number = z3.simplify(some_number) 100 | if z3.is_algebraic_value(some_number): 101 | some_number = some_number.approx(config.approx) 102 | 103 | if z3.is_int_value(some_number): 104 | return some_number.as_long() 105 | else: 106 | try: 107 | return float(some_number.numerator_as_long()) / float(some_number.denominator_as_long()) 108 | except Exception: 109 | return str(some_number) # last resort, return as string 110 | -------------------------------------------------------------------------------- /crestdsl/ml/__init__.py: -------------------------------------------------------------------------------- 1 | from .decorators import learn 2 | from .functionlearn import LearnedFunction 3 | from .learners import FunctionLearner, LinearRegressionLearner, PiecewiseLinearRegressionLearner 4 | -------------------------------------------------------------------------------- /crestdsl/ml/decorators.py: -------------------------------------------------------------------------------- 1 | from . import learners 2 | from . import functionlearn as fl 3 | import crestdsl.model as crest 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | 10 | def get_learner_from_string(learner_str): 11 | 12 | learner = { 13 | 'linear': learners.LinearRegressionLearner, 14 | 'piecewise': learners.PiecewiseLinearRegressionLearner, 15 | 'fastpiecewise': learners.FastPiecewiseLinearRegressionLearner, 16 | }.get(learner_str.lower(), None) 17 | 18 | if learner is None: 19 | raise ValueError(f"'{learner_str}' is not registered. Please check the spelling or pass a FunctionLearner class or object." ) 20 | return learner 21 | 22 | 23 | 24 | def learn(**kwargs): 25 | # if a string is provided, try to convert it to a Learner 26 | if isinstance(kwargs.get("learner", None), str): 27 | kwargs["learner"] = get_learner_from_string(kwargs.get("learner")) 28 | 29 | def decorator(modifier): 30 | if isinstance(modifier, crest.Transition): 31 | modifier.guard = fl.LearnedFromFunction(modifier, modifier.guard, **kwargs) 32 | else: 33 | modifier.function = fl.LearnedFromFunction(modifier, modifier.function, **kwargs) 34 | return modifier 35 | return decorator 36 | -------------------------------------------------------------------------------- /crestdsl/model/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: make this more beautiful by using the __all__ in the entity module, 2 | # rather than importing everything here 3 | 4 | from .datatypes import Types, INT, INTEGER, FLOAT, REAL, STRING, BOOL 5 | from .model import State, Transition, Update, Influence, Action, Dependency 6 | from .decorators import update, influence, transition, action, dependency, nodependencies 7 | from .ports import Port, Input, Output, Local 8 | from .resource import Resource 9 | from .entity import Entity, LogicalEntity, \ 10 | get_path_to_attribute, \ 11 | get_all_entities, get_all_influences, get_all_updates, get_all_ports, \ 12 | get_all_states, get_all_transitions, get_all_crest_objects, get_all_actions, \ 13 | get_entities, get_states, get_inputs, get_outputs, get_locals, get_ports, get_actions, \ 14 | get_updates, get_transitions, get_influences, get_dependencies, \ 15 | get_crest_objects, get_equivalent_in_system 16 | 17 | from .systemcheck import SystemCheck 18 | -------------------------------------------------------------------------------- /crestdsl/model/api/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: make this more beautiful by using the __all__ in the entity module, 2 | # rather than importing everything here 3 | 4 | from .api import get_parent, get_name, get_current, get_root, get_children, get_sources, get_targets 5 | from .convenienceAPI import pullup, relay, add, dependencies 6 | 7 | -------------------------------------------------------------------------------- /crestdsl/model/api/api.py: -------------------------------------------------------------------------------- 1 | import crestdsl.model as crest 2 | import crestdsl.model.meta as meta 3 | 4 | import operator 5 | 6 | def get_parent(entity): 7 | """ 8 | Returns the parent of a given entity. 9 | 10 | Parameters 11 | ---------- 12 | entity: Entity 13 | The entity whose parent should be returned. 14 | 15 | Returns 16 | ---------- 17 | Entity 18 | The entity's parent entity (or None). 19 | """ 20 | return getattr(entity, meta.PARENT_IDENTIFIER, None) 21 | 22 | 23 | def get_name(entity): 24 | """ 25 | Returns the name of a given entity. 26 | 27 | Parameters 28 | ---------- 29 | entity: Entity 30 | The entity whose name should be returned. 31 | 32 | Returns 33 | ---------- 34 | str 35 | The entity's name or the empty string if there is no name defined (usually the root). 36 | """ 37 | return getattr(entity, meta.NAME_IDENTIFIER, None) 38 | 39 | 40 | def get_current(entity): 41 | """ 42 | Returns the current automaton state of a given entity. 43 | 44 | Parameters 45 | ---------- 46 | entity: Entity 47 | The entity whose current state should be accessed. 48 | 49 | Returns 50 | ---------- 51 | State 52 | The entity's current automaton state. 53 | """ 54 | return getattr(entity, meta.CURRENT_IDENTIFIER, None) 55 | 56 | 57 | def get_root(entity): 58 | """ 59 | Returns the root entity of a system. 60 | 61 | Parameters 62 | ---------- 63 | entity: Entity 64 | Any entity within the system. 65 | 66 | Returns 67 | ---------- 68 | Entity 69 | The system's root entity. 70 | """ 71 | parent = get_parent(entity) 72 | if parent: 73 | return get_root(parent) 74 | return entity 75 | 76 | 77 | def get_children(entity): 78 | """ 79 | Returns the child entities of a given entity. 80 | 81 | Parameters 82 | ---------- 83 | entity: Entity 84 | The entity whose children should be returned. 85 | 86 | Returns 87 | ---------- 88 | list of Entity 89 | A list of the entity's subentities. 90 | """ 91 | return crest.get_entities(entity) 92 | 93 | 94 | def get_sources(entity): 95 | """ 96 | The "sources" ports of an entity. 97 | The sources ports are all ports that can be read by updates/transitions/influences. 98 | These are an entity's inputs, locals and all subentities' output ports. 99 | 100 | Parameters 101 | ---------- 102 | entity: Entity 103 | The entity whose sources should be returned. 104 | 105 | Returns 106 | ---------- 107 | list of Port 108 | The list of ports that can be read by modifiers. 109 | """ 110 | return crest.get_inputs(entity) + crest.get_locals(entity) + [o for e in crest.get_entities(entity) for o in crest.get_outputs(e)] 111 | 112 | 113 | def get_targets(entity): 114 | """ 115 | The "targets" ports of an entity. 116 | The targets ports are all ports that can be written by updates and influences. 117 | These are an entity's outputs, locals and all subentities' input ports. 118 | 119 | Parameters 120 | ---------- 121 | entity: Entity 122 | The entity whose targets should be returned. 123 | 124 | Returns 125 | ---------- 126 | list of Port 127 | The list of ports that can be written by modifiers. 128 | """ 129 | return crest.get_outputs(entity) + crest.get_locals(entity) + [i for e in crest.get_entities(entity) for i in crest.get_inputs(e)] 130 | -------------------------------------------------------------------------------- /crestdsl/model/datatypes.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Types(Enum): 5 | """ An enum holding all datatypes. 6 | Sometimes it's better to use ``Types.INT`` to clairfy that you're using a type. 7 | It might also resolve ambiguities 8 | """ 9 | INT = int 10 | FLOAT = float 11 | INTEGER = "integer" 12 | REAL = "real" 13 | STRING = str 14 | BOOL = bool 15 | 16 | 17 | # this is shorter to use! 18 | INT = Types.INT 19 | """ 20 | Datatype for int values (computer floats). 21 | This will be translated to the SMT solvers 32 bit Int theory. 22 | """ 23 | 24 | FLOAT = Types.FLOAT 25 | """ 26 | Datatype for floating point values (computer floats). 27 | This will be translated to the SMT solvers 32bit Float theory. 28 | """ 29 | 30 | INTEGER = Types.INTEGER 31 | """ 32 | Datatype for integer values. (Actual integers, not computer int). 33 | This will be translated to the SMT solvers Integer theory. 34 | """ 35 | 36 | REAL = Types.REAL 37 | """ 38 | Datatype for real values. (Actual real, not computer floating points). 39 | This will be translated to the SMT solvers Real theory. 40 | """ 41 | 42 | STRING = Types.STRING 43 | """ 44 | Datatype for character sequences. 45 | """ 46 | 47 | BOOL = Types.BOOL 48 | """ 49 | Datatype for boolean values. 50 | """ 51 | -------------------------------------------------------------------------------- /crestdsl/model/decorators.py: -------------------------------------------------------------------------------- 1 | from . import model 2 | from . import meta 3 | from .entity import MetaEntity 4 | 5 | def transition(source="", target=""): 6 | """ 7 | A decorator for the definition of a Transition. 8 | Decorate any method to make it a guarded transition. 9 | 10 | Don't forget to specify the source and target states. 11 | 12 | Parameters 13 | ---------- 14 | source: State or str or list 15 | The starting state of a transition. Either a state object, or the name (str) of it. 16 | target: State or str or list 17 | The target state of a transition. Either a state object, or the name (str) of it. 18 | """ 19 | def decorator(guard): 20 | return model.Transition(source=source, target=target, guard=guard) 21 | return decorator 22 | 23 | 24 | def influence(source="", target=""): 25 | """ 26 | A decorator for the definition of an Influence. 27 | Decorate any method to make it an influence between two ports. 28 | 29 | Don't forget to specify the source and target ports. 30 | 31 | Parameters 32 | ---------- 33 | source: Port or str 34 | The starting port of the influence. 35 | Either a port object, or the name (str) of it. 36 | target: Port or str 37 | The target port of the influence. 38 | Either a port object, or the name (str) of it. 39 | """ 40 | def decorator(function=None): 41 | return model.Influence(source=source, target=target, function=function) 42 | return decorator 43 | 44 | 45 | def update(*args, **kwargs): 46 | """ 47 | A decorator for the definition of an Update. 48 | Decorate any method to make it an update function. 49 | 50 | Don't forget to specify the state and target port. 51 | 52 | Parameters 53 | ---------- 54 | source: State or str 55 | The state in which the update should be executed. 56 | Either a state object, or the name (str) of it. 57 | target: Port or str 58 | The target port of the update. 59 | Either a port object, or the name (str) of it. 60 | """ 61 | def _update(func): 62 | return model.Update(function=func, state=state, target=target) 63 | if len(args) == 2 and callable(args[0]): 64 | # No arguments, this is the decorator 65 | # Set default values for the arguments 66 | state = None 67 | target = None 68 | return _update(args[0]) 69 | else: 70 | # This is just returning the decorator 71 | state = kwargs["state"] 72 | target = kwargs["target"] 73 | return _update 74 | 75 | 76 | def action(*args, **kwargs): 77 | """ 78 | A decorator for the definition of an Action. 79 | Decorate any method to make it an action function. 80 | 81 | Don't forget to specify the transition and target port. 82 | 83 | Parameters 84 | ---------- 85 | source: Transition or str 86 | The transition at which the update should be triggered. Either a transition object, or the name (str) of it. 87 | target: Port or str 88 | The target port of the action. Either a port object, or the name (str) of it. 89 | """ 90 | def _action(func): 91 | return model.Action(function=func, transition=transition, target=target) 92 | if len(args) == 2 and callable(args[0]): 93 | # No arguments, this is the decorator 94 | # Set default values for the arguments 95 | transition = None 96 | target = None 97 | return _action(args[0]) 98 | else: 99 | # This is just returning the decorator 100 | transition = kwargs["transition"] 101 | target = kwargs["target"] 102 | return _action 103 | 104 | 105 | def nodependencies(cls): 106 | """ 107 | A class-decorator to declare that a class' outputs don't depend in its inputs. 108 | 109 | No params! 110 | """ 111 | if not isinstance(cls, MetaEntity): 112 | raise ValueError(f"Error. You need to call this decorator on an Entity-class.") 113 | 114 | if not hasattr(cls, meta.DEPENDENCY_IDENTIFIER): 115 | setattr(cls, meta.DEPENDENCY_IDENTIFIER, []) 116 | return cls 117 | 118 | 119 | class dependency(object): 120 | """ 121 | A class-decorator to declare that a class's output depends on an input. 122 | This is necessary to resolve circular dependencies. 123 | 124 | Parameters 125 | ---------- 126 | source: Output or str (output portname) 127 | The dependency source (i.e. the output port). 128 | target: Input or str (input portname) 129 | The dependency target (i.e. the input port). 130 | """ 131 | def __init__(self, source, target): 132 | self.source = source 133 | self.target = target 134 | 135 | def __call__(self, cls): 136 | new_dependency = model.Dependency(self.source, self.target) 137 | if hasattr(cls, meta.DEPENDENCY_IDENTIFIER): 138 | getattr(cls, meta.DEPENDENCY_IDENTIFIER).append(new_dependency) 139 | else: 140 | setattr(cls, meta.DEPENDENCY_IDENTIFIER, [new_dependency]) 141 | 142 | return cls 143 | -------------------------------------------------------------------------------- /crestdsl/model/meta.py: -------------------------------------------------------------------------------- 1 | PARENT_IDENTIFIER = "_parent" 2 | CURRENT_IDENTIFIER = "current" 3 | NAME_IDENTIFIER = "_name" 4 | DEPENDENCY_IDENTIFIER = "_dependencies" 5 | 6 | import crestdsl.model.api as api 7 | 8 | class CrestObject(object): 9 | def __init__(self, name="", parent=None): 10 | setattr(self, NAME_IDENTIFIER, name) 11 | setattr(self, PARENT_IDENTIFIER, parent) 12 | 13 | def __str__(self): 14 | texts = [api.get_name(self)] 15 | parent = api.get_parent(self) 16 | 17 | while parent is not None and api.get_name(parent) is not "": 18 | texts.insert(0, api.get_name(parent)) 19 | parent = api.get_parent(parent) 20 | else: 21 | if api.get_name(self) is None or api.get_name(self) is "": 22 | texts = [self.__class__.__name__] 23 | else: 24 | texts.insert(0, parent.__class__.__name__) 25 | # print(texts) 26 | return ".".join(texts) 27 | 28 | 29 | class CrestList(list): 30 | """ a special class that's produced when using an annotation with several states (transitions, updates)""" 31 | @staticmethod 32 | def fromlist(listt): 33 | new = CrestList() 34 | new.extend(listt) 35 | return new 36 | -------------------------------------------------------------------------------- /crestdsl/model/ports.py: -------------------------------------------------------------------------------- 1 | from . import meta 2 | from . import api 3 | # from .resource import Resource 4 | # from weakref import WeakKeyDictionary 5 | 6 | """ PORTS """ 7 | class Port(meta.CrestObject): 8 | def __init__(self, resource=None, value=None, name=None, parent=None): 9 | """ 10 | Parameters 11 | ---------- 12 | resource : Resource 13 | A resource that specifies the types of values the port can take. 14 | value : object 15 | An initial value that matches the resource. 16 | """ 17 | self.resource = resource 18 | self._value = value # initialise value 19 | 20 | setattr(self, meta.NAME_IDENTIFIER, name) 21 | setattr(self, meta.PARENT_IDENTIFIER, parent) 22 | 23 | @property 24 | def value(self): 25 | return self._value 26 | 27 | @value.setter 28 | def value(self, val): 29 | if not self.resource.check_value(val): 30 | raise AssertionError(f"Cannot assign {val} to resource of domain {self.resource.domain}") 31 | self._value = val 32 | 33 | def to_plotly_json(self): 34 | return api.get_name(self) 35 | 36 | # def __str__(self): 37 | # return super().__str__() + f": {self.value}({str(self.resource)})" 38 | 39 | class Input(Port): 40 | """ 41 | An input port of an entity. 42 | Its value can only be read from inside the entity and set only from the outside. 43 | This means you should never write an input from inside its own entity! 44 | 45 | .. automethod:: __init__ 46 | """ 47 | pass 48 | 49 | 50 | class Output(Port): 51 | """ 52 | An output port of an entity. 53 | Its value can only be set from inside the entity and read only from the outside. 54 | This means you should never read an output from inside its own entity! 55 | 56 | .. automethod:: __init__ 57 | """ 58 | pass 59 | 60 | 61 | class Local(Port): 62 | """ 63 | A local port of an entity. 64 | Its value can only be set and read from the entity itself. 65 | 66 | .. automethod:: __init__ 67 | """ 68 | pass 69 | -------------------------------------------------------------------------------- /crestdsl/model/resource.py: -------------------------------------------------------------------------------- 1 | from .datatypes import Types 2 | import z3 3 | import numbers 4 | 5 | """ Resources """ 6 | 7 | class Resource(object): 8 | """ 9 | A resource is a value type for CREST models. 10 | Each port is initialised with a Resource that identifies the type of values it will hold. 11 | Note, that resouce objects should be centrally defined and reused, so that ports of the same resource 12 | can reference the same resource object. 13 | 14 | Resources are created with a *unit* i.e. it's name (e.g. Watt or Lumen) and a *domain*. 15 | The domain is either one of the crestdsl :doc:`Datatypes ` or a list of discrete values. 16 | """ 17 | 18 | def __init__(self, unit, domain): 19 | self.unit = unit 20 | self.domain = domain 21 | 22 | def check_value(self, val): 23 | assert val is not None, f"Setting a port's value to None is not allowed" 24 | 25 | # check that we only assign correct values to the ports 26 | if isinstance(self.domain, list): 27 | return val in self.domain 28 | elif self.domain is Types.INTEGER: 29 | return isinstance(val, int) or z3.is_int_value(val) or z3.is_int(val) 30 | elif self.domain is Types.REAL: # z3 types 31 | return (isinstance(val, numbers.Number) and not isinstance(val, bool)) or z3.is_real(val) 32 | elif self.domain is Types.INT: # TODO: check also for these types 33 | return isinstance(val, int) 34 | elif self.domain is Types.FLOAT: # TODO: check also for these types 35 | return (isinstance(val, numbers.Number) and not isinstance(val, bool)) 36 | elif self.domain is Types.STRING: # TODO: check also for these types 37 | return isinstance(val, str) 38 | elif self.domain is Types.BOOL: # TODO: check also for these types 39 | return isinstance(val, bool) 40 | else: 41 | return False 42 | 43 | def __getattr__(self, attr): 44 | if attr.startswith("__"): 45 | return super().__getattribute__(attr) 46 | 47 | if attr in ["unit", "domain"]: 48 | return super().__getattribute__(attr) 49 | elif type(self.domain) == list and attr in self.domain: 50 | return attr 51 | elif type(self.domain) == dict and attr in self.domain: 52 | return self.domain[attr] 53 | else: 54 | return super().__getattribute__(attr) 55 | 56 | def __str__(self): 57 | return self.unit 58 | -------------------------------------------------------------------------------- /crestdsl/planning/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/planning/__init__.py -------------------------------------------------------------------------------- /crestdsl/simulation/__init__.py: -------------------------------------------------------------------------------- 1 | from .simulator import Simulator 2 | from .interactivesimulator import InteractiveSimulator 3 | from .plansimulator import PlanSimulator 4 | from .epsilon import Epsilon, eps 5 | -------------------------------------------------------------------------------- /crestdsl/simulation/dependencyOrder.py: -------------------------------------------------------------------------------- 1 | # from operator import groupby 2 | import networkx as nx 3 | import crestdsl.model as crest 4 | import crestdsl.model.api as api 5 | from crestdsl import sourcehelper as SH 6 | 7 | import logging 8 | logger = logging.getLogger(__name__) 9 | 10 | MODIFIER = "modifier" 11 | 12 | def entity_modifier_graph(entity): 13 | """ 14 | Creates a bipartite, directed graph using [networkx](https://networkx.github.io/). 15 | Nodes: 16 | - ports 17 | - modifiers 18 | * influences 19 | * updates 20 | * subentities 21 | Edges: 22 | - influence.source --> influence 23 | - influence --> influence.target 24 | - accessd ports --> update 25 | - update --> update.target 26 | - subentity.input --> subentity 27 | - subentity --> subentity.output 28 | """ 29 | logger.debug(f"creating modifier graph for entity {entity._name} ({entity.__class__.__name__})") 30 | # create a bipartite graph 31 | DG = nx.DiGraph() 32 | portlist = set(api.get_sources(entity) + api.get_targets(entity)) 33 | for port in portlist: 34 | DG.add_node(id(port), port=port) 35 | 36 | for influence in crest.get_influences(entity): 37 | logger.debug(f"added node ({id(influence)}) for influence {influence._name} ({influence.source._name} -> {influence.target._name})") 38 | DG.add_node(id(influence), modifier=influence) 39 | # print(influence.source._name, "->", influence.target._name, influence._name) 40 | DG.add_edge(id(influence.source), id(influence)) 41 | DG.add_edge(id(influence), id(influence.target)) 42 | 43 | 44 | for update in crest.get_updates(entity): 45 | # if update.state is entity.current: 46 | DG.add_node(id(update), modifier=update) 47 | DG.add_edge(id(update), id(update.target)) 48 | accessed_ports = SH.get_accessed_ports(update.function, update) 49 | for accessed in accessed_ports: 50 | if accessed != update.target: 51 | # print(accessed._name, "->", update.target._name, update._name) 52 | DG.add_edge(id(accessed), id(update)) 53 | 54 | for subentity in crest.get_entities(entity): 55 | dependencies = crest.get_dependencies(subentity) 56 | logger.debug(f"Subentity {subentity._name} has the following dependencies defined: {dependencies}") 57 | if dependencies is not None: 58 | dep_ins = [] # stores the dependencies we actively treated ! 59 | for dep in dependencies: 60 | logger.debug(f"added node ({id(dep)}) for subentity dependency in {subentity._name} ({dep.source._name} -> {dep.target._name})") 61 | # if we're working with dependencies, use the dep-id as node, 62 | # so we can have multiple occurrences of the same modifier (subentity) 63 | DG.add_node(id(dep), modifier=subentity) 64 | DG.add_edge(id(dep), id(dep.source)) 65 | DG.add_edge(id(dep.target), id(dep)) 66 | dep_ins.append(dep.target) 67 | 68 | # make sure to create a subentity if there are inputs that are not mentioned in the dependencies. 69 | # if one of them is modified, we also have to re-run the subentity (to update internal locals) 70 | missing_inputs = set.difference(set(crest.get_inputs(subentity)), set(dep_ins)) 71 | if len(missing_inputs) > 0: 72 | logger.debug(f"adding links for not mentioned subentity inputs yet {missing_inputs}") 73 | DG.add_node(id(subentity), modifier=subentity) 74 | for _input in missing_inputs: 75 | if _input not in dep_ins: 76 | DG.add_edge(id(_input), id(subentity)) 77 | 78 | else: 79 | logger.debug(f"added node ({id(subentity)}) for subentity {subentity._name}") 80 | DG.add_node(id(subentity), modifier=subentity) 81 | for _in in crest.get_inputs(subentity): 82 | DG.add_edge(id(_in), id(subentity)) 83 | for _out in crest.get_outputs(subentity): 84 | DG.add_edge(id(subentity), id(_out)) 85 | return DG 86 | 87 | 88 | def plot_modifier_graph(entity, labels=True): 89 | graph = entity_modifier_graph(entity) 90 | pos = nx.nx_agraph.graphviz_layout(graph, prog="sfdp", args='-Grankdir=LR -Goverlap=false -Gsplines=true') # -Gnslimit=3 -Gnslimit1=3 91 | nx.draw(graph, pos) 92 | 93 | node_labels = {} 94 | if labels: 95 | for node in graph.nodes(): 96 | if MODIFIER in graph.node[node]: 97 | node_labels[node] = f"{graph.node[node]['modifier']._parent._name}.{graph.node[node]['modifier']._name}" 98 | else: 99 | node_labels[node] = f"{graph.node[node]['port']._parent._name}.{graph.node[node]['port']._name}" 100 | nx.draw_networkx_labels(graph, pos, labels=node_labels) 101 | 102 | def ordered_modifiers(entity): 103 | return get_entity_modifiers_in_dependency_order(entity) 104 | 105 | def get_entity_modifiers_in_dependency_order(entity): 106 | """ 107 | Uses [networkx](https://networkx.github.io/) functionality to find a linear order. 108 | The algorithm is networkx' [topoligical_sort](https://networkx.github.io/documentation/networkx-1.9/reference/generated/networkx.algorithms.dag.topological_sort.html) 109 | function. 110 | Note, that this function is right now non-deterministic, it would be better if we replace the algorithm with a deterministic one. 111 | """ 112 | # TODO: make deterministic? https://pypi.org/project/toposort/ 113 | 114 | # access the mod_graph 115 | try: 116 | orig_DG = entity._mod_graph 117 | except AttributeError as atterr: 118 | entity._mod_graph = entity_modifier_graph(entity) 119 | orig_DG = entity._mod_graph 120 | 121 | # NetworkX is slow. 122 | # Therefore, we cache if the entire graph is a DAG. 123 | # It usually is, but very rarely it isn't, 124 | # if it isn't we have to check over and over again. (performance loss) 125 | try: 126 | is_dag = orig_DG._is_dag 127 | except AttributeError: 128 | orig_DG._is_dag = nx.is_directed_acyclic_graph(entity._mod_graph) 129 | is_dag = orig_DG._is_dag 130 | 131 | # create a subgraph_view so that the inactive states are filtered 132 | def node_filter(node): 133 | obj = orig_DG.nodes[node] 134 | keep = not (MODIFIER in obj and isinstance(obj[MODIFIER], crest.Update) and obj[MODIFIER].state != api.get_current(obj[MODIFIER]._parent)) 135 | return keep 136 | 137 | DG = nx.graphviews.subgraph_view(orig_DG, filter_node=node_filter) 138 | 139 | # if the entire graph is not a DAG 140 | # and the subgraph also isn't a DAG 141 | # then find the cycle and inform the user 142 | if not is_dag and not nx.is_directed_acyclic_graph(DG): 143 | def get_text(nodetype, modifier_or_port): 144 | description = "Port" if nodetype != MODIFIER else "Entity" if isinstance(modifier_or_port, crest.Entity) else modifier_or_port.__class__.__name__ 145 | return f"{description}: {api.get_name(modifier_or_port)} ({api.get_name(api.get_parent(modifier_or_port))})" 146 | 147 | for cycle in nx.simple_cycles(DG): # then show us a cycle 148 | nodes = [] 149 | for cycle_index, node in enumerate(cycle): 150 | nodes.append("".join([get_text(nodetype, mod_or_port) for nodetype, mod_or_port in DG.nodes[node].items()])) 151 | as_text = " --> ".join(nodes[-1:]+nodes) 152 | logger.error(f"Cycle found: {as_text}") 153 | raise AssertionError("Cyclic dependencies discovered. This is not allowed in CREST.") 154 | 155 | topo_list = list(nx.topological_sort(DG)) 156 | # topo_port_list = [DG.node[node]['port'] for node in topo_list] 157 | # print([f"{port._parent._name}.{port._name}" for port in topo_port_list]) 158 | 159 | ordered_modifier_list = [] 160 | for node in topo_list: 161 | if MODIFIER in DG.nodes[node]: 162 | mod = DG.nodes[node][MODIFIER] 163 | ordered_modifier_list.append(mod) 164 | 165 | return ordered_modifier_list 166 | -------------------------------------------------------------------------------- /crestdsl/simulation/epsilon.py: -------------------------------------------------------------------------------- 1 | import numbers 2 | 3 | from crestdsl.config import config, to_python 4 | 5 | eps_string = "\u03B5" 6 | 7 | 8 | class Epsilon(numbers.Number): 9 | 10 | def __init__(self, numeric=0, epsilon=0): 11 | self.numeric = to_python(numeric) 12 | self.epsilon = to_python(epsilon) 13 | assert isinstance(self.numeric, numbers.Number) and not isinstance(self.numeric, bool) 14 | assert isinstance(self.epsilon, numbers.Number) and not isinstance(self.epsilon, bool) 15 | 16 | def __add__(self, other): 17 | if isinstance(other, Epsilon): 18 | return Epsilon(self.numeric + other.numeric, self.epsilon + other.epsilon) 19 | elif isinstance(other, numbers.Number) and not isinstance(other, bool): 20 | return Epsilon(self.numeric + other, self.epsilon) 21 | else: 22 | raise NotImplementedError(f"What?! How come we're calling this with a {type(other)}... (The value used >>{other}<<)") 23 | 24 | def __sub__(self, other): 25 | return self + -other 26 | 27 | def __radd__(self, other): 28 | return self + other 29 | 30 | def __rsub__(self, other): 31 | return -self + other 32 | 33 | def __neg__(self): 34 | return Epsilon(-self.numeric, -self.epsilon) 35 | 36 | def __abs__(self): 37 | if self < 0: 38 | return -self 39 | else: 40 | return Epsilon(self.numeric, self.epsilon) 41 | 42 | def __eq__(self, other): 43 | if isinstance(other, str): 44 | return False # Pandas needs this to return a value... 45 | if isinstance(other, Epsilon): 46 | return self.numeric == other.numeric and self.epsilon == other.epsilon 47 | elif isinstance(other, numbers.Number) and not isinstance(other, bool): 48 | return self.numeric == other and self.epsilon == 0 49 | else: 50 | raise NotImplementedError(f"What?! How come we're calling this with a {type(other)}... (The value used >>{other}<<)") 51 | 52 | def __ne__(self, other): 53 | return not self.__eq__(other) 54 | 55 | def __lt__(self, other): 56 | if isinstance(other, Epsilon): 57 | if self.numeric == other.numeric: 58 | return self.epsilon < other.epsilon 59 | if self.numeric < other.numeric: 60 | return True 61 | return False # numeric is greater 62 | elif isinstance(other, numbers.Number) and not isinstance(other, bool): 63 | return self < Epsilon(numeric=other) 64 | else: 65 | raise NotImplementedError(f"What?! How come we're calling this with a {type(other)}... (The value used >>{other}<<)") 66 | 67 | def __le__(self, other): 68 | return self == other or self < other 69 | 70 | def __ge__(self, other): 71 | return self == other or not (self < other) 72 | 73 | def __gt__(self, other): 74 | return (not self == other) and (not self < other) 75 | 76 | def __str__(self): 77 | n = self.numeric 78 | e = eps_string 79 | if self.epsilon == 0: 80 | return f"{n}" 81 | 82 | sign = "+" if self.epsilon > 0 else "-" 83 | times_e = f"{e}" if abs(self.epsilon) == 1 else f"{abs(self.epsilon)} * {e}" 84 | 85 | if self.numeric == 0 and self.epsilon > 0: 86 | return f"{times_e}" 87 | if self.numeric == 0: 88 | return f"{sign}{times_e}" 89 | else: 90 | return f"{n} {sign} {times_e}" 91 | 92 | def __repr__(self): 93 | return f"Epsilon(numeric={self.numeric}, epsilon={self.epsilon})" 94 | 95 | def to_number(self, eps_value=config.epsilon): 96 | return self.numeric + eps_value * self.epsilon 97 | 98 | def to_plotly_json(self): 99 | return self.to_number() 100 | 101 | 102 | eps = Epsilon(epsilon=1) 103 | -------------------------------------------------------------------------------- /crestdsl/simulation/fastconditiontimedchangecalculator.py: -------------------------------------------------------------------------------- 1 | from crestdsl import model 2 | from crestdsl import sourcehelper as SH 3 | import ast 4 | from .to_z3 import Z3Converter, get_z3_variable, get_z3_var, get_z3_value, get_minimum_dt_of_several 5 | from .z3conditionchangecalculator import Z3ConditionChangeCalculator, get_behaviour_change_dt_from_constraintset 6 | from .z3calculator import Z3Calculator 7 | from .conditiontimedchangecalculator import ConditionTimedChangeCalculator 8 | from .contextconditiontimedchangecalculator import ContextConditionTimedChangeCalculator, translate_to_context 9 | 10 | import multiprocessing 11 | import queue 12 | import threading 13 | 14 | 15 | import z3 16 | from crestdsl.config import to_python 17 | from .epsilon import Epsilon, eps_string 18 | 19 | from types import SimpleNamespace 20 | 21 | import logging 22 | logger = logging.getLogger(__name__) 23 | 24 | 25 | # TODO: extract this function and put in a central place 26 | # also do this in other files 27 | def log_if_level(level, message): 28 | """Logs the message if the level is below the specified level""" 29 | if logger.getEffectiveLevel() <= level: 30 | logger.log(level, message) 31 | 32 | class FastConditionTimedChangeCalculator(ContextConditionTimedChangeCalculator): 33 | """ 34 | THIS CLASS IS NEITHER FAST, NOR USEFUL. 35 | DELETE IT !!! 36 | """ 37 | 38 | def calculate_system(self, entity=None, include_subentities=True): 39 | logger.debug("FAST: Calculating for all entities") 40 | if not hasattr(self, "cache"): 41 | self.init_z3_constraints_and_vars() 42 | all_dts = [] 43 | 44 | """ setup workers with own context for each """ 45 | job_queue = queue.Queue() 46 | num_threads = 4 47 | 48 | thread_workers = [] 49 | for i in range(num_threads): 50 | new_cache = translate_to_context(self.cache, z3.Context()) 51 | thread_worker = threading.Thread(target=self.thread_crawler, args=(job_queue,new_cache,all_dts)) 52 | thread_worker.setDaemon(True) 53 | thread_worker.start() 54 | 55 | """ Fill queue with stuff to do """ 56 | # TODO: check for transitions whether they can be done by time only 57 | for trans in model.get_all_transitions(entity): 58 | if trans._parent.current is trans.source: 59 | job_queue.put( (self.get_transition_time, trans) ) 60 | 61 | for influence in model.get_all_influences(entity): 62 | if self.contains_if_condition(influence): 63 | job_queue.put( (self.get_condition_change_enablers, influence) ) 64 | 65 | 66 | # updates = [up for up in get_updates(self.entity) if up.state == up._parent.current] 67 | for update in model.get_all_updates(entity): 68 | if update.state is update._parent.current: # only the currently active updates 69 | if self.contains_if_condition(update): 70 | job_queue.put( (self.get_condition_change_enablers, update) ) 71 | 72 | """ wait for queue to finish """ 73 | job_queue.join() 74 | 75 | for tw in thread_workers: 76 | assert not tw.isAlive() 77 | 78 | 79 | # """ do the other things """ 80 | 81 | # workers = [] 82 | # for influence in model.get_all_influences(entity): 83 | # if self.contains_if_condition(influence): 84 | # ctx_i = z3.Context() 85 | # new_cache = translate_to_context(self.cache, ctx_i) 86 | # worker = threading.Thread(target=self.get_condition_change_enablers, args=(influence, all_dts, new_cache)) 87 | # worker.start() 88 | # workers.append(worker) 89 | # worker.join() 90 | # 91 | # 92 | # # updates = [up for up in get_updates(self.entity) if up.state == up._parent.current] 93 | # for update in model.get_all_updates(entity): 94 | # if update.state is update._parent.current: # only the currently active updates 95 | # if self.contains_if_condition(update): 96 | # ctx_i = z3.Context() 97 | # new_cache = translate_to_context(self.cache, ctx_i) 98 | # worker = threading.Thread(target=self.get_condition_change_enablers, args=(update, all_dts, new_cache)) 99 | # worker.start() 100 | # workers.append(worker) 101 | # worker.join() 102 | # 103 | # # TODO: check for transitions whether they can be done by time only 104 | # for trans in model.get_all_transitions(entity): 105 | # if trans._parent.current is trans.source: 106 | # ctx_i = z3.Context() 107 | # new_cache = translate_to_context(self.cache, ctx_i) 108 | # worker = threading.Thread(target=self.get_transition_time, args=(trans, all_dts, new_cache)) 109 | # worker.start() 110 | # workers.append(worker) 111 | # worker.join() 112 | 113 | # print(f"Working on {len(workers)} threads") 114 | # for worker in workers: 115 | # worker.join() 116 | 117 | """ Return all behaviour change times """ 118 | return all_dts 119 | 120 | def thread_crawler(self, job_queue, cache, all_dts): 121 | """ worker thread that one by one executes an SMT task in its context """ 122 | 123 | while True: 124 | (method, modif) = job_queue.get() 125 | method(modif, all_dts, cache) 126 | job_queue.task_done() 127 | 128 | return True 129 | -------------------------------------------------------------------------------- /crestdsl/simulation/interactivesimulator.py: -------------------------------------------------------------------------------- 1 | from crestdsl.model import * # bad practice, but used for the evaluation of commands 2 | from .simulator import Simulator 3 | 4 | import logging 5 | logger = logging.getLogger(__name__) 6 | 7 | import io 8 | try: 9 | import colored 10 | from colored import stylize 11 | color_enabled = True 12 | except ImportError: 13 | color_enabled = False 14 | except io.UnsupportedOperation: 15 | color_enabled = False 16 | logger.error("There is an error in the 'colored' package. They use 'fileno'. I guess we have to wait for a fix.") 17 | import random 18 | import sys 19 | 20 | class InteractiveSimulator(Simulator): 21 | """ 22 | This is a simulator will stop every time two transitions are enabled 23 | in the same entity at the same time and prompt the user for what to do. 24 | 25 | Next to choosing a transition,uUsers can perform various actions 26 | (e.g. inspect variables, plot the system or stop the simulation. 27 | """ 28 | 29 | 30 | def select_transition_to_trigger(self, entity): 31 | """ Override the (random) transition selection procedure. This one asks the user for input.""" 32 | transitions_from_current_state = [t for t in get_transitions(entity) if t.source is entity.current] 33 | enabled_transitions = [t for t in transitions_from_current_state if self._get_transition_guard_value(t)] 34 | 35 | if len(enabled_transitions) == 1: 36 | return enabled_transitions[0] 37 | elif len(enabled_transitions) > 1: 38 | if color_enabled: 39 | return self.prompt_transition_selection(entity, enabled_transitions) 40 | else: 41 | return self.prompt_transition_selection_no_colored(entity, enabled_transitions) 42 | else: 43 | return None 44 | 45 | def prompt_transition_selection_no_colored(self, entity, enabled_transitions): 46 | pad = 1 if len(enabled_transitions) <= 10 else 2 47 | transitions_texts = [idx.rjust(pad) + f" ... {trans._name} (transition to '{trans.target._name}')" for idx, trans in enumerate(enabled_transitions)] 48 | transitions_list = "\n".join(transitions_texts) 49 | 50 | longtext = f""" 51 | Non-Determinism detected 52 | There are multiple enabled transitions in entity: {str(entity)} 53 | (Current time: {self.global_time} -- Current automaton state: {entity.current._name}) 54 | 55 | Choose one of the following transitions by entering the according number: 56 | {transitions_list} 57 | 58 | Other commands: 59 | r ... choose a transition randomly 60 | p ... plot the system 61 | pe ... plot the entity in which non-determinism occurs 62 | q! ... to exit the script (not recommended in Jupyter mode) 63 | 64 | Any other input will be interpreted. 65 | This means you can use it to e.g. inspect ports values. 66 | The entity {str(entity)} is bound to the variable 'entity'. 67 | Example: entity.my_port.value will print the value of port my_port. 68 | """ 69 | print(longtext) 70 | while True: 71 | prompt = "Your choice: " 72 | userinput = input(prompt).strip() # read input 73 | if userinput == "p": 74 | self.plot() 75 | elif userinput == "pe": 76 | self.plot(entity=entity) 77 | elif userinput == "r": 78 | return random.choice(enabled_transitions) 79 | elif userinput == "q!": 80 | sys.exit() 81 | elif userinput in [str(idx) for idx in range(len(enabled_transitions))]: 82 | choice = int(userinput) 83 | return enabled_transitions[choice] # <<<<< This is the exit of the function, otherwise we're trapped !! 84 | else: 85 | try: 86 | print(eval(userinput)) 87 | except: 88 | text = f"I don't understand the input: " + \ 89 | userinput + \ 90 | f" (Please try again!)" 91 | print(text) 92 | 93 | 94 | def prompt_transition_selection(self, entity, enabled_transitions): 95 | pad = 1 if len(enabled_transitions) <= 10 else 2 96 | transitions_texts = [stylize(idx, colored.attr("bold")).rjust(pad) + f" ... {trans._name} (transition to '{trans.target._name}')" for idx, trans in enumerate(enabled_transitions)] 97 | transitions_list = "\n".join(transitions_texts) 98 | 99 | longtext = f""" 100 | {stylize(' Non-Determinism detected ', colored.fg('black') + colored.bg('dark_orange') + colored.attr('bold'))} 101 | There are multiple enabled transitions in entity: {stylize(' '+str(entity)+' ', colored.fg('black') + colored.bg('yellow_1') + colored.attr('bold'))} 102 | (Current time: {stylize(self.global_time, colored.attr("bold"))} -- Current automaton state: {stylize(entity.current._name, colored.attr("bold"))}) 103 | 104 | {stylize('Choose one of the following transitions by entering the according number:', colored.attr('underlined'))} 105 | {transitions_list} 106 | 107 | {stylize('Other commands:', colored.attr('underlined'))} 108 | {stylize('r', colored.attr("bold"))} ... choose a transition randomly 109 | {stylize('p', colored.attr("bold"))} ... plot the system 110 | {stylize('pe', colored.attr("bold"))} ... plot the entity in which non-determinism occurs 111 | {stylize('q!', colored.attr("bold"))} ... to exit the script (not recommended in Jupyter mode) 112 | 113 | {stylize('Any other input will be interpreted.', colored.attr('underlined'))} 114 | This means you can use it to e.g. inspect ports values. 115 | The entity {stylize(str(entity), colored.attr('bold'))} is bound to the variable {stylize('entity', colored.attr('bold'))}. 116 | {stylize('Example:', colored.attr('underlined'))} entity.my_port.value will print the value of port my_port. 117 | """ 118 | print(longtext) 119 | while True: 120 | prompt = "Your choice: " 121 | userinput = input(prompt).strip() # read input 122 | if userinput == "p": 123 | self.plot() 124 | elif userinput == "pe": 125 | self.plot(entity=entity) 126 | elif userinput == "r": 127 | return random.choice(enabled_transitions) 128 | elif userinput == "q!": 129 | sys.exit() 130 | elif userinput in [str(idx) for idx in range(len(enabled_transitions))]: 131 | choice = int(userinput) 132 | return enabled_transitions[choice] # <<<<< This is the exit of the function, otherwise we're trapped !! 133 | else: 134 | try: 135 | print(eval(userinput)) 136 | except: 137 | text = stylize(f"I don't understand the input: ", colored.fg("red") + colored.attr("bold")) + \ 138 | userinput + \ 139 | stylize(f" (Please try again!)", colored.fg("red") + colored.attr("bold")) 140 | print(text) 141 | -------------------------------------------------------------------------------- /crestdsl/simulation/plansimulator.py: -------------------------------------------------------------------------------- 1 | import crestdsl.model as crest 2 | import crestdsl.model.api as api 3 | from .simulator import Simulator 4 | 5 | import numbers 6 | import random 7 | import sys 8 | 9 | import logging 10 | logger = logging.getLogger(__name__) 11 | 12 | class PlanSimulator(Simulator): 13 | """ 14 | The PlanSimulator offers the same interface as the usual :doc:`Simulator`. 15 | However, it additionally allows the triggering of an execution_plan in the :func:`~run_plan` 16 | method. 17 | 18 | .. automethod:: run_plan 19 | """ 20 | 21 | 22 | def run_plan(self, execution_plan): 23 | """ 24 | Executes a plan. 25 | The execution plan is a heterogeneous list of the following items: 26 | - numeric values: (specify time advances) 27 | - pairs of (numeric, {entity: transition}-dict): 28 | advance a certain time and choose transition, 29 | everytime non-determinism is encountered in entity 30 | - {port: value} dicts (for setting of input ports) 31 | - {entity: transitions} dicts (for resolving conflicts) 32 | 33 | The list items will be iteratively consumed. 34 | If errors are observed they raise ValueError exceptions. 35 | 36 | If there is non-determinism, but no dict specifies how to resolve it, then we fall back to randomness. 37 | 38 | Here's a documented example of a plan: 39 | [ 40 | # advance 10 time units: 41 | 10, 42 | 43 | # set values: 44 | {entity.port : 33, entity.port2: -200}, 45 | # advance 20 time units and choose these transitions everytime there is a conflict in this period 46 | (20, {entity: entity.transition1, entity.subentity: entity.subentity.transition2} ), 47 | 48 | # advance 33 time units. 49 | # When you hit a conflict, check if the first element is an entity-state dict 50 | # if the entity is a key in the first element, then pop it and 51 | # use it to reolve the conflict (otherwise choose randomly) 52 | # then continue until anothoer conflict or end of advance 53 | 33, 54 | {entity: entity.transitionA}, 55 | {entity: entity.transitionB}, 56 | {entity: entity.transitionA}, 57 | 58 | # if you have two entities and you don't know which one will be conflicting first 59 | # (because they'll have conflicts at the same time) 60 | # you can put them both in a dict and duplicate the dict. 61 | # the first one will pop the first dict, the second one the second dict: 62 | 63 | 444, 64 | {entity.subentity1: entity.subentity2.transA, entity.subentity2: entity.subentity2.transB}, 65 | {entity.subentity1: entity.subentity2.transA, entity.subentity2: entity.subentity2.transB}, 66 | ] 67 | 68 | Parameters 69 | ---------- 70 | execution_plan: list 71 | The list of instructions that should be executed. 72 | 73 | Raises 74 | ------- 75 | ValueError 76 | In case there is something wrongly specified (e.g. take a transition that is not enabled, or so) 77 | """ 78 | # some setup 79 | self.execution_plan = execution_plan 80 | self._transition_selection = None 81 | 82 | while len(self.execution_plan) > 0: 83 | next_action = self.execution_plan.pop(0) 84 | logger.debug(f"Next action is {repr(next_action)}") 85 | if isinstance(next_action, numbers.Number) and not isinstance(next_action, bool): 86 | logger.info(f"Consuming command: advance {next_action} time units") 87 | self.advance(next_action) 88 | elif isinstance(next_action, tuple): 89 | assert isinstance(next_action[0], numbers.Number) and not isinstance(next_action, bool), "List entry have a numeric value first" 90 | assert isinstance(next_action[1], dict), "List entry must have a dict value second" 91 | 92 | # do some checks 93 | for entity, trans in next_action[1].items(): 94 | assert isinstance(entity, crest.Entity) 95 | assert isinstance(trans, crest.Transition) 96 | assert trans in crest.get_transitions(entity) 97 | 98 | self._transition_selection = next_action[1] 99 | self.advance(next_action[0]) 100 | self._transition_selection = None # reset it to None 101 | elif isinstance(next_action, dict): 102 | if not all([isinstance(port, crest.Port) for port in next_action.keys()]): 103 | raise ValueError(f"When consuming command I found a dict whose keys are not Port objects. Dict: \n{repr(next_action)}") 104 | as_strings = [f"{api.get_name(port)}: {value}" for port, value in next_action.items()] 105 | logger.info(f"Consuming command: setting port values { ', '.join(as_strings) }") 106 | self.set_values(next_action) 107 | else: 108 | raise ValueError(f"Don't know how to act for plan item:\n{repr(next_action)}.") 109 | 110 | def select_transition_to_trigger(self, entity): 111 | transitions_from_current_state = [t for t in crest.get_transitions(entity) if t.source is entity.current] 112 | enabled_transitions = [t for t in transitions_from_current_state if self._get_transition_guard_value(t)] 113 | 114 | if len(enabled_transitions) == 1: # no choice 115 | return enabled_transitions[0] 116 | elif len(enabled_transitions) > 1: # this is the tricky part 117 | logger.debug(f"Multiple transitions enabled in entity {api.get_name(entity)}") 118 | transition = self._select_transition_according_to_execution_plan(entity) 119 | if transition is False: 120 | return random.choice(enabled_transitions) 121 | else: 122 | assert transition is not None and transition in enabled_transitions, \ 123 | f"The plan says to execute transition {api.get_name(transition)}, but it's not amongst the enabled ones." 124 | return transition 125 | else: # no transitions at all 126 | return None 127 | 128 | def _select_transition_according_to_execution_plan(self, entity): 129 | if self._transition_selection is not None: 130 | if entity in self._transition_selection: 131 | logger.debug(f"There was a dedicated transition specified for entity {api.get_name(entity)}") 132 | return self._transition_selection[entity] 133 | else: 134 | logger.debug(f"No dedicated transition specified for entity {api.get_name(entity)}.") 135 | return False 136 | elif len(self.execution_plan) > 0: # there's another element in the plan 137 | logger.debug(f"Searching in execution_plan if there is a specificaiton for entity {api.get_name(entity)}") 138 | # now check if it's a dict and its keys are entities: 139 | if isinstance(self.execution_plan[0], dict) and \ 140 | all([isinstance(ent, crest.Entity) for ent in self.execution_plan[0].keys()]): 141 | logger.debug(f"There is an execution_plan. Let's see if entity {api.get_name(entity)} is specified.") 142 | # if the entity is specified in the first element, pop it and use it 143 | if entity in self.execution_plan[0]: 144 | logger.debug(f"Found specification in plan. Triggering execution of transition {self.execution_plan[0].get(entity)} in entity {api.get_name(entity)}") 145 | return self.execution_plan.pop(0).get(entity) 146 | else: # otherwise leave it and continue randomly 147 | logger.debug(f"There is a specification, but not for entity {api.get_name(entity)}. Falling back to random choice") 148 | return False 149 | else: # otherwise choose randomly 150 | logger.debug(f"There next list item is not an execution plan. Falling back to random choice") 151 | return False 152 | else: 153 | logger.debug(f"The execution_plan is empty. Falling back to random choice") 154 | return False 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /crestdsl/simulation/tracestore.py: -------------------------------------------------------------------------------- 1 | from crestdsl.model import Port, Entity, get_all_ports, get_all_entities, get_states 2 | import numbers 3 | from collections.abc import Iterable 4 | 5 | import pandas as pd 6 | 7 | 8 | import logging 9 | logger = logging.getLogger(__name__) 10 | 11 | # TODO: rewrite to use Pandas !!! 12 | TIMESTAMP = "timestamp" 13 | 14 | class TraceStore(object): 15 | 16 | def __init__(self): 17 | # self.datastore = dict() 18 | self._data = [] 19 | self._df = None 20 | 21 | @property 22 | def data(self): 23 | as_df = pd.DataFrame(self._data) # merge data into df 24 | 25 | if self._df is not None: 26 | as_df = pd.concat([self._df, as_df], ignore_index=True) 27 | self._df = as_df 28 | 29 | return as_df 30 | 31 | newData = pd.concat(self._data, ignore_index=True) 32 | self._data = [newData] # make sure we dont' have to cancat this again 33 | return newData 34 | 35 | def add_data(self, data): 36 | self._data.append(data) 37 | 38 | def save_multiple(self, timestamp, data): 39 | # for key, val in data.items(): 40 | # self.save(key, timestamp, val) 41 | 42 | data.update({"timestamp": timestamp}) 43 | self._data.append(pd.DataFrame(data, index=[0])) 44 | 45 | def save_entity(self, root_entity, timestamp): 46 | data = {"timestamp": timestamp} 47 | data.update({entity: entity.current for entity in get_all_entities(root_entity)}) 48 | data.update({port: port.value for port in get_all_ports(root_entity)}) 49 | # print(self.data.shape) 50 | # try: 51 | self._data.append(data) 52 | # except: 53 | # breakpoint() 54 | # pass 55 | # print(self.data.shape) 56 | 57 | def plot(self, traces=None): 58 | try: 59 | import cufflinks as cf 60 | cf.set_config_file(offline=True, theme='white') 61 | 62 | if traces is None: 63 | traces = self.data.columns.drop(TIMESTAMP).tolist() 64 | 65 | if not isinstance(traces, Iterable): 66 | traces = [traces] 67 | 68 | self.data.iplot(x=TIMESTAMP,y=traces, 69 | mode='markers+lines',symbol="x",size=5, 70 | width=.5,zerolinecolor="black") 71 | except ImportError as exc: 72 | logger.exception("It appears there was a problem during plotting.") 73 | -------------------------------------------------------------------------------- /crestdsl/simulation/transitiontime.py: -------------------------------------------------------------------------------- 1 | from .conditiontimedchangecalculator import ConditionTimedChangeCalculator 2 | from .to_z3 import get_minimum_dt_of_several 3 | from crestdsl.model import get_transitions 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class TransitionTimeCalculator(ConditionTimedChangeCalculator): 10 | 11 | def get_next_transition_time(self, entity=None): 12 | """calculates the time until one of the transitions can fire""" 13 | if not entity: 14 | entity = self.entity 15 | logger.debug(f"Calculating next transition time for entity {entity._name} ({entity.__class__.__name__}) and it's subentities") 16 | system_times = self.calculate_system(entity) 17 | 18 | # system_times = [t for e in get_all_entities(self.entity) for t in self.collect_transition_times_from_entity(e)] 19 | logger.debug("All transitions in entity %s (%s): ", entity._name, entity.__class__.__name__) 20 | logger.debug(str([(e._name, f"{t.source._name} -> {t.target._name} ({name})", dt) for (e, t, name, dt) in system_times])) 21 | 22 | if len(system_times) > 0: 23 | min_dt = get_minimum_dt_of_several([t[3] for t in system_times], self.timeunit) 24 | # minimum = min(system_times, key=lambda t: t[3]) 25 | if logger.getEffectiveLevel() <= logging.DEBUG: 26 | logger.debug("Next transition in: {min_dt}") 27 | return min_dt 28 | else: 29 | # this happens if there are no transitions fireable by increasing time only 30 | return None 31 | 32 | def calculate_entity_hook(self, entity): 33 | return self.collect_transition_times_from_entity(entity) 34 | 35 | def collect_transition_times_from_entity(self, entity=None): 36 | """ collect all transitions and their times """ 37 | if not entity: 38 | entity = self.entity 39 | logger.debug("Calculating transition times for entity: %s (%s)", entity._name, entity.__class__.__name__) 40 | 41 | dts = [] 42 | for name, trans in get_transitions(entity, as_dict=True).items(): 43 | if entity.current == trans.source: 44 | dt = self.get_transition_time(trans) 45 | dts.append((entity, trans, name, dt)) 46 | 47 | if dts: 48 | if logger.getEffectiveLevel() <= logging.DEBUG: 49 | logger.debug("times: ") 50 | logger.debug(str([(e._name, f"{t.source._name} -> {t.target._name} ({dt})", dt) for (e, t, name, dt) in dts])) 51 | else: 52 | if logger.getEffectiveLevel() <= logging.DEBUG: 53 | logger.debug("times: []") 54 | dts = list(filter(lambda t: t[3] is not None, dts)) # filter values with None as dt 55 | return dts 56 | -------------------------------------------------------------------------------- /crestdsl/simulation/z3simulator.py: -------------------------------------------------------------------------------- 1 | 2 | from crestdsl.model import get_all_ports, REAL 3 | 4 | from crestdsl import sourcehelper as SH 5 | 6 | import z3 7 | from .to_z3 import get_z3_value, get_z3_variable, Z3Converter, get_z3_var 8 | from .basesimulator import BaseSimulator 9 | 10 | import logging 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class Z3Simulator(BaseSimulator): 15 | 16 | def __init__(self, entity, timeunit=REAL, plotter=None, default_to_integer_real=True, record_traces=True): 17 | entity = self.to_z3_system(entity) 18 | super().__init__(entity, timeunit, plotter, default_to_integer_real, record_traces) 19 | 20 | def to_z3_system(self, system): 21 | for port in get_all_ports(system): 22 | port.value = get_z3_value(port, port._name) 23 | return system 24 | 25 | def set_values(self, **port_value_map): 26 | # convert to z3 values 27 | port_value_map = {port: get_z3_value(value, port._name) for port, value in port_value_map.items()} 28 | super().set_values(port_value_map) 29 | 30 | """ influence """ 31 | 32 | def _get_influence_function_value(self, influence): 33 | solver = z3.Solver() 34 | 35 | # create the z3 variables 36 | z3_vars = {} 37 | z3_vars[influence.source] = {influence.source._name: get_z3_value(influence.source, influence.source._name)} 38 | z3_vars[influence.target] = {influence.target._name: get_z3_variable(influence.target, influence.target._name)} 39 | 40 | # add source parameter 41 | z3_src = z3_vars[influence.source][influence.source._name] 42 | params = SH.get_param_names(influence.function) 43 | param_key = params[0] + "_" + str(id(influence)) 44 | z3_param = get_z3_variable(influence.source, params[0], str(id(influence))) 45 | 46 | if logger.getEffectiveLevel() <= logging.DEBUG: # only log if the level is appropriate, since z3's string-conversion takes ages 47 | logger.debug(f"adding param: z3_vars[{param_key}] = {params[0]}_0 : {z3_param} ") 48 | 49 | z3_vars[param_key] = {params[0] + "_0": z3_param} 50 | 51 | if logger.getEffectiveLevel() <= logging.DEBUG: # only log if the level is appropriate, since z3's string-conversion takes ages 52 | logger.debug(f"influence entry constraint: {z3_src} == {z3_param}") 53 | solver.add(z3_src == z3_param) 54 | 55 | # convert function 56 | conv = Z3Converter(z3_vars, entity=influence._parent, container=influence, use_integer_and_real=self.default_to_integer_real) 57 | conv.target = influence.target 58 | constraints = conv.to_z3(influence.function) 59 | 60 | if logger.getEffectiveLevel() <= logging.DEBUG: # only log if the level is appropriate, since z3's string-conversion takes ages 61 | logger.debug(f"{influence._name} {influence} constraints: { constraints }") 62 | 63 | if SH.is_lambda(influence.function): 64 | # equation for lambda result 65 | tgt = conv.z3_vars[influence.target][influence.target._name] 66 | solver.add(tgt == constraints) 67 | else: 68 | solver.add(constraints) 69 | 70 | if solver.check() == z3.sat: 71 | new_target_value = solver.model()[z3_vars[influence.target][influence.target._name]] 72 | new_target_value.type = z3_vars[influence.target][influence.target._name].type # remember to assign a type 73 | return new_target_value 74 | else: 75 | raise Exception("Problem when calculating the target value of the influence") 76 | 77 | def _get_transition_guard_value(self, transition): 78 | solver = z3.Solver() 79 | 80 | # create the z3 variables 81 | z3_vars = {} 82 | 83 | transition_ports = SH.get_accessed_ports(transition.guard, transition) 84 | for port in transition_ports: 85 | z3_vars[port] = {port._name: get_z3_value(port, port._name)} 86 | z3_vars[port][port._name + "_0"] = get_z3_value(port, port._name + "_0") 87 | 88 | conv = Z3Converter(z3_vars, entity=transition._parent, container=transition, use_integer_and_real=self.default_to_integer_real) 89 | constraints = conv.to_z3(transition.guard) 90 | solver.add(constraints) 91 | 92 | return solver.check() == z3.sat 93 | 94 | def _get_update_function_value(self, update, time): 95 | solver = z3.Solver() 96 | 97 | # create the z3 variables 98 | z3_vars = {} 99 | 100 | # create the TIME UNIT for dt 101 | z3_vars['dt'] = get_z3_var(self.timeunit, 'dt') 102 | z3_vars['dt'].type = self.timeunit 103 | solver.add(z3_vars['dt'] == time) 104 | 105 | z3_vars[update.target] = {update.target._name: get_z3_variable(update.target, update.target._name)} 106 | 107 | accessed_ports = SH.get_accessed_ports(update.function, update) 108 | for access in accessed_ports: 109 | if access != update.target: 110 | z3_vars[access] = {access._name: get_z3_value(access, access._name)} 111 | z3_vars[access][access._name + "_0"] = get_z3_value(access, access._name + "_0") 112 | 113 | # convert function 114 | conv = Z3Converter(z3_vars, entity=update._parent, container=update, use_integer_and_real=self.default_to_integer_real) 115 | conv.target = update.target 116 | constraints = conv.to_z3(update.function) 117 | 118 | if logger.getEffectiveLevel() <= logging.DEBUG: # only log if the level is appropriate, since z3's string-conversion takes ages 119 | logger.debug(f"{update._name} {update} constraints: { constraints }") 120 | 121 | if SH.is_lambda(update.function): 122 | # equation for lambda result 123 | tgt = conv.z3_vars[update.target][update.target._name] 124 | solver.add(tgt == constraints) 125 | else: 126 | solver.add(constraints) 127 | 128 | if solver.check() == z3.sat: 129 | new_target_value = solver.model()[z3_vars[update.target][update.target._name]] 130 | new_target_value.type = z3_vars[update.target][update.target._name].type # remember to assign a type 131 | return new_target_value 132 | else: 133 | raise Exception("Problem when calculating the target value of the influence") 134 | -------------------------------------------------------------------------------- /crestdsl/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from .draw_statespace import draw_plotly as plotly_statespace 2 | from .draw_statespace import draw_plot as plot_statespace 3 | 4 | # zero-config plotting, just use whatever is default 5 | def plot(object, name='', **kwargs): 6 | from crestdsl.config import config 7 | return config.default_plotter.plot(object, name=name, **kwargs) -------------------------------------------------------------------------------- /crestdsl/ui/draw_statespace.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | 3 | import plotly 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | def draw_plot(statespace): 10 | pos = plot_layout(statespace) 11 | nx.draw(statespace, pos) 12 | node_labels = {node: statespace.nodes[node].get("label", "") for node in statespace.nodes()} 13 | nx.draw_networkx_labels(statespace, pos, labels=node_labels) 14 | edge_labels = nx.get_edge_attributes(statespace, 'weight') 15 | nx.draw_networkx_edge_labels(statespace, pos, edge_labels=edge_labels) 16 | 17 | 18 | def draw_plotly(statespace, text_func=None, highlight=None, debug=False): 19 | plotly.offline.init_notebook_mode(connected=True) 20 | 21 | data, annotations = plotly_data(statespace, text_func, highlight, debug) 22 | 23 | axis = dict( 24 | showline=False, 25 | zeroline=False, 26 | showgrid=False, 27 | showticklabels=False, 28 | title='' 29 | ) 30 | layout = dict( 31 | showlegend=False, 32 | xaxis=axis, 33 | yaxis=axis, 34 | hovermode='closest', 35 | annotations=annotations 36 | ) 37 | 38 | plotly.offline.iplot(dict(data=data, layout=layout)) 39 | 40 | 41 | def plotly_data(statespace, text_func=None, highlight=None, debug=False): 42 | if highlight is None: 43 | highlight = [] 44 | 45 | pos = plot_layout(statespace) 46 | 47 | labels = {} 48 | if text_func is not None: 49 | for key, val in pos.items(): 50 | try: 51 | labels[key] = text_func(key) 52 | except: 53 | pass 54 | 55 | gammanodes = [n for n, gamma in statespace.nodes(data="gammanode", default=False) if gamma] 56 | trace_nodes = dict( # blue nodes 57 | type="scatter", 58 | x=[v[0] for k, v in pos.items() if k not in highlight and k not in gammanodes], 59 | y=[v[1] for k, v in pos.items() if k not in highlight and k not in gammanodes], 60 | text=[lbl for key, lbl in labels.items() if key not in highlight], 61 | mode='markers', 62 | marker=dict(size=15, color="blue"), 63 | hoverinfo='text' 64 | ) 65 | 66 | trace_gamma_nodes = dict( # blue nodes 67 | type="scatter", 68 | x=[v[0] for k, v in pos.items() if k not in highlight and k in gammanodes], 69 | y=[v[1] for k, v in pos.items() if k not in highlight and k in gammanodes], 70 | text=[lbl for key, lbl in labels.items() if key not in highlight], 71 | mode='markers', 72 | marker=dict(size=15, color="lightblue"), 73 | hoverinfo='text' 74 | ) 75 | 76 | trace_highlight = dict( # orange nodes 77 | type="scatter", 78 | x=[v[0] for k, v in pos.items() if k in highlight], 79 | y=[v[1] for k, v in pos.items() if k in highlight], 80 | text=[lbl for key, lbl in labels.items() if key in highlight], 81 | mode='markers', 82 | marker=dict(size=15, color="orange"), 83 | hoverinfo='text' 84 | ) 85 | 86 | trace_texts = dict( # 87 | type="scatter", 88 | x=[v[0] for k, v in pos.items()], 89 | y=[v[1] for k, v in pos.items()], 90 | text=[str(id(key)) for key, lbl in labels.items()], 91 | mode='markers+text', 92 | hoverinfo='text' 93 | ) 94 | 95 | # MIDDLE POINTS 96 | middle_node_trace = dict( 97 | type='scatter', 98 | x=[], 99 | y=[], 100 | text=[], 101 | mode='markers+text', 102 | marker=dict(opacity=0) 103 | ) 104 | 105 | for e in statespace.edges(data=True): 106 | x0, y0 = pos[e[0]] 107 | x1, y1 = pos[e[1]] 108 | middle_node_trace['x'].append((x0+x1)/2) 109 | middle_node_trace['y'].append((y0+y1)/2) 110 | middle_node_trace['text'].append(f"{e[2]['weight']}") 111 | 112 | # MIDDLE POINTS HOVER 113 | middle_node_hover_trace = dict( 114 | type='scatter', 115 | x=[], 116 | y=[], 117 | text=[], 118 | mode='markers', 119 | hoverinfo='text', 120 | marker=dict(opacity=0, color="white") 121 | ) 122 | 123 | for e in statespace.edges(data=True): 124 | x0, y0 = pos[e[0]] 125 | x1, y1 = pos[e[1]] 126 | middle_node_hover_trace['x'].append((x0+x1)/2) 127 | middle_node_hover_trace['y'].append((y0+y1)/2) 128 | transitions = e[2].get('transitions', []) 129 | if len(transitions) > 0: 130 | transitions_text = "
".join([f"{idx+1}. {t._parent}: {t.source._name} --> {t.target._name}" for idx, t in enumerate(transitions)]) 131 | else: 132 | transitions_text = "No CREST Transitions" 133 | middle_node_hover_trace['text'].append(f"Duration: {e[2]['weight']}
{transitions_text}") # 134 | 135 | # EDGES 136 | Xe = [] 137 | Ye = [] 138 | for e in statespace.edges(data='weight'): 139 | Xe.extend([pos[e[0]][0], pos[e[1]][0], None]) 140 | Ye.extend([pos[e[0]][1], pos[e[1]][1], None]) 141 | 142 | # trace_edges=dict( 143 | # type='scatter', 144 | # mode='lines+text', 145 | # x=Xe, 146 | # y=Ye, 147 | # line=dict(width=1, color='rgb(25,25,25)'), 148 | # ) 149 | 150 | annotations = [] # arrows connecting nodes (standard lines don't give arrows) 151 | for e in statespace.edges(data=True): 152 | x0, y0 = pos[e[0]] 153 | x1, y1 = pos[e[1]] 154 | annotations.append( 155 | dict(x=x1, y=y1, xref='x', yref='y', 156 | ax=x0, ay=y0, axref='x', ayref='y', 157 | opacity=.5, standoff=7, startstandoff=7 158 | ) 159 | ) 160 | 161 | if debug: 162 | return [trace_nodes, trace_highlight, middle_node_trace, trace_texts], annotations 163 | 164 | return [trace_nodes, trace_gamma_nodes, trace_highlight, middle_node_trace, middle_node_hover_trace], annotations 165 | 166 | 167 | def plot_layout(statespace): 168 | # cp = statespace.copy() 169 | pos = nx.nx_agraph.graphviz_layout(statespace, prog="sfdp", args='-Grankdir=LR -Goverlap=false -Gsplines=true') # -Gnslimit=3 -Gnslimit1=3 170 | return pos 171 | -------------------------------------------------------------------------------- /crestdsl/ui/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/add.png -------------------------------------------------------------------------------- /crestdsl/ui/icons/press32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/press32.png -------------------------------------------------------------------------------- /crestdsl/ui/icons/print32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/print32.png -------------------------------------------------------------------------------- /crestdsl/ui/icons/view_1_132.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/view_1_132.png -------------------------------------------------------------------------------- /crestdsl/ui/icons/wrench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/wrench.png -------------------------------------------------------------------------------- /crestdsl/ui/icons/zoom_in32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/zoom_in32.png -------------------------------------------------------------------------------- /crestdsl/ui/icons/zoom_out32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/crestdsl/ui/icons/zoom_out32.png -------------------------------------------------------------------------------- /crestdsl/ui/index.js: -------------------------------------------------------------------------------- 1 | element.append("
HAHAHA
"); 2 | var mxBasePath = './mxgraph/javascript/crestdsl' 3 | $('head').append(""); 4 | $('head').append(''); 5 | 6 | 7 | 8 | 9 | requirejs([ 10 | "https://code.jquery.com/jquery-3.3.1.slim.min.js", 11 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js", 12 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/python.min.js", 13 | // "./mxgraph/javascript/src/js/mxClient.js", 14 | "https://cdn.jsdelivr.net/npm/elkjs@0.3.0/lib/elk.bundled.js", 15 | "./crestdsl/ui/mxgraphCustom.js" 16 | ], function(jquery, highlight, python, ELK, custom){ 17 | $('head').append(''); 18 | 19 | // console.log('knownLayoutOptions', new ELK().knownLayoutOptions()); 20 | // console.log('knownLayoutAlgorithms', new ELK().knownLayoutAlgorithms()); 21 | // console.log('knownLayoutCategories', new ELK().knownLayoutCategories()); 22 | // console.log("test"); 23 | 24 | var elkgraph = ELKGRAPH; 25 | const elk = new ELK({ 26 | defaultLayoutOptions: { 27 | 'elk.algorithm': 'layered', 28 | 'elk.direction': 'RIGHT', 29 | 'elk.padding': '[top=50,left=50,bottom=50,right=50]', 30 | // 'elk.spacing.componentComponent': 25, 31 | 'elk.layered.spacing.nodeNodeBetweenLayers': 50, 32 | // 'elk.edgeLabels.inline': true, 33 | 'elk.edgeRouting': 'POLYLINE', 34 | 'elk.layered.unnecessaryBendpoints': true 35 | } 36 | }); 37 | 38 | // elkgraph = ELKGRAPH 39 | 40 | elk.layout(elkgraph) 41 | .then(function(g){ 42 | console.log(g); 43 | plot(g); 44 | }); 45 | } 46 | ); 47 | -------------------------------------------------------------------------------- /crestdsl/verification/__init__.py: -------------------------------------------------------------------------------- 1 | from .checklib import check 2 | from . import tctl 3 | from .normalform import normalform 4 | from .simplify import simplify 5 | 6 | from .statespace import StateSpace 7 | 8 | from .verifier import * 9 | from .modelchecker import ModelChecker 10 | # from .pointwise import PointwiseModelChecker 11 | # from .continuous import ContinuousModelChecker 12 | -------------------------------------------------------------------------------- /crestdsl/verification/alpha_beta.py: -------------------------------------------------------------------------------- 1 | from functools import singledispatch 2 | import operator 3 | import math 4 | 5 | from .tctl import * # I know, I know... but otherwise the formulas become unreadable !! 6 | from .tctl import AtomicProposition, NamedAtomicProposition # * in tctl only offers some of the classes, we need all ! 7 | 8 | import logging 9 | logger = logging.getLogger(__name__) 10 | 11 | """ Start ALPHA """ 12 | @singledispatch 13 | def alpha(formula, gamma): 14 | return formula 15 | 16 | @alpha.register(bool) 17 | def alpha_bool(formula, gamma): 18 | return formula 19 | 20 | @alpha.register(AtomicProposition) 21 | def alpha_AP(formula, gamma): 22 | return formula 23 | 24 | @alpha.register(Not) 25 | def alpha_Not(formula, gamma): 26 | return Not(alpha(formula.phi, gamma)) 27 | 28 | @alpha.register(And) 29 | def alpha_And(formula, gamma): 30 | return And(alpha(formula.phi, gamma), alpha(formula.psi, gamma)) 31 | 32 | @alpha.register(EU) 33 | def alpha_EU(formula, gamma): 34 | from .continuous import PA # delayed import 35 | 36 | intvl = formula.interval 37 | a_phi = alpha(formula.phi, gamma) 38 | a_psi = alpha(formula.psi, gamma) 39 | 40 | # only transform, if the interval is not a TCTL_cb interval 41 | # this means if it's not of the form [a,b] and not [a, inf) 42 | if (intvl.start_operator == operator.ge): 43 | if intvl.end_operator == operator.lt and intvl.end == math.inf: # [a, inf) 44 | return EU(a_phi, a_psi, interval=intvl) 45 | elif intvl.end_operator == operator.le and intvl.end != math.inf: # [a, b] 46 | return EU(a_phi, a_psi, interval=intvl) 47 | 48 | 49 | I_1 = Interval() 50 | I_1.start = intvl.start if (intvl.start_operator == operator.ge) else (intvl.start + gamma) 51 | I_1.end = intvl.end if (intvl.end_operator == operator.le) else (intvl.end - gamma) 52 | 53 | I_2 = (Interval() >= intvl.start) <= intvl.end 54 | 55 | p1 = And(Not(PA), EU(a_phi, a_psi, interval=I_1)) 56 | p2 = And(PA, EU(a_phi, a_psi, interval=I_2)) 57 | return Or(p1, p2) 58 | 59 | @alpha.register(AU) 60 | def alpha_AU(formula, gamma): 61 | from .continuous import PA # delayed import 62 | 63 | intvl = formula.interval 64 | a_phi = alpha(formula.phi, gamma) 65 | a_psi = alpha(formula.psi, gamma) 66 | 67 | # only transform, if the interval is not a TCTL_cb interval 68 | # this means if it's not of the form [a,b] and not [a, inf) 69 | if (intvl.start_operator == operator.ge): 70 | if intvl.end_operator == operator.lt and intvl.end == math.inf: # [a, inf) 71 | return AU(a_phi, a_psi, interval=intvl) 72 | elif intvl.end_operator == operator.le and intvl.end != math.inf: # [a, b] 73 | return AU(a_phi, a_psi, interval=intvl) 74 | 75 | 76 | 77 | I_1 = Interval() 78 | I_1.start = intvl.start if (intvl.start_operator == operator.ge) else (intvl.start + gamma) 79 | I_1.end = intvl.end if (intvl.end_operator == operator.le) else (intvl.end - gamma) 80 | 81 | I_2 = (Interval() >= intvl.start) <= intvl.end 82 | 83 | p1 = And(Not(PA), AU(a_phi, a_psi, interval=I_1)) 84 | p2 = And(PA, AU(a_phi, a_psi, interval=I_2)) 85 | return Or(p1, p2) 86 | """ END ALPHA """ 87 | 88 | """ START BETA """ 89 | 90 | @singledispatch 91 | def beta(formula): 92 | msg = f"Don't know how to do beta-transform on formula {formula} of type {type(formula)}" 93 | logger.error(msg) 94 | raise ValueError(msg) 95 | 96 | @beta.register(bool) 97 | def beta_bool(formula): 98 | return formula 99 | 100 | @beta.register(AtomicProposition) 101 | def beta_AP(formula): 102 | return formula 103 | 104 | @beta.register(Not) 105 | def beta_Not(formula): 106 | return Not(beta(formula.phi)) 107 | 108 | @beta.register(And) 109 | def beta_And(formula): 110 | return And(beta(formula.phi), beta(formula.psi)) 111 | 112 | @beta.register(EU) 113 | def beta_EU(formula): 114 | from .continuous import PA # delayed import 115 | 116 | new_psi = EU(beta(formula.phi), (And(beta(formula.psi), Or(Not(PA), beta(formula.phi)))), formula.interval) 117 | if formula.interval.ininterval(0): 118 | return Or(beta(formula.psi), new_psi) 119 | return new_psi 120 | 121 | @beta.register(AU) 122 | def beta_AU(formula): 123 | from .continuous import PA # delayed import 124 | 125 | new_psi = AU(beta(formula.phi), (And(beta(formula.psi), Or(Not(PA), beta(formula.phi)))), formula.interval) 126 | if formula.interval.ininterval(0): 127 | return Or(beta(formula.psi), new_psi) 128 | return new_psi 129 | 130 | """ END BETA """ 131 | -------------------------------------------------------------------------------- /crestdsl/verification/normalform.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import math 3 | from functools import singledispatch 4 | 5 | from .tctl import * # I know, I know... but otherwise the formulas become unreadable !! 6 | from .tctl import AtomicProposition, TCTLFormula # * in tctl only offers some of the classes, we need all ! 7 | 8 | @singledispatch 9 | def normalform(formula): 10 | # any other thing: return standard formula 11 | return formula 12 | 13 | """ BASE FORMS (needed to apply recursion) """ 14 | 15 | @normalform.register(Not) 16 | def normalform_Not(formula): 17 | phi = normalform(formula.phi) 18 | return Not(phi) 19 | 20 | @normalform.register(And) 21 | def normalform_And(formula): 22 | phi = normalform(formula.phi) 23 | psi = normalform(formula.psi) 24 | return And(phi, psi) 25 | 26 | @normalform.register(EU) 27 | def normalform_EU(formula): 28 | phi = normalform(formula.phi) 29 | psi = normalform(formula.psi) 30 | return EU(phi, psi, interval=formula.interval) 31 | 32 | # @normalform.register(AU) 33 | # def normalform_AU(formula): 34 | # phi = normalform(formula.phi) 35 | # psi = normalform(formula.psi) 36 | # return AU(phi, psi, interval=formula.interval) 37 | 38 | """ Transformations """ 39 | 40 | @normalform.register(bool) 41 | def normalform_bool(formula): 42 | """ 4.1 """ 43 | if formula is False: 44 | formula = Not(True) 45 | return formula 46 | 47 | @normalform.register(Or) 48 | def normalform_Or(formula): 49 | """ 4.2 """ 50 | phi = normalform(formula.phi) 51 | psi = normalform(formula.psi) 52 | return normalform(Not(And(Not(phi), Not(psi)))) 53 | 54 | @normalform.register(Implies) 55 | def normalform_Implies(formula): 56 | """ 4.3 """ 57 | phi = normalform(formula.phi) 58 | psi = normalform(formula.psi) 59 | return normalform(Or(Not(phi), psi)) 60 | 61 | @normalform.register(Equality) 62 | def normalform_Equality(formula): 63 | """ 4.4 """ 64 | phi = normalform(formula.phi) 65 | psi = normalform(formula.psi) 66 | return normalform(And(Implies(phi, psi), Implies(formula.psi, formula.phi))) 67 | 68 | @normalform.register(EF) 69 | def normalform_EF(formula): 70 | """ 4.5 """ 71 | # phi = normalform(formula.phi) 72 | return normalform(EU(True, formula.phi, interval=formula.interval)) 73 | 74 | @normalform.register(AF) 75 | def normalform_AF(formula): 76 | """ 4.6 """ 77 | # phi = normalform(formula.phi) 78 | return normalform(AU(True, formula.phi, interval=formula.interval)) 79 | 80 | @normalform.register(EG) 81 | def normalform_EG(formula): 82 | intvl = formula.interval 83 | a = intvl.start 84 | b = intvl.end 85 | phi = normalform(formula.phi) 86 | 87 | """ 4.7 rewrite EG<=b """ 88 | if intvl.start_operator == operator.ge and a == 0 and \ 89 | intvl.end_operator == operator.le: #and b != math.inf: 90 | return normalform(EU(phi, True, interval=(Interval() > intvl.end) )) 91 | 92 | """ 4.8 rewrite EG intvl.end) )) 96 | 97 | """ 4.9 any other interval """ 98 | if not intvl.compare(Interval()): # compare to [0,inf) 99 | return normalform(Not(AF(Not(phi), interval=formula.interval))) 100 | 101 | """ classic form, no interval boundaries, leave it [0, inf): """ 102 | if intvl.compare(Interval()): 103 | return EG(phi, interval=formula.interval) 104 | 105 | 106 | # """ 107 | # We need to transform this into other formulas, 108 | # because otherwise alpha and beta don't know what to do! 109 | # this is actually not well explained in the paper 110 | # """ 111 | # if intvl.compare(Interval()): 112 | # return normalform(Not(AF(Not(phi)))) 113 | 114 | raise ValueError("I couldn't transform an EG-formula to normalform because I cannot identify the correct method (based on interval). Formula \n {str(formula)}") 115 | 116 | 117 | @normalform.register(AG) 118 | def normalform_AG(formula, recursive=False): 119 | """ 4.10 rewrite AG_I """ 120 | # phi = normalform(formula.phi) 121 | return normalform(Not(EF(Not(formula.phi), interval=formula.interval))) 122 | 123 | @normalform.register(AU) 124 | def normalform_AU(formula, recursive=False): 125 | """ Procedure 2 - selection """ 126 | intvl = formula.interval 127 | a = formula.interval.start 128 | b = formula.interval.end 129 | phi = normalform(formula.phi) 130 | psi = normalform(formula.psi) 131 | 132 | """ 4.11 interval=[0,inf) """ 133 | if intvl.compare(Interval()): 134 | p1 = Not(EG(Not(psi))) 135 | p2 = Not(EU(Not(psi), And(Not(phi), Not(psi)))) 136 | return normalform(And(p1, p2)) 137 | 138 | """ 4.12 interval=[0,b] """ 139 | if intvl.start_operator == operator.ge and a == 0 and \ 140 | intvl.end_operator == operator.le: # and intvl.end != math.inf: 141 | intvl_spy = Interval() <= b 142 | p1 = Not(EG(Not(psi), Interval() <= b)) 143 | p2 = Not(EU(Not(psi), And(Not(phi), Not(psi)))) 144 | return normalform(And(p1, p2)) 145 | 146 | """ 4.13 interval=[0,b) """ 147 | if intvl.start_operator == operator.ge and a == 0 and \ 148 | intvl.end_operator == operator.lt: # and intvl.end != math.inf: 149 | intvl_spy = Interval() <= b 150 | p1 = Not(EG(Not(psi), Interval() < b)) 151 | p2 = Not(EU(Not(psi), And(Not(phi), Not(psi)))) 152 | return normalform(And(p1, p2)) 153 | 154 | """ 4.14 interval=[a,inf) """ 155 | if intvl.start_operator == operator.ge and a != 0 and \ 156 | intvl.end_operator == operator.lt and b == math.inf: 157 | p2 = AU(phi, psi, interval=(Interval() > 0) ) 158 | return normalform(AG(And(phi, p2), interval=Interval() < a)) 159 | 160 | """ 4.15 interval=(a,inf)""" 161 | if intvl.start_operator == operator.gt and a != 0 and \ 162 | intvl.end_operator == operator.lt and b == math.inf: 163 | p2 = AU(phi, psi, interval= Interval() > 0) 164 | return normalform(AG(And(phi, p2), interval=Interval() <= a)) 165 | 166 | """ 4.16 """ 167 | if intvl.start_operator == operator.ge and a != 0 and \ 168 | intvl.end_operator == operator.le and b != math.inf: 169 | p1 = Not(EF(EU(And(phi, Not(psi)), And(Not(phi), Not(psi))), interval=Interval() == a)) 170 | p2 = Not(EF(EU(Not(psi), True, interval=Interval() > (b-a)) ,interval=Interval() == a)) 171 | p3 = Not(EF(Not(phi), interval=Interval() < a)) 172 | return normalform(And(And(p1, p2), p3)) 173 | 174 | """ 4.17 """ 175 | if intvl.start_operator == operator.ge and a != 0 and \ 176 | intvl.end_operator == operator.le and b != math.inf: 177 | p1 = Not(EF(EU(And(phi, Not(psi)), And(Not(phi), Not(psi))), interval=(Interval() == a) )) 178 | p2 = Not(EF(EU(Not(psi), True, interval=Interval() >= (b - a)), interval=Interval() == a)) 179 | p3 = Not(EF(Not(phi), interval=Interval() < a)) 180 | return normalform(And(And(p1, p2), p3)) 181 | 182 | """ 4.18 """ 183 | if intvl.start_operator == operator.gt and a != 0 and \ 184 | intvl.end_operator == operator.lt and b != math.inf: 185 | a = AF( 186 | AU( phi, psi, interval=(Interval() > 0) <= (b-a)), 187 | interval=Interval() == a) 188 | b = AG(phi, interval=Interval() <= a) 189 | return normalform(And(a, b)) 190 | 191 | """ 4.19 """ 192 | if intvl.start_operator == operator.gt and a != 0 and \ 193 | intvl.end_operator == operator.lt and b != math.inf: 194 | a = AF( AU( phi, psi, interval=(Interval() > 0) < (b-a)), interval=Interval() == a) 195 | b = AG(phi, interval=Interval() <= a) 196 | return normalform(And(a, b)) 197 | 198 | # none of these cases 199 | return formula 200 | -------------------------------------------------------------------------------- /crestdsl/verification/reachabilitycalculator.py: -------------------------------------------------------------------------------- 1 | import methoddispatch 2 | 3 | from crestdsl.config import config 4 | 5 | from crestdsl import sourcehelper as SH 6 | 7 | from crestdsl.simulation.epsilon import Epsilon 8 | from crestdsl.config import to_python 9 | from crestdsl.simulation.z3calculator import Z3Calculator 10 | from crestdsl.verification import checklib 11 | 12 | import math # for infinity 13 | import z3 14 | 15 | from pprint import pformat 16 | import logging 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | 21 | class ReachabilityCalculator(methoddispatch.SingleDispatch, Z3Calculator): 22 | 23 | def create_system_constraints(self): 24 | logger.info("creating system constraints") 25 | 26 | @methoddispatch.singledispatch 27 | def isreachable(self, check, interval=None): 28 | raise ValueError(f"Don't now how to deal with checks of type {type(check)})") 29 | return False 30 | 31 | @isreachable.register(tuple) 32 | def isreachable_tuple(self, checks, interval=None): 33 | check = checklib.AndCheck(*list(checks)) 34 | return self.isreachable(check) 35 | 36 | @isreachable.register(list) 37 | def isreachable_list(self, checks, interval=None): 38 | check = checklib.AndCheck(*checks) 39 | return self.isreachable(check) 40 | 41 | @isreachable.register(checklib.Check) 42 | def isreachable_check(self, check, interval=None): 43 | logger.debug(f"Calculating whether all of the checks are reachable") 44 | solver = z3.Optimize() 45 | 46 | check_ports = check.get_ports() 47 | 48 | # build a mapping that shows the propagation of information to the guard (what influences the guard) 49 | modifier_map = self.get_modifier_map(check_ports) 50 | 51 | z3var_constraints, z3_vars = self.get_z3_vars(modifier_map) 52 | solver.add(z3var_constraints) 53 | 54 | if interval is None: 55 | solver.add(z3_vars['dt'] >= 0) 56 | logger.debug(f"Adding time start constraint: dt >= 0") 57 | else: 58 | interval = interval.resolve_infinitesimal() 59 | solver.add(interval.start_operator(z3_vars['dt'], interval.start)) 60 | # logger.debug(f"Adding time start constraint: {interval.start_operator(z3_vars['dt'], interval.start)}") 61 | if interval.end is not math.inf: # if it's infinity, just drop it... 62 | end = interval.end 63 | solver.add(interval.end_operator(z3_vars['dt'], end)) 64 | # logger.debug(f"Adding time end constraint: {interval.end_operator(z3_vars['dt'], end)}") 65 | 66 | # create the constraints for updates and influences 67 | for port, modifiers in modifier_map.items(): 68 | for modifier in modifiers: 69 | constraints = self._get_constraints_from_modifier(modifier, z3_vars) 70 | solver.add(constraints) 71 | 72 | conv = checklib.CheckZ3Converter(z3_vars) 73 | check_constraints = conv.convert(check) 74 | if logger.getEffectiveLevel() <= logging.DEBUG: # only log if the level is appropriate, since z3's string-conversion takes ages 75 | logger.debug(f"Check constraints: {check_constraints}") 76 | solver.add(check_constraints) 77 | 78 | objective = solver.minimize(z3_vars['dt']) # find minimal value of dt 79 | check = solver.check() 80 | # logger.debug("satisfiability: %s", check) 81 | if solver.check() == z3.sat: 82 | inf_coeff, numeric_coeff, eps_coeff = objective.lower_values() 83 | returnvalue = Epsilon(numeric_coeff, eps_coeff) 84 | logger.info(f"Minimum time to reach passing checks is {returnvalue}") 85 | return returnvalue 86 | elif check == z3.unknown: 87 | logger.warning(f"The calculation of the check reachability was UNKNOWN. This usually happening in the presence of non-linear constraints. Do we have any?") 88 | std_solver = z3.Solver() 89 | std_solver.add(solver.assertions()) 90 | std_solver_check = std_solver.check() 91 | if std_solver_check == z3.sat: 92 | min_dt = std_solver.model()[z3_vars['dt']] 93 | as_python = to_python(min_dt) 94 | logger.info(f"We did get a solution using the standard solver though: {as_python} Assuming that this is the smallest solution. CAREFUL THIS MIGHT BE WRONG!!!") 95 | return as_python 96 | elif std_solver_check == z3.unknown: 97 | logger.error(f"The standard solver was also not able to decide if there is a solution or not. The constraints are too hard!!!") 98 | return False 99 | else: 100 | logger.info("The standard solver says there is no solution to the constraints. This means we also couldn't minimize. Problem solved.") 101 | return False 102 | else: 103 | logger.debug(f"Constraint set to reach checks is unsatisfiable.") 104 | return False 105 | -------------------------------------------------------------------------------- /crestdsl/verification/simplify.py: -------------------------------------------------------------------------------- 1 | from functools import singledispatch 2 | import math 3 | 4 | from .tctl import * # I know, I know... but otherwise the formulas become unreadable !! 5 | from .tctl import AtomicProposition, NamedAtomicProposition # * in tctl only offers some of the classes, we need all ! 6 | 7 | """ Simplification, according to Page 167 - Table 3 """ 8 | # TODO: write test cases to check if they're actually working. 9 | 10 | @singledispatch 11 | def simplify(formula): 12 | # Don't know how to simplify. So do nothing. 13 | return formula 14 | 15 | @simplify.register(bool) 16 | def simplify_bool(formula): 17 | return formula 18 | 19 | @simplify.register(AtomicProposition) 20 | def simplify_AP(formula): 21 | return formula 22 | 23 | @simplify.register(Not) 24 | def simplify_Not(formula): 25 | phi = simplify(formula.phi) 26 | 27 | # 7.1 28 | if phi is True: 29 | return False 30 | # 7.2 31 | if phi is False: 32 | return True 33 | 34 | # 7.3 35 | if isinstance(phi, Or): 36 | return simplify(And(Not(phi.phi), Not(phi.psi))) 37 | 38 | # 7.4 39 | if isinstance(phi, And): 40 | return simplify(Or(Not(phi.phi), Not(phi.psi))) 41 | 42 | # 7.13 43 | if isinstance(phi, Not): 44 | return simplify(phi.phi) 45 | 46 | return Not(phi) 47 | 48 | 49 | @simplify.register(And) 50 | def simplify_And(formula): 51 | phi = simplify(formula.phi) 52 | psi = simplify(formula.psi) 53 | 54 | if phi is True and psi is True: 55 | return True 56 | 57 | # 7.7 and 7.8 58 | if phi is False or psi is False: 59 | return False 60 | # 7.5 61 | if phi is True: 62 | return psi 63 | # 7.6 64 | if psi is True: 65 | return phi 66 | 67 | return And(phi, psi) 68 | 69 | 70 | @simplify.register(Or) 71 | def simplify_Or(formula): 72 | phi = simplify(formula.phi) 73 | psi = simplify(formula.psi) 74 | 75 | if phi is False and psi is False: 76 | return False 77 | 78 | # 7.9 and 7.10 79 | if phi is True or psi is True: 80 | return True 81 | # 7.11 82 | if phi is False: 83 | return psi 84 | # 7.12 85 | if psi is False: 86 | return phi 87 | 88 | return Or(phi, psi) 89 | 90 | @simplify.register(EG) 91 | def simplify_EG(formula): 92 | phi = simplify(formula.phi) 93 | 94 | if phi is False: # 7.14 95 | return False 96 | elif phi is True: # 7.15 97 | return True 98 | else: 99 | return EG(phi) 100 | 101 | @simplify.register(EU) 102 | def simplify_EU(formula): 103 | phi = simplify(formula.phi) 104 | psi = simplify(formula.psi) 105 | 106 | if psi is False: # 7.16 107 | return False 108 | 109 | if psi is False: 110 | if formula.interval.ininterval(0): 111 | return psi # 7.18 112 | else: 113 | return False # 7.17 114 | 115 | if psi is True: 116 | if formula.interval.ininterval(0): 117 | return True # 7.19 118 | 119 | if phi is True and psi is True: 120 | if formula.interval.end == math.inf: 121 | return True # 7.20 122 | 123 | return EU(phi, psi, interval=formula.interval) 124 | 125 | 126 | @simplify.register(AU) 127 | def simplify_AU(formula): 128 | phi = simplify(formula.phi) 129 | psi = simplify(formula.psi) 130 | 131 | if psi is False: # 7.21 132 | return False 133 | 134 | if phi is False: # 7.23 135 | if formula.interval.ininterval(0): 136 | return phi 137 | else: # 7.22 138 | return False 139 | 140 | if psi is True: 141 | if formula.interval.ininterval(0): 142 | return True # 7.24 143 | 144 | if phi is True and psi is True: 145 | if formula.interval.end == math.inf: 146 | return True # 7.25 147 | 148 | return AU(phi, psi, interval=formula.interval) 149 | 150 | -------------------------------------------------------------------------------- /crestdsl/verification/small_normalform.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import math 3 | from functools import singledispatch 4 | 5 | from .tctl import * # I know, I know... but otherwise the formulas become unreadable !! 6 | from .tctl import AtomicProposition, TCTLFormula # * in tctl only offers some of the classes, we need all ! 7 | 8 | @singledispatch 9 | def small_normalform(formula): 10 | # any other thing: return standard formula 11 | return formula 12 | 13 | """ BASE FORMS (needed to apply recursion) """ 14 | 15 | @small_normalform.register(Not) 16 | def small_normalform_Not(formula): 17 | # any other thing: return standard formula 18 | return Not(small_normalform(formula.phi)) 19 | 20 | @small_normalform.register(And) 21 | def small_normalform_And(formula): 22 | phi = small_normalform(formula.phi) 23 | psi = small_normalform(formula.psi) 24 | return And(phi, psi) 25 | 26 | @small_normalform.register(EU) 27 | def small_normalform_EU(formula): 28 | phi = small_normalform(formula.phi) 29 | psi = small_normalform(formula.psi) 30 | return EU(phi, psi, interval=formula.interval) 31 | 32 | @small_normalform.register(AU) 33 | def small_normalform_AU(formula): 34 | phi = small_normalform(formula.phi) 35 | psi = small_normalform(formula.psi) 36 | return AU(phi, psi, interval=formula.interval) 37 | 38 | 39 | """ Transformations """ 40 | 41 | @small_normalform.register(bool) 42 | def small_normalform_bool(formula): 43 | if formula is False: 44 | formula = Not(True) 45 | return formula 46 | 47 | 48 | @small_normalform.register(Or) 49 | def small_normalform_Or(formula): 50 | phi = small_normalform(formula.phi) 51 | psi = small_normalform(formula.psi) 52 | return small_normalform(Not(And(Not(phi), Not(psi)))) 53 | 54 | @small_normalform.register(Implies) 55 | def small_normalform_Implies(formula): 56 | phi = small_normalform(formula.phi) 57 | psi = small_normalform(formula.psi) 58 | return small_normalform(Or(Not(phi), psi)) 59 | 60 | @small_normalform.register(Equality) 61 | def small_normalform_Equality(formula): 62 | phi = small_normalform(formula.phi) 63 | psi = small_normalform(formula.psi) 64 | return small_normalform(And(Implies(phi, psi), Implies(formula.psi, formula.phi))) 65 | 66 | @small_normalform.register(EF) 67 | def small_normalform_EF(formula): 68 | return small_normalform(EU(True, formula.phi, interval=formula.interval)) 69 | 70 | @small_normalform.register(AF) 71 | def small_normalform_AF(formula): 72 | return small_normalform(AU(True, formula.phi, interval=formula.interval)) 73 | 74 | @small_normalform.register(EG) 75 | def small_normalform_EG(formula): 76 | return small_normalform(Not(AF(Not(formula.phi), interval=formula.interval))) 77 | 78 | @small_normalform.register(AG) 79 | def small_normalform_AG(formula, recursive=False): 80 | return small_normalform(Not(EF(Not(formula.phi), interval=formula.interval))) 81 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/assets/crestlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/docs/assets/crestlogo.png -------------------------------------------------------------------------------- /docs/assets/crestlogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/assets/crestlogo_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/docs/assets/crestlogo_rectangle.png -------------------------------------------------------------------------------- /docs/assets/crestlogo_rounded_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/docs/assets/crestlogo_rounded_square.png -------------------------------------------------------------------------------- /docs/assets/crestlogo_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/docs/assets/crestlogo_square.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | You should be forwarded to the real webpage. If not, please click /here 4 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/readthedocs.requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-bibtex 2 | sphinx-autodoc-typehints 3 | 4 | z3-solver 5 | pwlf 6 | plotly 7 | graphviz 8 | networkx 9 | methoddispatch 10 | sklearn 11 | astor 12 | colored 13 | pandas 14 | numpy 15 | cufflinks -------------------------------------------------------------------------------- /docs/source/api/crestdsl.config.rst: -------------------------------------------------------------------------------- 1 | 2 | crestdsl.config module 3 | ======================= 4 | 5 | .. automodule:: crestdsl.config 6 | :members: 7 | :undoc-members: 8 | :show-inheritance: 9 | 10 | .. autoclass:: crestdsl.config.ConfigObject 11 | :members: 12 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.model.api.rst: -------------------------------------------------------------------------------- 1 | 2 | crestdsl.model.api package 3 | =========================== 4 | 5 | 6 | API Functions for Entities 7 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | .. autofunction:: crestdsl.model.api.get_name 9 | .. autofunction:: crestdsl.model.api.get_current 10 | .. autofunction:: crestdsl.model.api.get_root 11 | .. autofunction:: crestdsl.model.api.get_parent 12 | .. autofunction:: crestdsl.model.api.get_children 13 | .. autofunction:: crestdsl.model.api.get_sources 14 | .. autofunction:: crestdsl.model.api.get_targets 15 | 16 | 17 | 18 | Convenience API for Entities Creation 19 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | .. warning:: Only use these functions once the object has been created. 22 | (You can use them for example in the constructor.) 23 | 24 | .. autofunction:: crestdsl.model.api.add 25 | .. autofunction:: crestdsl.model.api.pullup 26 | .. autofunction:: crestdsl.model.api.relay 27 | .. autofunction:: crestdsl.model.api.dependencies 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.model.datatypes.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | crestdsl.model.datatypes module 4 | ================================ 5 | 6 | .. automodule:: crestdsl.model.datatypes 7 | :members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.model.rst: -------------------------------------------------------------------------------- 1 | crestdsl.model package 2 | ====================== 3 | 4 | .. contents:: 5 | :local: 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | :caption: Subpackages 10 | 11 | crestdsl.model.datatypes 12 | 13 | Domain Model 14 | ---------------------- 15 | 16 | .. autoclass:: crestdsl.model.Resource 17 | :members: 18 | 19 | .. autoclass:: crestdsl.model.Entity 20 | :members: 21 | :show-inheritance: 22 | 23 | Communication Interface 24 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | .. autoclass:: crestdsl.model.Input 27 | :members: 28 | :show-inheritance: 29 | 30 | .. autoclass:: crestdsl.model.Output 31 | :members: 32 | :show-inheritance: 33 | 34 | .. autoclass:: crestdsl.model.Local 35 | :members: 36 | :show-inheritance: 37 | 38 | Behaviour Automaton (Discrete Behaviour) 39 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 40 | 41 | .. autoclass:: crestdsl.model.State 42 | :members: 43 | :show-inheritance: 44 | 45 | .. autoclass:: crestdsl.model.Transition 46 | :members: 47 | :show-inheritance: 48 | 49 | .. autodecorator:: crestdsl.model.transition 50 | 51 | .. autoclass:: crestdsl.model.Action 52 | :members: 53 | :show-inheritance: 54 | 55 | .. autodecorator:: crestdsl.model.action 56 | 57 | Continuous Behaviour 58 | ~~~~~~~~~~~~~~~~~~~~~~~ 59 | 60 | .. autoclass:: crestdsl.model.Influence 61 | :members: 62 | :show-inheritance: 63 | 64 | .. autodecorator:: crestdsl.model.influence 65 | 66 | .. autoclass:: crestdsl.model.Update 67 | :members: 68 | :show-inheritance: 69 | 70 | .. autodecorator:: crestdsl.model.update 71 | 72 | Input/Output Dependencies 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | .. autoclass:: crestdsl.model.Dependency 76 | :members: 77 | :show-inheritance: 78 | 79 | 80 | .. autodecorator:: crestdsl.model.nodependencies 81 | 82 | 83 | .. autodecorator:: crestdsl.model.dependency 84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.model.systemcheck.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | crestdsl.model.systemcheck module 4 | ================================= 5 | 6 | .. autoclass:: crestdsl.model.SystemCheck 7 | :members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ===================== 3 | 4 | The crestdsl package is the root of the library. 5 | It wraps all other subpackages that are needed for the creation of and interaction with CREST systems. 6 | For example, the ``model`` package contains all APIs required for the creation of a crestdsl model, 7 | the ``simulation`` package provides various Simulator classes, and the ``ui`` package can be used to 8 | plot a CREST diagram of a model. 9 | 10 | 11 | .. warning:: This documentation is (unfortunately) not complete yet. 12 | Please bear with me. I will gradually extend it. 13 | 14 | 15 | .. toctree:: 16 | :name: Subpackages 17 | :maxdepth: 1 18 | :caption: Subpackages 19 | 20 | crestdsl.model 21 | crestdsl.model.systemcheck 22 | crestdsl.model.api 23 | 24 | crestdsl.simulation 25 | crestdsl.ui 26 | crestdsl.verification 27 | crestdsl.config 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.simulation.rst: -------------------------------------------------------------------------------- 1 | crestdsl.simulation package 2 | =========================== 3 | 4 | The simulation package contains everything that's required for the simulation of crestdsl systems. 5 | 6 | crestdsl provides in fact several simulator implementations for the exploration of model evolution. 7 | They mostly provide the same API and functionality and differ in minor details of the implementation. 8 | Below, three of them are presented. Their difference lies in the way they deal with non-determinism. 9 | CREST systems can be non-deterministic if two or more transitions are enabled from the same, 10 | currently active state. 11 | Despite the fact that non-determinism is a vital aspect of CREST to model the random execution, 12 | it is of high interest to also support its resolution and manual selection of the transition to follow, 13 | e.g. for the deliberate exploration of specific system behaviour. 14 | The dealing with such non-determinism is the discriminating difference between the individual simulators. 15 | 16 | The three simulator implementations treat such non-determinism as follows: 17 | 18 | 1. The first implementation, the most basic CREST simulator (``Simulator``), 19 | randomly chooses one enabled transition when it encounters non-determinism. 20 | It thus strictly follows the basic CREST semantics. 21 | This system is useful for initial explorations in non-deterministic 22 | models (e.g. to get a feeling for possible behaviour scenarios) 23 | and for general simulation of deterministic models. 24 | 25 | 2. Secondly, the ``InteractiveSimulator`` is used when it comes to manual exploration 26 | of a non-deterministic model. Anytime a situation with multiple enabled 27 | transitions is encountered, the simulator stops and prompts the 28 | user to make a choice. Evidently, this feature is meant for the 29 | exploration of smaller models, where such situations rarely occur. For 30 | highly non-deterministic models this form of exploration can become tedious. 31 | 32 | 3. The third simulator is the ``PlanSimulator``. 33 | It can be used for the programmatic simulation of a system trace. 34 | The simulation is launched with a list of commands that helps to orchestrate the execution. 35 | The command list contains information about advance and port setting actions. 36 | Additionally, the ports can specify which transition to take, in case non-determinism is encountered. 37 | Thus, the PlanSimulator can be even used to precisely specify a certain system evolution and chosen behaviour. 38 | This capacity is highly useful for unit testing or the analysis of specific execution schedules. 39 | The specification of large execution plans can quickly become overwhelming, 40 | especially for large systems. Usually, execution plans are therefore machine generated, 41 | e.g. in combination with a state space exploration or formal verification scenario. 42 | 43 | .. note:: This text is based on a part of Stefan Klikovits's PhD Thesis :cite:`Klikovits:PhDThesis:2019`. 44 | 45 | .. autoclass:: crestdsl.simulation.Simulator 46 | 47 | .. autoclass:: crestdsl.simulation.InteractiveSimulator 48 | :show-inheritance: 49 | 50 | .. autoclass:: crestdsl.simulation.PlanSimulator 51 | :show-inheritance: 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.ui.rst: -------------------------------------------------------------------------------- 1 | crestdsl.ui package 2 | =================== 3 | 4 | .. automodule:: crestdsl.ui 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | crestdsl.ui.dotter module 13 | ------------------------- 14 | 15 | .. automodule:: crestdsl.ui.dotter 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | crestdsl.ui.draw\_statespace module 21 | ----------------------------------- 22 | 23 | .. automodule:: crestdsl.ui.draw_statespace 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | crestdsl.ui.elk module 29 | ---------------------- 30 | 31 | .. automodule:: crestdsl.ui.elk 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/source/api/crestdsl.verification.rst: -------------------------------------------------------------------------------- 1 | crestdsl.verification package 2 | ============================= 3 | 4 | .. contents:: 5 | :local: 6 | 7 | Verifiable Properties (Checks) 8 | ------------------------------- 9 | 10 | .. autofunction:: crestdsl.verification.check 11 | 12 | 13 | Convenience API 14 | ----------------- 15 | 16 | .. autofunction:: crestdsl.verification.after 17 | 18 | .. autofunction:: crestdsl.verification.before 19 | 20 | 21 | 22 | .. autofunction:: crestdsl.verification.is_possible 23 | 24 | .. autofunction:: crestdsl.verification.always 25 | 26 | .. autofunction:: crestdsl.verification.always_possible 27 | 28 | .. autofunction:: crestdsl.verification.never 29 | 30 | .. autofunction:: crestdsl.verification.forever 31 | 32 | 33 | 34 | .. autoclass:: crestdsl.verification.Verifier 35 | :members: 36 | 37 | 38 | Expert Model Checking API 39 | -------------------------- 40 | 41 | .. autoclass:: crestdsl.verification.StateSpace 42 | :members: 43 | 44 | .. autoclass:: crestdsl.verification.ModelChecker 45 | 46 | TCTL API 47 | ~~~~~~~~~ 48 | 49 | .. autoclass:: crestdsl.verification.tctl.Interval 50 | :members: 51 | 52 | .. autoclass:: crestdsl.verification.tctl.TCTLFormula 53 | 54 | .. autoclass:: crestdsl.verification.tctl.Not 55 | :members: __init__ 56 | .. autoclass:: crestdsl.verification.tctl.And 57 | :members: __init__ 58 | .. autoclass:: crestdsl.verification.tctl.Or 59 | :members: __init__ 60 | .. autoclass:: crestdsl.verification.tctl.Implies 61 | :members: __init__ 62 | .. autoclass:: crestdsl.verification.tctl.Equality 63 | :members: __init__ 64 | 65 | 66 | .. autoclass:: crestdsl.verification.tctl.EU 67 | :members: __init__ 68 | .. autoclass:: crestdsl.verification.tctl.EF 69 | :members: __init__ 70 | .. autoclass:: crestdsl.verification.tctl.EG 71 | :members: __init__ 72 | 73 | .. autoclass:: crestdsl.verification.tctl.AU 74 | :members: __init__ 75 | .. autoclass:: crestdsl.verification.tctl.AF 76 | :members: __init__ 77 | .. autoclass:: crestdsl.verification.tctl.AG 78 | :members: __init__ 79 | 80 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../..')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'crestdsl' 23 | copyright = '2019, Stefan Klikovits' 24 | author = 'Stefan Klikovits' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '1.0.0' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.mathjax', 44 | 'sphinx.ext.napoleon', 45 | 'sphinx.ext.coverage', 46 | 'sphinxcontrib.bibtex', 47 | 'sphinx_autodoc_typehints' 48 | ] 49 | 50 | # Add any paths that contain templates here, relative to this directory. 51 | templates_path = ['_templates'] 52 | 53 | # The suffix(es) of source filenames. 54 | # You can specify multiple suffix as a list of string: 55 | # 56 | # source_suffix = ['.rst', '.md'] 57 | source_suffix = '.rst' 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This pattern also affects html_static_path and html_extra_path. 72 | exclude_patterns = [] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = None 76 | 77 | 78 | # -- Options for HTML output ------------------------------------------------- 79 | 80 | # The theme to use for HTML and HTML Help pages. See the documentation for 81 | # a list of builtin themes. 82 | # 83 | html_theme = 'sphinx_rtd_theme' 84 | html_logo = '../assets/crestlogo_rounded_square.png' 85 | # Theme options are theme-specific and customize the look and feel of a theme 86 | # further. For a list of options available for each theme, see the 87 | # documentation. 88 | 89 | # html_theme_options = {} 90 | 91 | # Add any paths that contain custom static files (such as style sheets) here, 92 | # relative to this directory. They are copied after the builtin static files, 93 | # so a file named "default.css" will overwrite the builtin "default.css". 94 | html_static_path = ['_static'] 95 | 96 | # Custom sidebar templates, must be a dictionary that maps document names 97 | # to template names. 98 | # 99 | # The default sidebars (for documents that don't match any pattern) are 100 | # defined by theme itself. Builtin themes are using these templates by 101 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 102 | # 'searchbox.html']``. 103 | # 104 | # html_sidebars = {} 105 | 106 | 107 | # -- Options for HTMLHelp output --------------------------------------------- 108 | 109 | # Output file base name for HTML help builder. 110 | htmlhelp_basename = 'crestdsldoc' 111 | 112 | 113 | # -- Options for LaTeX output ------------------------------------------------ 114 | 115 | latex_elements = { 116 | # The paper size ('letterpaper' or 'a4paper'). 117 | # 118 | # 'papersize': 'letterpaper', 119 | 120 | # The font size ('10pt', '11pt' or '12pt'). 121 | # 122 | # 'pointsize': '10pt', 123 | 124 | # Additional stuff for the LaTeX preamble. 125 | # 126 | # 'preamble': '', 127 | 128 | # Latex figure (float) alignment 129 | # 130 | # 'figure_align': 'htbp', 131 | } 132 | 133 | # Grouping the document tree into LaTeX files. List of tuples 134 | # (source start file, target name, title, 135 | # author, documentclass [howto, manual, or own class]). 136 | latex_documents = [ 137 | (master_doc, 'crestdsl.tex', 'crestdsl Documentation', 138 | 'Stefan Klikovits', 'manual'), 139 | ] 140 | 141 | 142 | # -- Options for manual page output ------------------------------------------ 143 | 144 | # One entry per manual page. List of tuples 145 | # (source start file, name, description, authors, manual section). 146 | man_pages = [ 147 | (master_doc, 'crestdsl', 'crestdsl Documentation', 148 | [author], 1) 149 | ] 150 | 151 | 152 | # -- Options for Texinfo output ---------------------------------------------- 153 | 154 | # Grouping the document tree into Texinfo files. List of tuples 155 | # (source start file, target name, title, author, 156 | # dir menu entry, description, category) 157 | texinfo_documents = [ 158 | (master_doc, 'crestdsl', 'crestdsl Documentation', 159 | author, 'crestdsl', 'One line description of project.', 160 | 'Miscellaneous'), 161 | ] 162 | 163 | 164 | # -- Options for Epub output ------------------------------------------------- 165 | 166 | # Bibliographic Dublin Core info. 167 | epub_title = project 168 | 169 | # The unique identifier of the text. This can be a ISBN number 170 | # or the project homepage. 171 | # 172 | # epub_identifier = '' 173 | 174 | # A unique identification for the text. 175 | # 176 | # epub_uid = '' 177 | 178 | # A list of files that should not be packed into the epub file. 179 | epub_exclude_files = ['search.html'] 180 | 181 | 182 | # -- Extension configuration ------------------------------------------------- 183 | -------------------------------------------------------------------------------- /docs/source/content/contact.rst: -------------------------------------------------------------------------------- 1 | Get in Touch! 2 | ===================== 3 | 4 | We are very proud of crestdsl, but we'd also like to hear from you. 5 | 6 | If you think that there is something we should improve, 7 | please leave an issue on `Github `_. 8 | 9 | We also appreciate any other kind of feedback. 10 | So don't be shy, you're warmly encouraged to leave supportive comments, 11 | constructive criticism or cheerful wishes either on Github, 12 | or contact Stefan via email (stefan.klikovits@unige.ch). 13 | 14 | 15 | Help Wanted! 16 | ------------- 17 | 18 | We're still working on a dedicated developer/contributor tutorial. 19 | 20 | In the meantime, if you like what you see and 21 | want to help us make crestdsl even better, 22 | don't hesitate to get in touch. 23 | We'll do everything we can to help you get started. 24 | -------------------------------------------------------------------------------- /docs/source/content/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ------------- 3 | 4 | This page describes the several ways of using crestdsl. 5 | Additionally to the local execution here, you can try crestdsl in your browser 6 | using Jupyter and Binder, as described on the :doc:`../index` page. 7 | 8 | Docker 9 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 10 | 11 | crestdsl is built upon various Python packages. 12 | The probably easiest way to start using crestdsl is via Docker. 13 | The crestdsl image contains the latest release of crestdsl and all dependencies. 14 | The image is based on Jupyter's docker stacks, so you develop right in your browser. 15 | 16 | .. code-block:: none 17 | 18 | docker run -p 8888:8888 crestdsl/release 19 | 20 | Remember to map the image's port `8888` to one of your host machine's ports (here `8888` too). 21 | 22 | **Local file storage** 23 | 24 | If you want to your persist files, 25 | you should look into mounting a volume using docker's 26 | `-v directive `_, like this: 27 | 28 | .. code-block:: none 29 | 30 | docker run -p 8888:8888 -v $(pwd):/home/jovyan/my_notebooks crestdsl/release 31 | 32 | 33 | .. note:: The docker image is quite large to download (~2GB). 34 | Make sure to have a good internet connection (or go grab a coffee). 35 | 36 | 37 | 38 | Local Installation 39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 40 | 41 | .. code-block:: none 42 | 43 | pip install crestdsl 44 | 45 | Alternatively, to install from sources, clone the repository and install locally. 46 | 47 | .. code-block:: none 48 | 49 | git clone https://github.com/crestdsl/CREST.git 50 | cd CREST 51 | pip install -e . 52 | 53 | .. note:: crestdsl was developed using Python 3.6. 54 | You will certainly have problems using any version of Python 2. 55 | For versions < 3.6, you might run into issues, I haven't tested it. 56 | 57 | **Required: Patched Z3 Prover** 58 | 59 | crestdsl makes heavy use of Microsoft Research's Z3 Solver. 60 | In order to increase usability, we added some minor improvements. 61 | The sources of this custom Z3 fork can be found at https://github.com/stklik/z3.git 62 | 63 | To install it, you will need Python, `make`, and a compiler such as GCC or Clang, as explained 64 | on the official `Z3 repository `_. 65 | If you have all these dependencies, you can run the following code to automatically install it. 66 | 67 | .. code-block:: none 68 | 69 | bash <(curl -fsSL http://github.com/crestdsl/CREST) 70 | 71 | 72 | **Optional: Jupyter** 73 | 74 | crestdsl has been developed to work well inside `Jupyter `_ notebooks. 75 | However, by default it will not install it. 76 | To make use of this very interactive Python engine, install it verification 77 | .. code-block:: none 78 | 79 | pip install jupyter 80 | 81 | For other means of installation or further information, visit the `Jupyter installation page `_ directly. 82 | 83 | 84 | **Optional: Graphviz** 85 | 86 | Some crestdsl modules require additional installed resources. 87 | For example, the `crestdsl.ui.dotter` module allows the plotting of system models using the graphviz package. 88 | Use your system's package manager / `Homebrew `_ to install the following package on Linux / OS X: 89 | 90 | * `graphviz` 91 | * `libgraphviz-dev` 92 | * `dvipng` 93 | 94 | For Windows users: I honestly have not tried it, 95 | but please let me know if you made it work. 96 | -------------------------------------------------------------------------------- /docs/source/content/simulation.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Simulation 4 | ========== 5 | 6 | Simulation allows us to execute the model and see its evolution. 7 | ``crestdsl``\ ’s simulator is located in the ``simultion`` module. In 8 | order to use it, we have to import it. 9 | 10 | .. code:: python 11 | 12 | # import the simulator 13 | from crestdsl.simulation import Simulator 14 | 15 | After the import, we can use a simulator by initialising it with a 16 | system model. In our case, we will explore the ``GrowLamp`` system that 17 | we defined above. 18 | 19 | .. code:: python 20 | 21 | gl = GrowLamp() 22 | sim = Simulator(gl) 23 | 24 | Stabilisation 25 | ------------- 26 | 27 | The simulator will execute the system’s transitions, updates and 28 | influences until reaching a fixpoint. This process is referred to as 29 | *stabilisation*. Once stable, there are no more transitions can be 30 | triggered and all updates/influences/actions have been executed. After 31 | stabilisation, all ports have their correct values, calculted from 32 | preceeding ports. 33 | 34 | In the GrowLamp, we see that the value’s of the ``temperature_out`` and 35 | ``light_out`` ports are wrong (based on the dummy values we defined as 36 | their initial values). After triggering the stabilisation, these values 37 | have been corrected. 38 | 39 | The simulator also has a convenience API ``plot()`` that allows the 40 | direct plotting of the entity, without having to import and call the 41 | ``elk`` library. 42 | 43 | .. code:: python 44 | 45 | sim.stabilise() 46 | sim.plot() 47 | 48 | Stabilisaiton also has to be called after the modification of input 49 | values, such that the new values are used to update any dependent ports. 50 | Further, all transitions have to be checked on whether they are enabled 51 | and executed if they are. 52 | 53 | Below, we show the modification of the growlamp and stabilisation. 54 | Compare the plot below to the plot above to see that the information has 55 | been updated. 56 | 57 | .. code:: python 58 | 59 | # modify the growlamp instance's inputs directly, the simulator points to that object and will use it 60 | gl.electricity_in.value = 500 61 | gl.switch_in.value = "on" 62 | sim.stabilise() 63 | sim.plot() 64 | 65 | Time advance 66 | ------------ 67 | 68 | Evidently, we also want to simulate the behaviour over time. The 69 | simulator’s ``advance(dt)`` method does precisely that, by advancing 70 | ``dt`` time units. 71 | 72 | Below we advance 500 time steps. The effect is that the global system 73 | time is now ``t=500`` (see the growing lamp’s title bar). Additionally, 74 | the local variable ``on_time``, which sums up the total amount of time 75 | the automaton has spent in the ``on`` state, has the value of 500 too - 76 | Just as expected! 77 | 78 | .. code:: python 79 | 80 | sim.advance(500) 81 | sim.plot() -------------------------------------------------------------------------------- /docs/source/content/verification.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Verification 4 | ============= 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. _index-page: 2 | 3 | `crestdsl` - Welcome 4 | ===================== 5 | 6 | |build-badge| |pypi-badge| |doc-badge| |cov-badge| (I know, I know, I'm really busy though...) 7 | 8 | .. |build-badge| image:: https://travis-ci.org/crestdsl/CREST.svg?branch=master 9 | :target: https://travis-ci.org/crestdsl/CREST 10 | 11 | .. |doc-badge| image:: https://readthedocs.org/projects/crestdsl/badge/?version=latest 12 | :target: https://crestdsl.readthedocs.io/?badge=latest 13 | 14 | .. |cov-badge| image:: https://codecov.io/gh/crestdsl/CREST/branch/master/graph/badge.svg 15 | :target: https://codecov.io/gh/crestdsl/CREST 16 | 17 | .. |pypi-badge| image:: https://img.shields.io/pypi/v/crestdsl.svg?color=blue 18 | :alt: PyPI 19 | :target: https://pypi.org/project/crestdsl/ 20 | 21 | 22 | crestdsl is an implementation of the CREST modelling language in Python. 23 | CREST is a formalism for hybrid systems modelling. 24 | It supports hierarchical component compositiion, 25 | synchronous communication, automaton-based discrete behaviour and a formal semantics. 26 | 27 | crestdsl is an internal domain-specific language (DSL) and shipped as Python package. 28 | This means you can run it on pretty much any operating system! 29 | 30 | crestdsl supports various use cases that include model creation and plotting, 31 | simulation and verification (using TCTL model checking). 32 | The main focus lies on simplicity and ease of use. 33 | 34 | 35 | .. _try-me: 36 | 37 | Try Me! (Browser-Demo) 38 | ---------------------- 39 | 40 | You can try crestdsl directly in your browser. 41 | Just click the badge and wait for crestdsl to be loaded into a Jupyter instance inside your browser: |crestdsl-demo| 42 | 43 | .. |crestdsl-demo| image:: https://img.shields.io/badge/crestdsl-demo-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC 44 | :target: https://mybinder.org/v2/gh/crestdsl/crestdsl-demo-binder/master?filepath=GettingStarted.ipynb 45 | 46 | .. note:: This live demo is made possible due to the effort of the `Binder `_ project. Thanks! 47 | 48 | You can use this link to launch an instance with some more system modelling examples: |more-demo| 49 | 50 | .. |more-demo| image:: https://img.shields.io/badge/more-crestdsl-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC 51 | :target: https:// 52 | 53 | 54 | Quick-Install 55 | -------------- 56 | 57 | To run crestdsl on your machine, use the pre-built Docker image: 58 | 59 | .. code-block:: none 60 | 61 | docker run -p 8888:8888 crestdsl/release 62 | 63 | For installation of crestdsl on your local machine and a description of dependencies, refer to :doc:`content/install`. 64 | 65 | 66 | 67 | Publications 68 | ------------- 69 | 70 | .. bibliography:: publications.bib 71 | :all: 72 | 73 | .. toctree:: 74 | :hidden: 75 | 76 | Home 77 | 78 | .. toctree:: 79 | :name: Main Content 80 | :maxdepth: 2 81 | :caption: Content 82 | 83 | content/install 84 | content/modelling 85 | content/simulation 86 | content/verification 87 | content/contact 88 | api/crestdsl 89 | 90 | 91 | .. Indices and tables 92 | ================== 93 | 94 | * :ref:`genindex` 95 | * :ref:`modindex` 96 | * :ref:`search` 97 | -------------------------------------------------------------------------------- /docs/source/publications.bib: -------------------------------------------------------------------------------- 1 | %% GEMOC 2017 - CREST 2 | @techreport{Klikovits:CRESTFormalization, 3 | keywords = {klikovits}, 4 | author = {Stefan Klikovits and Alban Linard and Didier Buchs}, 5 | author+an = {1=highlight}, 6 | title = {{CREST} Formalization}, 7 | institution = {Software Modeling and Verification Group, University of Geneva}, 8 | doi = {10.5281/zenodo.1284561}, 9 | year = {2018} 10 | } 11 | 12 | @inproceedings{DBLP:conf/models/KlikovitsLB17, 13 | keywords = {klikovits}, 14 | author = {Stefan Klikovits and 15 | Alban Linard and 16 | Didier Buchs}, 17 | author+an = {1=highlight}, 18 | title = {{CREST - A Continuous, REactive SysTems DSL}}, 19 | booktitle = {5th International Workshop on the {Globalization of Modeling Languages} {(GEMOC} 2017)}, 20 | pages = {286--291}, 21 | year = {2017}, 22 | url = {http://ceur-ws.org/Vol-2019/gemoc_2.pdf}, 23 | volume = 2019, 24 | series = {CEUR Workshop Proceedings}, 25 | } 26 | 27 | % SAM 2018 28 | @InProceedings{Klikovits:SAM18:CREST, 29 | keywords = {klikovits}, 30 | title = {{CREST} - {A} {DSL} for {Reactive Cyber-Physical Systems}}, 31 | author = {Stefan Klikovits and Alban Linard and Didier Buchs}, 32 | author+an = {1=highlight}, 33 | year = {2018}, 34 | month = {01}, 35 | pages = {29-45}, 36 | publisher = {Springer}, 37 | booktitle = {10th System Analysis and Modeling Conference (SAM 2018)}, 38 | xxxbooktitle="System Analysis and Modeling. Languages, Methods, and Tools for Systems Engineering", 39 | editor="Khendek, Ferhat and Gotzhein, Reinhard", 40 | series={Lecture Notes in Computer Science}, 41 | volume={11150}, 42 | doi={10.1007/978-3-030-01042-3_3}, 43 | isbn = {978-3-030-01041-6} 44 | } 45 | 46 | 47 | % MDE4IOT 48 | @inproceedings{Klikovits:MDE4IOT:CREST, 49 | keywords = {klikovits}, 50 | title={{ML4CREST: Machine Learning for {CPS} Models}}, 51 | author={Stefan Klikovits and Aur\'{e}lien Coet and Didier Buchs}, 52 | author+an = {1=highlight}, 53 | year = {2018}, 54 | month = {01}, 55 | pages = {515-520}, 56 | url = {http://ceur-ws.org/Vol-2245/mde4iot_paper_4.pdf}, 57 | volume = 2245, 58 | series = {CEUR Workshop Proceedings}, 59 | booktitle = {2nd International Workshop on Model-Driven Engineering for the Internet-of-Things (MDE4IoT) at MODELS'18} 60 | } 61 | 62 | % 63 | @phdthesis{Klikovits:PhDThesis:2019, 64 | author = {Stefan Klikovits}, 65 | title = {A Domain-Specific Language Approach to Hybrid CPS Modelling}, 66 | year = {2019}, 67 | school = {University of Geneva}, 68 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | importlib_resources 2 | colored>=1.3 3 | methoddispatch>=2 4 | matplotlib 5 | graphviz 6 | pygraphviz>=1.5 7 | astor>=0.7 8 | pandas>0.1 9 | numpy 10 | networkx>=2.2 11 | plotly>=3.5 12 | cufflinks>=0.14 13 | pwlf>=0.3 14 | importnb 15 | sklearn -------------------------------------------------------------------------------- /scripts/docker-build.sh: -------------------------------------------------------------------------------- 1 | docker build -t crest:latest . -------------------------------------------------------------------------------- /scripts/docker-run-tests.sh: -------------------------------------------------------------------------------- 1 | # runs the tests in a docker 2 | python3 setup.py sdist bdist_wheel > /dev/null 3 | docker run --rm \ 4 | -v ${PWD}/dist:/dist \ 5 | -v ${PWD}/scripts:/scripts \ 6 | -v ${PWD}/tests:/tests \ 7 | python scripts/install-run-tests.sh 8 | -------------------------------------------------------------------------------- /scripts/docker-run.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -p 8888:8888 -v $(pwd):/home/jovyan/CREST -e JUPYTER_ENABLE_LAB=yes crest -------------------------------------------------------------------------------- /scripts/install-run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pip install --upgrade pip 3 | ls -al /dist 4 | pip install /dist/* 5 | cd /tests 6 | python -m unittest discover --verbose 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # build: python setup.py sdist bdist_wheel 2 | # upload to test-pypi: python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* 3 | # upload to pypi: python -m twine upload dist/* 4 | 5 | import setuptools 6 | 7 | with open('requirements.txt', "r") as f: 8 | requires = f.read().split() 9 | 10 | with open("README.md", "r") as fh: 11 | long_description = fh.read() 12 | 13 | setuptools.setup( 14 | name="crestdsl", 15 | version="0.5.2", 16 | author="Stefan Klikovits", 17 | author_email="crestdsl@klikovits.net", 18 | description="A Continuous REactive SysTems DSL", 19 | long_description=long_description, 20 | long_description_content_type="text/markdown", 21 | url="https://github.com/crestdsl/CREST", 22 | packages=setuptools.find_packages(), 23 | setup_requires=['wheel'], 24 | install_requires=requires, 25 | include_package_data=True, 26 | classifiers=[ 27 | "Programming Language :: Python :: 3", 28 | "License :: OSI Approved :: MIT License", 29 | "Operating System :: OS Independent", 30 | ], 31 | test_suite='nose.collector', 32 | tests_require=['nose'], 33 | ) 34 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/tests/__init__.py -------------------------------------------------------------------------------- /tests/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/tests/model/__init__.py -------------------------------------------------------------------------------- /tests/model/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/tests/model/api/__init__.py -------------------------------------------------------------------------------- /tests/model/test_ports.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import crestdsl.model as crest 3 | 4 | real_res = crest.Resource("RealRes", crest.REAL) 5 | int_res = crest.Resource("intres", crest.INT) 6 | string_res = crest.Resource("stringres", crest.STRING) 7 | bool_res = crest.Resource("boolres", crest.BOOL) 8 | list_res = crest.Resource("ListRes", [1, 2, "five"]) 9 | 10 | 11 | class Test_Ports(unittest.TestCase): 12 | 13 | def test_raise_error_on_None_assignment(self): 14 | p = crest.Input(resource=int_res, value=0) 15 | with self.assertRaises(AssertionError): 16 | p.value = None 17 | 18 | def test_assign_int_value_to_real_port(self): 19 | p = crest.Input(resource=real_res, value=0) 20 | p.value = 15 21 | 22 | def test_assign_float_value_to_real_port(self): 23 | p = crest.Input(resource=real_res, value=0) 24 | p.value = 3.1415 25 | 26 | def test_assign_int_value_to_int_port(self): 27 | p = crest.Input(resource=int_res, value=0) 28 | p.value = 15 29 | 30 | def test_assign_float_value_to_int_port_should_fail(self): 31 | p = crest.Input(resource=int_res, value=0) 32 | with self.assertRaises(AssertionError): 33 | p.value = 3.1415 34 | 35 | def test_assign_string_value_to_int_port_should_fail(self): 36 | p = crest.Input(resource=int_res, value=0) 37 | with self.assertRaises(AssertionError): 38 | p.value = "a_string_value" 39 | 40 | def test_assign_wrong_value_to_real_port_should_fail(self): 41 | p = crest.Input(resource=real_res, value=0) 42 | with self.assertRaises(AssertionError): 43 | p.value = "abcdefg" 44 | 45 | def test_assign_value_to_list_port___number(self): 46 | p = crest.Input(resource=list_res, value=0) 47 | p.value = 1 48 | 49 | def test_assign_value_to_list_port___string(self): 50 | p = crest.Input(resource=list_res, value=0) 51 | p.value = "five" 52 | 53 | def test_assign_wrong_value_to_list_port_should_fail(self): 54 | p = crest.Input(resource=list_res, value=0) 55 | with self.assertRaises(AssertionError): 56 | p.value = "abcdefg" 57 | 58 | def test_assign_wrong_value_to_list_port_should_fail___number_as_string(self): 59 | p = crest.Input(resource=list_res, value=0) 60 | with self.assertRaises(AssertionError): 61 | p.value = "2" 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /tests/simulator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/tests/simulator/__init__.py -------------------------------------------------------------------------------- /tests/simulator/test_dependencyOrder.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import crestdsl.model as crest 3 | from crestdsl.simulation.dependencyOrder import ordered_modifiers 4 | 5 | testRes = crest.Resource("float-resource", crest.Types.REAL) 6 | class TestSubEntity(crest.Entity): 7 | in1 = crest.Input(resource=testRes, value=3) 8 | in2 = crest.Input(resource=testRes, value=3) 9 | out1 = crest.Output(resource=testRes, value=3) 10 | out2 = crest.Output(resource=testRes, value=3) 11 | 12 | class Test_getDependencyOrder(unittest.TestCase): 13 | 14 | def test_subentity_no_dependencies(self): 15 | class TestEntity(crest.Entity): 16 | sub = TestSubEntity() 17 | ent = TestEntity() 18 | 19 | self.assertListEqual([ent.sub], ordered_modifiers(ent)) 20 | 21 | def test_assert_throws_cyclic_exception(self): 22 | class TestEntity(crest.Entity): 23 | sub = TestSubEntity() 24 | inf = crest.Influence(source=sub.out1, target=sub.in1) 25 | ent = TestEntity() 26 | 27 | self.assertRaises(AssertionError, ordered_modifiers, ent) 28 | 29 | def test_assert_dependency_spec_breaks_cycle(self): 30 | class TestEntity(crest.Entity): 31 | sub = TestSubEntity() 32 | inf = crest.Influence(source=sub.out1, target=sub.in1) 33 | ent = TestEntity() 34 | ent.sub._dependencies = [ 35 | crest.Dependency(ent.sub.out1, ent.sub.in2) 36 | ] 37 | 38 | self.assertListEqual([ent.sub, ent.inf, ent.sub], ordered_modifiers(ent)) 39 | 40 | def test_assert_dependency_spec_breaks_cycle2(self): 41 | class TestEntity(crest.Entity): 42 | sub = TestSubEntity() 43 | inf = crest.Influence(source=sub.out1, target=sub.in1) 44 | ent = TestEntity() 45 | ent.sub._dependencies = [ 46 | crest.Dependency(ent.sub.out1, ent.sub.in1), 47 | crest.Dependency(ent.sub.out2, ent.sub.in2) 48 | ] 49 | 50 | self.assertRaises(AssertionError, ordered_modifiers, ent) 51 | 52 | 53 | def test_assert_dependency_spec_breaks_cycle_need_repetition(self): 54 | class TestEntity(crest.Entity): 55 | sub = TestSubEntity() 56 | inf = crest.Influence(source=sub.out1, target=sub.in1) 57 | ent = TestEntity() 58 | ent.sub._dependencies = [ 59 | crest.Dependency(ent.sub.out1, ent.sub.in2), 60 | crest.Dependency(ent.sub.out2, ent.sub.in1) 61 | ] 62 | 63 | self.assertListEqual([ent.sub, ent.inf, ent.sub], ordered_modifiers(ent)) 64 | 65 | def test_active_and_inactive_updates(self): 66 | class TestEntity(crest.Entity): 67 | sub = TestSubEntity() 68 | inf = crest.Influence(source=sub.out1, target=sub.in1) 69 | 70 | active = current = crest.State() 71 | inactive = crest.State() 72 | 73 | up_active = crest.Update(state=active, target=sub.in2, function=(lambda self, dt: 0)) 74 | up_inactive = crest.Update(state=inactive, target=sub.in2, function=(lambda self, dt: 0)) 75 | 76 | ent = TestEntity() 77 | 78 | ent.sub._dependencies = [ 79 | crest.Dependency(ent.sub.out1, ent.sub.in2), 80 | ] 81 | 82 | self.assertListEqual([ent.up_active, ent.sub, ent.inf, ent.sub], ordered_modifiers(ent)) 83 | 84 | 85 | def test_active_and_inactive_updates_two_subentities(self): 86 | class TestEntity(crest.Entity): 87 | sub = TestSubEntity() 88 | sub2 = TestSubEntity() 89 | inf = crest.Influence(source=sub.out1, target=sub.in1) 90 | influence2 = crest.Influence(source=sub.out2, target=sub2.in1) 91 | 92 | active = current = crest.State() 93 | inactive = crest.State() 94 | 95 | up_active = crest.Update(state=active, target=sub.in2, function=(lambda self, dt: 0)) 96 | up_inactive = crest.Update(state=inactive, target=sub.in2, function=(lambda self, dt: 0)) 97 | 98 | ent = TestEntity() 99 | 100 | ent.sub._dependencies = [ 101 | crest.Dependency(ent.sub.out1, ent.sub.in2), 102 | crest.Dependency(ent.sub.out2, ent.sub.in1) 103 | ] 104 | 105 | self.assertListEqual([ent.up_active, ent.sub, ent.inf, ent.sub, ent.influence2, ent.sub2], ordered_modifiers(ent)) 106 | -------------------------------------------------------------------------------- /tests/simulator/test_epsilon.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from crestdsl.simulation.epsilon import Epsilon, eps 3 | 4 | class EpsilonTest(unittest.TestCase): 5 | 6 | def test_eps_basic(self): 7 | self.assertEqual(Epsilon(epsilon=1), eps) 8 | self.assertEqual(Epsilon(epsilon=1)-Epsilon(epsilon=1), eps-eps) 9 | self.assertEqual(Epsilon(epsilon=1)-Epsilon(epsilon=1), 0) 10 | self.assertEqual(eps, eps) 11 | 12 | def test_eps_comparisons(self): 13 | self.assertLess(0, eps) 14 | self.assertLess(eps, 0.000000001) 15 | self.assertLess(eps, 0.1) 16 | self.assertLess(eps, 1) 17 | 18 | self.assertLessEqual(0, eps) 19 | self.assertLessEqual(eps, 0.000000001) 20 | self.assertLessEqual(eps, 0.1) 21 | self.assertLessEqual(eps, 1) 22 | 23 | self.assertGreater(eps, 0) 24 | self.assertGreater(0.00000000001, eps) 25 | self.assertGreater(0.1, eps) 26 | self.assertGreater(1, eps) 27 | 28 | self.assertGreaterEqual(eps, 0) 29 | self.assertGreaterEqual(0.00000000001, eps) 30 | self.assertGreaterEqual(0.1, eps) 31 | self.assertGreaterEqual(1, eps) 32 | 33 | self.assertLess(eps + eps + eps, eps + eps + eps + eps) 34 | self.assertLessEqual(eps + eps + eps, eps + eps + eps) 35 | 36 | 37 | self.assertEqual(min(1, 2+eps, 3-eps, 1-eps, 1+eps), 1-eps) 38 | self.assertEqual(max(1, -2+eps, -3-eps, 1-eps, 1+eps), 1+eps) 39 | 40 | def test_eps_math(self): 41 | 42 | self.assertEqual(Epsilon(-1, 1), eps-1) 43 | self.assertEqual(Epsilon(1, -1), 1-eps) 44 | 45 | self.assertEqual(Epsilon(2, -1), 2 - eps) 46 | 47 | self.assertEqual(Epsilon(2, 1), 2 + eps) 48 | self.assertEqual(Epsilon(2, -1), 2 - eps) 49 | self.assertEqual(Epsilon(5, -2), 5 - eps - eps) 50 | self.assertEqual(Epsilon(3, 2), 3 + eps + eps) 51 | 52 | self.assertEqual(Epsilon(2, 1) + 2, 4 + eps) 53 | self.assertEqual(Epsilon(2, 1) + Epsilon(2, 1), 4 + eps + eps) 54 | 55 | self.assertEqual(Epsilon(2, 1) - 2, eps) 56 | self.assertEqual(Epsilon(2, 1) - Epsilon(2, 1), 0) 57 | 58 | self.assertEqual(Epsilon(2, 1) - 2, eps) 59 | self.assertEqual(Epsilon(2, 1) - Epsilon(2, 1), 0) 60 | 61 | 62 | self.assertEqual(-Epsilon(2, 1), Epsilon(-2, -1)) 63 | self.assertEqual(-Epsilon(2, -1), Epsilon(-2, 1)) 64 | self.assertEqual(-Epsilon(), Epsilon()) 65 | 66 | self.assertEqual(30 - (25 + eps), 5 - eps) 67 | 68 | 69 | def test_eps_str(self): 70 | self.assertEqual(str(eps), "\u03B5") 71 | self.assertEqual(str(-eps), "-\u03B5") 72 | self.assertEqual(str(Epsilon(2,1)), "2 + \u03B5") 73 | self.assertEqual(str(Epsilon(3,-1)), "3 - \u03B5") 74 | self.assertEqual(str(Epsilon(2,2)), "2 + 2 * \u03B5") 75 | self.assertEqual(str(Epsilon(3,-3)), "3 - 3 * \u03B5") 76 | -------------------------------------------------------------------------------- /tests/simulator/test_plansimulator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import unittest.mock as mock 3 | import crestdsl.model as crest 4 | import crestdsl.simulation as sim 5 | import copy 6 | 7 | class PlanSimulatorTest(unittest.TestCase): 8 | 9 | def setUp(self): 10 | res = crest.Resource("testres", crest.REAL) 11 | class TestEntity(crest.Entity): 12 | in_port = crest.Input(res, 12) 13 | in_port2 = crest.Input(res, 33) 14 | state = current = crest.State() 15 | other_state = crest.State() 16 | 17 | # the evaluation of the guard of these will be mocked anyway 18 | transA = crest.Transition(source=state, target=other_state, guard=(lambda self: True)) 19 | transB = crest.Transition(source=state, target=other_state, guard=(lambda self: True)) 20 | 21 | 22 | self.testentity = TestEntity() 23 | self.testentity.sub1 = TestEntity() # create a subetity that's just 24 | self.testentity.sub2 = TestEntity() # create a subetity that's just 25 | 26 | def tearDown(self): 27 | self.testentity = None 28 | 29 | def test_None_action_raises_error(self): 30 | psim = sim.PlanSimulator(self.testentity) 31 | 32 | with self.assertRaises(ValueError) as context: 33 | psim.run_plan( [None] ) 34 | 35 | self.assertEqual(str(context.exception),"Don't know how to act for plan item:\nNone.") 36 | 37 | def test_String_action_raises_error(self): 38 | psim = sim.PlanSimulator(self.testentity) 39 | 40 | with self.assertRaises(ValueError) as context: 41 | psim.run_plan( ["ABCDEFG"] ) 42 | 43 | self.assertEqual(str(context.exception),f"Don't know how to act for plan item:\n'ABCDEFG'.") 44 | 45 | def test_number_triggers_advance_int(self): 46 | psim = sim.PlanSimulator(self.testentity) 47 | psim.advance = mock.MagicMock() 48 | 49 | psim.run_plan( [1] ) 50 | psim.advance.assert_called_with(1) 51 | 52 | def test_number_triggers_advance_float(self): 53 | psim = sim.PlanSimulator(self.testentity) 54 | psim.advance = mock.MagicMock() 55 | 56 | psim.run_plan( [3.186] ) 57 | psim.advance.assert_called_with(3.186) 58 | 59 | def test_number_triggers_advance_negative(self): 60 | psim = sim.PlanSimulator(self.testentity) 61 | psim.advance = mock.MagicMock() 62 | 63 | psim.run_plan( [-15.16] ) 64 | psim.advance.assert_called_with(-15.16) 65 | 66 | def test_number_triggers_advance_negative(self): 67 | psim = sim.PlanSimulator(self.testentity) 68 | epsilon_val = sim.Epsilon(3,-8) 69 | psim.advance = mock.MagicMock() 70 | 71 | psim.run_plan( [epsilon_val] ) 72 | psim.advance.assert_called_with(epsilon_val) 73 | 74 | def test_multiple_numbers_triggers_all_advances(self): 75 | psim = sim.PlanSimulator(self.testentity) 76 | psim.advance = mock.MagicMock() 77 | psim.run_plan( [1, 3.186, -15.16] ) 78 | 79 | calls = [mock.call(1), mock.call(3.186), mock.call(-15.16)] 80 | psim.advance.assert_has_calls(calls) 81 | 82 | def test_multiple_numbers_triggers_advances_no_mock(self): 83 | psim = sim.PlanSimulator(self.testentity) 84 | epsilon_val = sim.Epsilon(3,-8) 85 | psim.run_plan( [1, 3.186, -15.16, epsilon_val] ) 86 | 87 | self.assertEqual(psim.global_time, 1 + 3.186 + epsilon_val) 88 | 89 | def test_portvalue_dict_triggers_set_values(self): 90 | psim = sim.PlanSimulator(self.testentity) 91 | 92 | psim.run_plan( [{self.testentity.in_port: 123, self.testentity.in_port2: -33}] ) 93 | 94 | self.assertEqual(self.testentity.in_port.value, 123) 95 | self.assertEqual(self.testentity.in_port2.value, -33) 96 | 97 | 98 | def test_portvalue_raises_error_on_nonports(self): 99 | psim = sim.PlanSimulator(self.testentity) 100 | 101 | with self.assertRaises(ValueError) as context: 102 | psim.run_plan( [{"abc": 123}] ) 103 | self.assertEqual(str(context.exception),"When consuming command I found a dict whose keys are not Port objects. Dict: \n{'abc': 123}") 104 | 105 | def test_merge_setValues_and_time_advance(self): 106 | psim = sim.PlanSimulator(self.testentity) 107 | epsilon_val = sim.Epsilon(3,-8) 108 | psim.run_plan( [1, 3.186, {self.testentity.in_port: 123, self.testentity.in_port2: -33}, epsilon_val] ) 109 | 110 | self.assertEqual(self.testentity.in_port.value, 123) 111 | self.assertEqual(self.testentity.in_port2.value, -33) 112 | self.assertEqual(psim.global_time, 1 + 3.186 + epsilon_val) 113 | 114 | 115 | 116 | 117 | def test_select_transition_to_trigger_fixed_selection_for_advance(self): 118 | entity = self.testentity 119 | psim = sim.PlanSimulator(self.testentity) 120 | 121 | # we use this to determine which transition is called 122 | psim._transition_selection = {entity: entity.transA, entity.sub1: entity.sub1.transB} 123 | 124 | # pretend that the transitions are enabled 125 | psim._get_transition_guard_value = mock.MagicMock(return_value=True) 126 | 127 | self.assertEqual(psim._select_transition_according_to_execution_plan(entity), entity.transA) 128 | self.assertEqual(psim._select_transition_according_to_execution_plan(entity.sub1), entity.sub1.transB) 129 | self.assertFalse(psim._select_transition_according_to_execution_plan(entity.sub2)) 130 | 131 | 132 | def test_select_transition_to_trigger_iterative_selection(self): 133 | entity = self.testentity 134 | psim = sim.PlanSimulator(self.testentity) 135 | 136 | # pretend that the transitions are enabled 137 | psim._get_transition_guard_value = mock.MagicMock(return_value=True) 138 | 139 | # we use this to determine which transition is called 140 | psim._transition_selection = None 141 | psim.execution_plan = [ 142 | # advance 33 time units. 143 | # When you hit a conflict, check if the first element is an entity-state dict 144 | # if the entity is a key in the first element, then pop it and 145 | # use it to reolve the conflict (otherwise choose randomly) 146 | # then continue until anothoer conflict or end of advance 147 | {entity: entity.transA}, 148 | {entity: entity.transB}, 149 | {entity: entity.transA}, 150 | ] 151 | 152 | self.assertEqual(psim._select_transition_according_to_execution_plan(entity), entity.transA) 153 | self.assertEqual(psim._select_transition_according_to_execution_plan(entity), entity.transB) 154 | self.assertEqual(psim._select_transition_according_to_execution_plan(entity), entity.transA) 155 | self.assertFalse(psim._select_transition_according_to_execution_plan(entity)) 156 | self.assertEqual(len(psim.execution_plan), 0) 157 | 158 | 159 | 160 | 161 | # # if you have two entities and you don't know which one will be conflicting first 162 | # # (because they'll have conflicts at the same time) 163 | # # you can put them both in a dict and duplicate the dict. 164 | # # the first one will pop the first dict, the second one the second dict: 165 | # 444, 166 | # {entity.subentity1: entity.subentity2.transA, entity.subentity2: entity.subentity2.transB}, 167 | # {entity.subentity1: entity.subentity2.transA, entity.subentity2: entity.subentity2.transB}, 168 | -------------------------------------------------------------------------------- /tests/simulator/test_rewrite_if.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import ast 3 | from crestdsl import sourcehelper as SH 4 | from crestdsl.simulation.to_z3 import * 5 | 6 | import logging 7 | logging.basicConfig(level=logging.INFO) # basic logging level 8 | 9 | 10 | class TestRewriteIf(unittest.TestCase): 11 | 12 | def test_rewrite_single_if_copy_following_to_both_structural_check(self): 13 | def update(self, dt): 14 | y = 1 15 | x = 22 16 | if x < 30: 17 | y = 50 18 | else: 19 | y = 100.5 20 | y += 3 21 | return y * 4 22 | 23 | up_ast = SH.get_ast_from_function_definition(update) 24 | SH.RewriteIfElse().walk(up_ast) 25 | SH.add_parent_info(up_ast) 26 | 27 | # assert that y += 3 and return have been copied to then and to else 28 | ifnode = up_ast.body[2] 29 | then, orelse = ifnode.body, ifnode.orelse 30 | assert len(then) == 3, "then-branch has correct number of statements" 31 | assert len(orelse) == 3, "else-branch has correct number of statements" 32 | assert isinstance(then[1], ast.AugAssign), "AugAssign has not been copied to then-branch" 33 | assert isinstance(orelse[1], ast.AugAssign), "AugAssign has not been copied to else-branch" 34 | assert isinstance(then[2], ast.Return), "Return has not been copied to then-branch" 35 | assert isinstance(orelse[2], ast.Return), "Return has not been copied to else-branch" 36 | 37 | def test_rewrite_single_if_copy_following_to_body_structural_check(self): 38 | def update(self, dt): 39 | y = 1 40 | x = 22 41 | if x < 30: 42 | y = 50 43 | else: 44 | return 100.5 45 | y += 3 46 | return y * 4 47 | up_ast = SH.get_ast_from_function_definition(update) 48 | SH.RewriteIfElse().walk(up_ast) 49 | SH.add_parent_info(up_ast) 50 | 51 | ifnode = up_ast.body[2] 52 | then, orelse = ifnode.body, ifnode.orelse 53 | assert len(then) == 3, "then-branch has correct number of statements" 54 | assert len(orelse) == 1, "else-branch has correct number of statements" 55 | assert isinstance(then[1], ast.AugAssign), "AugAssign has not been copied to then-branch" 56 | assert isinstance(then[2], ast.Return), "Return has not been copied to then-branch" 57 | assert isinstance(orelse[0], ast.Return), "else-branch statement is a return (just as the original)" 58 | 59 | def test_rewrite_nested_if_copy_following_to_body_structural_check(self): 60 | def update(self, dt): 61 | y = 1 62 | x = 22 63 | if x < 30: 64 | if y < 50: 65 | y = 50 66 | else: 67 | y -= 15 68 | else: 69 | if y < 50: 70 | y = 44 71 | else: 72 | y -= 15 73 | return y + 100.5 74 | y += 3 75 | return y * 4 76 | up_ast = SH.get_ast_from_function_definition(update) 77 | SH.RewriteIfElse().walk(up_ast) 78 | SH.add_parent_info(up_ast) 79 | 80 | ifnode = up_ast.body[2] 81 | then, orelse = ifnode.body, ifnode.orelse 82 | assert len(then) == 1, "then-branch has correct number of statements" 83 | assert len(orelse) == 1, "else-branch has correct number of statements" 84 | 85 | assert len(then[0].body) == 3, "then-then-branch has correct number of statements" 86 | assert len(then[0].orelse) == 3, "then-then-branch has correct number of statements" 87 | assert len(orelse[0].body) == 3, "else-then-branch has correct number of statements" 88 | assert len(orelse[0].orelse) == 2, "then-then-branch has correct number of statements" 89 | 90 | def test_rewrite_nested_if_copy_subpart_following_to_nested_body_structural_check(self): 91 | def update(self, dt): 92 | y = 1 93 | x = 22 94 | if x < 30: 95 | if y < 50: 96 | y = 50 97 | else: 98 | y -= 15 99 | 100 | x += 22 101 | return x 102 | else: 103 | if y < 50: 104 | y = 44 105 | return y 106 | else: 107 | y -= 15 108 | return y + 100.5 109 | 110 | up_ast = SH.get_ast_from_function_definition(update) 111 | SH.RewriteIfElse().walk(up_ast) 112 | SH.add_parent_info(up_ast) 113 | 114 | ifnode = up_ast.body[2] 115 | then, orelse = ifnode.body, ifnode.orelse 116 | assert len(then) == 1, "then-branch has correct number of statements" 117 | assert len(orelse) == 1, "else-branch has correct number of statements" 118 | 119 | assert len(then[0].body) == 3, "then-then-branch has correct number of statements" 120 | assert len(then[0].orelse) == 3, "then-then-branch has correct number of statements" 121 | assert len(orelse[0].body) == 2, "else-then-branch has correct number of statements" 122 | assert len(orelse[0].orelse) == 2, "then-then-branch has correct number of statements" 123 | 124 | if __name__ == '__main__': 125 | unittest.main() 126 | -------------------------------------------------------------------------------- /tests/simulator/test_sourcehelper_ast.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | # import ast 3 | import astor 4 | from crestdsl import sourcehelper as SH 5 | # from pprint import pprint 6 | 7 | import logging 8 | logging.basicConfig(level=logging.INFO) # basic logging level 9 | 10 | 11 | class TestSourceHelper_ast(unittest.TestCase): 12 | 13 | @unittest.skip("this is a test for manual debugging") 14 | def test_getast_of_function(self): 15 | def func(self, dt): 16 | y = 1 17 | # zzz = "this should not be visible" 18 | x = 22 19 | if x < 30: 20 | y = 50 21 | else: 22 | y = 100.5 23 | y += 3 24 | return y * 4 25 | 26 | tree = SH.getast(func) 27 | print(astor.dump_tree(tree)) 28 | 29 | @unittest.skip("this is a test for manual debugging") 30 | def test_getast_of_lambda(self): 31 | func = (lambda x: x) 32 | tree = SH.getast(func) 33 | print(astor.dump_tree(tree)) 34 | print(astor.dump_tree(SH.get_ast_from_lambda(func))) 35 | 36 | @unittest.skip("this is a test for manual debugging") 37 | def test_getast_of_lambda_with_comments(self): 38 | pass 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/simulator/test_z3calculator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import crestdsl.model as crest 3 | from crestdsl.simulation.z3calculator import uses_dt_variable 4 | 5 | class uses_dt_variableTest(unittest.TestCase): 6 | 7 | def test_finds_used_dt(self): 8 | 9 | @crest.update(state="some_state", target="some_port") 10 | def myUpdate(self, dt): 11 | a = 133 12 | b = a * dt - 44 13 | return b / 12 14 | 15 | self.assertTrue(uses_dt_variable(myUpdate)) 16 | # assert cache 17 | self.assertTrue(myUpdate._cached_dt_use) 18 | 19 | def test_returns_false_for_unused_dt(self): 20 | 21 | @crest.update(state="some_state", target="some_port") 22 | def myUpdate(self, dt): 23 | a = 133 24 | b = a * 44 25 | return b / 12 26 | 27 | self.assertFalse(uses_dt_variable(myUpdate)) 28 | # assert cache 29 | self.assertFalse(myUpdate._cached_dt_use) 30 | 31 | def test_returns_cached_result(self): 32 | 33 | @crest.update(state="some_state", target="some_port") 34 | def myUpdate(self, dt): 35 | a = 133 36 | b = a * 44 37 | return b / 12 38 | 39 | myUpdate._cached_dt_use = True 40 | 41 | # should return True, because the result is cached 42 | self.assertTrue(uses_dt_variable(myUpdate)) 43 | 44 | def test_returns_cached_result_false(self): 45 | 46 | @crest.update(state="some_state", target="some_port") 47 | def myUpdate(self, dt): 48 | a = 133 49 | b = a * dt - 44 50 | return b / 12 51 | 52 | myUpdate._cached_dt_use = False 53 | 54 | # should return True, because the result is cached 55 | self.assertFalse(uses_dt_variable(myUpdate)) -------------------------------------------------------------------------------- /tests/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/tests/ui/__init__.py -------------------------------------------------------------------------------- /tests/ui/test_elk.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import crestdsl.model as crest 3 | from crestdsl.ui import elk 4 | 5 | import logging 6 | 7 | class TestReassignPort(unittest.TestCase): 8 | 9 | def setUp(self): 10 | class Test(crest.Entity): 11 | A = current = crest.State() 12 | port = crest.Input(crest.Resource("watt", crest.REAL), 3.14) 13 | self.testclass = Test 14 | 15 | def test_plot_entity(self): 16 | elk.plot(self.testclass(), suppress_output=True) # hope for no error here 17 | 18 | def test_plot_entity_type(self): 19 | # with self.assertLogs(logger="crestdsl.ui.elk", level='WARNING') as cm: 20 | # elk.plot(self.testclass) 21 | # self.assertTrue(cm.output[0].find("You called 'plot' on an Entity type instead of an entity. I will instantiate this class and plot the instance.") >= 0) 22 | 23 | """The above code is not working. I don't know why. 24 | For now, just make sure that this doesn't throw an exception""" 25 | elk.plot(self.testclass, suppress_output=True) 26 | 27 | 28 | def test_plot_int(self): 29 | with self.assertRaises(ValueError): 30 | elk.plot(1234, suppress_output=True) 31 | 32 | def test_plot_None(self): 33 | with self.assertRaises(ValueError): 34 | elk.plot(None, suppress_output=True) -------------------------------------------------------------------------------- /tests/verification/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestdsl/CREST/7fd97c50b0c6c923e1c477105bed4f0ea032bb99/tests/verification/__init__.py -------------------------------------------------------------------------------- /tests/verification/test_checklib.py: -------------------------------------------------------------------------------- 1 | import crestdsl.model as crest 2 | import unittest 3 | 4 | from crestdsl.verification import check 5 | 6 | class TestEntity(crest.Entity): 7 | res = crest.Resource("float-resource", crest.Types.REAL) 8 | listres = crest.Resource("list-res", ["first", "TWO", "3.14abc"]) 9 | port = crest.Local(resource=res, value=314.13) 10 | port2 = crest.Local(resource=listres, value="TWO") 11 | state = current = crest.State() 12 | state2 = crest.State() 13 | 14 | 15 | class TestStateCheck(unittest.TestCase): 16 | 17 | def test_state_check_eq(self): 18 | entity = TestEntity() 19 | c = check(entity) == entity.state 20 | assert c.check() 21 | 22 | def test_state_check_neq(self): 23 | entity = TestEntity() 24 | c = check(entity) != entity.state2 25 | assert c.check() 26 | 27 | def test_state_check_eq_fails(self): 28 | entity = TestEntity() 29 | c = check(entity) == entity.state2 30 | self.assertFalse(c.check()) 31 | 32 | def test_state_check_neq_fails(self): 33 | entity = TestEntity() 34 | c = check(entity) != entity.state 35 | self.assertFalse(c.check()) 36 | 37 | def test_state_check_eq_string(self): 38 | entity = TestEntity() 39 | c = check(entity) == "state" 40 | assert c.check() 41 | 42 | def test_state_check_neq_string(self): 43 | entity = TestEntity() 44 | c = check(entity) != "state2" 45 | assert c.check() 46 | 47 | 48 | class TestPortCheck(unittest.TestCase): 49 | 50 | def test_port_check_float_lt(self): 51 | entity = TestEntity() 52 | c = check(entity.port) < 400 53 | assert c.check() 54 | c = check(entity.port) < 200 55 | self.assertFalse(c.check()) 56 | c = check(entity.port) < 314.13 57 | self.assertFalse(c.check()) 58 | 59 | def test_port_check_float_le(self): 60 | entity = TestEntity() 61 | c = check(entity.port) <= 400 62 | assert c.check() 63 | c = check(entity.port) <= 314.13 64 | assert c.check() 65 | c = check(entity.port) <= 200 66 | self.assertFalse(c.check()) 67 | 68 | def test_port_check_float_eq(self): 69 | entity = TestEntity() 70 | c = check(entity.port) == 314.13 71 | assert c.check() 72 | c = check(entity.port) == 400 73 | self.assertFalse(c.check()) 74 | c = check(entity.port) == 200 75 | self.assertFalse(c.check()) 76 | 77 | def test_port_check_float_ne(self): 78 | entity = TestEntity() 79 | c = check(entity.port) != 314.13 80 | self.assertFalse(c.check()) 81 | c = check(entity.port) != 400 82 | assert c.check() 83 | c = check(entity.port) != 200 84 | assert c.check() 85 | 86 | def test_port_check_float_gt(self): 87 | entity = TestEntity() 88 | c = check(entity.port) > 200 89 | assert c.check() 90 | c = check(entity.port) > 400 91 | self.assertFalse(c.check()) 92 | c = check(entity.port) > 314.13 93 | self.assertFalse(c.check()) 94 | 95 | def test_port_check_float_ge(self): 96 | entity = TestEntity() 97 | c = check(entity.port) >= 200 98 | assert c.check() 99 | c = check(entity.port) >= 314.13 100 | assert c.check() 101 | c = check(entity.port) >= 400 102 | self.assertFalse(c.check()) 103 | 104 | """ LIST TYPES """ 105 | 106 | def test_port_check_list_eq(self): 107 | entity = TestEntity() 108 | c = check(entity.port2) == "TWO" 109 | assert c.check() 110 | c = check(entity.port2) == "first" 111 | self.assertFalse(c.check()) 112 | 113 | def test_port_check_list_ne(self): 114 | entity = TestEntity() 115 | c = check(entity.port2) != "first" 116 | assert c.check() 117 | c = check(entity.port2) != "TWO" 118 | self.assertFalse(c.check()) 119 | 120 | 121 | class GetAtomicChecksTest(unittest.TestCase): 122 | 123 | def test_port_check(self): 124 | entity = TestEntity() 125 | c = check(entity.port2) == "TWO" 126 | 127 | self.assertSetEqual(c.get_atomic_checks(), {c}) 128 | 129 | def test_state_check(self): 130 | entity = TestEntity() 131 | c = check(entity) == entity.state 132 | 133 | self.assertSetEqual(c.get_atomic_checks(), {c}) 134 | 135 | def test_and_check(self): 136 | entity = TestEntity() 137 | c1 = check(entity.port2) == "TWO" 138 | c2 = check(entity) == entity.state 139 | c = c1 & c2 140 | self.assertSetEqual(c.get_atomic_checks(), {c1,c2}) 141 | 142 | def test_or_check(self): 143 | entity = TestEntity() 144 | c1 = check(entity.port2) == "TWO" 145 | c2 = check(entity) == entity.state 146 | c = c1 | c2 147 | self.assertSetEqual(c.get_atomic_checks(), {c1,c2}) 148 | 149 | def test_not_check(self): 150 | entity = TestEntity() 151 | c1 = check(entity.port2) == "TWO" 152 | c2 = check(entity) == entity.state 153 | c = -(c1 | c2) 154 | self.assertSetEqual(c.get_atomic_checks(), {c1,c2}) 155 | 156 | def test_duplicate_check(self): 157 | entity = TestEntity() 158 | c1 = check(entity.port2) == "TWO" 159 | c2 = check(entity) == entity.state 160 | c = -(c1 | c2) & c2 161 | self.assertSetEqual(c.get_atomic_checks(), {c1,c2}) -------------------------------------------------------------------------------- /tests/verification/test_continuous.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | import math 4 | 5 | from crestdsl.verification import tctl, StateSpace 6 | from crestdsl.verification.tctl import AtomicProposition, NamedAtomicProposition 7 | from crestdsl.verification.continuous import ContinuousModelChecker 8 | 9 | 10 | 11 | class ContinuousModelCheckerTest(unittest.TestCase): 12 | 13 | @patch('crestdsl.verification.StateSpace') 14 | def test_need_transformation_atomic(self, statespace): 15 | formula = NamedAtomicProposition("myAP") 16 | mc = ContinuousModelChecker(statespace) 17 | 18 | self.assertFalse(mc.need_transformation(formula)) 19 | 20 | @patch('crestdsl.verification.StateSpace') 21 | def test_need_transformation_formula(self, statespace): 22 | ap = NamedAtomicProposition("myAP") 23 | formula = tctl.EG(ap) 24 | 25 | mc = ContinuousModelChecker(statespace) 26 | self.assertFalse(mc.need_transformation(formula)) 27 | 28 | @patch('crestdsl.verification.StateSpace') 29 | def test_need_transformation_timed_formula_full_interval(self, statespace): 30 | ap = NamedAtomicProposition("myAP") 31 | formula = tctl.EG(ap, interval=tctl.Interval()) 32 | 33 | mc = ContinuousModelChecker(statespace) 34 | self.assertFalse(mc.need_transformation(formula)) 35 | 36 | @patch('crestdsl.verification.StateSpace') 37 | def test_need_transformation_timed_formula_interval(self, statespace): 38 | ap = NamedAtomicProposition("myAP") 39 | formula = tctl.EG(ap, interval=(tctl.Interval() >= 15)) 40 | 41 | mc = ContinuousModelChecker(statespace) 42 | self.assertTrue(mc.need_transformation(formula)) 43 | 44 | 45 | @patch('crestdsl.verification.StateSpace') 46 | def test_need_transformation_timed_formula_other_interval(self, statespace): 47 | ap = NamedAtomicProposition("myAP") 48 | formula = tctl.EG(ap, interval=(tctl.Interval() <= 15)) 49 | 50 | mc = ContinuousModelChecker(statespace) 51 | self.assertTrue(mc.need_transformation(formula)) 52 | 53 | @patch('crestdsl.verification.StateSpace') 54 | def test_need_transformation_timed_formula_modified_interval_operators(self, statespace): 55 | ap = NamedAtomicProposition("myAP") 56 | formula = tctl.EG(ap, interval=(tctl.Interval() <= math.inf) > 0) 57 | 58 | mc = ContinuousModelChecker(statespace) 59 | self.assertFalse(mc.need_transformation(formula)) -------------------------------------------------------------------------------- /tests/verification/test_simplify.py: -------------------------------------------------------------------------------- 1 | import crestdsl.model as crest 2 | import unittest 3 | 4 | from crestdsl.verification import check 5 | from crestdsl.verification import simplify 6 | 7 | from crestdsl.verification.tctl import * # I know, I know... but otherwise the formulas become unreadable !! 8 | from crestdsl.verification.tctl import AtomicProposition, NamedAtomicProposition # * in tctl only offers some of the classes, we need all ! 9 | 10 | class SimplifyTest(unittest.TestCase): 11 | 12 | """ Testing simplification rules """ 13 | 14 | """ 7.1 """ 15 | def test_simplify_negated_True(self): 16 | self.assertEqual(False, simplify(Not(True))) 17 | 18 | """ 7.2 """ 19 | def test_simplify_negated_False(self): 20 | self.assertEqual(True, simplify(Not(False))) 21 | 22 | """ 7.3 """ 23 | def test_deMorgan_negated_Or(self): 24 | phi = NamedAtomicProposition("phi") 25 | psi = NamedAtomicProposition("psi") 26 | formula = Not(Or(phi, psi)) 27 | 28 | exp = And(Not(phi),Not(psi)) 29 | got = simplify(formula) 30 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 31 | 32 | """ 7.4 """ 33 | def test_deMorgan_negated_Or(self): 34 | phi = NamedAtomicProposition("phi") 35 | psi = NamedAtomicProposition("psi") 36 | formula = Not(And(phi, psi)) 37 | 38 | exp = Or(Not(phi),Not(psi)) 39 | got = simplify(formula) 40 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 41 | 42 | """ 7.5 """ 43 | def test_prune_True_from_And_left(self): 44 | phi = NamedAtomicProposition("phi") 45 | formula = And(True, phi) 46 | 47 | exp = phi 48 | got = simplify(formula) 49 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 50 | 51 | """ 7.6 """ 52 | def test_prune_True_from_And_right(self): 53 | phi = NamedAtomicProposition("phi") 54 | formula = And(phi, True) 55 | 56 | exp = phi 57 | got = simplify(formula) 58 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 59 | 60 | """ 7.7 """ 61 | def test_prune_False_from_And_left(self): 62 | phi = NamedAtomicProposition("phi") 63 | formula = And(False, phi) 64 | 65 | exp = False 66 | got = simplify(formula) 67 | self.assertFalse(got, f"Problem discovered. Expected {exp}, got {got}") 68 | 69 | """ 7.8 """ 70 | def test_prune_False_from_And_right(self): 71 | phi = NamedAtomicProposition("phi") 72 | formula = And(phi, False) 73 | 74 | exp = False 75 | got = simplify(formula) 76 | self.assertFalse(got, f"Problem discovered. Expected {exp}, got {got}") 77 | 78 | 79 | """ 7.9 """ 80 | def test_prune_True_from_Or_left(self): 81 | phi = NamedAtomicProposition("phi") 82 | formula = Or(True, phi) 83 | 84 | exp = phi 85 | got = simplify(formula) 86 | self.assertTrue(got, f"Problem discovered. Expected {exp}, got {got}") 87 | 88 | """ 7.10 """ 89 | def test_prune_True_from_Or_right(self): 90 | phi = NamedAtomicProposition("phi") 91 | formula = Or(phi, True) 92 | 93 | exp = phi 94 | got = simplify(formula) 95 | self.assertTrue(got, f"Problem discovered. Expected {exp}, got {got}") 96 | 97 | """ 7.11 """ 98 | def test_prune_False_from_Or_left(self): 99 | phi = NamedAtomicProposition("phi") 100 | formula = Or(False, phi) 101 | 102 | exp = phi 103 | got = simplify(formula) 104 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 105 | 106 | """ 7.12 """ 107 | def test_prune_False_from_Or_right(self): 108 | phi = NamedAtomicProposition("phi") 109 | formula = Or(phi, False) 110 | 111 | exp = phi 112 | got = simplify(formula) 113 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 114 | 115 | """ 7.13 Not tests""" 116 | def test_dont_prune_Not(self): 117 | phi = NamedAtomicProposition("phi") 118 | formula = Not(phi) 119 | 120 | exp = Not(phi) 121 | got = simplify(formula) 122 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 123 | 124 | def test_prune_nested_Not(self): 125 | phi = NamedAtomicProposition("phi") 126 | formula = Not(Not(phi)) 127 | 128 | exp = phi 129 | got = simplify(formula) 130 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 131 | 132 | def test_prune_double_nested_Not(self): 133 | phi = NamedAtomicProposition("phi") 134 | formula = Not(Not(Not(phi))) 135 | 136 | exp = Not(phi) 137 | got = simplify(formula) 138 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 139 | 140 | def test_prune_triple_nested_Not(self): 141 | phi = NamedAtomicProposition("phi") 142 | formula = Not(Not(Not(Not(phi)))) 143 | 144 | exp = phi 145 | got = simplify(formula) 146 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 147 | 148 | def test_prune_quadruple_nested_Not(self): 149 | phi = NamedAtomicProposition("phi") 150 | formula = Not(Not(Not(Not(Not(phi))))) 151 | 152 | exp = Not(phi) 153 | got = simplify(formula) 154 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 155 | 156 | 157 | """ Nested tests """ 158 | 159 | def test_prune_nested_True(self): 160 | phi = NamedAtomicProposition("phi") 161 | psi = NamedAtomicProposition("psi") 162 | formula = And(And(phi, True), And(True, psi)) 163 | 164 | exp = And(phi,psi) 165 | got = simplify(formula) 166 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 167 | 168 | 169 | def test_prune_nested_False(self): 170 | phi = NamedAtomicProposition("phi") 171 | psi = NamedAtomicProposition("psi") 172 | formula = And(And(phi, False), And(False, psi)) 173 | 174 | exp = And(phi,psi) 175 | got = simplify(formula) 176 | self.assertFalse(got, f"Problem discovered. Expected {exp}, got {got}") 177 | 178 | 179 | 180 | def test_large_example(self): 181 | pa = NamedAtomicProposition("pa") 182 | phi = NamedAtomicProposition("phi") 183 | formula = \ 184 | Not(And( 185 | pa, 186 | Not(And( 187 | Not(Not(phi)), 188 | Not(And( 189 | Not(EU( 190 | Not(And(Not(phi), Not(And(Not(Not(pa)), Not(True))))) , 191 | True 192 | )), 193 | Not( 194 | EU( 195 | Not(And( 196 | Not(phi), 197 | Not(And( 198 | Not(Not(pa)), 199 | Not(True) 200 | )) 201 | )), 202 | And( 203 | Not(True), 204 | Not(And( 205 | Not(phi), 206 | Not(And( 207 | Not(Not(pa)), 208 | Not(True) 209 | )) 210 | )) 211 | ) 212 | ) 213 | ) 214 | )) 215 | )) 216 | )) 217 | 218 | 219 | exp = \ 220 | Or(Not(pa), phi) 221 | got = simplify(formula) 222 | self.assertTrue(exp.eq(got), f"Problem discovered. Expected {exp}, got {got}") 223 | 224 | -------------------------------------------------------------------------------- /tests/verification/test_verifier.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import crestdsl.model as crest 4 | 5 | import crestdsl.verification as verif 6 | from crestdsl.verification import tctl, StateSpace, check 7 | from crestdsl.verification.tctl import AtomicProposition, NamedAtomicProposition 8 | 9 | 10 | class TestEntity(crest.Entity): 11 | res = crest.Resource("float-resource", crest.Types.REAL) 12 | listres = crest.Resource("list-res", ["first", "TWO", "3.14abc"]) 13 | port = crest.Local(resource=res, value=314.13) 14 | port2 = crest.Local(resource=listres, value="TWO") 15 | state = current = crest.State() 16 | state2 = crest.State() 17 | 18 | 19 | class VerifierTest(unittest.TestCase): 20 | 21 | def test_formula_attribute_getting(self): 22 | ap = NamedAtomicProposition("myAP") 23 | formula = tctl.And(ap, ap) 24 | 25 | verifier = verif.Verifier() 26 | verifier._formula = formula 27 | self.assertEqual(verifier.formula, formula) 28 | 29 | def test_formula_attribute_setting(self): 30 | ap = NamedAtomicProposition("myAP") 31 | formula = tctl.And(ap, ap) 32 | 33 | verifier = verif.Verifier() 34 | verifier.formula = formula 35 | self.assertEqual(verifier.formula, formula) 36 | 37 | def test_formula_attribute_override(self): 38 | ap = NamedAtomicProposition("myAP") 39 | formula = tctl.And(ap, ap) 40 | 41 | verifier = verif.Verifier() 42 | verifier.formula = ap 43 | 44 | """The log verification doesn't work. Not sure why.""" 45 | # with self.assertLogs('crestdsl.verification.verifier', level='WARNING') as cm: 46 | # verifier.formula = formula 47 | """for now, just check for exceptions""" 48 | verifier.formula = formula 49 | 50 | self.assertTrue(formula.eq(verifier.formula)) 51 | # self.assertTrue(cm.output[0].find(f"""There was already a formula stored for this Verifier. I overwrote the old one for you.""") >= 0) 52 | 53 | def test_system(self): 54 | entity = TestEntity() 55 | c1 = check(entity.port2) == "TWO" 56 | c2 = check(entity) == entity.state 57 | chk = c1 | c2 58 | 59 | verifier = verif.Verifier() 60 | verifier.is_possible(chk) 61 | 62 | self.assertEqual(verifier.system, entity) 63 | 64 | def test_system_different_entities(self): 65 | entity = TestEntity() 66 | entity2 = TestEntity() 67 | c1 = check(entity.port2) == "TWO" 68 | c2 = check(entity2) == entity2.state 69 | chk = c1 | c2 70 | 71 | verifier = verif.Verifier() 72 | verifier.is_possible(chk) 73 | 74 | with self.assertRaises(ValueError) as context: 75 | sys = verifier.system 76 | 77 | self.assertEqual(str(context.exception), f"The checks are from different systems. Please make sure that you only check properties from the same system.") 78 | 79 | def test_system_different_entities_in_other_port(self): 80 | entity = TestEntity() 81 | entity2 = TestEntity() 82 | c1 = check(entity.port2) < entity2.port2 83 | c2 = check(entity) == entity.state 84 | chk = c1 | c2 85 | 86 | verifier = verif.Verifier() 87 | verifier.is_possible(chk) 88 | 89 | with self.assertRaises(ValueError) as context: 90 | sys = verifier.system 91 | 92 | self.assertEqual(str(context.exception), f"The checks are from different systems. Please make sure that you only check properties from the same system.") 93 | 94 | 95 | def test_system_no_formula(self): 96 | verifier = verif.Verifier() 97 | with self.assertRaises(ValueError) as context: 98 | sys = verifier.system 99 | 100 | self.assertEqual(str(context.exception), f"You need to define a formula first.") 101 | 102 | 103 | def test_check(self): 104 | entity = TestEntity() 105 | c1 = check(entity.port2) == "TWO" 106 | c2 = check(entity) == entity.state 107 | chk = c1 & c2 108 | 109 | verifier = verif.Verifier() 110 | self.assertTrue(verifier.is_possible(chk).check()) 111 | 112 | def test_check_false(self): 113 | entity = TestEntity() 114 | c1 = check(entity.port2) == "ONE" 115 | c2 = check(entity) == entity.state 116 | chk = c1 & c2 117 | 118 | verifier = verif.Verifier() 119 | self.assertFalse(verifier.is_possible(chk).check()) 120 | 121 | 122 | class ApiTest(unittest.TestCase): 123 | 124 | def brainstorming(self): 125 | 126 | system = System() 127 | 128 | chk = check(system.port) <= 30 129 | chk2 = check(system) == system.state 130 | 131 | verif.is_possible(chk & chk2).before(15).after(30).check() 132 | 133 | verif.never(chk).after(25) 134 | verif.always(chk3).before(25) 135 | verif.always_possible(chk3, within=25) 136 | 137 | verif.forever(chk2).after(33) # not(always(not(chk2))) 138 | 139 | verif.after(40).forever(chk2) 140 | 141 | def test_ispossible_formula(self): 142 | ap = NamedAtomicProposition("myAP") 143 | 144 | verifier = verif.is_possible(ap) 145 | 146 | exp = tctl.EF(ap) 147 | got = verifier.formula 148 | self.assertTrue(exp.eq(got)) 149 | 150 | def test_always_formula(self): 151 | ap = NamedAtomicProposition("myAP") 152 | 153 | verifier = verif.always(ap) 154 | 155 | exp = tctl.AG(ap) 156 | got = verifier.formula 157 | self.assertTrue(exp.eq(got)) 158 | 159 | def test_always_possible_formula(self): 160 | ap = NamedAtomicProposition("myAP") 161 | 162 | verifier = verif.always_possible(ap) 163 | 164 | exp = tctl.AG(tctl.EF(ap)) 165 | got = verifier.formula 166 | self.assertTrue(exp.eq(got)) 167 | 168 | 169 | def test_always_possible_within_formula(self): 170 | ap = NamedAtomicProposition("myAP") 171 | 172 | verifier = verif.always_possible(ap, within=30) 173 | 174 | exp = tctl.AG(tctl.AF(ap, interval=tctl.Interval() <= 30)) 175 | got = verifier.formula 176 | self.assertTrue(exp.eq(got)) 177 | 178 | 179 | def test_forever_formula(self): 180 | ap = NamedAtomicProposition("myAP") 181 | 182 | verifier = verif.forever(ap) 183 | 184 | exp = tctl.EG(ap) 185 | got = verifier.formula 186 | self.assertTrue(exp.eq(got)) 187 | 188 | def test_never_formula(self): 189 | ap = NamedAtomicProposition("myAP") 190 | 191 | verifier = verif.never(ap) 192 | 193 | exp = tctl.AG(tctl.Not(ap)) 194 | got = verifier.formula 195 | self.assertTrue(exp.eq(got)) 196 | 197 | def test_before_formula(self): 198 | verifier = verif.before(22) 199 | self.assertEqual(22, verifier._before) 200 | self.assertIsNone(verifier._after) 201 | 202 | def test_after_formula(self): 203 | verifier = verif.after(33) 204 | self.assertEqual(33, verifier._after) 205 | self.assertIsNone(verifier._before) 206 | 207 | 208 | def test_formula_creation_with_before(self): 209 | ap = NamedAtomicProposition("myAP") 210 | 211 | verifier = verif.is_possible(ap).before(1234) 212 | 213 | exp = tctl.EF(ap, interval=tctl.Interval() <= 1234) 214 | got = verifier.formula 215 | self.assertTrue(exp.eq(got)) 216 | 217 | 218 | def test_formula_creation_with_after(self): 219 | ap = NamedAtomicProposition("myAP") 220 | 221 | verifier = verif.is_possible(ap).after(52) 222 | 223 | exp = tctl.EF(ap, interval=tctl.Interval() > 52) 224 | got = verifier.formula 225 | self.assertTrue(exp.eq(got)) 226 | 227 | --------------------------------------------------------------------------------