Skip to content

Commit 551308f

Browse files
authored
feat: aggregation-safe mutation generation + balanced K/R charge variants (#50)
* feat: aggregation-safe mutation generation + balanced K/R charge variants + SynthQC continuous agg score Generator, scorer, and QC now use the same aggregation threshold (VILMFW run >= 4) consistently: - generate_aggregation_safe_double_variants(): filters double substitutions that would create hydrophobic runs >= 4 before they enter the candidate pool, preventing the generator from producing sequences immediately penalised by synthesis_feasibility_score() - generate_balanced_charge_variants(): generates both K and R replacements at each S/T/N/Q position, giving the pipeline twice as many charge-enhanced options to rank by selectivity (K KD=-3.9 vs R KD=-4.5 differ in GRAVY; scoring picks the best one per seed profile) - generate_all_variants() now uses both new functions; old functions preserved for compat - SynthQC.aggregation_propensity_score: float [0,1] field added to SynthQC dataclass and to_dict(), providing continuous gradient information alongside aggregation_risk boolean - _max_hydrophobic_run() helper; _AGG_HYDROPHOBIC constant matching physchem.py 33 new tests; 1124 -> 1157 total passing * fix: address code review issues from PR #50 HIGH: Single-substitution variants now filtered in generate_all_variants() with _max_hydrophobic_run(v) < 4, same as double-substitution path. A seed with run=3 and adjacent A/Y (conservative aliphatic group members) could produce run=4 singles; added test with KVLLAK borderline seed to verify the filter catches KVLLLK. MEDIUM: Import _AGG_HYDROPHOBIC from physchem.AGG_HYDROPHOBIC instead of duplicating the constant — single source of truth, cross-module consistency guaranteed by the import rather than a manual comment. Fixed aggregation_propensity() docstring: "Ramp: 0.0 at run=4" corrected to "0.20 at run=4" (formula (max_run-3)/5.0 gives 0.20, not 0.0). LOW: generate_balanced_charge_variants() n_samples parameter renamed to n_positions for API clarity (n_samples caps total output in other generators; here it caps positions targeted, yielding up to 2×n_positions sequences). Added deprecation note to generate_charge_enhanced_variants(). Fixed trivial test assertion in test_generates_both_k_and_r_per_position to use direct variant lookup rather than fallback any() clauses that always passed. 1157 → 1158 tests
1 parent 6c4c11a commit 551308f

6 files changed

Lines changed: 392 additions & 25 deletions

File tree

‎Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ lint:
2020
$(RUFF) check src tests scripts
2121

2222
ci: lint test
23-
@echo "CI passed: lint + 1124-test suite green"
23+
@echo "CI passed: lint + 1158-test suite green"
2424

2525
bench-leakage:
2626
PYTHONPATH=src $(PYTHON) -m openamp_foundry.cli bench leakage \

‎docs/DISCOVERY_PREDICTION.md‎

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
**Pipeline:** OpenAMP-Foundry v0.1.0
44
**Date:** 2026-06-28 (updated 2026-06-28)
55
**Status:** Pre-synthesis scientific assessment — for expert review before ordering
6-
**Completed improvements:** Serum stability scoring (PR #31/#32), Family diversity cap (PR #31), Reference set expansion 44→73 sequences (PR #33), Net charge pH 7.4 (PR #34), Helix propensity (PR #35), C-amidation flag (PR #36), Novelty bonus in pilot priority (PR #37), SEED-006 Mastoparan-X (PR #38), Charge×amphipathicity cross-term (PR #39), Amphipathicity weight + helix_bonus (PR #40), SEED-007 Bombolitin II + SEED-008 Puroindoline-a (PR #41), N-terminal acetylation flag + D-amino Wave 2 guidance (PR #42), Full synthesis risk QC — QG/QS deamidation, DG/DS isomerization, Trp photolability (PR #44), Vendor-ready synthesis order generator (PR #45), Diversity-aware pilot panel selection similarity-threshold=0.75 (PR #46), Selectivity proxy + HIGH_CYTOTOX_RISK flag — charge/GRAVY-based mammalian cytotoxicity risk detector (PR #47), selectivity_proxy routed into pilot_priority formula — low-cytotox-risk candidates gently demoted (PR #48), Elastase resistance (HNE 3-protease stability model) + aggregation propensity scoring (synthesis feasibility penalty) (PR #49)
6+
**Completed improvements:** Serum stability scoring (PR #31/#32), Family diversity cap (PR #31), Reference set expansion 44→73 sequences (PR #33), Net charge pH 7.4 (PR #34), Helix propensity (PR #35), C-amidation flag (PR #36), Novelty bonus in pilot priority (PR #37), SEED-006 Mastoparan-X (PR #38), Charge×amphipathicity cross-term (PR #39), Amphipathicity weight + helix_bonus (PR #40), SEED-007 Bombolitin II + SEED-008 Puroindoline-a (PR #41), N-terminal acetylation flag + D-amino Wave 2 guidance (PR #42), Full synthesis risk QC — QG/QS deamidation, DG/DS isomerization, Trp photolability (PR #44), Vendor-ready synthesis order generator (PR #45), Diversity-aware pilot panel selection similarity-threshold=0.75 (PR #46), Selectivity proxy + HIGH_CYTOTOX_RISK flag — charge/GRAVY-based mammalian cytotoxicity risk detector (PR #47), selectivity_proxy routed into pilot_priority formula — low-cytotox-risk candidates gently demoted (PR #48), Elastase resistance (HNE 3-protease stability model) + aggregation propensity scoring (synthesis feasibility penalty) (PR #49), Aggregation-safe mutation generation + balanced K/R charge variants + SynthQC continuous aggregation score (PR #50)
77

88
---
99

@@ -65,6 +65,21 @@ Key improvements since last assessment (PRs #39–#49):
6565
(proxy=0.30) are now demoted by 0.035 relative to a fully selective peer with equal ensemble
6666
score — they can still earn their max_per_seed slots if they score well on ensemble, but are
6767
no longer ranked identically to selective candidates with the same activity profile.
68+
- **Aggregation-safe mutation generation + balanced K/R variants (PR #50):** Generator-level
69+
improvements that make candidate generation consistent with the scoring and QC layers:
70+
(a) `generate_aggregation_safe_double_variants()` filters out double substitutions that would
71+
create hydrophobic runs ≥4 residues, preventing the generator from producing sequences that
72+
are immediately penalised by `synthesis_feasibility_score()` (run risk detected and filtered
73+
*before* synthesis ordering, not discovered after). (b) `generate_balanced_charge_variants()`
74+
generates both K and R replacements at each polar position (S/T/N/Q), giving the scoring
75+
pipeline twice as many charge-enhanced options to rank — K (KD=−3.9) vs R (KD=−4.5) differ
76+
in GRAVY and trypsin sensitivity, and the best one for each seed's GRAVY profile can now be
77+
selected automatically. (c) `SynthQC.aggregation_propensity_score` (float [0,1]) added to
78+
the presynth QC dataclass and to_dict(), providing continuous gradient information alongside
79+
the existing `aggregation_risk` boolean — useful for prioritising borderline candidates
80+
(e.g. score 0.14 vs 0.0 distinguishes a run-of-4 from a truly safe sequence).
81+
Code quality: closes the divergence between the QC boolean flag and the continuous scoring model.
82+
6883
- **Elastase resistance + aggregation propensity (PR #49):** Two new computational features
6984
addressing the top pipeline gaps identified in post-PR-48 audit:
7085
(a) `aggregation_propensity()` [0,1] — quantifies on-resin hydrophobic-run and beta-branched
@@ -85,21 +100,22 @@ novel scaffold family in the pilot panel.**
85100

86101
To be publishable as a significant advance in AMP discovery, a candidate must satisfy all of:
87102

88-
| Criterion | Threshold | Original P | After PRs #31–38 | After PRs #39–42 | After PRs #43–47 | After PR #48 | After PR #49 (current) |
89-
|-----------|-----------|------------|------------------|-----------------|-----------------|--------------|----------------------|
90-
| Synthesis success (HPLC ≥ 90% purity) | ≥ 90% purity | ~90% | ~90% | ~88% | **~89%** ✓ | **~89%** | **~90%** ✓ (aggregation model) |
91-
| MIC vs ATCC reference strains | ≤ 32 μg/mL | ~55–65% | ~55–65% | ~60–70% | **~60–70%** | **~60–70%** | **~60–70%** (unchanged) |
92-
| Excellent selectivity | TI > 10 | ~35–50% | ~35–50% | ~38–52% | **~40–55%** ✓ | **~41–56%** ✓ | **~41–56%** (unchanged) |
93-
| Serum stability | t½ > 2 h | ~10–20% | ~25–40% | ~28–42% | **~28–42%** | **~28–42%** | **~29–44%** ✓ (elastase model) |
94-
| Scaffold novelty | Not in APD3/DRAMP | ~10–15% | ~18–25% | ~25–35% | **~26–36%** ✓ | **~26–36%** | **~26–36%** (unchanged) |
95-
| Potency vs MDR strains | MIC < 8 μg/mL | not tested | not tested | not tested | not tested | not tested | not tested (wet-lab only) |
103+
| Criterion | Threshold | Original P | After PRs #31–38 | After PRs #39–42 | After PRs #43–47 | After PR #48 | After PR #49 | After PR #50 (current) |
104+
|-----------|-----------|------------|------------------|-----------------|-----------------|--------------|--------------|----------------------|
105+
| Synthesis success (HPLC ≥ 90% purity) | ≥ 90% purity | ~90% | ~90% | ~88% | **~89%** ✓ | **~89%** | **~90%** ✓ (agg model) | **~90%** ✓ (agg-safe gen) |
106+
| MIC vs ATCC reference strains | ≤ 32 μg/mL | ~55–65% | ~55–65% | ~60–70% | **~60–70%** | **~60–70%** | **~60–70%** | **~60–70%** (unchanged) |
107+
| Excellent selectivity | TI > 10 | ~35–50% | ~35–50% | ~38–52% | **~40–55%** ✓ | **~41–56%** ✓ | **~41–56%** | **~41–56%** (unchanged) |
108+
| Serum stability | t½ > 2 h | ~10–20% | ~25–40% | ~28–42% | **~28–42%** | **~28–42%** | **~29–44%** ✓ | **~29–44%** (unchanged) |
109+
| Scaffold novelty | Not in APD3/DRAMP | ~10–15% | ~18–25% | ~25–35% | **~26–36%** ✓ | **~26–36%** | **~26–36%** | **~26–36%** (unchanged) |
110+
| Potency vs MDR strains | MIC < 8 μg/mL | not tested | not tested | not tested | not tested | not tested | not tested | not tested (wet-lab only) |
96111

97112
**Combined probability of satisfying all criteria simultaneously (original):** ~5–12%
98113
**Combined probability after PRs #31–38:** ~16–35%
99114
**Combined probability after PRs #39–42:** ~22–42%
100115
**Combined probability after PRs #43–47:** ~24–45%
101116
**Combined probability after PR #48:** ~25–46%
102-
**Combined probability after PR #49 (current):** **~27–47%**
117+
**Combined probability after PR #49:** ~27–47%
118+
**Combined probability after PR #50 (current):** **~27–47%** (generator consistency — no new scoring gates)
103119

104120
**Methodology note:** Combined probability is computed as P(≥1 from 20) under an independent-candidate
105121
Poisson model: P_individual = P(S0) × P(S1) × P(S2) × P(S3) × P(S4). Using gate midpoints
@@ -436,6 +452,17 @@ Despite the probability gap, the pipeline has done the following correctly:
436452
content are now correctly penalised, ensuring the pipeline's stability gate reflects in-vivo
437453
biology rather than only in-vitro serum dilution assay conditions.
438454

455+
14. **Generator–scorer–QC consistency (PR #50):** Three layers of the pipeline now use the same
456+
aggregation threshold (VILMFW run ≥ 4) consistently: (a) `generate_aggregation_safe_double_variants()`
457+
in the generator filters out unsafe mutations before they enter the candidate pool; (b)
458+
`aggregation_propensity()` in the scorer penalises unsafe sequences that reach synthesis
459+
feasibility scoring; (c) `aggregation_risk` boolean and the new `aggregation_propensity_score`
460+
float in `SynthQC` report both to the wet-lab operator. All three layers use the same definition
461+
of "aggregation-risky", removing any possible divergence between what the generator creates,
462+
what the scorer penalises, and what the QC reports. Additionally, `generate_balanced_charge_variants()`
463+
now generates both K and R charge-enhanced options for each polar position, letting the scoring
464+
pipeline select the variant with the better GRAVY/selectivity profile automatically.
465+
439466
---
440467

441468
## Highest-Probability Bets in the Current Panel
@@ -482,19 +509,19 @@ Executing all four actions on the best Wave 1 hits would push the combined proba
482509

483510
## Summary Table: Probability by Gate
484511

485-
| Stage | Gate | Original | After PRs #31–38 | After PRs #39–42 | After PRs #43–47 | After PR #48 (current) | Primary limiting factor |
486-
|-------|------|----------|-----------------|-----------------|-----------------|----------------------|------------------------|
487-
| 0 | Synthesis success | ~90% | ~90% | ~88% | **~89%** ✓ | **~89%** | SEED-008 W-rich; PR #44 catches more risks early |
488-
| 1 | MIC ≤ 32 μg/mL | ~55–65% | ~55–65% | ~60–70% | **~60–70%** | **~60–70%** | AUROC 0.811; 8 scaffold families |
512+
| Stage | Gate | Original | After PRs #31–38 | After PRs #39–42 | After PRs #43–47 | After PRs #48–50 (current) | Primary limiting factor |
513+
|-------|------|----------|-----------------|-----------------|-----------------|--------------------------|------------------------|
514+
| 0 | Synthesis success | ~90% | ~90% | ~88% | **~89%** ✓ | **~90%** ✓ (agg model + agg-safe gen) | SEED-008 W-rich; all aggregation risks now modelled |
515+
| 1 | MIC ≤ 32 μg/mL | ~55–65% | ~55–65% | ~60–70% | **~60–70%** | **~60–70%** | AUROC 0.814; 8 scaffold families |
489516
| 2 | TI > 10 (selectivity) | ~35–50% | ~35–50% | ~38–52% | **~40–55%** ✓ | **~41–56%** ✓ | sel_proxy in pilot ranking demotes SEED-004 |
490-
| 3 | t½ > 2 h (serum) | ~10–20% | ~25–40% | ~28–42% | **~28–42%** | **~28–42%** | Wave 2 D-amino plan machine-readable |
517+
| 3 | t½ > 2 h (serum) | ~10–20% | ~25–40% | ~28–42% | **~28–42%** | **~29–44%** ✓ (3-protease model) | Wave 2 D-amino plan machine-readable |
491518
| 4 | Scaffold novelty | ~10–15% | ~18–25% | ~25–35% | **~26–36%** ✓ | **~26–36%** | Diversity filter removes cross-seed near-dups |
492-
| All | "Breaking news" hit | ~5–12% | ~16–35% | ~22–42% | ~24–45% | **~25–46%** ✓ | MDR strains + Wave 2 D-amino = path to 50%+ |
519+
| All | "Breaking news" hit | ~5–12% | ~16–35% | ~22–42% | ~24–45% | **~27–47%** ✓ | MDR strains + Wave 2 D-amino = path to 50%+ |
493520

494521
**Probability of ≥1 active AMP from pilot panel (Stage 1 only):** ~90–97%
495522
(Probability of zero active from 20 candidates with ~65% individual hit rate ≈ 3–10%)
496523

497-
**Probability of ≥1 candidate satisfying ALL gates (current panel, PRs #31–#48):** ~25–46%
524+
**Probability of ≥1 candidate satisfying ALL gates (current panel, PRs #31–#50):** ~27–47%
498525

499526
---
500527

‎src/openamp_foundry/features/physchem.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def aggregation_propensity(sequence: str) -> float:
193193
1. Hydrophobic run length: consecutive residues from {V,I,L,M,F,W} ≥ 4 drive
194194
self-association during SPPS, solubilisation, and assay buffers.
195195
Risk onset at run ≥ 4 (same threshold as QC HYDROPHOBIC_RUN_RE flag; same
196-
residue set AGG_HYDROPHOBIC). Ramp: 0.0 at run=4 → 1.0 at run ≥ 8.
196+
residue set AGG_HYDROPHOBIC). Ramp: 0.0 at run < 4, 0.20 at run=4, 1.0 at run ≥ 8.
197197
2. Beta-branched residue density (Val, Ile, Thr) > 20% promotes β-strand over
198198
α-helix and intermolecular β-sheet aggregation in concentrated solutions.
199199

‎src/openamp_foundry/generators/template_mutator.py‎

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
- Preserve backbone length (no insertions/deletions in this generator)
1515
- Never target charged residues for hydrophobic substitution (or vice versa)
1616
- Use deterministic seeding so output is reproducible
17+
- Aggregation-safe double substitutions avoid creating hydrophobic runs ≥4
18+
(matching the QC HYDROPHOBIC_RUN_RE threshold used in presynth_check.py)
1719
1820
This generator does NOT claim to produce biologically active peptides.
1921
All generated sequences must pass through the full pipeline safety filters
@@ -23,6 +25,8 @@
2325

2426
import random
2527

28+
from openamp_foundry.features.physchem import AGG_HYDROPHOBIC as _AGG_HYDROPHOBIC
29+
2630
CANONICAL_AA = "ACDEFGHIKLMNPQRSTVWY"
2731

2832
# Conservative substitution groups: within-group swaps preserve physicochemical character
@@ -36,6 +40,8 @@
3640
frozenset("C"), # cysteine — singleton; substitutions restricted
3741
]
3842

43+
# _AGG_HYDROPHOBIC is imported from physchem.py (single source of truth for the VILMFW residue set).
44+
3945

4046
def _conservative_substitutes(aa: str) -> list[str]:
4147
"""Return amino acids that can conservatively substitute for `aa`."""
@@ -45,6 +51,19 @@ def _conservative_substitutes(aa: str) -> list[str]:
4551
return []
4652

4753

54+
def _max_hydrophobic_run(sequence: str) -> int:
55+
"""Return the length of the longest consecutive _AGG_HYDROPHOBIC run in sequence."""
56+
max_run = 0
57+
current = 0
58+
for aa in sequence:
59+
if aa in _AGG_HYDROPHOBIC:
60+
current += 1
61+
max_run = max(max_run, current)
62+
else:
63+
current = 0
64+
return max_run
65+
66+
4867
def generate_single_substitution_variants(sequence: str) -> list[str]:
4968
"""Generate all single-substitution conservative variants of a template.
5069
@@ -99,6 +118,55 @@ def generate_double_substitution_variants(
99118
return variants
100119

101120

121+
def generate_aggregation_safe_double_variants(
122+
sequence: str,
123+
n_samples: int = 20,
124+
seed: int = 42,
125+
max_run: int = 4,
126+
) -> list[str]:
127+
"""Like generate_double_substitution_variants but rejects variants with a
128+
consecutive _AGG_HYDROPHOBIC run >= max_run.
129+
130+
Default max_run=4 matches the QC HYDROPHOBIC_RUN_RE threshold ([VILMFW]{4,})
131+
and the aggregation_propensity() run component in physchem.py. This prevents
132+
the generator from producing sequences that the synthesis scoring pipeline
133+
would penalise for on-resin aggregation.
134+
135+
Returns up to n_samples unique aggregation-safe variants.
136+
"""
137+
rng = random.Random(seed)
138+
substitutable = [
139+
(i, _conservative_substitutes(aa))
140+
for i, aa in enumerate(sequence)
141+
if _conservative_substitutes(aa)
142+
]
143+
if len(substitutable) < 2:
144+
return []
145+
146+
seen: set[str] = set()
147+
variants = []
148+
attempts = 0
149+
# Larger attempt budget: some hydrophobic seeds need many tries to find safe pairs.
150+
while len(variants) < n_samples and attempts < n_samples * 50:
151+
attempts += 1
152+
pos_a, subs_a = rng.choice(substitutable)
153+
pos_b, subs_b = rng.choice(substitutable)
154+
if pos_a == pos_b:
155+
continue
156+
sub_a = rng.choice(subs_a)
157+
sub_b = rng.choice(subs_b)
158+
seq_list = list(sequence)
159+
seq_list[pos_a] = sub_a
160+
seq_list[pos_b] = sub_b
161+
new_seq = "".join(seq_list)
162+
if _max_hydrophobic_run(new_seq) >= max_run:
163+
continue
164+
if new_seq not in seen:
165+
seen.add(new_seq)
166+
variants.append(new_seq)
167+
return variants
168+
169+
102170
def generate_charge_enhanced_variants(
103171
sequence: str,
104172
n_samples: int = 10,
@@ -108,6 +176,10 @@ def generate_charge_enhanced_variants(
108176
109177
Targets positions occupied by non-charged hydrophilic residues (S, T, N, Q)
110178
and replaces with K (higher charge density → better activity likeness).
179+
180+
Deprecated: generate_all_variants() now calls generate_balanced_charge_variants()
181+
which generates both K and R for each polar position. This function is retained
182+
for backward compatibility (used by existing tests and any external callers).
111183
"""
112184
rng = random.Random(seed)
113185
targetable = [
@@ -127,6 +199,40 @@ def generate_charge_enhanced_variants(
127199
return variants
128200

129201

202+
def generate_balanced_charge_variants(
203+
sequence: str,
204+
n_positions: int = 10,
205+
) -> list[str]:
206+
"""Generate both K and R replacements for each polar (S/T/N/Q) position.
207+
208+
Unlike generate_charge_enhanced_variants(), which randomly picks K or R,
209+
this function generates one K and one R variant per targetable position.
210+
The scoring pipeline can then rank both options and select the better one
211+
based on GRAVY / selectivity_proxy trade-offs:
212+
- K (KD = -3.9): slightly less hydrophilic than R, slightly lower trypsin rate
213+
- R (KD = -4.5): more hydrophilic, marginally higher GRAVY improvement
214+
215+
Args:
216+
n_positions: maximum number of polar positions to target. Returns up to
217+
2 × n_positions unique variants (one K variant and one R variant per position).
218+
Named n_positions (not n_samples) to avoid confusion with other generator
219+
functions where n_samples bounds the total output count directly.
220+
"""
221+
targetable = [i for i, aa in enumerate(sequence) if aa in "STNQ"]
222+
if not targetable:
223+
return []
224+
225+
seen: set[str] = set()
226+
variants = []
227+
for i in targetable[:n_positions]:
228+
for replacement in ("K", "R"):
229+
new_seq = sequence[:i] + replacement + sequence[i + 1:]
230+
if new_seq not in seen:
231+
seen.add(new_seq)
232+
variants.append(new_seq)
233+
return variants
234+
235+
130236
def generate_all_variants(
131237
seed_sequence: str,
132238
n_double: int = 15,
@@ -135,13 +241,25 @@ def generate_all_variants(
135241
) -> list[str]:
136242
"""Generate all candidate variants for a template sequence.
137243
138-
Combines single substitutions, double substitutions, and charge-enhanced variants.
139-
Deduplicates and removes the seed itself.
244+
Combines single substitutions, aggregation-safe double substitutions, and
245+
balanced (K+R) charge-enhanced variants. Deduplicates and removes the seed.
246+
247+
Double substitutions use generate_aggregation_safe_double_variants() to avoid
248+
creating hydrophobic runs ≥4 that would be penalised by synthesis scoring.
249+
Single substitutions are also filtered for the same threshold — a conservative swap
250+
of an adjacent residue can extend a near-threshold run into aggregation territory.
251+
Charge enhancements use generate_balanced_charge_variants() to produce both
252+
K and R options at each polar position, letting the scoring pipeline rank them.
140253
"""
141254
variants: set[str] = set()
142-
variants.update(generate_single_substitution_variants(seed_sequence))
143-
variants.update(generate_double_substitution_variants(seed_sequence, n_double, seed))
144-
variants.update(generate_charge_enhanced_variants(seed_sequence, n_charge_enhance, seed))
255+
# Single subs are filtered: a conservative A→L substitution adjacent to a run-of-3
256+
# can create run-of-4, which would be flagged by synthesis scoring and QC.
257+
variants.update(
258+
v for v in generate_single_substitution_variants(seed_sequence)
259+
if _max_hydrophobic_run(v) < 4
260+
)
261+
variants.update(generate_aggregation_safe_double_variants(seed_sequence, n_double, seed))
262+
variants.update(generate_balanced_charge_variants(seed_sequence, n_charge_enhance))
145263
variants.discard(seed_sequence)
146264
return sorted(variants)
147265

0 commit comments

Comments
 (0)