Skip to content

Plate-budget acquisition#10

Merged
smcolby merged 2 commits into
mainfrom
plate-size
Mar 24, 2026
Merged

Plate-budget acquisition#10
smcolby merged 2 commits into
mainfrom
plate-size

Conversation

@smcolby
Copy link
Copy Markdown
Collaborator

@smcolby smcolby commented Mar 24, 2026

Replaces the flat k_per_iteration acquisition parameter with a physical plate-budget system and enhances the cost bar chart in the live dashboard to display per-category query counts alongside dollar costs on hover.


Motivation

k_per_iteration was a dimensionless count that didn't map to real experimental constraints. In practice, a wet-lab run fills a physical plate: each Primary Screen (PS) consumes 1 well, each Dose-Response Curve (DRC) consumes many more (e.g., 13 for a 13-point singlet DRC). Expressing the budget in wells rather than queries makes the config directly interpretable by experimentalists and naturally handles the asymmetric cost of DRC vs PS queries.


Changes

moal/config.py

ActiveLearningLoopConfig drops k_per_iteration and gains three new fields:

Field Default Description
plate_size 1536 Maximum wells per plate (one plate = one iteration)
wells_per_ps 1 Wells consumed by a single PS query
wells_per_drc 13 Wells consumed by a single DRC query

Backwards compatibility: plate_size=k, wells_per_ps=1, wells_per_drc=1
exactly replicates k_per_iteration=k behaviour.

moal/acquisition.py

CostAwareGreedyAcquisition.select() replaces the k: int parameter with plate_size, wells_per_ps, wells_per_drc. The selection loop now:

  1. Walks the unified ranked candidate list (highest score first).
  2. Tracks wells_used as each candidate is considered.
  3. Hard-stops when the next candidate's well cost would push wells_used above plate_size — it does not skip that candidate and search for a cheaper one. Remaining candidates are deferred to the next iteration, where the model will rescore them on the updated labeled pool.

The undersupply warning message is updated to report plate_size, wells_per_ps, and wells_per_drc rather than counts.

moal/loop.py

  • ActiveLearningLoop.run() signature updated: k_per_iterationplate_size, wells_per_ps, wells_per_drc.
  • Both acquisition.select() call sites updated (pre-loop seed + in-loop body).
  • Campaign startup banner updated to display well budget instead of query count.
  • Progress step label updated.
  • Per-category query counts (iter_n_ps, iter_n_drc_new, iter_n_drc_upgrade) are computed from new_records after each oracle query and forwarded todashboard.update().

moal/dashboard.py

LiveDashboard.update() gains three optional parameters (defaulting to 0 for backwards compatibility with any existing serialized snapshot data):

  • iter_n_drc_new — new first-pass DRC queries
  • iter_n_upgrades — PS→DRC upgrade queries
  • iter_n_ps — PS queries

These are stored per-iteration in the snapshot dict and surfaced as customdata on the three stacked bar traces in the per-iteration cost breakdown panel (panel 2). Hover tooltips now read, e.g.:

DRC
10 queries
$130
PS→DRC
3 upgrades
$39
PS
1200 queries
$1200

moal/cli.py

Extracts plate_size, wells_per_ps, wells_per_drc from config and passes them through to loop.run().

examples/default_config.yaml

k_per_iteration: 100 replaced with the three new fields and descriptive inline comments:

active_learning_loop:
  n_iterations: 20
  plate_size: 1536       # Maximum wells available per plate (per iteration)
  wells_per_ps: 1        # Wells consumed by a single PS query
  wells_per_drc: 13      # Wells consumed by a single DRC query

Tests

tests/test_acquisition.py

All existing select() call sites updated to the new signature. Two new tests
added:

  • test_drc_cost_stops_at_plate_boundary — verifies that a DRC candidate that would overflow the plate causes the loop to stop, even though there are remaining candidates with lower scores.
  • test_wells_used_never_exceeds_plate_size — property-style test across a range of plate sizes confirming wells_used never exceeds plate_size.

tests/test_loop.py

K = 5 constant replaced with PLATE_SIZE = 5, WELLS_PS = 1, WELLS_DRC = 1 (equivalent behaviour). All loop.run() calls updated.

smcolby and others added 2 commits March 24, 2026 10:52
Replace the flat query count (k_per_iteration) with three physical plate
parameters: plate_size, wells_per_ps, and wells_per_drc.

CostAwareGreedyAcquisition.select() now walks the ranked candidate list in
score order and hard-stops when the next candidate would push total wells
over plate_size. Remaining candidates are deferred to the next iteration
where all scores are refreshed on the updated model.

- moal/config.py: ActiveLearningLoopConfig gains plate_size=1536,
  wells_per_ps=1, wells_per_drc=13; k_per_iteration removed
- moal/acquisition.py: select() signature updated; greedy loop tracks
  wells_used and breaks on overflow; plate_size=0 guard replaces k=0
- moal/loop.py: run() signature updated; both acquisition.select() call
  sites updated; campaign banner updated
- moal/cli.py: passes the three new config fields to loop.run()
- examples/default_config.yaml: k_per_iteration replaced with the three
  new fields with explanatory comments
- tests/test_acquisition.py: all select() calls updated; two new tests:
  test_drc_cost_stops_at_plate_boundary and
  test_wells_used_never_exceeds_plate_size
- tests/test_loop.py: K=5 replaced with PLATE_SIZE=5/WELLS_PS=1/WELLS_DRC=1
- tests/test_cli.py: inline YAML snippets and assertions updated
- README.md / copilot-instructions.md: docs updated

Backwards compat: k_per_iteration=k is reproduced by
plate_size=k, wells_per_ps=1, wells_per_drc=1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each bar segment (DRC, PS→DRC, PS) in the per-iteration cost breakdown
panel now shows both the number of queries and the dollar cost on hover,
e.g. 'DRC / 10 queries / $100'.

- loop.py: compute iter_n_drc_new, iter_n_drc_upgrade, iter_n_ps from
  new_records and forward them to dashboard.update()
- dashboard.py: update() accepts iter_n_drc_new, iter_n_upgrades,
  iter_n_ps (default 0 for backwards compat); stores counts in the
  per-iteration snapshot; adds customdata + hovertemplate to the three
  go.Bar traces in _build_figure()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@smcolby smcolby merged commit 42975aa into main Mar 24, 2026
10 checks passed
@smcolby smcolby deleted the plate-size branch March 24, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant