diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6da98..bb17630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Salt-adjusted melting temperature calculation +### Changed ## [0.3] - 2025-02-06 ### Added - `__iter__` overload diff --git a/README.md b/README.md index b838ab1..328ff8f 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ ### Load ```pycon ->>> from opr import Primer ->>> primer1 = Primer(sequence="CTGGAGGACGGAAGAGGAAGTAA") +>>> from opr import Primer, MeltingTemperature +>>> primer1 = Primer(sequence="CTGGAGGACGGAAGAGGAAGTAA", salt=50) >>> primer1.sequence 'CTGGAGGACGGAAGAGGAAGTAA' ``` @@ -107,9 +107,17 @@ 1 ``` #### Melting temperature +##### Basic ```pycon >>> primer1.melting_temperature() 57.056521739130446 +>>> primer1.melting_temperature(MeltingTemperature.BASIC) +57.056521739130446 +``` +##### Salt-adjusted +```pycon +>>> primer1.melting_temperature(MeltingTemperature.SALT_ADJUSTED) +64.64203250676053 ``` ### Operations diff --git a/opr/functions.py b/opr/functions.py index 90c8c32..d82c610 100644 --- a/opr/functions.py +++ b/opr/functions.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """OPR functions.""" +import math from .params import A_WEIGHT, T_WEIGHT, C_WEIGHT, G_WEIGHT, ANHYDROUS_MOLECULAR_WEIGHT_CONSTANT @@ -38,6 +39,33 @@ def basic_melting_temperature_calc(sequence): return melting_temperature +def salt_adjusted_melting_temperature_calc(sequence, salt): + """ + Calculate the salt-adjusted melting temperature (Tm) of a primer sequence. + + :param sequence: Primer nucleotides sequence + :type sequence: str + :param salt: Sodium ion concentration in moles (unit mM) + :type salt: float + :return: Salt-adjusted melting temperature as float + """ + a_count = sequence.count('A') + t_count = sequence.count('T') + c_count = sequence.count('C') + g_count = sequence.count('G') + seq_length = len(sequence) + if seq_length <= 13: + salt_adjustment = 16.6 * (math.log10(salt)-3) - 16.6 * math.log10(0.050) + tm = (a_count + t_count) * 2 + (g_count + c_count) * 4 + salt_adjustment + else: + tm = ( + 100.5 + (41 * (g_count + c_count) / seq_length) + - (820 / seq_length) + + 16.6 * (math.log10(salt)-3) + ) + return tm + + def gc_clamp_calc(sequence): """ Calculate GC clamp. diff --git a/opr/primer.py b/opr/primer.py index 3cb53fc..df9ba81 100644 --- a/opr/primer.py +++ b/opr/primer.py @@ -11,7 +11,7 @@ from .params import DNA_COMPLEMENT_MAP from .params import PRIMER_ADDITION_ERROR, PRIMER_MULTIPLICATION_ERROR from .params import PRIMER_MELTING_TEMPERATURE_NOT_IMPLEMENTED_ERROR -from .functions import molecular_weight_calc, basic_melting_temperature_calc, gc_clamp_calc +from .functions import molecular_weight_calc, basic_melting_temperature_calc, salt_adjusted_melting_temperature_calc, gc_clamp_calc class MeltingTemperature(Enum): @@ -30,7 +30,7 @@ class Primer: >>> oprimer.molecular_weight """ - def __init__(self, sequence, name=DEFAULT_PRIMER_NAME): + def __init__(self, sequence, name=DEFAULT_PRIMER_NAME, salt=50): """ Initialize the Primer instance. @@ -38,6 +38,8 @@ def __init__(self, sequence, name=DEFAULT_PRIMER_NAME): :type sequence: str :param name: primer name :type name: str + :param salt: Sodium ion concentration in moles (unit mM) + :type salt: float :return: an instance of the Primer class """ self._sequence = Primer.validate_primer(sequence) @@ -47,6 +49,7 @@ def __init__(self, sequence, name=DEFAULT_PRIMER_NAME): self._gc_clamp = None self._single_runs = None self._double_runs = None + self._salt_level = salt self._melting_temperature = { MeltingTemperature.BASIC: None, MeltingTemperature.SALT_ADJUSTED: None, @@ -278,6 +281,8 @@ def melting_temperature(self, method=MeltingTemperature.BASIC): return self._melting_temperature[method] if method == MeltingTemperature.BASIC: self._melting_temperature[MeltingTemperature.BASIC] = basic_melting_temperature_calc(self._sequence) + elif method == MeltingTemperature.SALT_ADJUSTED: + self._melting_temperature[MeltingTemperature.SALT_ADJUSTED] = salt_adjusted_melting_temperature_calc(self._sequence, self._salt_level) else: raise NotImplementedError(PRIMER_MELTING_TEMPERATURE_NOT_IMPLEMENTED_ERROR) return self._melting_temperature[method] diff --git a/tests/test_calculations.py b/tests/test_calculations.py index 5ba683c..6f03ce4 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -50,6 +50,30 @@ def test_melt_temp_2(): # Reference: http://biotools.nubic.northwestern.edu/Oli assert round(basic_melt_temp, 1) == 12 +def test_melt_temp_3(): # Reference: http://biotools.nubic.northwestern.edu/OligoCalc.html + oprimer = Primer("CTGGAGGACGGAAGAGGAAGTAA") + salt_adjusted_melt_temp = oprimer.melting_temperature(method=MeltingTemperature.SALT_ADJUSTED) + assert round(salt_adjusted_melt_temp, 0) == 65.0 + + +def test_melt_temp_4(): # Reference: http://biotools.nubic.northwestern.edu/OligoCalc.html + oprimer = Primer("CTGGAGGACGGAAGAGGAAGTAAA", salt=65) + salt_adjusted_melt_temp = oprimer.melting_temperature(method=MeltingTemperature.SALT_ADJUSTED) + assert round(salt_adjusted_melt_temp, 0) == 67.0 + + +def test_melt_temp_5(): # Reference: http://biotools.nubic.northwestern.edu/OligoCalc.html + oprimer = Primer("CTGGAGG") + salt_adjusted_melt_temp = oprimer.melting_temperature(method=MeltingTemperature.SALT_ADJUSTED) + assert round(salt_adjusted_melt_temp, 0) == 24.0 + + +def test_melt_temp_6(): # Reference: http://biotools.nubic.northwestern.edu/OligoCalc.html + oprimer = Primer("CTGGAGG", salt=65) + salt_adjusted_melt_temp = oprimer.melting_temperature(method=MeltingTemperature.SALT_ADJUSTED) + assert round(salt_adjusted_melt_temp, 0) == 26.0 + + def test_single_runs_1(): # Reference: https://www.oligoevaluator.com/OligoCalcServlet oprimer = Primer("ATCGATCG") runs = oprimer.single_runs