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.
Files changed (39) hide show
  1. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/LICENSE +21 -21
  2. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/MANIFEST.in +11 -11
  3. pyjevsim-2.0.0/PKG-INFO +293 -0
  4. pyjevsim-2.0.0/README.md +266 -0
  5. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/__init__.py +40 -40
  6. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/atomic_model.py +38 -38
  7. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/behavior_executor.py +159 -128
  8. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/behavior_model.py +281 -259
  9. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/core_model.py +102 -102
  10. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/default_message_catcher.py +41 -41
  11. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/definition.py +101 -101
  12. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/executor.py +34 -27
  13. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/executor_factory.py +76 -76
  14. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/exgen.py +116 -116
  15. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/message_deliverer.py +56 -43
  16. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/restore_handler.py +115 -115
  17. pyjevsim-2.0.0/pyjevsim/schedule_queue.py +150 -0
  18. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_condition.py +100 -100
  19. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_executor.py +218 -218
  20. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_factory.py +62 -62
  21. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/snapshot_manager.py +119 -119
  22. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/structural_executor.py +152 -136
  23. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/structural_model.py +47 -47
  24. pyjevsim-2.0.0/pyjevsim/system_executor.py +930 -0
  25. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/system_message.py +107 -98
  26. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/system_object.py +56 -51
  27. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim/termination_manager.py +37 -37
  28. pyjevsim-2.0.0/pyjevsim.egg-info/PKG-INFO +293 -0
  29. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyproject.toml +42 -42
  30. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/setup.cfg +4 -4
  31. pyjevsim-1.3.1/PKG-INFO +0 -158
  32. pyjevsim-1.3.1/README.md +0 -131
  33. pyjevsim-1.3.1/pyjevsim/schedule_queue.py +0 -54
  34. pyjevsim-1.3.1/pyjevsim/system_executor.py +0 -648
  35. pyjevsim-1.3.1/pyjevsim.egg-info/PKG-INFO +0 -158
  36. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/SOURCES.txt +0 -0
  37. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/dependency_links.txt +0 -0
  38. {pyjevsim-1.3.1 → pyjevsim-2.0.0}/pyjevsim.egg-info/requires.txt +0 -0
  39. {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__
@@ -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
@@ -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