Skip to content

primer3_failure_reason

Primer3FailureReason Class

This module contains a Primer3FailureReason class. Based on user-specified criteria, Primer3 will disqualify primer designs if the characteristics of the design are outside an allowable range of parameters.

Failure reason strings are documented in the Primer3 source code, accessible here (at the time of Primer3FailureReason implementation): https://github.com/bioinfo-ut/primer3_masker/blob/master/src/libprimer3.c#L5581-L5604

Example Primer3 failure reasons emitted

Primer3 shall return a comma-delimited list of failure explanations. They will have key PRIMER_LEFT_EXPLAIN for designing individual left primers, PRIMER_RIGHT_EXPLAIN for designing individual right primers, and PRIMER_PAIR_EXPLAIN for designing primer pairs.

For individual primers:

>>> failure_string = 'considered 160, too many Ns 20, low tm 127, ok 13'
>>> Primer3FailureReason.parse_failures(failure_string)
Counter({<Primer3FailureReason.LOW_TM: 'low tm'>: 127, <Primer3FailureReason.TOO_MANY_NS: 'too many Ns'>: 20})
>>> failure_string = 'considered 238, low tm 164, high tm 12, high hairpin stability 23, ok 39'
>>> Primer3FailureReason.parse_failures(failure_string)
Counter({<Primer3FailureReason.LOW_TM: 'low tm'>: 164, <Primer3FailureReason.HAIRPIN_STABILITY: 'high hairpin stability'>: 23, <Primer3FailureReason.HIGH_TM: 'high tm'>: 12})
>>> failure_string = 'considered 166, unacceptable product size 161, ok 5'
>>> Primer3FailureReason.parse_failures(failure_string)
Counter({<Primer3FailureReason.PRODUCT_SIZE: 'unacceptable product size'>: 161})

Classes

Primer3FailureReason

Bases: StrEnum

Enum to represent the various reasons Primer3 removes primers and primer pairs.

https://github.com/bioinfo-ut/primer3_masker/blob/

6a40c4c408dc02b95ac02391457cda760092291a/src/libprimer3.c#L5581-L5605

This also contains custom failure values that are not generated by Primer3 but are convenient to have so that post-processing failures can be tracked in the same way that Primer3 failures are. These include LONG_DINUC, SECONDARY_STRUCTURE, and OFF_TARGET_AMPLIFICATION.

Source code in prymer/primer3/primer3_failure_reason.py
@unique
class Primer3FailureReason(StrEnum):
    """
    Enum to represent the various reasons Primer3 removes primers and primer pairs.

    These are taken from: https://github.com/bioinfo-ut/primer3_masker/blob/
      6a40c4c408dc02b95ac02391457cda760092291a/src/libprimer3.c#L5581-L5605

    This also contains custom failure values that are not generated by Primer3 but are convenient
    to have so that post-processing failures can be tracked in the same way that Primer3 failures
    are.  These include `LONG_DINUC`, `SECONDARY_STRUCTURE`, and `OFF_TARGET_AMPLIFICATION`.
    """

    # Failure reasons emitted by Primer3
    GC_CONTENT = "GC content failed"
    GC_CLAMP = "GC clamp failed"
    HAIRPIN_STABILITY = "high hairpin stability"
    HIGH_TM = "high tm"
    LOW_TM = "low tm"
    LOWERCASE_MASKING = "lowercase masking of 3' end"
    LONG_POLY_X = "long poly-x seq"
    PRODUCT_SIZE = "unacceptable product size"
    TOO_MANY_NS = "too many Ns"
    HIGH_ANY_COMPLEMENTARITY = "high any compl"
    HIGH_END_COMPLEMENTARITY = "high end compl"
    # Failure reasons not emitted by Primer3 and beneficial to keep track of
    LONG_DINUC = "long dinucleotide run"
    SECONDARY_STRUCTURE = "undesirable secondary structure"
    OFF_TARGET_AMPLIFICATION = "amplifies off-target regions"

    @classmethod
    def from_reason(cls, str_reason: str) -> Optional["Primer3FailureReason"]:
        """Returns the first `Primer3FailureReason` with the given reason for failure.
        If no failure exists, return `None`."""
        reason: Optional[Primer3FailureReason] = None
        try:
            reason = cls(str_reason)
        except ValueError:
            pass
        return reason

    @staticmethod
    def parse_failures(
        *failures: str,
    ) -> Counter["Primer3FailureReason"]:
        """When Primer3 encounters failures, extracts the reasons why designs that were considered
         by Primer3 failed.

        Args:
            failures: list of strings, with each string an "explanation" emitted by Primer3 about
                why the design failed.  Each string may be a comma delimited of failures, or a
                single failure.

        Returns:
            a `Counter` of each `Primer3FailureReason` reason.
        """

        failure_regex = r"^ ?(.+) ([0-9]+)$"
        by_fail_count: Counter[Primer3FailureReason] = Counter()
        # parse all failure strings and merge together counts for the same kinds of failures
        for failure in failures:
            split_failures = [fail.strip() for fail in failure.split(",")]
            for item in split_failures:
                result = re.match(failure_regex, item)
                if result is None:
                    continue
                reason = result.group(1)
                count = int(result.group(2))
                if reason in ["ok", "considered"]:
                    continue
                std_reason = Primer3FailureReason.from_reason(reason)
                if std_reason is None:
                    logging.getLogger(__name__).debug(f"Unknown Primer3 failure reason: {reason}")
                by_fail_count[std_reason] += count

        return by_fail_count

Functions

from_reason classmethod
from_reason(
    str_reason: str,
) -> Optional[Primer3FailureReason]

Returns the first Primer3FailureReason with the given reason for failure. If no failure exists, return None.

Source code in prymer/primer3/primer3_failure_reason.py
@classmethod
def from_reason(cls, str_reason: str) -> Optional["Primer3FailureReason"]:
    """Returns the first `Primer3FailureReason` with the given reason for failure.
    If no failure exists, return `None`."""
    reason: Optional[Primer3FailureReason] = None
    try:
        reason = cls(str_reason)
    except ValueError:
        pass
    return reason
parse_failures staticmethod
parse_failures(
    *failures: str,
) -> Counter[Primer3FailureReason]

When Primer3 encounters failures, extracts the reasons why designs that were considered by Primer3 failed.

Parameters:

Name Type Description Default
failures str

list of strings, with each string an "explanation" emitted by Primer3 about why the design failed. Each string may be a comma delimited of failures, or a single failure.

()

Returns:

Type Description
Counter[Primer3FailureReason]

a Counter of each Primer3FailureReason reason.

Source code in prymer/primer3/primer3_failure_reason.py
@staticmethod
def parse_failures(
    *failures: str,
) -> Counter["Primer3FailureReason"]:
    """When Primer3 encounters failures, extracts the reasons why designs that were considered
     by Primer3 failed.

    Args:
        failures: list of strings, with each string an "explanation" emitted by Primer3 about
            why the design failed.  Each string may be a comma delimited of failures, or a
            single failure.

    Returns:
        a `Counter` of each `Primer3FailureReason` reason.
    """

    failure_regex = r"^ ?(.+) ([0-9]+)$"
    by_fail_count: Counter[Primer3FailureReason] = Counter()
    # parse all failure strings and merge together counts for the same kinds of failures
    for failure in failures:
        split_failures = [fail.strip() for fail in failure.split(",")]
        for item in split_failures:
            result = re.match(failure_regex, item)
            if result is None:
                continue
            reason = result.group(1)
            count = int(result.group(2))
            if reason in ["ok", "considered"]:
                continue
            std_reason = Primer3FailureReason.from_reason(reason)
            if std_reason is None:
                logging.getLogger(__name__).debug(f"Unknown Primer3 failure reason: {reason}")
            by_fail_count[std_reason] += count

    return by_fail_count