Optimize a coding sequence for expression in another host
The route: measure what the CDS looks like to the host you are moving it to → decide what must not change → optimize → measure the same things again → check what you gave up.
Every number below came from running the engines on the ADH1 CDS of
data/samples/Genbank/SC_ADH1.gb — the real Saccharomyces cerevisiae alcohol dehydrogenase I
gene, CDS at 751..1797, 1,047 bp, 349 codons — which the Codon usage starter opens.
Step 1 — Measure the sequence against the host you are leaving, and the one you are going to
Tool: Codon Usage Analysis. Where: Tools → Analyze.
Select the CDS and run it twice, once per organism. The same 1,047 bp gives two very different readings:
| S. cerevisiae (its own host) | E. coli | |
|---|---|---|
| GC | 0.484 | 0.484 |
| Codons | 349 | 349 |
| Rare codons | 0 (0.0%) | 12 (3.4%) |
| Which ones | — | AGA ×8, CTA ×3, CCC ×1 |
| CAI | 0.812 | not available |
| Start codon usage | 1 | — |
| N-end-rule half-life | > 30 h | — |
That is the whole argument for codon optimization in one table. A gene perfectly adapted to its
own host has a dozen codons that E. coli barely uses, eight of them the same one — AGA, an
arginine codon whose tRNA is scarce in E. coli and a classic cause of stalled translation and
truncation products.
The empty CAI cell is not a bug and is not filled in. The bundled pydna reference tables carry
relative-adaptiveness weights for S. cerevisiae only, so a CAI for E. coli would have to be
invented. The panel reports null and says why. The rare-codon scan and GC work for both organisms
because those tables do exist for both.
Check: the region is a real, in-frame CDS. A codon-level analysis of a length that is not a multiple of three is refused up front, with the length in the message, rather than silently analysing a frameshift.
Step 2 — Choose the host table, and know where it came from
Tool: Codon Tables.
Three reference tables ship for instant use — E. coli K-12, H. sapiens, S. cerevisiae — Kazusa-derived relative synonymous frequencies.
They are the offline fallback, not the primary path. The intended route computes a table live from the record's own organism → its current NCBI reference assembly → real CDS. That matters because a bundled table is a cache that ages, and because three hosts is not a list of the organisms anyone might express in. The bundled set exists because Kazusa cannot be called directly from a browser, so a no-network default is needed; it is explicitly not meant to grow into an organism database. Add an organism by loading its sequences, not by picking from a menu of three.
You can also build a table from sequences you already have, which is what you want when your expression host is a strain rather than a species.
Step 3 — Say what must not change
Tool: Codon Optimization. Where: Tools → Design.
This is the step people skip, and it is the one that makes the difference between a sequence you can order and a sequence you have to debug. The optimizer separates constraints (hard — the solve fails rather than violating one) from objectives (soft — maximised, weighted, traded off).
For a CDS you are moving to E. coli, a minimum set:
| Spec | Role | Why |
|---|---|---|
EnforceTranslation | constraint | The protein must not change. Non-negotiable |
AvoidPattern — BsmBI | constraint | You are going to Golden-Gate this. A new CGTCTC anywhere is a silent disaster |
EnforceGCContent 0.35–0.65 in a 50 bp window | constraint | Local GC, not just global — a synthesis house will refuse a GC-poor or GC-rich window even when the whole gene averages fine |
CodonOptimize, method use_best_codon | objective | The actual optimization |
There are far more available — avoid hairpins, avoid rare codons below a frequency, uniquify all k-mers, enforce a melting temperature, enforce terminal GC, pin a subsequence, budget the number of edits, enforce a minimum number of changes, restrict a region to a set of choices. Each is a real spec of the ported solver, with its own location and priority, and each appears in the before/after evaluation whether it passed or not.
Three codon-optimization methods, not one, and they answer different questions: use_best_codon
takes the host's most frequent codon for each residue; the frequency-matching method reproduces the
host's distribution rather than its maximum; and harmonize_rca mimics the codon rank the gene
had in its original host — which needs the original host's table too, and is the one to reach for
when you care about co-translational folding rather than raw yield.
Step 4 — Run it, and read both sides of the ledger
With exactly the four specs above, targeting E. coli K-12, seed 1:
success true
length 1047 bp
edits 290 bases changed (27.7%) in 226 runs
objective score −177.3 → −25.3
constraints after:
PASS EnforceTranslation[0-1047] Enforced by nucleotides restrictions
PASS AvoidPattern[0-1047](BsmBI CGTCTC) Pattern not found
PASS EnforceGCContent[0-1047](0.35–0.65, window 50) PassedAnd the same measurements from step 1, re-run on the output:
| Before | After | |
|---|---|---|
| Rare codons for E. coli | 12 (AGA×8, CTA×3, CCC×1) | 0 |
| GC | 0.484 | 0.546 |
| CAI for S. cerevisiae | 0.812 | 0.588 |
| Protein (349 aa) | MSIPETQKGVIFYESHGKLE… | identical |
Read the last two rows together. The sequence got substantially better for E. coli and substantially worse for yeast — CAI 0.812 → 0.588 — which is exactly right and exactly what you are choosing to do. An optimized gene is optimized for a host; there is no such thing as an optimized sequence.
And the protein is byte-for-byte identical, which is the EnforceTranslation constraint doing its
job rather than a hope.
Check: success: true and every constraint reading PASS. A failed solve names the unsatisfiable
constraint instead of returning a best-effort sequence — which is the right behaviour, because a
sequence that quietly violates your BsmBI constraint is worse than no sequence.
Note what Enforced by nucleotides restrictions means on the first line, because it is the one
row here that is not a measurement. EnforceTranslation is satisfied by construction: the solver
only ever considered codons encoding the same residue, so the constraint cannot fail and was never
tested against your starting sequence. In the panel that row reads guaranteed, not measured for
exactly this reason. It is a true statement about the output and says nothing about the input — the
edit count is what tells you how much changed.
Step 5 — Look at what actually changed, and get alternatives
The result carries the edits as runs of changed bases, so the diff draws on the map rather than being a wall of two sequences. 226 runs across 1,047 bp is roughly one change every 4–5 bases, which is what a synonymous recode looks like.
If you want more than one candidate, there is a knob for it, and it is honest about why. The solver enumerates exhaustively whenever a localized sub-problem is smaller than the randomization threshold (default 10,000) — and at that default essentially every sub-problem is, so the solve is deterministic. Running it five times would give you the same sequence five times. Lowering the threshold forces the genuinely stochastic search paths, which return different sequences satisfying the same constraints. The panel reports how much the candidates actually differed rather than assuming they did.
One caveat worth stating: the seed makes runs reproducible within this engine. It does not reproduce DnaChisel's numpy stream, so a seeded run here will not match a seeded Python run.
Step 6 — Check it before you order it
Round-trip the optimized sequence through the tools you would have used anyway:
- Codon Usage Analysis against the target host — rare codons should be gone, and you should look at whether you also removed the ones near the 5′ end that slow initiation on purpose.
- Restriction Digest — confirm the sites you need are still there and the
ones you forbade are not.
AvoidPatterncovers what you named; it cannot know about the enzyme you did not mention. - Properties and Protein Analysis — the translation should be unchanged, and it is worth proving rather than trusting.
- Back-translate if you are starting from protein rather than DNA; it reads the same host tables from the same place, so it cannot disagree with the optimizer.
What you did, and where each piece lives
| Step | Tool | Hand-off |
|---|---|---|
| 1 | Codon Usage Analysis | Rare-codon census + CAI, per organism |
| 2 | Codon Tables | The host table, bundled or live or built |
| 3–4 | Codon Optimization | An optimized CDS with every constraint evaluated |
| 5 | Same panel | The edit runs, drawn on the map; alternative candidates |
| 6 | Digest · Properties · Protein Analysis | Confirmation before you spend money |
If it does not work
| Symptom | Where to look |
|---|---|
| "not a multiple of 3" | Your region is not an in-frame CDS. The message gives the length it saw |
| The solve fails | A constraint is unsatisfiable. It is named — usually GC bounds too tight for the window size, or a forbidden pattern the protein forces |
| CAI is unavailable | Only S. cerevisiae has CAI weights in the bundled pydna tables. The rare-codon scan still works |
| Five runs give five identical sequences | Expected. Lower the randomization threshold to force the stochastic paths |
| A seeded run does not match your Python run | It will not. The seed is reproducible within this engine only |
| The optimized gene expresses worse | Check whether you flattened the 5′ end. Maximum-codon optimization is not always the right objective — harmonize_rca exists for exactly this |
Related
The tools, in depth: Codon Optimization · Codon Usage Analysis · Codon Tables · Back-translate
What follows: RBS & Operon Design for the expression context around the CDS, then Golden Gate or clone-and-verify to build it.
The canvas this draws on: the sequence map.
Design a CRISPR guide, check where else it binds, and measure the edit you got
The route: enumerate guides on the target → read the scores for what they are → search for off-target sites → screen the guide for cloning → sequence the edited pool and decompose it.
Read a variant call set at a locus
The route: open the call set → jump to the region → read the records → check the census against what you expected → apply a change to the sequence, or hand the region to the alignment.