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.
- {cadaques-0.2.0 → cadaques-0.3.0}/.github/workflows/ci.yml +2 -2
- {cadaques-0.2.0 → cadaques-0.3.0}/.gitignore +2 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/CHANGELOG.md +46 -1
- {cadaques-0.2.0 → cadaques-0.3.0}/CITATION.cff +1 -1
- {cadaques-0.2.0 → cadaques-0.3.0}/PKG-INFO +97 -40
- {cadaques-0.2.0 → cadaques-0.3.0}/README.md +94 -39
- cadaques-0.3.0/examples/chemistry_recommend.py +68 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/pyproject.toml +2 -1
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/__init__.py +4 -2
- cadaques-0.3.0/src/cadaques/drivers/__init__.py +14 -0
- cadaques-0.3.0/src/cadaques/drivers/bo.py +256 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/drivers/reference.py +6 -2
- cadaques-0.3.0/src/cadaques/oracles/__init__.py +5 -0
- cadaques-0.3.0/src/cadaques/oracles/dataset.py +216 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/__init__.py +2 -1
- cadaques-0.3.0/src/cadaques/runtime/recommend.py +285 -0
- cadaques-0.3.0/src/cadaques/stats/__init__.py +235 -0
- cadaques-0.3.0/src/cadaques/verticals/__init__.py +11 -0
- cadaques-0.3.0/src/cadaques/verticals/chemistry.py +221 -0
- cadaques-0.3.0/studies/README.md +68 -0
- cadaques-0.3.0/studies/data/PROVENANCE.md +15 -0
- cadaques-0.3.0/studies/data/ocm291.csv +292 -0
- cadaques-0.3.0/studies/data/olympus_fullerenes.csv +247 -0
- cadaques-0.3.0/studies/data/olympus_snar.csv +67 -0
- cadaques-0.3.0/studies/ocm/featurize.py +114 -0
- cadaques-0.3.0/studies/ocm/prepare_ocm_full.py +56 -0
- cadaques-0.3.0/studies/run_study.py +218 -0
- cadaques-0.3.0/tests/test_bo_driver.py +85 -0
- cadaques-0.3.0/tests/test_chemistry_vertical.py +100 -0
- cadaques-0.3.0/tests/test_dataset_oracle.py +107 -0
- cadaques-0.3.0/tests/test_recommend.py +101 -0
- cadaques-0.3.0/tests/test_stats.py +104 -0
- cadaques-0.2.0/src/cadaques/drivers/__init__.py +0 -1
- cadaques-0.2.0/src/cadaques/oracles/__init__.py +0 -2
- {cadaques-0.2.0 → cadaques-0.3.0}/.readthedocs.yaml +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/ARCHITECTURE.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/LICENSE +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0001-campaign-is-the-aggregate-root.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0002-query-and-result-are-evaluation-specializations-of-action-and-observation.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0003-oracle-versus-resource-a-dual-protocol-not-a-rename.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0004-the-event-log-is-the-source-of-truth.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0005-schema-version-fields-now-migration-framework-later.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0006-failure-is-a-scientific-result-cost-invariants-have-veto-power.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0007-synchronous-first-the-async-boundary-is-the-resource-lifecycle.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0008-core-dependency-policy.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0009-plugins-by-ordinary-packaging-and-entry-points.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0010-semantic-versioning-and-pre-10-guarantees.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0011-the-driver-view-boundary.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/adr/0012-determinism-discipline-one-seed-named-streams.md +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/api/core.rst +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/api/drivers.rst +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/api/oracles.rst +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/concepts.rst +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/conf.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/index.rst +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/docs/quickstart.rst +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/examples/ising_tc.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/__init__.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/artifacts.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/campaign.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/cost.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/events.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/ledger.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/observation.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/protocols.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/records.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/spec.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/core/task.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/oracles/analytic.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/oracles/ising.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/__init__.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/driver.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/oracle.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/protocols/resource.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/campaign.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/replay.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/runtime/state.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/testing/__init__.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/src/cadaques/testing/contracts.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_artifacts.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_cadaques.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_contracts.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_envelopes.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_events.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_replay.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_resource.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_seeds.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_spec.py +0 -0
- {cadaques-0.2.0 → cadaques-0.3.0}/tests/test_state.py +0 -0
- {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: [
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
149
|
-
timestamped) exports to JSONL: a complete, replayable trace
|
|
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
|
-
|
|
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
|
-
*
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
123
|
-
timestamped) exports to JSONL: a complete, replayable trace
|
|
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
|
-
|
|
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
|
-
*
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
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.
|
|
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}")
|