From 2393014e50c12a0471f9d2743c3dfcfc6225fd9a Mon Sep 17 00:00:00 2001 From: Erin McAuley Date: Thu, 3 Oct 2024 14:26:25 -0400 Subject: [PATCH] feat: add thermo attributes to Oligo and Primer3 parsing logic; docs: update doctest examples --- prymer/api/oligo.py | 33 +++++++++++++++++++++++++++++---- prymer/api/primer_pair.py | 4 ++-- prymer/primer3/primer3.py | 16 +++++++++++++++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/prymer/api/oligo.py b/prymer/api/oligo.py index 4a003de..3a22c2d 100644 --- a/prymer/api/oligo.py +++ b/prymer/api/oligo.py @@ -83,10 +83,27 @@ class Oligo(OligoLike, Metric["Oligo"]): """Stores the properties of the designed oligo. - Oligos can include both single primer and internal probe designs. The penalty score of the - design is emitted by Primer3 and controlled by the corresponding design parameters. - The penalty for a primer is set by the combination of `PrimerAndAmpliconParameters` and - `PrimerWeights`, whereas a probe penalty is set by `ProbeParameters` and `ProbeWeights`. + Oligos can include both single primer and internal probe designs. Primer3 emits information + about the specific properties of a primer and/or probe, including the base sequence, + melting temperature (tm), and score. + + The penalty score of the design is emitted by Primer3 and controlled by the corresponding + design parameters. The penalty for a primer is set by the combination of + `PrimerAndAmpliconParameters` and `PrimerWeights`. + A probe penalty is set by `ProbeParameters` and `ProbeWeights`. + + The span of an `Oligo` object represents the mapping of the oligo + to the genome. `Oligo` objects can optionally have a `tail` sequence to prepend to the 5' end + of the primer or probe, as well as a `name` for downstream record keeping. + + `Oligo` objects can also optionally store thermodynamic characteristics of primer and/or probe + design. These include the calculated melting temperatures of the most stable hairpin structure, + self-, and end- complementarity. These values can be emitted by Primer3. + + These thermodynamic characteristics are meant to quantify how likely it is the primer or probe + will bind to itself (e.g., instead of the target DNA). A melting temperature close to or greater + than the intended melting temperature for target binding indicates the primer or probe is + likely to form stable hairpins or dimers, leading to reduced efficiency of the reaction. Attributes: tm: the calculated melting temperature of the oligo @@ -95,6 +112,11 @@ class Oligo(OligoLike, Metric["Oligo"]): bases: the base sequence of the oligo (excluding any tail) tail: an optional tail sequence to put on the 5' end of the primer name: an optional name to use for the primer + self_any_th: an optional calculated melting temperature that represents + the tendency of an oligo to bind to itself (self-complementarity) + self_end_th: an optional calculated melting temperature that represents + the tendency of a primer to bind to the 3'-END of an identical primer + hairpin_th: an optional calculated melting temperature of the oligo hairpin structure """ @@ -103,6 +125,9 @@ class Oligo(OligoLike, Metric["Oligo"]): span: Span bases: Optional[str] = None tail: Optional[str] = None + self_any_th: Optional[float] = None + self_end_th: Optional[float] = None + hairpin_th: Optional[float] = None def __post_init__(self) -> None: super(Oligo, self).__post_init__() diff --git a/prymer/api/primer_pair.py b/prymer/api/primer_pair.py index ccbff9e..6094e71 100644 --- a/prymer/api/primer_pair.py +++ b/prymer/api/primer_pair.py @@ -37,9 +37,9 @@ class methods to represent a primer pair. The primer pair is comprised of a lef Span(refname='chr1', start=21, end=100, strand=) >>> list(primer_pair) -[Oligo(name=None, tm=70.0, penalty=-123.0, span=Span(refname='chr1', start=1, end=20, strand=), bases='GGGGGGGGGGGGGGGGGGGG', tail=None), Oligo(name=None, tm=70.0, penalty=-123.0, span=Span(refname='chr1', start=101, end=120, strand=), bases='TTTTTTTTTTTTTTTTTTTT', tail=None)] +[Oligo(name=None, tm=70.0, penalty=-123.0, span=Span(refname='chr1', start=1, end=20, strand=), bases='GGGGGGGGGGGGGGGGGGGG', tail=None, self_any_th=None, self_end_th=None, hairpin_th=None), Oligo(name=None, tm=70.0, penalty=-123.0, span=Span(refname='chr1', start=101, end=120, strand=), bases='TTTTTTTTTTTTTTTTTTTT', tail=None, self_any_th=None, self_end_th=None, hairpin_th=None)] + -``` """ # noqa: E501 from dataclasses import dataclass diff --git a/prymer/primer3/primer3.py b/prymer/primer3/primer3.py index 5addb1b..5493174 100644 --- a/prymer/primer3/primer3.py +++ b/prymer/primer3/primer3.py @@ -530,13 +530,27 @@ def _build_oligos( bases = unmasked_design_seq[slice_offset:slice_end] if span.strand == Strand.NEGATIVE: bases = reverse_complement(bases) - + # assemble Primer3-emitted results into `Oligo` objects + # if thermodynamic melting temperatures are missing, default to None primers.append( Oligo( bases=bases, tm=float(design_results[f"PRIMER_{design_task.task_type}_{idx}_TM"]), penalty=float(design_results[f"PRIMER_{design_task.task_type}_{idx}_PENALTY"]), span=span, + self_any_th=float( + design_results.get( + f"PRIMER_{design_task.task_type}_{idx}_SELF_ANY_TH", None + ) + ), + self_end_th=float( + design_results.get( + f"PRIMER_{design_task.task_type}_{idx}_SELF_END_TH", None + ) + ), + hairpin_th=float( + design_results.get(f"PRIMER_{design_task.task_type}_{idx}_HAIRPIN_TH", None) + ), ) ) return primers