├── root_dir.py
├── tests
├── __init__.py
├── tilemap_nx
│ ├── 6x6_ABC.csv
│ ├── coordinates_holder_scale_test.py
│ ├── coordinates_holder_diagonal_test.py
│ └── coordinates_holder_straight_test.py
├── installation_test.py
├── environments
│ └── synced_float_test.py
└── source_test.py
├── examples
├── __init__.py
├── tilemap
│ ├── diagram.png
│ ├── 6x6_ABC.csv
│ ├── tilemap-csv_240.png
│ ├── simple_entity_icon.png
│ ├── coordinates_holder_setup.py
│ ├── info.md
│ ├── parse_tilemap_example_test.py
│ ├── simple_run_tilemap_example_test.py
│ ├── tilemap_example_template.py
│ ├── tilemap_process_visualizer_test.py
│ ├── tilemap_visualizer_test.py
│ ├── tilemap_example_model.py
│ ├── web_animation
│ │ └── sim_runner_cs.py
│ ├── diagram.bpmn
│ └── diagram.svg
├── resources
│ ├── resource_example.png
│ ├── simple_entity_icon.png
│ ├── parse_resource_example_test.py
│ ├── simple_run_resource_example_test.py
│ ├── resource_example_template.py
│ ├── visual_run_resource_example_test.py
│ ├── resource_example_model.py
│ └── resource_example.bpmn
├── wait_for_interrupt
│ ├── diagram.png
│ ├── parse_bpmn_to_model.py
│ ├── test_simple_run.py
│ ├── model_template.py
│ ├── interruptor.py
│ ├── test_wait_for_interrupt_visual.py
│ ├── model.py
│ └── diagram.bpmn
├── resources_loop
│ ├── resource_example.png
│ ├── simple_entity_icon.png
│ ├── parse_resource_loop_example_test.py
│ ├── simple_run_resource_loop_example_test.py
│ ├── resource_loop_example_template.py
│ ├── visual_run_resource_loop_example_test.py
│ └── resource_loop_example_model.py
└── basics
│ ├── visualization
│ ├── bpmn_example.png
│ ├── simple_entity_icon.png
│ └── visual_run_bpmn_example_test.py
│ ├── bpmn
│ ├── parse_bpmn_example_test.py
│ ├── bpmn_example_template.py
│ └── bpmn_example.bpmn
│ ├── model
│ ├── simple_run_bpmn_example_test.py
│ ├── long_run_bpmn_example_test.py
│ └── bpmn_example_model.py
│ └── web_canvas
│ └── sim_runner_cs.py
├── src
└── casymda
│ ├── bpmn
│ └── __init__.py
│ ├── blocks
│ ├── tilemap
│ │ ├── __init__.py
│ │ ├── segment.py
│ │ ├── coordinates_holder.py
│ │ └── tilemap_movement.py
│ ├── block_components
│ │ ├── __init__.py
│ │ ├── visualizer_interface.py
│ │ ├── visualizable_block.py
│ │ ├── state.py
│ │ └── block.py
│ ├── named_resource.py
│ ├── wait_for_interrupt.py
│ ├── resource_seize_queue.py
│ ├── __init__.py
│ ├── delay.py
│ ├── entity.py
│ ├── sink.py
│ ├── round_robin_gateway.py
│ ├── resource_release.py
│ ├── source.py
│ └── seize_process_release.py
│ ├── environments
│ ├── __init__.py
│ └── realtime_environment.py
│ ├── visualization
│ ├── __init__.py
│ ├── canvas
│ │ ├── __init__.py
│ │ ├── scaled_canvas.py
│ │ ├── tk_canvas.py
│ │ └── web_canvas.py
│ ├── tilemap
│ │ ├── __init__.py
│ │ ├── tilemap_visualizer_interface.py
│ │ └── tilemap_visualizer.py
│ ├── web_server
│ │ ├── __init__.py
│ │ ├── flatbuffers
│ │ │ ├── __init__.py
│ │ │ ├── ElementsUnion.py
│ │ │ ├── Anchor.py
│ │ │ ├── CanvasPosition.py
│ │ │ ├── Photo.py
│ │ │ ├── Canvas.py
│ │ │ ├── Element.py
│ │ │ ├── flatbuffer_serializer.py
│ │ │ ├── Image.py
│ │ │ └── Text.py
│ │ ├── canvas_animation.html
│ │ ├── flatbuffer_deserializer.js
│ │ ├── sim_controller.py
│ │ ├── pixijs_canvas_animation.js
│ │ ├── flask_sim_server.py
│ │ └── animation_client.js
│ ├── state_icons
│ │ ├── __init__.py
│ │ ├── busy.png
│ │ ├── empty.png
│ │ ├── blocked.png
│ │ └── state_icons_pathfinder.py
│ └── entity_visualizer.py
│ └── __init__.py
├── benchmark
├── results
│ └── .gitignore
├── model
│ ├── diagram.png
│ ├── simple_entity_icon.png
│ ├── parse_bpmn.py
│ ├── simple_run_test.py
│ ├── model_template.py
│ ├── web_visu.py
│ ├── run_benchmark.py
│ └── model.py
├── readme.md
└── analysis
│ ├── gather_data.py
│ └── plot.py
├── MANIFEST.in
├── conftest.py
├── .coveragerc
├── diagrams
├── blocks+animation.pdf
└── model+execution.pdf
├── Dockerfile
├── docker
├── docker-entrypoint_unit-test.sh
├── docker-entrypoint_examples-test_pypi.sh
└── docker-entrypoint_examples-test.sh
├── exec
├── basics
│ ├── run_web_canvas_cs.py
│ └── visual_run.py
├── tilemap
│ ├── tilemap_visual_run.py
│ ├── process_web_animation.py
│ └── tilemap_web_animation.py
├── resources
│ ├── simple_run.py
│ └── visual_run.py
└── wait_for_interrupt
│ └── visual_run.py
├── requirements.txt
├── .dockerignore
├── sonar-project.properties
├── flatbuffers
├── readme.md
└── csa.fbs
├── .gitattributes
├── LICENSE
├── .gitignore
├── setup.py
├── docker-compose.yml
└── readme.md
/root_dir.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/bpmn/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/blocks/tilemap/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/environments/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/visualization/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/benchmark/results/.gitignore:
--------------------------------------------------------------------------------
1 | *.csv
2 | *.png
3 |
--------------------------------------------------------------------------------
/src/casymda/visualization/canvas/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/visualization/tilemap/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/casymda/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.2.29"
2 |
--------------------------------------------------------------------------------
/src/casymda/visualization/state_icons/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include casymda/Visualization/state_icons/*.png
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
1 | # (necessary to make pytest import the root directory)
2 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 |
4 | [xml]
5 | output = coverage.xml
6 |
--------------------------------------------------------------------------------
/benchmark/model/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/benchmark/model/diagram.png
--------------------------------------------------------------------------------
/diagrams/blocks+animation.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/diagrams/blocks+animation.pdf
--------------------------------------------------------------------------------
/diagrams/model+execution.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/diagrams/model+execution.pdf
--------------------------------------------------------------------------------
/examples/tilemap/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/tilemap/diagram.png
--------------------------------------------------------------------------------
/examples/tilemap/6x6_ABC.csv:
--------------------------------------------------------------------------------
1 | A,0,0,0,0,B
2 | 1,1,1,1,1,0
3 | 0,0,0,0,0,0
4 | 0,1,1,1,1,1
5 | 0,0,0,0,0,0
6 | 0,0,0,0,0,C
--------------------------------------------------------------------------------
/tests/tilemap_nx/6x6_ABC.csv:
--------------------------------------------------------------------------------
1 | A,0,0,0,0,B
2 | 1,1,1,1,1,0
3 | 0,0,0,0,0,0
4 | 0,1,1,1,1,1
5 | 0,0,0,0,0,0
6 | 0,0,0,0,0,C
--------------------------------------------------------------------------------
/examples/tilemap/tilemap-csv_240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/tilemap/tilemap-csv_240.png
--------------------------------------------------------------------------------
/benchmark/model/simple_entity_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/benchmark/model/simple_entity_icon.png
--------------------------------------------------------------------------------
/examples/resources/resource_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/resources/resource_example.png
--------------------------------------------------------------------------------
/examples/tilemap/simple_entity_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/tilemap/simple_entity_icon.png
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/wait_for_interrupt/diagram.png
--------------------------------------------------------------------------------
/examples/resources/simple_entity_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/resources/simple_entity_icon.png
--------------------------------------------------------------------------------
/examples/resources_loop/resource_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/resources_loop/resource_example.png
--------------------------------------------------------------------------------
/examples/basics/visualization/bpmn_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/basics/visualization/bpmn_example.png
--------------------------------------------------------------------------------
/examples/resources_loop/simple_entity_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/resources_loop/simple_entity_icon.png
--------------------------------------------------------------------------------
/src/casymda/visualization/state_icons/busy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/src/casymda/visualization/state_icons/busy.png
--------------------------------------------------------------------------------
/src/casymda/visualization/state_icons/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/src/casymda/visualization/state_icons/empty.png
--------------------------------------------------------------------------------
/src/casymda/visualization/state_icons/blocked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/src/casymda/visualization/state_icons/blocked.png
--------------------------------------------------------------------------------
/examples/basics/visualization/simple_entity_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fladdimir/casymda/HEAD/examples/basics/visualization/simple_entity_icon.png
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile for test execution
2 |
3 | FROM python:3
4 | WORKDIR /usr/src
5 | COPY ./requirements.txt ./
6 | RUN pip install -r requirements.txt
7 |
--------------------------------------------------------------------------------
/docker/docker-entrypoint_unit-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | eval "pip install -e ."
3 | eval "pytest --cov-report xml --cov-report term --cov=src/casymda/ tests"
4 |
--------------------------------------------------------------------------------
/src/casymda/blocks/block_components/__init__.py:
--------------------------------------------------------------------------------
1 | """convenience import"""
2 | from casymda.blocks.block_components.visualizable_block import (
3 | VisualizableBlock,
4 | ) # type: ignore
5 |
--------------------------------------------------------------------------------
/exec/basics/run_web_canvas_cs.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.basics.web_canvas.sim_runner_cs import run_sim
6 |
7 | if __name__ == "__main__":
8 | run_sim()
9 |
--------------------------------------------------------------------------------
/tests/installation_test.py:
--------------------------------------------------------------------------------
1 | """test the installation"""
2 | from casymda import __version__
3 |
4 |
5 | def test_version():
6 | """version should not be none"""
7 | assert __version__ is not None
8 |
--------------------------------------------------------------------------------
/docker/docker-entrypoint_examples-test_pypi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # lets do a complete package install from pypi and see if the examples work
3 | eval "pip install casymda"
4 | eval "python3 -m pytest examples"
5 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | simpy
2 | xmltodict
3 | Pillow
4 | flask
5 | flask-cors
6 | black==19.3b0
7 | numpy
8 | networkx
9 | flatbuffers==1.12 # todo: update to 2.x
10 |
11 | pytest
12 | pytest-cov
13 | twine
14 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **__pycache__**
2 | *.pyc
3 | *.pyo
4 | *.pyd
5 |
6 | dist
7 | build
8 | .mypy_cache
9 | personal_stuff
10 |
11 | .git
12 | .gitignore
13 |
14 | .vscode
15 |
16 | venv
17 | virtual_env
18 |
19 | *_temp_*
20 |
--------------------------------------------------------------------------------
/exec/tilemap/tilemap_visual_run.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.tilemap import tilemap_visualizer_test
6 |
7 | if __name__ == "__main__":
8 | tilemap_visualizer_test.test_visualized_run_tilemap(rt_factor=0.25)
9 |
--------------------------------------------------------------------------------
/exec/resources/simple_run.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.resources import simple_run_resource_example_test
6 |
7 |
8 | if __name__ == "__main__":
9 | simple_run_resource_example_test.test_simple_run()
10 |
--------------------------------------------------------------------------------
/exec/tilemap/process_web_animation.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.tilemap.web_animation.sim_runner_cs import (
6 | run_sim_process_animation as action,
7 | )
8 |
9 | if __name__ == "__main__":
10 | action()
11 |
--------------------------------------------------------------------------------
/exec/tilemap/tilemap_web_animation.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.tilemap.web_animation.sim_runner_cs import (
6 | run_sim_tilemap_animation as action,
7 | )
8 |
9 | if __name__ == "__main__":
10 | action()
11 |
--------------------------------------------------------------------------------
/exec/wait_for_interrupt/visual_run.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | import examples.wait_for_interrupt.test_wait_for_interrupt_visual as test
6 |
7 | if __name__ == "__main__":
8 | test.FLOW_SPEED = 200
9 | test.test_visualized_run()
10 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/ElementsUnion.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 |
6 | class ElementsUnion(object):
7 | NONE = 0
8 | Photo = 1
9 | Image = 2
10 | Text = 3
11 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar-project.properties
2 | # must be unique in a given SonarQube instance
3 | sonar.projectKey=casymda
4 |
5 | # --- optional properties ---
6 |
7 | sonar.sources=src
8 |
9 | sonar.sourceEncoding=UTF-8
10 |
11 | sonar.python.coverage.reportPaths=coverage.xml
12 |
--------------------------------------------------------------------------------
/docker/docker-entrypoint_examples-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # lets do a complete package install and see if the examples work
3 | # (unfortunately its not possible to obtain a useable coverage report this way,
4 | # since the source path of the xml will point to the site-packages directory)
5 | eval "pip install ."
6 | eval "python3 -m pytest examples"
7 |
--------------------------------------------------------------------------------
/flatbuffers/readme.md:
--------------------------------------------------------------------------------
1 | # flatbuffers for transferring animation frame information
2 |
3 | ```l
4 | snap install flatc
5 |
6 | flatc --python -o src flatbuffers/csa.fbs
7 | git checkout HEAD -- src/casymda/__init__.py # (replacement of existing file avoidable?)
8 |
9 | flatc --js -o src/casymda/visualization/web_server flatbuffers/csa.fbs
10 | ```
11 |
--------------------------------------------------------------------------------
/exec/basics/visual_run.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.basics.visualization import visual_run_bpmn_example_test
6 |
7 |
8 | if __name__ == "__main__":
9 | visual_run_bpmn_example_test.MAX_ENTITIES = 6
10 | visual_run_bpmn_example_test.FLOW_SPEED = 200
11 | visual_run_bpmn_example_test.test_visualized_run()
12 |
--------------------------------------------------------------------------------
/exec/resources/visual_run.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from examples.resources import visual_run_resource_example_test
6 |
7 |
8 | if __name__ == "__main__":
9 | visual_run_resource_example_test.MAX_ENTITIES = 6
10 | visual_run_resource_example_test.FLOW_SPEED = 200
11 | visual_run_resource_example_test.test_visualized_run()
12 |
--------------------------------------------------------------------------------
/src/casymda/blocks/named_resource.py:
--------------------------------------------------------------------------------
1 | """named resource"""
2 | from simpy import Resource
3 |
4 |
5 | class NamedResource(Resource):
6 | """simpy resource with name"""
7 |
8 | def __init__(self, env, name, capacity=1, xy=None, ways=None):
9 | super().__init__(env, capacity=capacity)
10 |
11 | self.name = name
12 | self.xy = xy
13 | self.ways = ways
14 |
--------------------------------------------------------------------------------
/src/casymda/visualization/tilemap/tilemap_visualizer_interface.py:
--------------------------------------------------------------------------------
1 | from casymda.blocks.entity import Entity
2 |
3 |
4 | class TilemapVisualizerInterface:
5 | """interface for tilemap visualizations"""
6 |
7 | def animate(self, entity: Entity, x: float, y: float, current_time: float):
8 | raise NotImplementedError()
9 |
10 | def destroy(self, entity: Entity):
11 | raise NotImplementedError()
12 |
--------------------------------------------------------------------------------
/src/casymda/visualization/tilemap/tilemap_visualizer.py:
--------------------------------------------------------------------------------
1 | """
2 | 2D visualization of positions on a ScaledCanvas
3 | """
4 |
5 | from casymda.visualization.entity_visualizer import EntityVisualizer
6 | from casymda.visualization.tilemap.tilemap_visualizer_interface import (
7 | TilemapVisualizerInterface,
8 | )
9 |
10 |
11 | class TilemapVisualizer(EntityVisualizer, TilemapVisualizerInterface):
12 | """visualizes entity positions at a given canvas"""
13 |
--------------------------------------------------------------------------------
/benchmark/model/parse_bpmn.py:
--------------------------------------------------------------------------------
1 | """create a casymda model from a bpmn file and a template"""
2 | from casymda.bpmn.bpmn_parser import parse_bpmn
3 |
4 | BPMN_PATH = "benchmark/model/diagram.bpmn"
5 | TEMPLATE_PATH = "benchmark/model/model_template.py"
6 | JSON_PATH = "benchmark/model/_temp_bpmn.json"
7 | MODEL_PATH = "benchmark/model/model.py"
8 |
9 |
10 | def test_parse_bpmn():
11 | parse_bpmn(BPMN_PATH, JSON_PATH, TEMPLATE_PATH, MODEL_PATH)
12 |
13 |
14 | if __name__ == "__main__":
15 | test_parse_bpmn()
16 |
--------------------------------------------------------------------------------
/examples/tilemap/coordinates_holder_setup.py:
--------------------------------------------------------------------------------
1 | from casymda.blocks.tilemap.coordinates_holder import CoordinatesHolder, delete_cached
2 |
3 | CSV_FILE = "examples/tilemap/6x6_ABC.csv"
4 | SCALE = 40 # px / tile
5 | INCLUDE_DIAGONAL = True
6 |
7 |
8 | def get_coordinates_holder():
9 | """gets called before model instantiation to provide the coordinates_holder"""
10 | return CoordinatesHolder(CSV_FILE, scale=SCALE, include_diagonal=INCLUDE_DIAGONAL)
11 |
12 |
13 | if __name__ == "__main__":
14 | delete_cached(CSV_FILE)
15 |
--------------------------------------------------------------------------------
/benchmark/readme.md:
--------------------------------------------------------------------------------
1 | # benchmark
2 |
3 | simple sample model to compare execution times:
4 |
5 | ## execution
6 |
7 | activate corresponding environment
8 |
9 | python benchmark/model/run_benchmark.py
10 | (overwrites previous results)
11 |
12 | python benchmark/analysis/gather_data.py
13 | python benchmark/analysis/plot.py
14 |
15 | ## configuration
16 |
17 | see [run_benchmark_test.py](./model/run_benchmark_test.py)
18 |
19 | configurable number of created entities (longer runs) and inter-arrival-time (impacts number of simultaneously simulated entities)
20 |
--------------------------------------------------------------------------------
/examples/tilemap/info.md:
--------------------------------------------------------------------------------
1 | # Tilemap-based simulation of object movements
2 |
3 | The model contains an example for the casymda tilemap feature. Entities are moving on a small tilemap between the nodes "A" & "C".
4 |
5 | ## Setup the _CoordinatesHolder_
6 |
7 | Parsed, processed (shortest paths), and cached (pickled) tilemap csv information is held by a managing `coordinates_holder`.
8 | This coordinates-holder object is created/imported (from a _cooh-config_) as part of the `model_template` and before the generated model-blocks.
9 | It can then be referenced by the blocks in need.
10 |
--------------------------------------------------------------------------------
/src/casymda/visualization/state_icons/state_icons_pathfinder.py:
--------------------------------------------------------------------------------
1 | """the location of this module corresponds to
2 | the location of the icon image files"""
3 | import os
4 |
5 | from casymda.blocks.block_components.state import States
6 |
7 | CURRENT_FILE_PATH = os.path.dirname(os.path.realpath(__file__))
8 | STATE_PATHS = {}
9 | for state in States:
10 | STATE_PATHS[state] = CURRENT_FILE_PATH + "/" + state.value + ".png"
11 |
12 |
13 | def get_file_path(for_state: States) -> str:
14 | """returns the file path of the state icon"""
15 | return STATE_PATHS[for_state]
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files you want to always be normalized and converted
5 | # to native line endings on checkout.
6 | # e.g.: *.c text
7 |
8 | # Declare files that will always have CRLF line endings on checkout.
9 | # e.g.: *.sln text eol=crlf
10 |
11 | # Declare files that will always have LF line endings on checkout.
12 | *.sh text eol=lf
13 |
14 | # Denote all files that are truly binary and should not be modified.
15 | *.png binary
16 | *.jpg binary
17 | *.pdf binary
18 |
--------------------------------------------------------------------------------
/examples/tilemap/parse_tilemap_example_test.py:
--------------------------------------------------------------------------------
1 | """create a casymda model from a bpmn file and a template"""
2 | from casymda.bpmn.bpmn_parser import parse_bpmn
3 |
4 | BPMN_PATH = "examples/tilemap/diagram.bpmn"
5 | TEMPLATE_PATH = "examples/tilemap/tilemap_example_template.py"
6 | JSON_PATH = "examples/tilemap/_temp_bpmn.json"
7 | MODEL_PATH = "examples/tilemap/tilemap_example_model.py"
8 |
9 |
10 | def test_parse_bpmn():
11 | """parse_bpmn"""
12 | parse_bpmn(BPMN_PATH, JSON_PATH, TEMPLATE_PATH, MODEL_PATH)
13 |
14 |
15 | if __name__ == "__main__":
16 | test_parse_bpmn()
17 |
--------------------------------------------------------------------------------
/benchmark/analysis/gather_data.py:
--------------------------------------------------------------------------------
1 | import pandas
2 |
3 | # retrieve data relative to project root
4 |
5 | data_paths = [
6 | "benchmark/results/CPython38.csv",
7 | "./benchmark/results/PyPy73.csv",
8 | "../csa4cs/Benchmark/results/results.csv",
9 | "../csa4j/benchmark/results_merged.csv",
10 | ]
11 | result_path = "./benchmark/results/results.csv"
12 |
13 | results = pandas.DataFrame()
14 | for path in data_paths:
15 | read = pandas.read_csv(path, index_col=False)
16 | results = results.append(
17 | read,
18 | )
19 | results.to_csv(result_path, index=False)
20 |
--------------------------------------------------------------------------------
/examples/basics/bpmn/parse_bpmn_example_test.py:
--------------------------------------------------------------------------------
1 | """create a casymda model from a bpmn file and a template"""
2 | from casymda.bpmn.bpmn_parser import parse_bpmn
3 |
4 | BPMN_PATH = "examples/basics/bpmn/bpmn_example.bpmn"
5 | TEMPLATE_PATH = "examples/basics/bpmn/bpmn_example_template.py"
6 | JSON_PATH = "examples/basics/bpmn/_temp_bpmn.json"
7 | MODEL_PATH = "examples/basics/model/bpmn_example_model.py"
8 |
9 |
10 | def test_parse_bpmn():
11 | """parse_bpmn"""
12 | parse_bpmn(BPMN_PATH, JSON_PATH, TEMPLATE_PATH, MODEL_PATH)
13 |
14 |
15 | if __name__ == "__main__":
16 | test_parse_bpmn()
17 |
--------------------------------------------------------------------------------
/examples/resources/parse_resource_example_test.py:
--------------------------------------------------------------------------------
1 | """create a casymda model from a bpmn file and a template"""
2 | from casymda.bpmn.bpmn_parser import parse_bpmn
3 |
4 | BPMN_PATH = "examples/resources/resource_example.bpmn"
5 | TEMPLATE_PATH = "examples/resources/resource_example_template.py"
6 | JSON_PATH = "examples/resources/_temp_bpmn.json"
7 | MODEL_PATH = "examples/resources/resource_example_model.py"
8 |
9 |
10 | def test_parse_bpmn():
11 | """parse_bpmn"""
12 | parse_bpmn(BPMN_PATH, JSON_PATH, TEMPLATE_PATH, MODEL_PATH)
13 |
14 |
15 | if __name__ == "__main__":
16 | test_parse_bpmn()
17 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/parse_bpmn_to_model.py:
--------------------------------------------------------------------------------
1 | """create a casymda model from a bpmn file and a template"""
2 | from casymda.bpmn.bpmn_parser import parse_bpmn
3 |
4 | BPMN_PATH = "examples/wait_for_interrupt/diagram.bpmn"
5 | TEMPLATE_PATH = "examples/wait_for_interrupt/model_template.py"
6 | JSON_PATH = "examples/wait_for_interrupt/_temp_diagram_bpmn.json"
7 | MODEL_PATH = "examples/wait_for_interrupt/model.py"
8 |
9 |
10 | def test_parse_bpmn():
11 | """parse_bpmn"""
12 | parse_bpmn(BPMN_PATH, JSON_PATH, TEMPLATE_PATH, MODEL_PATH)
13 |
14 |
15 | if __name__ == "__main__":
16 | test_parse_bpmn()
17 |
--------------------------------------------------------------------------------
/src/casymda/blocks/tilemap/segment.py:
--------------------------------------------------------------------------------
1 | from typing import Tuple
2 |
3 |
4 | class Segment:
5 | """holds information about a segment of a path of an entity"""
6 |
7 | def __init__(
8 | self, origin, destination, direction, length, cumulated_length, cumulated_time
9 | ) -> None:
10 | self.origin: Tuple[float, float] = origin
11 | self.destination: Tuple[float, float] = destination
12 | self.direction: Tuple[float, float] = direction
13 | self.length: float = length
14 | self.cumulated_length: float = cumulated_length
15 | self.cumlated_time: float = cumulated_time
16 |
--------------------------------------------------------------------------------
/examples/resources_loop/parse_resource_loop_example_test.py:
--------------------------------------------------------------------------------
1 | """create a casymda model from a bpmn file and a template"""
2 | from casymda.bpmn.bpmn_parser import parse_bpmn
3 |
4 | BPMN_PATH = "examples/resources_loop/resource_loop_example.bpmn"
5 | TEMPLATE_PATH = "examples/resources_loop/resource_loop_example_template.py"
6 | JSON_PATH = "examples/resources_loop/_temp_bpmn.json"
7 | MODEL_PATH = "examples/resources_loop/resource_loop_example_model.py"
8 |
9 |
10 | def test_parse_bpmn():
11 | """parse_bpmn"""
12 | parse_bpmn(BPMN_PATH, JSON_PATH, TEMPLATE_PATH, MODEL_PATH)
13 |
14 |
15 | if __name__ == "__main__":
16 | test_parse_bpmn()
17 |
--------------------------------------------------------------------------------
/src/casymda/blocks/wait_for_interrupt.py:
--------------------------------------------------------------------------------
1 | """wait for interrupt"""
2 | from simpy import Interrupt
3 |
4 | from casymda.blocks.block_components import VisualizableBlock
5 |
6 |
7 | class WaitForInterrupt(VisualizableBlock):
8 | """processes entities until their process is interrupted"""
9 |
10 | def __init__(self, env, name, xy=None, ways=None):
11 | super().__init__(env, name, xy=xy, ways=ways)
12 |
13 | # note: entity.current_process might be used for interrupt
14 | def actual_processing(self, entity):
15 | try:
16 | yield self.env.timeout(float("inf"))
17 | except Interrupt:
18 | pass
19 |
--------------------------------------------------------------------------------
/src/casymda/blocks/resource_seize_queue.py:
--------------------------------------------------------------------------------
1 | """seize resource"""
2 | from casymda.blocks.block_components import VisualizableBlock
3 | from casymda.blocks import Entity
4 |
5 |
6 | class ResourceSeizeQueue(VisualizableBlock):
7 | """waiting for free resource before seizing and moving forward"""
8 |
9 | def __init__(self, env, name, xy=None, ways=None, resource=None):
10 | super().__init__(env, name, xy=xy, ways=ways)
11 |
12 | self.resource_to_request = resource
13 |
14 | def actual_processing(self, entity: Entity):
15 | req = self.resource_to_request.request()
16 | yield req
17 | entity.seized_resources.append(req)
18 |
--------------------------------------------------------------------------------
/examples/basics/model/simple_run_bpmn_example_test.py:
--------------------------------------------------------------------------------
1 | """sample model execution"""
2 | from simpy import Environment
3 |
4 | import examples.basics.model.bpmn_example_model as model_module
5 |
6 |
7 | def test_simple_run():
8 | """run model"""
9 |
10 | env = Environment()
11 | model = model_module.Model(env)
12 | model.source.max_entities = 6
13 | model.source.inter_arrival_time = 0
14 |
15 | model.env.run(until=123.1)
16 |
17 | assert model.sink.overall_count_in == model.source.overall_count_in
18 | assert model.spr_1.overall_count_in == 3
19 | assert model.proc_1.overall_count_in == 3
20 |
21 | print("\n\nsimulation done.\n\n")
22 |
--------------------------------------------------------------------------------
/examples/tilemap/simple_run_tilemap_example_test.py:
--------------------------------------------------------------------------------
1 | from simpy import Environment
2 |
3 | import examples.tilemap.tilemap_example_model as model_module
4 | from examples.tilemap.coordinates_holder_setup import SCALE
5 |
6 |
7 | def test_simple_run():
8 | """run model"""
9 |
10 | env = Environment()
11 | model = model_module.Model(env)
12 | model.source.max_entities = 1
13 | model.source.inter_arrival_time = 0
14 | model.tilemover.speed = 1
15 |
16 | env.run()
17 |
18 | assert model.sink.overall_count_in == 1
19 | assert env.now == 17.05 * SCALE # depends on scale: 17.05 * tilemap_scale
20 |
21 | print("\n\nsimulation done.\n\n")
22 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/test_simple_run.py:
--------------------------------------------------------------------------------
1 | from examples.wait_for_interrupt.model import Model
2 | from simpy import Environment
3 |
4 |
5 | def test_model():
6 | env = Environment()
7 | model = Model(env)
8 |
9 | env.run(until=3)
10 |
11 | assert model.source.overall_count_in == 6
12 | # three of the six produced entities are still waiting:
13 | assert model.sink.overall_count_in == 3
14 |
15 | assert model.sink_2.overall_count_in == model.source_2.overall_count_in == 3
16 |
17 | env.run(until=6)
18 |
19 | assert model.sink.overall_count_in == model.source.overall_count_in == 6
20 | assert model.sink_2.overall_count_in == model.source_2.overall_count_in == 6
21 |
--------------------------------------------------------------------------------
/benchmark/model/simple_run_test.py:
--------------------------------------------------------------------------------
1 | import benchmark.model.model as model_module
2 | from simpy import Environment
3 |
4 |
5 | def test_simple_run():
6 |
7 | model = model_module.Model(Environment())
8 |
9 | model.source.max_entities = 10
10 | model.source.inter_arrival_time = 0
11 |
12 | model.parallel_proc.process_time = 10
13 | model.sequential_proc.process_time = 10
14 |
15 | model.env.run()
16 |
17 | assert model.sink.overall_count_in == model.source.overall_count_in
18 | assert model.parallel_proc.overall_count_in == model.source.max_entities / 2
19 | assert model.sequential_proc.overall_count_in == model.source.max_entities / 2
20 | assert model.env.now == 50
21 |
--------------------------------------------------------------------------------
/src/casymda/blocks/__init__.py:
--------------------------------------------------------------------------------
1 | """convenience import for easier access"""
2 | from casymda import __version__
3 | from casymda.blocks.delay import Delay
4 | from casymda.blocks.entity import Entity
5 | from casymda.blocks.named_resource import NamedResource
6 | from casymda.blocks.resource_release import ResourceRelease
7 | from casymda.blocks.resource_seize_queue import ResourceSeizeQueue
8 | from casymda.blocks.round_robin_gateway import RoundRobinGateway
9 | from casymda.blocks.seize_process_release import SeizeProcessRelease
10 | from casymda.blocks.sink import Sink
11 | from casymda.blocks.source import Source
12 | from casymda.blocks.wait_for_interrupt import WaitForInterrupt
13 | from casymda.blocks.tilemap.tilemap_movement import TilemapMovement
14 |
--------------------------------------------------------------------------------
/src/casymda/blocks/delay.py:
--------------------------------------------------------------------------------
1 | """ process """
2 | from casymda.blocks.block_components import VisualizableBlock
3 |
4 |
5 | # processed entity waits for process time (timeout) before being forwarded
6 | # to the successor
7 |
8 |
9 | class Delay(VisualizableBlock):
10 | """simple delay for a given time"""
11 |
12 | def __init__(
13 | self,
14 | env,
15 | name,
16 | xy=None,
17 | ways=None,
18 | process_time=1.0,
19 | block_capacity=float("inf"),
20 | ):
21 | super().__init__(env, name, xy=xy, ways=ways, block_capacity=block_capacity)
22 | self.process_time = process_time
23 |
24 | def actual_processing(self, entity):
25 | yield self.env.timeout(self.process_time)
26 |
--------------------------------------------------------------------------------
/examples/basics/model/long_run_bpmn_example_test.py:
--------------------------------------------------------------------------------
1 | """sample model execution"""
2 | import examples.basics.model.bpmn_example_model as model_module
3 | from simpy import Environment
4 |
5 |
6 | def test_simple_run():
7 | """run model"""
8 |
9 | env = Environment()
10 | model = model_module.Model(env)
11 | model.source.max_entities = 10 ** 3 # increase for higher runtime
12 | model.source.inter_arrival_time = 0 # increase for lower runtime
13 |
14 | model.env.run()
15 |
16 | assert model.sink.overall_count_in == model.source.overall_count_in
17 | assert model.sink.overall_count_in == model.source.max_entities
18 |
19 | print("\n\nsimulation done.\n\n")
20 |
21 |
22 | if __name__ == "__main__":
23 | test_simple_run()
24 |
--------------------------------------------------------------------------------
/src/casymda/blocks/entity.py:
--------------------------------------------------------------------------------
1 | """ entity """
2 | from typing import List, Optional
3 |
4 | from simpy import Environment, Resource, Process
5 |
6 |
7 | class Entity:
8 | """flow object which is moved between components"""
9 |
10 | def __init__(self, env: Environment, name: str):
11 | self.env = env
12 | self.name = name
13 | self.current_process: Optional[Process] = None
14 |
15 | # requested resources, info needed for release
16 | self.seized_resources: List[Resource] = []
17 | self.time_of_last_arrival: float = -1
18 |
19 | self.block_resource_request = None
20 |
21 | # optional special icon path, checked by process_visualizer
22 | self.process_animation_icon_path: Optional[str] = None
23 |
--------------------------------------------------------------------------------
/src/casymda/blocks/block_components/visualizer_interface.py:
--------------------------------------------------------------------------------
1 | from abc import abstractmethod
2 |
3 | from casymda.blocks.block_components.state import States
4 | from casymda.blocks.entity import Entity
5 |
6 |
7 | class BlockVisualizer:
8 | """visualizer called from the visualizable_block"""
9 |
10 | @abstractmethod
11 | def animate_block(self, block, queuing_direction_x=None):
12 | raise NotImplementedError()
13 |
14 | @abstractmethod
15 | def change_block_state(self, block, state: States, new_value: bool):
16 | raise NotImplementedError()
17 |
18 | @abstractmethod
19 | def animate_entity_flow(self, entity: Entity, from_block, to_block):
20 | """animate_entity_flow
21 | destroy entity if to_block is None"""
22 | raise NotImplementedError()
23 |
--------------------------------------------------------------------------------
/src/casymda/blocks/sink.py:
--------------------------------------------------------------------------------
1 | """sink"""
2 | from casymda.blocks.block_components import VisualizableBlock
3 |
4 |
5 | class Sink(VisualizableBlock):
6 | """sink; yields 0 timeout event before no further processing is started"""
7 |
8 | def __init__(self, env, name, xy=None, ways=None):
9 | super().__init__(env, name, xy=xy, ways=ways)
10 |
11 | def process_entity(self, entity):
12 | yield self.env.timeout(0)
13 |
14 | entity.time_of_last_arrival = self.env.now
15 | self.on_enter(entity)
16 | self.overall_count_in += 1
17 | self.entities.append(entity)
18 | self.block_resource.release(entity.block_resource_request)
19 | self.on_exit(entity, None)
20 |
21 | def actual_processing(self, entity):
22 | """not called in this special block"""
23 |
--------------------------------------------------------------------------------
/examples/resources_loop/simple_run_resource_loop_example_test.py:
--------------------------------------------------------------------------------
1 | """sample model execution"""
2 | from simpy import Environment
3 |
4 | import examples.resources_loop.resource_loop_example_model as model_module
5 |
6 |
7 | def test_simple_run():
8 | """run model"""
9 |
10 | env = Environment()
11 | model = model_module.Model(env)
12 | model.source.max_entities = 6
13 | model.source.inter_arrival_time = 0
14 |
15 | model.env.run(until=11)
16 | assert model.sink.overall_count_in == 1
17 |
18 | model.env.run(until=None)
19 |
20 | assert model.source.overall_count_in == model.source.max_entities
21 | assert model.sink.overall_count_in == model.source.overall_count_in
22 | assert model.proc_1.overall_count_in == 11
23 | assert model.proc_2.overall_count_in == 5
24 |
25 | print("\n\nsimulation done.\n\n")
26 |
--------------------------------------------------------------------------------
/tests/environments/synced_float_test.py:
--------------------------------------------------------------------------------
1 | from casymda.environments.realtime_environment import SyncedFloat
2 |
3 | synced_float = SyncedFloat._create_factor_instance(factor=2.0)
4 |
5 |
6 | def test_right_division_float():
7 | result = 1.0 / synced_float
8 | assert result == 0.5
9 |
10 |
11 | def test_right_division_int():
12 | result = 1 / synced_float
13 | assert result == 0.5
14 |
15 |
16 | def test_left_division_float():
17 | result = synced_float / 4.0
18 | assert result == 0.5
19 |
20 |
21 | def test_left_division_int():
22 | result = synced_float / 4
23 | assert result == 0.5
24 |
25 |
26 | def test_multiplication_left_float():
27 | result = synced_float * 0.25
28 | assert result == 0.5
29 |
30 |
31 | def test_multiplication_right_float():
32 | result = 0.25 * synced_float
33 | assert result == 0.5
34 |
--------------------------------------------------------------------------------
/examples/resources/simple_run_resource_example_test.py:
--------------------------------------------------------------------------------
1 | """sample model execution"""
2 | from simpy import Environment
3 |
4 | import examples.resources.resource_example_model as model_module
5 |
6 |
7 | def test_simple_run():
8 | """run model"""
9 |
10 | env = Environment()
11 | model = model_module.Model(env)
12 | model.source.max_entities = 6
13 | model.source.inter_arrival_time = 0
14 |
15 | model.env.run(until=11)
16 | assert model.sink.overall_count_in == 2
17 |
18 | model.env.run(until=31)
19 |
20 | assert model.source.overall_count_in == model.source.max_entities
21 | assert model.sink.overall_count_in == model.source.overall_count_in
22 | assert model.proc_1.overall_count_in == model.source.overall_count_in
23 | assert model.proc_2.overall_count_in == model.source.overall_count_in
24 |
25 | print("\n\nsimulation done.\n\n")
26 |
--------------------------------------------------------------------------------
/src/casymda/blocks/round_robin_gateway.py:
--------------------------------------------------------------------------------
1 | """gateway"""
2 | from casymda.blocks.block_components import VisualizableBlock
3 | from casymda.blocks.entity import Entity
4 |
5 |
6 | class RoundRobinGateway(VisualizableBlock):
7 | """gateway which cyclically chooses next successor for incoming entities"""
8 |
9 | def __init__(self, env, name, xy=None, ways=None):
10 | super().__init__(env, name, xy=xy, ways=ways)
11 |
12 | self.round_robin = self.round_robin_generator()
13 |
14 | def actual_processing(self, entity):
15 | yield self.env.timeout(0)
16 |
17 | def find_successor(self, entity: Entity):
18 | return next(self.round_robin)
19 |
20 | def round_robin_generator(self):
21 | """iterate over successors, remember last one"""
22 | last = -1
23 | while True:
24 | last += 1
25 | last %= len(self.successors)
26 | yield self.successors[last]
27 |
--------------------------------------------------------------------------------
/benchmark/model/model_template.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | #!model
25 |
26 | # translate model_graph_names into corresponding objects
27 | self.model_graph = {
28 | self.model_components[name]: [
29 | self.model_components[nameSucc]
30 | for nameSucc in self.model_graph_names[name]
31 | ]
32 | for name in self.model_graph_names
33 | }
34 |
35 | for component in self.model_graph:
36 | component.successors = self.model_graph[component]
37 |
--------------------------------------------------------------------------------
/flatbuffers/csa.fbs:
--------------------------------------------------------------------------------
1 | // IDL for web-canvas data
2 |
3 | namespace casymda.visualization.web_server.flatbuffers;
4 |
5 | table Canvas {
6 | content: [Element];
7 | }
8 |
9 | union ElementsUnion { Photo, Image, Text }
10 |
11 | table Element {
12 | id: ushort;
13 | element: ElementsUnion;
14 | }
15 |
16 | table Photo { // caching should be implemented by the canvas/client
17 | factor: float = 1;
18 | path: string;
19 | }
20 |
21 | table Image { // "PlacedPhoto"
22 | position: CanvasPosition;
23 | anchor: Anchor;
24 | photo_id: ushort;
25 | path: string; // needed?
26 | factor: float = 1; // needed?
27 | }
28 |
29 | table Text {
30 | position: CanvasPosition;
31 | anchor: Anchor;
32 | text: string;
33 | fill: string;
34 | font_family: string;
35 | font_size: ubyte = 10;
36 | }
37 |
38 | struct CanvasPosition {
39 | x: ushort;
40 | y: ushort;
41 | }
42 |
43 | struct Anchor {
44 | x: float = 0;
45 | y: float = 0;
46 | }
47 |
48 | root_type Canvas;
49 |
--------------------------------------------------------------------------------
/examples/basics/bpmn/bpmn_example_template.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | #!model
25 |
26 | # translate model_graph_names into corresponding objects
27 | self.model_graph = {
28 | self.model_components[name]: [
29 | self.model_components[nameSucc]
30 | for nameSucc in self.model_graph_names[name]
31 | ]
32 | for name in self.model_graph_names
33 | }
34 |
35 | for component in self.model_graph:
36 | component.successors = self.model_graph[component]
37 |
--------------------------------------------------------------------------------
/examples/resources/resource_example_template.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | #!model
25 |
26 | # translate model_graph_names into corresponding objects
27 | self.model_graph = {
28 | self.model_components[name]: [
29 | self.model_components[nameSucc]
30 | for nameSucc in self.model_graph_names[name]
31 | ]
32 | for name in self.model_graph_names
33 | }
34 |
35 | for component in self.model_graph:
36 | component.successors = self.model_graph[component]
37 |
--------------------------------------------------------------------------------
/examples/resources_loop/resource_loop_example_template.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | #!model
25 |
26 | # translate model_graph_names into corresponding objects
27 | self.model_graph = {
28 | self.model_components[name]: [
29 | self.model_components[nameSucc]
30 | for nameSucc in self.model_graph_names[name]
31 | ]
32 | for name in self.model_graph_names
33 | }
34 |
35 | for component in self.model_graph:
36 | component.successors = self.model_graph[component]
37 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/model_template.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import Sink, Source, WaitForInterrupt
3 |
4 | from .interruptor import Interruptor
5 |
6 |
7 | class Model:
8 | """generated model"""
9 |
10 | def __init__(self, env):
11 |
12 | self.env = env
13 |
14 | #!resources+components
15 |
16 | #!model
17 |
18 | # translate model_graph_names into corresponding objects
19 | self.model_graph = {
20 | self.model_components[name]: [
21 | self.model_components[nameSucc]
22 | for nameSucc in self.model_graph_names[name]
23 | ]
24 | for name in self.model_graph_names
25 | }
26 |
27 | for component in self.model_graph:
28 | component.successors = self.model_graph[component]
29 |
30 | # custom additional wiring logic
31 | # nice possible extension: set this type of link in the bpmn and parse it
32 | self.free_wait.block_to_interrupt = self.wait
33 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/Anchor.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class Anchor(object):
12 | __slots__ = ["_tab"]
13 |
14 | # Anchor
15 | def Init(self, buf, pos):
16 | self._tab = flatbuffers.table.Table(buf, pos)
17 |
18 | # Anchor
19 | def X(self):
20 | return self._tab.Get(
21 | flatbuffers.number_types.Float32Flags,
22 | self._tab.Pos + flatbuffers.number_types.UOffsetTFlags.py_type(0),
23 | )
24 |
25 | # Anchor
26 | def Y(self):
27 | return self._tab.Get(
28 | flatbuffers.number_types.Float32Flags,
29 | self._tab.Pos + flatbuffers.number_types.UOffsetTFlags.py_type(4),
30 | )
31 |
32 |
33 | def CreateAnchor(builder, x, y):
34 | builder.Prep(4, 8)
35 | builder.PrependFloat32(y)
36 | builder.PrependFloat32(x)
37 | return builder.Offset()
38 |
--------------------------------------------------------------------------------
/tests/source_test.py:
--------------------------------------------------------------------------------
1 | """test source"""
2 | from typing import List
3 | from simpy import Environment
4 |
5 | from casymda.blocks import Source, Entity
6 | from casymda.blocks.block_components import VisualizableBlock
7 |
8 |
9 | def test_if_source_is_creating_entities():
10 | """test source"""
11 | env = Environment()
12 | core_source = Source(env, "source", inter_arrival_time=0)
13 | entity_receiver = EntityReceiver(env)
14 | core_source.successors.append(entity_receiver)
15 |
16 | env.run(until=1)
17 | assert len(entity_receiver.received_entities) == core_source.overall_count_in
18 | assert core_source.overall_count_in == core_source.max_entities
19 |
20 |
21 | class EntityReceiver(VisualizableBlock):
22 | """entity receiver"""
23 |
24 | received_entities: List[Entity] = []
25 |
26 | def __init__(self, env):
27 | super().__init__(env, "name")
28 |
29 | def process_entity(self, entity):
30 | self.received_entities.append(entity)
31 | yield self.env.timeout(0)
32 |
33 | def actual_processing(self, entity):
34 | pass
35 |
--------------------------------------------------------------------------------
/benchmark/analysis/plot.py:
--------------------------------------------------------------------------------
1 | import pandas
2 | import matplotlib.pyplot as plt
3 |
4 | results_csv = "benchmark/results/results.csv"
5 |
6 | results = pandas.read_csv(results_csv)
7 | print(results)
8 |
9 | print("\nFiltered:\n")
10 | inter_arrival_time = 10
11 | results = results[results["inter_arrival_time"] == inter_arrival_time]
12 | print(results)
13 |
14 | grouped_results = results.groupby(["runtime"]) # , "inter_arrival_time"])
15 |
16 | fig, ax = plt.subplots(figsize=(8, 6))
17 | for label, df in grouped_results:
18 | df.plot(
19 | x="n_entities",
20 | y="time",
21 | ylabel="time [s]",
22 | xlabel="# of entities",
23 | ax=ax,
24 | label=label,
25 | # loglog=True,
26 | # logx=True,
27 | grid=True,
28 | linestyle="--",
29 | marker="o",
30 | )
31 | plt.legend()
32 | explanation = "queuing" if inter_arrival_time == 0 else "no queuing"
33 | subtitle = f"inter-arrival time: {inter_arrival_time} ({explanation})"
34 | plt.title("Execution time by number of entities for different runtimes\n" + subtitle)
35 | plt.show()
36 |
--------------------------------------------------------------------------------
/src/casymda/blocks/resource_release.py:
--------------------------------------------------------------------------------
1 | """resource release"""
2 | from simpy import Resource
3 |
4 | from casymda.blocks.block_components import VisualizableBlock
5 | from casymda.blocks.entity import Entity
6 |
7 |
8 | class ResourceRelease(VisualizableBlock):
9 | """releases the specified resource
10 | (needs to be present in the entities resources list)"""
11 |
12 | def __init__(
13 | self,
14 | env,
15 | name,
16 | xy=None,
17 | ways=None,
18 | resource: Resource = None,
19 | ):
20 | super().__init__(env, name, xy=xy, ways=ways)
21 |
22 | self.resource_to_release: Resource = resource
23 |
24 | def actual_processing(self, entity: Entity):
25 |
26 | # find request of defined resource in entity requests list
27 | idx = list(map(lambda x: x.resource, entity.seized_resources)).index(
28 | self.resource_to_release
29 | )
30 | req = entity.seized_resources[idx]
31 |
32 | # done immediately, but necessary for generator behavior
33 | yield self.resource_to_release.release(req)
34 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/CanvasPosition.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class CanvasPosition(object):
12 | __slots__ = ["_tab"]
13 |
14 | # CanvasPosition
15 | def Init(self, buf, pos):
16 | self._tab = flatbuffers.table.Table(buf, pos)
17 |
18 | # CanvasPosition
19 | def X(self):
20 | return self._tab.Get(
21 | flatbuffers.number_types.Uint16Flags,
22 | self._tab.Pos + flatbuffers.number_types.UOffsetTFlags.py_type(0),
23 | )
24 |
25 | # CanvasPosition
26 | def Y(self):
27 | return self._tab.Get(
28 | flatbuffers.number_types.Uint16Flags,
29 | self._tab.Pos + flatbuffers.number_types.UOffsetTFlags.py_type(2),
30 | )
31 |
32 |
33 | def CreateCanvasPosition(builder, x, y):
34 | builder.Prep(2, 4)
35 | builder.PrependUint16(y)
36 | builder.PrependUint16(x)
37 | return builder.Offset()
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 FFC
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.
--------------------------------------------------------------------------------
/examples/tilemap/tilemap_example_template.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | TilemapMovement,
13 | )
14 | from examples.tilemap.coordinates_holder_setup import get_coordinates_holder
15 |
16 | coordinates_holder = get_coordinates_holder()
17 |
18 |
19 | class Model:
20 | """generated model"""
21 |
22 | def __init__(self, env):
23 |
24 | self.env = env
25 |
26 | #!resources+components
27 |
28 | #!model
29 |
30 | # translate model_graph_names into corresponding objects
31 | self.model_graph = {
32 | self.model_components[name]: [
33 | self.model_components[nameSucc]
34 | for nameSucc in self.model_graph_names[name]
35 | ]
36 | for name in self.model_graph_names
37 | }
38 |
39 | for component in self.model_graph:
40 | component.successors = self.model_graph[component]
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .DS_Store
3 |
4 | *_temp_*
5 |
6 | virtual_env/
7 | virtualenv/
8 | venv/
9 |
10 | .scannerwork/
11 |
12 | .mypy_cache/
13 |
14 | # Byte-compiled / optimized / DLL files
15 | __pycache__/
16 | *.py[cod]
17 | *$py.class
18 |
19 | # C extensions
20 | *.so
21 |
22 | # Distribution / packaging
23 | .Python
24 | build/
25 | develop-eggs/
26 | dist/
27 | downloads/
28 | eggs/
29 | .eggs/
30 | lib/
31 | lib64/
32 | parts/
33 | sdist/
34 | var/
35 | wheels/
36 | pip-wheel-metadata/
37 | share/python-wheels/
38 | *.egg-info/
39 | .installed.cfg
40 | *.egg
41 | MANIFEST
42 |
43 | # PyInstaller
44 | # Usually these files are written by a python script from a template
45 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
46 | *.manifest
47 | *.spec
48 |
49 | # Installer logs
50 | pip-log.txt
51 | pip-delete-this-directory.txt
52 |
53 | # Unit test / coverage reports
54 | htmlcov/
55 | .tox/
56 | .nox/
57 | .coverage
58 | .coverage.*
59 | .cache
60 | nosetests.xml
61 | coverage.xml
62 | *.cover
63 | .hypothesis/
64 | .pytest_cache/
65 |
66 | # Translations
67 | *.mo
68 | *.pot
69 |
70 |
71 | pypy_venv
72 |
73 | *.prof
74 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/interruptor.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from casymda.blocks.block_components import VisualizableBlock
4 | from casymda.blocks.block_components.block import Block
5 |
6 |
7 | class Interruptor(VisualizableBlock):
8 | """Interrupts the last entity in a linked block, if possible"""
9 |
10 | def __init__(self, env, name, xy=None, ways=None):
11 | super().__init__(env, name, xy=xy, ways=ways)
12 | self.block_to_interrupt: Optional[Block] = None
13 |
14 | def actual_processing(self, entity):
15 | yield self.env.timeout(0)
16 |
17 | if (
18 | self.block_to_interrupt is not None # block is set
19 | and len(self.block_to_interrupt.entities)
20 | > 0 # block has entities which are currently waiting to be interrupted
21 | ):
22 | # find entity to interrupt
23 | entity_to_interrupt = self.block_to_interrupt.entities[-1]
24 | entity_to_interrupt.current_process.interrupt()
25 | print(
26 | f"{self.name}-{entity.name} interrupted {self.block_to_interrupt.name}-{entity_to_interrupt.name}"
27 | )
28 | else:
29 | print(f"nothing to interrupt for {entity.name}")
30 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/test_wait_for_interrupt_visual.py:
--------------------------------------------------------------------------------
1 | import os
2 | from tkinter import *
3 |
4 | from casymda.visualization.canvas.tk_canvas import ScaledCanvasTk
5 | from casymda.visualization.process_visualizer import ProcessVisualizer
6 | from examples.wait_for_interrupt.model import Model
7 | from simpy import Environment
8 |
9 | FLOW_SPEED = 2 ** 20
10 |
11 |
12 | def test_visualized_run():
13 | if os.name != "nt" and os.environ.get("DISPLAY", "") == "":
14 | print("no display, animated run pointless (e.g. inside a container)")
15 | return
16 |
17 | env = Environment()
18 | model = Model(env)
19 |
20 | width = 336
21 | height = 331
22 | gui = Tk()
23 | canvas = ScaledCanvasTk(gui, width, height)
24 |
25 | visualizer = ProcessVisualizer(
26 | canvas,
27 | flow_speed=FLOW_SPEED,
28 | background_image_path="examples/wait_for_interrupt/diagram.png",
29 | default_entity_icon_path="examples/basics/visualization/simple_entity_icon.png",
30 | )
31 |
32 | for block in model.model_components.values():
33 | block.visualizer = visualizer
34 |
35 | model.env.run()
36 |
37 | assert model.sink.overall_count_in == model.source.max_entities
38 | assert model.sink_2.overall_count_in == model.source_2.max_entities
39 |
40 | gui.destroy()
41 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """setuptools setup"""
2 |
3 | from setuptools import find_packages, setup
4 |
5 | VERSION = "0.2.29"
6 |
7 | try:
8 | with open("readme.md") as f:
9 | long_description = f.read()
10 | except FileNotFoundError:
11 | long_description = ""
12 |
13 |
14 | setup(
15 | name="casymda",
16 | url="https://github.com/fladdimir/casymda",
17 | author="FFC",
18 | author_email="fladdi.mir@gmx.de",
19 | packages=find_packages("src"),
20 | package_dir={"": "src"},
21 | package_data={
22 | "casymda.visualization.state_icons": ["*.png"],
23 | "casymda.visualization.web_server": ["*.html", "*.js"],
24 | },
25 | version=VERSION,
26 | install_requires=[
27 | "simpy",
28 | "xmltodict",
29 | "Pillow",
30 | "flask",
31 | "flask-cors",
32 | "black==19.3b0",
33 | "numpy",
34 | "networkx",
35 | "flatbuffers==1.12",
36 | ],
37 | zip_safe=False,
38 | license="MIT",
39 | description="Simple DES modeling and simulation"
40 | + " based on SimPy, BPMN, and pixi.js",
41 | classifiers=[
42 | "Development Status :: 3 - Alpha",
43 | "Intended Audience :: Developers",
44 | "License :: OSI Approved :: MIT License",
45 | "Programming Language :: Python :: 3",
46 | ],
47 | long_description=long_description,
48 | long_description_content_type="text/markdown",
49 | )
50 |
--------------------------------------------------------------------------------
/examples/resources/visual_run_resource_example_test.py:
--------------------------------------------------------------------------------
1 | """simple example including visualization"""
2 | import os
3 | from simpy import Environment
4 | from tkinter import *
5 |
6 | from casymda.visualization.process_visualizer import ProcessVisualizer
7 | from casymda.visualization.canvas.tk_canvas import ScaledCanvasTk
8 | from examples.resources.resource_example_model import Model
9 |
10 | FLOW_SPEED = 2 ** 20
11 | MAX_ENTITIES = 6
12 |
13 |
14 | def test_visualized_run():
15 | """test visualized run"""
16 | if os.name != "nt" and os.environ.get("DISPLAY", "") == "":
17 | print("no display, animated run pointless (e.g. inside a container)")
18 | # (this check for DISPLAY does not work on win)
19 | return
20 |
21 | env = Environment()
22 | model = Model(env)
23 | model.source.max_entities = MAX_ENTITIES
24 |
25 | width = 800
26 | height = 600
27 | gui = Tk()
28 | canvas = ScaledCanvasTk(gui, width, height)
29 |
30 | visualizer = ProcessVisualizer(
31 | canvas,
32 | flow_speed=FLOW_SPEED,
33 | background_image_path="examples/resources/resource_example.png",
34 | default_entity_icon_path="examples/resources/simple_entity_icon.png",
35 | )
36 |
37 | for block in model.model_components.values():
38 | block.visualizer = visualizer
39 |
40 | model.env.run(until=31)
41 | assert model.sink.overall_count_in == 6
42 |
43 | gui.destroy()
44 |
--------------------------------------------------------------------------------
/examples/tilemap/tilemap_process_visualizer_test.py:
--------------------------------------------------------------------------------
1 | """simple example including visualization"""
2 | import os
3 | from simpy import Environment
4 | from tkinter import *
5 |
6 | from casymda.visualization.process_visualizer import ProcessVisualizer
7 | from casymda.visualization.canvas.tk_canvas import ScaledCanvasTk
8 | from examples.tilemap.tilemap_example_model import Model
9 |
10 | FLOW_SPEED = 2 ** 20
11 | MAX_ENTITIES = 1
12 | INTER_ARRIVAL_TIME = 0
13 |
14 |
15 | def test_visualized_run():
16 | """test visualized run"""
17 | if os.name != "nt" and os.environ.get("DISPLAY", "") == "":
18 | print("no display, animated run pointless (e.g. inside a container)")
19 | # (this check for DISPLAY does not work on win)
20 | return
21 |
22 | env = Environment()
23 | model = Model(env)
24 | model.source.max_entities = MAX_ENTITIES
25 |
26 | width = 451
27 | height = 212
28 | gui = Tk()
29 | canvas = ScaledCanvasTk(gui, width, height)
30 |
31 | visualizer = ProcessVisualizer(
32 | canvas,
33 | flow_speed=FLOW_SPEED,
34 | background_image_path="examples/tilemap/diagram.png",
35 | default_entity_icon_path="examples/tilemap/simple_entity_icon.png",
36 | )
37 |
38 | for block in model.model_components.values():
39 | block.visualizer = visualizer
40 |
41 | env.run()
42 | assert model.sink.overall_count_in == MAX_ENTITIES
43 |
44 | gui.destroy()
45 |
--------------------------------------------------------------------------------
/examples/resources_loop/visual_run_resource_loop_example_test.py:
--------------------------------------------------------------------------------
1 | """simple example including visualization"""
2 | import os
3 | from simpy import Environment
4 | from tkinter import *
5 |
6 | from casymda.visualization.process_visualizer import ProcessVisualizer
7 | from casymda.visualization.canvas.tk_canvas import ScaledCanvasTk
8 | from examples.resources_loop.resource_loop_example_model import Model
9 |
10 | FLOW_SPEED = 2 ** 15
11 | MAX_ENTITIES = 6
12 |
13 |
14 | def test_visualized_run():
15 | """test visualized run"""
16 | if os.name != "nt" and os.environ.get("DISPLAY", "") == "":
17 | print("no display, animated run pointless (e.g. inside a container)")
18 | # (this check for DISPLAY does not work on win)
19 | return
20 |
21 | env = Environment()
22 | model = Model(env)
23 | model.source.max_entities = MAX_ENTITIES
24 |
25 | width = 1065
26 | height = 472
27 | gui = Tk()
28 | canvas = ScaledCanvasTk(gui, width, height)
29 |
30 | visualizer = ProcessVisualizer(
31 | canvas,
32 | flow_speed=FLOW_SPEED,
33 | background_image_path="examples/resources_loop/resource_example.png",
34 | default_entity_icon_path="examples/resources_loop/simple_entity_icon.png",
35 | )
36 |
37 | for block in model.model_components.values():
38 | block.visualizer = visualizer
39 |
40 | model.env.run(until=None)
41 | assert model.sink.overall_count_in == 6
42 |
43 | gui.destroy()
44 |
--------------------------------------------------------------------------------
/examples/basics/visualization/visual_run_bpmn_example_test.py:
--------------------------------------------------------------------------------
1 | """simple example including visualization"""
2 | import os
3 | from simpy import Environment
4 | from tkinter import *
5 |
6 | from casymda.visualization.process_visualizer import ProcessVisualizer
7 | from casymda.visualization.canvas.tk_canvas import ScaledCanvasTk
8 | from examples.basics.model.bpmn_example_model import Model
9 |
10 | FLOW_SPEED = 2 ** 20
11 | MAX_ENTITIES = 2
12 |
13 |
14 | def test_visualized_run():
15 | """test visualized run"""
16 | if os.name != "nt" and os.environ.get("DISPLAY", "") == "":
17 | print("no display, animated run pointless (e.g. inside a container)")
18 | # (this check for DISPLAY does not work on win)
19 | return
20 |
21 | env = Environment()
22 | model = Model(env)
23 | model.source.max_entities = MAX_ENTITIES
24 |
25 | width = 800
26 | height = 600
27 | gui = Tk()
28 | canvas = ScaledCanvasTk(gui, width, height)
29 |
30 | visualizer = ProcessVisualizer(
31 | canvas,
32 | flow_speed=FLOW_SPEED,
33 | background_image_path="examples/basics/visualization/bpmn_example.png",
34 | default_entity_icon_path="examples/basics/visualization/simple_entity_icon.png",
35 | )
36 |
37 | for block in model.model_components.values():
38 | block.visualizer = visualizer
39 |
40 | model.env.run(until=float("inf"))
41 |
42 | assert model.sink.overall_count_in == model.source.max_entities == MAX_ENTITIES
43 |
44 | gui.destroy()
45 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/Photo.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class Photo(object):
12 | __slots__ = ["_tab"]
13 |
14 | @classmethod
15 | def GetRootAsPhoto(cls, buf, offset):
16 | n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
17 | x = Photo()
18 | x.Init(buf, n + offset)
19 | return x
20 |
21 | # Photo
22 | def Init(self, buf, pos):
23 | self._tab = flatbuffers.table.Table(buf, pos)
24 |
25 | # Photo
26 | def Factor(self):
27 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
28 | if o != 0:
29 | return self._tab.Get(
30 | flatbuffers.number_types.Float32Flags, o + self._tab.Pos
31 | )
32 | return 1.0
33 |
34 | # Photo
35 | def Path(self):
36 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
37 | if o != 0:
38 | return self._tab.String(o + self._tab.Pos)
39 | return None
40 |
41 |
42 | def PhotoStart(builder):
43 | builder.StartObject(2)
44 |
45 |
46 | def PhotoAddFactor(builder, factor):
47 | builder.PrependFloat32Slot(0, factor, 1.0)
48 |
49 |
50 | def PhotoAddPath(builder, path):
51 | builder.PrependUOffsetTRelativeSlot(
52 | 1, flatbuffers.number_types.UOffsetTFlags.py_type(path), 0
53 | )
54 |
55 |
56 | def PhotoEnd(builder):
57 | return builder.EndObject()
58 |
--------------------------------------------------------------------------------
/src/casymda/blocks/source.py:
--------------------------------------------------------------------------------
1 | """source"""
2 | from typing import Type
3 |
4 | from casymda.blocks.block_components import VisualizableBlock
5 | from casymda.blocks.entity import Entity
6 |
7 |
8 | class Source(VisualizableBlock):
9 | """the source has a scheduled "creation_loop" process,
10 | which creates entities and then processes them regularly
11 | (as if the entity just entered a regular block,
12 | but with 0 time delay)"""
13 |
14 | def __init__(
15 | self,
16 | env,
17 | name,
18 | xy=None,
19 | ways=None,
20 | entity_type: Type[Entity] = Entity,
21 | inter_arrival_time: int = 0,
22 | max_entities: int = 5,
23 | ):
24 |
25 | super().__init__(env, name, xy=xy, ways=ways)
26 |
27 | self.entity_type = entity_type
28 | self.inter_arrival_time = inter_arrival_time
29 | self.max_entities = max_entities
30 |
31 | self.entity_counter = 0
32 |
33 | self.env.process(self.creation_loop())
34 |
35 | def creation_loop(self):
36 | """create entities as needed"""
37 | for i in range(self.max_entities):
38 | self.entity_counter += 1
39 | entity = self.entity_type(self.env, "entity_" + str(self.entity_counter))
40 |
41 | entity.block_resource_request = self.block_resource.request()
42 | yield entity.block_resource_request
43 |
44 | self.env.process(self.process_entity(entity))
45 |
46 | if i < self.max_entities - 1:
47 | yield self.env.timeout(self.inter_arrival_time)
48 |
49 | def actual_processing(self, entity):
50 | yield self.env.timeout(0)
51 |
--------------------------------------------------------------------------------
/src/casymda/visualization/canvas/scaled_canvas.py:
--------------------------------------------------------------------------------
1 | """canvas"""
2 |
3 | from abc import abstractmethod
4 |
5 |
6 | class ScaledCanvas:
7 | """overview of canvas methods called from visualizer"""
8 |
9 | def __init__(self, scale: float) -> None:
10 | self.scale = scale
11 |
12 | @abstractmethod
13 | def load_image_file(self, path: str):
14 | """takes file path and returns loaded image file reference"""
15 |
16 | @abstractmethod
17 | def create_image(self, x_coord: int, y_coord: int, image_file, anchor="c") -> int:
18 | """places image on canvas and returns image reference"""
19 |
20 | @abstractmethod
21 | def create_text(
22 | self,
23 | x_coord: int,
24 | y_coord: int,
25 | text: str = "",
26 | anchor: str = "se",
27 | fill: str = "black",
28 | font: str = "Helvetica 16",
29 | ) -> int:
30 | """places text on canvas and returns reference"""
31 |
32 | @abstractmethod
33 | def delete(self, element_id: int) -> bool:
34 | """deletes element and returns True if deleted"""
35 |
36 | @abstractmethod
37 | def set_coords(self, element_id: int, x_y: tuple):
38 | """places the given element at a given position"""
39 |
40 | @abstractmethod
41 | def set_text_value(self, element_id: int, text: str):
42 | """changes the elements text"""
43 |
44 | @abstractmethod
45 | def get_width(self) -> int:
46 | """returns width"""
47 |
48 | @abstractmethod
49 | def get_height(self) -> int:
50 | """returns heigth"""
51 |
52 | def _scale_coords(self, x_y: tuple):
53 | """returns tuple values multiplied by factor"""
54 | return tuple(map(lambda c: int(c * self.scale), x_y))
55 |
--------------------------------------------------------------------------------
/src/casymda/blocks/block_components/visualizable_block.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List, Optional, Tuple
2 |
3 | from casymda.blocks.block_components.block import Block
4 | from casymda.blocks.block_components.visualizer_interface import BlockVisualizer
5 | from simpy import Environment
6 |
7 |
8 | class VisualizableBlock(Block):
9 | """extends block by visualization-related behavior"""
10 |
11 | def __init__(
12 | self,
13 | env: Environment,
14 | name: str,
15 | block_capacity=float("inf"),
16 | xy: Tuple[int, int] = (0, 0),
17 | ways: Dict[str, List[Tuple[int, int]]] = {},
18 | ):
19 |
20 | super().__init__(env, name, block_capacity=block_capacity)
21 |
22 | self.visualizer: Optional[BlockVisualizer] = None
23 |
24 | self.ways = ways
25 | self.xy_position: Tuple[int, int] = xy
26 |
27 | self.queuing: bool = True # read by visualizer
28 |
29 | def on_block_change(self):
30 | """animate the block, including e.g. it's contents"""
31 | super().on_block_change()
32 | if self.visualizer is not None:
33 | # to be done: the extension of Block could be replaced by
34 | # a composition and provision of the callbacks
35 | self.visualizer.animate_block(self)
36 |
37 | def on_block_state_change(self, state, new_value):
38 | super().on_block_state_change(state, new_value)
39 | if self.visualizer is not None:
40 | self.visualizer.change_block_state(self, state, new_value)
41 |
42 | def on_entity_movement(self, entity, successor):
43 | super().on_entity_movement(entity, successor)
44 | if self.visualizer is not None:
45 | self.visualizer.animate_entity_flow(entity, self, successor)
46 |
--------------------------------------------------------------------------------
/examples/tilemap/tilemap_visualizer_test.py:
--------------------------------------------------------------------------------
1 | """simple example including visualization"""
2 | import os
3 | from tkinter import Tk
4 |
5 | from casymda.environments.realtime_environment import (
6 | ChangeableFactorRealtimeEnvironment as RealtimeEnvironment,
7 | )
8 |
9 | from casymda.visualization.canvas.tk_canvas import ScaledCanvasTk
10 | from casymda.visualization.tilemap.tilemap_visualizer import TilemapVisualizer
11 | from examples.tilemap.tilemap_example_model import Model
12 |
13 |
14 | def test_visualized_run_tilemap(rt_factor=0.001):
15 | """test visualized run"""
16 | if os.name != "nt" and os.environ.get("DISPLAY", "") == "":
17 | print("no display, animated run pointless (e.g. inside a container)")
18 | # (this check for DISPLAY does not work on win)
19 | return
20 |
21 | MAX_ENTITIES = 1
22 |
23 | env = RealtimeEnvironment()
24 | env.factor.set_value(rt_factor)
25 |
26 | model = Model(env)
27 | model.source.max_entities = MAX_ENTITIES
28 |
29 | scale = 1.5
30 | width = 240 * scale
31 | height = 260 * scale
32 | gui = Tk()
33 | canvas = ScaledCanvasTk(gui, width, height, scale=scale)
34 |
35 | visualizer = TilemapVisualizer(
36 | canvas,
37 | background_image_path="examples/tilemap/tilemap-csv_240.png",
38 | default_entity_icon_path="examples/tilemap/simple_entity_icon.png",
39 | )
40 |
41 | model.tilemover.tilemap_visualizer = visualizer
42 |
43 | model.tilemover.speed = 1
44 |
45 | distance = 17.05 * model.tilemover.coordinates_holder.scale
46 |
47 | estimated_sim_time = distance / model.tilemover.speed
48 |
49 | env.run()
50 |
51 | gui.destroy()
52 |
53 | assert model.sink.overall_count_in == MAX_ENTITIES
54 | assert model.env.now - estimated_sim_time <= model.tilemover.MAX_ANIMATION_TIMESTEP
55 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/canvas_animation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Casymda Canvas Animation
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 |
real-time factor:
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/casymda/blocks/seize_process_release.py:
--------------------------------------------------------------------------------
1 | """seize process release"""
2 | from casymda.blocks import Delay, NamedResource, ResourceRelease, ResourceSeizeQueue
3 | from casymda.blocks.block_components import VisualizableBlock
4 |
5 |
6 | class SeizeProcessRelease(VisualizableBlock):
7 | """entities in this block follow the process:
8 | request resource, be processed, release resource"""
9 |
10 | def __init__(
11 | self, env, name, xy=None, ways=None, process_time=1.0, resource_capacity=1
12 | ):
13 | super().__init__(env, name, xy=xy, ways=ways)
14 |
15 | self.resource = NamedResource(self.env, "resource", capacity=resource_capacity)
16 |
17 | self.receiver = Receiver(self.env)
18 |
19 | self.seizer = ResourceSeizeQueue(
20 | env, self.name + "_seizer", resource=self.resource
21 | )
22 | self.seizer.successors.append(self.receiver)
23 | self.processor = Delay(env, self.name + "_processor", process_time=process_time)
24 | self.processor.successors.append(self.receiver)
25 | self.releaser = ResourceRelease(
26 | env, self.name + "_releaser", resource=self.resource
27 | )
28 | self.releaser.successors.append(self.receiver)
29 |
30 | def actual_processing(self, entity):
31 | yield self.env.process(self.seizer.process_entity(entity))
32 | yield self.env.process(self.processor.process_entity(entity))
33 | yield self.env.process(self.releaser.process_entity(entity))
34 |
35 |
36 | class Receiver:
37 | """receiver class to act as successor for inner blocks"""
38 |
39 | def __init__(self, env):
40 | self.env = env
41 | self.block_resource = NamedResource(env, "receiver_resource", capacity=1)
42 |
43 | def process_entity(self, entity):
44 | """just release the resource"""
45 | yield self.block_resource.release(entity.block_resource_request)
46 |
--------------------------------------------------------------------------------
/src/casymda/blocks/block_components/state.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Callable
3 |
4 |
5 | class States(Enum):
6 | """common block states"""
7 |
8 | empty = "empty" # "default", if all other states' count is 0
9 | busy = "busy" # counted
10 | blocked = "blocked" # by its successor, counted
11 |
12 |
13 | class StateManager:
14 | """
15 | a block is able to be in multiple states at a time.
16 | counts inc/dec events for each state and evaluates.
17 | """
18 |
19 | def __init__(self, on_state_change_callback: Callable[[States, bool], None]):
20 | self._counted_states = [States.busy, States.blocked] # could be parameter
21 | self._default_state = States.empty # could be parameter
22 | self._state_count = {state.value: 0 for state in self._counted_states}
23 | self._current_states = {state.value: False for state in States}
24 | self._on_state_change_callback = on_state_change_callback
25 |
26 | def increment_state_count(self, state):
27 | self._change_state_count(state, 1)
28 |
29 | def decrement_state_count(self, state):
30 | self._change_state_count(state, -1)
31 |
32 | def _change_state_count(self, state, change):
33 | self._state_count[state.value] += change
34 | self.evaluate_state_count()
35 |
36 | def evaluate_state_count(self):
37 | for state in self._counted_states:
38 | if self._state_count[state.value] > 0:
39 | self._set_new_state(state, True)
40 | else:
41 | self._set_new_state(state, False)
42 |
43 | if sum(self._state_count.values()) == 0:
44 | self._set_new_state(self._default_state, True)
45 | else:
46 | self._set_new_state(self._default_state, False)
47 |
48 | def _set_new_state(self, state, new_value):
49 | if self._current_states[state.value] != new_value:
50 | self._on_state_change_callback(state, new_value)
51 | self._current_states[state.value] = new_value
52 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/Canvas.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class Canvas(object):
12 | __slots__ = ["_tab"]
13 |
14 | @classmethod
15 | def GetRootAsCanvas(cls, buf, offset):
16 | n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
17 | x = Canvas()
18 | x.Init(buf, n + offset)
19 | return x
20 |
21 | # Canvas
22 | def Init(self, buf, pos):
23 | self._tab = flatbuffers.table.Table(buf, pos)
24 |
25 | # Canvas
26 | def Content(self, j):
27 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
28 | if o != 0:
29 | x = self._tab.Vector(o)
30 | x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4
31 | x = self._tab.Indirect(x)
32 | from casymda.visualization.web_server.flatbuffers.Element import Element
33 |
34 | obj = Element()
35 | obj.Init(self._tab.Bytes, x)
36 | return obj
37 | return None
38 |
39 | # Canvas
40 | def ContentLength(self):
41 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
42 | if o != 0:
43 | return self._tab.VectorLen(o)
44 | return 0
45 |
46 | # Canvas
47 | def ContentIsNone(self):
48 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
49 | return o == 0
50 |
51 |
52 | def CanvasStart(builder):
53 | builder.StartObject(1)
54 |
55 |
56 | def CanvasAddContent(builder, content):
57 | builder.PrependUOffsetTRelativeSlot(
58 | 0, flatbuffers.number_types.UOffsetTFlags.py_type(content), 0
59 | )
60 |
61 |
62 | def CanvasStartContentVector(builder, numElems):
63 | return builder.StartVector(4, numElems, 4)
64 |
65 |
66 | def CanvasEnd(builder):
67 | return builder.EndObject()
68 |
--------------------------------------------------------------------------------
/benchmark/model/web_visu.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | from multiprocessing import Value
6 |
7 | import casymda.visualization.web_server.flask_sim_server as flask_sim_server
8 | import root_dir
9 | from benchmark.model.model import Model
10 | from casymda.environments.realtime_environment import (
11 | ChangeableFactorRealtimeEnvironment as Environment,
12 | )
13 | from casymda.environments.realtime_environment import SyncedFloat
14 | from casymda.visualization.canvas.web_canvas import WebCanvas
15 | from casymda.visualization.process_visualizer import ProcessVisualizer
16 | from casymda.visualization.web_server.sim_controller import RunnableSimulation
17 |
18 | SCALE = 1
19 | FLOW_SPEED = 400
20 | MAX_ENTITIES = 10
21 |
22 |
23 | class ExampleRunnableSimulation(RunnableSimulation):
24 | """Runnable simulation of our example model"""
25 |
26 | # single place to configure the width and height of the canvas, also given to browser
27 | width = 622 * SCALE
28 | height = 341 * SCALE
29 | root_file = root_dir.__file__
30 |
31 | def simulate(self, shared_state: dict, should_run: Value, factor: SyncedFloat):
32 |
33 | env = Environment(factor=factor, should_run=should_run)
34 | model = Model(env)
35 | model.source.max_entities = MAX_ENTITIES
36 |
37 | canvas = WebCanvas(shared_state, self.width, self.height, scale=SCALE)
38 |
39 | visualizer = ProcessVisualizer(
40 | canvas,
41 | flow_speed=FLOW_SPEED,
42 | background_image_path="benchmark/model/diagram.png",
43 | default_entity_icon_path="benchmark/model/simple_entity_icon.png",
44 | )
45 |
46 | for block in model.model_components.values():
47 | block.visualizer = visualizer
48 |
49 | while env.peek() < float("inf"):
50 | model.env.step()
51 |
52 | print("simulation done.\n")
53 |
54 |
55 | def run_sim():
56 | runnable_sim = ExampleRunnableSimulation()
57 | flask_sim_server.run_server(runnable_sim, port=5000)
58 |
59 |
60 | if __name__ == "__main__":
61 | run_sim()
62 |
--------------------------------------------------------------------------------
/tests/tilemap_nx/coordinates_holder_scale_test.py:
--------------------------------------------------------------------------------
1 | """test calculation of coordinates and shortest paths"""
2 | import os
3 |
4 | import pytest
5 |
6 | from casymda.blocks.tilemap.coordinates_holder import CoordinatesHolder, delete_cached
7 |
8 | CSV_FILE = "6x6_ABC.csv"
9 | directory = os.path.dirname(__file__)
10 | csv_path = os.path.join(directory, CSV_FILE)
11 |
12 | SCALE = 10
13 |
14 |
15 | @pytest.fixture(scope="module", autouse=True)
16 | def cleanup():
17 | delete_cached(csv_path)
18 |
19 |
20 | def test_nodes_coordinates():
21 | coordinates_holder = CoordinatesHolder(
22 | csv_path, scale=SCALE, include_diagonal=False
23 | )
24 |
25 | x, y = coordinates_holder.get_coords("A")
26 | assert x == 0 + SCALE / 2
27 | assert y == 0 + SCALE / 2
28 |
29 | x, y = coordinates_holder.get_coords("B")
30 | assert x == 50 + SCALE / 2
31 | assert y == 0 + SCALE / 2
32 |
33 | x, y = coordinates_holder.get_coords("C")
34 | assert x == 50 + SCALE / 2
35 | assert y == 50 + SCALE / 2
36 |
37 |
38 | def test_paths_lengths_steps_A_B():
39 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE)
40 |
41 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
42 | "A", "B"
43 | )
44 | steps = len(coords_path) - 1 # without starting node
45 | assert length == 50
46 | assert steps == 5
47 |
48 |
49 | def test_paths_lengths_steps_A_C():
50 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE)
51 |
52 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
53 | "A", "C"
54 | )
55 | steps = len(coords_path) - 1 # without starting node
56 | assert length == 200
57 | assert steps == 20
58 |
59 |
60 | def test_paths_lengths_steps_C_B():
61 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE)
62 |
63 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
64 | "C", "B"
65 | )
66 | steps = len(coords_path) - 1 # without starting node
67 | assert length == 150
68 | assert steps == 15
69 |
--------------------------------------------------------------------------------
/examples/basics/web_canvas/sim_runner_cs.py:
--------------------------------------------------------------------------------
1 | """ simulation process and model-specific config """
2 | from multiprocessing import Value
3 |
4 | import casymda.visualization.web_server.flask_sim_server as flask_sim_server
5 | import root_dir
6 | from casymda.environments.realtime_environment import (
7 | ChangeableFactorRealtimeEnvironment as Environment,
8 | )
9 | from casymda.environments.realtime_environment import SyncedFloat
10 | from casymda.visualization.canvas.web_canvas import WebCanvas
11 | from casymda.visualization.process_visualizer import ProcessVisualizer
12 | from casymda.visualization.web_server.sim_controller import RunnableSimulation
13 | from examples.basics.model.bpmn_example_model import Model
14 |
15 | SCALE = 1
16 | FLOW_SPEED = 400
17 | MAX_ENTITIES = 6
18 |
19 |
20 | class ExampleRunnableSimulation(RunnableSimulation):
21 | """Runnable simulation of our example model"""
22 |
23 | # single place to configure the width and height of the canvas, also given to browser
24 | width = 800 * SCALE
25 | height = 400 * SCALE
26 | root_file = root_dir.__file__
27 |
28 | def simulate(self, shared_state: dict, should_run: Value, factor: SyncedFloat):
29 |
30 | env = Environment(factor=factor, should_run=should_run)
31 | model = Model(env)
32 | model.source.max_entities = MAX_ENTITIES
33 |
34 | canvas = WebCanvas(shared_state, self.width, self.height, scale=SCALE)
35 |
36 | visualizer = ProcessVisualizer(
37 | canvas,
38 | flow_speed=FLOW_SPEED,
39 | background_image_path="examples/basics/visualization/bpmn_example.png",
40 | default_entity_icon_path="examples/basics/visualization/simple_entity_icon.png",
41 | )
42 |
43 | for block in model.model_components.values():
44 | block.visualizer = visualizer
45 |
46 | while env.peek() < float("inf"):
47 | model.env.step()
48 |
49 | print("simulation done.\n")
50 |
51 |
52 | def run_sim():
53 | runnable_sim = ExampleRunnableSimulation()
54 | flask_sim_server.run_server(runnable_sim, port=5002)
55 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/Element.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class Element(object):
12 | __slots__ = ["_tab"]
13 |
14 | @classmethod
15 | def GetRootAsElement(cls, buf, offset):
16 | n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
17 | x = Element()
18 | x.Init(buf, n + offset)
19 | return x
20 |
21 | # Element
22 | def Init(self, buf, pos):
23 | self._tab = flatbuffers.table.Table(buf, pos)
24 |
25 | # Element
26 | def Id(self):
27 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
28 | if o != 0:
29 | return self._tab.Get(
30 | flatbuffers.number_types.Uint16Flags, o + self._tab.Pos
31 | )
32 | return 0
33 |
34 | # Element
35 | def ElementType(self):
36 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
37 | if o != 0:
38 | return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
39 | return 0
40 |
41 | # Element
42 | def Element(self):
43 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
44 | if o != 0:
45 | from flatbuffers.table import Table
46 |
47 | obj = Table(bytearray(), 0)
48 | self._tab.Union(obj, o)
49 | return obj
50 | return None
51 |
52 |
53 | def ElementStart(builder):
54 | builder.StartObject(3)
55 |
56 |
57 | def ElementAddId(builder, id):
58 | builder.PrependUint16Slot(0, id, 0)
59 |
60 |
61 | def ElementAddElementType(builder, elementType):
62 | builder.PrependUint8Slot(1, elementType, 0)
63 |
64 |
65 | def ElementAddElement(builder, element):
66 | builder.PrependUOffsetTRelativeSlot(
67 | 2, flatbuffers.number_types.UOffsetTFlags.py_type(element), 0
68 | )
69 |
70 |
71 | def ElementEnd(builder):
72 | return builder.EndObject()
73 |
--------------------------------------------------------------------------------
/examples/tilemap/tilemap_example_model.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | TilemapMovement,
13 | )
14 | from examples.tilemap.coordinates_holder_setup import get_coordinates_holder
15 |
16 | coordinates_holder = get_coordinates_holder()
17 |
18 |
19 | class Model:
20 | """generated model"""
21 |
22 | def __init__(self, env):
23 |
24 | self.env = env
25 |
26 | #!resources+components
27 |
28 | self.source = Source(
29 | self.env,
30 | "source",
31 | xy=(80, 59),
32 | inter_arrival_time=100,
33 | max_entities=2,
34 | ways={"tilemover": [(98, 59), (181, 59)]},
35 | )
36 |
37 | self.sink = Sink(self.env, "sink", xy=(369, 59), ways={})
38 |
39 | self.tilemover = TilemapMovement(
40 | self.env,
41 | "tilemover",
42 | xy=(231, 59),
43 | speed=10,
44 | coordinates_holder=coordinates_holder,
45 | from_node="A",
46 | to_node="C",
47 | ways={"sink": [(281, 59), (351, 59)]},
48 | )
49 |
50 | #!model
51 |
52 | self.model_components = {
53 | "source": self.source,
54 | "sink": self.sink,
55 | "tilemover": self.tilemover,
56 | }
57 |
58 | self.model_graph_names = {
59 | "source": ["tilemover"],
60 | "sink": [],
61 | "tilemover": ["sink"],
62 | }
63 | # translate model_graph_names into corresponding objects
64 | self.model_graph = {
65 | self.model_components[name]: [
66 | self.model_components[nameSucc]
67 | for nameSucc in self.model_graph_names[name]
68 | ]
69 | for name in self.model_graph_names
70 | }
71 |
72 | for component in self.model_graph:
73 | component.successors = self.model_graph[component]
74 |
--------------------------------------------------------------------------------
/tests/tilemap_nx/coordinates_holder_diagonal_test.py:
--------------------------------------------------------------------------------
1 | """test calculation of coordinates and shortest paths"""
2 | import os
3 |
4 | import pytest
5 |
6 | from casymda.blocks.tilemap.coordinates_holder import CoordinatesHolder, delete_cached
7 |
8 | CSV_FILE = "6x6_ABC.csv"
9 | directory = os.path.dirname(__file__)
10 | csv_path = os.path.join(directory, CSV_FILE)
11 |
12 | SCALE = 1
13 |
14 |
15 | @pytest.fixture(scope="module", autouse=True)
16 | def cleanup():
17 | delete_cached(csv_path)
18 |
19 |
20 | def test_nodes_coordinates():
21 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE, include_diagonal=True)
22 |
23 | x, y = coordinates_holder.get_coords("A")
24 | assert x == 0 + SCALE / 2
25 | assert y == 0 + SCALE / 2
26 |
27 | x, y = coordinates_holder.get_coords("B")
28 | assert x == 5 + SCALE / 2
29 | assert y == 0 + SCALE / 2
30 |
31 | x, y = coordinates_holder.get_coords("C")
32 | assert x == 5 + SCALE / 2
33 | assert y == 5 + SCALE / 2
34 |
35 |
36 | def test_paths_lengths_steps_A_B():
37 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE, include_diagonal=True)
38 |
39 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
40 | "A", "B"
41 | )
42 | steps = len(coords_path) - 1 # without starting node
43 | assert length == 5
44 | assert steps == 5
45 |
46 |
47 | def test_paths_lengths_steps_A_C():
48 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE, include_diagonal=True)
49 |
50 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
51 | "A", "C"
52 | )
53 | steps = len(coords_path) - 1 # without starting node
54 | assert length == 10 * 1 + 5 * 1.41
55 | assert steps == 15
56 |
57 |
58 | def test_paths_lengths_steps_C_B():
59 | coordinates_holder = CoordinatesHolder(csv_path, scale=SCALE, include_diagonal=True)
60 |
61 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
62 | "C", "B"
63 | )
64 | steps = len(coords_path) - 1 # without starting node
65 | assert length == 7 * 1 + 4 * 1.41
66 | assert steps == 11
67 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | # 0. start sonarqube server
5 | # docker-compose up sonarqube
6 | sonarqube:
7 | image: sonarqube:latest
8 | ports:
9 | - 9000:9000
10 |
11 | # 1. run tests
12 | # use "docker-compose build" to rebuild the image in case of requirements.txt changes
13 | # docker-compose run unit-test
14 | unit-test:
15 | build: ./
16 | volumes:
17 | - .:/usr/src
18 | entrypoint: /usr/src/docker/docker-entrypoint_unit-test.sh
19 |
20 | # docker-compose run examples-test
21 | examples-test:
22 | build: ./
23 | volumes:
24 | - .:/usr/src
25 | entrypoint: /usr/src/docker/docker-entrypoint_examples-test.sh
26 |
27 | # docker-compose run examples-test-pypi
28 | # (with install from pypi)
29 | examples-test-pypi:
30 | build: ./
31 | volumes:
32 | - .:/usr/src
33 | entrypoint: /usr/src/docker/docker-entrypoint_examples-test_pypi.sh
34 |
35 | # then: 2. run analysis (and parse coverage report from the test)
36 | analysis:
37 | # docker-compose up analysis
38 | image: newtmitch/sonar-scanner:latest
39 | volumes:
40 | - .:/usr/src
41 |
42 | # runs the browser-based basic animation example
43 | # use "docker-compose build" to rebuild the image if necessary
44 | # docker-compose up web-animation-basic-example
45 | web-animation-basic-example:
46 | build: ./
47 | volumes:
48 | - .:/usr/src
49 | ports:
50 | - "5000:5000"
51 | command: bash -c "pip install -e . && python3 ./exec/basics/run_web_canvas_cs.py"
52 |
53 | # runs the browser-based basic animation example
54 | # use "docker-compose build" to rebuild the image if necessary
55 | # docker-compose up web-animation-tilemap-simple
56 | web-animation-tilemap-simple:
57 | build: ./
58 | volumes:
59 | - .:/usr/src
60 | ports:
61 | - "5000:5000"
62 | command: bash -c "pip install -e . && python3 ./exec/tilemap/tilemap_web_animation.py"
63 |
64 | # runs the browser-based basic animation example
65 | # use "docker-compose build" to rebuild the image if necessary
66 | # docker-compose up web-animation-tilemap-simple-process
67 | web-animation-tilemap-simple-process:
68 | build: ./
69 | volumes:
70 | - .:/usr/src
71 | ports:
72 | - "5000:5000"
73 | command: bash -c "pip install -e . && python3 ./exec/tilemap/process_web_animation.py"
74 |
--------------------------------------------------------------------------------
/benchmark/model/run_benchmark.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append(".")
4 |
5 | import time
6 | import sys
7 |
8 | import benchmark.model.model as model_module
9 | import pandas
10 | from simpy import Environment
11 |
12 | RESULTS_DIR = "./benchmark/results/"
13 |
14 | runtime = (
15 | "Casymda@SimPy(PyPy73)" if "PyPy" in sys.version else "Casymda@SimPy(CPython38)"
16 | ) # to be changed
17 | n_entities = [10, 100, 1000, 10_000, 50_000, 100_000, 200_000] # ~1 min i5
18 | inter_arrival_times = [0, 10]
19 |
20 |
21 | def run_benchmark():
22 | # warmup run?
23 | results = []
24 | for n_entity in n_entities:
25 | for iat in inter_arrival_times:
26 | sequential_proc_time = 10
27 | overall_seq_time = iat + (n_entity / 2) * sequential_proc_time
28 | last_time = (n_entity - 1) * iat + sequential_proc_time
29 | expected_end = max(last_time, overall_seq_time)
30 | t = run(
31 | max_entities=n_entity,
32 | inter_arrival_time=iat,
33 | sequential_proc_time=sequential_proc_time,
34 | expected_end=expected_end,
35 | )
36 | result = {
37 | "runtime": runtime,
38 | "n_entities": n_entity,
39 | "inter_arrival_time": iat,
40 | "time": t,
41 | }
42 | results.append(result)
43 | pandas.DataFrame(results).to_csv(RESULTS_DIR + runtime + ".csv", index=False)
44 |
45 |
46 | def run(
47 | max_entities=10,
48 | inter_arrival_time=0,
49 | parallel_proc_time=10,
50 | sequential_proc_time=10,
51 | expected_end=50,
52 | ) -> float:
53 |
54 | model = model_module.Model(Environment())
55 |
56 | model.source.max_entities = max_entities
57 | model.source.inter_arrival_time = inter_arrival_time
58 |
59 | model.parallel_proc.process_time = parallel_proc_time
60 | model.sequential_proc.process_time = sequential_proc_time
61 |
62 | t0 = time.time()
63 | model.env.run()
64 | t = time.time() - t0
65 |
66 | assert model.sink.overall_count_in == model.source.overall_count_in
67 | assert model.parallel_proc.overall_count_in == model.source.max_entities / 2
68 | assert model.sequential_proc.overall_count_in == model.source.max_entities / 2
69 | assert model.env.now == expected_end
70 |
71 | return t
72 |
73 |
74 | if __name__ == "__main__":
75 | run_benchmark()
76 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffer_deserializer.js:
--------------------------------------------------------------------------------
1 | import { casymda } from "./csa_generated.js";
2 | console.log("flattbuffer deserializer loaded");
3 |
4 | export async function deserialize_fb(response) {
5 | const array = await response.arrayBuffer(); // blob?
6 | const data = new Uint8Array(array);
7 | const buffer = new flatbuffers.ByteBuffer(data);
8 | const canvas = casymda.visualization.web_server.flatbuffers.Canvas.getRootAsCanvas(
9 | buffer
10 | );
11 |
12 | const state = {};
13 |
14 | const num_elements = canvas.contentLength();
15 | for (let i = 0; i < num_elements; i++) {
16 | const fb_element = canvas.content(i);
17 | const fb_typ = fb_element.elementType();
18 | const id_num = fb_element.id();
19 |
20 | const element = {};
21 | if (
22 | fb_typ == casymda.visualization.web_server.flatbuffers.ElementsUnion.Photo
23 | ) {
24 | const fb_photo = new casymda.visualization.web_server.flatbuffers.Photo();
25 | fb_photo.__init(fb_element.element(fb_element).bb_pos, buffer);
26 | element.type = "photo";
27 | element.path = fb_photo.path();
28 | element.factor = fb_photo.factor();
29 | } else if (
30 | fb_typ == casymda.visualization.web_server.flatbuffers.ElementsUnion.Image
31 | ) {
32 | // image
33 | const fb_image = new casymda.visualization.web_server.flatbuffers.Image();
34 | fb_image.__init(fb_element.element(fb_element).bb_pos, buffer);
35 | element.type = "image";
36 | element.x = fb_image.position().x();
37 | element.y = fb_image.position().y();
38 | element.anchor = [fb_image.anchor().x(), fb_image.anchor().y()];
39 | element.photo_id = fb_image.photoId();
40 | element.path = fb_image.path();
41 | element.factor = fb_image.factor();
42 | } else if (
43 | fb_typ == casymda.visualization.web_server.flatbuffers.ElementsUnion.Text
44 | ) {
45 | // text
46 | const fb_text = new casymda.visualization.web_server.flatbuffers.Text();
47 | fb_text.__init(fb_element.element(fb_element).bb_pos, buffer);
48 | element.type = "text";
49 | element.x = fb_text.position().x();
50 | element.y = fb_text.position().y();
51 | element.anchor = [fb_text.anchor().x(), fb_text.anchor().y()];
52 | element.text = fb_text.text();
53 | element.fill = fb_text.fill();
54 | element.font_family = fb_text.fontFamily();
55 | element.font_size = fb_text.fontSize();
56 | }
57 | state[id_num.toString()] = element; // move to end
58 | }
59 | return state;
60 | }
61 |
--------------------------------------------------------------------------------
/tests/tilemap_nx/coordinates_holder_straight_test.py:
--------------------------------------------------------------------------------
1 | """test calculation of coordinates and shortest paths"""
2 | import os
3 |
4 | from casymda.blocks.tilemap.coordinates_holder import CoordinatesHolder, delete_cached
5 | import pytest
6 |
7 | CSV_FILE = "6x6_ABC.csv"
8 | directory = os.path.dirname(__file__)
9 | csv_path = os.path.join(directory, CSV_FILE)
10 |
11 | SCALE = 1
12 |
13 |
14 | @pytest.fixture(scope="module", autouse=True)
15 | def cleanup():
16 | delete_cached(csv_path)
17 |
18 |
19 | def test_nodes_coordinates():
20 | coordinates_holder = CoordinatesHolder(
21 | csv_path, scale=SCALE, include_diagonal=False
22 | )
23 |
24 | # (remember that the center of the tile is the aim)
25 | x, y = coordinates_holder.get_coords("A")
26 | assert x == 0 + SCALE / 2
27 | assert y == 0 + SCALE / 2
28 |
29 | x, y = coordinates_holder.get_coords("B")
30 | assert x == 5 + SCALE / 2
31 | assert y == 0 + SCALE / 2
32 |
33 | x, y = coordinates_holder.get_coords("C")
34 | assert x == 5 + SCALE / 2
35 | assert y == 5 + SCALE / 2
36 |
37 |
38 | def test_paths_lengths_steps_A_A():
39 | coordinates_holder = CoordinatesHolder(csv_path, scale=1, include_diagonal=False)
40 |
41 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
42 | "A", "A"
43 | )
44 | steps = len(coords_path) - 1 # without starting node
45 | assert length == 0
46 | assert steps == 1 # one step to target (which has the same position)
47 |
48 |
49 | def test_paths_lengths_steps_A_B():
50 | coordinates_holder = CoordinatesHolder(csv_path, scale=1, include_diagonal=False)
51 |
52 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
53 | "A", "B"
54 | )
55 | steps = len(coords_path) - 1 # without starting node
56 | assert length == 5
57 | assert steps == 5
58 |
59 |
60 | def test_paths_lengths_steps_A_C():
61 | coordinates_holder = CoordinatesHolder(csv_path, scale=1, include_diagonal=False)
62 |
63 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
64 | "A", "C"
65 | )
66 | steps = len(coords_path) - 1 # without starting node
67 | assert length == 20
68 | assert steps == 20
69 |
70 |
71 | def test_paths_lengths_steps_C_B():
72 | coordinates_holder = CoordinatesHolder(csv_path, scale=1, include_diagonal=False)
73 |
74 | coords_path, length = coordinates_holder.get_path_coords_and_length_from_to(
75 | "C", "B"
76 | )
77 | steps = len(coords_path) - 1 # without starting node
78 | assert length == 15
79 | assert steps == 15
80 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/model.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import Sink, Source, WaitForInterrupt
3 |
4 | from .interruptor import Interruptor
5 |
6 |
7 | class Model:
8 | """generated model"""
9 |
10 | def __init__(self, env):
11 |
12 | self.env = env
13 |
14 | #!resources+components
15 |
16 | self.source = Source(
17 | self.env,
18 | "source",
19 | xy=(55, 59),
20 | inter_arrival_time=0,
21 | max_entities=6,
22 | ways={"wait": [(73, 59), (131, 59)]},
23 | )
24 |
25 | self.source_2 = Source(
26 | self.env,
27 | "source_2",
28 | xy=(55, 250),
29 | inter_arrival_time=1,
30 | max_entities=6,
31 | ways={"free_wait": [(73, 250), (131, 250)]},
32 | )
33 |
34 | self.sink = Sink(self.env, "sink", xy=(311, 59), ways={})
35 |
36 | self.sink_2 = Sink(self.env, "sink_2", xy=(311, 250), ways={})
37 |
38 | self.wait = WaitForInterrupt(
39 | self.env, "wait", xy=(181, 59), ways={"sink": [(231, 59), (293, 59)]}
40 | )
41 |
42 | self.free_wait = Interruptor(
43 | self.env,
44 | "free_wait",
45 | xy=(181, 250),
46 | ways={"sink_2": [(231, 250), (293, 250)]},
47 | )
48 |
49 | #!model
50 |
51 | self.model_components = {
52 | "source": self.source,
53 | "source_2": self.source_2,
54 | "sink": self.sink,
55 | "sink_2": self.sink_2,
56 | "wait": self.wait,
57 | "free_wait": self.free_wait,
58 | }
59 |
60 | self.model_graph_names = {
61 | "source": ["wait"],
62 | "source_2": ["free_wait"],
63 | "sink": [],
64 | "sink_2": [],
65 | "wait": ["sink"],
66 | "free_wait": ["sink_2"],
67 | }
68 | # translate model_graph_names into corresponding objects
69 | self.model_graph = {
70 | self.model_components[name]: [
71 | self.model_components[nameSucc]
72 | for nameSucc in self.model_graph_names[name]
73 | ]
74 | for name in self.model_graph_names
75 | }
76 |
77 | for component in self.model_graph:
78 | component.successors = self.model_graph[component]
79 |
80 | # custom additional wiring logic
81 | # nice possible extension: set this type of link in the bpmn and parse it
82 | self.free_wait.block_to_interrupt = self.wait
83 |
--------------------------------------------------------------------------------
/src/casymda/environments/realtime_environment.py:
--------------------------------------------------------------------------------
1 | from ctypes import c_bool
2 | from multiprocessing import Value
3 | from time import monotonic as time, sleep
4 |
5 | from simpy.core import EmptySchedule, Environment, Infinity
6 |
7 |
8 | class SyncedFloat:
9 | """holds a float value which can be shared between threads and used as a factor for a casymda ChangeableFactorRealtimeEnvironment"""
10 |
11 | def __init__(self, synced: Value):
12 | """can be instanciated via factory class method"""
13 | self.synced: Value = synced
14 |
15 | def __truediv__(self, other):
16 | return self.synced.value / other
17 |
18 | def __rtruediv__(self, other):
19 | return other / self.synced.value
20 |
21 | def __mul__(self, other):
22 | return other * self.synced.value
23 |
24 | def __rmul__(self, other):
25 | return self.__mul__(other)
26 |
27 | def set_value(self, value: float) -> float:
28 | self.synced.value = value
29 | return self.synced.value
30 |
31 | @classmethod
32 | def _create_factor_instance(cls, factor=1.0) -> "SyncedFloat":
33 | value = Value("d", factor)
34 | return SyncedFloat(value)
35 |
36 |
37 | class ChangeableFactorRealtimeEnvironment(Environment):
38 | """Realtime environment without strict-mode, but with possibility to set the factor dynamically"""
39 |
40 | pause_sleep_time = 0.25
41 |
42 | def __init__(
43 | self,
44 | initial_time=0,
45 | factor=SyncedFloat._create_factor_instance(),
46 | should_run=Value(c_bool, True),
47 | ):
48 | """factor can be obtained via 'get_factor_instance' classmethod.
49 | also works when factor is"""
50 | self.factor: SyncedFloat = factor
51 | self.should_run: Value = should_run
52 | super().__init__(initial_time)
53 |
54 | def step(self):
55 | """modified step function without strict-mode but working with changing factors"""
56 |
57 | if not self.should_run.value:
58 | sleep(self.pause_sleep_time)
59 | return
60 |
61 | evt_time = self.peek()
62 |
63 | if evt_time is Infinity:
64 | raise EmptySchedule()
65 |
66 | sleep_time = (evt_time - self.now) * self.factor
67 |
68 | if sleep_time > 0.01:
69 | sleep(sleep_time)
70 |
71 | super().step()
72 |
73 | @classmethod
74 | def create_factor_instance(cls, factor=1.0) -> SyncedFloat:
75 | """forwards to SyncedFloat factory method"""
76 | return SyncedFloat._create_factor_instance(factor=factor)
77 |
78 | @classmethod
79 | def create_should_run_instance(cls) -> Value:
80 | return Value(c_bool, True)
81 |
--------------------------------------------------------------------------------
/src/casymda/visualization/canvas/tk_canvas.py:
--------------------------------------------------------------------------------
1 | """canvas based on tkinter"""
2 | import platform
3 | from tkinter import Tk, Canvas
4 |
5 | from PIL import Image, ImageTk
6 |
7 | from casymda.visualization.canvas.scaled_canvas import ScaledCanvas
8 |
9 |
10 | class ScaledCanvasTk(ScaledCanvas):
11 | """draws onto tkinter canvas"""
12 |
13 | def __init__(self, tk_gui: Tk, width, height, scale=1.0):
14 | super().__init__(scale)
15 | self.tk_gui = tk_gui
16 | self.width = width
17 | self.height = height
18 | self.canvas = Canvas(tk_gui, width=self.width, height=self.height, bg="white")
19 | self.canvas.pack(side="left")
20 | self.update()
21 |
22 | def load_image_file(self, path: str):
23 | """takes file path and returns loaded image file reference"""
24 |
25 | image = (
26 | Image.open(path).convert("RGBA")
27 | if platform.system() != "Darwin"
28 | else Image.open(path).convert("RGB")
29 | )
30 | width, height = self._scale_coords(image.size)
31 |
32 | resized_image = image.resize((width, height), Image.ANTIALIAS)
33 | image_file = ImageTk.PhotoImage(resized_image)
34 |
35 | return image_file
36 |
37 | def create_image(self, x_coord: int, y_coord: int, image_file, anchor="c"):
38 | x_y = self._scale_coords((x_coord, y_coord))
39 | image = self.canvas.create_image(*x_y, image=image_file, anchor=anchor)
40 | self.update()
41 | return image
42 |
43 | def create_text(
44 | self,
45 | x_coord: int,
46 | y_coord: int,
47 | text: str = "",
48 | anchor: str = "c",
49 | fill: str = "black",
50 | font: str = "Helvetica 10",
51 | ):
52 | x_y = self._scale_coords((x_coord, y_coord))
53 | created_text = self.canvas.create_text(
54 | *x_y, font=font, fill=fill, anchor=anchor, text=text
55 | )
56 | return created_text
57 |
58 | def set_coords(self, element_id, x_y):
59 | x_y = self._scale_coords(x_y)
60 | canvas_coords = self.canvas.coords(element_id, x_y)
61 | self.update()
62 | return canvas_coords
63 |
64 | def delete(self, element_id: int) -> bool:
65 | self.canvas.delete(element_id)
66 | self.update()
67 | return True
68 |
69 | def set_text_value(self, element_id: int, text: str):
70 | self.canvas.itemconfig(element_id, text=text)
71 | self.update()
72 |
73 | def get_width(self) -> int:
74 | return self.width
75 |
76 | def get_height(self) -> int:
77 | return self.height
78 |
79 | def update(self):
80 | """updates the canvas containing gui"""
81 | self.tk_gui.update()
82 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/flatbuffer_serializer.py:
--------------------------------------------------------------------------------
1 | import flatbuffers
2 |
3 | from . import Anchor, Canvas, CanvasPosition, Element, Image, Photo, Text
4 | from .ElementsUnion import ElementsUnion
5 |
6 |
7 | def serialize(state: dict):
8 |
9 | num_elements = len(state)
10 | fb = flatbuffers.Builder(1024) # default decisive?
11 |
12 | elements = []
13 | i: str
14 | e: dict
15 | for i, e in state.items():
16 |
17 | if e["type"] == "photo":
18 | path = fb.CreateString(e["path"])
19 | Photo.PhotoStart(fb)
20 | Photo.PhotoAddFactor(fb, e["factor"])
21 | Photo.PhotoAddPath(fb, path)
22 | elementBody = Photo.PhotoEnd(fb)
23 | elementType = ElementsUnion.Photo
24 |
25 | elif e["type"] == "image":
26 | path = fb.CreateString(e["path"])
27 | Image.ImageStart(fb)
28 | Image.ImageAddAnchor(fb, Anchor.CreateAnchor(fb, *e["anchor"]))
29 | Image.ImageAddFactor(fb, e["factor"])
30 | Image.ImageAddPath(fb, path)
31 | Image.ImageAddPhotoId(fb, e["photo_id"])
32 | Image.ImageAddPosition(
33 | fb, CanvasPosition.CreateCanvasPosition(fb, e["x"], e["y"])
34 | )
35 | elementBody = Image.ImageEnd(fb)
36 | elementType = ElementsUnion.Image
37 |
38 | elif e["type"] == "text":
39 | fill = fb.CreateString(e["fill"])
40 | font = fb.CreateString(e["font_family"])
41 | text = fb.CreateString(e["text"])
42 | Text.TextStart(fb)
43 | Text.TextAddAnchor(fb, Anchor.CreateAnchor(fb, *e["anchor"]))
44 | Text.TextAddFill(fb, fill)
45 | Text.TextAddFontFamily(fb, font)
46 | Text.TextAddFontSize(fb, e["font_size"])
47 | Text.TextAddPosition(
48 | fb, CanvasPosition.CreateCanvasPosition(fb, e["x"], e["y"])
49 | )
50 | Text.TextAddText(fb, text)
51 | elementBody = Text.TextEnd(fb)
52 | elementType = ElementsUnion.Text
53 |
54 | Element.ElementStart(fb)
55 | Element.ElementAddId(fb, int(i))
56 | Element.ElementAddElementType(fb, elementType)
57 | Element.ElementAddElement(fb, elementBody)
58 | element = Element.ElementEnd(fb)
59 | elements.append(element)
60 |
61 | Canvas.CanvasStartContentVector(fb, num_elements)
62 | for elem in elements:
63 | fb.PrependUOffsetTRelative(elem)
64 | content = fb.EndVector(num_elements)
65 |
66 | Canvas.CanvasStart(fb)
67 | Canvas.CanvasAddContent(fb, content)
68 | canvas = Canvas.CanvasEnd(fb)
69 |
70 | fb.Finish(canvas)
71 | binary = fb.Output()
72 |
73 | return binary
74 |
--------------------------------------------------------------------------------
/examples/basics/model/bpmn_example_model.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | self.source = Source(
25 | self.env,
26 | "source",
27 | xy=(31, 65),
28 | inter_arrival_time=0,
29 | max_entities=6,
30 | ways={"gateway_1": [(49, 65), (108, 65)]},
31 | )
32 |
33 | self.sink = Sink(self.env, "sink", xy=(600, 65), ways={})
34 |
35 | self.spr_1 = SeizeProcessRelease(
36 | self.env,
37 | "spr_1",
38 | xy=(289, 65),
39 | process_time=41,
40 | ways={"gateway_2": [(339, 65), (422, 65)]},
41 | )
42 |
43 | self.proc_1 = Delay(
44 | self.env,
45 | "proc_1",
46 | xy=(289, 186),
47 | process_time=120,
48 | ways={"gateway_2": [(339, 186), (447, 186), (447, 90)]},
49 | )
50 |
51 | self.gateway_1 = RoundRobinGateway(
52 | self.env,
53 | "gateway_1",
54 | xy=(133, 65),
55 | ways={
56 | "spr_1": [(158, 65), (239, 65)],
57 | "proc_1": [(133, 90), (133, 186), (239, 186)],
58 | },
59 | )
60 |
61 | self.gateway_2 = RoundRobinGateway(
62 | self.env, "gateway_2", xy=(447, 65), ways={"sink": [(472, 65), (582, 65)]}
63 | )
64 |
65 | #!model
66 |
67 | self.model_components = {
68 | "source": self.source,
69 | "sink": self.sink,
70 | "spr_1": self.spr_1,
71 | "proc_1": self.proc_1,
72 | "gateway_1": self.gateway_1,
73 | "gateway_2": self.gateway_2,
74 | }
75 |
76 | self.model_graph_names = {
77 | "source": ["gateway_1"],
78 | "sink": [],
79 | "spr_1": ["gateway_2"],
80 | "proc_1": ["gateway_2"],
81 | "gateway_1": ["spr_1", "proc_1"],
82 | "gateway_2": ["sink"],
83 | }
84 | # translate model_graph_names into corresponding objects
85 | self.model_graph = {
86 | self.model_components[name]: [
87 | self.model_components[nameSucc]
88 | for nameSucc in self.model_graph_names[name]
89 | ]
90 | for name in self.model_graph_names
91 | }
92 |
93 | for component in self.model_graph:
94 | component.successors = self.model_graph[component]
95 |
--------------------------------------------------------------------------------
/src/casymda/blocks/tilemap/coordinates_holder.py:
--------------------------------------------------------------------------------
1 | import os
2 | import random
3 | from typing import Any, Dict
4 |
5 | from . import tilemap_nx as tmnx
6 |
7 | MATRIX_CACHE = "_temp_matrix.pickle"
8 | LOOKUP_CACHE = "_temp_lookup.pickle"
9 |
10 |
11 | def delete_cached(csv_path):
12 | paths = get_cache_paths(csv_path)
13 | for path in paths:
14 | if os.path.exists(path):
15 | os.remove(path)
16 | print("removed: " + path)
17 | else:
18 | print(path + " does not exist")
19 |
20 |
21 | def get_cache_paths(csv_path: str):
22 | cache_directory = os.path.dirname(csv_path)
23 | pickled_matrix_path = os.path.join(cache_directory, MATRIX_CACHE)
24 | pickled_lookup_path = os.path.join(cache_directory, LOOKUP_CACHE)
25 | return pickled_matrix_path, pickled_lookup_path
26 |
27 |
28 | class CoordinatesHolder:
29 | """manages info about shortest paths between certain locations from a given csv tilemap"""
30 |
31 | def __init__(self, csv_path: str, scale=10, include_diagonal=False) -> None:
32 |
33 | self.csv_path = csv_path
34 | self.cache_directory = os.path.dirname(csv_path)
35 |
36 | self.pickled_matrix_path, self.pickled_lookup_path = get_cache_paths(csv_path)
37 |
38 | self.scale = scale # px / tile
39 |
40 | # let info get loaded by tmnx
41 | self.matrix_dict: Dict[Any, Any]
42 | self.node_lookup: Dict[Any, Any]
43 | self.matrix_dict, self.node_lookup = tmnx.initialize(
44 | self.pickled_matrix_path,
45 | self.pickled_lookup_path,
46 | self.csv_path,
47 | include_diagonal=include_diagonal,
48 | )
49 |
50 | def get_coords(self, target_name):
51 | return self.tile_to_coords(self.node_lookup[target_name])
52 |
53 | def get_path_coords_and_length_from_to(self, source_name, target_name):
54 | if source_name == target_name:
55 | return [self.get_coords(source_name), self.get_coords(target_name)], 0
56 | source, target = self.node_lookup[source_name], self.node_lookup[target_name]
57 | path_nodes = self.matrix_dict[source][target]["path"]
58 | coords_path = [self.tile_to_coords(tile) for tile in path_nodes]
59 | length = self.matrix_dict[source][target]["length"] * self.scale
60 | return coords_path, length
61 |
62 | def get_random_location_name(self):
63 | i = random.randint(0, len(self.node_lookup) - 1)
64 | return self.get_location_name_by_index(i)
65 |
66 | def get_location_name_by_index(self, index):
67 | return list(self.node_lookup)[index]
68 |
69 | def get_location_index_by_name(self, location_name):
70 | return list(self.node_lookup).index(location_name)
71 |
72 | def tile_to_coords(self, tile_tuple):
73 | scaled = [(c * self.scale + self.scale / 2) for c in tile_tuple]
74 | scaled.reverse() # row, col => y, x
75 | return tuple(scaled)
76 |
--------------------------------------------------------------------------------
/examples/resources/resource_example_model.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | self.resource_1 = NamedResource(
25 | self.env, "resource_1", xy=(395, 43), capacity=2, ways={}
26 | )
27 |
28 | self.source = Source(
29 | self.env,
30 | "source",
31 | xy=(31, 169),
32 | inter_arrival_time=0,
33 | max_entities=6,
34 | ways={"wait_for_resource": [(49, 169), (125, 169)]},
35 | )
36 |
37 | self.sink = Sink(self.env, "sink", xy=(735, 169), ways={})
38 |
39 | self.proc_1 = Delay(
40 | self.env,
41 | "proc_1",
42 | xy=(315, 169),
43 | process_time=5,
44 | ways={"proc_2": [(365, 169), (415, 169)]},
45 | )
46 |
47 | self.proc_2 = Delay(
48 | self.env,
49 | "proc_2",
50 | xy=(465, 169),
51 | process_time=5,
52 | ways={"release_resource": [(515, 169), (575, 169)]},
53 | )
54 |
55 | self.wait_for_resource = ResourceSeizeQueue(
56 | self.env,
57 | "wait_for_resource",
58 | resource=self.resource_1,
59 | xy=(175, 169),
60 | ways={"proc_1": [(225, 169), (265, 169)]},
61 | )
62 |
63 | self.release_resource = ResourceRelease(
64 | self.env,
65 | "release_resource",
66 | resource=self.resource_1,
67 | xy=(625, 169),
68 | ways={"sink": [(675, 169), (717, 169)]},
69 | )
70 |
71 | #!model
72 |
73 | self.model_components = {
74 | "source": self.source,
75 | "sink": self.sink,
76 | "proc_1": self.proc_1,
77 | "proc_2": self.proc_2,
78 | "wait_for_resource": self.wait_for_resource,
79 | "release_resource": self.release_resource,
80 | }
81 |
82 | self.model_graph_names = {
83 | "source": ["wait_for_resource"],
84 | "sink": [],
85 | "proc_1": ["proc_2"],
86 | "proc_2": ["release_resource"],
87 | "wait_for_resource": ["proc_1"],
88 | "release_resource": ["sink"],
89 | }
90 | # translate model_graph_names into corresponding objects
91 | self.model_graph = {
92 | self.model_components[name]: [
93 | self.model_components[nameSucc]
94 | for nameSucc in self.model_graph_names[name]
95 | ]
96 | for name in self.model_graph_names
97 | }
98 |
99 | for component in self.model_graph:
100 | component.successors = self.model_graph[component]
101 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/Image.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class Image(object):
12 | __slots__ = ["_tab"]
13 |
14 | @classmethod
15 | def GetRootAsImage(cls, buf, offset):
16 | n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
17 | x = Image()
18 | x.Init(buf, n + offset)
19 | return x
20 |
21 | # Image
22 | def Init(self, buf, pos):
23 | self._tab = flatbuffers.table.Table(buf, pos)
24 |
25 | # Image
26 | def Position(self):
27 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
28 | if o != 0:
29 | x = o + self._tab.Pos
30 | from casymda.visualization.web_server.flatbuffers.CanvasPosition import (
31 | CanvasPosition,
32 | )
33 |
34 | obj = CanvasPosition()
35 | obj.Init(self._tab.Bytes, x)
36 | return obj
37 | return None
38 |
39 | # Image
40 | def Anchor(self):
41 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
42 | if o != 0:
43 | x = o + self._tab.Pos
44 | from casymda.visualization.web_server.flatbuffers.Anchor import Anchor
45 |
46 | obj = Anchor()
47 | obj.Init(self._tab.Bytes, x)
48 | return obj
49 | return None
50 |
51 | # Image
52 | def PhotoId(self):
53 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
54 | if o != 0:
55 | return self._tab.Get(
56 | flatbuffers.number_types.Uint16Flags, o + self._tab.Pos
57 | )
58 | return 0
59 |
60 | # Image
61 | def Path(self):
62 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
63 | if o != 0:
64 | return self._tab.String(o + self._tab.Pos)
65 | return None
66 |
67 | # Image
68 | def Factor(self):
69 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
70 | if o != 0:
71 | return self._tab.Get(
72 | flatbuffers.number_types.Float32Flags, o + self._tab.Pos
73 | )
74 | return 1.0
75 |
76 |
77 | def ImageStart(builder):
78 | builder.StartObject(5)
79 |
80 |
81 | def ImageAddPosition(builder, position):
82 | builder.PrependStructSlot(
83 | 0, flatbuffers.number_types.UOffsetTFlags.py_type(position), 0
84 | )
85 |
86 |
87 | def ImageAddAnchor(builder, anchor):
88 | builder.PrependStructSlot(
89 | 1, flatbuffers.number_types.UOffsetTFlags.py_type(anchor), 0
90 | )
91 |
92 |
93 | def ImageAddPhotoId(builder, photoId):
94 | builder.PrependUint16Slot(2, photoId, 0)
95 |
96 |
97 | def ImageAddPath(builder, path):
98 | builder.PrependUOffsetTRelativeSlot(
99 | 3, flatbuffers.number_types.UOffsetTFlags.py_type(path), 0
100 | )
101 |
102 |
103 | def ImageAddFactor(builder, factor):
104 | builder.PrependFloat32Slot(4, factor, 1.0)
105 |
106 |
107 | def ImageEnd(builder):
108 | return builder.EndObject()
109 |
--------------------------------------------------------------------------------
/examples/tilemap/web_animation/sim_runner_cs.py:
--------------------------------------------------------------------------------
1 | """ simulation process and model-specific config """
2 | from multiprocessing import Value
3 |
4 | from casymda.environments.realtime_environment import (
5 | ChangeableFactorRealtimeEnvironment as Environment,
6 | SyncedFloat,
7 | )
8 | from casymda.visualization.canvas.web_canvas import WebCanvas
9 | from casymda.visualization.process_visualizer import ProcessVisualizer
10 | from casymda.visualization.tilemap.tilemap_visualizer import TilemapVisualizer
11 | import casymda.visualization.web_server.flask_sim_server as flask_sim_server
12 | from casymda.visualization.web_server.sim_controller import RunnableSimulation
13 |
14 | from examples.tilemap.tilemap_example_model import Model
15 | import root_dir
16 |
17 |
18 | class TilemapProcessRunnableSimulation(RunnableSimulation):
19 | """Runnable simulation of our example model"""
20 |
21 | # single place to configure the width and height of the canvas, also given to browser
22 | scale = 1.0
23 | width = 451 * scale
24 | height = 212 * scale
25 | flow_speed = 400
26 | root_file = root_dir.__file__
27 |
28 | def simulate(self, shared_state: dict, should_run: Value, factor: SyncedFloat):
29 |
30 | env = Environment(factor=factor, should_run=should_run)
31 | model = Model(env)
32 |
33 | canvas = WebCanvas(
34 | shared_state,
35 | self.width,
36 | self.height,
37 | scale=self.scale,
38 | )
39 |
40 | visualizer = ProcessVisualizer(
41 | canvas,
42 | flow_speed=self.flow_speed,
43 | background_image_path="examples/tilemap/diagram.png",
44 | default_entity_icon_path="examples/basics/visualization/simple_entity_icon.png",
45 | )
46 |
47 | for block in model.model_components.values():
48 | block.visualizer = visualizer
49 |
50 | while env.peek() < float("inf"):
51 | model.env.step()
52 |
53 | print("simulation done.\n")
54 |
55 |
56 | def run_sim_process_animation():
57 | runnable_sim = TilemapProcessRunnableSimulation()
58 | flask_sim_server.run_server(runnable_sim)
59 |
60 |
61 | class TilemapRunnableSimulation(RunnableSimulation):
62 | """Runnable simulation of our example model"""
63 |
64 | # single place to configure the width and height of the canvas, also given to browser
65 | scale = 1.2
66 | width = 240 * scale
67 | height = 260 * scale
68 | root_file = root_dir.__file__
69 |
70 | def simulate(self, shared_state: dict, should_run: Value, factor: SyncedFloat):
71 |
72 | env = Environment(factor=factor, should_run=should_run)
73 | model = Model(env)
74 |
75 | canvas = WebCanvas(
76 | shared_state,
77 | self.width,
78 | self.height,
79 | scale=self.scale,
80 | )
81 | visualizer = TilemapVisualizer(
82 | canvas,
83 | background_image_path="examples/tilemap/tilemap-csv_240.png",
84 | default_entity_icon_path="examples/tilemap/simple_entity_icon.png",
85 | )
86 |
87 | model.tilemover.tilemap_visualizer = visualizer
88 |
89 | env.run()
90 |
91 | print("simulation done.\n")
92 |
93 |
94 | def run_sim_tilemap_animation():
95 | runnable_sim = TilemapRunnableSimulation()
96 | flask_sim_server.run_server(runnable_sim)
97 |
--------------------------------------------------------------------------------
/benchmark/model/model.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | self.source = Source(
25 | self.env,
26 | "source",
27 | xy=(31, 65),
28 | inter_arrival_time=0,
29 | max_entities=10,
30 | ways={"gateway_1": [(49, 65), (108, 65)]},
31 | )
32 |
33 | self.sink = Sink(self.env, "sink", xy=(565, 65), ways={})
34 |
35 | self.parallel_proc = Delay(
36 | self.env,
37 | "parallel_proc",
38 | xy=(289, 65),
39 | process_time=1,
40 | block_capacity=float("inf"),
41 | ways={"gateway_2": [(339, 65), (422, 65)]},
42 | )
43 |
44 | self.sequential_proc = Delay(
45 | self.env,
46 | "sequential_proc",
47 | xy=(355, 229),
48 | process_time=1,
49 | block_capacity=1,
50 | ways={"gateway_2": [(405, 229), (447, 229), (447, 90)]},
51 | )
52 |
53 | self.buffer = Delay(
54 | self.env,
55 | "buffer",
56 | xy=(215, 229),
57 | process_time=0,
58 | block_capacity=float("inf"),
59 | ways={"sequential_proc": [(265, 229), (305, 229)]},
60 | )
61 |
62 | self.gateway_1 = RoundRobinGateway(
63 | self.env,
64 | "gateway_1",
65 | xy=(133, 65),
66 | ways={
67 | "parallel_proc": [(158, 65), (239, 65)],
68 | "buffer": [(133, 90), (133, 229), (165, 229)],
69 | },
70 | )
71 |
72 | self.gateway_2 = RoundRobinGateway(
73 | self.env, "gateway_2", xy=(447, 65), ways={"sink": [(472, 65), (547, 65)]}
74 | )
75 |
76 | #!model
77 |
78 | self.model_components = {
79 | "source": self.source,
80 | "sink": self.sink,
81 | "parallel_proc": self.parallel_proc,
82 | "sequential_proc": self.sequential_proc,
83 | "buffer": self.buffer,
84 | "gateway_1": self.gateway_1,
85 | "gateway_2": self.gateway_2,
86 | }
87 |
88 | self.model_graph_names = {
89 | "source": ["gateway_1"],
90 | "sink": [],
91 | "parallel_proc": ["gateway_2"],
92 | "sequential_proc": ["gateway_2"],
93 | "buffer": ["sequential_proc"],
94 | "gateway_1": ["parallel_proc", "buffer"],
95 | "gateway_2": ["sink"],
96 | }
97 | # translate model_graph_names into corresponding objects
98 | self.model_graph = {
99 | self.model_components[name]: [
100 | self.model_components[nameSucc]
101 | for nameSucc in self.model_graph_names[name]
102 | ]
103 | for name in self.model_graph_names
104 | }
105 |
106 | for component in self.model_graph:
107 | component.successors = self.model_graph[component]
108 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flatbuffers/Text.py:
--------------------------------------------------------------------------------
1 | # automatically generated by the FlatBuffers compiler, do not modify
2 |
3 | # namespace: flatbuffers
4 |
5 | import flatbuffers
6 | from flatbuffers.compat import import_numpy
7 |
8 | np = import_numpy()
9 |
10 |
11 | class Text(object):
12 | __slots__ = ["_tab"]
13 |
14 | @classmethod
15 | def GetRootAsText(cls, buf, offset):
16 | n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
17 | x = Text()
18 | x.Init(buf, n + offset)
19 | return x
20 |
21 | # Text
22 | def Init(self, buf, pos):
23 | self._tab = flatbuffers.table.Table(buf, pos)
24 |
25 | # Text
26 | def Position(self):
27 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
28 | if o != 0:
29 | x = o + self._tab.Pos
30 | from casymda.visualization.web_server.flatbuffers.CanvasPosition import (
31 | CanvasPosition,
32 | )
33 |
34 | obj = CanvasPosition()
35 | obj.Init(self._tab.Bytes, x)
36 | return obj
37 | return None
38 |
39 | # Text
40 | def Anchor(self):
41 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
42 | if o != 0:
43 | x = o + self._tab.Pos
44 | from casymda.visualization.web_server.flatbuffers.Anchor import Anchor
45 |
46 | obj = Anchor()
47 | obj.Init(self._tab.Bytes, x)
48 | return obj
49 | return None
50 |
51 | # Text
52 | def Text(self):
53 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
54 | if o != 0:
55 | return self._tab.String(o + self._tab.Pos)
56 | return None
57 |
58 | # Text
59 | def Fill(self):
60 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
61 | if o != 0:
62 | return self._tab.String(o + self._tab.Pos)
63 | return None
64 |
65 | # Text
66 | def FontFamily(self):
67 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
68 | if o != 0:
69 | return self._tab.String(o + self._tab.Pos)
70 | return None
71 |
72 | # Text
73 | def FontSize(self):
74 | o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
75 | if o != 0:
76 | return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
77 | return 10
78 |
79 |
80 | def TextStart(builder):
81 | builder.StartObject(6)
82 |
83 |
84 | def TextAddPosition(builder, position):
85 | builder.PrependStructSlot(
86 | 0, flatbuffers.number_types.UOffsetTFlags.py_type(position), 0
87 | )
88 |
89 |
90 | def TextAddAnchor(builder, anchor):
91 | builder.PrependStructSlot(
92 | 1, flatbuffers.number_types.UOffsetTFlags.py_type(anchor), 0
93 | )
94 |
95 |
96 | def TextAddText(builder, text):
97 | builder.PrependUOffsetTRelativeSlot(
98 | 2, flatbuffers.number_types.UOffsetTFlags.py_type(text), 0
99 | )
100 |
101 |
102 | def TextAddFill(builder, fill):
103 | builder.PrependUOffsetTRelativeSlot(
104 | 3, flatbuffers.number_types.UOffsetTFlags.py_type(fill), 0
105 | )
106 |
107 |
108 | def TextAddFontFamily(builder, fontFamily):
109 | builder.PrependUOffsetTRelativeSlot(
110 | 4, flatbuffers.number_types.UOffsetTFlags.py_type(fontFamily), 0
111 | )
112 |
113 |
114 | def TextAddFontSize(builder, fontSize):
115 | builder.PrependUint8Slot(5, fontSize, 10)
116 |
117 |
118 | def TextEnd(builder):
119 | return builder.EndObject()
120 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/sim_controller.py:
--------------------------------------------------------------------------------
1 | """runs simulation model in its own process"""
2 |
3 | import json
4 | from ctypes import c_bool
5 | from multiprocessing import Manager, Process, Value
6 | from typing import Optional
7 |
8 | from casymda.environments.realtime_environment import SyncedFloat
9 | from casymda.visualization.canvas import web_canvas
10 |
11 | from .flatbuffers import flatbuffer_serializer
12 |
13 |
14 | class RunnableSimulation:
15 | """abstract base class for simulations to be run via a SimController"""
16 |
17 | width: int
18 | height: int
19 | root_file: str
20 |
21 | def simulate(
22 | self, shared_state: dict, should_run: Value, factor: SyncedFloat
23 | ) -> None:
24 | raise NotImplementedError("abc")
25 |
26 |
27 | class SimController:
28 | """wraps RunnableSimulation to be controlled by a sim-server"""
29 |
30 | def __init__(self, simulation: RunnableSimulation) -> None:
31 |
32 | self.simulation = simulation
33 |
34 | self.sim_process: Optional[Process] = None
35 | self.shared_state: dict = Manager().dict()
36 | self.should_run = Value(c_bool, False)
37 | self.factor: SyncedFloat = SyncedFloat._create_factor_instance()
38 |
39 | def start_simulation_process(self):
40 | self.reset_sim()
41 | self._setup_sim()
42 | self.sim_process.start()
43 | return "simulation process started"
44 |
45 | def _setup_sim(self):
46 | self.shared_state = Manager().dict()
47 | self.should_run.value = True
48 | self.sim_process = Process(
49 | target=self.simulation.simulate,
50 | args=(
51 | self.shared_state,
52 | self.should_run,
53 | self.factor,
54 | ),
55 | )
56 |
57 | def pause_sim(self):
58 | self.should_run.value = False
59 | return "paused"
60 |
61 | def resume_sim(self):
62 | self.should_run.value = True
63 | return "resumed"
64 |
65 | def reset_sim(self):
66 | if self.sim_process is not None and self.sim_process.is_alive():
67 | self.sim_process.terminate()
68 | self.sim_process = None
69 | self.should_run.value = False
70 | return "reset"
71 |
72 | def get_state_dumps(self):
73 | state = self._get_state_dumps()
74 | return json.dumps(state)
75 |
76 | def get_partial_state_dumps(self):
77 | state = self._get_partial_state_dumps()
78 | return json.dumps(state)
79 |
80 | def get_state_dumps_fb(self):
81 | state = self._get_state_dumps()
82 | return flatbuffer_serializer.serialize(state)
83 |
84 | def get_partial_state_dumps_fb(self):
85 | state = self._get_partial_state_dumps()
86 | return flatbuffer_serializer.serialize(state)
87 |
88 | def _get_state_dumps(self):
89 | self.shared_state[web_canvas.UPDATED_KEY] = set() # reset
90 | state = self.shared_state.copy()
91 | del state[web_canvas.UPDATED_KEY]
92 | return state
93 |
94 | def _get_partial_state_dumps(self):
95 | updated_set = set(self.shared_state[web_canvas.UPDATED_KEY])
96 | state = self.shared_state.copy()
97 | self.shared_state[web_canvas.UPDATED_KEY] = set() # reset
98 | del state[web_canvas.UPDATED_KEY]
99 | for not_updated in state.keys() - updated_set:
100 | del state[not_updated]
101 | return state
102 |
103 | def set_rt_factor(self, value: float):
104 | return "set factor to " + str(self.factor.set_value(value))
105 |
106 | def get_sim_width(self) -> int:
107 | return self.simulation.width
108 |
109 | def get_sim_height(self) -> int:
110 | return self.simulation.height
111 |
--------------------------------------------------------------------------------
/examples/tilemap/diagram.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_1cnsqpp
6 |
7 |
8 | SequenceFlow_1cnsqpp
9 | SequenceFlow_1jp9f41
10 |
11 |
12 | SequenceFlow_1jp9f41
13 |
14 |
15 |
16 |
17 | inter_arrival_time=100;
18 | max_entities=2
19 |
20 |
21 |
22 | speed=10;
23 | coordinates_holder=coordinates_holder;
24 | from_node="A";
25 | to_node="C"
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 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/examples/resources_loop/resource_loop_example_model.py:
--------------------------------------------------------------------------------
1 | """model template for generated model files"""
2 | from casymda.blocks import (
3 | Delay,
4 | Entity,
5 | NamedResource,
6 | ResourceRelease,
7 | ResourceSeizeQueue,
8 | RoundRobinGateway,
9 | SeizeProcessRelease,
10 | Sink,
11 | Source,
12 | )
13 |
14 |
15 | class Model:
16 | """generated model"""
17 |
18 | def __init__(self, env):
19 |
20 | self.env = env
21 |
22 | #!resources+components
23 |
24 | self.resource_1 = NamedResource(
25 | self.env, "resource_1", xy=(525, 43), capacity=3, ways={}
26 | )
27 |
28 | self.source = Source(
29 | self.env,
30 | "source",
31 | xy=(25, 169),
32 | inter_arrival_time=0,
33 | max_entities=6,
34 | ways={"wait_for_resource": [(43, 169), (125, 169)]},
35 | )
36 |
37 | self.sink = Sink(self.env, "sink", xy=(1035, 169), ways={})
38 |
39 | self.proc_1 = Delay(
40 | self.env,
41 | "proc_1",
42 | xy=(525, 169),
43 | process_time=5,
44 | block_capacity=1,
45 | ways={"gateway_2": [(575, 169), (730, 169)]},
46 | )
47 |
48 | self.wait_for_resource = ResourceSeizeQueue(
49 | self.env,
50 | "wait_for_resource",
51 | resource=self.resource_1,
52 | xy=(175, 169),
53 | ways={"gateway_1": [(225, 169), (280, 169)]},
54 | )
55 |
56 | self.release_resource = ResourceRelease(
57 | self.env,
58 | "release_resource",
59 | resource=self.resource_1,
60 | xy=(895, 169),
61 | ways={"sink": [(945, 169), (1017, 169)]},
62 | )
63 |
64 | self.proc_2 = Delay(
65 | self.env,
66 | "proc_2",
67 | xy=(525, 363),
68 | process_time=5,
69 | block_capacity=1,
70 | ways={"gateway_1": [(475, 363), (305, 363), (305, 194)]},
71 | )
72 |
73 | self.gateway_2 = RoundRobinGateway(
74 | self.env,
75 | "gateway_2",
76 | xy=(755, 169),
77 | ways={
78 | "release_resource": [(780, 169), (845, 169)],
79 | "proc_2": [(755, 194), (755, 363), (575, 363)],
80 | },
81 | )
82 |
83 | self.gateway_1 = RoundRobinGateway(
84 | self.env,
85 | "gateway_1",
86 | xy=(305, 169),
87 | ways={"proc_1": [(330, 169), (475, 169)]},
88 | )
89 |
90 | #!model
91 |
92 | self.model_components = {
93 | "source": self.source,
94 | "sink": self.sink,
95 | "proc_1": self.proc_1,
96 | "wait_for_resource": self.wait_for_resource,
97 | "release_resource": self.release_resource,
98 | "proc_2": self.proc_2,
99 | "gateway_2": self.gateway_2,
100 | "gateway_1": self.gateway_1,
101 | }
102 |
103 | self.model_graph_names = {
104 | "source": ["wait_for_resource"],
105 | "sink": [],
106 | "proc_1": ["gateway_2"],
107 | "wait_for_resource": ["gateway_1"],
108 | "release_resource": ["sink"],
109 | "proc_2": ["gateway_1"],
110 | "gateway_2": ["release_resource", "proc_2"],
111 | "gateway_1": ["proc_1"],
112 | }
113 | # translate model_graph_names into corresponding objects
114 | self.model_graph = {
115 | self.model_components[name]: [
116 | self.model_components[nameSucc]
117 | for nameSucc in self.model_graph_names[name]
118 | ]
119 | for name in self.model_graph_names
120 | }
121 |
122 | for component in self.model_graph:
123 | component.successors = self.model_graph[component]
124 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/pixijs_canvas_animation.js:
--------------------------------------------------------------------------------
1 | let type = "WebGL";
2 | if (!PIXI.utils.isWebGLSupported()) {
3 | type = "canvas";
4 | }
5 | PIXI.utils.sayHello(type);
6 |
7 | let app, loader;
8 | let animated_elements;
9 |
10 | // create pixi canvas at certain element
11 | export function initialize_animation(parent_dom_element, width, height) {
12 | remove_old_canvas_elements(parent_dom_element);
13 | app = new PIXI.Application({
14 | width: width,
15 | height: height,
16 | backgroundColor: 0xffffff,
17 | });
18 | app.ticker.maxFPS = 30;
19 | app.stage.sortableChildren = true;
20 | parent_dom_element.appendChild(app.view);
21 |
22 | loader = new PIXI.Loader();
23 |
24 | animated_elements = {};
25 | }
26 |
27 | // process given state
28 | export function animate_simulation(state) {
29 | // process all elements
30 | for (var key in state) {
31 | var state_element = state[key];
32 | if (state_element.type === "text") _process_text(key, state_element);
33 | else if (state_element.type === "photo") _process_resource(state_element);
34 | // "photo" means image resource (to be loaded)
35 | else if (state_element.type === "image") _process_image(key, state_element);
36 | }
37 |
38 | // also check if some animated elements are not present anymore
39 | destroy_removed_elements(state);
40 | }
41 |
42 | // PROCESS ELEMENTS HELPER
43 | function _process_resource(state_element) {
44 | // check if the resource is already loaded and start to load it if not
45 | if (!(state_element.path in loader.resources) && !loader.loading) {
46 | let element_to_load = loader.add(
47 | state_element.path,
48 | "files?filepath=" + state_element.path
49 | );
50 | element_to_load.load();
51 | }
52 | }
53 |
54 | function _process_image(key, state_element) {
55 | var pixi_sprite;
56 | if (
57 | !(key in animated_elements) &&
58 | state_element.path in loader.resources &&
59 | loader.resources[state_element.path].isComplete
60 | ) {
61 | // create new sprite
62 | pixi_sprite = new PIXI.Sprite.from("files?filepath=" + state_element.path);
63 | pixi_sprite.scale.set((state_element.factor, state_element.factor));
64 | pixi_sprite.anchor.set(state_element.anchor[0], state_element.anchor[1]);
65 | pixi_sprite.zIndex = parseInt(key);
66 | app.stage.addChild(pixi_sprite);
67 | animated_elements[key] = pixi_sprite;
68 | }
69 | if (key in animated_elements) {
70 | // update properties
71 | pixi_sprite = animated_elements[key];
72 | pixi_sprite.position.x = state_element.x;
73 | pixi_sprite.position.y = state_element.y;
74 | }
75 | }
76 |
77 | function _process_text(key, state_element) {
78 | var pixi_text;
79 | if (animated_elements[key] === undefined) {
80 | // create text on canvas
81 | pixi_text = new PIXI.Text("", {
82 | fontFamily: state_element.font_family,
83 | fontSize: state_element.font_size,
84 | fill: state_element.fill,
85 | });
86 | pixi_text.anchor.set(state_element.anchor[0], state_element.anchor[1]);
87 | pixi_text.zIndex = parseInt(key);
88 | app.stage.addChild(pixi_text);
89 | animated_elements[key] = pixi_text;
90 | }
91 | // update properties: position / text
92 | pixi_text = animated_elements[key];
93 | pixi_text.position.x = state_element.x;
94 | pixi_text.position.y = state_element.y;
95 | pixi_text.text = state_element.text;
96 | }
97 |
98 | function remove_old_canvas_elements(parent_dom_element) {
99 | while (
100 | parent_dom_element &&
101 | parent_dom_element.lastChild &&
102 | parent_dom_element.lastChild.nodeName == "CANVAS"
103 | ) {
104 | parent_dom_element.removeChild(parent_dom_element.lastChild);
105 | }
106 | }
107 |
108 | function destroy_removed_elements(state) {
109 | for (var anim_key in animated_elements) {
110 | if (!(anim_key in state)) {
111 | animated_elements[anim_key].destroy();
112 | delete animated_elements[anim_key];
113 | }
114 | }
115 | }
116 |
117 | console.log("pixijs_canvas_animation.js loaded");
118 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/flask_sim_server.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | from casymda.visualization.web_server.sim_controller import (
5 | RunnableSimulation,
6 | SimController,
7 | )
8 | from flask import Flask, request, send_file, send_from_directory, Response
9 | from flask_cors import CORS
10 |
11 | logging.getLogger("werkzeug").setLevel(logging.ERROR)
12 |
13 |
14 | class FlaskSimServer:
15 | """provides control endpoints and serves visual state of a simulation model"""
16 |
17 | def __init__(
18 | self, sim_controller: SimController, port=5000, host="0.0.0.0"
19 | ) -> None:
20 | self.sim_controller: SimController = sim_controller
21 | self.root_file = sim_controller.simulation.root_file
22 | self.port = port
23 | self.host = host
24 |
25 | def run_sim_server(self):
26 | app = Flask(__name__)
27 | CORS(app)
28 | app_dir = os.path.dirname(os.path.abspath(self.root_file))
29 | flask_dir = os.path.dirname(os.path.abspath(__file__))
30 | print(
31 | "starting flask server, port: %s, app_dir: %s, flask_dir: %s"
32 | % (self.port, app_dir, flask_dir)
33 | )
34 |
35 | @app.route("/")
36 | def root():
37 | # assumed to be next to this file
38 | HTML_FILE = "canvas_animation.html"
39 | return send_from_directory(flask_dir, HTML_FILE)
40 |
41 | @app.route("/lib-files/")
42 | def lib_files(filename):
43 | # assumed to be next to this file
44 | return send_from_directory(flask_dir, filename)
45 |
46 | @app.route("/files")
47 | def provide_file():
48 | filepath = request.args.get("filepath")
49 | if filepath.startswith("/") or filepath.startswith("C:"):
50 | # absolute path
51 | return send_file(filepath)
52 | else:
53 | # relative path
54 | return send_from_directory(app_dir, filepath)
55 |
56 | @app.route("/width")
57 | def get_width():
58 | return str(self.sim_controller.get_sim_width())
59 |
60 | @app.route("/height")
61 | def get_height():
62 | return str(self.sim_controller.get_sim_height())
63 |
64 | @app.route("/state")
65 | def get_state():
66 | return self.sim_controller.get_state_dumps()
67 |
68 | @app.route("/partial_state")
69 | def get_partial_state():
70 | return self.sim_controller.get_partial_state_dumps()
71 |
72 | @app.route("/state_fb") # flatbuffers binary
73 | def get_state_fb():
74 | binary = self.sim_controller.get_state_dumps_fb()
75 | return Response(binary, mimetype="binary/octet-stream")
76 |
77 | @app.route("/partial_state_fb") # flatbuffers binary
78 | def get_partial_state_fb():
79 | binary = self.sim_controller.get_partial_state_dumps_fb()
80 | return Response(binary, mimetype="binary/octet-stream")
81 |
82 | @app.route("/start", methods=["POST"])
83 | def start():
84 | return self.sim_controller.start_simulation_process()
85 |
86 | @app.route("/stop", methods=["POST"])
87 | def stop():
88 | return self.sim_controller.reset_sim()
89 |
90 | @app.route("/pause", methods=["POST"])
91 | def pause():
92 | return self.sim_controller.pause_sim()
93 |
94 | @app.route("/resume", methods=["POST"])
95 | def resume():
96 | return self.sim_controller.resume_sim()
97 |
98 | @app.route("/rt_factor", methods=["POST"])
99 | def post_rt_factor():
100 | value = float(request.args.get("value"))
101 | return self.sim_controller.set_rt_factor(value)
102 |
103 | # set debug=False for IDE debugging
104 | app.run(debug=True, threaded=False, port=self.port, host=self.host)
105 |
106 |
107 | def run_server(runnable_sim: RunnableSimulation, port=5000, host="0.0.0.0"):
108 | sim_controller = SimController(runnable_sim)
109 | flask_sim_server = FlaskSimServer(sim_controller, port=port, host=host)
110 | flask_sim_server.run_sim_server()
111 |
--------------------------------------------------------------------------------
/src/casymda/visualization/canvas/web_canvas.py:
--------------------------------------------------------------------------------
1 | from casymda.visualization.canvas.scaled_canvas import ScaledCanvas
2 |
3 | ANCHOR_DICT = {
4 | "c": [0.5, 0.5],
5 | "nw": [0, 0],
6 | "n": [0.5, 0],
7 | "ne": [1, 0],
8 | "e": [1, 0.5],
9 | "se": [1, 1],
10 | "s": [0.5, 1],
11 | "sw": [0, 1],
12 | "w": [0, 0.5],
13 | }
14 |
15 | UPDATED_KEY = "__UPDATED__"
16 |
17 |
18 | class WebCanvas(ScaledCanvas):
19 | """
20 | json-serializable flat dictionary canvas (implements the scaled canvas).
21 | dictionary is flat so that it can be shared between multiple processes,
22 | e.g. when writing to the canvas from the simulation, and consuming the information from a browser via a web-server.
23 | """
24 |
25 | def __init__(self, dictionary, width, height, scale=1.0):
26 | super().__init__(scale)
27 | self.dict = dictionary
28 | self.width = width
29 | self.height = height
30 |
31 | self.photo_lookup = {}
32 |
33 | self.element_id_counter = 0
34 |
35 | self.dict[UPDATED_KEY] = set() # set of all updated element_ids
36 |
37 | def load_image_file(self, path):
38 | potential_key = path
39 | if potential_key in self.photo_lookup:
40 | return self.photo_lookup[potential_key]
41 | self.element_id_counter += 1
42 | self.dict[self.element_id_counter] = {
43 | "factor": self.scale,
44 | "path": path,
45 | "type": "photo",
46 | }
47 | self.update_updated(self.element_id_counter)
48 | self.photo_lookup[potential_key] = self.element_id_counter
49 | return self.element_id_counter
50 |
51 | def create_image(self, x_coord, y_coord, image_file, anchor="c"):
52 | x_coord, y_coord = self._scale_coords((x_coord, y_coord))
53 | self.element_id_counter += 1
54 | self.dict[self.element_id_counter] = {
55 | "x": x_coord,
56 | "y": y_coord,
57 | "anchor": ANCHOR_DICT[anchor],
58 | "type": "image",
59 | "photo_id": image_file,
60 | "path": self.dict[image_file]["path"],
61 | "factor": self.dict[image_file]["factor"],
62 | }
63 | self.update_updated(self.element_id_counter)
64 | return self.element_id_counter
65 |
66 | def create_text(
67 | self, x_coord, y_coord, text="", anchor="se", fill="black", font="Helvetica 16"
68 | ):
69 | x_coord, y_coord = self._scale_coords((x_coord, y_coord))
70 | self.element_id_counter += 1
71 | self.dict[self.element_id_counter] = {
72 | "x": x_coord,
73 | "y": y_coord,
74 | "anchor": ANCHOR_DICT[anchor],
75 | "type": "text",
76 | "text": text,
77 | "fill": fill,
78 | "font_family": font.split(" ")[0],
79 | "font_size": int(font.split(" ")[1]),
80 | }
81 | self.update_updated(self.element_id_counter)
82 | return self.element_id_counter
83 |
84 | def delete(self, element_id):
85 | if element_id in self.dict:
86 | del self.dict[element_id]
87 | return True
88 | return False
89 |
90 | def set_coords(self, element_id, x_y):
91 | x_coord, y_coord = self._scale_coords(x_y)
92 | if element_id in self.dict:
93 | entry = self.dict[element_id]
94 | entry["x"] = x_coord
95 | entry["y"] = y_coord
96 | self.dict[element_id] = entry
97 | self.update_updated(element_id)
98 | return True
99 | return False
100 |
101 | def set_text_value(self, element_id, text=""):
102 | if element_id in self.dict:
103 | entry = self.dict[element_id]
104 | entry["text"] = text
105 | self.dict[element_id] = entry
106 | self.update_updated(element_id)
107 | return True
108 | return False
109 |
110 | def get_width(self):
111 | return self.width
112 |
113 | def get_height(self):
114 | return self.height
115 |
116 | def update_updated(self, id: int):
117 | updated_set = set(self.dict[UPDATED_KEY])
118 | updated_set.add(id)
119 | self.dict[UPDATED_KEY] = updated_set # trigger process synchro
120 |
--------------------------------------------------------------------------------
/src/casymda/blocks/block_components/block.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional, Callable
2 |
3 | from simpy import Environment, Resource
4 |
5 | from casymda.blocks.block_components.state import StateManager, States
6 | from casymda.blocks.entity import Entity
7 |
8 |
9 | class Block:
10 | """
11 | Provides simulation-specific behavior of a basic block.
12 | Entities are accepted via the process_entity entry-point, and standard processing steps are executed.
13 | Actual processing behavior is to be provided by subclasses, implementing the actual_processing method.
14 | """
15 |
16 | def __init__(self, env: Environment, name: str, block_capacity=float("inf")):
17 | self.name = name
18 | self.env: Environment = env
19 | self.overall_count_in = 0
20 | on_enter_or_exit_method_callable = Callable[
21 | [Entity, Optional[Block], Optional[Block]], None
22 | ]
23 | self.do_on_enter_list: List[on_enter_or_exit_method_callable] = []
24 | self.do_on_exit_list: List[on_enter_or_exit_method_callable] = []
25 | self.entities: List[Entity] = []
26 | self.successors: List[Block] = []
27 | self.block_resource = Resource(env=env, capacity=block_capacity)
28 | self.state_manager = StateManager(self.on_block_state_change)
29 |
30 | self.env.process(self.late_state_evaluation())
31 |
32 | def on_enter(self, entity: Entity):
33 | """called when process_entity starts"""
34 | for method in self.do_on_enter_list:
35 | method(entity, None, self)
36 |
37 | def on_exit(self: "Block", entity: Entity, successor: "Block"):
38 | """called when an entities leaves the block"""
39 | self.entities.remove(entity)
40 | for method in self.do_on_exit_list:
41 | method(entity, self, successor)
42 | self.on_block_change()
43 | self.on_entity_movement(entity, successor)
44 |
45 | def process_entity(self, entity: Entity):
46 | """main entry point for entities coming from predecessors"""
47 | entity.time_of_last_arrival = self.env.now
48 | self.on_enter(entity)
49 | self.overall_count_in += 1
50 | yield self.env.process(self._process_entity(entity))
51 |
52 | def _process_entity(self, entity: Entity):
53 | """internal entry point for start of entity processing without calling on_enter or setting entity.time_of_last_arrival.
54 | useful e.g. for wip init"""
55 | self.entities.append(entity)
56 | self.on_block_change()
57 |
58 | # processing
59 | self.state_manager.increment_state_count(States.busy)
60 | entity.current_process = self.env.process(self.actual_processing(entity))
61 | yield entity.current_process # might be used for interrupt
62 |
63 | # wait for successor
64 | successor = self.find_successor(entity)
65 | req = successor.block_resource.request()
66 | self.state_manager.increment_state_count(States.blocked)
67 | self.state_manager.decrement_state_count(States.busy)
68 | yield req # wait until the chosen successor is ready
69 |
70 | # leaving
71 | self.block_resource.release(entity.block_resource_request)
72 | entity.block_resource_request = req # remember to be released
73 |
74 | self.on_exit(entity, successor)
75 | self.state_manager.decrement_state_count(States.blocked)
76 |
77 | self.env.process(successor.process_entity(entity))
78 |
79 | def find_successor(self, entity: Entity):
80 | """find next block to send entity to"""
81 | return self.successors[0]
82 |
83 | def on_block_change(self):
84 | """called on block change"""
85 |
86 | def on_block_state_change(self, state, new_value):
87 | """called when state of the block changes"""
88 |
89 | def on_entity_movement(self, entity: Entity, successor: "Block"):
90 | """called on entity movement from this block to the successor"""
91 |
92 | def late_state_evaluation(self):
93 | """schedule evaluation on sim start,
94 | when the visualizer has been loaded"""
95 | yield self.env.timeout(0)
96 | self.state_manager.evaluate_state_count()
97 |
98 | def actual_processing(self, entity: Entity):
99 | """to be implemented by concrete subclasses"""
100 | raise NotImplementedError()
101 |
--------------------------------------------------------------------------------
/src/casymda/visualization/entity_visualizer.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from typing import List, Optional
3 | from casymda.blocks.entity import Entity
4 |
5 | from casymda.visualization.canvas.scaled_canvas import ScaledCanvas
6 |
7 |
8 | class EntityAnimation:
9 | """class to hold animation related info of an entity"""
10 |
11 | def __init__(self, entity, canvas_image, image_file, x_y):
12 | self.entity = entity
13 | self.canvas_image = canvas_image
14 | self.image_file = image_file
15 | self.x_y = x_y
16 |
17 |
18 | class EntityVisualizer:
19 | """visualizes entities on a canvas when notified"""
20 |
21 | def __init__(
22 | self,
23 | canvas: ScaledCanvas,
24 | background_image_path: str = "",
25 | default_entity_icon_path: str = "",
26 | ):
27 |
28 | self.canvas = canvas
29 | self.background_image_path = background_image_path
30 | self.default_entity_icon_path = default_entity_icon_path
31 |
32 | self.background_image_file = self.canvas.load_image_file(
33 | self.background_image_path
34 | )
35 |
36 | self.background_image = self.canvas.create_image(
37 | 0, 0, self.background_image_file, anchor="nw"
38 | )
39 |
40 | time_text = "Time: " + str(0)
41 | # (scale absolute coordinates back)
42 | self.time_label: int = self.canvas.create_text(
43 | int((self.canvas.get_width() - 5) / self.canvas.scale),
44 | int((self.canvas.get_height() - 5) / self.canvas.scale),
45 | font="Helvetica 10",
46 | fill="black",
47 | anchor="se",
48 | text=time_text,
49 | )
50 |
51 | self.entity_animations: List[EntityAnimation] = []
52 |
53 | def animate(self, entity: Entity, x: float, y: float, current_time: float):
54 | """change entity position"""
55 |
56 | # find / create entity animation
57 | entity_anim = self._find_or_create_entity_animation(entity, x, y)
58 |
59 | entity_icon = entity_anim.canvas_image
60 | # set position
61 | self.canvas.set_coords(entity_icon, (x, y))
62 | entity_anim.x_y = (x, y)
63 | self.updatetime_label(current_time)
64 |
65 | def updatetime_label(self, now: float):
66 | """updates the own time_label with the given time"""
67 | text = "Time: " + str(datetime.timedelta(seconds=int(now)))
68 | self.canvas.set_text_value(self.time_label, text=text)
69 |
70 | def create_entity_animation(self, entity: Entity, x_y):
71 | """creates entity animation"""
72 |
73 | # possibility to look for entity file path if defined
74 | if entity.process_animation_icon_path is not None:
75 | file_path = entity.process_animation_icon_path
76 | else:
77 | file_path = self.default_entity_icon_path
78 | image_file = self.canvas.load_image_file(file_path)
79 | icon = self.canvas.create_image(*x_y, image_file=image_file, anchor="c")
80 |
81 | entity_animation = EntityAnimation(entity, icon, image_file, x_y)
82 | self.entity_animations.append(entity_animation)
83 |
84 | return entity_animation
85 |
86 | def destroy(self, entity: Entity):
87 | """destroy entity animation"""
88 | entity_animation = self._find_entity_animation(entity)
89 | if entity_animation is not None:
90 | self.canvas.delete(entity_animation.canvas_image)
91 | self.entity_animations.remove(entity_animation)
92 | entity_animation = None
93 |
94 | def update_entity_icon(self, entity: Entity):
95 | """recreate entity icon at current position if existing"""
96 | anim = self._find_entity_animation(entity)
97 | if anim is not None:
98 | x_y = anim.x_y
99 | self.destroy(entity)
100 | self.create_entity_animation(entity, x_y)
101 |
102 | def _find_entity_animation(self, entity: Entity) -> Optional[EntityAnimation]:
103 | return next((x for x in self.entity_animations if x.entity is entity), None)
104 |
105 | def _find_or_create_entity_animation(
106 | self, entity: Entity, x: float, y: float
107 | ) -> "EntityAnimation":
108 | existing = self._find_entity_animation(entity)
109 | return (
110 | self.create_entity_animation(entity, (x, y))
111 | if existing is None
112 | else existing
113 | )
114 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # BPMN-based creation of SimPy discrete event simulation models
2 |
3 | Wouldn't it be _cool_ to combine the block-based process modeling experience of commercial discrete event simulation packages with the amenities of proper IDE-based source-code editing?
4 | (Think Arena / Anylogic / ExtendSim / Plant Simulation / ... but with simple integration of third-party libraries, industry-standard interfaces, unit- and integration testing, dockerization, serverless execution in the cloud of your choice... and even actually working auto-completion! _:D_)
5 |
6 | And all that not only for free, but using the worlds most popular language for data analytics and machine learning?
7 |
8 | _Casymda_ enables you to create [SimPy](https://simpy.readthedocs.io/en/latest/) simulation models, with help of BPMN and the battle-tested [Camunda-Modeler](http://www.bpmn.io).
9 |
10 | Created BPMN process diagrams are parsed and converted to Python-code, combining visual oversight of model structure with code-based definition of model behavior.
11 | Immediately executable, including a token-based process animation, allowing for space-discrete entity movements, and ready to be wrapped as a gym-environment to let a machine-learning algorithm find a control strategy.
12 |
13 | Further information and sample projects:
14 |
15 | - [https://fladdimir.github.io/](https://fladdimir.github.io/tags/simpy/)
16 |
17 | ## Installation
18 |
19 | From [PyPI](https://pypi.org/project/casymda/):
20 |
21 | ```l
22 | pip install casymda
23 | ```
24 |
25 | ## Features
26 |
27 | - connectable blocks for processing of entities
28 | - graphical model description via camunda modeler
29 | - process visualization browser-based or via tkinter
30 | - space-discrete tilemap-movements of entities
31 | - gradually typed
32 |
33 | Coming soon:
34 |
35 | - automated model generation from process event-logs via [PM4Py](https://pypi.org/project/pm4py/)
36 |
37 | ## Examples
38 |
39 | Basic features are illustrated as part of the example models (which also serve as integration tests):
40 |
41 | - basics:
42 | - bpmn-based generation of a simple model file
43 | - run the generated model
44 | - process visualization via tkinter
45 | - browser-based visualization (served with [flask](https://palletsprojects.com/p/flask/), animated with [pixijs](https://www.pixijs.com/))
46 | - resources:
47 | - seize and release a resource via graphical modeling
48 | - tilemap:
49 | - entity movement along a shortest path defined by a csv-tilemap (built on networkx: )
50 |
51 | For setup just clone the repository and install casymda ([virtual environment](https://docs.python.org/3/tutorial/venv.html) recommended). See [basics-visual-run-tkinter](exec/basics/visual_run.py) for an example of how to cope with python-path issues.
52 |
53 | ## Design
54 |
55 | - [Model generation and execution](diagrams/model+execution.pdf)
56 | - [Blocks and animation](diagrams/blocks+animation.pdf)
57 |
58 | ## Development
59 |
60 | This project trusts [Black](https://black.readthedocs.io/en/stable/) for formatting, [Sonarqube](https://www.sonarqube.org/) for static code analysis, and [pytest](https://docs.pytest.org/en/latest/) for unit & integration testing. Developed and tested on Linux (Ubuntu 20.04), Python 3.8.5.
61 | Tests can be carried out inside a docker-container, optionally including an installation from pypi to verify a successful upload.
62 |
63 | ### Sonarqube
64 |
65 | sonarqube server (public docker image):
66 |
67 | ```l
68 | docker-compose up sonarqube
69 | ```
70 |
71 | sonar-scanner (public docker image):
72 |
73 | ```l
74 | docker-compose up analysis
75 | ```
76 |
77 | (run a docker-based unit-test first for coverage-reporting)
78 | (remember to share your drive via Docker-Desktop settings if necessary, to be re-applied after each password change)
79 |
80 | ### Tests
81 |
82 | ```l
83 | pytest --cov-report term --cov=src/casymda/ tests/
84 | ```
85 |
86 | integrations tests:
87 |
88 | ```l
89 | python3 -m pytest examples
90 | ```
91 |
92 | (integration-tests require tkinter, which may be installed via `sudo apt-get install python3-tk`)
93 |
94 | For Docker-based tests see [docker-compose.yml](docker-compose.yml)
95 |
96 | ```l
97 | docker-compose run unit-test
98 | docker-compose run examples-test
99 | docker-compose run examples-test-pypi
100 | ```
101 |
102 | ### Virtual environment setup
103 |
104 | ```l
105 | python3 -m venv venv
106 | ```
107 |
108 | ### Editable installation
109 |
110 | ```l
111 | pip install -e .
112 | ```
113 |
114 | ### Publish to pypi
115 |
116 | ```l
117 | python setup.py sdist
118 |
119 | twine upload dist/*
120 | ```
121 |
122 | pip install twine if necessary,
123 | remember to set the version in [setup.py](setup.py) and [src/casymda](src/casymda/__init__.py) as required
124 |
125 | ### PyPy3
126 |
127 | Tested PyPy3 (7.3.1-final). Install pypy3 pypy3-dev pypy-tk.
128 |
129 | Runtime could be decreased by factor ~2 when using PyPy3 for longer simulations runs (e.g. from ~45s to ~25s for a [simple example model test](examples/basics/model/long_run_bpmn_example_test.py) with MAX_ENTITIES set to 40.000 on an i5 notebook).
130 |
--------------------------------------------------------------------------------
/examples/wait_for_interrupt/diagram.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_0s7hb4m
6 |
7 |
8 | Flow_0s7hb4m
9 | Flow_1yenseu
10 |
11 |
12 |
13 | Flow_1yenseu
14 |
15 |
16 |
17 | Flow_0355zau
18 |
19 |
20 | Flow_0355zau
21 | Flow_124bh6i
22 |
23 |
24 | Flow_124bh6i
25 |
26 |
27 |
28 |
29 | inter_arrival_time=0;
30 | max_entities=6
31 |
32 |
33 |
34 | inter_arrival_time=1;
35 | max_entities=6
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 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/casymda/blocks/tilemap/tilemap_movement.py:
--------------------------------------------------------------------------------
1 | import math
2 | import operator
3 | from typing import Any, List, Optional
4 |
5 | import simpy
6 | from casymda.blocks.block_components import VisualizableBlock
7 | from .coordinates_holder import CoordinatesHolder
8 | from casymda.blocks.tilemap.segment import Segment
9 | from casymda.environments.realtime_environment import (
10 | ChangeableFactorRealtimeEnvironment,
11 | )
12 | from casymda.visualization.tilemap.tilemap_visualizer_interface import (
13 | TilemapVisualizerInterface as TilemapVisualizer,
14 | )
15 | from simpy import RealtimeEnvironment
16 |
17 |
18 | class TilemapMovement(VisualizableBlock):
19 | """
20 | Simulates and animates tilemap movements from a start node to a target node,
21 | either in one timestep (no visualizer set),
22 | or in steps of a configurable length (visualizer set).
23 | """
24 |
25 | MAX_ANIMATION_TIMESTEP = 0.1
26 |
27 | def __init__(
28 | self,
29 | env,
30 | name,
31 | xy=None,
32 | speed=1, # px / sec (sim-time)
33 | coordinates_holder=None,
34 | from_node="",
35 | to_node="",
36 | ways={},
37 | destroy_anim_on_arrival=True,
38 | block_capacity=float("inf"),
39 | ):
40 | super().__init__(env, name, xy=xy, ways=ways, block_capacity=block_capacity)
41 |
42 | self.speed = speed
43 | if coordinates_holder is None:
44 | raise Exception("coordinates holder not set in block: " + name)
45 | self.coordinates_holder: CoordinatesHolder = coordinates_holder
46 | self.from_node = from_node
47 | self.to_node = to_node
48 |
49 | self.tilemap_visualizer: Optional[TilemapVisualizer] = None
50 | self.destroy_anim_on_arrival = destroy_anim_on_arrival
51 |
52 | def actual_processing(self, entity):
53 | (
54 | path_coords,
55 | overall_length,
56 | ) = self.coordinates_holder.get_path_coords_and_length_from_to(
57 | self.from_node, self.to_node
58 | )
59 |
60 | speed = self.speed
61 |
62 | animation_loop: Any = None
63 | if self.tilemap_visualizer is not None:
64 | animation_loop = self.env.process(
65 | self.animation_loop(
66 | entity,
67 | path_coords,
68 | destroy_on_arrival=self.destroy_anim_on_arrival,
69 | speed=speed,
70 | ) # run animation seperately if required
71 | )
72 |
73 | time_needed = overall_length / speed
74 | yield self.env.timeout(time_needed)
75 |
76 | if self.tilemap_visualizer is not None:
77 | animation_loop.interrupt() # stop animation running "in parallel"
78 |
79 | def animation_loop(
80 | self, entity, path_coords, destroy_on_arrival: bool = True, speed=None
81 | ):
82 | if speed is None:
83 | speed = self.speed
84 |
85 | self.tilemap_visualizer.destroy(entity) # recreate possibly existing animations
86 | segments = self.get_segments(path_coords, speed)
87 | time_spent = 0
88 | try:
89 | while True: # while not interupted:
90 | self.calc_progress_and_animate(entity, segments, time_spent, speed)
91 |
92 | self.set_animation_timestep()
93 | yield self.env.timeout(self.MAX_ANIMATION_TIMESTEP)
94 | time_spent += self.MAX_ANIMATION_TIMESTEP
95 | except simpy.Interrupt:
96 | """looks like we arrived"""
97 | x, y = path_coords[-1]
98 | self.tilemap_visualizer.animate(entity, x, y, self.env.now)
99 | if destroy_on_arrival:
100 | self.tilemap_visualizer.destroy(entity)
101 |
102 | def calc_progress_and_animate(
103 | self, entity, segments: List[Segment], time_spent, speed
104 | ):
105 | distance = speed * time_spent
106 | current_segment = segments[-1]
107 | for segment in segments:
108 | current_segment = segment
109 | if segment.cumulated_length > distance:
110 | break
111 | remaining_length = max(current_segment.cumulated_length - distance, 0)
112 | current_length = current_segment.length - remaining_length
113 | progress = (
114 | (
115 | tuple(
116 | map(
117 | lambda x: x * (current_length / current_segment.length),
118 | current_segment.direction,
119 | )
120 | )
121 | )
122 | if current_segment.length > 0
123 | else current_segment.direction
124 | )
125 | current_position = tuple(map(operator.add, progress, current_segment.origin))
126 | x, y = current_position
127 | self.tilemap_visualizer.animate(entity, x, y, self.env.now)
128 |
129 | def set_animation_timestep(self):
130 | if isinstance(self.env, ChangeableFactorRealtimeEnvironment) or isinstance(
131 | self.env, RealtimeEnvironment
132 | ):
133 | self.MAX_ANIMATION_TIMESTEP = 0.04 / self.env.factor
134 | # keep reference to the factor -> change animation timestep dynamically if visualized (for a smooth animation)
135 |
136 | def get_segments(self, path_coords, speed):
137 | """pre-compute needed segment information for the animation"""
138 | segments = []
139 | cumulated_length = 0
140 | cumulated_time = 0
141 | for pc_idx in range(len(path_coords) - 1):
142 | origin = path_coords[pc_idx]
143 | destination = path_coords[pc_idx + 1]
144 | direction = tuple(map(operator.sub, destination, origin))
145 | squared = map((lambda x: x ** 2), direction)
146 | length = math.sqrt(sum(squared))
147 | time_needed = length / speed
148 |
149 | cumulated_length += length
150 | cumulated_time += time_needed
151 |
152 | segment = Segment(
153 | origin, destination, direction, length, cumulated_length, cumulated_time
154 | )
155 | segments.append(segment)
156 | return segments
157 |
--------------------------------------------------------------------------------
/src/casymda/visualization/web_server/animation_client.js:
--------------------------------------------------------------------------------
1 | import {
2 | initialize_animation,
3 | animate_simulation,
4 | } from "./pixijs_canvas_animation.js";
5 | import { deserialize_fb } from "./flatbuffer_deserializer.js";
6 |
7 | // constants
8 | const BINARY_TRANSFER = true;
9 | const BASE_URL = window.location.origin;
10 | const START_URL = BASE_URL + "/start";
11 | const PAUSE_URL = BASE_URL + "/pause";
12 | const RESUME_URL = BASE_URL + "/resume";
13 | const STOP_URL = BASE_URL + "/stop";
14 | const STATE_URL = BASE_URL + "/state" + (BINARY_TRANSFER ? "_fb" : "");
15 | const PARTIAL_STATE_URL =
16 | BASE_URL + "/partial_state" + (BINARY_TRANSFER ? "_fb" : "");
17 | const RT_FACTOR_BASE_URL = BASE_URL + "/rt_factor?value=";
18 |
19 | const CANVAS_WIDTH_URL = BASE_URL + "/width";
20 | const CANVAS_HEIGHT_URL = BASE_URL + "/height";
21 |
22 | const ANIMATION_CANVAS_PARENT_ID = "animation_canvas_parent";
23 | let ANIMATION_CANVAS_PARENT;
24 |
25 | const running_timeouts = [];
26 | let stopped = false;
27 | let state = {};
28 | let time_of_last_full_update = 0;
29 | const TIME_BETWEEN_FULL_UPDATES_MS = 500;
30 |
31 | const LOG_PERFORMANCE = true;
32 | let num_requests = 0;
33 | let num_animation_updates = 0;
34 | let animation_update_times = [];
35 | const LOG_INTERVAL_SEC = 2;
36 |
37 | // HTTP HELPER
38 | function post(url) {
39 | const request = new XMLHttpRequest();
40 | request.open("POST", url);
41 | request.send();
42 | }
43 |
44 | // SIMULATION INTERACTION
45 | async function start_simulation() {
46 | stop_simulation();
47 |
48 | // retrieve canvas dimensions first
49 | const canvas_width = await get_canvas_width();
50 | const canvas_height = await get_canvas_height();
51 |
52 | post(START_URL);
53 | initialize_animation(ANIMATION_CANVAS_PARENT, canvas_width, canvas_height);
54 |
55 | state = {};
56 |
57 | stopped = false;
58 | setup_request_state_loop();
59 | setup_animation_loop();
60 | if (LOG_PERFORMANCE) log_performance_loop();
61 | }
62 |
63 | function log_performance_loop() {
64 | let interval = window.setInterval(() => {
65 | console.log("Reqests/sec: " + num_requests / LOG_INTERVAL_SEC);
66 | console.log("Updates/sec: " + num_animation_updates / LOG_INTERVAL_SEC);
67 | console.log(
68 | "Average animation update time (ms): " +
69 | animation_update_times.reduce((a, b) => a + b, 0) /
70 | animation_update_times.length
71 | );
72 | num_requests = 0;
73 | num_animation_updates = 0;
74 | animation_update_times = [];
75 | }, LOG_INTERVAL_SEC * 1000);
76 | running_timeouts.push(interval);
77 | }
78 |
79 | function setup_request_state_loop() {
80 | let interval = window.setInterval(state_query, 35);
81 | running_timeouts.push(interval);
82 | }
83 |
84 | async function state_query() {
85 | if (Date.now() - time_of_last_full_update > TIME_BETWEEN_FULL_UPDATES_MS) {
86 | // full update
87 | time_of_last_full_update = Date.now();
88 | await full_state_update();
89 | } else {
90 | // partial update
91 | await partial_state_update();
92 | }
93 | num_requests++;
94 | }
95 |
96 | async function full_state_update() {
97 | const response = await fetch_state();
98 | state = await deserialize(response);
99 | }
100 |
101 | async function partial_state_update() {
102 | const partial_response = await fetch_partial_state();
103 | const partial_state = await deserialize(partial_response);
104 | state = partial_update(state, partial_state);
105 | }
106 |
107 | async function fetch_partial_state() {
108 | return fetch(PARTIAL_STATE_URL);
109 | }
110 |
111 | async function fetch_state() {
112 | return fetch(STATE_URL);
113 | }
114 |
115 | async function deserialize(response) {
116 | return BINARY_TRANSFER ? deserialize_binary(response) : response.json();
117 | }
118 |
119 | async function deserialize_binary(response) {
120 | return deserialize_fb(response);
121 | }
122 |
123 | function partial_update(state, partial_state) {
124 | return Object.assign(state, partial_state);
125 | }
126 |
127 | function setup_animation_loop() {
128 | const minTime = 35;
129 | let time = Date.now();
130 | animate_current_state(); // could be further improved by info on actually updated partial state
131 | time = Date.now() - time;
132 | if (LOG_PERFORMANCE) animation_update_times.push(time);
133 | let timeout = Math.max(minTime - time, 0);
134 | let interval = window.setTimeout(setup_animation_loop, timeout);
135 | running_timeouts.push(interval);
136 | }
137 |
138 | function animate_current_state() {
139 | animate_simulation(state);
140 | num_animation_updates++;
141 | }
142 |
143 | function pause_simulation() {
144 | post(PAUSE_URL);
145 | }
146 |
147 | function resume_simulation() {
148 | post(RESUME_URL);
149 | }
150 |
151 | function stop_simulation() {
152 | stopped = true;
153 | post(STOP_URL);
154 | while (running_timeouts.length > 0) {
155 | clearTimeout(running_timeouts.pop());
156 | }
157 | }
158 |
159 | function speed_slider_changed_to(value) {
160 | value = 101 - value; // 1 - 100
161 | value /= 100; // 0.01 - 1
162 | value = Math.pow(value, 3); // exponential speedup
163 | set_rt_factor(value);
164 | return value;
165 | }
166 |
167 | function set_rt_factor(value) {
168 | post(RT_FACTOR_BASE_URL + String(value));
169 | }
170 |
171 | async function get_canvas_height() {
172 | const response = await fetch(CANVAS_HEIGHT_URL);
173 | const content = await response.text();
174 | return Number(content);
175 | }
176 |
177 | async function get_canvas_width() {
178 | const response = await fetch(CANVAS_WIDTH_URL);
179 | const content = await response.text();
180 | return Number(content);
181 | }
182 |
183 | // document interaction
184 | document.addEventListener("DOMContentLoaded", () => {
185 | ANIMATION_CANVAS_PARENT = document.querySelector(
186 | "#" + ANIMATION_CANVAS_PARENT_ID
187 | );
188 | var speed_slider = document.getElementById("speed_slider");
189 | var speed_label = document.getElementById("speed_label");
190 | function change_factor(value) {
191 | speed_label.innerHTML = (1 / speed_slider_changed_to(value)).toFixed(2);
192 | }
193 | change_factor(speed_slider.value);
194 | speed_slider.oninput = function () {
195 | change_factor(this.value);
196 | };
197 | });
198 |
199 | window.start_simulation = start_simulation;
200 | window.pause_simulation = pause_simulation;
201 | window.resume_simulation = resume_simulation;
202 | window.stop_simulation = stop_simulation;
203 |
204 | console.log("animation_client.js loaded");
205 |
--------------------------------------------------------------------------------
/examples/tilemap/diagram.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/basics/bpmn/bpmn_example.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_1cnsqpp
6 |
7 |
8 | SequenceFlow_0cae7sq
9 | SequenceFlow_1jp9f41
10 |
11 |
12 | SequenceFlow_0r4f4b2
13 | SequenceFlow_11b1c5v
14 |
15 |
16 | SequenceFlow_1k19ov5
17 |
18 |
19 | SequenceFlow_1cnsqpp
20 | SequenceFlow_0cae7sq
21 | SequenceFlow_0r4f4b2
22 |
23 |
24 | SequenceFlow_1jp9f41
25 | SequenceFlow_11b1c5v
26 | SequenceFlow_1k19ov5
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | process_time=120
36 |
37 |
38 |
39 | inter_arrival_time=0;
40 | max_entities=6
41 |
42 |
43 |
44 | process_time=41
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/examples/resources/resource_example.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_1cnsqpp
6 |
7 |
8 | SequenceFlow_0cp9w6d
9 |
10 |
11 | SequenceFlow_1wlxup7
12 | SequenceFlow_11b1c5v
13 |
14 |
15 |
16 |
17 | SequenceFlow_11b1c5v
18 | SequenceFlow_0uwp86v
19 |
20 |
21 |
22 |
23 | SequenceFlow_1cnsqpp
24 | SequenceFlow_1wlxup7
25 |
26 |
27 | DataStoreReference_0yumxiy
28 | Property_0z98f3f
29 |
30 |
31 |
32 |
33 | SequenceFlow_0uwp86v
34 | SequenceFlow_0cp9w6d
35 |
36 | DataStoreReference_0yumxiy
37 |
38 |
39 |
40 |
41 | inter_arrival_time=0;
42 | max_entities=6
43 |
44 |
45 |
46 | process_time=5
47 |
48 |
49 |
50 | process_time=5
51 |
52 |
53 |
54 | capacity=2
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------