Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ All notable changes to MATPredict. Versions follow [semantic versioning](https:/
release tags may carry a clade suffix naming the lineage whose infrastructure that
release completed.

## [Unreleased]
## [0.6.1] — 2026-10-04 — `v0.6.1`

### Fixed
- Genomes that use an NCBI genetic code exonerate does not have no longer fail.
exonerate 2.4.0 has tables 1-6, 9-16 and 21-23; for any other code it exited 1
and the whole genome got no report. Code 26 (Alaninales: *Pachysolen*,
*Nakazawaea*; CUG = Ala) failed 20 of the 19,415 BFD Ascomycota genomes in the
v0.6.0 run. Exonerate is now skipped for such codes and gene models come from
miniprot alone (`polished_single`), which translates code 26 correctly.

### Changed
- No default NCBI e-mail. The address comes from `$MATPREDICT_NCBI_EMAIL` or the
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ authors:
affiliation: University of California-Riverside
orcid: "https://orcid.org/0000-0002-7591-0020"
title: "MATPredict: MAT locus identification in Fungi"
version: 0.6.0
version: 0.6.1
doi: TBD
date-released: 2026-10-03
date-released: 2026-10-04
url: "https://github.com/stajichlab/MATPredict"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ idiomorph or mating type it is (for example Plus/Minus, MAT1-1/MAT1-2, HD/PR
alleles). Every reference record traces to a publication or a validated
deposit, and every call reports the evidence behind it.

Current release: [`v0.6.0`](https://github.com/stajichlab/MATPredict/releases/tag/v0.6.0).
Current release: [`v0.6.1`](https://github.com/stajichlab/MATPredict/releases/tag/v0.6.1).
Changes: [CHANGELOG.md](CHANGELOG.md).

## Contents
Expand Down Expand Up @@ -385,14 +385,14 @@ No paper describes MATPredict yet. Cite the software and the release you used.
The citation metadata is in [CITATION.cff](CITATION.cff); GitHub shows it under
"Cite this repository".

> Stajich JE. MATPredict: MAT locus identification in Fungi. Version 0.6.0.
> Stajich JE. MATPredict: MAT locus identification in Fungi. Version 0.6.1.
> https://github.com/stajichlab/MATPredict

```bibtex
@software{matpredict,
author = {Stajich, Jason E.},
title = {MATPredict: MAT locus identification in Fungi},
version = {0.6.0},
version = {0.6.1},
url = {https://github.com/stajichlab/MATPredict},
year = {2026}
}
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "MATPredict"
version = "0.6.0"
version = "0.6.1"
description = "Fungal MAT locus annotator/classifier"
authors = [{name = "Jason Stajich", email = "jason.stajich@ucr.edu"}]
requires-python = ">=3.11"
Expand Down
31 changes: 31 additions & 0 deletions src/MATPredict/detect/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,23 @@ def search_fast_path(
#: single HSP with it and sum only across HSPs already grouped into one gene.


#: NCBI translation tables built into exonerate 2.4.0. Measured 2026-10-04 by
#: running `exonerate --geneticcode N` for every NCBI table: 1-6, 9-16 and
#: 21-23 run; 24 and higher exit 1 with "No built in genetic code
#: corresponding to id [N]". Table 26 (Alternative Yeast Nuclear, CUG = Ala,
#: the Alaninales: Pachysolen, Nakazawaea) made every such genome fail in the
#: v0.6.0 Ascomycota run (20 of 19,415 BFD genomes). miniprot 0.18 has table 26
#: (a synthetic gene with 15 CTG codons in 120 aa aligns at 100% identity with
#: `-T 26`, 87.5% with `-T 1` or `-T 12`).
EXONERATE_GENETIC_CODES = frozenset({1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 21, 22, 23})
_warned_exonerate_codes: set[int] = set()


def exonerate_supports_code(genetic_code: int | None) -> bool:
"""True when exonerate can translate with this code (None = table 1)."""
return genetic_code is None or int(genetic_code) in EXONERATE_GENETIC_CODES


def _gencode_args(genetic_code: int | None, flag: str) -> list[str]:
"""`[flag, str(code)]`, or `[]` for the standard code / unknown.

Expand Down Expand Up @@ -638,6 +655,20 @@ def polish_with_exonerate(
roles_by_family = _roles_by_family([family])
contig, win_start, _win_end = window

# exonerate has no table for some NCBI codes (EXONERATE_GENETIC_CODES).
# Running it anyway exits 1, which `_run_checked` makes fatal for the whole
# genome. Translating with a different table would give wrong residues.
# So skip exonerate: the caller's `classify` then records a
# `polished_single` model on miniprot alone, as for `polish: miniprot` genes.
if not exonerate_supports_code(genetic_code):
if int(genetic_code) not in _warned_exonerate_codes:
_warned_exonerate_codes.add(int(genetic_code))
logger.warning(
"exonerate has no genetic code %d; gene models use miniprot only",
int(genetic_code),
)
return None

with tempfile.TemporaryDirectory() as tmp_dir_name:
tmp_dir = Path(tmp_dir_name)
target_fasta = _extract_window(genome_fasta, window, tmp_dir)
Expand Down
54 changes: 54 additions & 0 deletions tests/detect/test_genetic_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,57 @@ def test_tblastn_omits_the_flag_for_the_standard_code(tmp_path):
search_localize(tmp_path / "g.fa", [fam], tmp_path / "r.faa", {"r": key},
runner=_runner(cmds), genetic_code=1)
assert "-db_gencode" not in next(c for c in cmds if c[0] == "tblastn")


def _exonerate_args(tmp_path, code, cmds):
from MATPredict.detect.family_registry import Family, FamilyKey
from MATPredict.detect.search import polish_with_exonerate
key = FamilyKey("P", "MAT")
fam = Family(key=key, vocabulary_type="enum", idiomorph_values=["a"],
idiomorph_pattern=None,
genes=[{"name": "g1", "role": "core_MAT"}], taxonomic_scope=[1])
(tmp_path / "g.fa").write_text(">c1\n" + "ACGT" * 100 + "\n")
return polish_with_exonerate(tmp_path / "g.fa", fam, "g1", tmp_path / "r.faa", {"r": key},
("c1", 1, 400), runner=_runner(cmds), genetic_code=code)


def test_exonerate_is_skipped_for_a_code_it_does_not_have(tmp_path):
"""Table 26 (Alaninales, CUG = Ala) is not built into exonerate 2.4.0. Run
anyway, it exits 1 and the whole genome failed (20 BFD genomes in the v0.6.0
Ascomycota run). Now exonerate is not run and the gene is left to miniprot."""
cmds = []
assert _exonerate_args(tmp_path, 26, cmds) is None
assert not any(c[0] == "exonerate" for c in cmds)


@pytest.mark.parametrize("code", [1, 12, None])
def test_exonerate_still_runs_for_codes_it_has(tmp_path, code):
cmds = []
_exonerate_args(tmp_path, code, cmds)
exo = next(c for c in cmds if c[0] == "exonerate")
if code in (None, 1):
assert "--geneticcode" not in exo
else:
assert exo[exo.index("--geneticcode") + 1] == str(code)


def test_miniprot_is_given_code_26(tmp_path):
from MATPredict.detect.family_registry import Family, FamilyKey
from MATPredict.detect.search import polish_with_miniprot
key = FamilyKey("P", "MAT")
fam = Family(key=key, vocabulary_type="enum", idiomorph_values=["a"],
idiomorph_pattern=None,
genes=[{"name": "g1", "role": "core_MAT"}], taxonomic_scope=[1])
(tmp_path / "g.fa").write_text(">c1\n" + "ACGT" * 100 + "\n")
cmds = []
polish_with_miniprot(tmp_path / "g.fa", fam, "g1", tmp_path / "r.faa", {"r": key},
("c1", 1, 400), runner=_runner(cmds), genetic_code=26)
mp = next(c for c in cmds if c[0] == "miniprot")
assert mp[mp.index("-T") + 1] == "26"


def test_the_exonerate_code_table_matches_exonerate_2_4_0():
from MATPredict.detect.search import EXONERATE_GENETIC_CODES, exonerate_supports_code
assert EXONERATE_GENETIC_CODES == {1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 21, 22, 23}
assert exonerate_supports_code(None) and exonerate_supports_code(12)
assert not exonerate_supports_code(26)
Loading