├── 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 | inter_arrival_time=100;max_entities=2speed=10;coordinates_holder=coordinates_holder;from_node="A";to_node="C"TilemapMovement:tilemoverSource:sourceSink:sink -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------