| """Generate per-species codon-usage tables from real Kazusa CUTG data.
|
|
|
| Reads ``core/data/cutg_plant_subset.spsum`` (the nuclear codon counts for the 18
|
| supported crops, taken verbatim from the Kazusa Codon Usage Tabulated from
|
| GenBank plant division) and writes ``core/codon_usage_tables.py`` containing:
|
|
|
| CODON_USAGE_TABLES {species: {aa: {codon: fraction}}} real per-AA usage
|
| SPECIES_CDS_COUNT {species: int} CDS compiled (provenance)
|
|
|
| Every species therefore uses its OWN measured codon usage — no cross-species
|
| proxies. Run from the ``cool/`` directory:
|
|
|
| python -m tools.build_codon_usage
|
|
|
| This keeps the data reproducible: the spsum subset is committed, and re-running
|
| the generator reproduces the table module byte-for-byte.
|
| """
|
| import os
|
|
|
| from core.codons import CODON_TO_AMINO_ACID
|
|
|
| HERE = os.path.dirname(os.path.abspath(__file__))
|
| CORE = os.path.normpath(os.path.join(HERE, "..", "core"))
|
| SUBSET = os.path.join(CORE, "data", "cutg_plant_subset.spsum")
|
| OUT = os.path.join(CORE, "codon_usage_tables.py")
|
|
|
|
|
| ORDER_RNA = ("CGA CGC CGG CGU AGA AGG CUA CUC CUG CUU UUA UUG UCA UCC UCG UCU AGC AGU "
|
| "ACA ACC ACG ACU CCA CCC CCG CCU GCA GCC GCG GCU GGA GGC GGG GGU GUA GUC GUG GUU "
|
| "AAA AAG AAC AAU CAA CAG CAC CAU GAA GAG GAC GAU UAC UAU UGC UGU UUC UUU "
|
| "AUA AUC AUU AUG UGG UAA UAG UGA").split()
|
| ORDER = [c.replace("U", "T") for c in ORDER_RNA]
|
| assert len(ORDER) == 64
|
|
|
|
|
| AA_ORDER = list("ACDEFGHIKLMNPQRSTVWY") + ["_"]
|
|
|
|
|
| def parse_subset(path):
|
| species, counts, names = {}, {}, {}
|
| cur = None
|
| with open(path, errors="replace") as fh:
|
| lines = [ln.rstrip("\n") for ln in fh]
|
| i = 0
|
| while i < len(lines):
|
| ln = lines[i]
|
| if ln.startswith("# species="):
|
| cur = ln.split("=", 1)[1].strip()
|
| elif cur and ln and ln[0].isdigit() and ":" in ln:
|
| names[cur] = ln.split(":", 1)[1].rsplit(":", 1)[0].strip()
|
| counts[cur] = int(ln.rsplit(":", 1)[1])
|
| nums = list(map(int, lines[i + 1].split()))
|
| assert len(nums) == 64, f"{cur}: expected 64 codon counts"
|
| species[cur] = nums
|
| i += 1
|
| cur = None
|
| i += 1
|
| return species, counts, names
|
|
|
|
|
| def to_usage(raw):
|
| by_aa = {}
|
| for codon, n in zip(ORDER, raw):
|
| by_aa.setdefault(CODON_TO_AMINO_ACID[codon], {})[codon] = n
|
| out = {}
|
| for aa, cod in by_aa.items():
|
| tot = sum(cod.values())
|
| out[aa] = {c: (round(v / tot, 3) if tot else 0.0) for c, v in cod.items()}
|
| return out
|
|
|
|
|
| def fmt_table(usage):
|
| rows = []
|
| for aa in AA_ORDER:
|
| if aa not in usage:
|
| continue
|
| inner = ", ".join(f"'{c}': {f:.3f}" for c, f in usage[aa].items())
|
| rows.append(f" '{aa}': {{{inner}}},")
|
| return "\n".join(rows)
|
|
|
|
|
| def main():
|
| species, counts, names = parse_subset(SUBSET)
|
| order = ['arabidopsis', 'rice', 'maize', 'tomato', 'soybean', 'wheat', 'barley',
|
| 'sorghum', 'potato', 'cassava', 'tobacco', 'grape', 'cotton', 'sugarcane',
|
| 'canola', 'banana', 'peanut', 'sunflower']
|
| lines = ['"""Per-species codon-usage tables — GENERATED, do not edit by hand.',
|
| "",
|
| "Source: Kazusa CUTG (Codon Usage Tabulated from GenBank), plant division.",
|
| "Each table is the measured nuclear codon usage for that species' own taxid —",
|
| "no cross-species proxies. Regenerate with: python -m tools.build_codon_usage",
|
| "",
|
| "SPECIES_CDS_COUNT records how many CDS were compiled per species (provenance /",
|
| "confidence): larger = more reliable codon statistics.",
|
| '"""',
|
| "",
|
| "CODON_USAGE_TABLES = {"]
|
| for sp in order:
|
| u = to_usage(species[sp])
|
| lines.append(f" # {names[sp]} (taxid in subset; {counts[sp]:,} CDS compiled)")
|
| lines.append(f" '{sp}': {{")
|
| lines.append(fmt_table(u))
|
| lines.append(" },")
|
| lines.append("}")
|
| lines.append("")
|
| lines.append("SPECIES_CDS_COUNT = {")
|
| for sp in order:
|
| lines.append(f" '{sp}': {counts[sp]},")
|
| lines.append("}")
|
| lines.append("")
|
| with open(OUT, "w") as fh:
|
| fh.write("\n".join(lines))
|
| print("wrote", os.path.relpath(OUT), "—", len(order), "species")
|
| for sp in order:
|
| print(f" {sp:12} {counts[sp]:>7,} CDS")
|
|
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|