Skip to content

Commit

Permalink
Merge Primer*Weights into Primer3Parameters, remove Primer3Input (#112)
Browse files Browse the repository at this point in the history
1. merges the `Primer*Weights` classes into their corresponding amplicon
and probe parameter classes (reduces 4 classes to two). The attributes
have the `_wt` suffix in their name. The
`primer3.primer3.primer3_weights` module is gone.
2. moves the `Primer3Input` functionality into `Primer3` directly.
`primer3.primer3.primer3_input` is now gone.
3. Adds in `WeightRange` for the weights when a value is out of range
(either less than, or greater than). Could use a better name?
4. `Primer3.design` now explicitly takes in the design task and target,
in addition to the params. It checks to make sure that the task (e.g.
design left primers) matches the params (e.g. amplicons).
5. Simplify `Primer3Task` (remove methods no longer used)
  • Loading branch information
nh13 authored Jan 21, 2025
1 parent d251ecb commit 8e1de20
Show file tree
Hide file tree
Showing 13 changed files with 461 additions and 890 deletions.
24 changes: 12 additions & 12 deletions prymer/api/picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from prymer.model import Oligo
from prymer.model import PrimerPair
from prymer.model import Span
from prymer.primer3 import PrimerAndAmpliconWeights
from prymer.primer3 import PrimerParameters


def score(
Expand All @@ -40,7 +40,7 @@ def score(
amplicon_tm: float,
amplicon_sizes: MinOptMax[int],
amplicon_tms: MinOptMax[float],
weights: PrimerAndAmpliconWeights,
params: PrimerParameters,
) -> float:
"""Score the amplicon in a manner similar to Primer3
Expand All @@ -59,7 +59,7 @@ def score(
amplicon_tm: the melting temperature of the amplicon
amplicon_sizes: minimum, optimal, and maximum amplicon sizes (lengths)
amplicon_tms: minimum, optimal, and maximum amplicon Tms
weights: the set of penalty weights
params: the set of primer3 parameters
Returns:
the penalty for the whole amplicon.
Expand All @@ -73,9 +73,9 @@ def score(
if amplicon_sizes.opt == 0:
size_penalty = 0.0
elif amplicon.length > amplicon_sizes.opt:
size_penalty = (amplicon.length - amplicon_sizes.opt) * weights.product_size_gt
size_penalty = (amplicon.length - amplicon_sizes.opt) * params.amplicon_size_wt.gt
else:
size_penalty = (amplicon_sizes.opt - amplicon.length) * weights.product_size_lt
size_penalty = (amplicon_sizes.opt - amplicon.length) * params.amplicon_size_wt.lt

# The penalty for the amplicon melting temperature.
# The difference in melting temperature between the calculated and optimal is weighted by the
Expand All @@ -84,9 +84,9 @@ def score(
if amplicon_tms.opt == 0.0:
tm_penalty = 0.0
elif amplicon_tm > amplicon_tms.opt:
tm_penalty = (amplicon_tm - amplicon_tms.opt) * weights.product_tm_gt
tm_penalty = (amplicon_tm - amplicon_tms.opt) * params.amplicon_tm_wt.gt
else:
tm_penalty = (amplicon_tms.opt - amplicon_tm) * weights.product_tm_lt
tm_penalty = (amplicon_tms.opt - amplicon_tm) * params.amplicon_tm_wt.lt

# Put it all together
return left_primer.penalty + right_primer.penalty + size_penalty + tm_penalty
Expand All @@ -99,7 +99,7 @@ def build_primer_pairs( # noqa: C901
amplicon_sizes: MinOptMax[int],
amplicon_tms: MinOptMax[float],
max_heterodimer_tm: Optional[float],
weights: PrimerAndAmpliconWeights,
params: PrimerParameters,
fasta_path: Path,
thermo: Optional[Thermo] = None,
) -> Iterator[PrimerPair]:
Expand All @@ -116,11 +116,11 @@ def build_primer_pairs( # noqa: C901
amplicon_sizes: minimum, optimal, and maximum amplicon sizes (lengths)
amplicon_tms: minimum, optimal, and maximum amplicon Tms
max_heterodimer_tm: if supplied, heterodimer Tms will be calculated for primer pairs,
and those exceeding the maximum Tm will be discarded
weights: the set of penalty weights
and those exceeding the maximum Tm will be discarded
params: the set of penalty params
fasta_path: the path to the FASTA file from which the amplicon sequence will be retrieved.
thermo: a [`Thermo`][prymer.Thermo] instance for performing thermodynamic calculations
including amplicon tm; if not provided, a default Thermo instance will be created
including amplicon tm; if not provided, a default Thermo instance will be created
Returns:
An iterator over all the valid primer pairs, sorted by primer pair penalty.
Expand Down Expand Up @@ -197,7 +197,7 @@ def build_primer_pairs( # noqa: C901
amplicon_tm=amp_tm,
amplicon_sizes=amplicon_sizes,
amplicon_tms=amplicon_tms,
weights=weights,
params=params,
)

pairings.append((i, j, penalty, amp_tm))
Expand Down
42 changes: 42 additions & 0 deletions prymer/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,48 @@ def __str__(self) -> str:
return f"(min:{self.min}, opt:{self.opt}, max:{self.max})"


@dataclass(slots=True, frozen=True, init=True)
class Weights:
"""Stores a pair of penalty weights.
Weights are used when comparing a primer or probe property (e.g. primer length) to the optimal
parameterized value. If the value is less than, then the `lt` weight is used. If the value is
greater than, then the `gt` weight is used.
The two values can be either int or float values but must be of the same type within one
Range object (for example, `lt` cannot be a float while `gt` is an int).
Examples of interacting with the `Range` class
```python
>>> range = Weights(lt=1.0, gt=4.0)
>>> print(range)
(lt:1.0, gt:4.0)
>>> list(range)
[1.0, 4.0]
```
Attributes:
lt: the minimum value (inclusive)
gt: the maximum value (inclusive)
Raises:
ValueError: if lt and gt are not the same type
"""

lt: float
gt: float

def __iter__(self) -> Iterator[float]:
"""Returns an iterator of min and max"""
return iter([self.lt, self.gt])

def __str__(self) -> str:
"""Returns a string representation of min and max"""
return f"(lt:{self.lt}, gt:{self.gt})"


@unique
class Strand(StrEnum):
"""Represents the strand of a span to the genome."""
Expand Down
12 changes: 4 additions & 8 deletions prymer/primer3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,26 @@
from prymer.primer3.primer3 import Primer3Failure
from prymer.primer3.primer3 import Primer3Result
from prymer.primer3.primer3_failure_reason import Primer3FailureReason
from prymer.primer3.primer3_input import Primer3Input
from prymer.primer3.primer3_input_tag import Primer3InputTag
from prymer.primer3.primer3_parameters import PrimerAndAmpliconParameters
from prymer.primer3.primer3_parameters import Primer3Parameters
from prymer.primer3.primer3_parameters import PrimerParameters
from prymer.primer3.primer3_parameters import ProbeParameters
from prymer.primer3.primer3_task import DesignLeftPrimersTask
from prymer.primer3.primer3_task import DesignPrimerPairsTask
from prymer.primer3.primer3_task import DesignRightPrimersTask
from prymer.primer3.primer3_task import PickHybProbeOnly
from prymer.primer3.primer3_weights import PrimerAndAmpliconWeights
from prymer.primer3.primer3_weights import ProbeWeights

__all__ = [
"Primer3",
"Primer3Result",
"Primer3Failure",
"Primer3FailureReason",
"Primer3Input",
"Primer3InputTag",
"DesignLeftPrimersTask",
"DesignPrimerPairsTask",
"DesignRightPrimersTask",
"PickHybProbeOnly",
"PrimerAndAmpliconParameters",
"Primer3Parameters",
"PrimerParameters",
"ProbeParameters",
"ProbeWeights",
"PrimerAndAmpliconWeights",
]
Loading

0 comments on commit 8e1de20

Please sign in to comment.