R/zzz.R

Defines functions .verify_nn_tables .build_all_tm_tables .complete_nn_rc

# =============================================================================
# .build_all_tm_tables() - updated with reverse complement completion (Bug 9 fix)
#
# WHAT CHANGED:
#   Added .complete_nn_rc() helper that adds the 6 missing reverse complement
#   rows to any standard 17-row DNA/RNA NN table.
#
#   The 16 Watson-Crick NN pairs split into:
#     10 asymmetric pairs: each has a distinct RC partner → 6 unique new rows needed
#        AA/TT ↔ TT/AA   (TT/AA missing)
#        CA/GT ↔ TG/AC   (TG/AC missing)
#        GA/CT ↔ TC/AG   (TC/AG missing)
#        CT/GA ↔ AG/TC   (AG/TC missing)
#        GT/CA ↔ AC/TG   (AC/TG missing)
#        GG/CC ↔ CC/GG   (CC/GG missing)
#     6 self-complementary palindromes: RC maps to themselves → no new rows needed
#        AT/TA, TA/AT, CG/GC, GC/CG, CA/GT→already, GC/CG→already
#        (Actually: AT/TA, TA/AT, CG/GC, GC/CG are true palindromes)
#
#   RNA_DNA_NN_Sugimoto_1995 already contains all 16 + 6 RC rows → skip.
#   RNA_NN_Chen_2012 has extra wobble rows → complete only the standard 16.
# =============================================================================

# -- Helper: add the 6 missing reverse complement rows to a standard NN table --
#
# There are exactly 16 valid Watson-Crick dinucleotide keys (XY/X'Y' where
# X' = complement of X, Y' = complement of Y). Published NN tables list only
# 10 of these - the 6 missing ones are thermodynamically equivalent to an
# existing row when read from the other strand direction.
#
# The complete mapping (verified by enumeration):
#   TT/AA  <- same ΔH/ΔS as  AA/TT   (homodimer palindrome)
#   AC/TG  <- same ΔH/ΔS as  CA/GT   (RC pair)
#   AG/TC  <- same ΔH/ΔS as  GA/CT   (RC pair)
#   TC/AG  <- same ΔH/ΔS as  CT/GA   (RC pair)
#   TG/AC  <- same ΔH/ΔS as  GT/CA   (RC pair)
#   CC/GG  <- same ΔH/ΔS as  GG/CC   (homodimer palindrome)
#
# @param tbl       Matrix with dimnames - a standard NN table (17+ rows).
# @param skip_rows Row names to exclude from completion (init/sym rows and any
#                  extra wobble rows like in RNA_NN_Chen_2012).
# @return          The input matrix with up to 6 new rows appended.
#
.complete_nn_rc <- function(tbl,
                            skip_rows = c("init", "init_A/T", "init_G/C",
                                          "init_oneG/C", "init_allA/T",
                                          "init_5T/A", "sym")) {
  # Fixed mapping: new key -> source key to copy values from
  # This is the complete, verified set - do not derive algorithmically
  # (TT/AA and CC/GG are self-palindromes under the RC formula and would
  # be missed by a naive "RC != self" filter).
  missing_map <- c(
    "TT/AA" = "AA/TT",
    "AC/TG" = "CA/GT",
    "AG/TC" = "GA/CT",
    "TC/AG" = "CT/GA",
    "TG/AC" = "GT/CA",
    "CC/GG" = "GG/CC"
  )
  
  # Only add rows not already present (handles RNA_DNA_NN_Sugimoto_1995
  # which was published with the full 16-pair set)
  to_add <- missing_map[!names(missing_map) %in% rownames(tbl)]
  
  if (length(to_add) == 0L) return(tbl)   # already complete
  
  # Verify source rows exist before copying
  missing_sources <- to_add[!to_add %in% rownames(tbl)]
  if (length(missing_sources) > 0L)
    stop(sprintf("Source rows missing from table: %s",
                 paste(missing_sources, collapse=", ")))
  
  new_rows           <- tbl[to_add, , drop = FALSE]
  rownames(new_rows) <- names(to_add)
  
  rbind(tbl, new_rows)
}

# -- Main table builder --------------------------------------------------------
.build_all_tm_tables <- function() {
  
  nn_col     <- c("left", "right")
  nn_row_std <- c("init", "init_A/T", "init_G/C", "init_oneG/C", "init_allA/T",
                  "init_5T/A", "sym",
                  "AA/TT", "AT/TA", "TA/AT", "CA/GT", "GT/CA",
                  "CT/GA", "GA/CT", "CG/GC", "GC/CG", "GG/CC")
  
  # -- DNA NN Tables -----------------------------------------------------------
  DNA_NN_SantaLucia_2004_bug <- .complete_nn_rc(matrix(c(
    0.2,-5.7,  2.2,6.9,  0,0,  0,0,  0,0,  0,0,  0,-1.4,
    -7.6,-21.3,  -7.2,-20.4,  -7.2,-21.3,  -8.5,-22.7,  -8.4,-22.4,
    -7.8,-21.0,  -8.2,-22.2,  -10.6,-27.2,  -9.8,-24.4,  -8.0,-19.9
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  DNA_NN_SantaLucia_2004 <- .complete_nn_rc(matrix(c(
    0.2,-5.7,  2.2,6.9,  0,0,  0,0,  0,0,  0,0,  0,-1.4,
    -7.6,-21.3,  -7.2,-20.4,  -7.2,-21.3,  -8.5,-22.7,  -8.4,-22.4,
    -7.8,-21.0,  -8.2,-22.2,  -10.6,-27.2,  -9.8,-24.4,  -8.0,-19.9
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  DNA_NN_Breslauer_1986 <- .complete_nn_rc(matrix(c(
    0,0,  0,0,  0,0,  0,-16.8,  0,-20.1,  0,0,  0,-1.3,
    -9.1,-24.0,  -8.6,-23.9,  -6.0,-16.9,  -5.8,-12.9,  -6.5,-17.3,
    -7.8,-20.8,  -5.6,-13.5,  -11.9,-27.8,  -11.1,-26.7,  -11.0,-26.6
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  DNA_NN_Sugimoto_1996 <- .complete_nn_rc(matrix(c(
    0.6,-9.0,  0,0,  0,0,  0,0,  0,0,  0,0,  0,-1.4,
    -8.0,-21.9,  -5.6,-15.2,  -6.6,-18.4,  -8.2,-21.0,  -9.4,-25.5,
    -6.6,-16.4,  -8.8,-23.5,  -11.8,-29.0,  -10.5,-26.4,  -10.9,-28.4
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  DNA_NN_Allawi_1998 <- .complete_nn_rc(matrix(c(
    0,0,  2.3,4.1,  0.1,-2.8,  0,0,  0,0,  0,0,  0,-1.4,
    -7.9,-22.2,  -7.2,-20.4,  -7.2,-21.3,  -8.5,-22.7,  -8.4,-22.4,
    -7.8,-21.0,  -8.2,-22.2,  -10.6,-27.2,  -9.8,-24.4,  -8.0,-19.9
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  # -- RNA NN Tables -----------------------------------------------------------
  RNA_NN_Freier_1986 <- .complete_nn_rc(matrix(c(
    0,-10.8,  0,0,  0,0,  0,0,  0,0,  0,0,  0,-1.4,
    -6.6,-18.4,  -5.7,-15.5,  -8.1,-22.6,  -10.5,-27.8,  -10.2,-26.2,
    -7.6,-19.2,  -13.3,-35.5,  -8.0,-19.4,  -14.2,-34.9,  -12.2,-29.7
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  RNA_NN_Xia_1998 <- .complete_nn_rc(matrix(c(
    3.61,-1.5,  3.72,10.5,  0,0,  0,0,  0,0,  0,0,  0,-1.4,
    -6.82,-19.0,  -9.38,-26.7,  -7.69,-20.5,  -10.44,-26.9,  -11.40,-29.5,
    -10.48,-27.1,  -12.44,-32.5,  -10.64,-26.7,  -14.88,-36.9,  -13.39,-32.7
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  
  # Chen_2012 has extra wobble rows - complete_nn_rc skips non-standard rows
  # automatically and only processes the 16 standard Watson-Crick rows.
  chen_rows <- c(nn_row_std,
                 "GT/TG","GG/TT","AG/TT","TG/AT","TT/AG","TG/GT",
                 "AT/TG","CG/GT","CT/GG","GG/CT","GT/CG")
  RNA_NN_Chen_2012 <- .complete_nn_rc(matrix(c(
    6.40,6.99,  3.85,11.04,  0,0,  0,0,  0,0,  0,0,  0,-1.4,
    -7.09,-19.8,  -9.11,-25.8,  -8.50,-22.9,  -11.03,-28.8,  -11.98,-31.3,
    -10.90,-28.5,  -13.21,-34.9,  -10.88,-27.4,  -16.04,-40.6,  -14.18,-35.0,
    -13.83,-46.9,  -17.82,-56.7,  -3.96,-11.6,  -0.96,-1.8,  -10.38,-31.8,
    -12.64,-38.9,  -7.39,-21.0,  -5.56,-13.9,  -9.44,-24.7,  -7.03,-16.8,
    -11.09,-28.8
  ), ncol=2, byrow=TRUE, dimnames=list(chen_rows, nn_col)),
  # Pass the extra wobble rows as skip so they are not RC-completed
  skip_rows = c("init","init_A/T","init_G/C","init_oneG/C","init_allA/T",
                "init_5T/A","sym",
                "GT/TG","GG/TT","AG/TT","TG/AT","TT/AG","TG/GT",
                "AT/TG","CG/GT","CT/GG","GG/CT","GT/CG"))
  
  # -- RNA/DNA Hybrid - already complete (22 rows including all RC pairs) ------
  # RNA_DNA_NN_Sugimoto_1995 was published with the full symmetric set.
  # .complete_nn_rc() will find nothing to add (returns tbl unchanged).
  hybrid_rows <- c("init","init_A/T","init_G/C","init_oneG/C","init_allA/T",
                   "init_5T/A","sym",
                   "AA/TT","AC/TG","AG/TC","AT/TA",
                   "CA/GT","CC/GG","CG/GC","CT/GA",
                   "GA/CT","GC/CG","GG/CC","GT/CA",
                   "TA/AT","TC/AG","TG/AC","TT/AA")
  RNA_DNA_NN_Sugimoto_1995 <- matrix(c(
    1.9,-3.9,  0,0,  0,0,  0,0,  0,0,  0,0,  0,0,
    -11.5,-36.4,  -7.8,-21.6,  -7.0,-19.7,  -8.3,-23.9,
    -10.4,-28.4,  -12.8,-31.9,  -16.3,-47.1,  -9.1,-23.5,
    -8.6,-22.9,   -8.0,-17.1,   -9.3,-23.2,  -5.9,-12.3,
    -7.8,-23.2,  -5.5,-13.5,  -9.0,-26.1,  -7.8,-21.9
  ), ncol=2, byrow=TRUE, dimnames=list(hybrid_rows, nn_col))
  # No .complete_nn_rc() needed - already has all 16 unique pairs
  
  # -- IMM, TMM, DE tables (unchanged) -----------------------------------------
  imm_rows <- c(
    "AG/TT","AT/TG","CG/GT","CT/GG","GG/CT","GG/TT","GT/CG","GT/TG",
    "TG/AT","TG/GT","TT/AG","AA/TG","AG/TA","CA/GG","CG/GA","GA/CG",
    "GG/CA","TA/AG","TG/AA","AC/TT","AT/TC","CC/GT","CT/GC","GC/CT",
    "GT/CC","TC/AT","TT/AC","AA/TC","AC/TA","CA/GC","CC/GA","GA/CC",
    "GC/CA","TA/AC","TC/AA","AA/TA","CA/GA","GA/CA","TA/AA","AC/TC",
    "CC/GC","GC/CC","TC/AC","AG/TG","CG/GG","GG/CG","TG/AG","AT/TT",
    "CT/GT","GT/CT","TT/AT","AI/TC","TI/AC","AC/TI","TC/AI","CI/GC",
    "GI/CC","CC/GI","GC/CI","AI/TA","TI/AA","AA/TI","TA/AI","CI/GA",
    "GI/CA","CA/GI","GA/CI","AI/TT","TI/AT","AT/TI","TT/AI","CI/GT",
    "GI/CT","CT/GI","GT/CI","AI/TG","TI/AG","AG/TI","TG/AI","CI/GG",
    "GI/CG","CG/GI","GG/CI","AI/TI","TI/AI","CI/GI","GI/CI"
  )
  DNA_IMM_Peyret_1999 <- matrix(c(
    1.0,0.9,    -2.5,-8.3,  -4.1,-11.7,  -2.8,-8.0,   3.3,10.4,
    5.8,16.3,  -4.4,-12.3,   4.1,9.5,   -0.1,-1.7,   -1.4,-6.2,
    -1.3,-5.3,  -0.6,-2.3,  -0.7,-2.3,   -0.7,-2.3,   -4.0,-13.2,
    -0.6,-1.0,   0.5,3.2,    0.7,0.7,     3.0,7.4,     0.7,0.2,
    -1.2,-6.2,  -0.8,-4.5,  -1.5,-6.1,    2.3,5.4,     5.2,13.5,
    1.2,0.7,    1.0,0.7,    2.3,4.6,     5.3,14.6,    1.9,3.7,
    0.6,-0.6,   5.2,14.2,  -0.7,-3.8,    3.4,8.0,     7.6,20.2,
    1.2,1.7,   -0.9,-4.2,  -2.9,-9.8,    4.7,12.9,    0.0,-4.4,
    -1.5,-7.2,   3.6,8.9,    6.1,16.4,   -3.1,-9.5,   -4.9,-15.3,
    -6.0,-15.8,  1.6,3.6,   -2.7,-10.8,  -5.0,-15.8,  -2.2,-8.4,
    0.2,-1.5,  -8.9,-25.5, -5.9,-17.4,  -8.8,-25.4,  -4.9,-13.9,
    -5.4,-13.7, -6.8,-19.1, -8.3,-23.8,  -5.0,-12.6,  -8.3,-25.0,
    -3.4,-11.2, -0.7,-2.6,  -1.3,-4.6,    2.6,8.9,    -7.8,-21.1,
    -7.0,-20.0, -7.6,-20.2,  0.49,-0.7,  -6.5,-22.0,  -5.6,-18.7,
    -0.8,-4.3,  -1.0,-2.4,  -3.5,-10.6,   0.1,-1.0,   -4.3,-12.1,
    -4.9,-15.8, -1.9,-8.5,   0.1,-1.8,    1.0,1.0,     7.1,21.3,
    -1.1,-3.2,   5.8,16.9,  -7.6,-22.0,  -3.3,-11.9,   0.1,-2.3,
    1.3,3.0,   -0.5,-1.3
  ), ncol=2, byrow=TRUE, dimnames=list(imm_rows, nn_col))
  
  tmm_rows <- c(
    "AA/TA","TA/AA","CA/GA","GA/CA","AC/TC","TC/AC","CC/GC","GC/CC",
    "AG/TG","TG/AG","CG/GG","GG/CG","AT/TT","TT/AT","CT/GT","GT/CT",
    "AA/TC","AC/TA","CA/GC","CC/GA","GA/CC","GC/CA","TA/AC","TC/AA",
    "AC/TT","AT/TC","CC/GT","CT/GC","GC/CT","GT/CC","TC/AT","TT/AC",
    "AA/TG","AG/TA","CA/GG","CG/GA","GA/CG","GG/CA","TA/AG","TG/AA",
    "AG/TT","AT/TG","CG/GT","CT/GG","GG/CT","GT/CG","TG/AT","TT/AG"
  )
  DNA_TMM_Bommarito_2000 <- matrix(c(
    -3.1,-7.8,  -2.5,-6.3,  -4.3,-10.7,  -8.0,-22.5,  -0.1,0.5,
    -0.7,-1.3,  -2.1,-5.1,  -3.9,-10.6,  -1.1,-2.1,   -1.1,-2.7,
    -3.8,-9.5,  -0.7,-19.2, -2.4,-6.5,   -3.2,-8.9,   -6.1,-16.9,
    -7.4,-21.2, -1.6,-4.0,  -1.8,-3.8,   -2.6,-5.9,   -2.7,-6.0,
    -5.0,-13.8, -3.2,-7.1,  -2.3,-5.9,   -2.7,-7.0,   -0.9,-1.7,
    -2.3,-6.3,  -3.2,-8.0,  -3.9,-10.6,  -4.9,-13.5,  -3.0,-7.8,
    -2.5,-6.3,  -0.7,-1.2,  -1.9,-4.4,   -2.5,-5.9,   -3.9,-9.6,
    -6.0,-15.5, -4.3,-11.1, -4.6,-11.4,  -2.0,-4.7,   -2.4,-5.8,
    -3.2,-8.7,  -3.5,-9.4,  -3.8,-9.0,   -6.6,-18.7,  -5.7,-15.9,
    -5.9,-16.1, -3.9,-10.5, -3.6,-9.8
  ), ncol=2, byrow=TRUE, dimnames=list(tmm_rows, nn_col))
  
  de_dna_rows <- c(
    "AA/.T","AC/.G","AG/.C","AT/.A","CA/.T","CC/.G","CG/.C","CT/.A",
    "GA/.T","GC/.G","GG/.C","GT/.A","TA/.T","TC/.G","TG/.C","TT/.A",
    ".A/AT",".C/AG",".G/AC",".T/AA",".A/CT",".C/CG",".G/CC",".T/CA",
    ".A/GT",".C/GG",".G/GC",".T/GA",".A/TT",".C/TG",".G/TC",".T/TA"
  )
  DNA_DE_Bommarito_2000 <- matrix(c(
    0.2,2.3,  -6.3,-17.1, -3.7,-10.0, -2.9,-7.6,   0.6,3.3,
    -4.4,-12.6, -4.0,-11.9, -4.1,-13.0, -1.1,-1.6,  -5.1,-14.0,
    -3.9,-10.9, -4.2,-15.0, -6.9,-20.0, -4.0,-10.9, -4.9,-13.8,
    -0.2,-0.5,  -0.7,-0.8,  -2.1,-3.9,  -5.9,-16.5, -0.5,-1.1,
    4.4,14.9,  -0.2,-0.1,  -2.6,-7.4,   4.7,14.2,  -1.6,-3.6,
    -3.9,-11.2, -3.2,-10.4, -4.1,-13.1,  2.9,10.4,  -4.4,-13.1,
    -5.2,-15.0, -3.8,-12.6
  ), ncol=2, byrow=TRUE, dimnames=list(de_dna_rows, nn_col))
  
  de_rna_rows <- c(
    ".T/AA",".T/CA",".T/GA",".T/TA",".G/AC",".G/CC",".G/GC",".G/TC",
    ".C/AG",".C/CG",".C/GG",".C/TG",".T/AG",".T/CG",".T/GG",".T/TG",
    ".A/AT",".A/CT",".A/GT",".A/TT",".G/AT",".G/CT",".G/GT",".G/TT",
    "AT/.A","CT/.A","GT/.A","TT/.A","AG/.C","CG/.C","GG/.C","TG/.C",
    "AC/.G","CC/.G","GC/.G","TC/.G","AT/.G","CT/.G","GT/.G","TT/.G",
    "AA/.T","CA/.T","GA/.T","TA/.T","AG/.T","CG/.T","GG/.T","TG/.T"
  )
  RNA_DE_Turner_2010 <- matrix(c(
    -4.9,-13.20,  -0.9,-1.30,  -5.5,-15.10,  -2.3,-5.50,
    -9.0,-23.50,  -4.1,-10.60, -8.6,-22.20,  -7.5,-20.31,
    -7.4,-20.30,  -2.8,-7.70,  -6.4,-16.40,  -3.6,-9.70,
    -4.9,-13.20,  -0.9,-1.30,  -5.5,-15.10,  -2.3,-5.50,
    -5.7,-16.10,  -0.7,-1.90,  -5.8,-16.40,  -2.2,-6.80,
    -5.7,-16.10,  -0.7,-1.90,  -5.8,-16.40,  -2.2,-6.80,
    -0.5,-0.60,    6.9,22.60,   0.6,2.60,     0.6,2.60,
    -1.6,-4.50,    0.7,3.20,   -4.6,-14.80,  -0.4,-1.30,
    -2.4,-6.10,    3.3,11.60,   0.8,3.20,    -1.4,-4.20,
    -0.5,-0.60,    6.9,22.60,   0.6,2.60,     0.6,2.60,
    1.6,6.10,     2.2,8.10,    0.7,3.50,     3.1,10.60,
    1.6,6.10,     2.2,8.10,    0.7,3.50,     3.1,10.60
  ), ncol=2, byrow=TRUE, dimnames=list(de_rna_rows, nn_col))
  
  # -- GC Content Coefficient Table (unchanged) -----------------------------
  GC_VARTAB <- data.frame(
    A = c(69.3, 81.5, 81.5, 81.5, 78.0, 67.0, 81.5, 77.1),
    B = c(0.41, 0.41, 0.41, 0.41, 0.70, 0.80, 0.41, 0.41),
    C = c(650,  675,  675,  500,  500,  500,  600,  528),
    D = rep(1, 8),
    salt_correct = c(NA, NA, "Schildkraut2010",
                        rep("Wetmur1991", 3),
                        "Schildkraut2010", "SantaLucia1998-1"),
    row.names = c("Chester1993","QuikChange","Schildkraut1965",
                  "Wetmur1991_MELTING","Wetmur1991_RNA","Wetmur1991_RNA/DNA",
                  "Primer3Plus","vonAhsen2001"),
    stringsAsFactors = FALSE
  )
  
  # -- Weber group (UFMG) parameter sets, from VarGibbs 5.0 -----------------
  # Generated from the shipped .par files, full precision. Mapping verified:
  # P-SL98/P-SG96/P-XIA98/P-FR86 reproduce the tables above exactly, and
  # P-PY99+P-AL97+P-AL98{,B,C}+P-WT05 reproduce all 87 IMM rows exactly.
  # Each table carries attr 'salt_mM'; do not salt-correct a set twice.

  # ---- DNA, salt-resolved (Weber 2015; Fig. 3 data, never tabulated) ----
  # AOP-OW04 at 69 mM [Na+]
  # source: AOP-OW04-69.par
  DNA_NN_Weber_OW04_69 <- .complete_nn_rc(matrix(c(
         0.0000,   0.0000,     4.8549,  15.0479,     2.7622,   8.5772,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -0.5873,    -6.8315, -20.4105,
        -3.0941,  -8.7078,   -10.4021, -32.1233,   -14.7626, -43.1062,    -0.0815,   1.5161,
        -7.4408, -20.5458,    -6.1987, -17.9548,   -14.9050, -41.4616,    -1.8135,  -2.2130,
        -9.0220, -24.7968
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(DNA_NN_Weber_OW04_69, "salt_mM") <- 69

  # AOP-OW04 at 119 mM [Na+]
  # source: AOP-OW04-119.par
  DNA_NN_Weber_OW04_119 <- .complete_nn_rc(matrix(c(
         0.0000,   0.0000,     4.0569,  11.1000,     2.2427,   5.4788,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -0.5873,    -7.5348, -22.2100,
        -0.4697,  -0.6289,   -13.8995, -42.2140,   -16.2807, -46.9669,     0.2914,   2.7872,
        -6.2247, -16.5012,    -8.2538, -23.7549,   -15.3115, -41.9659,    -3.1981,  -6.0332,
       -10.4625, -28.5610
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(DNA_NN_Weber_OW04_119, "salt_mM") <- 119

  # AOP-OW04 at 220 mM [Na+]
  # source: AOP-OW04-220.par
  DNA_NN_Weber_OW04_220 <- .complete_nn_rc(matrix(c(
         0.0000,   0.0000,     4.3309,  12.6063,     2.4710,   6.7606,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -0.5873,    -7.4688, -21.6832,
        -0.8398,  -1.8294,   -12.9102, -38.6391,   -15.5770, -44.4811,     0.7896,   4.1875,
        -6.7601, -18.0953,    -7.5420, -21.2968,   -15.6929, -43.0770,    -2.5826,  -4.1353,
        -9.8508, -26.5768
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(DNA_NN_Weber_OW04_220, "salt_mM") <- 220

  # AOP-OW04 at 621 mM [Na+]
  # source: AOP-OW04-621.par
  DNA_NN_Weber_OW04_621 <- .complete_nn_rc(matrix(c(
         0.0000,   0.0000,     2.8755,  10.9634,     1.4790,   6.6027,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -0.5873,    -5.7538, -16.2941,
        -0.4605,  -0.5974,   -10.7988, -31.9130,   -10.7744, -30.3081,    -0.8610,  -0.9626,
        -4.1898, -10.7677,    -7.2483, -20.2750,   -10.5953, -28.4924,    -4.4725, -10.2831,
        -8.1045, -21.6798
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(DNA_NN_Weber_OW04_621, "salt_mM") <- 621

  # AOP-OW04 at 1020 mM [Na+]
  # source: AOP-OW04-1020.par
  DNA_NN_Weber_OW04_1020 <- .complete_nn_rc(matrix(c(
         0.0000,   0.0000,     6.4573,  16.9991,     4.8550,  11.8191,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -0.5873,    -8.6278, -24.3084,
        -2.1846,  -5.6406,   -13.8317, -40.2750,   -15.0245, -41.9995,    -1.7673,  -3.2100,
        -6.9985, -18.5138,    -9.5341, -26.2352,   -14.4129, -38.8461,    -6.7989, -15.8173,
       -11.0161, -29.2985
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(DNA_NN_Weber_OW04_1020, "salt_mM") <- 1021

  # ---- DNA, general purpose ----
  # AOP-CMB, combined dataset (recommended DNA set)
  # source: AOP-CMB.par
  DNA_NN_Weber_2015 <- .complete_nn_rc(matrix(c(
         0.0000,   0.0000,     2.5326,   5.6637,     0.4900,  -0.6757,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -0.5873,    -7.2009, -20.2175,
        -5.6921, -15.8947,    -4.0774, -11.3388,    -8.5782, -23.1654,    -7.2564, -19.3191,
        -6.4646, -17.0925,    -7.1036, -18.9155,    -9.7028, -24.8796,    -9.5938, -24.4371,
        -8.7684, -22.7375
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(DNA_NN_Weber_2015, "salt_mM") <- 1020

  # ---- RNA, measured at five salts, VIF scheme ----
  # AOP-VIF at 71 mM [Na+]
  # source: AOP-VIFRW-71.par
  RNA_NN_Weber_VIF_71 <- .complete_nn_rc(matrix(c(
         3.4085,   0.2886,     4.6722,  12.4606,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -5.3315, -14.8085,
       -10.2716, -29.6132,   -10.3296, -31.9802,   -10.3505, -29.3566,   -12.4315, -32.9656,
       -11.5553, -31.1152,   -12.1350, -33.8251,   -10.6616, -27.9859,   -14.2091, -36.2688,
       -12.8534, -32.1972
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_VIF_71, "salt_mM") <- 71

  # AOP-VIF at 121 mM [Na+]
  # source: AOP-VIFRW-121.par
  RNA_NN_Weber_VIF_121 <- .complete_nn_rc(matrix(c(
         2.3170,   0.2578,     5.6296,  15.1423,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -9.5363, -28.9960,
       -11.4066, -32.4378,   -10.5677, -33.7691,   -10.1748, -29.8826,   -12.2827, -32.2623,
       -11.3606, -30.2829,   -12.8637, -37.1318,   -10.8907, -29.1438,   -13.9826, -35.8758,
       -11.6163, -28.9123
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_VIF_121, "salt_mM") <- 121

  # AOP-VIF at 221 mM [Na+]
  # source: AOP-VIFRW-221.par
  RNA_NN_Weber_VIF_221 <- .complete_nn_rc(matrix(c(
         2.4417,  -1.2287,     5.2434,  14.1869,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -7.2734, -20.9687,
       -11.0415, -30.9510,    -9.4127, -29.3164,   -11.1475, -32.0746,   -11.7833, -30.3817,
       -12.4938, -33.5013,   -11.9556, -33.3326,   -11.9159, -31.8045,   -13.5035, -33.7292,
       -11.8934, -29.0209
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_VIF_221, "salt_mM") <- 221

  # AOP-VIF at 621 mM [Na+]
  # source: AOP-VIFRW-621.par
  RNA_NN_Weber_VIF_621 <- .complete_nn_rc(matrix(c(
         2.0381,  -0.7355,     4.7624,  12.6860,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -5.0673, -13.7959,
       -12.0086, -33.5987,    -9.4331, -29.1261,   -12.3988, -36.0577,   -13.1418, -34.7699,
       -11.6446, -30.8224,   -11.9930, -33.4713,   -11.8000, -31.5396,   -13.1341, -32.6632,
       -11.4641, -27.8822
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_VIF_621, "salt_mM") <- 621

  # AOP-VIF at 1021 mM [Na+]
  # source: AOP-VIFRW-1021.par
  RNA_NN_Weber_VIF_1021 <- .complete_nn_rc(matrix(c(
         5.2375,   1.0265,     1.8683,   4.8400,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -6.9088, -18.9939,
       -12.3315, -35.5068,   -11.1870, -31.5678,    -9.2476, -22.8267,   -13.0887, -34.4079,
       -11.4727, -29.7037,   -13.2535, -34.7600,   -12.2039, -30.9536,   -15.5716, -38.5593,
       -15.0523, -36.9921
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_VIF_1021, "salt_mM") <- 1021

  # ---- RNA, measured at five salts, FIF scheme ----
  # AOP-FIF at 71 mM [Na+]
  # source: AOP-FIFRW-71.par
  RNA_NN_Weber_FIF_71 <- .complete_nn_rc(matrix(c(
         3.6100,  -1.5000,     3.7200,  10.5000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -5.6583, -15.6834,
        -8.5043, -25.9600,   -10.5270, -30.0115,   -11.0010, -29.8012,   -11.8700, -31.8195,
       -10.7268, -29.1405,   -12.8979, -34.6410,   -10.5983, -27.3390,   -14.6024, -36.9660,
       -13.3232, -33.1294
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_FIF_71, "salt_mM") <- 71

  # AOP-FIF at 121 mM [Na+]
  # source: AOP-FIFRW-121.par
  RNA_NN_Weber_FIF_121 <- .complete_nn_rc(matrix(c(
         3.6100,  -1.5000,     3.7200,  10.5000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,   -10.6106, -31.9905,
        -7.5798, -22.7640,   -11.5058, -32.8953,   -11.7860, -32.2437,   -11.1365, -29.0034,
        -9.6600, -25.3309,   -14.8961, -40.8555,   -10.9362, -28.2149,   -15.0954, -38.0510,
       -12.9669, -31.8119
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_FIF_121, "salt_mM") <- 121

  # AOP-FIF at 221 mM [Na+]
  # source: AOP-FIFRW-221.par
  RNA_NN_Weber_FIF_221 <- .complete_nn_rc(matrix(c(
         3.6100,  -1.5000,     3.7200,  10.5000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -7.7054, -22.0235,
        -8.4715, -24.8591,   -10.0105, -28.2728,   -12.2437, -33.5058,   -11.0624, -28.5283,
       -11.3271, -30.2921,   -13.3287, -35.6580,   -12.0183, -31.3994,   -14.3687, -35.5373,
       -13.0358, -31.6893
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_FIF_221, "salt_mM") <- 221

  # AOP-FIF at 621 mM [Na+]
  # source: AOP-FIFRW-621.par
  RNA_NN_Weber_FIF_621 <- .complete_nn_rc(matrix(c(
         3.6100,  -1.5000,     3.7200,  10.5000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -5.8155, -15.6770,
        -9.0920, -26.3098,   -10.2523, -28.5906,   -13.3474, -36.6460,   -12.7358, -33.5822,
       -10.4105, -27.1001,   -13.6846, -36.4177,   -11.7763, -30.4166,   -14.5406, -35.7457,
       -13.1793, -31.8911
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_FIF_621, "salt_mM") <- 621

  # AOP-FIF at 1021 mM [Na+]
  # source: AOP-FIFRW-1021.par
  RNA_NN_Weber_FIF_1021 <- .complete_nn_rc(matrix(c(
         3.6100,  -1.5000,     3.7200,  10.5000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,  -1.4000,    -6.4314, -17.6766,
       -12.7794, -37.0377,   -13.5099, -39.1452,    -8.3871, -20.6134,   -12.5166, -33.0215,
       -10.0627, -25.7313,   -13.4641, -35.8415,   -10.8365, -27.2504,   -14.7595, -36.6631,
       -13.6056, -33.2045
  ), ncol=2, byrow=TRUE, dimnames=list(nn_row_std, nn_col)))
  attr(RNA_NN_Weber_FIF_1021, "salt_mM") <- 1021

  # ---- DNA/RNA hybrid ----
  # AOP-DRFT, curve fitting (recommended high salt)
  # source: AOP-DRFT.par
  RNA_DNA_NN_Weber_2019_FT <- matrix(c(
         1.4943,  -8.5131,     0.0000,   0.0000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,   0.0000,    -8.2694, -25.2054,
        -9.3923, -25.2281,   -10.8693, -31.0383,    -9.8990, -28.5875,    -9.7935, -26.2840,
       -11.9087, -28.2205,   -12.6023, -33.6818,   -12.2978, -33.1193,   -11.5436, -33.0019,
       -13.4801, -34.4699,   -12.4230, -32.8537,   -11.7433, -31.0498,    -9.5595, -28.8683,
        -9.7656, -25.4503,   -11.4676, -32.1090,   -10.2758, -29.8576
  ), ncol=2, byrow=TRUE, dimnames=list(hybrid_rows, nn_col))
  attr(RNA_DNA_NN_Weber_2019_FT, "salt_mM") <- 1000

  # AOP-DRVH, Van't Hoff
  # source: AOP-DRVH.par
  RNA_DNA_NN_Weber_2019_VH <- matrix(c(
         1.5947,  -8.6455,     0.0000,   0.0000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,   0.0000,    -7.7206, -23.8767,
       -11.4595, -31.6731,   -10.5706, -30.0225,    -9.8591, -28.5101,   -10.6942, -29.0233,
       -11.3728, -26.5422,   -13.0108, -35.1695,   -12.1751, -32.9612,   -11.6522, -32.9572,
       -14.0583, -35.7112,   -12.4383, -32.9497,   -10.5935, -27.3484,   -10.2428, -30.8614,
        -9.5993, -24.4245,   -10.7358, -29.8654,    -9.9268, -28.6058
  ), ncol=2, byrow=TRUE, dimnames=list(hybrid_rows, nn_col))
  attr(RNA_DNA_NN_Weber_2019_VH, "salt_mM") <- 1000

  # AOP-DRLS, low salt
  # source: AOP-DRLS.par
  RNA_DNA_NN_Weber_2019_LS <- matrix(c(
         0.9630,  -6.5119,     0.0000,   0.0000,     0.0000,   0.0000,     0.0000,   0.0000,
         0.0000,   0.0000,     0.0000,   0.0000,     0.0000,   0.0000,   -10.8561, -35.9011,
       -10.2316, -29.0019,    -8.6009, -22.9547,    -9.4736, -30.5491,    -9.5454, -25.1770,
       -11.8223, -30.8244,   -11.9288, -29.9238,   -12.2281, -33.2457,    -8.7011, -25.6312,
        -8.4153, -22.4119,    -7.4720, -19.3490,   -12.7010, -37.8656,    -9.9307, -30.7749,
       -12.2396, -33.9475,   -13.8697, -37.9789,   -10.6987, -32.0137
  ), ncol=2, byrow=TRUE, dimnames=list(hybrid_rows, nn_col))
  attr(RNA_DNA_NN_Weber_2019_LS, "salt_mM") <- 100

  # -- Return assembled list ------------------------------------------------
  list(
    DNA_NN_Breslauer_1986    = DNA_NN_Breslauer_1986,
    DNA_NN_Sugimoto_1996     = DNA_NN_Sugimoto_1996,
    DNA_NN_Allawi_1998       = DNA_NN_Allawi_1998,
    DNA_NN_SantaLucia_2004   = DNA_NN_SantaLucia_2004,
    RNA_NN_Freier_1986       = RNA_NN_Freier_1986,
    RNA_NN_Xia_1998          = RNA_NN_Xia_1998,
    RNA_NN_Chen_2012         = RNA_NN_Chen_2012,
    RNA_DNA_NN_Sugimoto_1995 = RNA_DNA_NN_Sugimoto_1995,
    DNA_IMM_Peyret_1999      = DNA_IMM_Peyret_1999,
    DNA_TMM_Bommarito_2000   = DNA_TMM_Bommarito_2000,
    DNA_DE_Bommarito_2000    = DNA_DE_Bommarito_2000,
    RNA_DE_Turner_2010       = RNA_DE_Turner_2010,
    GC_VARTAB                = GC_VARTAB,

    # -- Weber group sets ---------------------------------------------------
    DNA_NN_Weber_OW04_69     = DNA_NN_Weber_OW04_69,
    DNA_NN_Weber_OW04_119    = DNA_NN_Weber_OW04_119,
    DNA_NN_Weber_OW04_220    = DNA_NN_Weber_OW04_220,
    DNA_NN_Weber_OW04_621    = DNA_NN_Weber_OW04_621,
    DNA_NN_Weber_OW04_1020   = DNA_NN_Weber_OW04_1020,
    DNA_NN_Weber_2015        = DNA_NN_Weber_2015,
    RNA_NN_Weber_VIF_71      = RNA_NN_Weber_VIF_71,
    RNA_NN_Weber_VIF_121     = RNA_NN_Weber_VIF_121,
    RNA_NN_Weber_VIF_221     = RNA_NN_Weber_VIF_221,
    RNA_NN_Weber_VIF_621     = RNA_NN_Weber_VIF_621,
    RNA_NN_Weber_VIF_1021    = RNA_NN_Weber_VIF_1021,
    RNA_NN_Weber_FIF_71      = RNA_NN_Weber_FIF_71,
    RNA_NN_Weber_FIF_121     = RNA_NN_Weber_FIF_121,
    RNA_NN_Weber_FIF_221     = RNA_NN_Weber_FIF_221,
    RNA_NN_Weber_FIF_621     = RNA_NN_Weber_FIF_621,
    RNA_NN_Weber_FIF_1021    = RNA_NN_Weber_FIF_1021,
    RNA_DNA_NN_Weber_2019_FT = RNA_DNA_NN_Weber_2019_FT,
    RNA_DNA_NN_Weber_2019_VH = RNA_DNA_NN_Weber_2019_VH,
    RNA_DNA_NN_Weber_2019_LS = RNA_DNA_NN_Weber_2019_LS
  )
}

# =============================================================================
# VERIFICATION - run this after building to confirm completion
# =============================================================================
.verify_nn_tables <- function(tbl_list) {
  # The 6 RC pairs that should now be present in every standard DNA/RNA NN table
  expected_rc_pairs <- c("TT/AA","TG/AC","TC/AG","AG/TC","AC/TG","CC/GG")
  
  standard_tables <- c("DNA_NN_Breslauer_1986","DNA_NN_Sugimoto_1996",
                       "DNA_NN_Allawi_1998","DNA_NN_SantaLucia_2004",
                       "RNA_NN_Freier_1986","RNA_NN_Xia_1998",
                       "RNA_NN_Chen_2012","RNA_DNA_NN_Sugimoto_1995",
                       "DNA_NN_Weber_2015","DNA_NN_Weber_OW04_69",
                       "DNA_NN_Weber_OW04_119","DNA_NN_Weber_OW04_220",
                       "DNA_NN_Weber_OW04_621","DNA_NN_Weber_OW04_1020",
                       "RNA_NN_Weber_VIF_71","RNA_NN_Weber_VIF_121",
                       "RNA_NN_Weber_VIF_221","RNA_NN_Weber_VIF_621",
                       "RNA_NN_Weber_VIF_1021","RNA_NN_Weber_FIF_71",
                       "RNA_NN_Weber_FIF_121","RNA_NN_Weber_FIF_221",
                       "RNA_NN_Weber_FIF_621","RNA_NN_Weber_FIF_1021")
  
  all_ok <- TRUE
  for (nm in standard_tables) {
    tbl      <- tbl_list[[nm]]
    missing  <- expected_rc_pairs[!expected_rc_pairs %in% rownames(tbl)]
    n_rows   <- nrow(tbl)
    status   <- if (length(missing) == 0) "OK" else paste("MISSING:", paste(missing, collapse=", "))
    cat(sprintf("  %-35s  rows=%-3d  %s\n", nm, n_rows, status))
    if (length(missing) > 0) all_ok <- FALSE
  }
  # -- Weber sets must all carry a salt attribute --------------------------
  weber <- grep("Weber", names(tbl_list), value = TRUE)
  cat("\n  salt attributes on Weber sets:\n")
  for (nm in weber) {
    s <- attr(tbl_list[[nm]], "salt_mM")
    cat(sprintf("  %-28s %8s mM  %s\n", nm,
                if (is.null(s)) "NA" else format(s),
                if (!is.null(s) && !is.na(s)) "OK" else "MISSING"))
    if (is.null(s) || is.na(s)) all_ok <- FALSE
  }

  # -- Published values that were previously mistranscribed -----------------
  sl <- tbl_list$DNA_NN_SantaLucia_2004
  ref <- list("TA/AT" = c(-7.2, -21.3), "GG/CC" = c(-8.0, -19.9))
  cat("\n  SantaLucia 2004 spot checks (vs P-SL04.par and P-SL98.par):\n")
  for (k in names(ref)) {
    ok <- isTRUE(all.equal(unname(sl[k, ]), ref[[k]], tolerance = 1e-8))
    cat(sprintf("  %-28s %s\n", k, if (ok) "OK" else "FAIL"))
    if (!ok) all_ok <- FALSE
  }

  if (all_ok) {
    cat("\nAll tables complete. keys_fr lookups will always succeed.\n")
    cat("The keys_rf / pos_nn_rf fallback in tm_nn_core() can now be removed.\n")
  }
  invisible(all_ok)
}


# -- Usage --------------------------------------------------------------------
# Build once at package development time:
#   .TM_CONSTANTS <- .build_all_tm_tables()
#   .verify_nn_tables(.TM_CONSTANTS)
#   usethis::use_data(.TM_CONSTANTS, internal = TRUE, overwrite = TRUE)
#
# Expected verification output:
#   DNA_NN_Breslauer_1986    rows=23  OK
#   DNA_NN_Sugimoto_1996     rows=23  OK
#   DNA_NN_Allawi_1998       rows=23  OK
#   DNA_NN_SantaLucia_2004   rows=23  OK
#   RNA_NN_Freier_1986       rows=23  OK
#   RNA_NN_Xia_1998          rows=23  OK
#   RNA_NN_Chen_2012         rows=28  OK   (17 std + 11 wobble; 6 RC added to std)
#   RNA_DNA_NN_Sugimoto_1995 rows=23  OK   (already had all 6 RC pairs)
#
# After building, in tm_nn_core() the NN sum simplifies to:
#   matched_nn <- keys_fr %in% rownames(nn_tbl)
#   delta_h    <- delta_h + sum(nn_tbl[keys_fr[matched_nn], 1])
#   delta_s    <- delta_s + sum(nn_tbl[keys_fr[matched_nn], 2])
#   # No keys_rf, no pos_nn_rf, no which_nn_fr/rf needed.

Try the TmCalculator package in your browser

Any scripts or data that you put into this service are public.

TmCalculator documentation built on Aug. 28, 2026, 5:09 p.m.