> ## Documentation Index
> Fetch the complete documentation index at: https://proto.evodesign.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cell-Type-Specific Regulatory DNA

> Design a K562-specific 200 bp enhancer by gradient descent against the Malinois activity model

This program designs a 200 bp regulatory DNA sequence that is active in one cell type but not in
others. It uses the Malinois activity model as a differentiable constraint and the
`GradientOptimizer` to shape the sequence: the objective maximizes predicted K562 activity while
minimizing activity in HepG2 and SKNSH, yielding a K562-specific enhancer.

This runs three hundred gradient steps and returns twenty designs. It requires a GPU for the
Malinois model.

<a href="https://github.com/evo-design/proto-language/blob/main/examples/notebooks/k562-specificity.ipynb" target="_blank" class="tutorial-notebook-btn">
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />

    <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
  </svg>

  <span>Open as a runnable notebook</span>

  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M7 17L17 7M9 7h8v8" />
  </svg>
</a>

<a href="https://github.com/evo-design/proto-language/blob/main/examples/scripts/malinois_k562_specificity_gradient_design.py" target="_blank" class="tutorial-notebook-btn">
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M16 18l6-6-6-6" />

    <path d="M8 6l-6 6 6 6" />
  </svg>

  <span>View as a Python script</span>

  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M7 17L17 7M9 7h8v8" />
  </svg>
</a>

<Note>
  **Runtime:** this walkthrough runs real models on a GPU and takes several minutes to complete. The first run is slower because it builds the tool environment and downloads model weights.
</Note>

## The design segment and generator

A `Segment` is the stretch of sequence being designed; a `Construct` groups the segments that make up one molecule. The segment is seeded with `"A" * SEQ_LENGTH`, a placeholder that fixes the length (200 bp) and `sequence_type="dna"` vocabulary; the optimizer overwrites the actual bases. Instead of holding discrete characters, the `GradientOptimizer` represents each position as a set of differentiable per-position logits and updates them by gradient descent. `PositionWeightGenerator` is the only component that maps those continuous logits back to a discrete sequence; with `sampling_mode="argmax"` it decodes the most likely base at each position. `generator.assign(segment)` binds it to the segment it decodes.

```python python icon="python" theme={null}
from proto_language.core import Segment, Construct
from proto_language.generator import PositionWeightGenerator, PositionWeightGeneratorConfig

SEQ_LENGTH = 200

segment = Segment(sequence="A" * SEQ_LENGTH, sequence_type="dna", label="enhancer_insert")
construct = Construct([segment], label="k562_specific_design")

generator = PositionWeightGenerator(PositionWeightGeneratorConfig(sampling_mode="argmax"))
generator.assign(segment)
```

## The specificity objective

Constraints score how well a sequence meets the design objective; the optimizer searches for sequences that lower the combined loss. Malinois predicts MPRA activity for 200 bp DNA inserts in the K562, HepG2, and SK-N-SH (SKNSH) cell contexts, and each constraint maps the requested cell-type score to a bounded objective: `direction="max"` rewards higher predicted activity, `direction="min"` rewards lower activity. The helper builds three constraints over the same segment: maximize K562 activity, minimize HepG2 activity, and minimize SKNSH activity, each with `seq_length=SEQ_LENGTH` and `weight=1.0`. Each `Constraint` carries a `label`, the key its diagnostics appear under in the result metadata. All three back-propagate through the Malinois model, which is what lets the `GradientOptimizer` use them.

```python python icon="python" theme={null}
from proto_language.core import Constraint
from proto_language.constraint import MalinoisActivityConfig, malinois_activity_constraint


def malinois(cell_type, direction, label, weight):
    return Constraint(
        inputs=[segment],
        function=malinois_activity_constraint,
        function_config=MalinoisActivityConfig(cell_type=cell_type, direction=direction, seq_length=SEQ_LENGTH),
        label=label,
        weight=weight,
    )


constraints = [
    malinois("K562", "max", "k562_max", 1.0),
    malinois("HepG2", "min", "hepg2_min", 1.0),
    malinois("SKNSH", "min", "sknsh_min", 1.0),
]
```

## Run the gradient optimization

The `GradientOptimizer` runs continuous gradient descent on the segment's per-position logits, backpropagating each constraint into a logit gradient, merging the per-constraint gradients, and updating the logits each step. The config sets `num_results=20` parallel trajectories over `num_steps=300` steps with base learning rate `lr=0.5`. Updates use `ml_optimizer="adam"`, and the per-constraint gradients are combined with `merger="weighted_sum"` (each scaled by its `weight`). `lr_schedule="cosine"` with `scale_lr_by_temperature=True` anneals the learning rate on a cosine curve across the run. `gumbel_logit_init=True` adds Gumbel noise to the initial logits so the 20 trajectories diverge, and `save_best=True` returns each trajectory's lowest-loss design rather than its final step. The optional `custom_logging` callback fires at tracked steps; here `track` records each snapshot's sequence and its K562 raw activity (read from the `k562_max` constraint metadata) into `trajectory`. `Program(..., seed=0)` makes the run deterministic.

```python python icon="python" theme={null}
from proto_language.core import Program
from proto_language.optimizer import GradientOptimizer, GradientOptimizerConfig

# Record the sequence (and its K562 activity, when available) at each tracked step.
trajectory = []


def track(step, segments):
    seq = segments[0].proposal_sequences[0]
    act = seq.metadata.get("constraints", {}).get("k562_max", {}).get("data", {}).get("malinois_raw_score")
    trajectory.append((step, str(seq.sequence), act))


optimizer = GradientOptimizer(
    target_segment=segment,
    constructs=[construct],
    generators=[generator],
    constraints=constraints,
    config=GradientOptimizerConfig(
        num_results=20,
        num_steps=300,
        lr=0.5,
        softmax_schedule="constant",
        lr_schedule="cosine",
        scale_lr_by_temperature=True,
        ml_optimizer="adam",
        merger="weighted_sum",
        gumbel_logit_init=True,
        save_best=True,
    ),
    custom_logging=track,
)

program = Program([optimizer], num_results=20, seed=0)
program.run()
```

## Inspect the result

`segment.result_sequences` holds the returned designs; with `save_best=True` the first entry is the lowest-loss design. The first block prints a few evenly spaced snapshots from `trajectory`, each showing the step number, the K562 raw activity at that step, and the start of the sequence, so you can watch the activity rise as the logits are optimized. The final lines print the top design's full sequence and its predicted K562 activity, read back from the `k562_max` constraint's `malinois_raw_score` metadata.

```python python icon="python" theme={null}
best = segment.result_sequences[0]
k562 = best.metadata.get("constraints", {}).get("k562_max", {}).get("data", {}).get("malinois_raw_score")

def representative(traj, n=4):
    if len(traj) <= n:
        return traj
    idx = sorted({round(i * (len(traj) - 1) / (n - 1)) for i in range(n)})
    return [traj[i] for i in idx]

print("trajectory (the sequence is reshaped toward a K562-specific enhancer):")
for step, seq, act in representative(trajectory):
    act_str = f"{act:6.2f}" if act is not None else "   -  "
    print(f"  step {step:3d} | K562 {act_str} | {seq[:60]}...")

print(f"\ntop design: {best.sequence}")
print(f"K562 predicted activity: {k562}")
```

```text theme={null}
trajectory (the sequence is reshaped toward a K562-specific enhancer):
  step   1 | K562   0.59 | CCTGAGAAGGGTACGTCGCTATAATTCTCCTGATTGGTCGGTGCAGCCCAATTTGGGTTG...
  step 101 | K562   9.44 | CCTCGCCCCCATGAGGCGCTATAGAACACCTGACGGCTGCTTCCCGCCCAATTTGGGTTT...
  step 200 | K562  10.20 | CCTCGCCCACATGAGGCGCTATAGAACACCTGACGGCTGCTTCCCGCCCAATTTGGGTTT...
  step 300 | K562  10.31 | CCTCGCCCACATGAGGCGCTATAGAACACCTGACGGCTGCTTCCCGCCCAATTTGAGTTT...

top design: CCTCGCCCACATGAGGCGCTATAGAACACCTGACGGCTGCTTCCCGCCCAATTTGAGTTTGCGCGCGCCATTTGCATGACGGCGACTAATCCTGCCGGCTGTCAGAGTGGCGCGCGAAAGATAAGACCAAAAAACCGCTTAACCGCCCCTTATCTCTTGTACCAGATGCACAGGCCGCCGCCCACTTAGTGAAATTTGAG
K562 predicted activity: 10.392728805541992
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Gradient Protein Hallucination" icon="sparkles" href="/docs/language/guides/examples/gradient-protein-hallucination">
    The gradient optimizer applied to proteins.
  </Card>

  <Card title="Using Optimizers" icon="circle-play" href="/docs/language/guides/using-optimizers">
    The gradient optimizer in context.
  </Card>
</CardGroup>
