a1489c0fc1c99793e499e3915bc346b581664ad4
max
  Wed Jul 8 02:37:20 2026 -0700
Add support for all NCBI alternative genetic codes to dnautil

Adds struct geneticCode plus the full set of NCBI translation tables (from
gc.prt) so DNA can be translated with any genetic code, not just the Standard
and Vertebrate Mitochondrial codes that were hardcoded.

New API: geneticCodeForId/geneticCodeForName, lookupCodonInCode,
isStopCodonInCode, dnaTranslateSomeInCode (explicit-handle, reentrant), and
setDefaultGeneticCode for the process-global default. lookupCodon and friends
are unchanged in behavior, still defaulting to the Standard code.

The genetic code data lives in the auto-generated lib/geneticCodeTable.h,
produced by oneShot/dnaGeneticCodes/dnaGeneticCodes.py from NCBI's gc.prt.
Added a unit test under lib/tests. refs #16550

diff --git src/lib/dnautil.c src/lib/dnautil.c
index 5e27251f5a2..95fb6f075d3 100644
--- src/lib/dnautil.c
+++ src/lib/dnautil.c
@@ -5,125 +5,137 @@
  * The DNA it generates will include the bases 
  * as lowercase tcag.  It will generally accept
  * uppercase as well, and also 'n' or 'N' or '-'
  * for unknown bases. 
  *
  * Amino acids are stored as single character upper case. 
  *
  * This file is copyright 2002 Jim Kent, but license is hereby
  * granted for all use - public, private or commercial. */
 
 #include "common.h"
 #include "dnautil.h"
 
 
 struct codonTable
-/* The dread codon table. */
+/* The dread codon table.  Amino acid translations live in geneticCodes[]
+ * below (one entry per NCBI genetic code); this table just maps codon <->
+ * index and holds the unique per-codon labels used by lookupUniqCodon. */
     {
     DNA *codon;		/* Lower case. */
-    AA protCode;	/* Upper case. The "Standard" code */
-    AA mitoCode;	/* Upper case. Vertebrate Mitochondrial translations */
     AA uniqCode;	/* unique code for each codon */
     };
 
 struct codonTable codonTable[] =
-/* The master codon/protein table. */
-{
-    {"ttt", 'F', 'F', 'a'},
-    {"ttc", 'F', 'F', 'b'},
-    {"tta", 'L', 'L', 'c'},
-    {"ttg", 'L', 'L', 'd'},
-
-    {"tct", 'S', 'S', 'e'},
-    {"tcc", 'S', 'S', 'f'},
-    {"tca", 'S', 'S', 'g'},
-    {"tcg", 'S', 'S', 'h'},
-
-    {"tat", 'Y', 'Y', 'i'},
-    {"tac", 'Y', 'Y', 'j'},
-    {"taa", 0, 0, 'k'},
-    {"tag", 0, 0, 'l'},
-
-    {"tgt", 'C', 'C', 'm'},
-    {"tgc", 'C', 'C', 'n'},
-    {"tga", 0, 'W', 'o'},
-    {"tgg", 'W', 'W', 'p'},
-
-
-    {"ctt", 'L', 'L', 'q'},
-    {"ctc", 'L', 'L', 'r'},
-    {"cta", 'L', 'L', 's'},
-    {"ctg", 'L', 'L', 't'},
-
-    {"cct", 'P', 'P', 'u'},
-    {"ccc", 'P', 'P', 'v'},
-    {"cca", 'P', 'P', 'w'},
-    {"ccg", 'P', 'P', 'x'},
-
-    {"cat", 'H', 'H', 'y'},
-    {"cac", 'H', 'H', 'z'},
-    {"caa", 'Q', 'Q', 'A'},
-    {"cag", 'Q', 'Q', 'B'},
-
-    {"cgt", 'R', 'R', 'C'},
-    {"cgc", 'R', 'R', 'D'},
-    {"cga", 'R', 'R', 'E'},
-    {"cgg", 'R', 'R', 'F'},
-
-
-    {"att", 'I', 'I', 'G'},
-    {"atc", 'I', 'I', 'H'},
-    {"ata", 'I', 'M', 'I'},
-    {"atg", 'M', 'M', 'J'},
-
-    {"act", 'T', 'T', 'K'},
-    {"acc", 'T', 'T', 'L'},
-    {"aca", 'T', 'T', 'M'},
-    {"acg", 'T', 'T', 'N'},
-
-    {"aat", 'N', 'N', 'O'},
-    {"aac", 'N', 'N', 'P'},
-    {"aaa", 'K', 'K', 'Q'},
-    {"aag", 'K', 'K', 'R'},
-
-    {"agt", 'S', 'S', 'S'},
-    {"agc", 'S', 'S', 'T'},
-    {"aga", 'R', 0, 'U'},
-    {"agg", 'R', 0, 'V'},
-
-
-    {"gtt", 'V', 'V', 'W'},
-    {"gtc", 'V', 'V', 'X'},
-    {"gta", 'V', 'V', 'Y'},
-    {"gtg", 'V', 'V', 'Z'},
-
-    {"gct", 'A', 'A', '1'},
-    {"gcc", 'A', 'A', '2'},
-    {"gca", 'A', 'A', '3'},
-    {"gcg", 'A', 'A', '4'},
-
-    {"gat", 'D', 'D', '5'},
-    {"gac", 'D', 'D', '6'},
-    {"gaa", 'E', 'E', '7'},
-    {"gag", 'E', 'E', '8'},
-
-    {"ggt", 'G', 'G', '9'},
-    {"ggc", 'G', 'G', '0'},
-    {"gga", 'G', 'G', '@'},
-    {"ggg", 'G', 'G', '$'},
+/* The master codon table, in NCBI codon order (base1 outer, base3 inner,
+ * each t,c,a,g), which is the order the geneticCodes[] strings index into. */
+{
+    {"ttt", 'a'},
+    {"ttc", 'b'},
+    {"tta", 'c'},
+    {"ttg", 'd'},
+
+    {"tct", 'e'},
+    {"tcc", 'f'},
+    {"tca", 'g'},
+    {"tcg", 'h'},
+
+    {"tat", 'i'},
+    {"tac", 'j'},
+    {"taa", 'k'},
+    {"tag", 'l'},
+
+    {"tgt", 'm'},
+    {"tgc", 'n'},
+    {"tga", 'o'},
+    {"tgg", 'p'},
+
+
+    {"ctt", 'q'},
+    {"ctc", 'r'},
+    {"cta", 's'},
+    {"ctg", 't'},
+
+    {"cct", 'u'},
+    {"ccc", 'v'},
+    {"cca", 'w'},
+    {"ccg", 'x'},
+
+    {"cat", 'y'},
+    {"cac", 'z'},
+    {"caa", 'A'},
+    {"cag", 'B'},
+
+    {"cgt", 'C'},
+    {"cgc", 'D'},
+    {"cga", 'E'},
+    {"cgg", 'F'},
+
+
+    {"att", 'G'},
+    {"atc", 'H'},
+    {"ata", 'I'},
+    {"atg", 'J'},
+
+    {"act", 'K'},
+    {"acc", 'L'},
+    {"aca", 'M'},
+    {"acg", 'N'},
+
+    {"aat", 'O'},
+    {"aac", 'P'},
+    {"aaa", 'Q'},
+    {"aag", 'R'},
+
+    {"agt", 'S'},
+    {"agc", 'T'},
+    {"aga", 'U'},
+    {"agg", 'V'},
+
+
+    {"gtt", 'W'},
+    {"gtc", 'X'},
+    {"gta", 'Y'},
+    {"gtg", 'Z'},
+
+    {"gct", '1'},
+    {"gcc", '2'},
+    {"gca", '3'},
+    {"gcg", '4'},
+
+    {"gat", '5'},
+    {"gac", '6'},
+    {"gaa", '7'},
+    {"gag", '8'},
+
+    {"ggt", '9'},
+    {"ggc", '0'},
+    {"gga", '@'},
+    {"ggg", '$'},
 };
 
+/* struct geneticCode is defined in dnautil.h.  geneticCodes[] below is
+ * auto-generated from NCBI's gc.prt; see the header of geneticCodeTable.h for
+ * how to regenerate it with src/oneShot/dnaGeneticCodes/dnaGeneticCodes.py.
+ * Its aa/starts strings index the same way as codonTable[] above. */
+#include "geneticCodeTable.h"
+
+/* The genetic code lookupCodon/isStopCodon/dnaTranslateSome translate with.
+ * Defaults to Standard (id 1); change with setDefaultGeneticCode.  This is
+ * process-global and not thread-safe. */
+static struct geneticCode *defaultGeneticCode = NULL;
+
 /* A table that gives values 0 for t
 			     1 for c
 			     2 for a
 			     3 for g
  * (which is order aa's are in biochemistry codon tables)
  * and gives -1 for all others. */
 int ntVal[256];
 int ntValLower[256];	/* NT values only for lower case. */
 int ntValUpper[256];	/* NT values only for upper case. */
 int ntVal5[256];
 int ntValNoN[256]; /* Like ntVal, but with T_BASE_VAL in place of -1 for nonexistent ones. */
 DNA valToNt[(N_BASE_VAL|MASKED_BASE_BIT)+1];
 
 /* convert tables for bit-4 indicating masked */
 int ntValMasked[256];
@@ -182,51 +194,113 @@
     valToNtMasked[C_BASE_VAL] = 'C';
     valToNtMasked[A_BASE_VAL] = 'A';
     valToNtMasked[G_BASE_VAL] = 'G';
     valToNtMasked[N_BASE_VAL] = 'N';
 
     valToNtMasked[T_BASE_VAL|MASKED_BASE_BIT] = 't';
     valToNtMasked[C_BASE_VAL|MASKED_BASE_BIT] = 'c';
     valToNtMasked[A_BASE_VAL|MASKED_BASE_BIT] = 'a';
     valToNtMasked[G_BASE_VAL|MASKED_BASE_BIT] = 'g';
     valToNtMasked[N_BASE_VAL|MASKED_BASE_BIT] = 'n';
 
     inittedNtVal = TRUE;
     }
 }
 
-/* Returns one letter code for protein, 
- * 0 for stop codon or X for bad input,
- * The "Standard" Code */
-AA lookupCodon(DNA *dna)
+struct geneticCode *geneticCodeForId(int id)
+/* Return the genetic code with the given NCBI transl_table id, or NULL if
+ * there is no such code. */
 {
-int ix;
 int i;
-char c;
+for (i=0; i<ArraySize(geneticCodes); ++i)
+    if (geneticCodes[i].id == id)
+	return &geneticCodes[i];
+return NULL;
+}
+
+struct geneticCode *geneticCodeForName(char *name)
+/* Return the genetic code with the given name (case-insensitive), or NULL if
+ * there is no such code. */
+{
+int i;
+for (i=0; i<ArraySize(geneticCodes); ++i)
+    if (sameWord(geneticCodes[i].name, name))
+	return &geneticCodes[i];
+return NULL;
+}
 
+static int codonIndex(DNA *dna)
+/* Return 0-63 index into a genetic code for the codon at dna, or -1 if any of
+ * the three bases is not a nucleotide. */
+{
+int ix = 0;
+int i;
 if (!inittedNtVal)
     initNtVal();
-ix = 0;
 for (i=0; i<3; ++i)
     {
     int bv = ntVal[(int)dna[i]];
     if (bv<0)
-	return 'X';
+	return -1;
     ix = (ix<<2) + bv;
     }
-c = codonTable[ix].protCode;
-return c;
+return ix;
+}
+
+AA lookupCodonInCode(struct geneticCode *code, DNA *dna)
+/* Return one letter code for protein using the given genetic code,
+ * 0 for stop codon, or X for bad input. */
+{
+int ix = codonIndex(dna);
+if (ix < 0)
+    return 'X';
+AA c = code->aa[ix];
+return (c == '*') ? 0 : c;
+}
+
+static void initDefaultGeneticCode()
+/* Make sure defaultGeneticCode points at something (the Standard code). */
+{
+if (defaultGeneticCode == NULL)
+    defaultGeneticCode = geneticCodeForId(1);
+}
+
+void setDefaultGeneticCode(int id)
+/* Set the genetic code used by lookupCodon/isStopCodon/dnaTranslateSome to the
+ * NCBI transl_table with the given id (1 = Standard).  Aborts on unknown id.
+ * Sets process-global state; not thread-safe. */
+{
+struct geneticCode *code = geneticCodeForId(id);
+if (code == NULL)
+    errAbort("setDefaultGeneticCode: unknown genetic code id %d", id);
+defaultGeneticCode = code;
+}
+
+/* Returns one letter code for protein,
+ * 0 for stop codon or X for bad input,
+ * using the default genetic code (Standard unless setDefaultGeneticCode
+ * changed it). */
+AA lookupCodon(DNA *dna)
+{
+initDefaultGeneticCode();
+return lookupCodonInCode(defaultGeneticCode, dna);
+}
+
+boolean isStopCodonInCode(struct geneticCode *code, DNA *dna)
+/* Return TRUE if it's a stop codon in the given genetic code. */
+{
+return lookupCodonInCode(code, dna) == 0;
 }
 
 boolean isStopCodon(DNA *dna)
 /* Return TRUE if it's a stop codon. */
 {
 return lookupCodon(dna) == 0;
 }
 
 boolean isKozak(char *dna, int dnaSize, int pos)
 /* Return TRUE if it's a Kozak compatible start. */
 {
 if (lookupCodon(dna+pos) != 'M')
    {
    return FALSE;
    }
@@ -252,50 +326,34 @@
 if (selenocysteine)
     {
     /* Luckily the mitochondria *also* replaces TGA with 
      * something else, even though it isn't selenocysteine */
     return lookupMitoCodon(dna) == 0;
     }
 else
     {
     return lookupCodon(dna) == 0;
     }
 }
 
 
 /* Returns one letter code for protein,
  * 0 for stop codon or X for bad input,
- * Vertebrate Mitochondrial Code */
+ * Vertebrate Mitochondrial Code (NCBI genetic code 2) */
 AA lookupMitoCodon(DNA *dna)
 {
-int ix;
-int i;
-char c;
-
-if (!inittedNtVal)
-    initNtVal();
-ix = 0;
-for (i=0; i<3; ++i)
-    {
-    int bv = ntVal[(int)dna[i]];
-    if (bv<0)
-	return 'X';
-    ix = (ix<<2) + bv;
-    }
-c = codonTable[ix].mitoCode;
-c = toupper(c);
-return c;
+return lookupCodonInCode(geneticCodeForId(2), dna);
 }
 
 AA lookupUniqCodon(DNA *dna)
 {
 int ix;
 int i;
 char c;
 
 if (!inittedNtVal)
     initNtVal();
 ix = 0;
 for (i=0; i<3; ++i)
     {
     int bv = ntVal[(int)dna[i]];
     if (bv<0)
@@ -317,50 +375,59 @@
     return -1;
 if ((v2 = ntVal[(int)start[1]]) < 0)
     return -1;
 if ((v3 = ntVal[(int)start[2]]) < 0)
     return -1;
 return ((v1<<4) + (v2<<2) + v3);
 }
 
 DNA *valToCodon(int val)
 /* Return  codon corresponding to val (0-63) */
 {
 assert(val >= 0 && val < 64);
 return codonTable[val].codon;
 }
 
-void dnaTranslateSome(DNA *dna, char *out, int outSize)
-/* Translate DNA upto a stop codon or until outSize-1 amino acids, 
- * whichever comes first. Output will be zero terminated. */
+void dnaTranslateSomeInCode(struct geneticCode *code, DNA *dna, char *out, int outSize)
+/* Translate DNA with the given genetic code upto a stop codon or until
+ * outSize-1 amino acids, whichever comes first. Output will be zero
+ * terminated. */
 {
 int i;
 int dnaSize;
 int protSize = 0;
 
 outSize -= 1;  /* Room for terminal zero */
 dnaSize = strlen(dna);
 for (i=0; i<dnaSize-2; i+=3)
     {
     if (protSize >= outSize)
         break;
-    if ((out[protSize++] = lookupCodon(dna+i)) == 0)
+    if ((out[protSize++] = lookupCodonInCode(code, dna+i)) == 0)
         break;
     }
 out[protSize] = 0;
 }
 
+void dnaTranslateSome(DNA *dna, char *out, int outSize)
+/* Translate DNA upto a stop codon or until outSize-1 amino acids,
+ * whichever comes first. Output will be zero terminated. */
+{
+initDefaultGeneticCode();
+dnaTranslateSomeInCode(defaultGeneticCode, dna, out, outSize);
+}
+
 /* A little array to help us decide if a character is a 
  * nucleotide, and if so convert it to lower case. */
 char ntChars[256];
 
 static void initNtChars()
 {
 static boolean initted = FALSE;
 
 if (!initted)
     {
     zeroBytes(ntChars, sizeof(ntChars));
     ntChars['a'] = ntChars['A'] = 'a';
     ntChars['c'] = ntChars['C'] = 'c';
     ntChars['g'] = ntChars['G'] = 'g';
     ntChars['t'] = ntChars['T'] = 't';