cadaques 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.
Files changed (90) hide show
  1. {cadaques-0.2.0 → cadaques-0.3.0}/.github/workflows/ci.yml +2 -2
  2. {cadaques-0.2.0 → cadaques-0.3.0}/.gitignore +2 -0
  3. {cadaques-0.2.0 → cadaques-0.3.0}/CHANGELOG.md +46 -1
  4. {cadaques-0.2.0 → cadaques-0.3.0}/CITATION.cff +1 -1
  5. {cadaques-0.2.0 → cadaques-0.3.0}/PKG-INFO +97 -40
  6. {cadaques-0.2.0 → cadaques-0.3.0}/README.md +94 -39
  7. cadaques-0.3.0/examples/chemistry_recommend.py +68 -0
  8. {cadaques-0.2.0 → cadaques-0.3.0}/pyproject.toml +2 -1
  9. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/__init__.py +4 -2
  10. cadaques-0.3.0/src/cadaques/drivers/__init__.py +14 -0
  11. cadaques-0.3.0/src/cadaques/drivers/bo.py +256 -0
  12. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/drivers/reference.py +6 -2
  13. cadaques-0.3.0/src/cadaques/oracles/__init__.py +5 -0
  14. cadaques-0.3.0/src/cadaques/oracles/dataset.py +216 -0
  15. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/__init__.py +2 -1
  16. cadaques-0.3.0/src/cadaques/runtime/recommend.py +285 -0
  17. cadaques-0.3.0/src/cadaques/stats/__init__.py +235 -0
  18. cadaques-0.3.0/src/cadaques/verticals/__init__.py +11 -0
  19. cadaques-0.3.0/src/cadaques/verticals/chemistry.py +221 -0
  20. cadaques-0.3.0/studies/README.md +68 -0
  21. cadaques-0.3.0/studies/data/PROVENANCE.md +15 -0
  22. cadaques-0.3.0/studies/data/ocm291.csv +292 -0
  23. cadaques-0.3.0/studies/data/olympus_fullerenes.csv +247 -0
  24. cadaques-0.3.0/studies/data/olympus_snar.csv +67 -0
  25. cadaques-0.3.0/studies/ocm/featurize.py +114 -0
  26. cadaques-0.3.0/studies/ocm/prepare_ocm_full.py +56 -0
  27. cadaques-0.3.0/studies/run_study.py +218 -0
  28. cadaques-0.3.0/tests/test_bo_driver.py +85 -0
  29. cadaques-0.3.0/tests/test_chemistry_vertical.py +100 -0
  30. cadaques-0.3.0/tests/test_dataset_oracle.py +107 -0
  31. cadaques-0.3.0/tests/test_recommend.py +101 -0
  32. cadaques-0.3.0/tests/test_stats.py +104 -0
  33. cadaques-0.2.0/src/cadaques/drivers/__init__.py +0 -1
  34. cadaques-0.2.0/src/cadaques/oracles/__init__.py +0 -2
  35. {cadaques-0.2.0 → cadaques-0.3.0}/.readthedocs.yaml +0 -0
  36. {cadaques-0.2.0 → cadaques-0.3.0}/ARCHITECTURE.md +0 -0
  37. {cadaques-0.2.0 → cadaques-0.3.0}/LICENSE +0 -0
  38. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0001-campaign-is-the-aggregate-root.md +0 -0
  39. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0002-query-and-result-are-evaluation-specializations-of-action-and-observation.md +0 -0
  40. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0003-oracle-versus-resource-a-dual-protocol-not-a-rename.md +0 -0
  41. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0004-the-event-log-is-the-source-of-truth.md +0 -0
  42. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0005-schema-version-fields-now-migration-framework-later.md +0 -0
  43. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0006-failure-is-a-scientific-result-cost-invariants-have-veto-power.md +0 -0
  44. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0007-synchronous-first-the-async-boundary-is-the-resource-lifecycle.md +0 -0
  45. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0008-core-dependency-policy.md +0 -0
  46. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0009-plugins-by-ordinary-packaging-and-entry-points.md +0 -0
  47. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0010-semantic-versioning-and-pre-10-guarantees.md +0 -0
  48. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0011-the-driver-view-boundary.md +0 -0
  49. {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0012-determinism-discipline-one-seed-named-streams.md +0 -0
  50. {cadaques-0.2.0 → cadaques-0.3.0}/docs/api/core.rst +0 -0
  51. {cadaques-0.2.0 → cadaques-0.3.0}/docs/api/drivers.rst +0 -0
  52. {cadaques-0.2.0 → cadaques-0.3.0}/docs/api/oracles.rst +0 -0
  53. {cadaques-0.2.0 → cadaques-0.3.0}/docs/concepts.rst +0 -0
  54. {cadaques-0.2.0 → cadaques-0.3.0}/docs/conf.py +0 -0
  55. {cadaques-0.2.0 → cadaques-0.3.0}/docs/index.rst +0 -0
  56. {cadaques-0.2.0 → cadaques-0.3.0}/docs/quickstart.rst +0 -0
  57. {cadaques-0.2.0 → cadaques-0.3.0}/examples/ising_tc.py +0 -0
  58. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/__init__.py +0 -0
  59. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/artifacts.py +0 -0
  60. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/campaign.py +0 -0
  61. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/cost.py +0 -0
  62. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/events.py +0 -0
  63. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/ledger.py +0 -0
  64. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/observation.py +0 -0
  65. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/protocols.py +0 -0
  66. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/records.py +0 -0
  67. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/spec.py +0 -0
  68. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/task.py +0 -0
  69. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/oracles/analytic.py +0 -0
  70. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/oracles/ising.py +0 -0
  71. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/__init__.py +0 -0
  72. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/driver.py +0 -0
  73. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/oracle.py +0 -0
  74. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/resource.py +0 -0
  75. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/campaign.py +0 -0
  76. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/replay.py +0 -0
  77. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/state.py +0 -0
  78. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/testing/__init__.py +0 -0
  79. {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/testing/contracts.py +0 -0
  80. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_artifacts.py +0 -0
  81. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_cadaques.py +0 -0
  82. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_contracts.py +0 -0
  83. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_envelopes.py +0 -0
  84. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_events.py +0 -0
  85. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_replay.py +0 -0
  86. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_resource.py +0 -0
  87. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_seeds.py +0 -0
  88. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_spec.py +0 -0
  89. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_state.py +0 -0
  90. {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_task.py +0 -0
@@ -1,6 +1,6 @@
1
1
  name: CI
2
2
  on:
3
- push: { branches: [main] }
3
+ push: { branches: ["**"] }
4
4
  pull_request:
5
5
  jobs:
6
6
  test:
@@ -12,5 +12,5 @@ jobs:
12
12
  - uses: actions/checkout@v4
13
13
  - uses: actions/setup-python@v5
14
14
  with: { python-version: "${{ matrix.python-version }}" }
15
- - run: pip install -e ".[dev]"
15
+ - run: pip install -e ".[dev,bo]"
16
16
  - run: python -m pytest tests/ -q --cov=cadaques
@@ -10,3 +10,5 @@ docs/_build/
10
10
  docs/_build/
11
11
  .DS_Store
12
12
  demo_campaign.jsonl # opcional: y ckpt_demo/ si quieres
13
+ examples/next_experiments.csv
14
+ studies/results/
@@ -5,7 +5,52 @@ the pre-1.0 stability policy. The claim of record is the latest tag.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
- ## [0.2.0] — 2026-08
8
+ ## [0.3.0] — 2026-08 — DOI: [10.5281/zenodo.22028069](https://doi.org/10.5281/zenodo.22028069)
9
+
10
+ The generality proof: retrospective campaigns over real data, cost-aware
11
+ intelligence, audited statistics, recommendation mode, and the first
12
+ domain vertical — on the unchanged 0.2 kernel.
13
+
14
+ ### Added
15
+ - **DatasetOracle** (`oracles/dataset.py`): a measured table as an Oracle —
16
+ declared miss policy (`nearest` within normalized tolerance, or FAILED
17
+ `dataset_miss` settling the tariff: a miss consumes budget), deterministic
18
+ economics with optional per-row recorded cost columns, `from_csv`,
19
+ data-derived `.bounds`, and `.as_history()` for recommendation mode.
20
+ - **BayesianDriver** (`drivers/bo.py`, extra `cadaques[bo]`): Matérn GP +
21
+ Expected Improvement per predicted unit cost (cost surrogate fitted on the
22
+ driver's own settled history), budget-annealed exploration via
23
+ `fraction_used`, cost-aware flat-EI fallback, `rank()` for batch scoring;
24
+ scikit-learn stays out of core (lazy import).
25
+ - **cadaques.stats**: paired bootstrap CI and Wilcoxon signed-rank on NumPy
26
+ alone (exact null to n=25, tie/continuity-corrected approximation beyond),
27
+ `compare()`/`Comparison.summary()`, and seed-paired `paired_campaigns()`.
28
+ - **Recommendation mode** (`runtime/recommend.py`): `recommend()` →
29
+ `RankedCandidates` — seeded bit-for-bit reproducible, provenanced (driver
30
+ spec, history sha256, schema `cadaques.recommendation/1`), metered thinking
31
+ time, `to_csv`/`to_json`; task constraints filter the candidate pool
32
+ *before* ranking.
33
+ - **Chemistry vertical** (`verticals/chemistry.py`): `ReactionTable` — column
34
+ roles (composition, conditions, objective, cost) compiled to kernel
35
+ objects; `recommend_next()` and `campaign()` frontends; end-to-end
36
+ `examples/chemistry_recommend.py`.
37
+ - **Studies package** (`studies/`): frozen protocol v1 for the vertical paper
38
+ — pinned quartet (Ising, OCM-291 featurized, Olympus snar/fullerenes with
39
+ real per-row costs), 20 paired seeds × 4 drivers, pre-registered honesty
40
+ notes, `run_study.py --smoke` verified on all four.
41
+
42
+ ### Changed
43
+ - Reference drivers consume rng in **sorted parameter order** (ADR-0012), so
44
+ canonical-JSON spec round-trips and dict orderings cannot alter a seeded
45
+ campaign. Seeded sequences differ from 0.2 in multi-parameter spaces; the
46
+ paper reproduction stays pinned at `v0.1.0-paper`.
47
+ - CI runs on all branches and installs `[dev,bo]`.
48
+
49
+ ### Compatibility
50
+ - No interface changes; all 0.2 and 0.1 imports and constructor calls
51
+ unchanged; 107 tests.
52
+
53
+ ## [0.2.0] — 2026-08 — DOI: [10.5281/zenodo.21915298](https://doi.org/10.5281/zenodo.21915298)
9
54
 
10
55
  The campaign becomes a durable object: the twelve-object kernel of the
11
56
  campaign architecture, event-sourced and replayable, with full 0.1
@@ -8,7 +8,7 @@ authors:
8
8
  given-names: "Jorge"
9
9
  affiliation: "Universidad Autónoma de Madrid, IFIMAC"
10
10
  orcid: "https://orcid.org/0000-0001-6876-8022"
11
- version: 0.2.0
11
+ version: 0.3.0
12
12
  date-released: 2026-08-07
13
13
  license: MIT
14
14
  repository-code: "https://github.com/jorgebravoabad/cadaques"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cadaques
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Cost-Aware Dual Architecture for QUery-Efficient diScovery: autonomous discovery campaigns — any oracle, any driver, one budget.
5
5
  Project-URL: Repository, https://github.com/jorgebravoabad/cadaques
6
6
  Author-email: Jorge Bravo-Abad <jorge.bravo@uam.es>
@@ -14,6 +14,8 @@ Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Topic :: Scientific/Engineering :: Physics
15
15
  Requires-Python: >=3.10
16
16
  Requires-Dist: numpy>=1.24
17
+ Provides-Extra: bo
18
+ Requires-Dist: scikit-learn>=1.3; extra == 'bo'
17
19
  Provides-Extra: dev
18
20
  Requires-Dist: mypy; extra == 'dev'
19
21
  Requires-Dist: pytest-cov; extra == 'dev'
@@ -47,7 +49,8 @@ per unit cost. *Every query counts.*
47
49
  ## Installation
48
50
 
49
51
  ```bash
50
- pip install cadaques
52
+ pip install cadaques # core: NumPy only
53
+ pip install "cadaques[bo]" # + the Gaussian-process driver (scikit-learn)
51
54
  ```
52
55
 
53
56
  Or, for development, install the latest version from source:
@@ -84,7 +87,7 @@ print(f"Queries: {outcome.n_queries}, stop reason: {outcome.stop_reason}")
84
87
  print(f"Spent: {outcome.budget.spent}")
85
88
  ```
86
89
 
87
- ## The campaign is a durable object (0.2)
90
+ ## The campaign is a durable object (since 0.2)
88
91
 
89
92
  Since 0.2 a campaign is not a script you ran once — it is a declarative,
90
93
  inspectable, replayable object:
@@ -131,10 +134,62 @@ What this buys, concretely:
131
134
  - **Conformance suites.** `cadaques.testing` ships the Oracle/Driver/
132
135
  Resource contract checks; external adapters are encouraged to run them.
133
136
 
137
+ ## From a spreadsheet to the next experiments (0.3)
138
+
139
+ The first domain vertical. A chemist declares column roles once — composition
140
+ descriptors, reaction conditions, an objective, a recorded cost per run — and
141
+ the table compiles to kernel objects:
142
+
143
+ ```python
144
+ from cadaques.verticals import ReactionTable
145
+
146
+ table = ReactionTable.from_csv(
147
+ "reactions.csv",
148
+ objective="c2_yield",
149
+ composition=("x_Mn", "x_Ce"),
150
+ conditions=("T", "P"),
151
+ cost_columns={"seconds": "run_s"},
152
+ )
153
+
154
+ # Recommendation mode: which experiments should the lab run next?
155
+ ranking = table.recommend_next(k=5, seed=42) # requires: pip install "cadaques[bo]"
156
+ print(ranking.summary())
157
+ ranking.to_csv("next_experiments.csv") # for the lab notebook
158
+
159
+ # Campaign mode: would Bayesian optimization have beaten random on this table?
160
+ from cadaques import Budget, Cost
161
+ from cadaques.drivers import BayesianDriver, RandomDriver
162
+ from cadaques.stats import paired_campaigns
163
+
164
+ result = paired_campaigns(
165
+ lambda seed: table.campaign(BayesianDriver(space=table.oracle().bounds),
166
+ Budget(total=Cost(seconds=25000)), seed=seed),
167
+ lambda seed: table.campaign(RandomDriver(space=table.oracle().bounds),
168
+ Budget(total=Cost(seconds=25000)), seed=seed),
169
+ seeds=range(20), metric=lambda o: o.best.value, alternative="greater",
170
+ )
171
+ print(result.summary()) # paired bootstrap CI + Wilcoxon, quotable as-is
172
+ ```
173
+
174
+ Behind this sit the 0.3 components: **`DatasetOracle`** (a measured table as a
175
+ hidden oracle, with a *declared* miss policy — a dataset miss is a FAILED result
176
+ that settles its declared cost — and deterministic economics that can replay
177
+ recorded per-row costs); **`BayesianDriver`** (Matérn GP with Expected
178
+ Improvement *per predicted unit cost*, learned from the driver's own settled
179
+ history, exploration annealed with `fraction_used`); **`cadaques.stats`** (paired
180
+ bootstrap and Wilcoxon on NumPy alone, exact null to n=25); and **recommendation
181
+ mode** (`recommend()` → a ranked, seeded, bit-for-bit reproducible object whose
182
+ provenance records the driver's declaration, a fingerprint of the history, and
183
+ the measured *thinking time* — the price of intelligence, attached to the advice
184
+ it produced). A frozen retrospective-benchmark protocol over four public
185
+ datasets lives in `studies/`.
186
+
134
187
  ## Design principles
135
188
 
136
189
  - **Dual agnosticism.** Oracles and Drivers are `typing.Protocol` classes with
137
- two methods each. Anything that speaks the protocol plugs in.
190
+ two methods each. Anything that speaks the protocol plugs in. Since 0.2 the
191
+ dual architecture has an operational counterpart: `Resource`
192
+ (submit/status/collect) with an `OracleResource` adapter.
138
193
  - **Declared vs. settled cost.** Oracles declare a price *ex ante*
139
194
  (`oracle.price(query)`); the actual cost is settled *ex post* inside each
140
195
  `Result`. Real oracles deviate from their estimates — the ledger records both,
@@ -142,31 +197,15 @@ What this buys, concretely:
142
197
  - **Both sides are metered.** Driver decisions cost wall time — and tokens, if the
143
198
  driver is an LLM agent. A campaign's economics include the price of intelligence,
144
199
  enabling the question: *when does an expensive smart driver beat a cheap dumb one?*
200
+ - **Failure is a result.** A failed evaluation — an oracle exception, a dataset
201
+ miss — settles its declared cost and stays on the record, exactly like a wasted
202
+ experiment.
145
203
  - **Budget-aware strategies.** Drivers receive a read-only `BudgetView` and may
146
204
  adapt: the reference `AnnealedLocalDriver` explores while rich and exploits
147
- while poor.
148
- - **The ledger is the provenance.** Every transaction (declared, settled,
149
- timestamped) exports to JSONL: a complete, replayable trace of the campaign.
150
-
151
- ## Status and roadmap
152
-
153
- `0.1.0` — core protocols, campaign runner, multi-currency budget and ledger,
154
- reference drivers, and a canonical Ising-2D oracle with fidelity-dependent cost.
155
- Available on [PyPI](https://pypi.org/project/cadaques/) and archived on
156
- [Zenodo](https://doi.org/10.5281/zenodo.21293589).
157
-
158
- This is an early release: the API may evolve until `1.0`. Planned next steps
159
- include a Bayesian-optimization driver, campaign replay, expanded documentation,
160
- and an LLM-agent driver adapter.
161
-
162
- ## Citation
163
-
164
- If you use CADAQUES in academic work, please cite it (see `CITATION.cff`).
165
- DOI: [10.5281/zenodo.21293589](https://doi.org/10.5281/zenodo.21293589)
166
-
167
- ## License
168
-
169
- MIT.
205
+ while poor; `BayesianDriver` anneals its exploration margin the same way.
206
+ - **The event log is the provenance.** Every transition (declared, settled,
207
+ timestamped, schema-versioned) exports to JSONL: a complete, replayable trace
208
+ of the campaign, from which the accounting ledger is derived.
170
209
 
171
210
  ## Status, stability and roadmap
172
211
 
@@ -176,22 +215,40 @@ roadmap below is a plan, not a feature list.
176
215
 
177
216
  *Stability policy (ADR-0010).* Semantic versioning; nothing public breaks
178
217
  without a deprecation shim spanning at least two minor releases. The exact
179
- implementation accompanying the arXiv paper is permanently tagged
180
- (`v0.1.0`) and its imports run against every release via compatibility
181
- shims (`cadaques.core.protocols`, `cadaques.core.campaign`,
182
- `CampaignResult`). Durable design decisions live in `docs/adr/`.
218
+ implementation accompanying v1 of the arXiv paper is permanently tagged
219
+ (`v0.1.0-paper`), and its imports run against every release via compatibility
220
+ shims (`cadaques.core.protocols`, `cadaques.core.campaign`, `CampaignResult`).
221
+ Durable design decisions live in `docs/adr/`.
183
222
 
184
223
  *Kernel (shipped in 0.2).* Task · Query/Result and the general
185
224
  Action/Observation envelopes · Driver · Oracle · Resource (with the
186
225
  `OracleResource` adapter) · Campaign · Budget · Event · Artifact ·
187
226
  Outcome · CampaignSpec — the twelve objects of the campaign
188
227
  architecture, with event-sourced state, seed streams, replay,
189
- checkpoint/resume and public conformance suites.
190
-
191
- *Roadmap (not yet shipped).* A Bayesian-optimization driver and a
192
- hidden-dataset oracle for retrospective studies; a statistics module for
193
- paired driver comparisons; asynchronous executors and a Slurm-backed
194
- workflow resource behind the frozen Resource lifecycle (ADR-0007);
195
- declarative constraint vocabulary for specs; plugin entry points for
196
- external drivers and oracles. See `ARCHITECTURE.md` and
197
- `docs/adr/` for boundaries and decision gates.
228
+ checkpoint/resume and public conformance suites (`cadaques.testing`).
229
+
230
+ *Generality proof (shipped in 0.3).* `DatasetOracle` for retrospective
231
+ campaigns over measured tables; the cost-aware `BayesianDriver` (extra
232
+ `cadaques[bo]`); the audited `cadaques.stats` module; recommendation mode
233
+ (`recommend()`, `RankedCandidates`); the chemistry vertical
234
+ (`ReactionTable`); and the frozen study protocol in `studies/`.
235
+
236
+ *Roadmap (not yet shipped).* Asynchronous executors and a Slurm-backed
237
+ workflow resource behind the frozen Resource lifecycle (ADR-0007); a
238
+ portfolio Driver allocating budget across strategies as a cost-aware
239
+ bandit; native categorical and mixed search spaces; declarative
240
+ constraint vocabulary for specs; plugin entry points for external
241
+ drivers and oracles; an LLM-agent driver adapter with token metering.
242
+ See `ARCHITECTURE.md` and `docs/adr/` for boundaries and decision gates.
243
+
244
+ ## Citation
245
+
246
+ If you use CADAQUES in academic work, please cite it (see `CITATION.cff`).
247
+ Concept DOI (always resolves to the latest version):
248
+ [10.5281/zenodo.21293589](https://doi.org/10.5281/zenodo.21293589).
249
+ Version DOIs are listed per release in [`CHANGELOG.md`](CHANGELOG.md).
250
+ Paper: [arXiv:2607.16127](https://arxiv.org/abs/2607.16127).
251
+
252
+ ## License
253
+
254
+ MIT.
@@ -21,7 +21,8 @@ per unit cost. *Every query counts.*
21
21
  ## Installation
22
22
 
23
23
  ```bash
24
- pip install cadaques
24
+ pip install cadaques # core: NumPy only
25
+ pip install "cadaques[bo]" # + the Gaussian-process driver (scikit-learn)
25
26
  ```
26
27
 
27
28
  Or, for development, install the latest version from source:
@@ -58,7 +59,7 @@ print(f"Queries: {outcome.n_queries}, stop reason: {outcome.stop_reason}")
58
59
  print(f"Spent: {outcome.budget.spent}")
59
60
  ```
60
61
 
61
- ## The campaign is a durable object (0.2)
62
+ ## The campaign is a durable object (since 0.2)
62
63
 
63
64
  Since 0.2 a campaign is not a script you ran once — it is a declarative,
64
65
  inspectable, replayable object:
@@ -105,10 +106,62 @@ What this buys, concretely:
105
106
  - **Conformance suites.** `cadaques.testing` ships the Oracle/Driver/
106
107
  Resource contract checks; external adapters are encouraged to run them.
107
108
 
109
+ ## From a spreadsheet to the next experiments (0.3)
110
+
111
+ The first domain vertical. A chemist declares column roles once — composition
112
+ descriptors, reaction conditions, an objective, a recorded cost per run — and
113
+ the table compiles to kernel objects:
114
+
115
+ ```python
116
+ from cadaques.verticals import ReactionTable
117
+
118
+ table = ReactionTable.from_csv(
119
+ "reactions.csv",
120
+ objective="c2_yield",
121
+ composition=("x_Mn", "x_Ce"),
122
+ conditions=("T", "P"),
123
+ cost_columns={"seconds": "run_s"},
124
+ )
125
+
126
+ # Recommendation mode: which experiments should the lab run next?
127
+ ranking = table.recommend_next(k=5, seed=42) # requires: pip install "cadaques[bo]"
128
+ print(ranking.summary())
129
+ ranking.to_csv("next_experiments.csv") # for the lab notebook
130
+
131
+ # Campaign mode: would Bayesian optimization have beaten random on this table?
132
+ from cadaques import Budget, Cost
133
+ from cadaques.drivers import BayesianDriver, RandomDriver
134
+ from cadaques.stats import paired_campaigns
135
+
136
+ result = paired_campaigns(
137
+ lambda seed: table.campaign(BayesianDriver(space=table.oracle().bounds),
138
+ Budget(total=Cost(seconds=25000)), seed=seed),
139
+ lambda seed: table.campaign(RandomDriver(space=table.oracle().bounds),
140
+ Budget(total=Cost(seconds=25000)), seed=seed),
141
+ seeds=range(20), metric=lambda o: o.best.value, alternative="greater",
142
+ )
143
+ print(result.summary()) # paired bootstrap CI + Wilcoxon, quotable as-is
144
+ ```
145
+
146
+ Behind this sit the 0.3 components: **`DatasetOracle`** (a measured table as a
147
+ hidden oracle, with a *declared* miss policy — a dataset miss is a FAILED result
148
+ that settles its declared cost — and deterministic economics that can replay
149
+ recorded per-row costs); **`BayesianDriver`** (Matérn GP with Expected
150
+ Improvement *per predicted unit cost*, learned from the driver's own settled
151
+ history, exploration annealed with `fraction_used`); **`cadaques.stats`** (paired
152
+ bootstrap and Wilcoxon on NumPy alone, exact null to n=25); and **recommendation
153
+ mode** (`recommend()` → a ranked, seeded, bit-for-bit reproducible object whose
154
+ provenance records the driver's declaration, a fingerprint of the history, and
155
+ the measured *thinking time* — the price of intelligence, attached to the advice
156
+ it produced). A frozen retrospective-benchmark protocol over four public
157
+ datasets lives in `studies/`.
158
+
108
159
  ## Design principles
109
160
 
110
161
  - **Dual agnosticism.** Oracles and Drivers are `typing.Protocol` classes with
111
- two methods each. Anything that speaks the protocol plugs in.
162
+ two methods each. Anything that speaks the protocol plugs in. Since 0.2 the
163
+ dual architecture has an operational counterpart: `Resource`
164
+ (submit/status/collect) with an `OracleResource` adapter.
112
165
  - **Declared vs. settled cost.** Oracles declare a price *ex ante*
113
166
  (`oracle.price(query)`); the actual cost is settled *ex post* inside each
114
167
  `Result`. Real oracles deviate from their estimates — the ledger records both,
@@ -116,31 +169,15 @@ What this buys, concretely:
116
169
  - **Both sides are metered.** Driver decisions cost wall time — and tokens, if the
117
170
  driver is an LLM agent. A campaign's economics include the price of intelligence,
118
171
  enabling the question: *when does an expensive smart driver beat a cheap dumb one?*
172
+ - **Failure is a result.** A failed evaluation — an oracle exception, a dataset
173
+ miss — settles its declared cost and stays on the record, exactly like a wasted
174
+ experiment.
119
175
  - **Budget-aware strategies.** Drivers receive a read-only `BudgetView` and may
120
176
  adapt: the reference `AnnealedLocalDriver` explores while rich and exploits
121
- while poor.
122
- - **The ledger is the provenance.** Every transaction (declared, settled,
123
- timestamped) exports to JSONL: a complete, replayable trace of the campaign.
124
-
125
- ## Status and roadmap
126
-
127
- `0.1.0` — core protocols, campaign runner, multi-currency budget and ledger,
128
- reference drivers, and a canonical Ising-2D oracle with fidelity-dependent cost.
129
- Available on [PyPI](https://pypi.org/project/cadaques/) and archived on
130
- [Zenodo](https://doi.org/10.5281/zenodo.21293589).
131
-
132
- This is an early release: the API may evolve until `1.0`. Planned next steps
133
- include a Bayesian-optimization driver, campaign replay, expanded documentation,
134
- and an LLM-agent driver adapter.
135
-
136
- ## Citation
137
-
138
- If you use CADAQUES in academic work, please cite it (see `CITATION.cff`).
139
- DOI: [10.5281/zenodo.21293589](https://doi.org/10.5281/zenodo.21293589)
140
-
141
- ## License
142
-
143
- MIT.
177
+ while poor; `BayesianDriver` anneals its exploration margin the same way.
178
+ - **The event log is the provenance.** Every transition (declared, settled,
179
+ timestamped, schema-versioned) exports to JSONL: a complete, replayable trace
180
+ of the campaign, from which the accounting ledger is derived.
144
181
 
145
182
  ## Status, stability and roadmap
146
183
 
@@ -150,22 +187,40 @@ roadmap below is a plan, not a feature list.
150
187
 
151
188
  *Stability policy (ADR-0010).* Semantic versioning; nothing public breaks
152
189
  without a deprecation shim spanning at least two minor releases. The exact
153
- implementation accompanying the arXiv paper is permanently tagged
154
- (`v0.1.0`) and its imports run against every release via compatibility
155
- shims (`cadaques.core.protocols`, `cadaques.core.campaign`,
156
- `CampaignResult`). Durable design decisions live in `docs/adr/`.
190
+ implementation accompanying v1 of the arXiv paper is permanently tagged
191
+ (`v0.1.0-paper`), and its imports run against every release via compatibility
192
+ shims (`cadaques.core.protocols`, `cadaques.core.campaign`, `CampaignResult`).
193
+ Durable design decisions live in `docs/adr/`.
157
194
 
158
195
  *Kernel (shipped in 0.2).* Task · Query/Result and the general
159
196
  Action/Observation envelopes · Driver · Oracle · Resource (with the
160
197
  `OracleResource` adapter) · Campaign · Budget · Event · Artifact ·
161
198
  Outcome · CampaignSpec — the twelve objects of the campaign
162
199
  architecture, with event-sourced state, seed streams, replay,
163
- checkpoint/resume and public conformance suites.
164
-
165
- *Roadmap (not yet shipped).* A Bayesian-optimization driver and a
166
- hidden-dataset oracle for retrospective studies; a statistics module for
167
- paired driver comparisons; asynchronous executors and a Slurm-backed
168
- workflow resource behind the frozen Resource lifecycle (ADR-0007);
169
- declarative constraint vocabulary for specs; plugin entry points for
170
- external drivers and oracles. See `ARCHITECTURE.md` and
171
- `docs/adr/` for boundaries and decision gates.
200
+ checkpoint/resume and public conformance suites (`cadaques.testing`).
201
+
202
+ *Generality proof (shipped in 0.3).* `DatasetOracle` for retrospective
203
+ campaigns over measured tables; the cost-aware `BayesianDriver` (extra
204
+ `cadaques[bo]`); the audited `cadaques.stats` module; recommendation mode
205
+ (`recommend()`, `RankedCandidates`); the chemistry vertical
206
+ (`ReactionTable`); and the frozen study protocol in `studies/`.
207
+
208
+ *Roadmap (not yet shipped).* Asynchronous executors and a Slurm-backed
209
+ workflow resource behind the frozen Resource lifecycle (ADR-0007); a
210
+ portfolio Driver allocating budget across strategies as a cost-aware
211
+ bandit; native categorical and mixed search spaces; declarative
212
+ constraint vocabulary for specs; plugin entry points for external
213
+ drivers and oracles; an LLM-agent driver adapter with token metering.
214
+ See `ARCHITECTURE.md` and `docs/adr/` for boundaries and decision gates.
215
+
216
+ ## Citation
217
+
218
+ If you use CADAQUES in academic work, please cite it (see `CITATION.cff`).
219
+ Concept DOI (always resolves to the latest version):
220
+ [10.5281/zenodo.21293589](https://doi.org/10.5281/zenodo.21293589).
221
+ Version DOIs are listed per release in [`CHANGELOG.md`](CHANGELOG.md).
222
+ Paper: [arXiv:2607.16127](https://arxiv.org/abs/2607.16127).
223
+
224
+ ## License
225
+
226
+ MIT.
@@ -0,0 +1,68 @@
1
+ """The first vertical, end to end: a reaction CSV in, ranked experiments out.
2
+
3
+ A chemist's table — composition descriptors, reaction conditions, a
4
+ target property, a cost per run — becomes, with one role declaration:
5
+ (a) a ranked, reproducible, provenanced recommendation for the next
6
+ experiments, and (b) a budgeted retrospective campaign for method
7
+ comparison. Run:
8
+
9
+ pip install "cadaques[bo]"
10
+ python examples/chemistry_recommend.py
11
+ """
12
+
13
+ from pathlib import Path
14
+
15
+ from cadaques import Budget, Cost
16
+ from cadaques.drivers import BayesianDriver, RandomDriver
17
+ from cadaques.stats import paired_campaigns
18
+ from cadaques.verticals import ReactionTable
19
+
20
+ HERE = Path(__file__).parent
21
+
22
+ # --- a small synthetic OCM-style table (replace with your CSV) -----------
23
+ ROWS = [
24
+ {"x_Mn": 0.10, "x_Ce": 0.05, "T": 700, "P": 1.0, "c2_yield": 0.18, "run_s": 3600},
25
+ {"x_Mn": 0.20, "x_Ce": 0.05, "T": 750, "P": 1.0, "c2_yield": 0.29, "run_s": 3650},
26
+ {"x_Mn": 0.20, "x_Ce": 0.10, "T": 800, "P": 1.5, "c2_yield": 0.43, "run_s": 3800},
27
+ {"x_Mn": 0.25, "x_Ce": 0.10, "T": 820, "P": 1.6, "c2_yield": 0.47, "run_s": 3850},
28
+ {"x_Mn": 0.30, "x_Ce": 0.15, "T": 850, "P": 2.0, "c2_yield": 0.44, "run_s": 4000},
29
+ {"x_Mn": 0.15, "x_Ce": 0.20, "T": 760, "P": 1.8, "c2_yield": 0.33, "run_s": 3700},
30
+ {"x_Mn": 0.28, "x_Ce": 0.08, "T": 840, "P": 1.4, "c2_yield": 0.45, "run_s": 3900},
31
+ ]
32
+
33
+ table = ReactionTable(
34
+ rows=ROWS,
35
+ objective="c2_yield",
36
+ composition=("x_Mn", "x_Ce"),
37
+ conditions=("T", "P"),
38
+ cost_columns={"seconds": "run_s"},
39
+ tariff={"seconds": 3800.0},
40
+ name="ocm_demo",
41
+ )
42
+
43
+ # --- (a) recommendation mode: what should the lab try next? ---------------
44
+ ranking = table.recommend_next(k=5, seed=42)
45
+ print(ranking.summary())
46
+ out_csv = HERE / "next_experiments.csv"
47
+ ranking.to_csv(out_csv)
48
+ print(f"\nExported for the lab notebook: {out_csv}")
49
+
50
+ # --- (b) campaign mode: would BO have beaten random on this table? ---------
51
+ def make(driver_cls):
52
+ def factory(seed: int):
53
+ t = ReactionTable(**{**table.__dict__, "tolerance": 0.8})
54
+ drv = (BayesianDriver(space=t.oracle().bounds, n_initial=3)
55
+ if driver_cls is BayesianDriver
56
+ else RandomDriver(space=t.oracle().bounds))
57
+ return t.campaign(drv, Budget(total=Cost(seconds=25000.0)),
58
+ seed=seed, meter_driver=False)
59
+ return factory
60
+
61
+ comparison = paired_campaigns(
62
+ make(BayesianDriver), make(RandomDriver),
63
+ seeds=range(8),
64
+ metric=lambda o: o.best.value,
65
+ metric_name="best c2_yield under equal budget",
66
+ alternative="greater",
67
+ )
68
+ print("\n" + comparison.summary())
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cadaques"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Cost-Aware Dual Architecture for QUery-Efficient diScovery: autonomous discovery campaigns — any oracle, any driver, one budget."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -22,6 +22,7 @@ dependencies = ["numpy>=1.24"]
22
22
 
23
23
  [project.optional-dependencies]
24
24
  dev = ["pytest>=7", "pytest-cov", "mypy", "ruff"]
25
+ bo = ["scikit-learn>=1.3"]
25
26
  docs = ["sphinx>=8", "furo"]
26
27
 
27
28
  [project.urls]
@@ -5,6 +5,8 @@ any oracle, any driver, one budget. Every query counts.
5
5
  """
6
6
 
7
7
  from .runtime.campaign import Campaign, CampaignResult, DriverStopped, Outcome
8
+ from . import stats
9
+ from .runtime.recommend import Candidate, RankedCandidates, recommend
8
10
  from .runtime.replay import checkpoint, replay, resume
9
11
  from .runtime.state import CampaignState, reduce
10
12
  from .core.cost import Budget, BudgetExceeded, BudgetView, Cost
@@ -19,10 +21,10 @@ from .protocols.driver import Driver
19
21
  from .protocols.oracle import Oracle
20
22
  from .protocols.resource import JobHandle, JobStatus, OracleResource, Resource
21
23
 
22
- __version__ = "0.2.0"
24
+ __version__ = "0.3.0"
23
25
 
24
26
  __all__ = [
25
27
  "Budget", "BudgetExceeded", "BudgetView", "Campaign", "CampaignResult", "CampaignSpec", "SpecError",
26
28
  "Cost", "Driver", "Event", "EventLog", "DriverStopped", "Ledger", "Oracle", "OracleResource", "Query", "Resource",
27
- "Action", "FailureRecord", "Observation", "ObservationStatus", "Outcome", "CampaignState", "Result", "SearchSpace", "store_artifact", "checkpoint", "replay", "resume", "Task", "InvalidQuery", "Transaction", "__version__",
29
+ "Action", "FailureRecord", "Observation", "ObservationStatus", "Outcome", "CampaignState", "Result", "SearchSpace", "store_artifact", "Candidate", "RankedCandidates", "checkpoint", "recommend", "replay", "resume", "Task", "InvalidQuery", "Transaction", "__version__",
28
30
  ]
@@ -0,0 +1,14 @@
1
+ """Reference drivers. BayesianDriver requires the [bo] extra and is
2
+ imported lazily so core installs stay light (ADR-0008)."""
3
+
4
+ from .reference import AnnealedLocalDriver, RandomDriver
5
+
6
+ __all__ = ["AnnealedLocalDriver", "BayesianDriver", "RandomDriver"]
7
+
8
+
9
+ def __getattr__(name: str):
10
+ if name == "BayesianDriver":
11
+ from .bo import BayesianDriver
12
+
13
+ return BayesianDriver
14
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")