qellaris 0.5.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 (104) hide show
  1. qellaris-0.5.0/LICENSE +25 -0
  2. qellaris-0.5.0/MANIFEST.in +19 -0
  3. qellaris-0.5.0/PKG-INFO +505 -0
  4. qellaris-0.5.0/PUBLISHING.md +140 -0
  5. qellaris-0.5.0/PYPI_README.md +460 -0
  6. qellaris-0.5.0/README.md +1182 -0
  7. qellaris-0.5.0/examples/example.py +91 -0
  8. qellaris-0.5.0/paper/QAIO_2027_Baseline_Benchmark_Paper.docx +0 -0
  9. qellaris-0.5.0/paper/build_overleaf.py +37 -0
  10. qellaris-0.5.0/paper/build_paper.py +311 -0
  11. qellaris-0.5.0/paper/overleaf/README.md +20 -0
  12. qellaris-0.5.0/paper/overleaf/main.tex +530 -0
  13. qellaris-0.5.0/paper/overleaf/references.bib +172 -0
  14. qellaris-0.5.0/paper/paper-source.md +288 -0
  15. qellaris-0.5.0/pyproject.toml +99 -0
  16. qellaris-0.5.0/setup.cfg +4 -0
  17. qellaris-0.5.0/src/qellaris/__init__.py +40 -0
  18. qellaris-0.5.0/src/qellaris/__main__.py +111 -0
  19. qellaris-0.5.0/src/qellaris/_version.py +3 -0
  20. qellaris-0.5.0/src/qellaris/cli.py +230 -0
  21. qellaris-0.5.0/src/qellaris/control_env.py +504 -0
  22. qellaris-0.5.0/src/qellaris/data/kensington_buildings.json +356793 -0
  23. qellaris-0.5.0/src/qellaris/data/kensington_landuse.json +51216 -0
  24. qellaris-0.5.0/src/qellaris/data/kensington_streets.json +55913 -0
  25. qellaris-0.5.0/src/qellaris/data/kensington_towers.json +1 -0
  26. qellaris-0.5.0/src/qellaris/decision_metrics.py +70 -0
  27. qellaris-0.5.0/src/qellaris/demographics.py +316 -0
  28. qellaris-0.5.0/src/qellaris/domain.py +171 -0
  29. qellaris-0.5.0/src/qellaris/equity.py +627 -0
  30. qellaris-0.5.0/src/qellaris/equity_scenarios.py +230 -0
  31. qellaris-0.5.0/src/qellaris/evaluation.py +260 -0
  32. qellaris-0.5.0/src/qellaris/experiment.py +518 -0
  33. qellaris-0.5.0/src/qellaris/forecasting.py +217 -0
  34. qellaris-0.5.0/src/qellaris/geography.py +409 -0
  35. qellaris-0.5.0/src/qellaris/grid.py +420 -0
  36. qellaris-0.5.0/src/qellaris/hardware_job.py +113 -0
  37. qellaris-0.5.0/src/qellaris/inventory.py +103 -0
  38. qellaris-0.5.0/src/qellaris/menu.py +210 -0
  39. qellaris-0.5.0/src/qellaris/milp_reference.py +56 -0
  40. qellaris-0.5.0/src/qellaris/netbravo.py +442 -0
  41. qellaris-0.5.0/src/qellaris/ofcom.py +231 -0
  42. qellaris-0.5.0/src/qellaris/optimization.py +332 -0
  43. qellaris-0.5.0/src/qellaris/paths.py +98 -0
  44. qellaris-0.5.0/src/qellaris/placement.py +245 -0
  45. qellaris-0.5.0/src/qellaris/power.py +171 -0
  46. qellaris-0.5.0/src/qellaris/quantum.py +514 -0
  47. qellaris-0.5.0/src/qellaris/quantum_zoo.py +594 -0
  48. qellaris-0.5.0/src/qellaris/qubo.py +152 -0
  49. qellaris-0.5.0/src/qellaris/radio.py +189 -0
  50. qellaris-0.5.0/src/qellaris/real_data.py +98 -0
  51. qellaris-0.5.0/src/qellaris/region_packages.py +202 -0
  52. qellaris-0.5.0/src/qellaris/regions.py +519 -0
  53. qellaris-0.5.0/src/qellaris/rl.py +237 -0
  54. qellaris-0.5.0/src/qellaris/road_network.py +197 -0
  55. qellaris-0.5.0/src/qellaris/scenario_generation.py +102 -0
  56. qellaris-0.5.0/src/qellaris/sdk.py +195 -0
  57. qellaris-0.5.0/src/qellaris/simulation.py +928 -0
  58. qellaris-0.5.0/src/qellaris/solara_app.py +2255 -0
  59. qellaris-0.5.0/src/qellaris/solvers.py +375 -0
  60. qellaris-0.5.0/src/qellaris/template.py +736 -0
  61. qellaris-0.5.0/src/qellaris/tests/__init__.py +1 -0
  62. qellaris-0.5.0/src/qellaris/tests/conftest.py +8 -0
  63. qellaris-0.5.0/src/qellaris/tests/test_benchmark.py +182 -0
  64. qellaris-0.5.0/src/qellaris/tests/test_demographics.py +261 -0
  65. qellaris-0.5.0/src/qellaris/tests/test_demographics_page.py +114 -0
  66. qellaris-0.5.0/src/qellaris/tests/test_entry_points.py +194 -0
  67. qellaris-0.5.0/src/qellaris/tests/test_equity.py +67 -0
  68. qellaris-0.5.0/src/qellaris/tests/test_equity_scenarios.py +307 -0
  69. qellaris-0.5.0/src/qellaris/tests/test_equity_state.py +152 -0
  70. qellaris-0.5.0/src/qellaris/tests/test_evaluation.py +144 -0
  71. qellaris-0.5.0/src/qellaris/tests/test_geometry.py +121 -0
  72. qellaris-0.5.0/src/qellaris/tests/test_hardware_job.py +38 -0
  73. qellaris-0.5.0/src/qellaris/tests/test_inventory.py +114 -0
  74. qellaris-0.5.0/src/qellaris/tests/test_land_use.py +222 -0
  75. qellaris-0.5.0/src/qellaris/tests/test_launcher.py +88 -0
  76. qellaris-0.5.0/src/qellaris/tests/test_layout.py +103 -0
  77. qellaris-0.5.0/src/qellaris/tests/test_layout_factorial_archive.py +43 -0
  78. qellaris-0.5.0/src/qellaris/tests/test_menu.py +73 -0
  79. qellaris-0.5.0/src/qellaris/tests/test_milp_reference.py +28 -0
  80. qellaris-0.5.0/src/qellaris/tests/test_netbravo.py +73 -0
  81. qellaris-0.5.0/src/qellaris/tests/test_netbravo_ingest.py +156 -0
  82. qellaris-0.5.0/src/qellaris/tests/test_paths.py +139 -0
  83. qellaris-0.5.0/src/qellaris/tests/test_placement.py +110 -0
  84. qellaris-0.5.0/src/qellaris/tests/test_power.py +213 -0
  85. qellaris-0.5.0/src/qellaris/tests/test_quantum.py +219 -0
  86. qellaris-0.5.0/src/qellaris/tests/test_quantum_zoo.py +101 -0
  87. qellaris-0.5.0/src/qellaris/tests/test_radio.py +57 -0
  88. qellaris-0.5.0/src/qellaris/tests/test_region_packages.py +130 -0
  89. qellaris-0.5.0/src/qellaris/tests/test_regions.py +231 -0
  90. qellaris-0.5.0/src/qellaris/tests/test_rl_env.py +159 -0
  91. qellaris-0.5.0/src/qellaris/tests/test_scenario_generation.py +38 -0
  92. qellaris-0.5.0/src/qellaris/tests/test_sdk.py +60 -0
  93. qellaris-0.5.0/src/qellaris/tests/test_study_area_page.py +149 -0
  94. qellaris-0.5.0/src/qellaris/tests/test_template.py +218 -0
  95. qellaris-0.5.0/src/qellaris/tests/test_ui_model.py +157 -0
  96. qellaris-0.5.0/src/qellaris/ui.py +198 -0
  97. qellaris-0.5.0/src/qellaris.egg-info/PKG-INFO +505 -0
  98. qellaris-0.5.0/src/qellaris.egg-info/SOURCES.txt +102 -0
  99. qellaris-0.5.0/src/qellaris.egg-info/dependency_links.txt +1 -0
  100. qellaris-0.5.0/src/qellaris.egg-info/entry_points.txt +13 -0
  101. qellaris-0.5.0/src/qellaris.egg-info/requires.txt +21 -0
  102. qellaris-0.5.0/src/qellaris.egg-info/top_level.txt +1 -0
  103. qellaris-0.5.0/workspace/runs/qaio-simulator/solver_comparison.csv +8 -0
  104. qellaris-0.5.0/workspace/runs/qaio-simulator/summary.json +364 -0
qellaris-0.5.0/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qellaris benchmark team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ Bundled Kensington map extracts (src/qellaris/data/*.json) are derived from
24
+ OpenStreetMap, (c) OpenStreetMap contributors, and are available under the
25
+ Open Database License (ODbL) 1.0: https://opendatacommons.org/licenses/odbl/
@@ -0,0 +1,19 @@
1
+ # Source release: reproducible application, examples and paper; no local workspace.
2
+ include README.md PYPI_README.md PUBLISHING.md LICENSE pyproject.toml MANIFEST.in
3
+ recursive-include src/qellaris *.py *.json
4
+ recursive-include examples *.py
5
+ include paper/paper-source.md
6
+ include paper/build_paper.py paper/build_overleaf.py
7
+ include paper/QAIO_2027_Baseline_Benchmark_Paper.docx
8
+ include paper/overleaf/main.tex paper/overleaf/references.bib paper/overleaf/README.md
9
+ # Keep the exact input to the paper-table builder, not the entire run history.
10
+ include workspace/runs/qaio-simulator/solver_comparison.csv
11
+ include workspace/runs/qaio-simulator/summary.json
12
+ global-exclude *.py[cod] ~$* *.log
13
+ prune paper/tmp_render
14
+ prune paper/tmp_render_20260902
15
+ prune .idea
16
+ prune .run
17
+ prune .venv
18
+ prune .venv-py311
19
+ prune .pytest_cache
@@ -0,0 +1,505 @@
1
+ Metadata-Version: 2.4
2
+ Name: qellaris
3
+ Version: 0.5.0
4
+ Summary: Decision-focused evaluation harness for cellular sleep control: synthetic demand, interchangeable forecasters/controllers, shared capacity-flow scoring.
5
+ Author: Qellaris benchmark team
6
+ License-Expression: MIT
7
+ Project-URL: Quantum Algorithm Zoo, https://quantumalgorithmzoo.org/
8
+ Keywords: cellular networks,energy saving,cell sleep,benchmark,qubo,qaoa,quantum optimisation,reinforcement learning,mesa,agent-based model,forecasting
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Scientific/Engineering :: Physics
20
+ Classifier: Topic :: System :: Networking
21
+ Classifier: Framework :: Jupyter
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: mesa<4,>=3.3
26
+ Requires-Dist: solara<2,>=1.61
27
+ Requires-Dist: matplotlib<5,>=3.9
28
+ Requires-Dist: numpy<3,>=2
29
+ Requires-Dist: ipyvuetify<3
30
+ Requires-Dist: ipyvue<2
31
+ Requires-Dist: networkx<4,>=3.4
32
+ Requires-Dist: altair<7,>=5
33
+ Requires-Dist: gymnasium<2,>=1.0
34
+ Provides-Extra: quantum
35
+ Requires-Dist: dimod>=0.12; extra == "quantum"
36
+ Requires-Dist: dwave-samplers>=1.3; extra == "quantum"
37
+ Requires-Dist: qiskit>=1.0; extra == "quantum"
38
+ Requires-Dist: qiskit-aer>=0.15; extra == "quantum"
39
+ Requires-Dist: scipy>=1.11; extra == "quantum"
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest>=8; extra == "dev"
42
+ Requires-Dist: build>=1.2; extra == "dev"
43
+ Requires-Dist: twine>=6; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # qellaris
47
+
48
+ **A benchmark for switching mobile-network sectors off to save energy, scored
49
+ on what each decision actually costs.**
50
+
51
+ Mobile base stations burn power even when nobody is using them. Putting
52
+ lightly used radio sectors to sleep saves energy, but sleep too many and
53
+ people go unserved. `qellaris` is a test bench for methods that make that
54
+ call every 15 minutes. It provides synthetic people moving on real
55
+ OpenStreetMap streets, interchangeable demand forecasters and controllers,
56
+ and **one shared scoring rule** that grades classical, quantum-inspired and
57
+ reinforcement-learning methods in exactly the same way.
58
+
59
+ ```bash
60
+ pip install qellaris # benchmark, dashboard and command line
61
+ pip install "qellaris[quantum]" # + D-Wave samplers and Qiskit (QAOA)
62
+ qellaris menu # a guided menu: it asks, then runs the command
63
+ ```
64
+
65
+ Requires Python 3.11 or newer. Works on Windows, macOS and Linux.
66
+
67
+ > **What this is not.** It makes no claim of quantum advantage, real operator
68
+ > radio accuracy, or a calibrated digital twin of a live network. Demand,
69
+ > people and capacities are synthetic. The quantum solvers are classical
70
+ > simulations of quantum computers; nothing is sent to real quantum hardware
71
+ > unless you submit the exported job yourself.
72
+
73
+ ---
74
+
75
+ ## Contents
76
+
77
+ Why another benchmark? · Install · Five-minute tour · The dashboard, page by
78
+ page · Command-line reference · Plug in your own forecaster or controller ·
79
+ Solvers · The Quantum Algorithm Zoo page · Study areas and region packages ·
80
+ How a schedule is scored · Fair comparisons · Equity · Data, licences and
81
+ limits
82
+
83
+ ---
84
+
85
+ ## Why another benchmark?
86
+
87
+ Most forecasting papers stop at accuracy, and most optimisation papers assume
88
+ the forecast is perfect. In practice a network operator forecasts demand,
89
+ commits a schedule, and only then learns how many people actually turned up.
90
+ `qellaris` scores that whole loop, and splits every bad outcome into its two
91
+ causes:
92
+
93
+ ```
94
+ solver_gap + forecast_gap = total_decision_regret
95
+ ```
96
+
97
+ * **forecast gap**: what the forecast's errors cost you, given a perfect
98
+ optimiser;
99
+ * **solver gap**: what the optimiser cost you, given the same forecast.
100
+
101
+ All three schedules involved (yours, the reference solver's on your
102
+ forecast, and the reference solver's on the true demand) start from the same
103
+ network state and are scored by the same rule. So you can tell whether a
104
+ forecaster or an optimiser caused a bad result.
105
+
106
+ This matters because **accuracy and good decisions disagree**. In the
107
+ project's own runs, a seasonal forecaster was 5.6 times more accurate than a
108
+ flat average (by mean absolute error), yet the schedules built on it cost 58%
109
+ more in forecast gap. The flat average over-predicts in quiet hours, keeps
110
+ more sectors awake, and so rarely leaves anyone unserved. The benchmark
111
+ scores the decision, not the prediction.
112
+
113
+ ---
114
+
115
+ ## Install
116
+
117
+ ```bash
118
+ pip install qellaris
119
+ ```
120
+
121
+ Optional extras:
122
+
123
+ | Extra | Adds | Install |
124
+ | --- | --- | --- |
125
+ | `quantum` | `dimod`, `dwave-samplers`, `qiskit`, `qiskit-aer`, `scipy`: the D-Wave and QAOA solvers | `pip install "qellaris[quantum]"` |
126
+ | `dev` | `pytest`, `build`, `twine` | `pip install "qellaris[dev]"` |
127
+
128
+ Without the `quantum` extra the benchmark simply offers fewer solvers.
129
+
130
+ Downloaded maps and run results are written to a `workspace/` folder in the
131
+ current directory. Set the `QELLARIS_HOME` environment variable to put them
132
+ somewhere fixed.
133
+
134
+ ---
135
+
136
+ ## Five-minute tour
137
+
138
+ ```bash
139
+ qellaris menu
140
+ ```
141
+
142
+ The menu asks a few questions, prints the exact command it is about to run,
143
+ and runs it. Use it to learn the commands, then type them directly. Its
144
+ options:
145
+
146
+ 1. Open the dashboard
147
+ 2. Run the benchmark (quick look, all solvers, or five seeds)
148
+ 3. Browse the Quantum Algorithm Zoo
149
+ 4. Download a study area from OpenStreetMap
150
+ 5. Share or import study areas as `.zip` packages
151
+ 6. Worked examples (write your own agent)
152
+ 7. Which solvers are installed?
153
+
154
+ Or go straight to it:
155
+
156
+ ```bash
157
+ qellaris ui # dashboard at http://127.0.0.1:8765
158
+ qellaris benchmark --geography kensington # leaderboard of every installed solver
159
+ qellaris template --compare # four example policies side by side
160
+ qellaris zoo --fit direct # quantum algorithms that could schedule
161
+ ```
162
+
163
+ `python -m qellaris <command>` works the same way if your scripts folder is
164
+ not on `PATH`.
165
+
166
+ ---
167
+
168
+ ## The dashboard, page by page
169
+
170
+ `qellaris ui` opens an interactive dashboard (built with Mesa and Solara) in
171
+ your browser. The left sidebar sets the population size, random seed, demand
172
+ event, scheduling solver and map; **Step** and **▶** run the simulation, and
173
+ **Reset** rebuilds it with new settings. Six named tabs:
174
+
175
+ **Live map.** The study area with its demand areas, how busy each one is,
176
+ radio coverage, every sector (awake or asleep), the simulated people, and
177
+ which mast serves which area. A toggle colours buildings by the use the model
178
+ assigns them. Footprints whose OpenStreetMap tags state their use are drawn
179
+ solid; ones that inherit their area's use are drawn faint. Beside the map: the
180
+ time of day, the current decision and its costs, and a live **solver
181
+ leaderboard** that runs every installed solver on the interval about to be
182
+ scheduled and ranks them.
183
+
184
+ **Time series.** Total and forecast demand over time, the split of decision
185
+ cost into forecast gap and solver gap, and energy, unserved demand and equity
186
+ per interval.
187
+
188
+ **Equity audit.** Who actually gets served, across twelve synthetic
189
+ dimensions: age, income band, device, access needs, work pattern, household
190
+ and more. For each it shows the gap between best- and worst-served groups,
191
+ which direction the gap runs, and whether the energy-saving schedule caused
192
+ it. The same interval is replayed with every sector awake for comparison.
193
+
194
+ **Demographics.** Who is out there and what they are doing (travelling, at
195
+ home, at work, out), with no service information at all. It is kept separate
196
+ from the equity audit so "this group is large and busy" is never confused
197
+ with "this group is being failed".
198
+
199
+ **Study area.** Download a new area by place name or bounding box, see every
200
+ area you already have, switch to one with **Use** (no restart), save any area
201
+ as a `.zip` region package, or drop someone else's package in. Beside it, a
202
+ land-use audit shows what the area actually contains and which venue (if any)
203
+ hosts the demand event.
204
+
205
+ **Quantum Algorithm Zoo.** Every algorithm from the Quantum Algorithm Zoo
206
+ with a plain-language description, plus a guide to plugging one into the
207
+ benchmark (see the Quantum Algorithm Zoo section below).
208
+
209
+ ---
210
+
211
+ ## Command-line reference
212
+
213
+ Everything is one command, `qellaris`, with subcommands. Each subcommand also
214
+ exists as its own script (`qellaris-zoo`, `qellaris-menu`, ...).
215
+
216
+ | Command | What it does |
217
+ | --- | --- |
218
+ | `qellaris menu` | Guided menu over everything below |
219
+ | `qellaris ui [--port 8765] [--no-browser]` | Interactive dashboard |
220
+ | `qellaris benchmark [...]` | Forecast-and-schedule benchmark; prints a leaderboard and writes CSV/JSON results |
221
+ | `qellaris benchmark --list-backends` | Which solvers are installed |
222
+ | `qellaris equity --scenarios protective neutral ...` | Compare fairness rules on one identical population |
223
+ | `qellaris train --episodes 120` | Train the reinforcement-learning controller |
224
+ | `qellaris template --compare / --forecast / --explain / --broker` | Worked examples for writing your own agent |
225
+ | `qellaris region NAME --place "Camden, London" --radius-km 1` | Download a study area from OpenStreetMap |
226
+ | `qellaris region-pack list / export / import` | Share study areas as `.zip` files |
227
+ | `qellaris zoo [--fit direct] [--search WORD] [--guide]` | Quantum Algorithm Zoo catalogue and guide |
228
+ | `qellaris hardware-job JOB.json RESULT.json` | Decode results returned from a real quantum computer |
229
+ | `qellaris opencellid`, `qellaris netbravo` | Fetch optional real cell-site and signal datasets |
230
+
231
+ Useful `benchmark` options:
232
+
233
+ ```bash
234
+ qellaris benchmark --geography kensington \
235
+ --solvers greedy annealing qaoa always_on \
236
+ --scenario entertainment_event \
237
+ --seeds 17 23 42 101 2027 \
238
+ --output workspace/runs/my-run
239
+ ```
240
+
241
+ * `--geography`: `kensington` (bundled), `synthetic` (no map), or the path of
242
+ any downloaded study area
243
+ * `--scenario`: `normal`, `entertainment_event` (best venue the area has), or
244
+ a specific venue type such as `park_event`
245
+ * `--seeds`: repeat the whole experiment and report paired confidence
246
+ intervals
247
+ * `--point-forecast`: schedule on the forecast itself rather than its upper
248
+ bound
249
+ * `--inventory FILE.json`: use your own fixed tower and sector layout
250
+
251
+ Run any command with `--help` for the full list.
252
+
253
+ ---
254
+
255
+ ## Plug in your own forecaster or controller
256
+
257
+ No Mesa or Gymnasium subclassing is needed. Both receive plain JSON-safe
258
+ lists, and two decorators register them:
259
+
260
+ ```python
261
+ from qellaris import register_agent, register_forecaster
262
+ from qellaris.template import build_env, run_episode
263
+
264
+ @register_forecaster("last-value")
265
+ class LastValue:
266
+ def forecast(self, request):
267
+ last = {row["zone_id"]: row["demand"] for row in request["records"]}
268
+ return [{"step": request["train_steps"] + step, "zone_id": zone,
269
+ "prediction": value, "lower": value * 0.8, "upper": value * 1.2}
270
+ for zone, value in last.items()
271
+ for step in range(request["horizon"])]
272
+
273
+ @register_agent("busy-sectors")
274
+ class BusySectors:
275
+ def act(self, state):
276
+ sectors, width = state["network_shape"]
277
+ load = state["network_vector"]
278
+ return [int(load[i * width + 2] > 0.25) for i in range(sectors)] # 1 = awake
279
+
280
+ result = run_episode(BusySectors(), build_env(days=1))
281
+ ```
282
+
283
+ A controller receives, each interval, the forecast and its upper bound, which
284
+ sectors were awake, eight features per sector, and a separate demographic
285
+ table. It returns one 0/1 per sector. Its schedule is then scored by the
286
+ shared rule. `qellaris template --explain` prints exactly what arrives.
287
+
288
+ For reinforcement learning there is a Gymnasium environment:
289
+
290
+ ```python
291
+ import gymnasium as gym
292
+ import qellaris.control_env # registers the environment
293
+
294
+ env = gym.make("QaioSectorControl-v0", n_agents=240)
295
+ state, info = env.reset(seed=17)
296
+ state, reward, terminated, truncated, info = env.step(env.action_space.sample())
297
+ ```
298
+
299
+ The reward is the negative of the same score every solver gets, and `info`
300
+ reports energy, unserved demand and switching separately.
301
+
302
+ ---
303
+
304
+ ## Solvers
305
+
306
+ | Solver | How it chooses | Needs |
307
+ | --- | --- | --- |
308
+ | `greedy` | Builds a schedule sector by sector, then improves it locally | — |
309
+ | `exact` | Branch and bound; reports whether it *proved* its answer optimal | — |
310
+ | `annealing` | Simulated annealing on the QUBO formula | — |
311
+ | `rl` | A small learned policy (one shared linear model per sector) | — |
312
+ | `always_on` | Keeps every sector awake: the do-nothing reference | — |
313
+ | `dwave_anneal`, `tabu`, `steepest` | D-Wave's classical samplers on the QUBO | `[quantum]` |
314
+ | `qubo_exact` | Checks every combination of the QUBO core | `[quantum]` |
315
+ | `qaoa`, `qaoa_noisy` | QAOA circuits in Qiskit's simulator (ideal and noisy) | `[quantum]` |
316
+
317
+ **QUBO** is the scheduling problem rewritten as one formula over on/off
318
+ variables, the input format quantum annealers and QAOA accept. A full
319
+ network has too many sectors to simulate on a quantum computer, so `greedy`
320
+ fixes the sectors it is sure about and the quantum-style solvers decide only
321
+ the 12 to 16 it is least sure about (the *core*). They return suggested
322
+ schedules only; the shared scoring grades them, so no method can look better
323
+ by being marked differently.
324
+
325
+ When `qaoa` runs, the first test interval is also saved as
326
+ `hardware_job.json`, which holds everything a real quantum computer needs. It
327
+ is marked `PREPARED_NOT_SUBMITTED` until someone sends it. When results come
328
+ back, `qellaris hardware-job` turns them into a full schedule.
329
+
330
+ ---
331
+
332
+ ## The Quantum Algorithm Zoo page
333
+
334
+ The [Quantum Algorithm Zoo](https://quantumalgorithmzoo.org/), maintained by
335
+ Stephen Jordan, catalogues quantum algorithms that are faster than the best
336
+ known classical methods for some problem. `qellaris` lists all 74 entries, on
337
+ the dashboard and from the command line, each with a short description of
338
+ what the algorithm does and how. Each one is also rated for where it could fit
339
+ in this benchmark:
340
+
341
+ | Rating | Count | Meaning | Examples |
342
+ | --- | --- | --- | --- |
343
+ | Wired in | 1 | Already runs here, on a simulator | QAOA |
344
+ | Direct fit | 9 | Could choose which sectors sleep | Grover search, quantum annealing, quantum-walk simulated annealing, thermal-state sampling, quantum backtracking, SDP bounds, DQI |
345
+ | Adjacent | 13 | Could help forecasting, training or checking results | linear systems (HHL), quantum ML, gradient estimation, network flows |
346
+ | Not applicable | 51 | Solves a problem not in this benchmark | Shor's factoring, knot invariants, hidden subgroups |
347
+
348
+ Two more views explain how to add one, and in which benchmark scenarios it
349
+ could make a difference. Examples are demand spikes, proving a schedule is
350
+ optimal, and larger problems on real hardware.
351
+
352
+ ```bash
353
+ qellaris zoo # the whole catalogue with descriptions
354
+ qellaris zoo --fit direct # only the ones that could produce a schedule
355
+ qellaris zoo --search annealing
356
+ qellaris zoo --guide # how to plug one in + which scenario to use
357
+ ```
358
+
359
+ ```python
360
+ from qellaris import quantum_zoo
361
+
362
+ for row in quantum_zoo.by_fit("direct"):
363
+ print(row["name"], "-", row["description"])
364
+ print(" fits at:", row["hook"])
365
+ print(" how:", row["how"])
366
+ print(" when:", row["when"])
367
+ ```
368
+
369
+ Adding a new schedule-producing algorithm means one subclass with one method:
370
+
371
+ ```python
372
+ from qellaris.quantum import QUANTUM_SOLVERS, QuboBackendSolver
373
+ from qellaris.solvers import SolverProvenance
374
+
375
+ class MyQuantumSolver(QuboBackendSolver):
376
+ provenance = SolverProvenance("my_quantum", "quantum", "simulator",
377
+ "what it is, for the results table")
378
+ core_size = 14
379
+
380
+ def available(self):
381
+ return True
382
+
383
+ def sample(self, reduced):
384
+ # `reduced` is the QUBO for the core. Return on/off lists, best first.
385
+ return my_algorithm(reduced)
386
+
387
+ QUANTUM_SOLVERS["my_quantum"] = MyQuantumSolver # now usable in --solvers
388
+ ```
389
+
390
+ The zoo's speedups describe how each algorithm scales on very large versions
391
+ of its own problem. None was measured on this benchmark.
392
+
393
+ ---
394
+
395
+ ## Study areas and region packages
396
+
397
+ The **Kensington** (London) study area ships inside the package and works
398
+ offline. Any other area can be downloaded from OpenStreetMap by name or by
399
+ bounding box:
400
+
401
+ ```bash
402
+ qellaris region kreuzberg --place "Kreuzberg, Berlin" --radius-km 1.0
403
+ qellaris region nottingham --bbox 52.938,-1.172,52.966,-1.125
404
+ qellaris benchmark --geography workspace/regions/kreuzberg
405
+ ```
406
+
407
+ A download saves streets, land use, building footprints and mapped masts,
408
+ plus a manifest counting what arrived, so an empty layer is visible rather
409
+ than silent. Areas wider than 12 km are refused before any request is made.
410
+
411
+ Downloads take minutes, so a finished area can be passed on as a single
412
+ **region package**:
413
+
414
+ ```bash
415
+ qellaris region-pack list # what you have, and whether it is complete
416
+ qellaris region-pack export kreuzberg -o kreuzberg.zip
417
+ qellaris region-pack import kreuzberg.zip # on another machine
418
+ ```
419
+
420
+ Importing accepts only the known map-layer files, refuses paths that would
421
+ land outside the regions folder, and never runs anything inside the package.
422
+ The dashboard's Study area page offers the same three actions with buttons.
423
+
424
+ The map shapes the simulation: the land use found in the area sets the mix of
425
+ residents, commuters, students and visitors, and decides which demand events
426
+ are possible. An area with no stadium gets no match day.
427
+
428
+ ---
429
+
430
+ ## How a schedule is scored
431
+
432
+ Energy uses the EARTH base-station power model (Auer et al., 2011). Each
433
+ transceiver chain draws 130 W when awake and idle, plus 4.7 W for every watt
434
+ radiated (up to 20 W), and 75 W asleep. A 2x2 MIMO sector therefore draws
435
+ 260 W idle, 448 W at full load and 150 W asleep. A sleeping sector is not
436
+ free, and that is counted.
437
+
438
+ Each schedule is scored by:
439
+
440
+ * routing demand to awake sectors with a capacity-limited maximum flow;
441
+ * totalling energy in watt-hours, load-dependent;
442
+ * adding a price for unserved demand (550 Wh per unit), a larger price for
443
+ falling below 95% service in critical areas such as hospitals (1,100 Wh),
444
+ and a small price per sector switched on or off (1.375 Wh).
445
+
446
+ Lower is better. The prices are policy choices, stated openly, not
447
+ measurements. Each person's service comes from the same flow calculation, so
448
+ the dashboard's "people served" always matches the solver's "demand served".
449
+
450
+ ---
451
+
452
+ ## Fair comparisons
453
+
454
+ * **Paired seeds.** Within a seed every solver faces exactly the same people
455
+ and demand, so differences are measured seed by seed. That removes the
456
+ large seed-to-seed variation that would otherwise hide real differences.
457
+ `--seeds` reports bootstrap confidence intervals for each paired
458
+ difference.
459
+ * **Held-out events.** A demand spike the forecaster never saw runs beside
460
+ an identical run without it, so you can see where robustness breaks.
461
+ * **Separate regimes.** Service is reported separately for quiet periods and
462
+ the event, so an overloaded event cell cannot hide differences elsewhere.
463
+ * **Switching and equity** are reported next to energy, so a saving cannot
464
+ hide as constant sector flapping or as one neglected group.
465
+ * **Honest proofs.** `exact` reports `proven_optimal` per interval rather
466
+ than implying a proof it ran out of time to finish.
467
+
468
+ ---
469
+
470
+ ## Equity
471
+
472
+ Fairness is built into the allocation and the audit, not added as a chart at
473
+ the end. Critical areas (healthcare, for example) get a 95% service floor.
474
+ When capacity runs short inside an area, people with more constraints are
475
+ served first. The audit reports demand-weighted service gaps across synthetic
476
+ groups and attributes each gap to either the network or the energy-saving
477
+ schedule.
478
+
479
+ `qellaris equity` compares twelve named fairness rules on one identical
480
+ population, from `neutral` (no one prioritised) through `headcount` and
481
+ `volume_first` to `maximum_protection`.
482
+
483
+ All attributes are synthetic scenario variables, not data about real people.
484
+ Gender, ethnicity and language are not modelled. Income band is recorded for
485
+ audit only and never changes behaviour.
486
+
487
+ ---
488
+
489
+ ## Data, licences and limits
490
+
491
+ * **Code:** MIT licence.
492
+ * **Bundled Kensington map:** © OpenStreetMap contributors, Open Database
493
+ Licence (ODbL). Downloaded areas carry the same attribution.
494
+ * **Optional datasets:** OpenCellID (needs a free key) for approximate cell
495
+ sites; JRC NetBravo (DOI 10.2905/JRC.YA8MHB0) for observed signal strength,
496
+ used only to calibrate, never relabelled as observed demand.
497
+ * **Synthetic by design:** demand, people, demographics, sector capacities,
498
+ directions and bands are modelled unless you supply a real inventory. Do
499
+ not describe results as observed operator traffic.
500
+ * **Single operator:** the benchmark models one network; there is no
501
+ competition between operators.
502
+ * **Not a placement tool:** tower positions and sector directions are inputs
503
+ (automatic or your JSON file), not something the benchmark optimises.
504
+
505
+ The test suite ships inside the package as `qellaris.tests`.
@@ -0,0 +1,140 @@
1
+ # Publishing `qellaris` to PyPI
2
+
3
+ Python packages are published on **PyPI** (<https://pypi.org>), the Python
4
+ Package Index run by the Python Software Foundation. python.org itself does
5
+ not host packages. Once an upload goes through, anyone can run
6
+ `pip install qellaris`.
7
+
8
+ The name `qellaris` was unclaimed on PyPI on 2026-10-17. The first upload
9
+ claims it for your account.
10
+
11
+ ## What is already set up
12
+
13
+ | File | Purpose |
14
+ | --- | --- |
15
+ | `pyproject.toml` | Name, version (0.5.0), dependencies, `[quantum]`/`[dev]` extras, console scripts, classifiers, project URLs, licence |
16
+ | `PYPI_README.md` | The project page PyPI shows (`readme =` in `pyproject.toml`) |
17
+ | `LICENSE` | MIT, plus the ODbL notice for the bundled OSM extracts |
18
+ | `MANIFEST.in` | What goes into the source archive (sdist) |
19
+ | `src/qellaris/_version.py` | Runtime `qellaris.__version__`. Keep it equal to `pyproject.toml` |
20
+
21
+ ## 1. One-time account setup
22
+
23
+ 1. Create accounts on <https://pypi.org/account/register/> and on the test
24
+ index <https://test.pypi.org/account/register/>. They are separate.
25
+ 2. Turn on two-factor authentication. PyPI requires it before you can upload.
26
+ 3. Create an API token under *Account settings → API tokens*. The first token
27
+ has to be "entire account" scope because the project does not exist yet.
28
+ After the first upload, replace it with a token scoped to `qellaris`.
29
+ 4. Do the same on TestPyPI.
30
+
31
+ Never commit a token. Either let twine prompt for it (username `__token__`,
32
+ password = the token), or set it for the current shell only:
33
+
34
+ ```powershell
35
+ $env:TWINE_USERNAME = "__token__"
36
+ $env:TWINE_PASSWORD = "<paste token here>"
37
+ ```
38
+
39
+ ## 2. Run the tests
40
+
41
+ ```powershell
42
+ python -m pip install -e ".[dev]"
43
+ python -m pytest
44
+ ```
45
+
46
+ ## 3. Build
47
+
48
+ ```powershell
49
+ Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue
50
+ python -m build
51
+ ```
52
+
53
+ This writes `dist/qellaris-0.5.0-py3-none-any.whl` and
54
+ `dist/qellaris-0.5.0.tar.gz`.
55
+
56
+ ## 4. Check the build
57
+
58
+ ```powershell
59
+ python -m twine check dist/*
60
+ ```
61
+
62
+ Both files must say `PASSED`. This checks that `PYPI_README.md` will render
63
+ on the project page.
64
+
65
+ Then smoke-test the wheel in a clean environment:
66
+
67
+ ```powershell
68
+ python -m venv $env:TEMP\qellaris-smoke
69
+ & $env:TEMP\qellaris-smoke\Scripts\python.exe -m pip install dist\qellaris-0.5.0-py3-none-any.whl
70
+ & $env:TEMP\qellaris-smoke\Scripts\qellaris.exe zoo --fit wired
71
+ & $env:TEMP\qellaris-smoke\Scripts\qellaris.exe benchmark --list-backends
72
+ ```
73
+
74
+ ## 5. Rehearse on TestPyPI
75
+
76
+ ```powershell
77
+ python -m twine upload --repository testpypi dist/*
78
+ ```
79
+
80
+ Install it back from there. The extra index lets pip find Mesa, Solara and
81
+ the other dependencies, which TestPyPI does not host:
82
+
83
+ ```powershell
84
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ qellaris
85
+ ```
86
+
87
+ ## 6. Publish for real
88
+
89
+ ```powershell
90
+ python -m twine upload dist/*
91
+ ```
92
+
93
+ You can't overwrite or reuse a version number on PyPI, even after deleting
94
+ it. If something is wrong, bump the version and upload again.
95
+
96
+ ## Releasing a new version
97
+
98
+ 1. Bump `version` in `pyproject.toml` **and** `__version__` in
99
+ `src/qellaris/_version.py`.
100
+ 2. Repeat steps 2–6.
101
+ 3. Tag the commit: `git tag v0.5.1` and `git push --tags`.
102
+
103
+ ## Optional: publish from GitHub without a token
104
+
105
+ PyPI's *Trusted Publishing* lets a GitHub Actions workflow upload with no
106
+ stored secret. On PyPI, open *Your projects → qellaris → Publishing* and add
107
+ the repository `newguy1234/qaio-cellular-benchmark` with the workflow file
108
+ name. Then add `.github/workflows/publish.yml`:
109
+
110
+ ```yaml
111
+ name: publish
112
+ on:
113
+ release:
114
+ types: [published]
115
+ jobs:
116
+ pypi:
117
+ runs-on: ubuntu-latest
118
+ environment: pypi
119
+ permissions:
120
+ id-token: write
121
+ steps:
122
+ - uses: actions/checkout@v4
123
+ - uses: actions/setup-python@v5
124
+ with:
125
+ python-version: "3.12"
126
+ - run: python -m pip install build
127
+ - run: python -m build
128
+ - uses: pypa/gh-action-pypi-publish@release/v1
129
+ ```
130
+
131
+ Publishing a GitHub release then uploads that version.
132
+
133
+ ## Before the first upload, check
134
+
135
+ - [ ] The author in `pyproject.toml` (`Qellaris benchmark team`) and the
136
+ copyright line in `LICENSE` are how you want to be credited publicly.
137
+ - [ ] The GitHub URLs in `[project.urls]` point at the public repository.
138
+ - [ ] `python -m twine check dist/*` passes.
139
+ - [ ] The wheel is a reasonable size. It carries the bundled Kensington
140
+ OSM extracts, and PyPI's default limit is 100 MB per file.