Skip to main content
Using Constraints
In proto-language, a constraint is a scoring function that quantifies how well a sequence satisfies a design requirement. During optimization, each candidate sequence is evaluated by every constraint in a program, and the optimizer searches for the sequences that minimize the resulting scores. This guide describes the constraint interface, the built-in constraints, the definition and registration of custom constraints, and the validation of a constraint prior to optimization. A small DNA design task serves as the running example: the construction of a 100 bp insert whose composition is balanced enough to synthesize reliably and to express efficiently. Open as a runnable notebook View as a Python script

The contract

A constraint operates on a batch of proposals rather than on a single sequence. It receives a list of sequence tuples, list[tuple[Sequence, ...]], containing one tuple per proposal, and returns a list of ConstraintOutput objects, one per proposal. Each ConstraintOutput carries a numeric score together with optional metadata. A batch of sequences passes through a constraint function to a score between 0.0 (perfect) and 1.0 (worst); optimizers drive the scores toward 0 Scores are bounded to the interval [0.0, 1.0], and their polarity is fixed throughout the framework: a score of 0.0 denotes a perfectly satisfied requirement, and a score of 1.0 denotes a maximally violated one. Because every constraint adheres to this convention, constraints of different types may be combined freely, and a single optimizer can minimize them simultaneously.

Defining the sequence

The basic unit of design is the Segment, a contiguous region of sequence with a defined type and length. A Construct groups the segments that together form a single molecule. The example below defines a single variable 100 bp DNA segment.
python

Applying the built-in constraints

Many common sequence properties are available as built-in constraints. A Constraint object binds a scoring function to specific inputs: inputs identifies the segments it reads, function_config supplies its parameters, and label names its diagnostics in the output. A synthesizable, well-expressed insert is subject to two requirements: a balanced GC content, constrained here to 40-60%, and the absence of long single-base runs, which are prone to synthesis errors and can impede polymerases. These requirements correspond to the gc_content_constraint and max_homopolymer_constraint built-ins.
python

Running the optimization

A constraint does not act in isolation; an optimizer proposes sequences, which the constraints then score. The configuration below is the minimal optimization loop, described in detail in Using Optimizers. The relevant result here is the information that the constraints report once the run has completed.
python
Each constraint records its score and diagnostics for every segment, indexed first by the segment label and then by the constraint label. These values are retrieved from the result sequence through metadata["segments"].
python

Defining a custom constraint

When no built-in constraint expresses the property of interest, a constraint can be defined as a function that conforms to the interface. The simplest form requires neither a decorator nor a configuration class; a function that accepts the batch and returns one ConstraintOutput per proposal is sufficient. The following example is biologically motivated. Spurious in-frame ATG codons within an insert can initiate off-target translation and reduce expression of the intended product. The constraint penalizes such internal start codons, disregarding the legitimate first codon.
python

Registering a constraint

The inline form is appropriate for single use. A constraint intended for repeated use is registered with the @constraint decorator and a BaseConfig subclass. Registration supplies a configuration schema, with defaults, bounds, and descriptions; makes the constraint’s parameters tunable; and allows the constraint to be instantiated from the registry by key, in the same manner as the built-in constraints. Configuration classes derive from BaseConfig and declare their fields with ConfigField rather than with Pydantic’s Field.
python

Validating a constraint

A constraint with an inverted polarity or an indexing error can direct an entire run toward the wrong objective without raising an error. Before it is incorporated into an optimizer, a constraint should be evaluated directly on sequences whose expected scores are known: a clean insert should score near 0.0, and a degenerate one near 1.0.
python

Practical considerations

The final score should be clamped to [0.0, 1.0] using min(max(raw, 0.0), 1.0), and the values from which it was derived should be retained in metadata. The score directs the optimizer, whereas the metadata records the basis for that score and can be inspected without re-evaluating the constraint.
Scores must be finite and within [0.0, 1.0]. Returning a bare float in place of a ConstraintOutput raises a TypeError, and an unbounded or NaN score causes the optimizer to behave erratically, because proposals can no longer be compared.
The polarity convention is strict: 0.0 is perfect and 1.0 is worst. A constraint for which a larger value is preferable must invert its score before returning it; otherwise it will oppose every other constraint in the program.

Next Steps

Using Generators

Generate the candidate sequences that constraints evaluate.

Using Optimizers

Search sequence space to minimize constraint scores.

Constraints concept

How constraints relate to the rest of the model.

Constraint reference

The complete catalog of built-in constraints.