simweave 0.4.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 (56) hide show
  1. simweave-0.4.0/MANIFEST.in +51 -0
  2. simweave-0.4.0/PKG-INFO +304 -0
  3. simweave-0.4.0/README.md +255 -0
  4. simweave-0.4.0/pyproject.toml +71 -0
  5. simweave-0.4.0/requirements-dev.txt +7 -0
  6. simweave-0.4.0/requirements.txt +3 -0
  7. simweave-0.4.0/setup.cfg +4 -0
  8. simweave-0.4.0/src/simweave/__init__.py +198 -0
  9. simweave-0.4.0/src/simweave/agents/__init__.py +23 -0
  10. simweave-0.4.0/src/simweave/agents/agent.py +161 -0
  11. simweave-0.4.0/src/simweave/agents/compass.py +48 -0
  12. simweave-0.4.0/src/simweave/agents/routing.py +113 -0
  13. simweave-0.4.0/src/simweave/continuous/__init__.py +31 -0
  14. simweave-0.4.0/src/simweave/continuous/solver.py +237 -0
  15. simweave-0.4.0/src/simweave/continuous/systems/__init__.py +14 -0
  16. simweave-0.4.0/src/simweave/continuous/systems/mass_spring_damper.py +43 -0
  17. simweave-0.4.0/src/simweave/continuous/systems/pendulum.py +49 -0
  18. simweave-0.4.0/src/simweave/continuous/systems/quarter_car.py +59 -0
  19. simweave-0.4.0/src/simweave/continuous/systems/rlc.py +44 -0
  20. simweave-0.4.0/src/simweave/continuous/systems/thermal.py +127 -0
  21. simweave-0.4.0/src/simweave/core/__init__.py +18 -0
  22. simweave-0.4.0/src/simweave/core/clock.py +64 -0
  23. simweave-0.4.0/src/simweave/core/entity.py +74 -0
  24. simweave-0.4.0/src/simweave/core/environment.py +121 -0
  25. simweave-0.4.0/src/simweave/core/logging.py +41 -0
  26. simweave-0.4.0/src/simweave/core/scheduler.py +85 -0
  27. simweave-0.4.0/src/simweave/currency/__init__.py +64 -0
  28. simweave-0.4.0/src/simweave/currency/codes.py +257 -0
  29. simweave-0.4.0/src/simweave/currency/format.py +92 -0
  30. simweave-0.4.0/src/simweave/currency/fx.py +126 -0
  31. simweave-0.4.0/src/simweave/currency/money.py +328 -0
  32. simweave-0.4.0/src/simweave/discrete/__init__.py +28 -0
  33. simweave-0.4.0/src/simweave/discrete/properties.py +113 -0
  34. simweave-0.4.0/src/simweave/discrete/queues.py +317 -0
  35. simweave-0.4.0/src/simweave/discrete/resources.py +89 -0
  36. simweave-0.4.0/src/simweave/discrete/services.py +228 -0
  37. simweave-0.4.0/src/simweave/mc/__init__.py +5 -0
  38. simweave-0.4.0/src/simweave/mc/runner.py +151 -0
  39. simweave-0.4.0/src/simweave/spatial/__init__.py +5 -0
  40. simweave-0.4.0/src/simweave/spatial/graph.py +147 -0
  41. simweave-0.4.0/src/simweave/supplychain/__init__.py +9 -0
  42. simweave-0.4.0/src/simweave/supplychain/inventory.py +77 -0
  43. simweave-0.4.0/src/simweave/supplychain/optimization.py +197 -0
  44. simweave-0.4.0/src/simweave/supplychain/warehouse.py +153 -0
  45. simweave-0.4.0/src/simweave/units/__init__.py +25 -0
  46. simweave-0.4.0/src/simweave/units/si.py +213 -0
  47. simweave-0.4.0/src/simweave/viz/__init__.py +77 -0
  48. simweave-0.4.0/src/simweave/viz/_plotly.py +35 -0
  49. simweave-0.4.0/src/simweave/viz/plots.py +618 -0
  50. simweave-0.4.0/src/simweave/viz/recorders.py +172 -0
  51. simweave-0.4.0/src/simweave/viz/themes.py +187 -0
  52. simweave-0.4.0/src/simweave.egg-info/PKG-INFO +304 -0
  53. simweave-0.4.0/src/simweave.egg-info/SOURCES.txt +54 -0
  54. simweave-0.4.0/src/simweave.egg-info/dependency_links.txt +1 -0
  55. simweave-0.4.0/src/simweave.egg-info/requires.txt +50 -0
  56. simweave-0.4.0/src/simweave.egg-info/top_level.txt +1 -0
@@ -0,0 +1,51 @@
1
+ # Controls what setuptools ships in the source distribution (sdist).
2
+ # The wheel is narrower still (only src/simweave/**/*.py) and governed by
3
+ # [tool.setuptools] in pyproject.toml.
4
+ #
5
+ # Rule of thumb: the sdist should contain everything needed to rebuild
6
+ # the wheel from a clean checkout, and NOTHING that's internal dev
7
+ # documentation, scratchpads, or editor/tool metadata.
8
+
9
+ # --- include the essentials ------------------------------------------------
10
+ include README.md
11
+ include LICENSE
12
+ include pyproject.toml
13
+ include requirements.txt
14
+ include requirements-dev.txt
15
+
16
+ # Package sources only; no tests, no demos in the sdist.
17
+ recursive-include src *.py
18
+
19
+ # --- exclude internal design / integration docs ---------------------------
20
+ # These are for contributors and for the EdgeWeave repo, not end users of
21
+ # the PyPI package.
22
+ exclude PACKAGING.md
23
+ exclude CURRENCY_DESIGN.md
24
+ exclude SIMWEAVE_API.md
25
+ exclude EdgeWeave.md
26
+ exclude VIZ_DESIGN.md
27
+
28
+ # --- exclude repo-level directories that don't belong in the sdist --------
29
+ prune .github
30
+ prune .claude
31
+ prune .idea
32
+ prune .vscode
33
+ prune archive
34
+ prune demos
35
+ prune docs
36
+ prune tests
37
+
38
+ # --- exclude common editor / tool metadata anywhere in the tree -----------
39
+ global-exclude .DS_Store
40
+ global-exclude Thumbs.db
41
+ global-exclude .claude*
42
+ global-exclude .cursor*
43
+ global-exclude CLAUDE.md
44
+ global-exclude .ruff_cache
45
+ global-exclude .mypy_cache
46
+ global-exclude .pytest_cache
47
+ global-exclude __pycache__
48
+ global-exclude *.py[co]
49
+ global-exclude *.log
50
+ global-exclude *.swp
51
+ global-exclude *~
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.4
2
+ Name: simweave
3
+ Version: 0.4.0
4
+ Summary: SimWeave: a hybrid discrete/continuous simulation engine with currency, units, agents, supply chains, and Monte Carlo.
5
+ Author: Stuart
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: numpy>=1.23
10
+ Provides-Extra: optim
11
+ Requires-Dist: scipy>=1.10; extra == "optim"
12
+ Provides-Extra: plot
13
+ Requires-Dist: matplotlib>=3.7; extra == "plot"
14
+ Provides-Extra: viz
15
+ Requires-Dist: plotly>=5.18; extra == "viz"
16
+ Provides-Extra: graph
17
+ Requires-Dist: networkx>=3.0; extra == "graph"
18
+ Provides-Extra: geo
19
+ Requires-Dist: osmnx>=1.8; extra == "geo"
20
+ Requires-Dist: networkx>=3.0; extra == "geo"
21
+ Provides-Extra: fast
22
+ Requires-Dist: numba>=0.58; extra == "fast"
23
+ Provides-Extra: intl
24
+ Requires-Dist: babel>=2.14; extra == "intl"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == "dev"
27
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
28
+ Requires-Dist: scipy>=1.10; extra == "dev"
29
+ Requires-Dist: networkx>=3.0; extra == "dev"
30
+ Requires-Dist: babel>=2.14; extra == "dev"
31
+ Requires-Dist: plotly>=5.18; extra == "dev"
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
34
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
35
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == "docs"
36
+ Requires-Dist: mkdocs-include-markdown-plugin>=6.2; extra == "docs"
37
+ Requires-Dist: mkdocs-gen-files>=0.5; extra == "docs"
38
+ Requires-Dist: plotly>=5.18; extra == "docs"
39
+ Requires-Dist: networkx>=3.0; extra == "docs"
40
+ Requires-Dist: scipy>=1.10; extra == "docs"
41
+ Requires-Dist: babel>=2.14; extra == "docs"
42
+ Provides-Extra: all
43
+ Requires-Dist: scipy>=1.10; extra == "all"
44
+ Requires-Dist: matplotlib>=3.7; extra == "all"
45
+ Requires-Dist: plotly>=5.18; extra == "all"
46
+ Requires-Dist: networkx>=3.0; extra == "all"
47
+ Requires-Dist: numba>=0.58; extra == "all"
48
+ Requires-Dist: babel>=2.14; extra == "all"
49
+
50
+ # SimWeave — a hybrid discrete/continuous simulation engine
51
+ [![CI](https://github.com/Notabot123/simweave/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Notabot123/simweave/actions/workflows/ci.yml)
52
+
53
+ `simweave` (PyPI: [`simweave`](https://pypi.org/project/simweave/)) is a
54
+ lightweight Python simulation library built around an **atomic-clock
55
+ core** with an **optional heap-based event queue** layered on top. It is
56
+ designed for Monte Carlo studies where reproducibility and vectorised
57
+ aggregation across replicates matter as much as per-event precision.
58
+
59
+ Sister project: [EdgeWeave](https://github.com/) — desktop frontend that
60
+ generates and runs SimWeave scenarios.
61
+
62
+ ## Design philosophy
63
+
64
+ ### 1. Atomic clock, with an event queue when you need it
65
+
66
+ The heart of the engine is `simweave.core.environment.SimEnvironment`. It
67
+ owns a `Clock` (fixed `dt`), an `EventQueue` (heapq) for sparse future
68
+ callbacks, and a list of `Process` objects ticked every step.
69
+
70
+ ```
71
+ for t in clock:
72
+ event_queue.fire_due(t, env) # scheduler-driven work
73
+ for proc in processes:
74
+ proc.tick(dt, env) # atomic-clock-driven work
75
+ clock.advance(dt)
76
+ ```
77
+
78
+ Fixed-step ticking gives Monte Carlo a shared time grid so `N` replicates
79
+ aggregate cleanly along a single axis. For scenarios where nothing
80
+ happens for long stretches — a factory idle overnight, an agent waiting
81
+ for a part — the environment supports `skip_idle_gaps=True` which
82
+ fast-forwards to the next scheduled event when every process reports
83
+ `has_work(env) == False`. That gives DEVS-like efficiency when demand is
84
+ sparse without losing the atomic time grid when it's not.
85
+
86
+ ### 2. Minimal dependencies
87
+
88
+ Only `numpy` is a hard dependency. Everything else is optional:
89
+
90
+ | Extra | Used for | Install |
91
+ |--------------------|----------------------------------------------------------|--------------------------------------|
92
+ | `simweave[optim]` | scipy — DE optimiser, Poisson CDF | `pip install simweave[optim]` |
93
+ | `simweave[graph]` | networkx — interop with external graphs | `pip install simweave[graph]` |
94
+ | `simweave[geo]` | osmnx — OSM map ingestion | `pip install simweave[geo]` |
95
+ | `simweave[plot]` | matplotlib — bring-your-own static plotting | `pip install simweave[plot]` |
96
+ | `simweave[viz]` | plotly — first-class viz module (`simweave.viz`) | `pip install simweave[viz]` |
97
+ | `simweave[intl]` | babel — locale-aware money formatting | `pip install simweave[intl]` |
98
+ | `simweave[fast]` | numba — opt-in JIT for MC inner loops | `pip install simweave[fast]` |
99
+ | `simweave[dev]` | pytest / mypy / plus the optional libs needed for tests | `pip install simweave[dev]` |
100
+ | `simweave[all]` | everything above | `pip install simweave[all]` |
101
+
102
+ `numpy` stays in core because vectorised inventory / Monte Carlo maths
103
+ benefits dramatically from it. Queues still use `collections.deque` for
104
+ O(1) push/pop at both ends.
105
+
106
+ ### 3. Modular, but not split into separate libraries
107
+
108
+ The submodules compose through the shared `Entity` base class:
109
+
110
+ ```
111
+ simweave/
112
+ core/ Clock, EventQueue, Entity, SimEnvironment
113
+ units/ SI exponent-tagged units (preserved from the original)
114
+ discrete/ Queue, PriorityQueue, Service, ArrivalGenerator, Resource, ResourcePool
115
+ continuous/ DynamicSystem, RK4/Euler, ContinuousProcess (hybrid)
116
+ spatial/ Graph + A*-friendly adj_view adapter, grid_graph helper
117
+ agents/ Compass, routing.a_star, Agent (Entity subclass)
118
+ supplychain/ InventoryItems, Warehouse, steady-state optimisation
119
+ mc/ run_monte_carlo, run_batched_mc
120
+ ```
121
+
122
+ A hybrid sim that moves a pallet (Agent) through a service line (Service
123
+ chain) while a mass-spring-damper physics model logs vibration under
124
+ load is completely natural in one package; forcing the user to import
125
+ three separate libraries to do it would be the wrong trade.
126
+
127
+ ## Installation
128
+
129
+ ```bash
130
+ git clone https://github.com/Notabot123/simweave
131
+ cd simweave
132
+ python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate on Windows
133
+ pip install -e .[dev] # editable install with test deps
134
+ pytest # should show green
135
+ ```
136
+
137
+ ## Worked examples
138
+
139
+ Sixteen runnable scripts live under [`demos/`](demos/):
140
+
141
+ **Discrete-event / agents / Monte Carlo**
142
+ 1. `01_simple_queue.py` — M/M/1 queue, Little's law diagnostics
143
+ 2. `02_chained_services.py` — three-stage production line with blocking
144
+ 3. `03_resource_pool.py` — bounded concurrency via a physician pool
145
+ 4. `04_supply_chain_basic.py` — two-echelon SKU inventory with daily demand
146
+ 5. `05_supply_chain_optimise.py` — Poisson + DE reorder-point optimisation
147
+ 6. `06_agent_astar.py` — two agents A*-routing on a grid
148
+ 7. `07_monte_carlo.py` — serial vs threads vs processes vs batched numpy
149
+ 8. `08_hybrid_continuous_discrete.py` — queue coupled to an MSD
150
+
151
+ **Continuous dynamical systems** (added for teaching use)
152
+
153
+ 9. `09_mass_spring_damper.py` — under-, critical-, over-damped responses
154
+ 10. `10_quarter_car.py` — 2-DOF suspension over a speed bump, three damper setups
155
+ 11. `11_series_rlc.py` — RLC step and resonant drive, reports ω₀, ζ, Q
156
+ 12. `12_thermal_system.py` — single-body RC thermal + CPU/heatsink two-mass model
157
+
158
+ **Currency, visualisation, units, optimisation**
159
+
160
+ 13. `13_money_cashflow.py` — `Money`, FX conversion, locale-aware formatting
161
+ 14. `14_viz_tour.py` — every `simweave.viz` plot helper end-to-end, with HTML index
162
+ 15. `15_units_dimensional.py` — SI-units algebra (`Distance / TimeUnit → Velocity`)
163
+ 16. `16_inventory_optimisation.py` — recover demand rate from a sim, then optimise reorder points
164
+
165
+ Run any of them directly, e.g. `python demos/01_simple_queue.py` from
166
+ the repo root. The two-digit prefix is just an ordering hint; the
167
+ scripts have no dependency on each other. A small `demos/_bootstrap.py`
168
+ shim adds `src/` to `sys.path` if the package isn't installed; with
169
+ `pip install -e .` in place, the shim becomes a no-op.
170
+
171
+ ## Monte Carlo performance: picking the right strategy
172
+
173
+ Monte Carlo scaling on a modern laptop looks like this, ordered by
174
+ expected speedup on compute-bound simulations:
175
+
176
+ | Strategy | When to reach for it | Typical speedup vs serial |
177
+ |--------------------------------|---------------------------------------------------------------------|----------------------------|
178
+ | `run_batched_mc` (numpy) | You can express the full population as arrays on a shared time grid | 10–100× on vector ops |
179
+ | `executor="processes"` | Replicates are independent, GIL-bound compute | ~N-cores× |
180
+ | `numba` JIT on hot loops | Handful of inner functions dominate profile | 3–30× on those functions |
181
+ | Cython (or C extension) | Truly maxing out a known-stable hot path | 2–5× over numba |
182
+ | `executor="threads"` | I/O-bound scenarios (DB hits, REST calls between replicates) | N-workers× |
183
+ | Python 3.13 free-threading | As above, but for compute, once libs stabilise | N-cores× |
184
+
185
+ ### My recommended ordering
186
+
187
+ 1. **Profile first.** `python -m cProfile demo.py` usually reveals that
188
+ 90% of time is in one or two functions. Optimise those, not the
189
+ scheduler.
190
+ 2. **Vectorise with numpy where possible.** `run_batched_mc` takes a
191
+ `(n_runs, ...)` step function. Any MC where the per-replicate state
192
+ is small and the dynamics are linear-algebraic is a win here. The
193
+ coin-flip toy in `demos/07_monte_carlo.py` runs 10,000 replicates in
194
+ milliseconds this way.
195
+ 3. **Then reach for `processes`.** `ProcessPoolExecutor` sidesteps the
196
+ GIL without you writing any parallel code. The pickling overhead is
197
+ negligible when each replicate runs for ≥ a second. This is the best
198
+ default for a "complicated simulation" that can't be vectorised.
199
+ 4. **Then numba.** `@njit` on the inner tick loop often adds 3–30× on
200
+ the hot path. Numba plays nicely with numpy arrays and compiles on
201
+ first call. Prefer it to Cython unless you already have a Cython
202
+ toolchain and need C-level interop — Cython's compile-and-link dance
203
+ is overkill for 95% of cases.
204
+ 5. **Cython only as a last resort.** If you're building a PyPI wheel
205
+ and every ms counts, Cython can squeeze more out. In practice you
206
+ then have to ship platform-specific wheels, which doubles the CI
207
+ pipeline. Numba's JIT-at-import sidesteps that.
208
+ 6. **Threads for IO.** The moment your replicate talks to a database,
209
+ an HTTP endpoint, or the filesystem, `executor="threads"` wins
210
+ because while one thread waits on a syscall another runs. On the
211
+ 3.13 free-threading build threads will also help compute; treat it
212
+ as a near-term option rather than a today option.
213
+
214
+ ### Seeding and reproducibility
215
+
216
+ Every Monte Carlo run receives a seed. `run_monte_carlo` materialises
217
+ `list(seeds or range(n_runs))` up front and stamps each replicate with
218
+ its own `np.random.Generator`, so results are deterministic per seed.
219
+ That determinism holds even when replicates are farmed across processes
220
+ — every worker sees the same integer seed.
221
+
222
+ ## Packaging, CI, and releases
223
+
224
+ Continuous integration runs on every push and PR — see `.github/workflows/ci.yml`.
225
+ It matrixes `pytest --cov=simweave` across Python 3.10–3.13 on ubuntu / macos /
226
+ windows, runs `ruff` and `mypy`, and builds both `sdist` and `wheel` with
227
+ `python -m build`. Artefacts are uploaded on every green build and can be
228
+ downloaded from the Actions UI for local testing.
229
+
230
+ `release.yml` publishes on tag push:
231
+
232
+ - Tags matching a stable semver shape (`v0.1.0`, `v0.2.0`, `v1.0.0`) →
233
+ **PyPI**.
234
+ - Tags with a pre-release suffix (`v0.1.0rc1`, `v0.1.0a2`, `v0.1.0-beta`) →
235
+ **TestPyPI**.
236
+ - Both use PyPA's trusted publishing (OIDC). No API tokens in GitHub
237
+ secrets — configure once per project on pypi.org / test.pypi.org.
238
+
239
+ See [`PACKAGING.md`](PACKAGING.md) for the full release checklist and
240
+ recommended strategy (pure-Python wheels today; numba for hot paths
241
+ before Cython).
242
+
243
+ ## Public API reference
244
+
245
+ [`SIMWEAVE_API.md`](SIMWEAVE_API.md) is a drop-in reference suitable for
246
+ copying into a consuming app's repo (e.g. EdgeWeave). It documents the
247
+ top-level imports, the continuous-dynamics protocol for plug-in systems,
248
+ and a code-generation template to follow.
249
+
250
+ ## Integration with EdgeWeave
251
+
252
+ [`EdgeWeave.md`](EdgeWeave.md) describes the flow-based low-code app
253
+ that `simweave` is designed to feed. The boundary between the two is
254
+ intentionally thin:
255
+
256
+ - **simweave → EdgeWeave as `@node`s.** Any `simweave` entity with a
257
+ `tick(dt, env)` signature is trivially wrappable as an EdgeWeave node.
258
+ An `ArrivalGenerator` becomes a "Source" node; a `Service` becomes a
259
+ "Process" node; a `Queue` becomes a "Buffer" node. Inputs and outputs
260
+ flow as entity references along drawflow wires.
261
+ - **simweave stays headless.** No GUI, no fastapi dependency. EdgeWeave
262
+ imports `simweave` and constructs the environment; `simweave` never
263
+ imports EdgeWeave. This keeps `simweave` useful as a standalone library
264
+ and as a published pypi package.
265
+ - **Performance path.** If EdgeWeave wants to JIT hot inner loops,
266
+ enable `simweave[fast]` and let numba do the work — the simulation
267
+ graph stays pure Python while the tight numeric kernels compile
268
+ down. Cython only enters the picture if the EdgeWeave team decides to
269
+ ship prebuilt C extensions for the inner loops, which would be a
270
+ follow-on optimisation rather than a day-one requirement.
271
+ - **Serialisation.** Every entity already lives in a dataclass or has
272
+ a short `__init__` signature, so a `to_dict()` / `from_dict()`
273
+ round-trip is a small follow-up when EdgeWeave needs to serialise a
274
+ flow graph.
275
+
276
+ ## Project layout
277
+
278
+ ```
279
+ sim_engine/
280
+ src/simweave/ # the package (src layout)
281
+ tests/ # pytest suite (83 tests, scipy-dependent ones skip gracefully)
282
+ demos/ # worked examples (see above)
283
+ archive/ # legacy code kept for reference, safe to delete later
284
+ pyproject.toml
285
+ requirements.txt
286
+ requirements-dev.txt
287
+ EdgeWeave.md # integration notes for the flow-based front-end
288
+ README.md
289
+ ```
290
+
291
+ ## Next steps on the roadmap
292
+
293
+ - **True sim-based optimisation.** `cost_optimise_stock_sim` already
294
+ wraps a user callback; a future step is an adaptive surrogate model
295
+ (kriging / BO) so each evaluation doesn't need a full MC sweep.
296
+ - **DEVS-style segment.** When `skip_idle_gaps=True` covers most of the
297
+ gains of a pure event-queue system, a thin DEVS-style adapter on top
298
+ of the event queue would give users who prefer generator-style code
299
+ (à la SimPy) an entry point without forking the kernel.
300
+ - **Graph-agnostic spatial layer.** `adj_view` already handles simweave
301
+ Graph, dict-of-dict, and networkx; next is an osmnx adapter so
302
+ `simweave[geo]` lets agents route on real road networks without
303
+ baking osmnx into the core.
304
+ - **Numba hot paths.** The A* inner loop and the warehouse vecto
@@ -0,0 +1,255 @@
1
+ # SimWeave — a hybrid discrete/continuous simulation engine
2
+ [![CI](https://github.com/Notabot123/simweave/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Notabot123/simweave/actions/workflows/ci.yml)
3
+
4
+ `simweave` (PyPI: [`simweave`](https://pypi.org/project/simweave/)) is a
5
+ lightweight Python simulation library built around an **atomic-clock
6
+ core** with an **optional heap-based event queue** layered on top. It is
7
+ designed for Monte Carlo studies where reproducibility and vectorised
8
+ aggregation across replicates matter as much as per-event precision.
9
+
10
+ Sister project: [EdgeWeave](https://github.com/) — desktop frontend that
11
+ generates and runs SimWeave scenarios.
12
+
13
+ ## Design philosophy
14
+
15
+ ### 1. Atomic clock, with an event queue when you need it
16
+
17
+ The heart of the engine is `simweave.core.environment.SimEnvironment`. It
18
+ owns a `Clock` (fixed `dt`), an `EventQueue` (heapq) for sparse future
19
+ callbacks, and a list of `Process` objects ticked every step.
20
+
21
+ ```
22
+ for t in clock:
23
+ event_queue.fire_due(t, env) # scheduler-driven work
24
+ for proc in processes:
25
+ proc.tick(dt, env) # atomic-clock-driven work
26
+ clock.advance(dt)
27
+ ```
28
+
29
+ Fixed-step ticking gives Monte Carlo a shared time grid so `N` replicates
30
+ aggregate cleanly along a single axis. For scenarios where nothing
31
+ happens for long stretches — a factory idle overnight, an agent waiting
32
+ for a part — the environment supports `skip_idle_gaps=True` which
33
+ fast-forwards to the next scheduled event when every process reports
34
+ `has_work(env) == False`. That gives DEVS-like efficiency when demand is
35
+ sparse without losing the atomic time grid when it's not.
36
+
37
+ ### 2. Minimal dependencies
38
+
39
+ Only `numpy` is a hard dependency. Everything else is optional:
40
+
41
+ | Extra | Used for | Install |
42
+ |--------------------|----------------------------------------------------------|--------------------------------------|
43
+ | `simweave[optim]` | scipy — DE optimiser, Poisson CDF | `pip install simweave[optim]` |
44
+ | `simweave[graph]` | networkx — interop with external graphs | `pip install simweave[graph]` |
45
+ | `simweave[geo]` | osmnx — OSM map ingestion | `pip install simweave[geo]` |
46
+ | `simweave[plot]` | matplotlib — bring-your-own static plotting | `pip install simweave[plot]` |
47
+ | `simweave[viz]` | plotly — first-class viz module (`simweave.viz`) | `pip install simweave[viz]` |
48
+ | `simweave[intl]` | babel — locale-aware money formatting | `pip install simweave[intl]` |
49
+ | `simweave[fast]` | numba — opt-in JIT for MC inner loops | `pip install simweave[fast]` |
50
+ | `simweave[dev]` | pytest / mypy / plus the optional libs needed for tests | `pip install simweave[dev]` |
51
+ | `simweave[all]` | everything above | `pip install simweave[all]` |
52
+
53
+ `numpy` stays in core because vectorised inventory / Monte Carlo maths
54
+ benefits dramatically from it. Queues still use `collections.deque` for
55
+ O(1) push/pop at both ends.
56
+
57
+ ### 3. Modular, but not split into separate libraries
58
+
59
+ The submodules compose through the shared `Entity` base class:
60
+
61
+ ```
62
+ simweave/
63
+ core/ Clock, EventQueue, Entity, SimEnvironment
64
+ units/ SI exponent-tagged units (preserved from the original)
65
+ discrete/ Queue, PriorityQueue, Service, ArrivalGenerator, Resource, ResourcePool
66
+ continuous/ DynamicSystem, RK4/Euler, ContinuousProcess (hybrid)
67
+ spatial/ Graph + A*-friendly adj_view adapter, grid_graph helper
68
+ agents/ Compass, routing.a_star, Agent (Entity subclass)
69
+ supplychain/ InventoryItems, Warehouse, steady-state optimisation
70
+ mc/ run_monte_carlo, run_batched_mc
71
+ ```
72
+
73
+ A hybrid sim that moves a pallet (Agent) through a service line (Service
74
+ chain) while a mass-spring-damper physics model logs vibration under
75
+ load is completely natural in one package; forcing the user to import
76
+ three separate libraries to do it would be the wrong trade.
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ git clone https://github.com/Notabot123/simweave
82
+ cd simweave
83
+ python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate on Windows
84
+ pip install -e .[dev] # editable install with test deps
85
+ pytest # should show green
86
+ ```
87
+
88
+ ## Worked examples
89
+
90
+ Sixteen runnable scripts live under [`demos/`](demos/):
91
+
92
+ **Discrete-event / agents / Monte Carlo**
93
+ 1. `01_simple_queue.py` — M/M/1 queue, Little's law diagnostics
94
+ 2. `02_chained_services.py` — three-stage production line with blocking
95
+ 3. `03_resource_pool.py` — bounded concurrency via a physician pool
96
+ 4. `04_supply_chain_basic.py` — two-echelon SKU inventory with daily demand
97
+ 5. `05_supply_chain_optimise.py` — Poisson + DE reorder-point optimisation
98
+ 6. `06_agent_astar.py` — two agents A*-routing on a grid
99
+ 7. `07_monte_carlo.py` — serial vs threads vs processes vs batched numpy
100
+ 8. `08_hybrid_continuous_discrete.py` — queue coupled to an MSD
101
+
102
+ **Continuous dynamical systems** (added for teaching use)
103
+
104
+ 9. `09_mass_spring_damper.py` — under-, critical-, over-damped responses
105
+ 10. `10_quarter_car.py` — 2-DOF suspension over a speed bump, three damper setups
106
+ 11. `11_series_rlc.py` — RLC step and resonant drive, reports ω₀, ζ, Q
107
+ 12. `12_thermal_system.py` — single-body RC thermal + CPU/heatsink two-mass model
108
+
109
+ **Currency, visualisation, units, optimisation**
110
+
111
+ 13. `13_money_cashflow.py` — `Money`, FX conversion, locale-aware formatting
112
+ 14. `14_viz_tour.py` — every `simweave.viz` plot helper end-to-end, with HTML index
113
+ 15. `15_units_dimensional.py` — SI-units algebra (`Distance / TimeUnit → Velocity`)
114
+ 16. `16_inventory_optimisation.py` — recover demand rate from a sim, then optimise reorder points
115
+
116
+ Run any of them directly, e.g. `python demos/01_simple_queue.py` from
117
+ the repo root. The two-digit prefix is just an ordering hint; the
118
+ scripts have no dependency on each other. A small `demos/_bootstrap.py`
119
+ shim adds `src/` to `sys.path` if the package isn't installed; with
120
+ `pip install -e .` in place, the shim becomes a no-op.
121
+
122
+ ## Monte Carlo performance: picking the right strategy
123
+
124
+ Monte Carlo scaling on a modern laptop looks like this, ordered by
125
+ expected speedup on compute-bound simulations:
126
+
127
+ | Strategy | When to reach for it | Typical speedup vs serial |
128
+ |--------------------------------|---------------------------------------------------------------------|----------------------------|
129
+ | `run_batched_mc` (numpy) | You can express the full population as arrays on a shared time grid | 10–100× on vector ops |
130
+ | `executor="processes"` | Replicates are independent, GIL-bound compute | ~N-cores× |
131
+ | `numba` JIT on hot loops | Handful of inner functions dominate profile | 3–30× on those functions |
132
+ | Cython (or C extension) | Truly maxing out a known-stable hot path | 2–5× over numba |
133
+ | `executor="threads"` | I/O-bound scenarios (DB hits, REST calls between replicates) | N-workers× |
134
+ | Python 3.13 free-threading | As above, but for compute, once libs stabilise | N-cores× |
135
+
136
+ ### My recommended ordering
137
+
138
+ 1. **Profile first.** `python -m cProfile demo.py` usually reveals that
139
+ 90% of time is in one or two functions. Optimise those, not the
140
+ scheduler.
141
+ 2. **Vectorise with numpy where possible.** `run_batched_mc` takes a
142
+ `(n_runs, ...)` step function. Any MC where the per-replicate state
143
+ is small and the dynamics are linear-algebraic is a win here. The
144
+ coin-flip toy in `demos/07_monte_carlo.py` runs 10,000 replicates in
145
+ milliseconds this way.
146
+ 3. **Then reach for `processes`.** `ProcessPoolExecutor` sidesteps the
147
+ GIL without you writing any parallel code. The pickling overhead is
148
+ negligible when each replicate runs for ≥ a second. This is the best
149
+ default for a "complicated simulation" that can't be vectorised.
150
+ 4. **Then numba.** `@njit` on the inner tick loop often adds 3–30× on
151
+ the hot path. Numba plays nicely with numpy arrays and compiles on
152
+ first call. Prefer it to Cython unless you already have a Cython
153
+ toolchain and need C-level interop — Cython's compile-and-link dance
154
+ is overkill for 95% of cases.
155
+ 5. **Cython only as a last resort.** If you're building a PyPI wheel
156
+ and every ms counts, Cython can squeeze more out. In practice you
157
+ then have to ship platform-specific wheels, which doubles the CI
158
+ pipeline. Numba's JIT-at-import sidesteps that.
159
+ 6. **Threads for IO.** The moment your replicate talks to a database,
160
+ an HTTP endpoint, or the filesystem, `executor="threads"` wins
161
+ because while one thread waits on a syscall another runs. On the
162
+ 3.13 free-threading build threads will also help compute; treat it
163
+ as a near-term option rather than a today option.
164
+
165
+ ### Seeding and reproducibility
166
+
167
+ Every Monte Carlo run receives a seed. `run_monte_carlo` materialises
168
+ `list(seeds or range(n_runs))` up front and stamps each replicate with
169
+ its own `np.random.Generator`, so results are deterministic per seed.
170
+ That determinism holds even when replicates are farmed across processes
171
+ — every worker sees the same integer seed.
172
+
173
+ ## Packaging, CI, and releases
174
+
175
+ Continuous integration runs on every push and PR — see `.github/workflows/ci.yml`.
176
+ It matrixes `pytest --cov=simweave` across Python 3.10–3.13 on ubuntu / macos /
177
+ windows, runs `ruff` and `mypy`, and builds both `sdist` and `wheel` with
178
+ `python -m build`. Artefacts are uploaded on every green build and can be
179
+ downloaded from the Actions UI for local testing.
180
+
181
+ `release.yml` publishes on tag push:
182
+
183
+ - Tags matching a stable semver shape (`v0.1.0`, `v0.2.0`, `v1.0.0`) →
184
+ **PyPI**.
185
+ - Tags with a pre-release suffix (`v0.1.0rc1`, `v0.1.0a2`, `v0.1.0-beta`) →
186
+ **TestPyPI**.
187
+ - Both use PyPA's trusted publishing (OIDC). No API tokens in GitHub
188
+ secrets — configure once per project on pypi.org / test.pypi.org.
189
+
190
+ See [`PACKAGING.md`](PACKAGING.md) for the full release checklist and
191
+ recommended strategy (pure-Python wheels today; numba for hot paths
192
+ before Cython).
193
+
194
+ ## Public API reference
195
+
196
+ [`SIMWEAVE_API.md`](SIMWEAVE_API.md) is a drop-in reference suitable for
197
+ copying into a consuming app's repo (e.g. EdgeWeave). It documents the
198
+ top-level imports, the continuous-dynamics protocol for plug-in systems,
199
+ and a code-generation template to follow.
200
+
201
+ ## Integration with EdgeWeave
202
+
203
+ [`EdgeWeave.md`](EdgeWeave.md) describes the flow-based low-code app
204
+ that `simweave` is designed to feed. The boundary between the two is
205
+ intentionally thin:
206
+
207
+ - **simweave → EdgeWeave as `@node`s.** Any `simweave` entity with a
208
+ `tick(dt, env)` signature is trivially wrappable as an EdgeWeave node.
209
+ An `ArrivalGenerator` becomes a "Source" node; a `Service` becomes a
210
+ "Process" node; a `Queue` becomes a "Buffer" node. Inputs and outputs
211
+ flow as entity references along drawflow wires.
212
+ - **simweave stays headless.** No GUI, no fastapi dependency. EdgeWeave
213
+ imports `simweave` and constructs the environment; `simweave` never
214
+ imports EdgeWeave. This keeps `simweave` useful as a standalone library
215
+ and as a published pypi package.
216
+ - **Performance path.** If EdgeWeave wants to JIT hot inner loops,
217
+ enable `simweave[fast]` and let numba do the work — the simulation
218
+ graph stays pure Python while the tight numeric kernels compile
219
+ down. Cython only enters the picture if the EdgeWeave team decides to
220
+ ship prebuilt C extensions for the inner loops, which would be a
221
+ follow-on optimisation rather than a day-one requirement.
222
+ - **Serialisation.** Every entity already lives in a dataclass or has
223
+ a short `__init__` signature, so a `to_dict()` / `from_dict()`
224
+ round-trip is a small follow-up when EdgeWeave needs to serialise a
225
+ flow graph.
226
+
227
+ ## Project layout
228
+
229
+ ```
230
+ sim_engine/
231
+ src/simweave/ # the package (src layout)
232
+ tests/ # pytest suite (83 tests, scipy-dependent ones skip gracefully)
233
+ demos/ # worked examples (see above)
234
+ archive/ # legacy code kept for reference, safe to delete later
235
+ pyproject.toml
236
+ requirements.txt
237
+ requirements-dev.txt
238
+ EdgeWeave.md # integration notes for the flow-based front-end
239
+ README.md
240
+ ```
241
+
242
+ ## Next steps on the roadmap
243
+
244
+ - **True sim-based optimisation.** `cost_optimise_stock_sim` already
245
+ wraps a user callback; a future step is an adaptive surrogate model
246
+ (kriging / BO) so each evaluation doesn't need a full MC sweep.
247
+ - **DEVS-style segment.** When `skip_idle_gaps=True` covers most of the
248
+ gains of a pure event-queue system, a thin DEVS-style adapter on top
249
+ of the event queue would give users who prefer generator-style code
250
+ (à la SimPy) an entry point without forking the kernel.
251
+ - **Graph-agnostic spatial layer.** `adj_view` already handles simweave
252
+ Graph, dict-of-dict, and networkx; next is an osmnx adapter so
253
+ `simweave[geo]` lets agents route on real road networks without
254
+ baking osmnx into the core.
255
+ - **Numba hot paths.** The A* inner loop and the warehouse vecto
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "simweave"
7
+ version = "0.4.0"
8
+ description = "SimWeave: a hybrid discrete/continuous simulation engine with currency, units, agents, supply chains, and Monte Carlo."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Stuart" }]
13
+ dependencies = [
14
+ "numpy>=1.23",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ optim = ["scipy>=1.10"]
19
+ plot = ["matplotlib>=3.7"]
20
+ viz = ["plotly>=5.18"]
21
+ graph = ["networkx>=3.0"]
22
+ geo = ["osmnx>=1.8", "networkx>=3.0"]
23
+ fast = ["numba>=0.58"]
24
+ intl = ["babel>=2.14"]
25
+ dev = [
26
+ "pytest>=8.0",
27
+ "pytest-cov>=4.1",
28
+ "scipy>=1.10",
29
+ "networkx>=3.0",
30
+ "babel>=2.14",
31
+ "plotly>=5.18",
32
+ ]
33
+ docs = [
34
+ "mkdocs>=1.6",
35
+ "mkdocs-material>=9.5",
36
+ "mkdocstrings[python]>=0.25",
37
+ "mkdocs-include-markdown-plugin>=6.2",
38
+ "mkdocs-gen-files>=0.5",
39
+ # Match the runtime deps mkdocstrings needs to actually import simweave
40
+ # so the auto-generated API pages render the full surface.
41
+ "plotly>=5.18",
42
+ "networkx>=3.0",
43
+ "scipy>=1.10",
44
+ "babel>=2.14",
45
+ ]
46
+ all = [
47
+ "scipy>=1.10",
48
+ "matplotlib>=3.7",
49
+ "plotly>=5.18",
50
+ "networkx>=3.0",
51
+ "numba>=0.58",
52
+ "babel>=2.14",
53
+ ]
54
+
55
+ [tool.setuptools]
56
+ # Keep the wheel lean: only Python sources under src/simweave go in.
57
+ # README/LICENSE/pyproject are included automatically as distribution metadata.
58
+ include-package-data = false
59
+
60
+ [tool.setuptools.packages.find]
61
+ where = ["src"]
62
+
63
+ [tool.setuptools.package-data]
64
+ # If we ever need to ship data files inside the package, list them here
65
+ # explicitly. Leaving empty keeps the wheel to *.py only.
66
+ simweave = []
67
+
68
+ [tool.pytest.ini_options]
69
+ testpaths = ["tests"]
70
+ pythonpath = ["src"]
71
+ addopts = "-q"