Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

REE-Designer

Agent-coordinated protein design for rare-earth-element binding.

Takes templates of known or suspected REE-binding proteins from REE-Miner, generates candidate designs around their metal sites, scores them with REE-Finder, and iterates in a Design-Build-Test-Learn loop to produce a ranked list of designs for wet-lab testing.

REE-Miner templates
        │
        ▼
   ┌─────────────────────────────────────────────────────┐
   │  DBTL loop (deterministic, checkpointed)            │
   │                                                     │
   │   Design ──► Build ──► Test ──► Learn ──┐           │
   │     ▲                    │              │           │
   │     │                    ▼              │           │
   │     │              REE-Finder           │           │
   │     │        (Boltz → ESMBind →         │           │
   │     │         Amber MD → DFT)           │           │
   │     └────────────────────────────────────┘          │
   │                     ▲                               │
   │              LLM advisor (bounded)                  │
   └─────────────────────────────────────────────────────┘
        │
        ▼
  ranked designs + wet-lab report

Install

pip install -e ".[dev]"          # core + tests
pip install -e ".[advisor]"      # + the LLM advisor

Try it

The demo runs the whole loop on a laptop — no GPU, no Amber, no scheduler — against a surrogate scorer:

ree-designer check configs/demo.yaml    # validate everything, submit nothing
ree-designer run   configs/demo.yaml

It designs against human calmodulin EF-hand loop 1 (DKDGDGTITTKE), whose position-2 Lys is precisely the residue the lanmodulin literature says to replace with Pro. Outputs land in campaigns/demo/:

File Contents
report.md The human document — caveats, recommendations, assay guidance
recommendations.csv The wet-lab shortlist, ordered by evidence strength
designs.csv Every design scored, with all objectives and provenance
campaign_state.json Full state; resume by re-running run

The surrogate scorer is not a model. Its energies come from coordination heuristics plus hash noise. It exists to exercise the plumbing — including the awkward parts (variable cluster composition, designs that form no site, identical Nd/Ca site predictions) that the real backend produces.

For a real campaign, fill in configs/production.yaml and run the same commands.

How it works

1. Templates (templates.py)

Ingests REE-Miner's cam_dataset_entries.json / training_dataset_esm.json, or its PDB-mining CSVs. Two numbering hazards are handled at ingest so nothing downstream has to think about them:

  • Miner's binding_positions are 0-indexed (re.Match.start()); everything here and in REE-Finder is 1-indexed.
  • PDB seqid.num is author numbering and does not index the extracted sequence. Contacts that cannot be reconciled are dropped, not guessed.

2. Design (design/)

All backends implement one interface, so a round can mix a cheap heuristic sweep with an expensive structure generator and rank the output on one scale.

Backend What it does
heuristic Chemistry-driven mutagenesis: the EF-hand position-2 proline switch, carboxylate enrichment bounded by charge balance, soft-donor removal, Asp/Glu reach tuned to ionic radius
knowledge Mutations learned from the corpus of known binders — see below
saturation Systematic scanning over a focused alphabet — how the campaign finds effects the rules miss
combinatorial Recombines substitutions the campaign has measured as good; returns nothing without that evidence rather than guessing
proteinmpnn Sequence design on a fixed backbone, coordinating residues held fixed
rfdiffusion Backbone generation around the coordination motif (submitted as a GPU job, chained into ProteinMPNN)

Every proposal carries a rationale string that flows through to the report, so a bench scientist can see why a mutation was suggested.

3. Learning from known binders (knowledge.py)

The knowledge backend asks the corpus rather than the literature: across proteins that demonstrably bind lanthanides versus proteins that bind calcium, which residues at which loop positions actually differ? Every proposal carries its counts and test statistic into the wet-lab report.

ree-designer corpus configs/demo.yaml     # what the corpus supports, and what it can't

Getting a defensible answer took four guards, each of which silently produces a confident wrong recommendation if missed:

Labels can't come from the thing being tested. REE-Miner sets is_ree_selective = (pos2_aa == "P") and derives metal_code from it. Testing "is Pro enriched at position 2?" against those labels recovers the rule that generated them, with an arbitrarily small p-value and no information. from_miner_loops_csv refuses those columns and labels from protein family instead — lanmodulin is a characterised Ln binder, calmodulin a characterised Ca sensor.

Redundancy has to collapse, but not too far. The bundled SaCaM sequence carries loops byte-identical to calmodulin's, and two lanmodulin loops are the same sequence. But the conventional 90%-identity threshold, applied to a 12-residue motif, merges any two loops differing at one position — which is exactly the resolution needed, since the Asp→Pro switch is a single residue. The default collapses exact duplicates only; at 90% the best attainable p-value degrades from 0.006 to 0.048.

Loop positions aren't homologous across families. Every Ln loop carries its structural glycine at loop position 5; every Ca loop at position 6. The loops are anchored at positions 1 and 12, so positions 5–11 sit in different registers and compare different parts of the loop. Raw enrichment there is an alignment artefact — it suggests replacing calmodulin's coordinating Asp25 with LanM's structural glycine. Those positions are detected and excluded.

A tied block is not a winner. All lanthanide loops in the bundled corpus come from one protein, so every position where it differs from the calcium set yields an identical contingency table. Six positions tie at q=0.04 and no test can rank within them. The corpus reports the block as a block.

What survives on the bundled corpus is K22P (the literature answer, recovered from data) and G24D (a novel hypothesis: carboxylate density at loop position 4, which the hand-coded rules never proposed). Both are proposed as separate single mutants — because the DBTL loop can deconfound by measurement what sequence comparison cannot deconfound by counting.

Point the backend at a REE-Miner workspace to grow the corpus:

design:
  backends: [heuristic, knowledge, saturation, combinatorial]
  params:
    knowledge:
      loops_csv:    ree_miner_data/datasets/cam_efhand_loops.csv
      contacts_csv: ree_miner_data/datasets/pdb_contacts_raw.csv

contacts_csv adds donor propensities from residues observed contacting lanthanides in solved structures — non-circular by construction, and the fallback for sites that aren't EF-hand loops. Propensities are ranked on a Wilson lower bound, not the point estimate: Trp is rare enough in the proteome that one incidental contact otherwise scores as high as seven aspartates.

Adding a second independent Ln-binding protein (Hans-LanM, lanpepsy, an XoxF site) is the single highest-value thing you can do here — it breaks the phylogenetic confounding that currently ties six positions together.

4. Scoring (scoring/)

REEFinderBackend generates a driver script and a PBS Parsl config, submits them, and reassembles the results. Four properties of REE-Finder shaped this adapter:

  • Pipeline.run's return value is unusable for attribution — a flat list[(coords, energy)] with no protein or ion label. Results are harvested from sims/{design}/{ion}/clusters/ instead.
  • Per-residue probabilities are never persisted. The generated driver dumps them before delegating, since site confidence cannot be recovered otherwise.
  • src/ree-finder is not an importable package (it contains a hyphen), so the driver puts it on sys.path and imports its modules flatly.
  • The FASTA parser strides by line index (lines[::2] / lines[1::2]), so one wrapped sequence silently pairs every design with the wrong header. Every written batch is re-read the same way and checked before submission.

5. Energy comparison (scoring/energy.py)

The part that decides whether the rankings mean anything.

REE-Finder returns energy_hartree: the absolute SCF energy of a carved cluster. It is not a binding energy and is not comparable between designs whose clusters differ in composition. The magnitudes make the trap plain — one extra water shifts the total by about −76 Ha, while the entire chemical effect being designed for is 0.01–0.1 Ha. Ranking on raw energy ranks designs by how many waters extract_clusters happened to include.

Two defensible modes, and a refusal to produce a number when neither applies:

  • strict (default) — compare only identical molecular formulas. Exact. Its cost is coverage: designs with a different water count drop out.
  • linear_reference (opt-in) — least-squares fit E ≈ Σ nₑ εₑ and compare residuals. Recovers coverage, loses rigour. For triage, never for a headline.

Selectivity is a double difference, because comparing an Nd cluster against a Ca cluster directly is meaningless (different metals, electron counts, total charge):

ΔΔE = [E(design, Nd) − E(ref, Nd)] − [E(design, Ca) − E(ref, Ca)]

The metal-dependent constants cancel, provided each ion's pair is composition-matched to the reference.

6. The loop (dbtl/)

A deterministic state machine, checkpointed atomically before every wait so the orchestrator can be killed and resumed while jobs sit in the PBS queue.

Designs are scored on six objectives, each normalized 0–1, higher always better: affinity, selectivity, site_confidence, shell_quality, parsimony, novelty. Missing objectives are dropped and the remaining weights renormalized, never imputed as zero — otherwise a design REE-Finder could not evaluate would rank below one it evaluated badly, penalising designs for the pipeline's coverage gaps.

Output is both a weighted ranking (an ordered list to work down) and a Pareto front (the honest view of where the trade-offs are). Selection reserves slots for Pareto-optimal and diverse designs so the campaign does not collapse onto one lineage by round two.

7. The advisor (advisor/)

Consulted at three points, with strictly bounded authority:

  • adjust_strategy — reweights the next round's budget across backends and nominates saturation positions. Its output is clamped to the campaign's own budget and backend set; unknown backends are dropped.
  • interpret_round — writes the campaign-log paragraph.
  • write_report — writes the wet-lab narrative.

It never selects designs, never changes an objective weight, never touches a score. Ranking stays reproducible. Every call degrades to a no-op without credentials, so a campaign always runs without it.

What this pipeline cannot tell you

Stated here as prominently as in every generated report, because these are properties of the modelling stack rather than of any one run:

  1. Only Nd is modelled. REE-Finder supports CA, MG, FE, ND. There is no intra-lanthanide selectivity data — nothing distinguishes Nd from Pr or Dy. The chemistry module knows the whole series and uses it for design heuristics (radius-matched carboxylate reach), but no score can confirm it.
  2. Nd and Ca site predictions are identical by construction. ESMBind was not trained on lanthanides, so ESMBIND_CHANNEL['ND'] == 'CA'. Both ions are modelled at the same residues; any Nd-over-Ca signal comes from the Amber 12-6-4 parameters and DFT, never from the site predictor.
  3. DFT energies are absolute cluster energies, not binding energies. See above.
  4. Nothing models fold stability, expression, or solubility. A design can score well and still be undesigned protein. Mutational load is tracked as parsimony precisely because nothing else guards it.

Every recommendation therefore carries an evidence tier (A–D) so a ranked list is never mistaken for a set of measurements.

Development

pytest                       # 149 tests, no network, no GPU
ruff check src tests

Layout

src/ree_designer/
  chem.py          REE coordination chemistry (radii, donors, EF-hand motifs)
  knowledge.py     Known-binder corpus: contrast statistics, donor propensities
  types.py         Domain types; the contract between every stage
  templates.py     REE-Miner ingest
  config.py        Campaign YAML → constructed objects
  report.py        CSV + Markdown deliverables with evidence tiers
  cli.py           templates / check / run / report / status
  io/              REE-Finder-safe FASTA
  runners/         Local and PBS Pro job execution
  design/          Generative backends
  scoring/         REE-Finder adapter, surrogate, energy comparison
  dbtl/            Objectives, selection, state, campaign loop
  advisor/         Bounded LLM advisor

About

REE-Designer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages