@teselagen/sequence-utils 0.3.35 → 0.3.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Calculate End Stability (3' end stability) of a primer
3
+ *
4
+ * The maximum stability for the last five 3' bases of a left or right primer.
5
+ * Bigger numbers mean more stable 3' ends. The value is the maximum delta G
6
+ * (kcal/mol) for duplex disruption for the five 3' bases.
7
+ *
8
+ * According to Primer3 documentation:
9
+ * - Most stable 5mer duplex: GCGCG = 6.86 kcal/mol (SantaLucia 1998)
10
+ * - Most labile 5mer duplex: TATAT = 0.86 kcal/mol (SantaLucia 1998)
11
+ *
12
+ * @param {string} sequence - DNA sequence (5' to 3')
13
+ * @returns {number} - Delta G (kcal/mol) for the last 5 bases at 3' end
14
+ * @throws {Error} Invalid sequence or too short.
15
+ */
16
+ export default function calculateEndStability(sequence: string): number;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Validate DNA sequence
3
+ *
4
+ * @param {string} sequence - DNA sequence
5
+ * @returns {boolean} - True if valid
6
+ */
7
+ export function isValidSequence(sequence: string): boolean;
8
+ /**
9
+ * Calculate melting temperature using SantaLucia (1998) method
10
+ *
11
+ * @param {string} sequence - DNA sequence (5' to 3')
12
+ * @returns {number} - Melting temperature in Celsius
13
+ * @throws {Error} Invalid sequence or too short.
14
+ */
15
+ export default function calculateSantaLuciaTm(sequence: string): number;
16
+ export namespace SANTA_LUCIA_NN {
17
+ namespace AA {
18
+ let dH: number;
19
+ let dS: number;
20
+ }
21
+ namespace TT {
22
+ let dH_1: number;
23
+ export { dH_1 as dH };
24
+ let dS_1: number;
25
+ export { dS_1 as dS };
26
+ }
27
+ namespace AT {
28
+ let dH_2: number;
29
+ export { dH_2 as dH };
30
+ let dS_2: number;
31
+ export { dS_2 as dS };
32
+ }
33
+ namespace TA {
34
+ let dH_3: number;
35
+ export { dH_3 as dH };
36
+ let dS_3: number;
37
+ export { dS_3 as dS };
38
+ }
39
+ namespace CA {
40
+ let dH_4: number;
41
+ export { dH_4 as dH };
42
+ let dS_4: number;
43
+ export { dS_4 as dS };
44
+ }
45
+ namespace TG {
46
+ let dH_5: number;
47
+ export { dH_5 as dH };
48
+ let dS_5: number;
49
+ export { dS_5 as dS };
50
+ }
51
+ namespace GT {
52
+ let dH_6: number;
53
+ export { dH_6 as dH };
54
+ let dS_6: number;
55
+ export { dS_6 as dS };
56
+ }
57
+ namespace AC {
58
+ let dH_7: number;
59
+ export { dH_7 as dH };
60
+ let dS_7: number;
61
+ export { dS_7 as dS };
62
+ }
63
+ namespace CT {
64
+ let dH_8: number;
65
+ export { dH_8 as dH };
66
+ let dS_8: number;
67
+ export { dS_8 as dS };
68
+ }
69
+ namespace AG {
70
+ let dH_9: number;
71
+ export { dH_9 as dH };
72
+ let dS_9: number;
73
+ export { dS_9 as dS };
74
+ }
75
+ namespace GA {
76
+ let dH_10: number;
77
+ export { dH_10 as dH };
78
+ let dS_10: number;
79
+ export { dS_10 as dS };
80
+ }
81
+ namespace TC {
82
+ let dH_11: number;
83
+ export { dH_11 as dH };
84
+ let dS_11: number;
85
+ export { dS_11 as dS };
86
+ }
87
+ namespace CG {
88
+ let dH_12: number;
89
+ export { dH_12 as dH };
90
+ let dS_12: number;
91
+ export { dS_12 as dS };
92
+ }
93
+ namespace GC {
94
+ let dH_13: number;
95
+ export { dH_13 as dH };
96
+ let dS_13: number;
97
+ export { dS_13 as dS };
98
+ }
99
+ namespace GG {
100
+ let dH_14: number;
101
+ export { dH_14 as dH };
102
+ let dS_14: number;
103
+ export { dS_14 as dS };
104
+ }
105
+ namespace CC {
106
+ let dH_15: number;
107
+ export { dH_15 as dH };
108
+ let dS_15: number;
109
+ export { dS_15 as dS };
110
+ }
111
+ }
112
+ export namespace SANTA_LUCIA_INIT {
113
+ export namespace GC_1 {
114
+ let dH_16: number;
115
+ export { dH_16 as dH };
116
+ let dS_16: number;
117
+ export { dS_16 as dS };
118
+ }
119
+ export { GC_1 as GC };
120
+ export namespace AT_1 {
121
+ let dH_17: number;
122
+ export { dH_17 as dH };
123
+ let dS_17: number;
124
+ export { dS_17 as dS };
125
+ }
126
+ export { AT_1 as AT };
127
+ }
@@ -0,0 +1 @@
1
+ export {};
package/index.cjs CHANGED
@@ -3563,7 +3563,7 @@ const genbankFeatureTypes = [
3563
3563
  { name: "regulatory", color: "#3F6C51" },
3564
3564
  { name: "SecStr", color: "#7B4B94" },
3565
3565
  { name: "Site", color: "#7D82B8" },
3566
- { name: "telomere", color: "DE9151" },
3566
+ { name: "telomere", color: "#DE9151" },
3567
3567
  { name: "tmRNA", color: "#B7E3CC" },
3568
3568
  { name: "unsure", color: "#C4FFB2" },
3569
3569
  { name: "V_segment", color: "#D6F7A3" },
@@ -6532,17 +6532,25 @@ function tidyUpSequenceData(pSeqData, options = {}) {
6532
6532
  });
6533
6533
  if (!noTranslationData) {
6534
6534
  seqData.translations = flatMap(seqData.translations, (translation) => {
6535
+ var _a, _b;
6535
6536
  if (noCdsTranslations && translation.translationType === "CDS Feature") {
6536
6537
  return [];
6537
6538
  }
6538
- if (!translation.aminoAcids && !seqData.noSequence) {
6539
- translation.aminoAcids = getAminoAcidDataForEachBaseOfDna(
6539
+ const codonStart = ((_b = (_a = translation == null ? void 0 : translation.notes) == null ? void 0 : _a.codon_start) == null ? void 0 : _b[0]) - 1 || 0;
6540
+ const expandedRange = expandOrContractRangeByLength(
6541
+ translation,
6542
+ -codonStart,
6543
+ true,
6544
+ seqData.sequence.length
6545
+ );
6546
+ if (!expandedRange.aminoAcids && !seqData.noSequence) {
6547
+ expandedRange.aminoAcids = getAminoAcidDataForEachBaseOfDna(
6540
6548
  seqData.sequence,
6541
- translation.forward,
6542
- translation
6549
+ expandedRange.forward,
6550
+ expandedRange
6543
6551
  );
6544
6552
  }
6545
- return translation;
6553
+ return expandedRange;
6546
6554
  });
6547
6555
  }
6548
6556
  if (annotationsAsObjects) {
@@ -16513,6 +16521,160 @@ function calculateNebTa(sequences, primerConc, { monovalentCationConc, polymeras
16513
16521
  }
16514
16522
  }
16515
16523
  __name(calculateNebTa, "calculateNebTa");
16524
+ const PRIMER3_PARAMS = {
16525
+ saltMonovalent: 50,
16526
+ // mM
16527
+ saltDivalent: 1.5,
16528
+ // mM
16529
+ dntpConc: 0.6,
16530
+ // mM
16531
+ dnaConc: 50,
16532
+ // nM
16533
+ R: 1.987
16534
+ // Gas constant (cal/K·mol)
16535
+ };
16536
+ const SANTA_LUCIA_NN = {
16537
+ AA: { dH: -7.9, dS: -22.2 },
16538
+ TT: { dH: -7.9, dS: -22.2 },
16539
+ AT: { dH: -7.2, dS: -20.4 },
16540
+ TA: { dH: -7.2, dS: -21.3 },
16541
+ CA: { dH: -8.5, dS: -22.7 },
16542
+ TG: { dH: -8.5, dS: -22.7 },
16543
+ GT: { dH: -8.4, dS: -22.4 },
16544
+ AC: { dH: -8.4, dS: -22.4 },
16545
+ CT: { dH: -7.8, dS: -21 },
16546
+ AG: { dH: -7.8, dS: -21 },
16547
+ GA: { dH: -8.2, dS: -22.2 },
16548
+ TC: { dH: -8.2, dS: -22.2 },
16549
+ CG: { dH: -10.6, dS: -27.2 },
16550
+ GC: { dH: -9.8, dS: -24.4 },
16551
+ GG: { dH: -8, dS: -19.9 },
16552
+ CC: { dH: -8, dS: -19.9 }
16553
+ };
16554
+ const SANTA_LUCIA_INIT = {
16555
+ GC: { dH: 0.1, dS: -2.8 },
16556
+ // initiation with terminal GC
16557
+ AT: { dH: 2.3, dS: 4.1 }
16558
+ // initiation with terminal AT
16559
+ };
16560
+ function getEffectiveMonovalentConc() {
16561
+ let effectiveMono = PRIMER3_PARAMS.saltMonovalent;
16562
+ {
16563
+ const freeMg = Math.max(
16564
+ 0,
16565
+ PRIMER3_PARAMS.saltDivalent - PRIMER3_PARAMS.dntpConc
16566
+ );
16567
+ effectiveMono += 120 * Math.sqrt(freeMg);
16568
+ }
16569
+ return effectiveMono;
16570
+ }
16571
+ __name(getEffectiveMonovalentConc, "getEffectiveMonovalentConc");
16572
+ function applySaltCorrection(deltaS, nnPairs) {
16573
+ const effectiveMono = getEffectiveMonovalentConc();
16574
+ return deltaS + 0.368 * nnPairs * Math.log(effectiveMono / 1e3);
16575
+ }
16576
+ __name(applySaltCorrection, "applySaltCorrection");
16577
+ function isValidSequence(sequence) {
16578
+ return /^[ATGCN]+$/.test(sequence);
16579
+ }
16580
+ __name(isValidSequence, "isValidSequence");
16581
+ function calculateSantaLuciaTm(sequence) {
16582
+ try {
16583
+ sequence = sequence == null ? void 0 : sequence.toUpperCase().trim();
16584
+ if (!isValidSequence(sequence)) {
16585
+ throw new Error("Invalid sequence: contains non-DNA characters");
16586
+ }
16587
+ if (sequence.length < 2) {
16588
+ throw new Error("Sequence too short: minimum length is 2 bases");
16589
+ }
16590
+ let deltaH = 0;
16591
+ let deltaS = 0;
16592
+ for (let i = 0; i < sequence.length - 1; i++) {
16593
+ const dinucleotide = sequence.substring(i, i + 2);
16594
+ if (dinucleotide.includes("N")) {
16595
+ continue;
16596
+ }
16597
+ const params = SANTA_LUCIA_NN[dinucleotide];
16598
+ if (params) {
16599
+ deltaH += params.dH;
16600
+ deltaS += params.dS;
16601
+ }
16602
+ }
16603
+ const firstBase = sequence[0];
16604
+ const lastBase = sequence[sequence.length - 1];
16605
+ if (firstBase === "G" || firstBase === "C") {
16606
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16607
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16608
+ } else {
16609
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16610
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16611
+ }
16612
+ if (lastBase === "G" || lastBase === "C") {
16613
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16614
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16615
+ } else {
16616
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16617
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16618
+ }
16619
+ const nnPairs = sequence.length - 1;
16620
+ deltaS = applySaltCorrection(deltaS, nnPairs);
16621
+ const C = PRIMER3_PARAMS.dnaConc * 1e-9;
16622
+ const Tm = deltaH * 1e3 / (deltaS + PRIMER3_PARAMS.R * Math.log(C / 4));
16623
+ return Tm - 273.15;
16624
+ } catch (e) {
16625
+ return `Error calculating Tm for sequence ${sequence}. ${e}`;
16626
+ }
16627
+ }
16628
+ __name(calculateSantaLuciaTm, "calculateSantaLuciaTm");
16629
+ function calculateEndStability(sequence) {
16630
+ try {
16631
+ sequence = sequence == null ? void 0 : sequence.toUpperCase().trim();
16632
+ if (!isValidSequence(sequence)) {
16633
+ throw new Error("Invalid sequence: contains non-DNA characters");
16634
+ }
16635
+ if (sequence.length < 5) {
16636
+ throw new Error(
16637
+ "Sequence too short: minimum length is 5 bases for end stability calculation"
16638
+ );
16639
+ }
16640
+ const last5Bases = sequence.substring(sequence.length - 5);
16641
+ let deltaH = 0;
16642
+ let deltaS = 0;
16643
+ for (let i = 0; i < 4; i++) {
16644
+ const dinucleotide = last5Bases.substring(i, i + 2);
16645
+ if (dinucleotide.includes("N")) {
16646
+ continue;
16647
+ }
16648
+ const params = SANTA_LUCIA_NN[dinucleotide];
16649
+ if (params) {
16650
+ deltaH += params.dH;
16651
+ deltaS += params.dS;
16652
+ }
16653
+ }
16654
+ const firstBase = last5Bases[0];
16655
+ const lastBase = last5Bases[last5Bases.length - 1];
16656
+ if (firstBase === "G" || firstBase === "C") {
16657
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16658
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16659
+ } else {
16660
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16661
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16662
+ }
16663
+ if (lastBase === "G" || lastBase === "C") {
16664
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16665
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16666
+ } else {
16667
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16668
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16669
+ }
16670
+ const T = 310.15;
16671
+ const deltaG = deltaH - T * deltaS / 1e3;
16672
+ return Math.round(Math.abs(deltaG) * 100) / 100;
16673
+ } catch (e) {
16674
+ return `Error calculating end stability for sequence ${sequence}. ${e}`;
16675
+ }
16676
+ }
16677
+ __name(calculateEndStability, "calculateEndStability");
16516
16678
  function getDigestFragmentsForCutsites(sequenceLength, circular, cutsites, opts = {}) {
16517
16679
  const fragments = [];
16518
16680
  const pairs = [];
@@ -16674,9 +16836,11 @@ exports.annotateSingleSeq = annotateSingleSeq;
16674
16836
  exports.annotationTypes = annotationTypes;
16675
16837
  exports.autoAnnotate = autoAnnotate;
16676
16838
  exports.bioData = bioData;
16839
+ exports.calculateEndStability = calculateEndStability;
16677
16840
  exports.calculateNebTa = calculateNebTa;
16678
16841
  exports.calculateNebTm = calculateNebTm;
16679
16842
  exports.calculatePercentGC = calculatePercentGC;
16843
+ exports.calculateSantaLuciaTm = calculateSantaLuciaTm;
16680
16844
  exports.calculateTm = calculateTm;
16681
16845
  exports.computeDigestFragments = computeDigestFragments;
16682
16846
  exports.condensePairwiseAlignmentDifferences = condensePairwiseAlignmentDifferences;
package/index.d.ts CHANGED
@@ -63,6 +63,8 @@ export { default as condensePairwiseAlignmentDifferences } from './condensePairw
63
63
  export { default as addGapsToSeqReads } from './addGapsToSeqReads';
64
64
  export { default as calculateNebTm } from './calculateNebTm';
65
65
  export { default as calculateNebTa } from './calculateNebTa';
66
+ export { default as calculateSantaLuciaTm } from './calculateSantaLuciaTm';
67
+ export { default as calculateEndStability } from './calculateEndStability';
66
68
  export { default as getDigestFragmentsForCutsites } from './getDigestFragmentsForCutsites';
67
69
  export { default as getDigestFragmentsForRestrictionEnzymes } from './getDigestFragmentsForRestrictionEnzymes';
68
70
  export { default as convertDnaCaretPositionOrRangeToAA } from './convertDnaCaretPositionOrRangeToAA';
package/index.js CHANGED
@@ -3561,7 +3561,7 @@ const genbankFeatureTypes = [
3561
3561
  { name: "regulatory", color: "#3F6C51" },
3562
3562
  { name: "SecStr", color: "#7B4B94" },
3563
3563
  { name: "Site", color: "#7D82B8" },
3564
- { name: "telomere", color: "DE9151" },
3564
+ { name: "telomere", color: "#DE9151" },
3565
3565
  { name: "tmRNA", color: "#B7E3CC" },
3566
3566
  { name: "unsure", color: "#C4FFB2" },
3567
3567
  { name: "V_segment", color: "#D6F7A3" },
@@ -6530,17 +6530,25 @@ function tidyUpSequenceData(pSeqData, options = {}) {
6530
6530
  });
6531
6531
  if (!noTranslationData) {
6532
6532
  seqData.translations = flatMap(seqData.translations, (translation) => {
6533
+ var _a, _b;
6533
6534
  if (noCdsTranslations && translation.translationType === "CDS Feature") {
6534
6535
  return [];
6535
6536
  }
6536
- if (!translation.aminoAcids && !seqData.noSequence) {
6537
- translation.aminoAcids = getAminoAcidDataForEachBaseOfDna(
6537
+ const codonStart = ((_b = (_a = translation == null ? void 0 : translation.notes) == null ? void 0 : _a.codon_start) == null ? void 0 : _b[0]) - 1 || 0;
6538
+ const expandedRange = expandOrContractRangeByLength(
6539
+ translation,
6540
+ -codonStart,
6541
+ true,
6542
+ seqData.sequence.length
6543
+ );
6544
+ if (!expandedRange.aminoAcids && !seqData.noSequence) {
6545
+ expandedRange.aminoAcids = getAminoAcidDataForEachBaseOfDna(
6538
6546
  seqData.sequence,
6539
- translation.forward,
6540
- translation
6547
+ expandedRange.forward,
6548
+ expandedRange
6541
6549
  );
6542
6550
  }
6543
- return translation;
6551
+ return expandedRange;
6544
6552
  });
6545
6553
  }
6546
6554
  if (annotationsAsObjects) {
@@ -16511,6 +16519,160 @@ function calculateNebTa(sequences, primerConc, { monovalentCationConc, polymeras
16511
16519
  }
16512
16520
  }
16513
16521
  __name(calculateNebTa, "calculateNebTa");
16522
+ const PRIMER3_PARAMS = {
16523
+ saltMonovalent: 50,
16524
+ // mM
16525
+ saltDivalent: 1.5,
16526
+ // mM
16527
+ dntpConc: 0.6,
16528
+ // mM
16529
+ dnaConc: 50,
16530
+ // nM
16531
+ R: 1.987
16532
+ // Gas constant (cal/K·mol)
16533
+ };
16534
+ const SANTA_LUCIA_NN = {
16535
+ AA: { dH: -7.9, dS: -22.2 },
16536
+ TT: { dH: -7.9, dS: -22.2 },
16537
+ AT: { dH: -7.2, dS: -20.4 },
16538
+ TA: { dH: -7.2, dS: -21.3 },
16539
+ CA: { dH: -8.5, dS: -22.7 },
16540
+ TG: { dH: -8.5, dS: -22.7 },
16541
+ GT: { dH: -8.4, dS: -22.4 },
16542
+ AC: { dH: -8.4, dS: -22.4 },
16543
+ CT: { dH: -7.8, dS: -21 },
16544
+ AG: { dH: -7.8, dS: -21 },
16545
+ GA: { dH: -8.2, dS: -22.2 },
16546
+ TC: { dH: -8.2, dS: -22.2 },
16547
+ CG: { dH: -10.6, dS: -27.2 },
16548
+ GC: { dH: -9.8, dS: -24.4 },
16549
+ GG: { dH: -8, dS: -19.9 },
16550
+ CC: { dH: -8, dS: -19.9 }
16551
+ };
16552
+ const SANTA_LUCIA_INIT = {
16553
+ GC: { dH: 0.1, dS: -2.8 },
16554
+ // initiation with terminal GC
16555
+ AT: { dH: 2.3, dS: 4.1 }
16556
+ // initiation with terminal AT
16557
+ };
16558
+ function getEffectiveMonovalentConc() {
16559
+ let effectiveMono = PRIMER3_PARAMS.saltMonovalent;
16560
+ {
16561
+ const freeMg = Math.max(
16562
+ 0,
16563
+ PRIMER3_PARAMS.saltDivalent - PRIMER3_PARAMS.dntpConc
16564
+ );
16565
+ effectiveMono += 120 * Math.sqrt(freeMg);
16566
+ }
16567
+ return effectiveMono;
16568
+ }
16569
+ __name(getEffectiveMonovalentConc, "getEffectiveMonovalentConc");
16570
+ function applySaltCorrection(deltaS, nnPairs) {
16571
+ const effectiveMono = getEffectiveMonovalentConc();
16572
+ return deltaS + 0.368 * nnPairs * Math.log(effectiveMono / 1e3);
16573
+ }
16574
+ __name(applySaltCorrection, "applySaltCorrection");
16575
+ function isValidSequence(sequence) {
16576
+ return /^[ATGCN]+$/.test(sequence);
16577
+ }
16578
+ __name(isValidSequence, "isValidSequence");
16579
+ function calculateSantaLuciaTm(sequence) {
16580
+ try {
16581
+ sequence = sequence == null ? void 0 : sequence.toUpperCase().trim();
16582
+ if (!isValidSequence(sequence)) {
16583
+ throw new Error("Invalid sequence: contains non-DNA characters");
16584
+ }
16585
+ if (sequence.length < 2) {
16586
+ throw new Error("Sequence too short: minimum length is 2 bases");
16587
+ }
16588
+ let deltaH = 0;
16589
+ let deltaS = 0;
16590
+ for (let i = 0; i < sequence.length - 1; i++) {
16591
+ const dinucleotide = sequence.substring(i, i + 2);
16592
+ if (dinucleotide.includes("N")) {
16593
+ continue;
16594
+ }
16595
+ const params = SANTA_LUCIA_NN[dinucleotide];
16596
+ if (params) {
16597
+ deltaH += params.dH;
16598
+ deltaS += params.dS;
16599
+ }
16600
+ }
16601
+ const firstBase = sequence[0];
16602
+ const lastBase = sequence[sequence.length - 1];
16603
+ if (firstBase === "G" || firstBase === "C") {
16604
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16605
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16606
+ } else {
16607
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16608
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16609
+ }
16610
+ if (lastBase === "G" || lastBase === "C") {
16611
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16612
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16613
+ } else {
16614
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16615
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16616
+ }
16617
+ const nnPairs = sequence.length - 1;
16618
+ deltaS = applySaltCorrection(deltaS, nnPairs);
16619
+ const C = PRIMER3_PARAMS.dnaConc * 1e-9;
16620
+ const Tm = deltaH * 1e3 / (deltaS + PRIMER3_PARAMS.R * Math.log(C / 4));
16621
+ return Tm - 273.15;
16622
+ } catch (e) {
16623
+ return `Error calculating Tm for sequence ${sequence}. ${e}`;
16624
+ }
16625
+ }
16626
+ __name(calculateSantaLuciaTm, "calculateSantaLuciaTm");
16627
+ function calculateEndStability(sequence) {
16628
+ try {
16629
+ sequence = sequence == null ? void 0 : sequence.toUpperCase().trim();
16630
+ if (!isValidSequence(sequence)) {
16631
+ throw new Error("Invalid sequence: contains non-DNA characters");
16632
+ }
16633
+ if (sequence.length < 5) {
16634
+ throw new Error(
16635
+ "Sequence too short: minimum length is 5 bases for end stability calculation"
16636
+ );
16637
+ }
16638
+ const last5Bases = sequence.substring(sequence.length - 5);
16639
+ let deltaH = 0;
16640
+ let deltaS = 0;
16641
+ for (let i = 0; i < 4; i++) {
16642
+ const dinucleotide = last5Bases.substring(i, i + 2);
16643
+ if (dinucleotide.includes("N")) {
16644
+ continue;
16645
+ }
16646
+ const params = SANTA_LUCIA_NN[dinucleotide];
16647
+ if (params) {
16648
+ deltaH += params.dH;
16649
+ deltaS += params.dS;
16650
+ }
16651
+ }
16652
+ const firstBase = last5Bases[0];
16653
+ const lastBase = last5Bases[last5Bases.length - 1];
16654
+ if (firstBase === "G" || firstBase === "C") {
16655
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16656
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16657
+ } else {
16658
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16659
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16660
+ }
16661
+ if (lastBase === "G" || lastBase === "C") {
16662
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16663
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16664
+ } else {
16665
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16666
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16667
+ }
16668
+ const T = 310.15;
16669
+ const deltaG = deltaH - T * deltaS / 1e3;
16670
+ return Math.round(Math.abs(deltaG) * 100) / 100;
16671
+ } catch (e) {
16672
+ return `Error calculating end stability for sequence ${sequence}. ${e}`;
16673
+ }
16674
+ }
16675
+ __name(calculateEndStability, "calculateEndStability");
16514
16676
  function getDigestFragmentsForCutsites(sequenceLength, circular, cutsites, opts = {}) {
16515
16677
  const fragments = [];
16516
16678
  const pairs = [];
@@ -16673,9 +16835,11 @@ export {
16673
16835
  annotationTypes,
16674
16836
  autoAnnotate,
16675
16837
  bioData,
16838
+ calculateEndStability,
16676
16839
  calculateNebTa,
16677
16840
  calculateNebTm,
16678
16841
  calculatePercentGC,
16842
+ calculateSantaLuciaTm,
16679
16843
  calculateTm,
16680
16844
  computeDigestFragments,
16681
16845
  condensePairwiseAlignmentDifferences,
package/index.umd.cjs CHANGED
@@ -3565,7 +3565,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3565
3565
  { name: "regulatory", color: "#3F6C51" },
3566
3566
  { name: "SecStr", color: "#7B4B94" },
3567
3567
  { name: "Site", color: "#7D82B8" },
3568
- { name: "telomere", color: "DE9151" },
3568
+ { name: "telomere", color: "#DE9151" },
3569
3569
  { name: "tmRNA", color: "#B7E3CC" },
3570
3570
  { name: "unsure", color: "#C4FFB2" },
3571
3571
  { name: "V_segment", color: "#D6F7A3" },
@@ -6534,17 +6534,25 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
6534
6534
  });
6535
6535
  if (!noTranslationData) {
6536
6536
  seqData.translations = flatMap(seqData.translations, (translation) => {
6537
+ var _a, _b;
6537
6538
  if (noCdsTranslations && translation.translationType === "CDS Feature") {
6538
6539
  return [];
6539
6540
  }
6540
- if (!translation.aminoAcids && !seqData.noSequence) {
6541
- translation.aminoAcids = getAminoAcidDataForEachBaseOfDna(
6541
+ const codonStart = ((_b = (_a = translation == null ? void 0 : translation.notes) == null ? void 0 : _a.codon_start) == null ? void 0 : _b[0]) - 1 || 0;
6542
+ const expandedRange = expandOrContractRangeByLength(
6543
+ translation,
6544
+ -codonStart,
6545
+ true,
6546
+ seqData.sequence.length
6547
+ );
6548
+ if (!expandedRange.aminoAcids && !seqData.noSequence) {
6549
+ expandedRange.aminoAcids = getAminoAcidDataForEachBaseOfDna(
6542
6550
  seqData.sequence,
6543
- translation.forward,
6544
- translation
6551
+ expandedRange.forward,
6552
+ expandedRange
6545
6553
  );
6546
6554
  }
6547
- return translation;
6555
+ return expandedRange;
6548
6556
  });
6549
6557
  }
6550
6558
  if (annotationsAsObjects) {
@@ -16515,6 +16523,160 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
16515
16523
  }
16516
16524
  }
16517
16525
  __name(calculateNebTa, "calculateNebTa");
16526
+ const PRIMER3_PARAMS = {
16527
+ saltMonovalent: 50,
16528
+ // mM
16529
+ saltDivalent: 1.5,
16530
+ // mM
16531
+ dntpConc: 0.6,
16532
+ // mM
16533
+ dnaConc: 50,
16534
+ // nM
16535
+ R: 1.987
16536
+ // Gas constant (cal/K·mol)
16537
+ };
16538
+ const SANTA_LUCIA_NN = {
16539
+ AA: { dH: -7.9, dS: -22.2 },
16540
+ TT: { dH: -7.9, dS: -22.2 },
16541
+ AT: { dH: -7.2, dS: -20.4 },
16542
+ TA: { dH: -7.2, dS: -21.3 },
16543
+ CA: { dH: -8.5, dS: -22.7 },
16544
+ TG: { dH: -8.5, dS: -22.7 },
16545
+ GT: { dH: -8.4, dS: -22.4 },
16546
+ AC: { dH: -8.4, dS: -22.4 },
16547
+ CT: { dH: -7.8, dS: -21 },
16548
+ AG: { dH: -7.8, dS: -21 },
16549
+ GA: { dH: -8.2, dS: -22.2 },
16550
+ TC: { dH: -8.2, dS: -22.2 },
16551
+ CG: { dH: -10.6, dS: -27.2 },
16552
+ GC: { dH: -9.8, dS: -24.4 },
16553
+ GG: { dH: -8, dS: -19.9 },
16554
+ CC: { dH: -8, dS: -19.9 }
16555
+ };
16556
+ const SANTA_LUCIA_INIT = {
16557
+ GC: { dH: 0.1, dS: -2.8 },
16558
+ // initiation with terminal GC
16559
+ AT: { dH: 2.3, dS: 4.1 }
16560
+ // initiation with terminal AT
16561
+ };
16562
+ function getEffectiveMonovalentConc() {
16563
+ let effectiveMono = PRIMER3_PARAMS.saltMonovalent;
16564
+ {
16565
+ const freeMg = Math.max(
16566
+ 0,
16567
+ PRIMER3_PARAMS.saltDivalent - PRIMER3_PARAMS.dntpConc
16568
+ );
16569
+ effectiveMono += 120 * Math.sqrt(freeMg);
16570
+ }
16571
+ return effectiveMono;
16572
+ }
16573
+ __name(getEffectiveMonovalentConc, "getEffectiveMonovalentConc");
16574
+ function applySaltCorrection(deltaS, nnPairs) {
16575
+ const effectiveMono = getEffectiveMonovalentConc();
16576
+ return deltaS + 0.368 * nnPairs * Math.log(effectiveMono / 1e3);
16577
+ }
16578
+ __name(applySaltCorrection, "applySaltCorrection");
16579
+ function isValidSequence(sequence) {
16580
+ return /^[ATGCN]+$/.test(sequence);
16581
+ }
16582
+ __name(isValidSequence, "isValidSequence");
16583
+ function calculateSantaLuciaTm(sequence) {
16584
+ try {
16585
+ sequence = sequence == null ? void 0 : sequence.toUpperCase().trim();
16586
+ if (!isValidSequence(sequence)) {
16587
+ throw new Error("Invalid sequence: contains non-DNA characters");
16588
+ }
16589
+ if (sequence.length < 2) {
16590
+ throw new Error("Sequence too short: minimum length is 2 bases");
16591
+ }
16592
+ let deltaH = 0;
16593
+ let deltaS = 0;
16594
+ for (let i = 0; i < sequence.length - 1; i++) {
16595
+ const dinucleotide = sequence.substring(i, i + 2);
16596
+ if (dinucleotide.includes("N")) {
16597
+ continue;
16598
+ }
16599
+ const params = SANTA_LUCIA_NN[dinucleotide];
16600
+ if (params) {
16601
+ deltaH += params.dH;
16602
+ deltaS += params.dS;
16603
+ }
16604
+ }
16605
+ const firstBase = sequence[0];
16606
+ const lastBase = sequence[sequence.length - 1];
16607
+ if (firstBase === "G" || firstBase === "C") {
16608
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16609
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16610
+ } else {
16611
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16612
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16613
+ }
16614
+ if (lastBase === "G" || lastBase === "C") {
16615
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16616
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16617
+ } else {
16618
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16619
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16620
+ }
16621
+ const nnPairs = sequence.length - 1;
16622
+ deltaS = applySaltCorrection(deltaS, nnPairs);
16623
+ const C = PRIMER3_PARAMS.dnaConc * 1e-9;
16624
+ const Tm = deltaH * 1e3 / (deltaS + PRIMER3_PARAMS.R * Math.log(C / 4));
16625
+ return Tm - 273.15;
16626
+ } catch (e) {
16627
+ return `Error calculating Tm for sequence ${sequence}. ${e}`;
16628
+ }
16629
+ }
16630
+ __name(calculateSantaLuciaTm, "calculateSantaLuciaTm");
16631
+ function calculateEndStability(sequence) {
16632
+ try {
16633
+ sequence = sequence == null ? void 0 : sequence.toUpperCase().trim();
16634
+ if (!isValidSequence(sequence)) {
16635
+ throw new Error("Invalid sequence: contains non-DNA characters");
16636
+ }
16637
+ if (sequence.length < 5) {
16638
+ throw new Error(
16639
+ "Sequence too short: minimum length is 5 bases for end stability calculation"
16640
+ );
16641
+ }
16642
+ const last5Bases = sequence.substring(sequence.length - 5);
16643
+ let deltaH = 0;
16644
+ let deltaS = 0;
16645
+ for (let i = 0; i < 4; i++) {
16646
+ const dinucleotide = last5Bases.substring(i, i + 2);
16647
+ if (dinucleotide.includes("N")) {
16648
+ continue;
16649
+ }
16650
+ const params = SANTA_LUCIA_NN[dinucleotide];
16651
+ if (params) {
16652
+ deltaH += params.dH;
16653
+ deltaS += params.dS;
16654
+ }
16655
+ }
16656
+ const firstBase = last5Bases[0];
16657
+ const lastBase = last5Bases[last5Bases.length - 1];
16658
+ if (firstBase === "G" || firstBase === "C") {
16659
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16660
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16661
+ } else {
16662
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16663
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16664
+ }
16665
+ if (lastBase === "G" || lastBase === "C") {
16666
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
16667
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
16668
+ } else {
16669
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
16670
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
16671
+ }
16672
+ const T = 310.15;
16673
+ const deltaG = deltaH - T * deltaS / 1e3;
16674
+ return Math.round(Math.abs(deltaG) * 100) / 100;
16675
+ } catch (e) {
16676
+ return `Error calculating end stability for sequence ${sequence}. ${e}`;
16677
+ }
16678
+ }
16679
+ __name(calculateEndStability, "calculateEndStability");
16518
16680
  function getDigestFragmentsForCutsites(sequenceLength, circular, cutsites, opts = {}) {
16519
16681
  const fragments = [];
16520
16682
  const pairs = [];
@@ -16676,9 +16838,11 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
16676
16838
  exports2.annotationTypes = annotationTypes;
16677
16839
  exports2.autoAnnotate = autoAnnotate;
16678
16840
  exports2.bioData = bioData;
16841
+ exports2.calculateEndStability = calculateEndStability;
16679
16842
  exports2.calculateNebTa = calculateNebTa;
16680
16843
  exports2.calculateNebTm = calculateNebTm;
16681
16844
  exports2.calculatePercentGC = calculatePercentGC;
16845
+ exports2.calculateSantaLuciaTm = calculateSantaLuciaTm;
16682
16846
  exports2.calculateTm = calculateTm;
16683
16847
  exports2.computeDigestFragments = computeDigestFragments;
16684
16848
  exports2.condensePairwiseAlignmentDifferences = condensePairwiseAlignmentDifferences;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teselagen/sequence-utils",
3
- "version": "0.3.35",
3
+ "version": "0.3.37",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "escape-string-regexp": "5.0.0",
@@ -0,0 +1,86 @@
1
+ import {
2
+ isValidSequence,
3
+ SANTA_LUCIA_NN,
4
+ SANTA_LUCIA_INIT
5
+ } from "./calculateSantaLuciaTm.js";
6
+
7
+ /**
8
+ * Calculate End Stability (3' end stability) of a primer
9
+ *
10
+ * The maximum stability for the last five 3' bases of a left or right primer.
11
+ * Bigger numbers mean more stable 3' ends. The value is the maximum delta G
12
+ * (kcal/mol) for duplex disruption for the five 3' bases.
13
+ *
14
+ * According to Primer3 documentation:
15
+ * - Most stable 5mer duplex: GCGCG = 6.86 kcal/mol (SantaLucia 1998)
16
+ * - Most labile 5mer duplex: TATAT = 0.86 kcal/mol (SantaLucia 1998)
17
+ *
18
+ * @param {string} sequence - DNA sequence (5' to 3')
19
+ * @returns {number} - Delta G (kcal/mol) for the last 5 bases at 3' end
20
+ * @throws {Error} Invalid sequence or too short.
21
+ */
22
+ export default function calculateEndStability(sequence) {
23
+ try {
24
+ sequence = sequence?.toUpperCase().trim();
25
+
26
+ if (!isValidSequence(sequence)) {
27
+ throw new Error("Invalid sequence: contains non-DNA characters");
28
+ }
29
+
30
+ if (sequence.length < 5) {
31
+ throw new Error(
32
+ "Sequence too short: minimum length is 5 bases for end stability calculation"
33
+ );
34
+ }
35
+
36
+ const last5Bases = sequence.substring(sequence.length - 5);
37
+
38
+ let deltaH = 0; // kcal/mol
39
+ let deltaS = 0; // cal/K·mol
40
+
41
+ // Calculate nearest-neighbor contributions for the 4 dinucleotides
42
+ for (let i = 0; i < 4; i++) {
43
+ const dinucleotide = last5Bases.substring(i, i + 2);
44
+
45
+ if (dinucleotide.includes("N")) {
46
+ continue;
47
+ }
48
+
49
+ const params = SANTA_LUCIA_NN[dinucleotide];
50
+ if (params) {
51
+ deltaH += params.dH;
52
+ deltaS += params.dS;
53
+ }
54
+ }
55
+
56
+ // Add initiation parameters for terminal base pairs
57
+ const firstBase = last5Bases[0];
58
+ const lastBase = last5Bases[last5Bases.length - 1];
59
+
60
+ // Terminal GC or AT initiation
61
+ if (firstBase === "G" || firstBase === "C") {
62
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
63
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
64
+ } else {
65
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
66
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
67
+ }
68
+
69
+ if (lastBase === "G" || lastBase === "C") {
70
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
71
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
72
+ } else {
73
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
74
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
75
+ }
76
+
77
+ // Calculate deltaG at 37°C (310.15 K)
78
+ // deltaG = deltaH - T * deltaS
79
+ const T = 310.15; // 37°C in Kelvin
80
+ const deltaG = deltaH - (T * deltaS) / 1000; // Result in kcal/mol
81
+
82
+ return Math.round(Math.abs(deltaG) * 100) / 100;
83
+ } catch (e) {
84
+ return `Error calculating end stability for sequence ${sequence}. ${e}`;
85
+ }
86
+ }
@@ -0,0 +1,21 @@
1
+ import assert from "assert";
2
+ import calculateEndStability from "./calculateEndStability";
3
+
4
+ describe("Calculate the stability of the primer ends.", () => {
5
+ it("should return the end stability score of a given primer sequence", () => {
6
+ assert.equal(calculateEndStability("AGCGGATAACAATTTCACACAGGA"), 3.89);
7
+ assert.equal(calculateEndStability("AGCGGATAACAATTTCAC"), 3.24);
8
+ assert.equal(calculateEndStability("AGCGGATAACAATTTcac"), 3.24);
9
+ assert.equal(calculateEndStability("ataataccgcgccacatagc"), 2.99);
10
+ assert.equal(calculateEndStability("AGCGGATAACAATACNNN"), 0.6);
11
+ assert.equal(calculateEndStability("AGCGGATAACAATACnnn"), 0.6);
12
+ assert.equal(
13
+ calculateEndStability("AGCGGATAACAYZAKLPATAC"),
14
+ "Error calculating end stability for sequence AGCGGATAACAYZAKLPATAC. Error: Invalid sequence: contains non-DNA characters"
15
+ );
16
+ assert.equal(
17
+ calculateEndStability("AGCG"),
18
+ "Error calculating end stability for sequence AGCG. Error: Sequence too short: minimum length is 5 bases for end stability calculation"
19
+ );
20
+ });
21
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Primer3 Melting Temperature Calculator
3
+ *
4
+ * Implements the melting temperature calculation algorithm from Primer3
5
+ * based on the documentation at https://primer3.ut.ee/primer3web_help.htm
6
+ *
7
+ * Uses SantaLucia (1998) nearest-neighbor thermodynamics method with
8
+ * fixed Primer3 custom parameters:
9
+ * - Formula: SantaLucia (1998)
10
+ * - Salt correction: SantaLucia (1998)
11
+ * - Monovalent salt: 50.0 mM
12
+ * - Divalent salt: 1.5 mM
13
+ * - dNTP concentration: 0.6 mM
14
+ * - DNA concentration: 50.0 nM
15
+ *
16
+ * References:
17
+ * - SantaLucia JR (1998) "A unified view of polymer, dumbbell and
18
+ * oligonucleotide DNA nearest-neighbor thermodynamics",
19
+ * Proc Natl Acad Sci 95:1460-65
20
+ */
21
+
22
+ // Primer3 custom parameters (fixed)
23
+ const PRIMER3_PARAMS = {
24
+ saltMonovalent: 50.0, // mM
25
+ saltDivalent: 1.5, // mM
26
+ dntpConc: 0.6, // mM
27
+ dnaConc: 50.0, // nM
28
+ R: 1.987 // Gas constant (cal/K·mol)
29
+ };
30
+
31
+ // SantaLucia (1998) nearest-neighbor parameters
32
+ // dH in kcal/mol, dS in cal/K·mol
33
+ export const SANTA_LUCIA_NN = {
34
+ AA: { dH: -7.9, dS: -22.2 },
35
+ TT: { dH: -7.9, dS: -22.2 },
36
+ AT: { dH: -7.2, dS: -20.4 },
37
+ TA: { dH: -7.2, dS: -21.3 },
38
+ CA: { dH: -8.5, dS: -22.7 },
39
+ TG: { dH: -8.5, dS: -22.7 },
40
+ GT: { dH: -8.4, dS: -22.4 },
41
+ AC: { dH: -8.4, dS: -22.4 },
42
+ CT: { dH: -7.8, dS: -21.0 },
43
+ AG: { dH: -7.8, dS: -21.0 },
44
+ GA: { dH: -8.2, dS: -22.2 },
45
+ TC: { dH: -8.2, dS: -22.2 },
46
+ CG: { dH: -10.6, dS: -27.2 },
47
+ GC: { dH: -9.8, dS: -24.4 },
48
+ GG: { dH: -8.0, dS: -19.9 },
49
+ CC: { dH: -8.0, dS: -19.9 }
50
+ };
51
+
52
+ // Initiation parameters (SantaLucia 1998)
53
+ export const SANTA_LUCIA_INIT = {
54
+ GC: { dH: 0.1, dS: -2.8 }, // initiation with terminal GC
55
+ AT: { dH: 2.3, dS: 4.1 } // initiation with terminal AT
56
+ };
57
+
58
+ /**
59
+ * Calculate effective monovalent cation concentration
60
+ * Accounts for divalent cations (Mg2+) binding to dNTPs
61
+ * Formula from von Ahsen et al. (2001)
62
+ *
63
+ * @returns {number} - Effective monovalent concentration in mM
64
+ */
65
+ function getEffectiveMonovalentConc() {
66
+ let effectiveMono = PRIMER3_PARAMS.saltMonovalent;
67
+
68
+ // Adjust for divalent cations
69
+ if (PRIMER3_PARAMS.saltDivalent > 0) {
70
+ const freeMg = Math.max(
71
+ 0,
72
+ PRIMER3_PARAMS.saltDivalent - PRIMER3_PARAMS.dntpConc
73
+ );
74
+ effectiveMono += 120 * Math.sqrt(freeMg);
75
+ }
76
+
77
+ return effectiveMono;
78
+ }
79
+
80
+ /**
81
+ * Apply SantaLucia (1998) salt correction to entropy
82
+ *
83
+ * @param {number} deltaS - Entropy in cal/K·mol
84
+ * @param {number} nnPairs - Number of nearest-neighbor pairs
85
+ * @returns {number} - Corrected entropy in cal/K·mol
86
+ */
87
+ function applySaltCorrection(deltaS, nnPairs) {
88
+ const effectiveMono = getEffectiveMonovalentConc();
89
+ // SantaLucia (1998) salt correction
90
+ return deltaS + 0.368 * nnPairs * Math.log(effectiveMono / 1000);
91
+ }
92
+
93
+ /**
94
+ * Validate DNA sequence
95
+ *
96
+ * @param {string} sequence - DNA sequence
97
+ * @returns {boolean} - True if valid
98
+ */
99
+ export function isValidSequence(sequence) {
100
+ return /^[ATGCN]+$/.test(sequence);
101
+ }
102
+
103
+ /**
104
+ * Calculate melting temperature using SantaLucia (1998) method
105
+ *
106
+ * @param {string} sequence - DNA sequence (5' to 3')
107
+ * @returns {number} - Melting temperature in Celsius
108
+ * @throws {Error} Invalid sequence or too short.
109
+ */
110
+ export default function calculateSantaLuciaTm(sequence) {
111
+ // Convert to uppercase and validate
112
+ try {
113
+ sequence = sequence?.toUpperCase().trim();
114
+
115
+ if (!isValidSequence(sequence)) {
116
+ throw new Error("Invalid sequence: contains non-DNA characters");
117
+ }
118
+
119
+ if (sequence.length < 2) {
120
+ throw new Error("Sequence too short: minimum length is 2 bases");
121
+ }
122
+
123
+ let deltaH = 0; // kcal/mol
124
+ let deltaS = 0; // cal/K·mol
125
+
126
+ // Calculate nearest-neighbor contributions
127
+ for (let i = 0; i < sequence.length - 1; i++) {
128
+ const dinucleotide = sequence.substring(i, i + 2);
129
+
130
+ // Skip if contains N
131
+ if (dinucleotide.includes("N")) {
132
+ continue;
133
+ }
134
+
135
+ const params = SANTA_LUCIA_NN[dinucleotide];
136
+ if (params) {
137
+ deltaH += params.dH;
138
+ deltaS += params.dS;
139
+ }
140
+ }
141
+
142
+ // Add initiation parameters
143
+ const firstBase = sequence[0];
144
+ const lastBase = sequence[sequence.length - 1];
145
+
146
+ // Terminal GC or AT initiation
147
+ if (firstBase === "G" || firstBase === "C") {
148
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
149
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
150
+ } else {
151
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
152
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
153
+ }
154
+
155
+ if (lastBase === "G" || lastBase === "C") {
156
+ deltaH += SANTA_LUCIA_INIT.GC.dH;
157
+ deltaS += SANTA_LUCIA_INIT.GC.dS;
158
+ } else {
159
+ deltaH += SANTA_LUCIA_INIT.AT.dH;
160
+ deltaS += SANTA_LUCIA_INIT.AT.dS;
161
+ }
162
+
163
+ // Apply salt correction
164
+ const nnPairs = sequence.length - 1;
165
+ deltaS = applySaltCorrection(deltaS, nnPairs);
166
+
167
+ // Calculate Tm using: Tm = deltaH / (deltaS + R * ln(C/4))
168
+ // where C is DNA concentration in M (convert from nM)
169
+ const C = PRIMER3_PARAMS.dnaConc * 1e-9; // Convert nM to M
170
+ const Tm = (deltaH * 1000) / (deltaS + PRIMER3_PARAMS.R * Math.log(C / 4));
171
+
172
+ // Convert from Kelvin to Celsius
173
+ return Tm - 273.15;
174
+ } catch (e) {
175
+ return `Error calculating Tm for sequence ${sequence}. ${e}`;
176
+ }
177
+ }
@@ -0,0 +1,39 @@
1
+ import assert from "assert";
2
+ import calculateSantaLuciaTm from "./calculateSantaLuciaTm";
3
+
4
+ describe("calculate Tm based on SantaLucia 1998", () => {
5
+ it("should return the melting temperature of a given sequence, if no degenerate bases are present", () => {
6
+ assert.equal(
7
+ calculateSantaLuciaTm("AGCGGATAACAATTTCACACAGGA"),
8
+ 60.805947394707346
9
+ );
10
+ assert.equal(
11
+ calculateSantaLuciaTm("AGCGGATAACAATTTCAC"),
12
+ 50.301642635069356
13
+ );
14
+ assert.equal(
15
+ calculateSantaLuciaTm("AGCGGATAACAATTTcac"),
16
+ 50.301642635069356
17
+ );
18
+ assert.equal(
19
+ calculateSantaLuciaTm("ataataccgcgccacatagc"),
20
+ 58.27798862992364
21
+ );
22
+ assert.equal(
23
+ calculateSantaLuciaTm("AGCGGATAACAATACNNN"),
24
+ 40.92944342497407
25
+ );
26
+ assert.equal(
27
+ calculateSantaLuciaTm("AGCGGATAACAATACnnn"),
28
+ 40.92944342497407
29
+ );
30
+ assert.equal(
31
+ calculateSantaLuciaTm("AGCGGATAACAYZAKLPATAC"),
32
+ "Error calculating Tm for sequence AGCGGATAACAYZAKLPATAC. Error: Invalid sequence: contains non-DNA characters"
33
+ );
34
+ assert.equal(
35
+ calculateSantaLuciaTm("A"),
36
+ "Error calculating Tm for sequence A. Error: Sequence too short: minimum length is 2 bases"
37
+ );
38
+ });
39
+ });
@@ -17,7 +17,7 @@ const genbankFeatureTypes = [
17
17
  { name: "regulatory", color: "#3F6C51" },
18
18
  { name: "SecStr", color: "#7B4B94" },
19
19
  { name: "Site", color: "#7D82B8" },
20
- { name: "telomere", color: "DE9151" },
20
+ { name: "telomere", color: "#DE9151" },
21
21
  { name: "tmRNA", color: "#B7E3CC" },
22
22
  { name: "unsure", color: "#C4FFB2" },
23
23
  { name: "V_segment", color: "#D6F7A3" },
package/src/index.js CHANGED
@@ -96,6 +96,8 @@ export { default as condensePairwiseAlignmentDifferences } from "./condensePairw
96
96
  export { default as addGapsToSeqReads } from "./addGapsToSeqReads";
97
97
  export { default as calculateNebTm } from "./calculateNebTm";
98
98
  export { default as calculateNebTa } from "./calculateNebTa";
99
+ export { default as calculateSantaLuciaTm } from "./calculateSantaLuciaTm";
100
+ export { default as calculateEndStability } from "./calculateEndStability";
99
101
  export { default as getDigestFragmentsForCutsites } from "./getDigestFragmentsForCutsites";
100
102
  export { default as getDigestFragmentsForRestrictionEnzymes } from "./getDigestFragmentsForRestrictionEnzymes";
101
103
  export { default as convertDnaCaretPositionOrRangeToAA } from "./convertDnaCaretPositionOrRangeToAA";
@@ -9,6 +9,7 @@ import tidyUpAnnotation from "./tidyUpAnnotation";
9
9
  import getDegenerateDnaStringFromAaString from "./getDegenerateDnaStringFromAAString";
10
10
  import { getFeatureTypes } from "./featureTypesAndColors";
11
11
  import getAminoAcidStringFromSequenceString from "./getAminoAcidStringFromSequenceString";
12
+ import { expandOrContractRangeByLength } from "@teselagen/range-utils";
12
13
 
13
14
  export default function tidyUpSequenceData(pSeqData, options = {}) {
14
15
  const {
@@ -137,14 +138,21 @@ export default function tidyUpSequenceData(pSeqData, options = {}) {
137
138
  //filter off cds translations
138
139
  return [];
139
140
  }
140
- if (!translation.aminoAcids && !seqData.noSequence) {
141
- translation.aminoAcids = getAminoAcidDataForEachBaseOfDna(
141
+ const codonStart = translation?.notes?.codon_start?.[0] - 1 || 0;
142
+ const expandedRange = expandOrContractRangeByLength(
143
+ translation,
144
+ -codonStart,
145
+ true,
146
+ seqData.sequence.length
147
+ );
148
+ if (!expandedRange.aminoAcids && !seqData.noSequence) {
149
+ expandedRange.aminoAcids = getAminoAcidDataForEachBaseOfDna(
142
150
  seqData.sequence,
143
- translation.forward,
144
- translation
151
+ expandedRange.forward,
152
+ expandedRange
145
153
  );
146
154
  }
147
- return translation;
155
+ return expandedRange;
148
156
  });
149
157
  }
150
158