Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nh13 committed Jan 15, 2025
1 parent 4348446 commit b9f1c86
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 414 deletions.
4 changes: 2 additions & 2 deletions prymer/primer3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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 ProbeParameters
from prymer.primer3.primer3_task import DesignLeftPrimersTask
from prymer.primer3.primer3_task import DesignPrimerPairsTask
Expand All @@ -24,7 +24,7 @@
"DesignPrimerPairsTask",
"DesignRightPrimersTask",
"PickHybProbeOnly",
"PrimerAndAmpliconParameters",
"AmpliconParameters",
"ProbeParameters",
"ProbeWeights",
"PrimerAndAmpliconWeights",
Expand Down
52 changes: 28 additions & 24 deletions prymer/primer3/primer3.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,21 @@
```
The `design()` method on `Primer3` is used to design the primers given a
[`Primer3Input`][prymer.primer3.primer3_input.Primer3Input]. The latter includes all the
[`Primer3Parameters`][prymer.primer3.primer3_input.Primer3Parameters]. The latter includes all the
parameters and target region.
```python
>>> from prymer.primer3.primer3_parameters import PrimerAndAmpliconParameters
>>> from prymer.primer3.primer3_parameters import AmpliconParameters
>>> from prymer import MinOptMax
>>> target = Span(refname="chr1", start=201, end=250, strand=Strand.POSITIVE)
>>> params = PrimerAndAmpliconParameters( \
>>> params = AmpliconParameters( \
amplicon_sizes=MinOptMax(min=100, max=250, opt=200), \
amplicon_tms=MinOptMax(min=55.0, max=100.0, opt=70.0), \
primer_sizes=MinOptMax(min=29, max=31, opt=30), \
primer_tms=MinOptMax(min=63.0, max=67.0, opt=65.0), \
primer_gcs=MinOptMax(min=30.0, max=65.0, opt=45.0), \
)
>>> design_input = Primer3Input( \
>>> design_input = AmpliconParameters( \
target=target, \
primer_and_amplicon_params=params, \
task=DesignLeftPrimersTask(), \
Expand Down Expand Up @@ -138,8 +138,9 @@
from prymer.model import PrimerPair
from prymer.model import Span
from prymer.model import Strand
from prymer.primer3 import AmpliconParameters, ProbeParameters
from prymer.primer3.primer3_failure_reason import Primer3FailureReason
from prymer.primer3.primer3_input import Primer3Input
from prymer.primer3.primer3_parameters import Primer3Parameters
from prymer.primer3.primer3_input_tag import Primer3InputTag
from prymer.primer3.primer3_task import DesignLeftPrimersTask
from prymer.primer3.primer3_task import DesignPrimerPairsTask
Expand Down Expand Up @@ -292,7 +293,7 @@ def get_design_sequences(self, region: Span) -> tuple[str, str]:

@staticmethod
def _screen_pair_results(
design_input: Primer3Input, designed_primer_pairs: list[PrimerPair]
design_input: AmpliconParameters, designed_primer_pairs: list[PrimerPair]
) -> tuple[list[PrimerPair], list[Oligo]]:
"""Screens primer pair designs emitted by Primer3 for dinucleotide run length.
Expand All @@ -310,21 +311,21 @@ def _screen_pair_results(
valid: bool = True
if (
primer_pair.left_primer.longest_dinucleotide_run_length
> design_input.primer_and_amplicon_params.primer_max_dinuc_bases
> design_input.primer_max_dinuc_bases
): # if the left primer has too many dinucleotide bases, fail it
dinuc_pair_failures.append(primer_pair.left_primer)
valid = False
if (
primer_pair.right_primer.longest_dinucleotide_run_length
> design_input.primer_and_amplicon_params.primer_max_dinuc_bases
> design_input.primer_max_dinuc_bases
): # if the right primer has too many dinucleotide bases, fail it
dinuc_pair_failures.append(primer_pair.right_primer)
valid = False
if valid: # if neither failed, append the pair to a list of valid designs
valid_primer_pair_designs.append(primer_pair)
return valid_primer_pair_designs, dinuc_pair_failures

def design(self, design_input: Primer3Input) -> Primer3Result: # noqa: C901
def design(self, design_input: Primer3Parameters) -> Primer3Result: # noqa: C901
"""Designs primers, primer pairs, and/or internal probes given a target region.
Args:
Expand All @@ -342,19 +343,21 @@ def design(self, design_input: Primer3Input) -> Primer3Result: # noqa: C901
design_region: Span
match design_input.task:
case PickHybProbeOnly():
if design_input.target.length < design_input.probe_params.probe_sizes.min:
input = typing.cast(ProbeParameters, design_input)
if input.target.length < input.probe_sizes.min:
raise ValueError(
"Target region required to be at least as large as the"
" minimal probe size: "
f"target length: {design_input.target.length}, "
f"minimal probe size: {design_input.probe_params.probe_sizes.min}"
f"target length: {input.target.length}, "
f"minimal probe size: {input.probe_sizes.min}"
)
design_region = design_input.target
case DesignRightPrimersTask() | DesignLeftPrimersTask() | DesignPrimerPairsTask():
input = typing.cast(AmpliconParameters, design_input)
design_region = self._create_design_region(
target_region=design_input.target,
max_amplicon_length=design_input.primer_and_amplicon_params.max_amplicon_length,
min_primer_length=design_input.primer_and_amplicon_params.min_primer_length,
target_region=input.target,
max_amplicon_length=input.max_amplicon_length,
min_primer_length=input.min_primer_length,
)
case _ as unreachable:
assert_never(unreachable) # pragma: no cover
Expand Down Expand Up @@ -403,14 +406,15 @@ def design(self, design_input: Primer3Input) -> Primer3Result: # noqa: C901

match design_input.task:
case DesignPrimerPairsTask(): # Primer pair design
input = typing.cast(AmpliconParameters, design_input)
all_pair_results: list[PrimerPair] = Primer3._build_primer_pairs(
design_input=design_input,
design_input=input,
design_results=primer3_results,
design_region=design_region,
unmasked_design_seq=soft_masked,
)
return Primer3._assemble_primer_pairs(
design_input=design_input,
design_input=input,
design_results=primer3_results,
unfiltered_designs=all_pair_results,
)
Expand All @@ -435,7 +439,7 @@ def design(self, design_input: Primer3Input) -> Primer3Result: # noqa: C901

@staticmethod
def _build_oligos(
design_input: Primer3Input,
design_input: Primer3Parameters,
design_results: dict[str, Any],
design_region: Span,
design_task: Union[DesignLeftPrimersTask, DesignRightPrimersTask, PickHybProbeOnly],
Expand Down Expand Up @@ -515,7 +519,7 @@ def _build_oligos(

@staticmethod
def _assemble_single_designs(
design_input: Primer3Input,
design_input: Primer3Parameters,
design_results: dict[str, str],
unfiltered_designs: list[Oligo],
) -> Primer3Result:
Expand All @@ -540,7 +544,7 @@ def _assemble_single_designs(

@staticmethod
def _build_primer_pairs(
design_input: Primer3Input,
design_input: Primer3Parameters,
design_results: dict[str, Any],
design_region: Span,
unmasked_design_seq: str,
Expand Down Expand Up @@ -603,7 +607,7 @@ def _build_primer_pair(num: int, primer_pair: tuple[Oligo, Oligo]) -> PrimerPair

@staticmethod
def _assemble_primer_pairs(
design_input: Primer3Input,
design_input: AmpliconParameters,
design_results: dict[str, Any],
unfiltered_designs: list[PrimerPair],
) -> Primer3Result:
Expand Down Expand Up @@ -718,7 +722,7 @@ def _create_design_region(
return design_region


def _check_design_results(design_input: Primer3Input, design_results: dict[str, str]) -> int:
def _check_design_results(design_input: Primer3Parameters, design_results: dict[str, str]) -> int:
"""Checks for any additional Primer3 errors and reports out the count of emitted designs."""
count_tag = design_input.task.count_tag
maybe_count: Optional[str] = design_results.get(count_tag)
Expand All @@ -733,7 +737,7 @@ def _check_design_results(design_input: Primer3Input, design_results: dict[str,
return count


def _has_acceptable_dinuc_run(design_input: Primer3Input, oligo_design: Oligo) -> bool:
def _has_acceptable_dinuc_run(design_input: Primer3Parameters, oligo_design: Oligo) -> bool:
"""
True if the design's longest dinucleotide run is no more than the stipulated maximum.
Expand All @@ -744,7 +748,7 @@ def _has_acceptable_dinuc_run(design_input: Primer3Input, oligo_design: Oligo) -
`ProbeParameters.probe_max_dinuc_bases`.
Args:
design_input: the Primer3Input object that wraps task-specific and design-specific params
design_input: the Primer3Parameters object that wraps task-specific and design-specific params
oligo_design: the design candidate
Returns:
Expand Down
183 changes: 0 additions & 183 deletions prymer/primer3/primer3_input.py

This file was deleted.

Loading

0 comments on commit b9f1c86

Please sign in to comment.