YAML/JSON condition spec#
The condition spec builds an annotated CIF from a small YAML or JSON file. You name a design, point it at a starting structure, say which parts to keep, and set the conditions you want. For those of you coming from previous RFdiffusion versions, this should be the most familiar of the three ways to make an annotated CIF. It is best used when the design problem is better expressed as data than as code: something you template across many targets, diff in a pull request, or hand to a teammate who does not want to touch Python.
This page is a tour of the moving parts. For the exhaustive syntax, every contig token, and every
condition annotation, see the
field-by-field YAML/JSON reference.
This page is an overview of how to think about building your YAML/JSON condition specification files, the YAML/JSON reference contains all of the conditions you could possibly use to define your design.
The shape of the workflow#
Every design (unless generating an unconditioned monomer) in a condition spec goes through the same three stages, in order:
Load one or more starting structures under a design key you choose.
contigsubsets the loaded structure to the residues you want and inserts any variable-length placeholder segments, in one pass.conditionssets the conditions you want, resolved against the structurecontigproduced.
The tool then runs the same dry-run safeguards and prints a summary of what it it thinks you are trying to design. If a safeguard fails, it stops rather than writing a CIF it cannot guarantee.
Here is a short example of all three pieces in a YAML file:
binder_redesign:
input: ./inputs/complex.cif
contig: "A1-120, /0, B1-80"
conditions:
C_CRD: {true: [{select: "sel('B') and is_protein_backbone"}]} # backbone-only, no sequence leak
C_HOT: {true: [{select: "sel('A/*/[45, 46, 82]')"}]}
binder_redesign is a name (key) you chose, not a reserved keyword. A single file can define several
designs this way, each under its own key. The one reserved top-level key is global_args, for
defaults you want folded into every design in the file.
Important
Anything you do not mention is not provided. Setting only C_HOT above gets you a hotspot and
nothing else: no fixed coordinates, no known sequence. Trust the printed summary over your intent.
Now let’s look at what each stage does in more detail.
Step 1: describe the input#
my_design:
input: ./inputs/complex.cif
input_as_annotated_cif: true # optional: load existing conditions instead of starting fresh
input takes one path or a list; a list gets combined into one structure, and chain ids must not
overlap.
Important
Relative paths resolve against the spec file’s directory, not your working directory.
Set input_as_annotated_cif to false for a fresh structure you want cleaned up (missing atoms added, MSE converted to MET,
any existing conditioning ignored). Set it to true for a CIF you or someone else already annotated,
so editing it further does not lose what is already set.
Step 2: subset and insert segments with contig#
contig is one comma-separated string of tokens, such as "A42-48, E1:2-20, A52, E2:10R". It does
two things in one pass:
Subsets. Any input atom not covered by a preserved segment (
A42-48, a chain letter plus a residue range) is dropped. This is how you crop a structure down to a motif or an interface.Inserts expandable segments. A token like
E1:2-20adds a variable-length placeholder the model fills in at generation time; naming it (E1) letsconditions:select it later.
Warning
res_id in contig and in conditions: selections resolves against your original input
numbering, the mmCIF label_seq_id, before contig renumbers the output. Expandable segments
have no original res_id, so select them by res_name instead.
Note
A contig string must keep at least one preserved segment. One that is entirely expandable
segments is rejected: input exists to be referenced by a preserved segment, so a design that
generates everything from scratch has no reason to reference one. For a pure de novo design with no
real anchor at all, see where this pattern doesn’t reach.
Omit contig entirely and the whole input is kept as is.
See contig
tokens for chain-assignment rules when a run of tokens
mixes a preserved segment with an expandable one.
Step 3: select atoms, then set a condition#
A select: value is one AtomWorks query string that allows you to select specific
subsets of your structure. This is what will allow you to tell RFD4-Proteina if you
want to unindex certain residues, redesign a seqence for part of a chain, etc.
It takes on two possible formats, shown below:
# Path syntax: sel('<chain>/<res_name>/<res_id>/<atom_name>'), trailing fields default to '*'
select: "sel('A/*/[45, 46, 82]')"
# Query syntax: a Python boolean expression over any annotation
select: "is_ligand and not is_protein_backbone"
Note
The selecion syntax has 4 possible fields: chain/res_name/res_id/atom_name, if you omit one of these
specifications, it omits everything that comes after it. For example,
sel('B/*/N') is not “chain B, atom N”; it parses as chain=B, res_name=*, res_id=N and fails, since res_id must be numeric. To reach atom_name, fill every field before
it: sel('B/*/*/N'). See Selection syntax for the
full grammar of both styles.
With a selection in hand, a condition entry maps the value you want to the list of selections that should get it:
conditions:
C_CRD: # reveals ground-truth coordinates (fixes the atomic coordinates of chain B)
true:
- select: "sel('B')"
C_HB: # an enum value with no "ground truth", here it is used to say that any nitrogen backbone atom in chain B can be used as an H-bond donor
DONOR:
- select: "sel('B/*/*/N')"
C_SEQ_PROB_CYS: # explicit zero is different from leaving CYS unspecified, here it means residues B10-20 in the output structure should not be cysteine
0.0:
- select: "sel('B/*/[10-20]')"
C_CRD and C_SEQ take a boolean directly, true
copies the structure’s own coordinate or identity at the selection, false leaves it free. Every
other condition is an enum, string, or float value you choose yourself.
Overlapping entries for the same annotation resolve last write wins, in
declaration order. The annotations reference
and the condition annotations table list every
value each one accepts, including C_IDX and C_CRDGRP, whose group labels work a little
differently, see index and coordinate group
fields.
Running the model#
Point inputs= at the spec file exactly the way you would at a CIF:
rfd4-generate inputs=my_designs.yaml num_replicates=10 batch_size=8
Each top-level design key runs as one design problem; generate.py builds the annotated CIF in
memory and consumes it directly. You do not need to write anything to disk first.
To inspect what a design actually produces before spending GPU time on it, materialize it to annotated CIFs instead:
scripts/write_condition_spec_cifs.py my_designs.yaml --out-dir ./cifs
The resulting file is exactly what rfd4-generate would have read.
Examples#
Two complete, runnable specs live under docs/how-to/input_prep/examples/, one for a simpler ppi example
and one for a more complicated binding pocket redesign. The PPI specificity
tutorila notebook also uses the YAML specification format.
condition_spec_ppi.yaml builds a de novo PPI binder against PD-L1, using an expandable binder chain, backbone-only coordinate conditioning on the target, and atom-level hotspots.
condition_spec_complicated.yaml redesigns a small molecule pocket, exercising a multi-chain contig, an expandable segment carrying secondary structure conditioning, index groups, and both selection styles.
Fix a binder backbone, redesign its sequence, hotspot the target.
binder_redesign:
input: complex.cif
conditions:
# `is_protein_backbone` is what makes this backbone-only -- `sel('B')` alone would
# condition on every sidechain atom too, which is not "redesign its sequence".
C_CRD: {true: [{select: "sel('B') and is_protein_backbone"}]} # backbone-only coords, no sequence leak
C_HOT: {true: [{select: "sel('A/*/[45, 46, 82]')"}]}
Scaffold a motif with a variable length linker to a new binder chain.
motif_scaffold:
input: motif.cif
contig: "A1-50, LINK:10-30, /0, BINDER:60-90" # BINDER's run has no anchor -> fresh chain id
conditions:
C_CRD: {true: [{select: "sel('A/*/[1, 50]')"}]}
C_SEQ: {true: [{select: "chain_id == 'A' and res_name != 'LINK'"}]} # query syntax: exclude the placeholder
Where this pattern scales#
A condition spec is the right call when the design problem is naturally a handful of selections over a starting structure (a motif, an interface, a redesign) and you want to template or diff it as data. The examples above show a de novo PPI binder and a small-molecule pocket redesign built this way.
However, there are three things the Python API can do that a condition spec cannot express:
Two-body conditions, like base pairing, which have no selection-pair grammar in the spec format.
Programmatic, data-driven selections computed over the parsed structure, and combining structures beyond a simple concatenation of
inputpaths.Pure de novo generation with no real anchor residue. A spec’s
contigalways needs at least one preserved segment frominput(see the note above), so it cannot express a design built entirely from scratch.data/benchmarks/monomer/*.cifare exactly this case — a single wholly expandable chain with no conditions at all — built directly withinsert_expandable_segment()on a placeholderAtomArrayrather than through a spec.
If you hit either of those, or just want to generate many variants programmatically, switch to the Python API. If you get stuck choosing between all three routes, see the comparison table.
