Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Primer*Weights into Primer3Parameters, remove Primer3Input #112

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 AmpliconParameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to rename it from PrimerAndAmplicon I'd vote for PrimerParameters as it feels like the majority of the parameters are around primers. Also, can be used for designing just left primers (no amplicon), just right primers (no amplicon) or both (and therefore an amplicon).



def score(
Expand All @@ -40,7 +40,7 @@ def score(
amplicon_tm: float,
amplicon_sizes: MinOptMax[int],
amplicon_tms: MinOptMax[float],
weights: PrimerAndAmpliconWeights,
params: AmpliconParameters,
) -> 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: AmpliconParameters,
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
49 changes: 49 additions & 0 deletions prymer/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,55 @@
return f"(min:{self.min}, opt:{self.opt}, max:{self.max})"


@dataclass(slots=True, frozen=True, init=True)
class WeightRange(Generic[Numeric]):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, but a few thoughts:

  • It's not really a range, more of a pair. I.e. I can have a higher penalty weight for "primer tm too low" than "primer tm too high" - they're independent values. Perhaps it's just Weights?
  • I think in p3 weights are always floats because they are scaling factors, so i think we could un-generify it and juse always have the values be floats

"""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 = WeightRange(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: Numeric
gt: Numeric

def __post_init__(self) -> None:
dtype = type(self.lt)
if not isinstance(self.gt, dtype):
raise TypeError(

Check warning on line 122 in prymer/model.py

View check run for this annotation

Codecov / codecov/patch

prymer/model.py#L122

Added line #L122 was not covered by tests
"Min and max must be the same type; " f"received min: {dtype}, max: {type(self.gt)}"
)

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 AmpliconParameters
from prymer.primer3.primer3_parameters import Primer3Parameters
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",
"AmpliconParameters",
"ProbeParameters",
"ProbeWeights",
"PrimerAndAmpliconWeights",
]
Loading
Loading