evolutionary-stability-optimizer 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evolutionary_stability_optimizer-0.1.0/LICENSE +21 -0
- evolutionary_stability_optimizer-0.1.0/PKG-INFO +501 -0
- evolutionary_stability_optimizer-0.1.0/README.md +468 -0
- evolutionary_stability_optimizer-0.1.0/eso/__init__.py +11 -0
- evolutionary_stability_optimizer-0.1.0/eso/cli.py +111 -0
- evolutionary_stability_optimizer-0.1.0/eso/codon_usage.py +96 -0
- evolutionary_stability_optimizer-0.1.0/eso/constraints.py +179 -0
- evolutionary_stability_optimizer-0.1.0/eso/custom_score.py +170 -0
- evolutionary_stability_optimizer-0.1.0/eso/data/__init__.py +0 -0
- evolutionary_stability_optimizer-0.1.0/eso/data/human-antibody-heavy-chain-codon-frequencies.csv +62 -0
- evolutionary_stability_optimizer-0.1.0/eso/data/human-antibody-light-chain-codon-frequencies.csv +62 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/__init__.py +0 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/_overlap.py +112 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/common_motifs.py +81 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/dispatch.py +185 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/methylation.py +96 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/motif_utils.py +77 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/recombination.py +329 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/slippage.py +249 -0
- evolutionary_stability_optimizer-0.1.0/eso/detection/staubility_variant.py +284 -0
- evolutionary_stability_optimizer-0.1.0/eso/io_utils.py +312 -0
- evolutionary_stability_optimizer-0.1.0/eso/optimize.py +266 -0
- evolutionary_stability_optimizer-0.1.0/eso/pipeline.py +322 -0
- evolutionary_stability_optimizer-0.1.0/eso/report.py +57 -0
- evolutionary_stability_optimizer-0.1.0/eso/sequence_utils.py +41 -0
- evolutionary_stability_optimizer-0.1.0/pyproject.toml +58 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Itamar Menuhin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: evolutionary-stability-optimizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Evolutionary Stability Optimizer (ESO) - detects and removes hypermutable sites (recombination, slippage, methylation hotspots) from engineered DNA sequences while preserving translation and codon usage.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: synthetic-biology,dna,codon-optimization,genetic-stability,bioinformatics
|
|
8
|
+
Author: Itamar Menuhin
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Provides-Extra: docx-report
|
|
20
|
+
Requires-Dist: biopython (>=1.87,<2.0)
|
|
21
|
+
Requires-Dist: dnachisel (>=3.2.16,<4.0.0)
|
|
22
|
+
Requires-Dist: numpy (>=1.26,<3.0)
|
|
23
|
+
Requires-Dist: pandas (>=3.0.3,<4.0.0)
|
|
24
|
+
Requires-Dist: python-Levenshtein (>=0.27.3,<1.0.0)
|
|
25
|
+
Requires-Dist: python-docx (>=1.1.0,<2.0.0) ; extra == "docx-report"
|
|
26
|
+
Requires-Dist: scikit-learn (>=1.9.0.0,<2.0.0.0)
|
|
27
|
+
Project-URL: Bug Tracker, https://github.com/itamar-menuhin/evolutionary-stability-optimizer/issues
|
|
28
|
+
Project-URL: Documentation, https://github.com/itamar-menuhin/evolutionary-stability-optimizer#readme
|
|
29
|
+
Project-URL: Homepage, https://github.com/itamar-menuhin/evolutionary-stability-optimizer
|
|
30
|
+
Project-URL: Repository, https://github.com/itamar-menuhin/evolutionary-stability-optimizer
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# ESO - Evolutionary Stability Optimizer
|
|
34
|
+
|
|
35
|
+
[](https://github.com/itamar-menuhin/evolutionary-stability-optimizer/actions/workflows/tests.yml)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](pyproject.toml)
|
|
38
|
+
|
|
39
|
+
ESO detects hypermutable sites in engineered DNA sequences and optimizes them away with
|
|
40
|
+
[DNAChisel](https://github.com/Edinburgh-Genome-Foundry/DNAChisel), while preserving the
|
|
41
|
+
amino-acid translation and optimizing for host codon usage. This implementation follows
|
|
42
|
+
the approach introduced in Menuhin-Gruman et al. (2022, *ACS Synthetic Biology*) - see
|
|
43
|
+
[Citation](#citation) below.
|
|
44
|
+
|
|
45
|
+
Genes built from repetitive or duplicated DNA elements (a side effect of standard codon
|
|
46
|
+
optimization, which tends to reuse the same "best" codon repeatedly) are prone to mutate
|
|
47
|
+
away during propagation in a host organism, through mechanisms like replication slippage
|
|
48
|
+
and recombination-mediated deletion. ESO detects these hotspots and asks DNAChisel to
|
|
49
|
+
route around them while it optimizes, using the empirical mutation-rate model from the
|
|
50
|
+
[EFM Calculator](https://doi.org/10.1021/acssynbio.5b00068) (Jack et al., 2015, ACS
|
|
51
|
+
Synthetic Biology).
|
|
52
|
+
|
|
53
|
+
## Quickstart
|
|
54
|
+
|
|
55
|
+
**1. Requirements**: Python 3.11 or newer. Check what you have installed:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python --version
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If that prints something below `Python 3.11`, or fails with "command not found"/"not
|
|
62
|
+
recognized", install a current version from [python.org/downloads](https://www.python.org/downloads/)
|
|
63
|
+
first (the installer's default settings are fine) before continuing.
|
|
64
|
+
|
|
65
|
+
**2. Install ESO.** In a terminal, `cd` into this folder (the one this README is in), then:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This can take a minute or two the first time. If it ends with a line that doesn't contain
|
|
72
|
+
the word `error`, it worked - skip to step 3. If you do see an error, check
|
|
73
|
+
[Troubleshooting](#troubleshooting) below before asking for help.
|
|
74
|
+
|
|
75
|
+
**3. Try it on a real sequence.** Put a FASTA file (a plain text file starting with a `>`
|
|
76
|
+
line, then the DNA sequence) in a folder by itself, e.g. `my_sequences/gene.fasta`
|
|
77
|
+
containing:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
>my_gene
|
|
81
|
+
ATGGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTGCTTAA
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
eso-optimize --input-folder my_sequences --output-path my_results
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**4. Check the result.** Look inside `my_results/gene/` - `final_sequence.txt` has the
|
|
91
|
+
optimized sequence, `recombination_sites.csv`/`slippage_sites.csv` list what was detected
|
|
92
|
+
and fixed. If the very last line printed to the terminal was `Success!`, it worked.
|
|
93
|
+
|
|
94
|
+
That's the whole loop. Everything below this point is reference material for going
|
|
95
|
+
further (different host organisms, custom scoring, locking regions from editing, and so
|
|
96
|
+
on) - not required to get a first result.
|
|
97
|
+
|
|
98
|
+
If you'd rather be walked through setup interactively instead of reading this file, try the
|
|
99
|
+
[ESO Onboarding Assistant](https://chatgpt.com/g/g-6a60d7b081108191a8bc208b89958267-eso-onboarding-assistant),
|
|
100
|
+
a ChatGPT assistant configured specifically for onboarding onto this tool (see
|
|
101
|
+
[`docs/custom-gpt-setup.md`](docs/custom-gpt-setup.md) for its configuration).
|
|
102
|
+
|
|
103
|
+
## Troubleshooting
|
|
104
|
+
|
|
105
|
+
- **`'eso-optimize' is not recognized` / `command not found: eso-optimize`** (common on
|
|
106
|
+
Windows right after a fresh install): the command was installed, but its folder isn't on
|
|
107
|
+
your terminal's PATH yet. Use this instead - it always works, no PATH needed:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python -m eso.cli --input-folder my_sequences --output-path my_results
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
(Every `eso-optimize ...` example in this README works identically as
|
|
114
|
+
`python -m eso.cli ...`.)
|
|
115
|
+
- **An error mentioning `Microsoft Visual Studio`, `CMake`, or "failed building wheel"
|
|
116
|
+
during `pip install .`**: this means `pip` tried to compile a dependency from source
|
|
117
|
+
instead of using a prebuilt version - almost always fixed by upgrading pip first
|
|
118
|
+
(`python -m pip install --upgrade pip`) and trying the install again, since an older
|
|
119
|
+
`pip` can miss prebuilt wheels that a newer one finds.
|
|
120
|
+
- **`ModuleNotFoundError` or `ImportError` right after install**: double check you're
|
|
121
|
+
running `python`/`eso-optimize` from the *same* Python installation you ran
|
|
122
|
+
`pip install .` with. If you're not sure, run
|
|
123
|
+
`python -m pip show eso` - if that fails, you installed into a different Python than the
|
|
124
|
+
one you're now running.
|
|
125
|
+
- **"No such file or directory" for your input folder**: `--input-folder` is relative to
|
|
126
|
+
wherever your terminal's current directory is - either `cd` there first, or use a full
|
|
127
|
+
path (e.g. `C:\Users\you\my_sequences` or `/Users/you/my_sequences`).
|
|
128
|
+
- **Nothing in the output folder / an empty `results` list**: ESO looks for files ending
|
|
129
|
+
in `.fasta`/`.fna`/`.ffn`/`.faa`/`.frn`/`.fa`/`.gb`/`.gbk`/`.genbank` (optionally
|
|
130
|
+
gzipped), directly inside `--input-folder` or one level under it - check your file's
|
|
131
|
+
extension matches one of those.
|
|
132
|
+
- Anything else: the error messages this tool prints are meant to be read directly and
|
|
133
|
+
acted on (not just a Python traceback to decode) - if one isn't clear, that's a bug in
|
|
134
|
+
the tool itself, worth reporting rather than working around.
|
|
135
|
+
|
|
136
|
+
## What it detects
|
|
137
|
+
|
|
138
|
+
- **Replication slippage** - short tandem repeats (a base unit of length 1-15 repeated
|
|
139
|
+
3+ times, or a single nucleotide repeated 4+ times) that polymerase can skip or
|
|
140
|
+
duplicate during replication.
|
|
141
|
+
- **Recombination-mediated deletion (RMD)** - pairs of near-identical sites (16+ nt,
|
|
142
|
+
within Levenshtein distance 1 of each other) that homologous recombination can delete
|
|
143
|
+
between.
|
|
144
|
+
- **Methylation motifs** - sequence motifs recognized by host methylation machinery,
|
|
145
|
+
which can trigger repair-associated mutation. Given as a PSSM/MEME file, a bundled
|
|
146
|
+
common motif (E. coli's Dam/Dcm systems), or your own IUPAC consensus string
|
|
147
|
+
(e.g. `"GATC"`) - no file needed for either of the latter two.
|
|
148
|
+
|
|
149
|
+
Two independently-developed implementations of the recombination/slippage detectors are
|
|
150
|
+
included (`eso.detection.recombination`/`eso.detection.slippage`, and
|
|
151
|
+
`eso.detection.staubility_variant`) - they haven't yet been reconciled into a single
|
|
152
|
+
canonical algorithm. Both are routed through `eso.detection.dispatch`
|
|
153
|
+
(`find_recombination_sites(seq, num_sites, mode="thorough" | "fast")` and
|
|
154
|
+
`find_slippage_sites(seq, num_sites, mode="default" | "fast")`), also exposed via
|
|
155
|
+
`eso.pipeline.main(..., recombination_mode=..., slippage_mode=...)` /
|
|
156
|
+
`eso-optimize --recombination-mode --slippage-mode`. Both of these collapse overlapping
|
|
157
|
+
candidates down to one representative per distinct site - a human-facing report/count
|
|
158
|
+
view, and what `--num-sites`/`num_sites` limits. `eso.pipeline.main`/`eso-optimize` always
|
|
159
|
+
build correction constraints from every detected candidate regardless (see "Using ESO as
|
|
160
|
+
a library" below if you're calling detection yourself, and
|
|
161
|
+
`docs/detector-comparisons.md`'s coverage-gap entry for why). See
|
|
162
|
+
[`docs/detector-comparisons.md`](docs/detector-comparisons.md) for the tradeoffs,
|
|
163
|
+
benchmarks, and bugs found while comparing them - recombination's two modes trade off
|
|
164
|
+
sensitivity for speed, but slippage's are fully equivalent in what they detect (a pure
|
|
165
|
+
speed choice). Methylation motif detection only has one implementation
|
|
166
|
+
(`eso.detection.methylation`) - a second was built and compared, but deleted after it
|
|
167
|
+
turned out to disagree in accuracy, not just speed (see the same doc for the writeup).
|
|
168
|
+
|
|
169
|
+
**Not included**: STABLES also carried a junction-linker hotspot checker
|
|
170
|
+
(`linker_suspect_utils.py`), applying the same slippage/recombination-detection idea to a
|
|
171
|
+
different, narrower problem - checking whether joining a target gene to a host sequence
|
|
172
|
+
via a linker creates a *new* hotspot at the junction. It was ported into an earlier version
|
|
173
|
+
of this repo (`eso.detection.junction_linker`) but removed: its detection thresholds (a
|
|
174
|
+
4-repeat homopolymer check, a 12-mer recombination check) don't match the calibrated -9
|
|
175
|
+
filter this codebase's other detectors use, and tracing them back into the original
|
|
176
|
+
STABLES `select_fusion_linkers.py` pipeline showed they act as a near-absolute veto in
|
|
177
|
+
STABLES' own linker-selection algorithm - deliberate, STABLES-specific conservatism for
|
|
178
|
+
screening many cheap, redesignable linker candidates, not a general-purpose hotspot
|
|
179
|
+
detector. That's a different problem from ESO's (evaluating one already-chosen sequence),
|
|
180
|
+
so it was judged out of scope for this library rather than reconciled with the other
|
|
181
|
+
detectors' thresholds.
|
|
182
|
+
|
|
183
|
+
## Install
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
poetry install
|
|
187
|
+
# or: pip install -e .
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Word-document diff reports (`eso.report`) need the optional `docx-report` extra:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
poetry install -E docx-report
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Usage
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from eso import main
|
|
200
|
+
|
|
201
|
+
message, results = main(
|
|
202
|
+
input_folder="path/to/fasta_files",
|
|
203
|
+
output_path="path/to/output",
|
|
204
|
+
optimize=True,
|
|
205
|
+
mini_gc=0.3,
|
|
206
|
+
maxi_gc=0.7,
|
|
207
|
+
method="use_best_codon",
|
|
208
|
+
organism_name="e_coli", # or a TaxID, or a bundled table (see eso.codon_usage)
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Or from the command line:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
eso-optimize --input-folder path/to/fasta_files --output-path path/to/output --organism-name e_coli
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
For each FASTA/GenBank file found in `input_folder`, this writes to `output_path/<file_stem>/`:
|
|
219
|
+
|
|
220
|
+
- `final_sequence.txt` - the optimized sequence, plus CAI-before/after and edit-count stats.
|
|
221
|
+
- `recombination_sites.csv` / `slippage_sites.csv` / `motif_sites.csv` - detected hotspots,
|
|
222
|
+
one row per distinct site (a human-facing report, limited by `--num-sites` if given).
|
|
223
|
+
- `recombination_sites_corrected.csv` / `slippage_sites_corrected.csv` (only written when
|
|
224
|
+
`optimize` is on) - every candidate that actually received a correction constraint during
|
|
225
|
+
optimization. This can list more rows than the report CSVs above - collapsing several
|
|
226
|
+
overlapping candidates down to one representative per distinct site is the right call for
|
|
227
|
+
a report, but optimization still needs to correct every one of them individually to avoid
|
|
228
|
+
silently leaving part of a real hotspot unconstrained (see
|
|
229
|
+
[`docs/detector-comparisons.md`](docs/detector-comparisons.md)'s overlap-collapse
|
|
230
|
+
coverage-gap entry). Check this file, not the report CSVs, if you're trying to account
|
|
231
|
+
for every edit in `final_sequence.txt`.
|
|
232
|
+
- `sequence_comparison.docx` - a diff view of original vs. optimized sequence (if the
|
|
233
|
+
`docx-report` extra is installed).
|
|
234
|
+
|
|
235
|
+
`organism_name` accepts anything supported by
|
|
236
|
+
[python-codon-tables](https://github.com/Edinburgh-Genome-Foundry/python_codon_tables)
|
|
237
|
+
(a species name or NCBI TaxID), or one of the bundled custom tables in
|
|
238
|
+
`eso.codon_usage.CODON_USAGE_TABLES` (`C1`, `kompas`, `human_antibody_heavy_chain`,
|
|
239
|
+
`human_antibody_light_chain`) for hosts not in that database.
|
|
240
|
+
|
|
241
|
+
See [`examples/antibody_optimization`](examples/antibody_optimization) for a complete
|
|
242
|
+
worked example (human antibody heavy/light chain optimization).
|
|
243
|
+
|
|
244
|
+
## Using ESO as a library, instead of files
|
|
245
|
+
|
|
246
|
+
`main()` (above) is file-in, file-out - convenient for a one-off CLI run, but if you
|
|
247
|
+
already have sequences in memory as part of your own code (e.g. generated, fetched from a
|
|
248
|
+
database, or produced by an earlier step in your own pipeline), you don't need to write
|
|
249
|
+
them to disk first. `eso.optimize.optimization_engine` (also importable as
|
|
250
|
+
`eso.optimization_engine`) takes a plain DNA string and returns a plain DNA string - no
|
|
251
|
+
files involved:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from eso import optimization_engine, suspect_site_extractor
|
|
255
|
+
|
|
256
|
+
seq = "ATG" + "GCT" * 15 + "TAA" # any DNA string you already have
|
|
257
|
+
|
|
258
|
+
# 1. detect hotspots (skip this step and the dataframes below entirely if you only
|
|
259
|
+
# want codon/GC optimization, with no hotspot avoidance)
|
|
260
|
+
sites = suspect_site_extractor(seq, compute_motifs=False, num_sites=50)
|
|
261
|
+
|
|
262
|
+
# 2. optimize, avoiding what was detected - use the _raw dataframes, NOT
|
|
263
|
+
# sites["df_recombination"]/sites["df_slippage"] (those are collapsed to one
|
|
264
|
+
# representative per distinct site, for a human-facing report/count - using
|
|
265
|
+
# them here can silently leave part of a real, only-partially-overlapping
|
|
266
|
+
# hotspot with no correction at all; see docs/detector-comparisons.md)
|
|
267
|
+
final_seq, objectives_summary, num_edits = optimization_engine(
|
|
268
|
+
seq,
|
|
269
|
+
organism_name="e_coli",
|
|
270
|
+
df_recombination=sites["df_recombination_raw"],
|
|
271
|
+
df_slippage=sites["df_slippage_raw"],
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`final_seq` is a plain `str` you can feed straight back into whatever your own code does
|
|
276
|
+
next (write it out yourself, pass it to another function, etc.) - nothing here touches
|
|
277
|
+
the filesystem. `suspect_site_extractor` (also importable as
|
|
278
|
+
`eso.suspect_site_extractor`) is the same detection step `main()` runs internally; call
|
|
279
|
+
it on its own if you only want the hotspot dataframes, with no optimization at all.
|
|
280
|
+
`num_sites` only limits `df_recombination`/`df_slippage` (the reported view) - the `_raw`
|
|
281
|
+
dataframes always include every detected candidate, regardless of `num_sites`.
|
|
282
|
+
|
|
283
|
+
This composes directly with a custom scoring function - just pass `custom_score_fn=...`
|
|
284
|
+
to `optimization_engine` instead of `organism_name`:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
final_seq, _, num_edits = optimization_engine(
|
|
288
|
+
seq,
|
|
289
|
+
custom_score_fn=my_model.predict, # any function: whole seq (str) -> a number, higher = better
|
|
290
|
+
df_recombination=sites["df_recombination_raw"],
|
|
291
|
+
df_slippage=sites["df_slippage_raw"],
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
See `optimization_engine`'s docstring (`eso/optimize.py`) for every parameter
|
|
296
|
+
(`mini_gc`/`maxi_gc`, `orf_regions`/`exclusion_regions`, `method`, and so on) - everything
|
|
297
|
+
available via `main()`/the CLI is available here too, just without the file layer.
|
|
298
|
+
|
|
299
|
+
## Scoring sequences your own way, instead of CAI/tAI
|
|
300
|
+
|
|
301
|
+
By default, optimization scores codon choices against a codon-usage table (CAI/tAI-style,
|
|
302
|
+
via `organism_name`). To score sequences with your own logic instead, write a Python file
|
|
303
|
+
defining a `score(seq)` function (`seq` is a plain DNA string like `"ATGCGT..."`, the whole
|
|
304
|
+
ORF being optimized; return a number, higher = better) - copy
|
|
305
|
+
[`examples/custom_score_template.py`](examples/custom_score_template.py) as a starting
|
|
306
|
+
point.
|
|
307
|
+
|
|
308
|
+
From the command line:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
eso-optimize --input-folder path/to/fasta_files --custom-score-file my_score.py
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
(`--custom-score-file` overrides `--organism-name`/`--method`.) A `--custom-score-file`
|
|
315
|
+
with a mistake in it - a missing `score` function, a typo, a function that crashes or
|
|
316
|
+
returns the wrong type - fails immediately with a plain-English message, before any
|
|
317
|
+
optimization runs, rather than surfacing later as a Python traceback.
|
|
318
|
+
|
|
319
|
+
From Python, either call `optimization_engine`/`eso.pipeline.main` directly with
|
|
320
|
+
`custom_score_fn`/`custom_score_minimize`, or reuse the same file-loading + validation the
|
|
321
|
+
CLI uses via `eso.custom_score.load_custom_score_from_file`:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from eso.custom_score import load_custom_score_from_file
|
|
325
|
+
from eso.optimize import optimization_engine
|
|
326
|
+
|
|
327
|
+
score_fn = load_custom_score_from_file("my_score.py") # same validation as the CLI
|
|
328
|
+
final_seq, _, _ = optimization_engine(seq, custom_score_fn=score_fn)
|
|
329
|
+
|
|
330
|
+
# or, skipping the file entirely:
|
|
331
|
+
final_seq, _, _ = optimization_engine(
|
|
332
|
+
seq,
|
|
333
|
+
custom_score_fn=lambda whole_orf: whole_orf.count("G") + whole_orf.count("C"),
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`custom_score_fn` is called once on the whole ORF being optimized, on every trial mutation
|
|
338
|
+
tried during `optimize()` - this can be slow for a long sequence or an expensive function
|
|
339
|
+
(a warning is raised). `custom_score_minimize=True` (`--custom-score-minimize` on the CLI)
|
|
340
|
+
treats a *lower* `custom_score_fn` value as better, instead of higher.
|
|
341
|
+
|
|
342
|
+
**Scope**: custom scoring is automatically restricted to `orf_regions` (one scored region
|
|
343
|
+
per ORF, matching how the built-in CAI/tAI codon-usage scoring is already scoped via
|
|
344
|
+
DNAChisel's `CodonOptimize(location=orf, ...)`) - `custom_score_fn` never sees any non-ORF
|
|
345
|
+
flanking sequence (UTRs, locked/excluded regions). If you don't pass `orf_regions`, this
|
|
346
|
+
is the whole sequence (trimmed to a multiple of 3), same as everywhere else in ESO.
|
|
347
|
+
|
|
348
|
+
**An earlier version of this feature also supported a "windowed" mode** (scoring
|
|
349
|
+
fixed-size chunks and summing them, mirroring how the built-in CAI/tAI scoring works
|
|
350
|
+
internally) as a claimed speed optimization. It was removed after benchmarking found no
|
|
351
|
+
case where it was actually faster than the whole-ORF evaluation above - comparable at
|
|
352
|
+
best, meaningfully slower at worst, since DNAChisel's own optimizer ends up calling the
|
|
353
|
+
score function considerably more often when it's chunk-localizable - while carrying a
|
|
354
|
+
real, unpreventable correctness risk (a score that doesn't genuinely decompose per-chunk,
|
|
355
|
+
true of most real external/ML models, would silently compute a different, structurally
|
|
356
|
+
unrelated quantity, with no reliable way to detect this automatically). See
|
|
357
|
+
`docs/detector-comparisons.md` for the full investigation, including an initial benchmark
|
|
358
|
+
that was itself flawed and had to be corrected before the removal decision was made.
|
|
359
|
+
|
|
360
|
+
## Restricting ORF and exclusion regions per sequence
|
|
361
|
+
|
|
362
|
+
By default, the entire sequence is treated as one in-frame, translation-preserving ORF
|
|
363
|
+
with nothing locked. To instead give each sequence its own ORF region(s) (e.g. skip a
|
|
364
|
+
UTR) and/or exclusion regions that must never be edited (e.g. a known regulatory
|
|
365
|
+
element), pass `indexes` (from Python) or `--indexes-file` (from the CLI).
|
|
366
|
+
|
|
367
|
+
From the command line, point at a JSON file - copy
|
|
368
|
+
[`examples/indexes_template.json`](examples/indexes_template.json) as a starting point:
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
[
|
|
372
|
+
{
|
|
373
|
+
"file": "my_gene",
|
|
374
|
+
"seq_index": "0",
|
|
375
|
+
"orf_regions": "1-6, 51-68",
|
|
376
|
+
"exclusion_regions": "1-6, 50-68"
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
eso-optimize --input-folder path/to/fasta_files --indexes-file indexes.json
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
- `file` is the FASTA/GenBank file's stem (no extension, e.g. `"my_gene"` for
|
|
386
|
+
`my_gene.fasta`).
|
|
387
|
+
- `seq_index` is which record within that file, 0-indexed in file order, as a string.
|
|
388
|
+
- `orf_regions`/`exclusion_regions` are 1-indexed, inclusive region strings (e.g.
|
|
389
|
+
`"1-6, 51-68"` for two separate regions); omit `exclusion_regions` (or use `""`) for no
|
|
390
|
+
exclusions.
|
|
391
|
+
|
|
392
|
+
A malformed `--indexes-file` (bad JSON, missing `file`/`seq_index`) fails immediately with
|
|
393
|
+
a plain-English message; malformed region strings themselves are validated the same way
|
|
394
|
+
whether `indexes` came from a file or was passed directly to `eso.pipeline.main`/`main`.
|
|
395
|
+
|
|
396
|
+
From Python, pass the equivalent dict directly:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from eso import main
|
|
400
|
+
|
|
401
|
+
message, results = main(
|
|
402
|
+
input_folder="path/to/fasta_files",
|
|
403
|
+
indexes={("my_gene", "0"): ("1-6, 51-68", "1-6, 50-68")},
|
|
404
|
+
)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Motif sources: methylation, cryptic ribosome binding, cryptic promoters, and your own
|
|
408
|
+
|
|
409
|
+
Motif detection (`--compute-motifs`) isn't only about methylation - the same PSSM-based
|
|
410
|
+
scanner (`eso.detection.methylation.find_motif_sites`) works for any short sequence motif
|
|
411
|
+
you want to flag. Needs at least one motif, from any combination of three sources:
|
|
412
|
+
|
|
413
|
+
- **A MEME-minimal-format PSSM file** (`--motifs-path` / `motifs_path=`) - the original
|
|
414
|
+
option, for a curated or experimentally-derived motif set you already have as a file.
|
|
415
|
+
- **Bundled common motifs** (`--common-motifs dam,dcm` / `common_motifs=["dam", "dcm"]`)
|
|
416
|
+
- no file needed. See [`eso/detection/common_motifs.py`](eso/detection/common_motifs.py)
|
|
417
|
+
(`COMMON_MOTIFS`) for the full list and sources; currently:
|
|
418
|
+
- **Methylation** (E. coli, already a first-class host here - see `eso.codon_usage`'s
|
|
419
|
+
bundled `e_coli` table): `dam` (GATC, N6-methyladenine), `dcm` (CCWGG,
|
|
420
|
+
C5-methylcytosine on the internal C).
|
|
421
|
+
- **Cryptic ribosome binding**: `shine_dalgarno` (AGGAGG) - flags a copy of the
|
|
422
|
+
bacterial RBS consensus occurring *inside* a coding region, a known source of
|
|
423
|
+
unintended internal translation initiation.
|
|
424
|
+
- **Cryptic bacterial promoter elements**: `sigma70_minus35` (TTGACA),
|
|
425
|
+
`sigma70_minus10` (TATAAT) - the two sigma70 hexamers; an accidental occurrence of
|
|
426
|
+
either inside a coding sequence is a classic source of unwanted transcription. Each
|
|
427
|
+
hexamer is flagged independently (a real promoter needs both, correctly spaced,
|
|
428
|
+
which this detector doesn't check for) - treat an isolated hit as a coarse screen,
|
|
429
|
+
not a confirmed cryptic promoter.
|
|
430
|
+
- **Not included, and why** (see the module docstring for the full explanation):
|
|
431
|
+
transcription terminators (a secondary-structure property, not a fixed linear
|
|
432
|
+
motif), the Kozak sequence (something to match, not avoid - a different problem),
|
|
433
|
+
and restriction enzyme sites (already covered comprehensively by DNAChisel's own
|
|
434
|
+
`dnachisel.list_common_enzymes()` / `EnzymeSitePattern`, usable directly with
|
|
435
|
+
`AvoidPattern` during optimization - no need to duplicate it here).
|
|
436
|
+
- **Your own IUPAC consensus string** - the easy custom-motif path, no PSSM/MEME file
|
|
437
|
+
needed:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
from eso.detection.motif_utils import motif_from_consensus, motifs_from_consensus_dict
|
|
441
|
+
|
|
442
|
+
my_motif = motif_from_consensus("my_site", "GANTC") # N = any base, standard IUPAC codes
|
|
443
|
+
# or define several at once:
|
|
444
|
+
my_motifs = motifs_from_consensus_dict({"site_a": "GATC", "site_b": "CCWGG"})
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Pass the result as `relevant_motifs` to `eso.detection.methylation.find_motif_sites`
|
|
448
|
+
directly, or combine with the other two sources yourself before calling it - there's no
|
|
449
|
+
CLI flag for this one yet (a MEME file is still the CLI's path for anything beyond the
|
|
450
|
+
bundled common motifs).
|
|
451
|
+
|
|
452
|
+
Most published methylation/restriction motifs (REBASE, NEB's technical notes, the primary
|
|
453
|
+
literature) are naturally given exactly this way - a short consensus sequence with IUPAC
|
|
454
|
+
ambiguity codes (`W` = A or T, `N` = any base, etc.) - not as a position-probability
|
|
455
|
+
matrix, so this avoids hand-authoring a MEME file just to check for one. For organisms
|
|
456
|
+
beyond E. coli, [REBASE](http://rebase.neb.com) is the standard reference database for
|
|
457
|
+
methylation motifs - look up the motif there and pass it straight to
|
|
458
|
+
`motif_from_consensus`.
|
|
459
|
+
|
|
460
|
+
## Development
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
poetry install --with dev
|
|
464
|
+
pytest
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for this repo's testing/documentation
|
|
468
|
+
conventions, how the two independently-developed detector implementations
|
|
469
|
+
relate to each other, and how to report a bug.
|
|
470
|
+
|
|
471
|
+
## Citation
|
|
472
|
+
|
|
473
|
+
If you use this tool, please cite the paper it implements:
|
|
474
|
+
|
|
475
|
+
> Menuhin-Gruman, I., Arbel, M., Amitay, N., Sionov, K., Naki, D., Katzir, I., Edgar, O.,
|
|
476
|
+
> Bergman, S., & Tuller, T. (2022). Evolutionary Stability Optimizer (ESO): A Novel
|
|
477
|
+
> Approach to Identify and Avoid Mutational Hotspots in DNA Sequences While Maintaining
|
|
478
|
+
> High Expression Levels. *ACS Synthetic Biology*, 11(3), 1142-1151.
|
|
479
|
+
> https://doi.org/10.1021/acssynbio.1c00426
|
|
480
|
+
|
|
481
|
+
```bibtex
|
|
482
|
+
@article{menuhingruman2022eso,
|
|
483
|
+
title = {Evolutionary Stability Optimizer (ESO): A Novel Approach to Identify and
|
|
484
|
+
Avoid Mutational Hotspots in DNA Sequences While Maintaining High
|
|
485
|
+
Expression Levels},
|
|
486
|
+
author = {Menuhin-Gruman, Itamar and Arbel, Matan and Amitay, Niv and Sionov, Karin
|
|
487
|
+
and Naki, Doron and Katzir, Itai and Edgar, Omer and Bergman, Shaked
|
|
488
|
+
and Tuller, Tamir},
|
|
489
|
+
journal = {ACS Synthetic Biology},
|
|
490
|
+
volume = {11},
|
|
491
|
+
number = {3},
|
|
492
|
+
pages = {1142--1151},
|
|
493
|
+
year = {2022},
|
|
494
|
+
doi = {10.1021/acssynbio.1c00426}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## License
|
|
499
|
+
|
|
500
|
+
[MIT](LICENSE)
|
|
501
|
+
|