Skip to content

Commit

Permalink
feat: add penalty weights for pick_hyb_probe task
Browse files Browse the repository at this point in the history
  • Loading branch information
emmcauley committed Sep 23, 2024
1 parent 70f6840 commit a47953e
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 20 deletions.
22 changes: 14 additions & 8 deletions prymer/primer3/primer3_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
The module uses:
1. [`Primer3Parameters`][prymer.primer3.primer3_parameters.Primer3Parameters]
to specify user-specified criteria for primer design
2. [`Primer3Weights`][prymer.primer3.primer3_weights.Primer3Weights] to establish penalties
based on those criteria
3. [`Primer3Task`][prymer.primer3.primer3_task.Primer3Task] to organize task-specific
to specify user-specified criteria for primer design
2. [`PrimerAndAmpliconWeights`][prymer.primer3.primer3_weights.PrimerAndAmpliconWeights]
to establish penalties based on those criteria
3. [`ProbeWeights`][prymer.primer3.primer3_weights.ProbeWeights] to specify penalties based on probe
design criteria
4. [`Primer3Task`][prymer.primer3.primer3_task.Primer3Task] to organize task-specific
logic.
4. [`Span`](index.md#prymer.api.span.Span] to specify the target region.
5. [`Span`](index.md#prymer.api.span.Span] to specify the target region.
The `Primer3Input.to_input_tags(]` method
The main purpose of this class is to generate the
Expand Down Expand Up @@ -81,12 +83,14 @@

from dataclasses import dataclass
from typing import Any
from typing import Optional

from prymer.api.span import Span
from prymer.primer3.primer3_input_tag import Primer3InputTag
from prymer.primer3.primer3_parameters import Primer3Parameters
from prymer.primer3.primer3_task import Primer3TaskType
from prymer.primer3.primer3_weights import Primer3Weights
from prymer.primer3.primer3_weights import PrimerAndAmpliconWeights
from prymer.primer3.primer3_weights import ProbeWeights


@dataclass(frozen=True, init=True, slots=True)
Expand All @@ -96,7 +100,8 @@ class Primer3Input:
target: Span
task: Primer3TaskType
params: Primer3Parameters
weights: Primer3Weights = Primer3Weights()
primer_weights: Optional[PrimerAndAmpliconWeights] = PrimerAndAmpliconWeights()
probe_weights: Optional[ProbeWeights] = None

def to_input_tags(self, design_region: Span) -> dict[Primer3InputTag, Any]:
"""Assembles `Primer3InputTag` and values for input to `Primer3`
Expand All @@ -116,6 +121,7 @@ def to_input_tags(self, design_region: Span) -> dict[Primer3InputTag, Any]:
assembled_tags = {
**primer3_task_params,
**self.params.to_input_tags(),
**self.weights.to_input_tags(),
**self.primer_weights.to_input_tags(),
**(self.probe_weights.to_input_tags() if self.probe_weights is not None else {}),
}
return assembled_tags
46 changes: 39 additions & 7 deletions prymer/primer3/primer3_weights.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
```python
>>> Primer3Weights(product_size_lt=1, product_size_gt=1)
>>> PrimerAndAmpliconWeights(product_size_lt=1, product_size_gt=1)
Primer3Weights(product_size_lt=1, product_size_gt=1, ...)
>>> Primer3Weights(product_size_lt=5, product_size_gt=1)
>>> PrimerAndAmpliconWeights(product_size_lt=5, product_size_gt=1)
Primer3Weights(product_size_lt=5, product_size_gt=1, ...)
```
Expand All @@ -30,22 +30,23 @@


@dataclass(frozen=True, init=True, slots=True)
class Primer3Weights:
class PrimerAndAmpliconWeights:
"""Holds the weights that Primer3 uses to adjust penalties
that originate from the designed primer(s).
The weights that Primer3 uses when a parameter is less than optimal are labeled with "_lt".
"_gt" weights are penalties applied when a parameter is greater than optimal.
Some of these settings depart from the default settings enumerated in the Primer3 manual.
Please see the Primer3 manual for additional details:
https://primer3.org/manual.html#globalTags
Example:
>>> Primer3Weights() #default implementation
Primer3Weights(product_size_lt=1, product_size_gt=1, product_tm_lt=0.0, product_tm_gt=0.0, primer_end_stability=0.25, primer_gc_lt=0.25, primer_gc_gt=0.25, primer_self_any=0.1, primer_self_end=0.1, primer_size_lt=0.5, primer_size_gt=0.1, primer_tm_lt=1.0, primer_tm_gt=1.0)
>>> PrimerAndAmpliconWeights() #default implementation
PrimerAndAmpliconWeights(product_size_lt=1, product_size_gt=1, product_tm_lt=0.0, product_tm_gt=0.0, primer_end_stability=0.25, primer_gc_lt=0.25, primer_gc_gt=0.25, primer_self_any=0.1, primer_self_end=0.1, primer_size_lt=0.5, primer_size_gt=0.1, primer_tm_lt=1.0, primer_tm_gt=1.0, probe_size_lt=0.25, probe_size_gt=0.25, probe_tm_lt=1.0, probe_tm_gt=1.0, probe_gc_lt=0.5, probe_gc_gt=0.5, probe_self_any=1.0, probe_self_end=1.0)
>>> Primer3Weights(product_size_lt=5)
Primer3Weights(product_size_lt=5, product_size_gt=1, product_tm_lt=0.0, product_tm_gt=0.0, primer_end_stability=0.25, primer_gc_lt=0.25, primer_gc_gt=0.25, primer_self_any=0.1, primer_self_end=0.1, primer_size_lt=0.5, primer_size_gt=0.1, primer_tm_lt=1.0, primer_tm_gt=1.0)
>>> PrimerAndAmpliconWeights(product_size_lt=5)
PrimerAndAmpliconWeights(product_size_lt=5, product_size_gt=1, product_tm_lt=0.0, product_tm_gt=0.0, primer_end_stability=0.25, primer_gc_lt=0.25, primer_gc_gt=0.25, primer_self_any=0.1, primer_self_end=0.1, primer_size_lt=0.5, primer_size_gt=0.1, primer_tm_lt=1.0, primer_tm_gt=1.0, probe_size_lt=0.25, probe_size_gt=0.25, probe_tm_lt=1.0, probe_tm_gt=1.0, probe_gc_lt=0.5, probe_gc_gt=0.5, probe_self_any=1.0, probe_self_end=1.0)
""" # noqa: E501

product_size_lt: int = 1
Expand Down Expand Up @@ -80,3 +81,34 @@ def to_input_tags(self) -> dict[Primer3InputTag, Any]:
Primer3InputTag.PRIMER_WT_TM_GT: self.primer_tm_gt,
}
return mapped_dict


@dataclass(frozen=True, init=True, slots=True)
class ProbeWeights:
"""Holds the weights that Primer3 uses to adjust penalties
that originate from the designed internal probe(s)."""

probe_size_lt: float = 0.25
probe_size_gt: float = 0.25
probe_tm_lt: float = 1.0
probe_tm_gt: float = 1.0
probe_gc_lt: float = 0.5
probe_gc_gt: float = 0.5
probe_self_any: float = 1.0
probe_self_end: float = 1.0
probe_hairpin_th: float = 1.0

def to_input_tags(self) -> dict[Primer3InputTag, Any]:
"""Maps weights to Primer3InputTag to feed directly into Primer3."""
mapped_dict = {
Primer3InputTag.PRIMER_INTERNAL_WT_SIZE_LT: self.probe_size_lt,
Primer3InputTag.PRIMER_INTERNAL_WT_SIZE_GT: self.probe_size_gt,
Primer3InputTag.PRIMER_INTERNAL_WT_TM_LT: self.probe_tm_lt,
Primer3InputTag.PRIMER_INTERNAL_WT_TM_GT: self.probe_tm_gt,
Primer3InputTag.PRIMER_INTERNAL_WT_GC_PERCENT_LT: self.probe_gc_lt,
Primer3InputTag.PRIMER_INTERNAL_WT_GC_PERCENT_GT: self.probe_gc_gt,
Primer3InputTag.PRIMER_INTERNAL_WT_SELF_ANY: self.probe_self_any,
Primer3InputTag.PRIMER_INTERNAL_WT_SELF_END: self.probe_self_end,
Primer3InputTag.PRIMER_INTERNAL_WT_HAIRPIN_TH: self.probe_hairpin_th,
}
return mapped_dict
26 changes: 21 additions & 5 deletions tests/primer3/test_primer3_weights.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from prymer.primer3.primer3_input_tag import Primer3InputTag
from prymer.primer3.primer3_weights import Primer3Weights
from prymer.primer3.primer3_weights import PrimerAndAmpliconWeights
from prymer.primer3.primer3_weights import ProbeWeights


def test_primer_weights_valid() -> None:
"""Test instantiation of Primer3Weights object with valid input"""
test_weights = Primer3Weights()
"""Test instantiation of `PrimerAndAmpliconWeights` object with valid input"""
test_weights = PrimerAndAmpliconWeights()
test_dict = test_weights.to_input_tags()
assert test_dict[Primer3InputTag.PRIMER_PAIR_WT_PRODUCT_SIZE_LT] == 1
assert test_dict[Primer3InputTag.PRIMER_PAIR_WT_PRODUCT_SIZE_GT] == 1
Expand All @@ -22,9 +23,24 @@ def test_primer_weights_valid() -> None:
assert len((test_dict.values())) == 13


def test_probe_weights_valid() -> None:
test_weights = ProbeWeights()
test_dict = test_weights.to_input_tags()
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_SIZE_LT] == 0.25
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_SIZE_GT] == 0.25
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_TM_LT] == 1.0
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_TM_GT] == 1.0
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_GC_PERCENT_LT] == 0.5
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_GC_PERCENT_GT] == 0.5
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_SELF_ANY] == 1.0
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_SELF_END] == 1.0
assert test_dict[Primer3InputTag.PRIMER_INTERNAL_WT_HAIRPIN_TH] == 1.0
assert len((test_dict.values())) == 9


def test_primer_weights_to_input_tags() -> None:
"""Test results from to_input_tags() with and without default values"""
default_map = Primer3Weights().to_input_tags()
default_map = PrimerAndAmpliconWeights().to_input_tags()
assert default_map[Primer3InputTag.PRIMER_PAIR_WT_PRODUCT_SIZE_LT] == 1
customized_map = Primer3Weights(product_size_lt=5).to_input_tags()
customized_map = PrimerAndAmpliconWeights(product_size_lt=5).to_input_tags()
assert customized_map[Primer3InputTag.PRIMER_PAIR_WT_PRODUCT_SIZE_LT] == 5

0 comments on commit a47953e

Please sign in to comment.