pyjevsim 1.3.1__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/LICENSE +21 -21
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/MANIFEST.in +11 -11
- pyjevsim-2.0.0/PKG-INFO +293 -0
- pyjevsim-2.0.0/README.md +266 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/__init__.py +40 -40
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/atomic_model.py +38 -38
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/behavior_executor.py +159 -128
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/behavior_model.py +281 -259
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/core_model.py +102 -102
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/default_message_catcher.py +41 -41
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/definition.py +101 -101
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/executor.py +34 -27
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/executor_factory.py +76 -76
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/exgen.py +116 -116
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/message_deliverer.py +56 -43
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/restore_handler.py +115 -115
- pyjevsim-2.0.0/pyjevsim/schedule_queue.py +150 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_condition.py +100 -100
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_executor.py +218 -218
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_factory.py +62 -62
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_manager.py +119 -119
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/structural_executor.py +152 -136
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/structural_model.py +47 -47
- pyjevsim-2.0.0/pyjevsim/system_executor.py +930 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/system_message.py +107 -98
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/system_object.py +56 -51
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/termination_manager.py +37 -37
- pyjevsim-2.0.0/pyjevsim.egg-info/PKG-INFO +293 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyproject.toml +42 -42
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/setup.cfg +4 -4
- pyjevsim-1.3.1/PKG-INFO +0 -158
- pyjevsim-1.3.1/README.md +0 -131
- pyjevsim-1.3.1/pyjevsim/schedule_queue.py +0 -54
- pyjevsim-1.3.1/pyjevsim/system_executor.py +0 -648
- pyjevsim-1.3.1/pyjevsim.egg-info/PKG-INFO +0 -158
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/SOURCES.txt +0 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/dependency_links.txt +0 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/requires.txt +0 -0
- {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/top_level.txt +0 -0
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023 eventsim
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 eventsim
|
|
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.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
include LICENSE
|
|
2
|
-
include README.md
|
|
3
|
-
include pyproject.toml
|
|
4
|
-
recursive-include pyjevsim *.py
|
|
5
|
-
recursive-exclude tests *
|
|
6
|
-
recursive-exclude examples *
|
|
7
|
-
recursive-exclude docs *
|
|
8
|
-
recursive-exclude utils *
|
|
9
|
-
recursive-exclude venv *
|
|
10
|
-
global-exclude *.pyc
|
|
11
|
-
global-exclude __pycache__
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
recursive-include pyjevsim *.py
|
|
5
|
+
recursive-exclude tests *
|
|
6
|
+
recursive-exclude examples *
|
|
7
|
+
recursive-exclude docs *
|
|
8
|
+
recursive-exclude utils *
|
|
9
|
+
recursive-exclude venv *
|
|
10
|
+
global-exclude *.pyc
|
|
11
|
+
global-exclude __pycache__
|
pyjevsim-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyjevsim
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A DEVS(Discrete Event System Specification) Modeling & Simulation environment with journaling functionality
|
|
5
|
+
Author-email: Changbeom Choi <me@cbchoi.info>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/eventsim/pyjevsim
|
|
8
|
+
Project-URL: Documentation, https://pyjevsim.readthedocs.io/en/latest/index.html
|
|
9
|
+
Project-URL: Repository, https://github.com/eventsim/pyjevsim
|
|
10
|
+
Project-URL: Issues, https://github.com/eventsim/pyjevsim/issues
|
|
11
|
+
Keywords: DEVS,simulation,discrete-event,modeling
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Classifier: Intended Audience :: Science/Research
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: dill>=0.3.6
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# pyjevsim
|
|
29
|
+
## Introduction
|
|
30
|
+
pyjevsim is a DEVS(discrete event system specification) environment that provides journaling functionality.
|
|
31
|
+
It provides the ability to snapshot and restore models or simulation engines.
|
|
32
|
+
It's compatible with Python versions 3.10+.
|
|
33
|
+
|
|
34
|
+
For more information, see the documentation. : [pyjevsim](https://pyjevsim.readthedocs.io/en/latest/index.html)
|
|
35
|
+
|
|
36
|
+
## Installing
|
|
37
|
+
You can install pyjevsim via
|
|
38
|
+
```
|
|
39
|
+
git clone https://github.com/eventsim/pyjevsim
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Dependencies
|
|
43
|
+
The only dependency required by pyjevsim is dill ~= 0.3.6 for model serialization and restoration.
|
|
44
|
+
dill is an essential library for serializing models and simulation states and can be installed via.
|
|
45
|
+
```
|
|
46
|
+
pip install dill
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Optional Dependencies
|
|
50
|
+
pytest is an optional dependency required for running test cases and example executions.
|
|
51
|
+
You can install pyjevsim via
|
|
52
|
+
```
|
|
53
|
+
pip install pytest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Additionally, you can install all necessary libraries, including optional dependencies, by running the following command:
|
|
57
|
+
```
|
|
58
|
+
pip install -r requirements.txt
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Working with pyjevsim
|
|
62
|
+
Once you have installed the library, you can begin working with it.
|
|
63
|
+
|
|
64
|
+
### Quick Start
|
|
65
|
+
The docs describe how to configure a simulation via pyjevsim's BehaviorModel and SysExecutor.
|
|
66
|
+
Check out the [documentation](link) to configure your simulation.
|
|
67
|
+
|
|
68
|
+
### Example
|
|
69
|
+
There is a banksim example that uses pyjevsim's DEVS functionality and journaling features.
|
|
70
|
+
[documentation](link)
|
|
71
|
+
|
|
72
|
+
### Output messages are shared by reference
|
|
73
|
+
|
|
74
|
+
When a model's output port has multiple downstream subscribers, every
|
|
75
|
+
subscriber receives the **same** `SysMessage` object. pyjevsim does not
|
|
76
|
+
deep-copy outputs during propagation — and neither does any other major
|
|
77
|
+
Python DEVS engine (xdevs.py and PythonPDEVS share references the same
|
|
78
|
+
way; `benchmark/aliasing_test.py` empirically demonstrates this for all
|
|
79
|
+
four engines in the comparison set). Treat received messages as
|
|
80
|
+
immutable; if your model needs to mutate a payload, copy it on the
|
|
81
|
+
receiver side:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
def ext_trans(self, port, msg):
|
|
85
|
+
payload = list(msg.retrieve()) # local copy, safe to mutate
|
|
86
|
+
payload.append(my_local_data)
|
|
87
|
+
...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
See [`benchmark/results/ALIASING.md`](benchmark/results/ALIASING.md) for
|
|
91
|
+
the full investigation and per-engine source pointers.
|
|
92
|
+
|
|
93
|
+
## Benchmarks
|
|
94
|
+
|
|
95
|
+
The [`benchmark/`](benchmark/) directory contains a DEVStone suite plus
|
|
96
|
+
adapters that run the same workload against other Python DEVS engines so the
|
|
97
|
+
pyjevsim baseline can be tracked over time.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
benchmark/
|
|
101
|
+
├── devstone/ # original pyjevsim-only DEVStone (flat)
|
|
102
|
+
│ ├── atomic.py
|
|
103
|
+
│ └── topology.py
|
|
104
|
+
├── engines/ # cross-engine canonical DEVStone
|
|
105
|
+
│ ├── common.py # shared RunResult dataclass
|
|
106
|
+
│ ├── pyjevsim/ # adapter for this repo
|
|
107
|
+
│ ├── xdevs/ # adapter for xdevs.py (pip install xdevs)
|
|
108
|
+
│ ├── pypdevs/ # adapter for PythonPDEVS minimal kernel
|
|
109
|
+
│ └── reference/ # hand-rolled flat-FEL engine (floor)
|
|
110
|
+
├── run_devstone.py # pyjevsim-only runner
|
|
111
|
+
├── run_compare.py # cross-engine comparison runner
|
|
112
|
+
└── results/
|
|
113
|
+
├── BASELINE.md # captured baseline numbers
|
|
114
|
+
├── baseline.csv
|
|
115
|
+
└── devstone_sweep.csv
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### pyjevsim-only sweep
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
python -m benchmark.run_devstone --sweep \
|
|
122
|
+
--output benchmark/results/devstone_sweep.csv
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Cross-engine comparison
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
pip install xdevs # optional
|
|
129
|
+
python -m benchmark.run_compare --list-engines
|
|
130
|
+
python -m benchmark.run_compare \
|
|
131
|
+
--output benchmark/results/baseline.csv
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Sparse-time baseline
|
|
135
|
+
|
|
136
|
+
`run_sparse` runs a tiny periodic-generator-plus-sink topology while
|
|
137
|
+
sweeping the inter-event simulated period. Holds the work constant at
|
|
138
|
+
100 events; only the simulated-time gap between events varies. Isolates
|
|
139
|
+
per-tick overhead in V_TIME mode (see
|
|
140
|
+
[`benchmark/results/SPARSE.md`](benchmark/results/SPARSE.md)):
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
python -m benchmark.run_sparse --output benchmark/results/sparse.csv
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Output aliasing test
|
|
147
|
+
|
|
148
|
+
`benchmark/aliasing_test.py` empirically demonstrates that all four
|
|
149
|
+
engines share output value references across multiple subscribers — see
|
|
150
|
+
[`benchmark/results/ALIASING.md`](benchmark/results/ALIASING.md). The
|
|
151
|
+
prevailing convention is "treat received values as immutable; copy on
|
|
152
|
+
the receiver if you need to mutate".
|
|
153
|
+
|
|
154
|
+
Current baseline (best-of-three, no synthetic CPU work) — see
|
|
155
|
+
[`benchmark/results/BASELINE.md`](benchmark/results/BASELINE.md):
|
|
156
|
+
|
|
157
|
+
| variant | d × w | pyjevsim tr/s | xdevs tr/s | pypdevs tr/s | reference tr/s |
|
|
158
|
+
|---------|-------|---------------|------------|--------------|----------------|
|
|
159
|
+
| LI | 4 × 4 | 175 k | 689 k | 765 k | 1.68 M |
|
|
160
|
+
| HI | 4 × 4 | 233 k | 546 k | 888 k | 2.00 M |
|
|
161
|
+
| HO | 4 × 4 | 241 k | 757 k | 918 k | 1.97 M |
|
|
162
|
+
|
|
163
|
+
Use `--int-cycles N` / `--ext-cycles N` to inject synthetic CPU work per
|
|
164
|
+
transition and shift the measurement toward user-code cost.
|
|
165
|
+
|
|
166
|
+
## Debugging Uncaught Output Messages
|
|
167
|
+
|
|
168
|
+
By default `SysExecutor` drops output messages that hit a port with no
|
|
169
|
+
downstream coupling — the simulator stays on its fast path and the
|
|
170
|
+
events disappear silently. When wiring up a model graph it is often
|
|
171
|
+
useful to know *which* events are leaking; pass `track_uncaught=True`
|
|
172
|
+
and they get routed to the built-in `DefaultMessageCatcher` (accessible
|
|
173
|
+
as `se.dmc`) so you can observe them:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
se = SysExecutor(1, ex_mode=ExecutionType.V_TIME, track_uncaught=True)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The flag costs ~10-15% throughput on dense graphs with many dangling
|
|
180
|
+
outputs (every uncoupled emit pays for one `ext_trans` + reschedule on
|
|
181
|
+
the catcher), so leave it off in production runs.
|
|
182
|
+
|
|
183
|
+
## Execution Modes
|
|
184
|
+
|
|
185
|
+
SysExecutor supports three execution modes via `ExecutionType`:
|
|
186
|
+
|
|
187
|
+
| Mode | Description |
|
|
188
|
+
|------|-------------|
|
|
189
|
+
| `V_TIME` | Virtual time — simulation runs as fast as possible |
|
|
190
|
+
| `R_TIME` | Real time — simulation paces itself to wall-clock time |
|
|
191
|
+
| `HLA_TIME` | HLA/RTI-controlled time — time advancement is driven externally |
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from pyjevsim.system_executor import SysExecutor
|
|
195
|
+
from pyjevsim.definition import ExecutionType
|
|
196
|
+
|
|
197
|
+
se = SysExecutor(1, ex_mode=ExecutionType.V_TIME)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Multi-threading Support
|
|
201
|
+
|
|
202
|
+
SysExecutor provides thread-safe APIs for multi-threaded simulation environments where external threads inject events while the simulation runs.
|
|
203
|
+
|
|
204
|
+
### Pause / Resume
|
|
205
|
+
|
|
206
|
+
Pause the simulation to allow external threads to accumulate events, then resume.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
se.pause_sim() # Pauses the simulation loop
|
|
210
|
+
# External threads can safely call insert_external_event() while paused
|
|
211
|
+
se.resume_sim() # Resumes the simulation loop
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### External Event Injection
|
|
215
|
+
|
|
216
|
+
Insert events from external threads into the simulation. Thread-safe via internal synchronization.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
se.insert_external_event("port_name", message, scheduled_time=0)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Output Event Callback
|
|
223
|
+
|
|
224
|
+
Register a callback to be notified when output events are generated, avoiding polling.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
se.set_output_event_callback(lambda: print("output ready"))
|
|
228
|
+
events = se.handle_external_output_event()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## HLA/RTI Integration (HLA_TIME Mode)
|
|
232
|
+
|
|
233
|
+
For HLA/RTI-controlled simulations, use `HLA_TIME` mode with `step()` and `get_next_event_time()`.
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
se = SysExecutor(1, ex_mode=ExecutionType.HLA_TIME)
|
|
237
|
+
se.register_entity(model)
|
|
238
|
+
se.init_sim()
|
|
239
|
+
|
|
240
|
+
# RTI-driven loop
|
|
241
|
+
while not se.is_terminated():
|
|
242
|
+
next_time = se.get_next_event_time()
|
|
243
|
+
# ... request time advance from RTI, wait for grant ...
|
|
244
|
+
granted_time = ... # time granted by RTI
|
|
245
|
+
output_events = se.step(granted_time)
|
|
246
|
+
# ... publish output_events to RTI ...
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### `step(granted_time)`
|
|
250
|
+
|
|
251
|
+
Runs one RTI-granted simulation step using the same Parallel-DEVS
|
|
252
|
+
four-phase tick that the standalone V_TIME path uses, so HLA federates
|
|
253
|
+
get correct `δ_int / δ_ext / δ_con` semantics:
|
|
254
|
+
|
|
255
|
+
- Every event whose `req_time <= granted_time` fires inside the call.
|
|
256
|
+
- Multiple cascade rounds at the same simulated instant complete in one
|
|
257
|
+
`step()` (sigma=0 chains do not require multiple grants).
|
|
258
|
+
- During each round, `global_time` reflects the actual event time so
|
|
259
|
+
models observe correct simulated time inside their transitions.
|
|
260
|
+
- Per IEEE 1516-2010, `global_time` lands at `granted_time` when the
|
|
261
|
+
call returns, even if the last processed event was earlier.
|
|
262
|
+
- Returns the `output_event_queue` contents drained during this step
|
|
263
|
+
(a `deque` of `(time, message)` tuples) so the federate can republish
|
|
264
|
+
them as RTI interactions.
|
|
265
|
+
|
|
266
|
+
### `get_next_event_time()`
|
|
267
|
+
|
|
268
|
+
Returns the earliest scheduled event time across the FEL and the
|
|
269
|
+
external-event queue. Use it to compute the Time Advance Request value
|
|
270
|
+
for the RTI.
|
|
271
|
+
|
|
272
|
+
### Federate ambassador
|
|
273
|
+
|
|
274
|
+
pyjevsim ships the simulator-side hooks (above) but not an RTI
|
|
275
|
+
ambassador. Wire `step` / `get_next_event_time` /
|
|
276
|
+
`insert_external_event` / `set_output_event_callback` into the federate
|
|
277
|
+
ambassador of your chosen IEEE 1516-2010 RTI client.
|
|
278
|
+
|
|
279
|
+
## Graceful Termination
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
se.terminate_simulation() # Sets SIMULATION_TERMINATED state
|
|
283
|
+
se.is_terminated() # Returns True if terminated
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Signal handlers (SIGTERM, SIGINT) automatically invoke `terminate_simulation()` on all registered SysExecutor instances.
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
Author: Changbeom Choi (@cbchoi)
|
|
290
|
+
Copyright (c) 2014-2020 Handong Global University
|
|
291
|
+
Copyright (c) 2021-2024 Hanbat National University
|
|
292
|
+
License: MIT. The full license text is available at:
|
|
293
|
+
- https://github.com/eventsim/pyjevsim/blob/main/LICENSE
|
pyjevsim-2.0.0/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# pyjevsim
|
|
2
|
+
## Introduction
|
|
3
|
+
pyjevsim is a DEVS(discrete event system specification) environment that provides journaling functionality.
|
|
4
|
+
It provides the ability to snapshot and restore models or simulation engines.
|
|
5
|
+
It's compatible with Python versions 3.10+.
|
|
6
|
+
|
|
7
|
+
For more information, see the documentation. : [pyjevsim](https://pyjevsim.readthedocs.io/en/latest/index.html)
|
|
8
|
+
|
|
9
|
+
## Installing
|
|
10
|
+
You can install pyjevsim via
|
|
11
|
+
```
|
|
12
|
+
git clone https://github.com/eventsim/pyjevsim
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Dependencies
|
|
16
|
+
The only dependency required by pyjevsim is dill ~= 0.3.6 for model serialization and restoration.
|
|
17
|
+
dill is an essential library for serializing models and simulation states and can be installed via.
|
|
18
|
+
```
|
|
19
|
+
pip install dill
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Optional Dependencies
|
|
23
|
+
pytest is an optional dependency required for running test cases and example executions.
|
|
24
|
+
You can install pyjevsim via
|
|
25
|
+
```
|
|
26
|
+
pip install pytest
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Additionally, you can install all necessary libraries, including optional dependencies, by running the following command:
|
|
30
|
+
```
|
|
31
|
+
pip install -r requirements.txt
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Working with pyjevsim
|
|
35
|
+
Once you have installed the library, you can begin working with it.
|
|
36
|
+
|
|
37
|
+
### Quick Start
|
|
38
|
+
The docs describe how to configure a simulation via pyjevsim's BehaviorModel and SysExecutor.
|
|
39
|
+
Check out the [documentation](link) to configure your simulation.
|
|
40
|
+
|
|
41
|
+
### Example
|
|
42
|
+
There is a banksim example that uses pyjevsim's DEVS functionality and journaling features.
|
|
43
|
+
[documentation](link)
|
|
44
|
+
|
|
45
|
+
### Output messages are shared by reference
|
|
46
|
+
|
|
47
|
+
When a model's output port has multiple downstream subscribers, every
|
|
48
|
+
subscriber receives the **same** `SysMessage` object. pyjevsim does not
|
|
49
|
+
deep-copy outputs during propagation — and neither does any other major
|
|
50
|
+
Python DEVS engine (xdevs.py and PythonPDEVS share references the same
|
|
51
|
+
way; `benchmark/aliasing_test.py` empirically demonstrates this for all
|
|
52
|
+
four engines in the comparison set). Treat received messages as
|
|
53
|
+
immutable; if your model needs to mutate a payload, copy it on the
|
|
54
|
+
receiver side:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
def ext_trans(self, port, msg):
|
|
58
|
+
payload = list(msg.retrieve()) # local copy, safe to mutate
|
|
59
|
+
payload.append(my_local_data)
|
|
60
|
+
...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See [`benchmark/results/ALIASING.md`](benchmark/results/ALIASING.md) for
|
|
64
|
+
the full investigation and per-engine source pointers.
|
|
65
|
+
|
|
66
|
+
## Benchmarks
|
|
67
|
+
|
|
68
|
+
The [`benchmark/`](benchmark/) directory contains a DEVStone suite plus
|
|
69
|
+
adapters that run the same workload against other Python DEVS engines so the
|
|
70
|
+
pyjevsim baseline can be tracked over time.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
benchmark/
|
|
74
|
+
├── devstone/ # original pyjevsim-only DEVStone (flat)
|
|
75
|
+
│ ├── atomic.py
|
|
76
|
+
│ └── topology.py
|
|
77
|
+
├── engines/ # cross-engine canonical DEVStone
|
|
78
|
+
│ ├── common.py # shared RunResult dataclass
|
|
79
|
+
│ ├── pyjevsim/ # adapter for this repo
|
|
80
|
+
│ ├── xdevs/ # adapter for xdevs.py (pip install xdevs)
|
|
81
|
+
│ ├── pypdevs/ # adapter for PythonPDEVS minimal kernel
|
|
82
|
+
│ └── reference/ # hand-rolled flat-FEL engine (floor)
|
|
83
|
+
├── run_devstone.py # pyjevsim-only runner
|
|
84
|
+
├── run_compare.py # cross-engine comparison runner
|
|
85
|
+
└── results/
|
|
86
|
+
├── BASELINE.md # captured baseline numbers
|
|
87
|
+
├── baseline.csv
|
|
88
|
+
└── devstone_sweep.csv
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### pyjevsim-only sweep
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
python -m benchmark.run_devstone --sweep \
|
|
95
|
+
--output benchmark/results/devstone_sweep.csv
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Cross-engine comparison
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
pip install xdevs # optional
|
|
102
|
+
python -m benchmark.run_compare --list-engines
|
|
103
|
+
python -m benchmark.run_compare \
|
|
104
|
+
--output benchmark/results/baseline.csv
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Sparse-time baseline
|
|
108
|
+
|
|
109
|
+
`run_sparse` runs a tiny periodic-generator-plus-sink topology while
|
|
110
|
+
sweeping the inter-event simulated period. Holds the work constant at
|
|
111
|
+
100 events; only the simulated-time gap between events varies. Isolates
|
|
112
|
+
per-tick overhead in V_TIME mode (see
|
|
113
|
+
[`benchmark/results/SPARSE.md`](benchmark/results/SPARSE.md)):
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
python -m benchmark.run_sparse --output benchmark/results/sparse.csv
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Output aliasing test
|
|
120
|
+
|
|
121
|
+
`benchmark/aliasing_test.py` empirically demonstrates that all four
|
|
122
|
+
engines share output value references across multiple subscribers — see
|
|
123
|
+
[`benchmark/results/ALIASING.md`](benchmark/results/ALIASING.md). The
|
|
124
|
+
prevailing convention is "treat received values as immutable; copy on
|
|
125
|
+
the receiver if you need to mutate".
|
|
126
|
+
|
|
127
|
+
Current baseline (best-of-three, no synthetic CPU work) — see
|
|
128
|
+
[`benchmark/results/BASELINE.md`](benchmark/results/BASELINE.md):
|
|
129
|
+
|
|
130
|
+
| variant | d × w | pyjevsim tr/s | xdevs tr/s | pypdevs tr/s | reference tr/s |
|
|
131
|
+
|---------|-------|---------------|------------|--------------|----------------|
|
|
132
|
+
| LI | 4 × 4 | 175 k | 689 k | 765 k | 1.68 M |
|
|
133
|
+
| HI | 4 × 4 | 233 k | 546 k | 888 k | 2.00 M |
|
|
134
|
+
| HO | 4 × 4 | 241 k | 757 k | 918 k | 1.97 M |
|
|
135
|
+
|
|
136
|
+
Use `--int-cycles N` / `--ext-cycles N` to inject synthetic CPU work per
|
|
137
|
+
transition and shift the measurement toward user-code cost.
|
|
138
|
+
|
|
139
|
+
## Debugging Uncaught Output Messages
|
|
140
|
+
|
|
141
|
+
By default `SysExecutor` drops output messages that hit a port with no
|
|
142
|
+
downstream coupling — the simulator stays on its fast path and the
|
|
143
|
+
events disappear silently. When wiring up a model graph it is often
|
|
144
|
+
useful to know *which* events are leaking; pass `track_uncaught=True`
|
|
145
|
+
and they get routed to the built-in `DefaultMessageCatcher` (accessible
|
|
146
|
+
as `se.dmc`) so you can observe them:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
se = SysExecutor(1, ex_mode=ExecutionType.V_TIME, track_uncaught=True)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The flag costs ~10-15% throughput on dense graphs with many dangling
|
|
153
|
+
outputs (every uncoupled emit pays for one `ext_trans` + reschedule on
|
|
154
|
+
the catcher), so leave it off in production runs.
|
|
155
|
+
|
|
156
|
+
## Execution Modes
|
|
157
|
+
|
|
158
|
+
SysExecutor supports three execution modes via `ExecutionType`:
|
|
159
|
+
|
|
160
|
+
| Mode | Description |
|
|
161
|
+
|------|-------------|
|
|
162
|
+
| `V_TIME` | Virtual time — simulation runs as fast as possible |
|
|
163
|
+
| `R_TIME` | Real time — simulation paces itself to wall-clock time |
|
|
164
|
+
| `HLA_TIME` | HLA/RTI-controlled time — time advancement is driven externally |
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from pyjevsim.system_executor import SysExecutor
|
|
168
|
+
from pyjevsim.definition import ExecutionType
|
|
169
|
+
|
|
170
|
+
se = SysExecutor(1, ex_mode=ExecutionType.V_TIME)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Multi-threading Support
|
|
174
|
+
|
|
175
|
+
SysExecutor provides thread-safe APIs for multi-threaded simulation environments where external threads inject events while the simulation runs.
|
|
176
|
+
|
|
177
|
+
### Pause / Resume
|
|
178
|
+
|
|
179
|
+
Pause the simulation to allow external threads to accumulate events, then resume.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
se.pause_sim() # Pauses the simulation loop
|
|
183
|
+
# External threads can safely call insert_external_event() while paused
|
|
184
|
+
se.resume_sim() # Resumes the simulation loop
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### External Event Injection
|
|
188
|
+
|
|
189
|
+
Insert events from external threads into the simulation. Thread-safe via internal synchronization.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
se.insert_external_event("port_name", message, scheduled_time=0)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Output Event Callback
|
|
196
|
+
|
|
197
|
+
Register a callback to be notified when output events are generated, avoiding polling.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
se.set_output_event_callback(lambda: print("output ready"))
|
|
201
|
+
events = se.handle_external_output_event()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## HLA/RTI Integration (HLA_TIME Mode)
|
|
205
|
+
|
|
206
|
+
For HLA/RTI-controlled simulations, use `HLA_TIME` mode with `step()` and `get_next_event_time()`.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
se = SysExecutor(1, ex_mode=ExecutionType.HLA_TIME)
|
|
210
|
+
se.register_entity(model)
|
|
211
|
+
se.init_sim()
|
|
212
|
+
|
|
213
|
+
# RTI-driven loop
|
|
214
|
+
while not se.is_terminated():
|
|
215
|
+
next_time = se.get_next_event_time()
|
|
216
|
+
# ... request time advance from RTI, wait for grant ...
|
|
217
|
+
granted_time = ... # time granted by RTI
|
|
218
|
+
output_events = se.step(granted_time)
|
|
219
|
+
# ... publish output_events to RTI ...
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `step(granted_time)`
|
|
223
|
+
|
|
224
|
+
Runs one RTI-granted simulation step using the same Parallel-DEVS
|
|
225
|
+
four-phase tick that the standalone V_TIME path uses, so HLA federates
|
|
226
|
+
get correct `δ_int / δ_ext / δ_con` semantics:
|
|
227
|
+
|
|
228
|
+
- Every event whose `req_time <= granted_time` fires inside the call.
|
|
229
|
+
- Multiple cascade rounds at the same simulated instant complete in one
|
|
230
|
+
`step()` (sigma=0 chains do not require multiple grants).
|
|
231
|
+
- During each round, `global_time` reflects the actual event time so
|
|
232
|
+
models observe correct simulated time inside their transitions.
|
|
233
|
+
- Per IEEE 1516-2010, `global_time` lands at `granted_time` when the
|
|
234
|
+
call returns, even if the last processed event was earlier.
|
|
235
|
+
- Returns the `output_event_queue` contents drained during this step
|
|
236
|
+
(a `deque` of `(time, message)` tuples) so the federate can republish
|
|
237
|
+
them as RTI interactions.
|
|
238
|
+
|
|
239
|
+
### `get_next_event_time()`
|
|
240
|
+
|
|
241
|
+
Returns the earliest scheduled event time across the FEL and the
|
|
242
|
+
external-event queue. Use it to compute the Time Advance Request value
|
|
243
|
+
for the RTI.
|
|
244
|
+
|
|
245
|
+
### Federate ambassador
|
|
246
|
+
|
|
247
|
+
pyjevsim ships the simulator-side hooks (above) but not an RTI
|
|
248
|
+
ambassador. Wire `step` / `get_next_event_time` /
|
|
249
|
+
`insert_external_event` / `set_output_event_callback` into the federate
|
|
250
|
+
ambassador of your chosen IEEE 1516-2010 RTI client.
|
|
251
|
+
|
|
252
|
+
## Graceful Termination
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
se.terminate_simulation() # Sets SIMULATION_TERMINATED state
|
|
256
|
+
se.is_terminated() # Returns True if terminated
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Signal handlers (SIGTERM, SIGINT) automatically invoke `terminate_simulation()` on all registered SysExecutor instances.
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
Author: Changbeom Choi (@cbchoi)
|
|
263
|
+
Copyright (c) 2014-2020 Handong Global University
|
|
264
|
+
Copyright (c) 2021-2024 Hanbat National University
|
|
265
|
+
License: MIT. The full license text is available at:
|
|
266
|
+
- https://github.com/eventsim/pyjevsim/blob/main/LICENSE
|