Skip to content

Commit

Permalink
Add/salt adjusted (#51)
Browse files Browse the repository at this point in the history
* implement `salt_adjusted_melting_temperature_calc`

* integrate `salt_adjusted_melting_temperature_calc` into `melting_temperature` of primer class

* update naming

* add testcases for salt adjusted temperature calculations

* `CHANGELOG.md` updated

* add `salt adjusted` case in `Melting temperature` section of `README.md`

* additional test cases use different amounts of salt

* add `salt` to `init` + integrate internally

* update docstring + feedback applied

* `CHANGELOG.md` updated

* Update sequence

* reflect salt in README.md example

* update docstring

* refactor and move inside of the loop

* `README.md` updated
  • Loading branch information
AHReccese authored Feb 28, 2025
1 parent c97321a commit ce54d15
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
```
Expand Down Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions opr/functions.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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.
Expand Down
9 changes: 7 additions & 2 deletions opr/primer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -30,14 +30,16 @@ 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.
:param sequence: primer nucleotides sequence
: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)
Expand All @@ -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,
Expand Down Expand Up @@ -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]
24 changes: 24 additions & 0 deletions tests/test_calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ce54d15

Please sign in to comment.