simulsi 0.2.0__tar.gz → 0.3.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.
- {simulsi-0.2.0 → simulsi-0.3.0}/.gitignore +1 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/CHANGELOG.md +34 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/PKG-INFO +23 -5
- {simulsi-0.2.0 → simulsi-0.3.0}/README.md +19 -4
- {simulsi-0.2.0 → simulsi-0.3.0}/benchmarks/README.md +33 -0
- simulsi-0.3.0/docs/api/analysis.md +6 -0
- simulsi-0.3.0/docs/api/decisions.md +5 -0
- simulsi-0.3.0/docs/api/entities.md +3 -0
- simulsi-0.3.0/docs/api/events.md +3 -0
- simulsi-0.3.0/docs/api/experiments.md +7 -0
- simulsi-0.3.0/docs/api/io.md +5 -0
- simulsi-0.3.0/docs/api/metrics.md +3 -0
- simulsi-0.3.0/docs/api/models.md +7 -0
- simulsi-0.3.0/docs/api/processes.md +11 -0
- simulsi-0.3.0/docs/api/randomness.md +7 -0
- simulsi-0.3.0/docs/api/resources.md +9 -0
- simulsi-0.3.0/docs/api/simulation.md +9 -0
- simulsi-0.3.0/docs/api/statistics.md +3 -0
- simulsi-0.3.0/docs/api/validation.md +5 -0
- simulsi-0.3.0/docs/api/visualization.md +3 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/architecture.md +2 -2
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/cli.md +15 -3
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/concepts.md +170 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/examples.md +8 -8
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/experiments.md +72 -5
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/faq.md +1 -1
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/getting-started.md +1 -2
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/index.md +23 -4
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/research.md +7 -4
- {simulsi-0.2.0 → simulsi-0.3.0}/pyproject.toml +1 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/__init__.py +2 -1
- simulsi-0.3.0/src/simulsi/_version.py +1 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/analysis/__init__.py +5 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/analysis/comparison.py +70 -32
- simulsi-0.3.0/src/simulsi/analysis/warmup.py +162 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/benchmarks.py +8 -1
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/cli/main.py +139 -6
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/config/schema.py +4 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/core/model.py +32 -7
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/core/simulation.py +71 -5
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/experiments/__init__.py +2 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/experiments/experiment.py +169 -15
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/introspection/graph.py +1 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/metrics/collectors.py +1 -1
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/processes/__init__.py +15 -0
- simulsi-0.3.0/src/simulsi/processes/flow.py +107 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/processes/process.py +12 -3
- simulsi-0.3.0/src/simulsi/processes/schedules.py +227 -0
- simulsi-0.3.0/src/simulsi/processes/trace.py +118 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/queues/discipline.py +7 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/queues/queue.py +39 -13
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/randomness/__init__.py +7 -1
- simulsi-0.3.0/src/simulsi/randomness/fitting.py +337 -0
- simulsi-0.3.0/src/simulsi/randomness/inverse.py +100 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/randomness/stream.py +33 -7
- simulsi-0.3.0/src/simulsi/resources/__init__.py +4 -0
- simulsi-0.3.0/src/simulsi/resources/container.py +228 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/resources/resource.py +9 -6
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/scenarios/scenario.py +2 -2
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/statistics/__init__.py +8 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/statistics/core.py +127 -2
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/web/server.py +45 -5
- simulsi-0.3.0/src/simulsi/web/static/assets/index-DCyzlFeN.js +9 -0
- simulsi-0.3.0/src/simulsi/web/static/assets/index-quY_3V8w.css +2 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/web/static/index.html +2 -2
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/cli/test_cli.py +11 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/integration/test_experiments.py +70 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/integration/test_web_server.py +11 -2
- simulsi-0.3.0/tests/unit/test_fitting_and_traces.py +178 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_model.py +12 -0
- simulsi-0.3.0/tests/unit/test_modelling_primitives.py +382 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_statistics.py +14 -0
- simulsi-0.3.0/tests/unit/test_variance_reduction.py +113 -0
- simulsi-0.3.0/tests/unit/test_warmup.py +57 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/web/README.md +2 -1
- simulsi-0.2.0/src/simulsi/_version.py +0 -1
- simulsi-0.2.0/src/simulsi/resources/__init__.py +0 -3
- simulsi-0.2.0/src/simulsi/web/static/assets/index-CNMcVqxw.css +0 -2
- simulsi-0.2.0/src/simulsi/web/static/assets/index-DDtrYZla.js +0 -9
- {simulsi-0.2.0 → simulsi-0.3.0}/LICENSE +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/NOTICE +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/extending.md +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/docs/visualization.md +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/__main__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/analysis/report.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/analysis/sensitivity.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/cli/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/cli/templates.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/config/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/core/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/core/checkpoint.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/core/clock.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/core/trace.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/cost/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/cost/model.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/entities/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/entities/entity.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/errors.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/events/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/events/event.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/experiments/montecarlo.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/experiments/provenance.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/introspection/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/metrics/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/models/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/models/queueing.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/optimization/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/optimization/objective.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/processes/disruption.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/py.typed +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/queues/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/randomness/distributions.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/scenarios/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/serialization/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/serialization/io.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/validation/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/validation/validate.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/visualization/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/visualization/plots.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/src/simulsi/web/__init__.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/docs/test_docs.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/integration/test_analysis_features.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/integration/test_examples.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/integration/test_visualization.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/performance/test_performance.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/property/test_invariants.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/statistical/test_queueing_theory.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_checkpoint.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_entities.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_events.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_metrics.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_montecarlo_and_engine_extras.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_processes.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_queues.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_randomness.py +0 -0
- {simulsi-0.2.0 → simulsi-0.3.0}/tests/unit/test_resources.py +0 -0
|
@@ -6,6 +6,40 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-10-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Per-run warm-up override (`Model.simulate(warmup=)`, `simulsi run --warmup`);
|
|
14
|
+
the model warm-up is scaled proportionally for shorter runs, with a note.
|
|
15
|
+
- Dashboard runs experiments as background jobs with a progress bar
|
|
16
|
+
(`POST /api/run` returns 202, `GET /api/jobs/{id}`).
|
|
17
|
+
- Multiple-comparison adjustment in `compare(adjust="bonferroni"|"holm"|"bh")`,
|
|
18
|
+
the CLI (`--adjust`), configs and the dashboard.
|
|
19
|
+
- Warm-up detection with MSER-5 (`simulsi.analysis.mser`, `suggest_warmup`,
|
|
20
|
+
`simulsi warmup`).
|
|
21
|
+
- Sequential experiments: `Experiment.run_until(relative_precision, ...)` and
|
|
22
|
+
`simulsi experiment --until-precision`.
|
|
23
|
+
- Antithetic variates (`Experiment(antithetic=True)`, inverse-transform
|
|
24
|
+
sampling modes on `RandomStream`) and control variates
|
|
25
|
+
(`ExperimentResult.control_variate`, `statistics.control_variate`).
|
|
26
|
+
- Documentation site with API reference (mkdocs-material, GitHub Pages).
|
|
27
|
+
- Modelling primitives: `Container` (`sim.container`) for levels and stock;
|
|
28
|
+
filtered gets on queues (`queue.get(filter=...)`); `sim.wait_until(predicate)`;
|
|
29
|
+
shift schedules (`capacity_schedule`); non-stationary arrivals
|
|
30
|
+
(`arrivals` with `PiecewiseRate` or a rate function); `batch`, `split`/`join`,
|
|
31
|
+
`Router` and `shortest_queue`.
|
|
32
|
+
- Input modelling: `fit_distribution` (MLE fits ranked by AIC/BIC/KS, with
|
|
33
|
+
`FitReport.format()` and `plot()`), `load_values` and `simulsi fit`;
|
|
34
|
+
trace-driven input with `load_trace`, `trace_arrivals` and `TraceReplay`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Faster process engine (about 14% more events per second on process models);
|
|
39
|
+
`benchmarks/compare_simpy.py` compares against SimPy.
|
|
40
|
+
- Worker process pools are reused across experiments in the same process
|
|
41
|
+
(`shutdown_workers()` to release them).
|
|
42
|
+
|
|
9
43
|
## [0.2.0] - 2026-10-02
|
|
10
44
|
|
|
11
45
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simulsi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A general-purpose, reproducible simulation and scenario experimentation framework for Python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Yashjindal11/simulsi
|
|
6
6
|
Project-URL: Documentation, https://github.com/Yashjindal11/simulsi/tree/main/docs
|
|
@@ -42,6 +42,9 @@ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
|
42
42
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
43
43
|
Requires-Dist: ruff==0.16.10; extra == 'dev'
|
|
44
44
|
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
45
|
+
Provides-Extra: docs
|
|
46
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
45
48
|
Provides-Extra: pandas
|
|
46
49
|
Requires-Dist: pandas>=2.1; extra == 'pandas'
|
|
47
50
|
Provides-Extra: parquet
|
|
@@ -55,6 +58,12 @@ Description-Content-Type: text/markdown
|
|
|
55
58
|
|
|
56
59
|
# SimulSI
|
|
57
60
|
|
|
61
|
+
[](https://github.com/Yashjindal11/simulsi/actions/workflows/ci.yml)
|
|
62
|
+
[](https://pypi.org/project/simulsi/)
|
|
63
|
+
[](https://pypi.org/project/simulsi/)
|
|
64
|
+
[](https://yashjindal11.github.io/simulsi/)
|
|
65
|
+
[](LICENSE)
|
|
66
|
+
|
|
58
67
|
**Simulation Intelligence** - *Model the system. Simulate the future.*
|
|
59
68
|
|
|
60
69
|
SimulSI is a general-purpose, reproducible simulation and scenario
|
|
@@ -120,14 +129,19 @@ for commercial packages; see
|
|
|
120
129
|
|
|
121
130
|
## Installation
|
|
122
131
|
|
|
123
|
-
|
|
132
|
+
Python 3.11+, from [PyPI](https://pypi.org/project/simulsi/):
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install simulsi # core (includes the CLI and the web dashboard)
|
|
136
|
+
pip install "simulsi[viz]" # + matplotlib plots
|
|
137
|
+
pip install "simulsi[all]" # + pandas, Parquet, matplotlib, Plotly
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
From source (for development):
|
|
124
141
|
|
|
125
142
|
```bash
|
|
126
143
|
git clone https://github.com/Yashjindal11/simulsi.git && cd simulsi
|
|
127
144
|
python -m venv .venv
|
|
128
|
-
.venv/bin/pip install -e . # core
|
|
129
|
-
.venv/bin/pip install -e ".[viz]" # + matplotlib plots
|
|
130
|
-
.venv/bin/pip install -e ".[all]" # + pandas, Parquet, matplotlib, Plotly
|
|
131
145
|
.venv/bin/pip install -e ".[dev]" # everything needed to run the tests
|
|
132
146
|
```
|
|
133
147
|
|
|
@@ -176,6 +190,10 @@ python scripts/run_examples.py # all of them, quick mode
|
|
|
176
190
|
|
|
177
191
|
## Documentation
|
|
178
192
|
|
|
193
|
+
The full documentation, including an API reference for every public class
|
|
194
|
+
and function, is at **https://yashjindal11.github.io/simulsi/**. The same
|
|
195
|
+
pages are in [`docs/`](docs):
|
|
196
|
+
|
|
179
197
|
- [Getting started](docs/getting-started.md) - installation, quickstart, first experiment
|
|
180
198
|
- [Core concepts](docs/concepts.md) - simulation, events, entities, resources, queues, processes, randomness, metrics, disruptions, validation, introspection
|
|
181
199
|
- [Experiments and analysis](docs/experiments.md) - scenarios, experiments, Monte Carlo, statistics, comparison, sensitivity, cost, optimisation
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# SimulSI
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Yashjindal11/simulsi/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/simulsi/)
|
|
5
|
+
[](https://pypi.org/project/simulsi/)
|
|
6
|
+
[](https://yashjindal11.github.io/simulsi/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
3
9
|
**Simulation Intelligence** - *Model the system. Simulate the future.*
|
|
4
10
|
|
|
5
11
|
SimulSI is a general-purpose, reproducible simulation and scenario
|
|
@@ -65,14 +71,19 @@ for commercial packages; see
|
|
|
65
71
|
|
|
66
72
|
## Installation
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
Python 3.11+, from [PyPI](https://pypi.org/project/simulsi/):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install simulsi # core (includes the CLI and the web dashboard)
|
|
78
|
+
pip install "simulsi[viz]" # + matplotlib plots
|
|
79
|
+
pip install "simulsi[all]" # + pandas, Parquet, matplotlib, Plotly
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
From source (for development):
|
|
69
83
|
|
|
70
84
|
```bash
|
|
71
85
|
git clone https://github.com/Yashjindal11/simulsi.git && cd simulsi
|
|
72
86
|
python -m venv .venv
|
|
73
|
-
.venv/bin/pip install -e . # core
|
|
74
|
-
.venv/bin/pip install -e ".[viz]" # + matplotlib plots
|
|
75
|
-
.venv/bin/pip install -e ".[all]" # + pandas, Parquet, matplotlib, Plotly
|
|
76
87
|
.venv/bin/pip install -e ".[dev]" # everything needed to run the tests
|
|
77
88
|
```
|
|
78
89
|
|
|
@@ -121,6 +132,10 @@ python scripts/run_examples.py # all of them, quick mode
|
|
|
121
132
|
|
|
122
133
|
## Documentation
|
|
123
134
|
|
|
135
|
+
The full documentation, including an API reference for every public class
|
|
136
|
+
and function, is at **https://yashjindal11.github.io/simulsi/**. The same
|
|
137
|
+
pages are in [`docs/`](docs):
|
|
138
|
+
|
|
124
139
|
- [Getting started](docs/getting-started.md) - installation, quickstart, first experiment
|
|
125
140
|
- [Core concepts](docs/concepts.md) - simulation, events, entities, resources, queues, processes, randomness, metrics, disruptions, validation, introspection
|
|
126
141
|
- [Experiments and analysis](docs/experiments.md) - scenarios, experiments, Monte Carlo, statistics, comparison, sensitivity, cost, optimisation
|
|
@@ -57,3 +57,36 @@ variation. Re-run the script on your hardware rather than relying on them.
|
|
|
57
57
|
serial runs either way (tested).
|
|
58
58
|
* `tests/performance/test_performance.py` guards against regressions with
|
|
59
59
|
generous floors (about 10x below these numbers) and a linear-scaling check.
|
|
60
|
+
|
|
61
|
+
## Comparison with SimPy
|
|
62
|
+
|
|
63
|
+
[`compare_simpy.py`](compare_simpy.py) runs the same M/M/2 queue (arrival
|
|
64
|
+
rate 1.8, service rate 1.0, a generator process per customer, NumPy draws)
|
|
65
|
+
in SimulSI and in [SimPy](https://simpy.readthedocs.io/), and checks that
|
|
66
|
+
both produce the same mean wait.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install simpy
|
|
70
|
+
python benchmarks/compare_simpy.py --customers 100000 --repeat 3
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Measured on 2026-10-06 (Python 3.12.15, SimulSI 0.3.0.dev, SimPy 4.1.2,
|
|
74
|
+
Apple silicon, best of 3):
|
|
75
|
+
|
|
76
|
+
| library | seconds | customers/s | mean wait |
|
|
77
|
+
|---|---:|---:|---:|
|
|
78
|
+
| SimulSI | 1.35 | 73,800 | 4.6536 |
|
|
79
|
+
| SimPy | 0.84 | 118,700 | 4.6536 |
|
|
80
|
+
|
|
81
|
+
The two give identical results, and SimulSI takes about 1.6x as long. Part of
|
|
82
|
+
the gap is extra work SimulSI does on every request and release: it records
|
|
83
|
+
time-weighted utilization and queue length, a wait-time tally and counters,
|
|
84
|
+
and tracks which process holds each unit so unreleased resources can be
|
|
85
|
+
reported. SimPy leaves that bookkeeping to the model. The rest is per-event
|
|
86
|
+
overhead (lazy cancellation, the event status machine). Profiling for this
|
|
87
|
+
comparison led to hot-path changes that cut the gap from 1.85x and made the
|
|
88
|
+
`processes` benchmark about 14% faster.
|
|
89
|
+
|
|
90
|
+
If raw speed on very large models matters most, SimPy (or a compiled
|
|
91
|
+
engine) is the better choice. SimulSI is aimed at the experiment layer:
|
|
92
|
+
replications, statistics, scenarios and analysis.
|
|
@@ -106,7 +106,7 @@ model is pickled by reference (module + qualified name); models loaded from
|
|
|
106
106
|
files by the CLI are re-imported in each worker by an initializer. Results
|
|
107
107
|
come back in task order. Start-up cost (about a second with the `spawn`
|
|
108
108
|
start method used on macOS and Windows) makes parallelism worthwhile only when
|
|
109
|
-
replications take longer than that. See [benchmarks](
|
|
109
|
+
replications take longer than that. See [benchmarks](https://github.com/Yashjindal11/simulsi/blob/main/benchmarks/README.md).
|
|
110
110
|
|
|
111
111
|
## Checkpointing
|
|
112
112
|
|
|
@@ -143,7 +143,7 @@ files are found automatically; otherwise pass `model=`.
|
|
|
143
143
|
## Performance
|
|
144
144
|
|
|
145
145
|
The engine is pure Python. Measured numbers are in
|
|
146
|
-
[benchmarks/README.md](
|
|
146
|
+
[benchmarks/README.md](https://github.com/Yashjindal11/simulsi/blob/main/benchmarks/README.md). Practical guidance:
|
|
147
147
|
|
|
148
148
|
* Prefer `yield number` / `sim.use(...)` in processes; avoid creating
|
|
149
149
|
entities you never inspect.
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
```text
|
|
4
4
|
simulsi init [DIR] [--force]
|
|
5
5
|
simulsi validate TARGET [-p KEY=VALUE ...] [--smoke T]
|
|
6
|
-
simulsi run TARGET [-p KEY=VALUE ...] [--seed N] [--duration T] [-m METRIC ...] [--trace FILE] [--json]
|
|
7
|
-
simulsi experiment CONFIG [-r N] [-w N] [--seed N] [-o DIR] [--checkpoint FILE] [-q]
|
|
8
|
-
simulsi analyze RESULTS [-m METRIC ...] [--baseline NAME] [--confidence C] [--precision P] [--json]
|
|
6
|
+
simulsi run TARGET [-p KEY=VALUE ...] [--seed N] [--duration T] [--warmup T] [-m METRIC ...] [--trace FILE] [--json]
|
|
7
|
+
simulsi experiment CONFIG [-r N] [-w N] [--seed N] [-o DIR] [--checkpoint FILE] [--adjust METHOD] [--until-precision P] [-q]
|
|
8
|
+
simulsi analyze RESULTS [-m METRIC ...] [--baseline NAME] [--confidence C] [--precision P] [--adjust METHOD] [--json]
|
|
9
|
+
simulsi warmup TARGET [-p KEY=VALUE ...] [--series KEY] [-r N] [--duration T] [--bins N] [--json]
|
|
10
|
+
simulsi fit FILE [--column NAME] [--candidates NAME ...] [--criterion aic|bic|ks] [--json]
|
|
9
11
|
simulsi benchmark [--sizes N ...] [--workers N ...] [--replications N] [--no-memory] [--json]
|
|
10
12
|
simulsi visualize TARGET [-o DIR] [-m METRIC ...] [--backend matplotlib|plotly] [--format png|svg|pdf]
|
|
11
13
|
simulsi ui [RESULTS ...] [--model REF ...] [--host H] [--port P] [--no-browser]
|
|
@@ -41,6 +43,12 @@ Parameters given with `-p key=value` are parsed as YAML scalars, so
|
|
|
41
43
|
`experiment.json`, `replications.csv`, `summary.csv` (and Parquet if asked).
|
|
42
44
|
* **analyze** reloads saved results: summary, comparison, provenance, and
|
|
43
45
|
with `--precision 0.05` how many replications each metric needs.
|
|
46
|
+
* **warmup** suggests a warm-up period: it averages a recorded time series
|
|
47
|
+
(default: the first resource's queue length) over replications and applies
|
|
48
|
+
MSER-5.
|
|
49
|
+
* **fit** fits input distributions to a CSV column or a text file of
|
|
50
|
+
numbers, ranks them (AIC, BIC or KS) and prints the best one as a config
|
|
51
|
+
spec you can paste into `experiment.yaml`.
|
|
44
52
|
* **benchmark** measures engine throughput (10k / 100k / 1M events),
|
|
45
53
|
process-based model throughput and parallel experiment speed on your
|
|
46
54
|
machine, with tracemalloc peak memory.
|
|
@@ -67,6 +75,10 @@ experiment:
|
|
|
67
75
|
common_random_numbers: true # same seeds for every scenario (paired comparisons)
|
|
68
76
|
confidence: 0.95
|
|
69
77
|
on_error: raise # or "record" to keep going and store errors
|
|
78
|
+
multiple_comparisons: holm # none (default), bonferroni, holm or bh
|
|
79
|
+
target_precision: 0.05 # optional: add replications until +-5% (relative) ...
|
|
80
|
+
precision_metrics: [resource.server.wait.mean] # ... for these metrics (default: metrics)
|
|
81
|
+
max_replications: 500 # upper limit for precision-based stopping
|
|
70
82
|
|
|
71
83
|
parameters: # base values for every scenario
|
|
72
84
|
arrival_rate: 0.9
|
|
@@ -232,6 +232,132 @@ Metrics (prefix `queue.<name>.`): `mean_length`, `max_length`, `wait.mean/max`,
|
|
|
232
232
|
`puts`, `gets`, `removed`, `throughput`, `length_final`. Items never vanish:
|
|
233
233
|
`puts == gets + removed + length`.
|
|
234
234
|
|
|
235
|
+
`orders.get(filter=lambda o: o.startswith("rush"))` takes the first matching
|
|
236
|
+
item in discipline order (waiting until one arrives); a filtered get that is
|
|
237
|
+
waiting does not hold up gets for other items. `try_get(filter)` is the
|
|
238
|
+
non-blocking form.
|
|
239
|
+
|
|
240
|
+
## Containers
|
|
241
|
+
|
|
242
|
+
A `Container` holds a *level* (fuel, stock, cash, parts in a bin) rather than
|
|
243
|
+
distinct items. `get(amount)` waits until enough is there; `put(amount)` waits
|
|
244
|
+
until there is room. Waiters are served strictly in arrival order.
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from simulsi import Simulation
|
|
248
|
+
|
|
249
|
+
sim = Simulation(seed=2)
|
|
250
|
+
stock = sim.container("stock", capacity=200, init=120)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def demand(sim):
|
|
254
|
+
rng = sim.stream("demand")
|
|
255
|
+
while True:
|
|
256
|
+
yield 1.0
|
|
257
|
+
yield stock.get(rng.integers(5, 15))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def replenish(sim):
|
|
261
|
+
while True:
|
|
262
|
+
yield sim.wait_until(lambda: stock.level < 40) # reorder point
|
|
263
|
+
yield 3.0 # lead time
|
|
264
|
+
yield stock.put(150)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
sim.process(demand(sim))
|
|
268
|
+
sim.process(replenish(sim))
|
|
269
|
+
m = sim.run(until=500).metrics
|
|
270
|
+
print(m["container.stock.level.mean"], m["container.stock.stockouts"])
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Metrics (prefix `container.<name>.`): `level.mean/min/max`, `level_final`,
|
|
274
|
+
`put_amount`, `get_amount`, `stockouts` (gets that had to wait),
|
|
275
|
+
`get_wait.mean`, `put_wait.mean`.
|
|
276
|
+
|
|
277
|
+
`sim.wait_until(predicate)` works with any model state: the predicate is
|
|
278
|
+
checked after every event until it is true, and the yield returns the time
|
|
279
|
+
it became true. Keep predicates cheap.
|
|
280
|
+
|
|
281
|
+
## Schedules and time-varying arrivals
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
from simulsi import Simulation
|
|
285
|
+
from simulsi.processes import PiecewiseRate, arrivals, capacity_schedule
|
|
286
|
+
|
|
287
|
+
sim = Simulation(seed=4)
|
|
288
|
+
agents = sim.resource("agents", 2)
|
|
289
|
+
# shifts in minutes: 2 agents at night, 6 from 08:00, 4 from 17:00, every day
|
|
290
|
+
capacity_schedule(sim, agents, [(0, 2), (480, 6), (1020, 4)], period=1440)
|
|
291
|
+
# calls per minute by time of day
|
|
292
|
+
calls = PiecewiseRate([(0, 0.05), (480, 0.6), (720, 0.9), (1020, 0.3)], period=1440)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def call(sim, i):
|
|
296
|
+
yield from sim.use(agents, sim.stream("talk").exponential(6.0))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
arrivals(sim, calls, call, stream="calls")
|
|
300
|
+
m = sim.run(until=7 * 1440).metrics
|
|
301
|
+
print(m["resource.agents.utilization"], m["resource.agents.wait.mean"])
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
* `capacity_schedule(sim, resource, [(time, capacity), ...], period=None)`
|
|
305
|
+
changes capacity at the given offsets. Lowering capacity does not
|
|
306
|
+
interrupt work in progress; the new limit applies to later grants.
|
|
307
|
+
* `arrivals(sim, rate, spawn, stream=..., limit=None, until=None)` calls
|
|
308
|
+
`spawn(sim, i)` at every arrival and starts it as a process if it returns a
|
|
309
|
+
generator. `rate` can be a number (Poisson process), a `PiecewiseRate`
|
|
310
|
+
(exact, one random draw per arrival) or any function `rate(t)` together with
|
|
311
|
+
`max_rate=` (generated by thinning).
|
|
312
|
+
|
|
313
|
+
## Batching, split/join and routing
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from simulsi import Simulation
|
|
317
|
+
from simulsi.processes import Router, batch, join, shortest_queue, split
|
|
318
|
+
|
|
319
|
+
sim = Simulation(seed=5)
|
|
320
|
+
parts = sim.queue("parts")
|
|
321
|
+
lines = [sim.resource("line-a", 1), sim.resource("line-b", 1)]
|
|
322
|
+
inspection = Router(sim, "inspection", {"pass": 0.9, "rework": 0.1})
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def supplier(sim):
|
|
326
|
+
for i in range(12):
|
|
327
|
+
yield 0.5
|
|
328
|
+
yield parts.put(i)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def finish(sim, line):
|
|
332
|
+
yield from sim.use(line, 2.0)
|
|
333
|
+
return inspection.choose()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def assembler(sim):
|
|
337
|
+
while True:
|
|
338
|
+
kit = yield from batch(parts, 4, timeout=5.0) # up to 4 parts
|
|
339
|
+
if not kit:
|
|
340
|
+
return
|
|
341
|
+
results = yield from join(split(sim, [finish(sim, shortest_queue(lines))
|
|
342
|
+
for _ in range(2)]))
|
|
343
|
+
print(sim.now, kit, results)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
sim.process(supplier(sim))
|
|
347
|
+
sim.process(assembler(sim))
|
|
348
|
+
m = sim.run().metrics
|
|
349
|
+
print(m["route.inspection.pass"], m["route.inspection.rework"])
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
* `batch(queue, size, timeout=None)` collects items (`yield from`); with a
|
|
353
|
+
timeout it returns whatever has arrived by then.
|
|
354
|
+
* `split(sim, generators)` starts parallel processes and `join(processes)`
|
|
355
|
+
waits for all of them and returns their return values in order (a failure
|
|
356
|
+
in any of them is raised in the joiner).
|
|
357
|
+
* `Router(sim, name, {route: probability})` draws from its own stream
|
|
358
|
+
(`route:<name>`) and counts `route.<name>.<route>`.
|
|
359
|
+
* `shortest_queue(resources)` picks the resource with the fewest waiting.
|
|
360
|
+
|
|
235
361
|
## Randomness
|
|
236
362
|
|
|
237
363
|
```python
|
|
@@ -258,6 +384,50 @@ In a simulation use `sim.stream("name")`: streams are keyed by name, so the
|
|
|
258
384
|
arrival stream is the same whatever else the model draws. This is what makes
|
|
259
385
|
common-random-number comparisons effective.
|
|
260
386
|
|
|
387
|
+
## Input data: fitting and traces
|
|
388
|
+
|
|
389
|
+
Input distributions should come from data. `fit_distribution` fits the
|
|
390
|
+
exponential, gamma, lognormal, normal, uniform, triangular and Poisson
|
|
391
|
+
families by maximum likelihood and ranks them:
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
import numpy as np
|
|
395
|
+
|
|
396
|
+
from simulsi.randomness import fit_distribution
|
|
397
|
+
|
|
398
|
+
observed = np.random.default_rng(7).gamma(2.5, 1.6, size=400) # e.g. service times
|
|
399
|
+
report = fit_distribution(observed)
|
|
400
|
+
print(report.format()) # AIC, BIC, KS statistic and p-value per family
|
|
401
|
+
service = report.best.distribution # a simulsi Gamma, ready to sample
|
|
402
|
+
print(service, report.best.distribution.to_spec())
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
* Families whose support does not fit the data (gamma or lognormal with
|
|
406
|
+
zeros, Poisson with fractions) are listed in `report.skipped`.
|
|
407
|
+
* KS p-values use parameters estimated from the same data, so they are
|
|
408
|
+
optimistic. With fewer than about 30 observations, or when the best family
|
|
409
|
+
is still rejected, use `Empirical(data)` instead.
|
|
410
|
+
* `report.plot()` overlays the top fits on a histogram.
|
|
411
|
+
* `simulsi fit data.csv --column service` does the same from the command
|
|
412
|
+
line and prints a spec for `experiment.yaml`.
|
|
413
|
+
|
|
414
|
+
**Trace-driven runs** replay history instead of sampling. With recorded
|
|
415
|
+
arrivals and service times, a valid model should reproduce the recorded
|
|
416
|
+
waits, which makes this a strong validation check:
|
|
417
|
+
|
|
418
|
+
```py
|
|
419
|
+
from simulsi.processes import TraceReplay, load_trace, trace_arrivals
|
|
420
|
+
|
|
421
|
+
history = load_trace("arrivals.csv") # rows like {"time": 3.5, "service": 4.1}
|
|
422
|
+
|
|
423
|
+
def job(sim, record):
|
|
424
|
+
yield from sim.use(server, record["service"])
|
|
425
|
+
|
|
426
|
+
trace_arrivals(sim, history, job) # one arrival per row, at row["time"]
|
|
427
|
+
repair_times = TraceReplay([12.0, 30.5, 8.2], cycle=True)
|
|
428
|
+
repair_times.sample() # next recorded value
|
|
429
|
+
```
|
|
430
|
+
|
|
261
431
|
## Metrics
|
|
262
432
|
|
|
263
433
|
```python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
Every example is a single file in [`examples/`](
|
|
3
|
+
Every example is a single file in [`examples/`](https://github.com/Yashjindal11/simulsi/blob/main/examples) with a
|
|
4
4
|
module-level `model` and a `main(replications=...)` function. All data is
|
|
5
5
|
synthetic. Run one with `python examples/<name>.py`, or all of them quickly
|
|
6
6
|
with `python scripts/run_examples.py`. They also work with the CLI:
|
|
@@ -11,7 +11,7 @@ simulsi validate examples/manufacturing.py
|
|
|
11
11
|
simulsi visualize examples/bank_queue.py --out plots
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
## 1. Bank queue - [`bank_queue.py`](
|
|
14
|
+
## 1. Bank queue - [`bank_queue.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/bank_queue.py)
|
|
15
15
|
|
|
16
16
|
Poisson arrivals, log-normal service times (specified by mean and coefficient
|
|
17
17
|
of variation via `LogNormal.from_moments`), a pool of tellers, and customers
|
|
@@ -22,7 +22,7 @@ the reneging fraction, for baseline / four tellers / lunch rush / both,
|
|
|
22
22
|
compared with paired CIs. It ends with replication advice: how many
|
|
23
23
|
replications are needed for a +-5% estimate of the mean wait.
|
|
24
24
|
|
|
25
|
-
## 2. Emergency department - [`hospital.py`](
|
|
25
|
+
## 2. Emergency department - [`hospital.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/hospital.py)
|
|
26
26
|
|
|
27
27
|
Patients get an acuity level (`Categorical`), see a triage nurse, wait for a
|
|
28
28
|
treatment room and then a doctor. Rooms and doctors use the `"priority"`
|
|
@@ -35,7 +35,7 @@ their acuity's target (a service level), doctor and room utilization. It
|
|
|
35
35
|
compares an extra doctor with extra rooms - which one helps depends on which
|
|
36
36
|
resource binds.
|
|
37
37
|
|
|
38
|
-
## 3. Warehouse - [`warehouse.py`](
|
|
38
|
+
## 3. Warehouse - [`warehouse.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/warehouse.py)
|
|
39
39
|
|
|
40
40
|
Orders arrive with a time-varying rate (a non-homogeneous Poisson process
|
|
41
41
|
generated by thinning), each with a Poisson number of lines. Pickers walk
|
|
@@ -45,7 +45,7 @@ and pick, then hand orders to packers through a `Queue` buffer.
|
|
|
45
45
|
time and picker/packer utilization, comparing a seventh picker against
|
|
46
46
|
faster picking from better slotting.
|
|
47
47
|
|
|
48
|
-
## 4. Manufacturing - [`manufacturing.py`](
|
|
48
|
+
## 4. Manufacturing - [`manufacturing.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/manufacturing.py)
|
|
49
49
|
|
|
50
50
|
Two machines in series with a finite buffer. A full buffer blocks machine 1
|
|
51
51
|
while it still holds the finished job. Each machine fails at random
|
|
@@ -58,7 +58,7 @@ afterwards.
|
|
|
58
58
|
costs). Scenarios: preventive maintenance (longer MTBF), a bigger buffer, a
|
|
59
59
|
second technician.
|
|
60
60
|
|
|
61
|
-
## 5. Transportation - [`transportation.py`](
|
|
61
|
+
## 5. Transportation - [`transportation.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/transportation.py)
|
|
62
62
|
|
|
63
63
|
Buses circulate around a loop of stops. Passengers queue at each stop with a
|
|
64
64
|
random destination; a bus alights passengers, boards as many as fit, dwells
|
|
@@ -69,7 +69,7 @@ buses (congestion) and load factor (capacity utilization). It compares a
|
|
|
69
69
|
fourth bus with bigger buses that have the same total number of seats -
|
|
70
70
|
frequency against capacity.
|
|
71
71
|
|
|
72
|
-
## 6. Aviation - [`aviation.py`](
|
|
72
|
+
## 6. Aviation - [`aviation.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/aviation.py)
|
|
73
73
|
|
|
74
74
|
A synthetic day of arrivals in two banks plus background traffic. Flights
|
|
75
75
|
land with log-normal delays, wait on the taxiway if no gate is free (a gate
|
|
@@ -84,7 +84,7 @@ an airline planning tool, and it uses no real airline data.
|
|
|
84
84
|
|
|
85
85
|
## Smaller examples
|
|
86
86
|
|
|
87
|
-
* [`queue.py`](
|
|
87
|
+
* [`queue.py`](https://github.com/Yashjindal11/simulsi/blob/main/examples/queue.py) - M/M/c in 30 lines, compared with the
|
|
88
88
|
Erlang-C formula.
|
|
89
89
|
* `simulsi init` - the starter "service desk" project with a YAML experiment
|
|
90
90
|
and cost model.
|