smolpy-engine-rs 1.8.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.
- smolpy_engine_rs-1.8.0/Cargo.lock +277 -0
- smolpy_engine_rs-1.8.0/Cargo.toml +13 -0
- smolpy_engine_rs-1.8.0/PKG-INFO +40 -0
- smolpy_engine_rs-1.8.0/README.md +23 -0
- smolpy_engine_rs-1.8.0/pyproject.toml +28 -0
- smolpy_engine_rs-1.8.0/smol-engine/Cargo.toml +12 -0
- smolpy_engine_rs-1.8.0/smol-engine/src/frames.rs +29 -0
- smolpy_engine_rs-1.8.0/smol-engine/src/lib.rs +18 -0
- smolpy_engine_rs-1.8.0/smol-engine/src/rng.rs +174 -0
- smolpy_engine_rs-1.8.0/smol-engine/src/scenario.rs +484 -0
- smolpy_engine_rs-1.8.0/smol-engine/src/sim.rs +854 -0
- smolpy_engine_rs-1.8.0/smol-engine/src/timebase.rs +363 -0
- smolpy_engine_rs-1.8.0/smolpy-engine-rs/Cargo.toml +17 -0
- smolpy_engine_rs-1.8.0/smolpy-engine-rs/README.md +23 -0
- smolpy_engine_rs-1.8.0/smolpy-engine-rs/smoke_test.py +55 -0
- smolpy_engine_rs-1.8.0/smolpy-engine-rs/src/lib.rs +107 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "block-buffer"
|
|
7
|
+
version = "0.12.1"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"hybrid-array",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "cfg-if"
|
|
16
|
+
version = "1.0.5"
|
|
17
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
|
+
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
|
19
|
+
|
|
20
|
+
[[package]]
|
|
21
|
+
name = "const-oid"
|
|
22
|
+
version = "0.10.3"
|
|
23
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
+
checksum = "be95d4a884358a21a199689323c29e9256b4a1b315e22f183d155422041d3a9a"
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "cpufeatures"
|
|
28
|
+
version = "0.3.1"
|
|
29
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
+
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"libc",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "crypto-common"
|
|
37
|
+
version = "0.2.2"
|
|
38
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
39
|
+
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
|
40
|
+
dependencies = [
|
|
41
|
+
"hybrid-array",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "digest"
|
|
46
|
+
version = "0.11.3"
|
|
47
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
+
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
|
49
|
+
dependencies = [
|
|
50
|
+
"block-buffer",
|
|
51
|
+
"const-oid",
|
|
52
|
+
"crypto-common",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[[package]]
|
|
56
|
+
name = "heck"
|
|
57
|
+
version = "0.5.0"
|
|
58
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
59
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "hybrid-array"
|
|
63
|
+
version = "0.4.15"
|
|
64
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
65
|
+
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
|
|
66
|
+
dependencies = [
|
|
67
|
+
"typenum",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "itoa"
|
|
72
|
+
version = "1.0.19"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "3bd1574e617a9a6c1f8f8181bd7e841e1f20e4277607ef28945765188d6284d4"
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "libc"
|
|
78
|
+
version = "0.2.191"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
|
81
|
+
|
|
82
|
+
[[package]]
|
|
83
|
+
name = "memchr"
|
|
84
|
+
version = "2.8.3"
|
|
85
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
86
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
87
|
+
|
|
88
|
+
[[package]]
|
|
89
|
+
name = "once_cell"
|
|
90
|
+
version = "1.21.4"
|
|
91
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
92
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
93
|
+
|
|
94
|
+
[[package]]
|
|
95
|
+
name = "portable-atomic"
|
|
96
|
+
version = "1.15.0"
|
|
97
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
98
|
+
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|
99
|
+
|
|
100
|
+
[[package]]
|
|
101
|
+
name = "proc-macro2"
|
|
102
|
+
version = "1.0.107"
|
|
103
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
104
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
105
|
+
dependencies = [
|
|
106
|
+
"unicode-ident",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[[package]]
|
|
110
|
+
name = "pyo3"
|
|
111
|
+
version = "0.30.1"
|
|
112
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
113
|
+
checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
|
|
114
|
+
dependencies = [
|
|
115
|
+
"libc",
|
|
116
|
+
"once_cell",
|
|
117
|
+
"portable-atomic",
|
|
118
|
+
"pyo3-build-config",
|
|
119
|
+
"pyo3-ffi",
|
|
120
|
+
"pyo3-macros",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
[[package]]
|
|
124
|
+
name = "pyo3-build-config"
|
|
125
|
+
version = "0.30.1"
|
|
126
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
127
|
+
checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
|
|
128
|
+
dependencies = [
|
|
129
|
+
"target-lexicon",
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
[[package]]
|
|
133
|
+
name = "pyo3-ffi"
|
|
134
|
+
version = "0.30.1"
|
|
135
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
136
|
+
checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
|
|
137
|
+
dependencies = [
|
|
138
|
+
"libc",
|
|
139
|
+
"pyo3-build-config",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[[package]]
|
|
143
|
+
name = "pyo3-macros"
|
|
144
|
+
version = "0.30.1"
|
|
145
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
146
|
+
checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
|
|
147
|
+
dependencies = [
|
|
148
|
+
"proc-macro2",
|
|
149
|
+
"pyo3-macros-backend",
|
|
150
|
+
"quote",
|
|
151
|
+
"syn",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
[[package]]
|
|
155
|
+
name = "pyo3-macros-backend"
|
|
156
|
+
version = "0.30.1"
|
|
157
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
158
|
+
checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
|
|
159
|
+
dependencies = [
|
|
160
|
+
"heck",
|
|
161
|
+
"proc-macro2",
|
|
162
|
+
"quote",
|
|
163
|
+
"syn",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
[[package]]
|
|
167
|
+
name = "quote"
|
|
168
|
+
version = "1.0.47"
|
|
169
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
170
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
171
|
+
dependencies = [
|
|
172
|
+
"proc-macro2",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
[[package]]
|
|
176
|
+
name = "serde"
|
|
177
|
+
version = "1.0.229"
|
|
178
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
179
|
+
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
|
180
|
+
dependencies = [
|
|
181
|
+
"serde_core",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
[[package]]
|
|
185
|
+
name = "serde_core"
|
|
186
|
+
version = "1.0.229"
|
|
187
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
188
|
+
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
|
189
|
+
dependencies = [
|
|
190
|
+
"serde_derive",
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
[[package]]
|
|
194
|
+
name = "serde_derive"
|
|
195
|
+
version = "1.0.229"
|
|
196
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
197
|
+
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
198
|
+
dependencies = [
|
|
199
|
+
"proc-macro2",
|
|
200
|
+
"quote",
|
|
201
|
+
"syn",
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
[[package]]
|
|
205
|
+
name = "serde_json"
|
|
206
|
+
version = "1.0.154"
|
|
207
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
208
|
+
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
|
209
|
+
dependencies = [
|
|
210
|
+
"itoa",
|
|
211
|
+
"memchr",
|
|
212
|
+
"serde",
|
|
213
|
+
"serde_core",
|
|
214
|
+
"zmij",
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
[[package]]
|
|
218
|
+
name = "sha2"
|
|
219
|
+
version = "0.11.1"
|
|
220
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
221
|
+
checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
|
|
222
|
+
dependencies = [
|
|
223
|
+
"cfg-if",
|
|
224
|
+
"cpufeatures",
|
|
225
|
+
"digest",
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
[[package]]
|
|
229
|
+
name = "smol-engine"
|
|
230
|
+
version = "1.8.0"
|
|
231
|
+
dependencies = [
|
|
232
|
+
"serde_json",
|
|
233
|
+
"sha2",
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
[[package]]
|
|
237
|
+
name = "smolpy-engine-rs"
|
|
238
|
+
version = "1.8.0"
|
|
239
|
+
dependencies = [
|
|
240
|
+
"pyo3",
|
|
241
|
+
"smol-engine",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
[[package]]
|
|
245
|
+
name = "syn"
|
|
246
|
+
version = "3.0.10"
|
|
247
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
248
|
+
checksum = "4ae9ccbea3a7b0b09443036490c8f34a8b1b760fd345e5c0dae0aac718faea79"
|
|
249
|
+
dependencies = [
|
|
250
|
+
"proc-macro2",
|
|
251
|
+
"quote",
|
|
252
|
+
"unicode-ident",
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
[[package]]
|
|
256
|
+
name = "target-lexicon"
|
|
257
|
+
version = "0.13.6"
|
|
258
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
259
|
+
checksum = "8c447be3a2abfa84b2d91680c56600bd6b2873866de27b36ec911be705e291da"
|
|
260
|
+
|
|
261
|
+
[[package]]
|
|
262
|
+
name = "typenum"
|
|
263
|
+
version = "1.20.1"
|
|
264
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
265
|
+
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
|
266
|
+
|
|
267
|
+
[[package]]
|
|
268
|
+
name = "unicode-ident"
|
|
269
|
+
version = "1.0.27"
|
|
270
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
271
|
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
|
272
|
+
|
|
273
|
+
[[package]]
|
|
274
|
+
name = "zmij"
|
|
275
|
+
version = "1.0.23"
|
|
276
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
277
|
+
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
members = ["smol-engine", "smolpy-engine-rs"]
|
|
3
|
+
resolver = "2"
|
|
4
|
+
|
|
5
|
+
[workspace.package]
|
|
6
|
+
version = "1.8.0"
|
|
7
|
+
edition = "2021"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
repository = "https://github.com/jwszolek/SMOLPy"
|
|
10
|
+
|
|
11
|
+
[profile.release]
|
|
12
|
+
lto = "fat"
|
|
13
|
+
codegen-units = 1
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smolpy-engine-rs
|
|
3
|
+
Version: 1.8.0
|
|
4
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Programming Language :: Rust
|
|
7
|
+
Classifier: Topic :: System :: Networking
|
|
8
|
+
Classifier: Topic :: Scientific/Engineering
|
|
9
|
+
Summary: Native Rust simulation engine for SMOLPy: SimPy's results bit for bit, about 40x faster
|
|
10
|
+
License: MIT
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
13
|
+
Project-URL: Documentation, https://jwszolek.github.io/SMOLPy/
|
|
14
|
+
Project-URL: Homepage, https://github.com/jwszolek/SMOLPy
|
|
15
|
+
Project-URL: Source, https://github.com/jwszolek/SMOLPy/tree/main/rust
|
|
16
|
+
|
|
17
|
+
# smolpy-engine-rs
|
|
18
|
+
|
|
19
|
+
The native simulation engine of [SMOLPy](https://github.com/jwszolek/SMOLPy), a network description language and discrete-event simulator for industrial networks. It is written in Rust and exposed to Python with PyO3 ([ADR-0008](https://jwszolek.github.io/SMOLPy/adr/0008-rust-des-engine-mvp/)).
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install smolpy smolpy-engine-rs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
result = net.simulate(duration=60_000, backend="rust")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **The same model as SimPy.** SimPy, SMOLPy's reference engine, and this engine implement the same written contract, [Model Semantics](https://jwszolek.github.io/SMOLPy/model-semantics/). On deterministic scenarios they give the same results bit for bit, down to the event count. On random traffic, which uses each engine's own generator, they agree statistically.
|
|
30
|
+
- **About 40x faster.**
|
|
31
|
+
- B1 at 10 M frames runs in 4.9 s, against 200.8 s on SimPy.
|
|
32
|
+
- A 10 201-node plant (B4) at 100 M frames runs in 5.2 min, against 106 min.
|
|
33
|
+
- **Scope so far.**
|
|
34
|
+
- Adapters, switches (both QoS modes, learning and converged MAC tables), links, and flows with every pattern and IMIX sizes.
|
|
35
|
+
- The metrics `throughput`, `latency`, `bytes_sent`, `bytes_received`, `queue_depth` and `utilization`.
|
|
36
|
+
- MQTT, Modbus TCP and PROFINET follow. A scenario that uses them is rejected before the run, with a message naming the element.
|
|
37
|
+
- **Versions.** The package is released together with `smolpy`, with the same version. It checks that it reads the Scenario IR version that `smolpy` writes.
|
|
38
|
+
|
|
39
|
+
One abi3 wheel per platform serves every Python from 3.10: Linux x86_64 and aarch64, macOS arm64 and x86_64, and Windows x86_64.
|
|
40
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# smolpy-engine-rs
|
|
2
|
+
|
|
3
|
+
The native simulation engine of [SMOLPy](https://github.com/jwszolek/SMOLPy), a network description language and discrete-event simulator for industrial networks. It is written in Rust and exposed to Python with PyO3 ([ADR-0008](https://jwszolek.github.io/SMOLPy/adr/0008-rust-des-engine-mvp/)).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install smolpy smolpy-engine-rs
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
result = net.simulate(duration=60_000, backend="rust")
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- **The same model as SimPy.** SimPy, SMOLPy's reference engine, and this engine implement the same written contract, [Model Semantics](https://jwszolek.github.io/SMOLPy/model-semantics/). On deterministic scenarios they give the same results bit for bit, down to the event count. On random traffic, which uses each engine's own generator, they agree statistically.
|
|
14
|
+
- **About 40x faster.**
|
|
15
|
+
- B1 at 10 M frames runs in 4.9 s, against 200.8 s on SimPy.
|
|
16
|
+
- A 10 201-node plant (B4) at 100 M frames runs in 5.2 min, against 106 min.
|
|
17
|
+
- **Scope so far.**
|
|
18
|
+
- Adapters, switches (both QoS modes, learning and converged MAC tables), links, and flows with every pattern and IMIX sizes.
|
|
19
|
+
- The metrics `throughput`, `latency`, `bytes_sent`, `bytes_received`, `queue_depth` and `utilization`.
|
|
20
|
+
- MQTT, Modbus TCP and PROFINET follow. A scenario that uses them is rejected before the run, with a message naming the element.
|
|
21
|
+
- **Versions.** The package is released together with `smolpy`, with the same version. It checks that it reads the Scenario IR version that `smolpy` writes.
|
|
22
|
+
|
|
23
|
+
One abi3 wheel per platform serves every Python from 3.10: Linux x86_64 and aarch64, macOS arm64 and x86_64, and Windows x86_64.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.7,<2"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smolpy-engine-rs"
|
|
7
|
+
description = "Native Rust simulation engine for SMOLPy: SimPy's results bit for bit, about 40x faster"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
version = "1.8.0"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Rust",
|
|
16
|
+
"Topic :: System :: Networking",
|
|
17
|
+
"Topic :: Scientific/Engineering",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/jwszolek/SMOLPy"
|
|
22
|
+
Documentation = "https://jwszolek.github.io/SMOLPy/"
|
|
23
|
+
Source = "https://github.com/jwszolek/SMOLPy/tree/main/rust"
|
|
24
|
+
|
|
25
|
+
[tool.maturin]
|
|
26
|
+
features = ["pyo3/extension-module"]
|
|
27
|
+
module-name = "smolpy_engine_rs"
|
|
28
|
+
manifest-path = "smolpy-engine-rs/Cargo.toml"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "smol-engine"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
repository.workspace = true
|
|
7
|
+
description = "SMOLPy's native discrete-event network simulation engine (ADR-0008)"
|
|
8
|
+
publish = false # shipped as the Python package smolpy-engine-rs, not on crates.io
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
serde_json = { version = "1.0.154", features = ["arbitrary_precision"] }
|
|
12
|
+
sha2 = "0.11.1"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//! Ethernet frames on the wire (ADR-0010), as `smolpy.model.frames`.
|
|
2
|
+
//!
|
|
3
|
+
//! A frame runs from the destination MAC to the FCS, is at least 64 B and
|
|
4
|
+
//! carries an 802.1Q tag with a priority above 0. On the wire every frame is
|
|
5
|
+
//! preceded by a preamble and SFD (8 B) and followed by an inter-frame gap
|
|
6
|
+
//! (12 B).
|
|
7
|
+
|
|
8
|
+
pub const VLAN_TAG_BYTES: u64 = 4;
|
|
9
|
+
pub const PREAMBLE_SFD_BYTES: u64 = 8;
|
|
10
|
+
pub const IFG_BYTES: u64 = 12;
|
|
11
|
+
pub const WIRE_OVERHEAD_BYTES: u64 = PREAMBLE_SFD_BYTES + IFG_BYTES;
|
|
12
|
+
|
|
13
|
+
/// A flow frame of `size` bytes (untagged, FCS included), with the tag a
|
|
14
|
+
/// priority above 0 needs.
|
|
15
|
+
pub fn flow_frame_size(size: u64, priority: u8) -> u64 {
|
|
16
|
+
size + if priority > 0 { VLAN_TAG_BYTES } else { 0 }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[cfg(test)]
|
|
20
|
+
mod tests {
|
|
21
|
+
use super::*;
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn flow_frames() {
|
|
25
|
+
assert_eq!(flow_frame_size(100, 0), 100);
|
|
26
|
+
assert_eq!(flow_frame_size(100, 3), 104);
|
|
27
|
+
assert_eq!(WIRE_OVERHEAD_BYTES, 20);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//! SMOLPy's native discrete-event engine (ADR-0008): a second implementation
|
|
2
|
+
//! of the semantics contract (docs/model-semantics.md), with no Python
|
|
3
|
+
//! dependency. `smolpy-engine-rs` exposes it to Python.
|
|
4
|
+
|
|
5
|
+
pub mod frames;
|
|
6
|
+
pub mod rng;
|
|
7
|
+
pub mod scenario;
|
|
8
|
+
pub mod sim;
|
|
9
|
+
pub mod timebase;
|
|
10
|
+
|
|
11
|
+
pub use scenario::CAPABILITIES;
|
|
12
|
+
pub use sim::Outcome;
|
|
13
|
+
|
|
14
|
+
/// Read a Scenario IR JSON document and run it.
|
|
15
|
+
pub fn run_json(text: &str) -> Result<Outcome, String> {
|
|
16
|
+
let scenario = scenario::parse(text)?;
|
|
17
|
+
sim::run(&scenario)
|
|
18
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
//! Random streams (Model Semantics §6) and the distributions of the traffic
|
|
2
|
+
//! patterns.
|
|
3
|
+
//!
|
|
4
|
+
//! - **Streams.** Every traffic statement has a stable source id,
|
|
5
|
+
//! `"<kind>:<from>-><to>#<k>"`, and its generator is seeded from the first
|
|
6
|
+
//! 8 bytes, big-endian, of SHA-256 of `"<run seed>:<source id>"`, exactly
|
|
7
|
+
//! as `smolpy.model.streams`.
|
|
8
|
+
//! - **Generator.** The engine's own: xoshiro256** seeded through SplitMix64.
|
|
9
|
+
//! Its draws differ from SimPy's `random.Random`, so stochastic scenarios
|
|
10
|
+
//! are compared statistically (ADR-0009).
|
|
11
|
+
//! - **Distributions** use the formulas of Python's `random` module, so the
|
|
12
|
+
//! two engines draw from the same distributions.
|
|
13
|
+
|
|
14
|
+
use sha2::{Digest, Sha256};
|
|
15
|
+
|
|
16
|
+
/// The stream seed of a traffic statement.
|
|
17
|
+
pub fn stream_seed(run_seed: u64, source_id: &str) -> u64 {
|
|
18
|
+
let digest = Sha256::digest(format!("{run_seed}:{source_id}").as_bytes());
|
|
19
|
+
u64::from_be_bytes(digest[..8].try_into().unwrap())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// xoshiro256** (Blackman and Vigna), seeded through SplitMix64.
|
|
23
|
+
pub struct Rng {
|
|
24
|
+
s: [u64; 4],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl Rng {
|
|
28
|
+
pub fn new(seed: u64) -> Rng {
|
|
29
|
+
let mut x = seed;
|
|
30
|
+
let mut next = || {
|
|
31
|
+
x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
|
32
|
+
let mut z = x;
|
|
33
|
+
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
|
34
|
+
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
|
35
|
+
z ^ (z >> 31)
|
|
36
|
+
};
|
|
37
|
+
Rng {
|
|
38
|
+
s: [next(), next(), next(), next()],
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn next_u64(&mut self) -> u64 {
|
|
43
|
+
let s = &mut self.s;
|
|
44
|
+
let result = s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
|
|
45
|
+
let t = s[1] << 17;
|
|
46
|
+
s[2] ^= s[0];
|
|
47
|
+
s[3] ^= s[1];
|
|
48
|
+
s[1] ^= s[2];
|
|
49
|
+
s[0] ^= s[3];
|
|
50
|
+
s[2] ^= t;
|
|
51
|
+
s[3] = s[3].rotate_left(45);
|
|
52
|
+
result
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Uniform on [0, 1), with 53 random bits as `random.random()`.
|
|
56
|
+
pub fn random(&mut self) -> f64 {
|
|
57
|
+
(self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// `random.expovariate(lambd)`: `-log(1 - random()) / lambd`.
|
|
61
|
+
pub fn expovariate(&mut self, lambd: f64) -> f64 {
|
|
62
|
+
-(1.0 - self.random()).ln() / lambd
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// `random.paretovariate(alpha)`: `(1 - random()) ** (-1 / alpha)`.
|
|
66
|
+
pub fn paretovariate(&mut self, alpha: f64) -> f64 {
|
|
67
|
+
(1.0 - self.random()).powf(-1.0 / alpha)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// `random.uniform(a, b)`: `a + (b - a) * random()`.
|
|
71
|
+
pub fn uniform(&mut self, a: f64, b: f64) -> f64 {
|
|
72
|
+
a + (b - a) * self.random()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Group sizes of the `batch` pattern, as `smolpy.core.batch.BatchSpec`.
|
|
77
|
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
78
|
+
pub enum Batch {
|
|
79
|
+
Fixed(u64),
|
|
80
|
+
Geom(f64),
|
|
81
|
+
Pareto(f64),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl Batch {
|
|
85
|
+
/// E[X], with the same zeta approximation as the reference engine.
|
|
86
|
+
pub fn mean(self) -> f64 {
|
|
87
|
+
match self {
|
|
88
|
+
Batch::Fixed(k) => k as f64,
|
|
89
|
+
Batch::Geom(m) => m,
|
|
90
|
+
Batch::Pareto(a) => zeta(a),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pub fn draw(self, rng: &mut Rng) -> u64 {
|
|
95
|
+
match self {
|
|
96
|
+
Batch::Fixed(k) => k,
|
|
97
|
+
Batch::Geom(m) => {
|
|
98
|
+
let p = 1.0 / m;
|
|
99
|
+
if p >= 1.0 {
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
// inverse transform for the geometric distribution on {1, 2, ...}
|
|
103
|
+
let u = 1.0 - rng.random(); // in (0, 1]
|
|
104
|
+
1 + (u.ln() / (1.0 - p).ln()) as u64
|
|
105
|
+
}
|
|
106
|
+
Batch::Pareto(a) => (rng.paretovariate(a) as u64).max(1),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Riemann zeta(s) for s > 1: direct sum plus an Euler-Maclaurin tail, as
|
|
112
|
+
/// `smolpy.core.batch._zeta` (whose sum is exact; this one is compensated).
|
|
113
|
+
pub fn zeta(s: f64) -> f64 {
|
|
114
|
+
let terms = 10_000;
|
|
115
|
+
let n = terms as f64;
|
|
116
|
+
let (mut sum, mut c) = (0.0f64, 0.0f64);
|
|
117
|
+
for k in 1..terms {
|
|
118
|
+
let y = (k as f64).powf(-s) - c;
|
|
119
|
+
let t = sum + y;
|
|
120
|
+
c = (t - sum) - y;
|
|
121
|
+
sum = t;
|
|
122
|
+
}
|
|
123
|
+
let tail = n.powf(1.0 - s) / (s - 1.0) + 0.5 * n.powf(-s) + s * n.powf(-s - 1.0) / 12.0;
|
|
124
|
+
sum + tail
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[cfg(test)]
|
|
128
|
+
mod tests {
|
|
129
|
+
use super::*;
|
|
130
|
+
|
|
131
|
+
#[test]
|
|
132
|
+
fn stream_seed_is_a_sha256_prefix() {
|
|
133
|
+
// the value of Model Semantics §6 and tests/test_semantics.py
|
|
134
|
+
assert_eq!(stream_seed(42, "flow:a->b#0"), 5_297_917_812_165_586_412);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[test]
|
|
138
|
+
fn a_seed_gives_the_same_draws() {
|
|
139
|
+
let (mut a, mut b) = (Rng::new(7), Rng::new(7));
|
|
140
|
+
for _ in 0..1000 {
|
|
141
|
+
assert_eq!(a.random().to_bits(), b.random().to_bits());
|
|
142
|
+
}
|
|
143
|
+
assert_ne!(Rng::new(7).random(), Rng::new(8).random());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn mean(n: usize, mut f: impl FnMut() -> f64) -> f64 {
|
|
147
|
+
(0..n).map(|_| f()).sum::<f64>() / n as f64
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[test]
|
|
151
|
+
fn distributions_have_their_means() {
|
|
152
|
+
let mut r = Rng::new(1);
|
|
153
|
+
let n = 400_000;
|
|
154
|
+
let u = mean(n, || r.random());
|
|
155
|
+
assert!((u - 0.5).abs() < 0.003, "{u}");
|
|
156
|
+
assert!((0..n).all(|_| (0.0..1.0).contains(&r.random())));
|
|
157
|
+
let e = mean(n, || r.expovariate(1.0 / 20.0));
|
|
158
|
+
assert!((e / 20.0 - 1.0).abs() < 0.01, "{e}");
|
|
159
|
+
let p = mean(n, || r.paretovariate(3.0)); // mean a / (a - 1) = 1.5
|
|
160
|
+
assert!((p / 1.5 - 1.0).abs() < 0.01, "{p}");
|
|
161
|
+
let un = mean(n, || r.uniform(0.3, 1.2));
|
|
162
|
+
assert!((un / 0.75 - 1.0).abs() < 0.005, "{un}");
|
|
163
|
+
for batch in [Batch::Fixed(8), Batch::Geom(4.0), Batch::Pareto(2.5)] {
|
|
164
|
+
let m = mean(n, || batch.draw(&mut r) as f64);
|
|
165
|
+
assert!((m / batch.mean() - 1.0).abs() < 0.02, "{batch:?}: {m}");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn zeta_values() {
|
|
171
|
+
assert!((zeta(2.0) - std::f64::consts::PI.powi(2) / 6.0).abs() < 1e-12);
|
|
172
|
+
assert!((zeta(2.5) - 1.341_487_257_250_917).abs() < 1e-12);
|
|
173
|
+
}
|
|
174
|
+
}
|