def test_grow_primers(self):
        temp_calculator = TemperatureConfig().create_calculator()
        prefix = "TTTTTTTTTTAAAAAC"
        overlap = "CGCC"
        suffix = "AAAAACTTTTTTTTTT"

        temp_thresh_fw = 25.0
        temp_thresh_rv = 25.0
        overlaps = [SSMPrimerSpec(len(prefix), len(overlap), len(overlap), 0)]

        mutations = [AminoMutation(len(prefix), "", "", "", 0)]

        sequence = prefix + overlap + suffix
        print(sequence)

        fw_computed, rv_computed = grow_primers(min(len(prefix),
                                                    len(suffix)), 0, sequence,
                                                mutations, overlaps,
                                                temp_thresh_fw, temp_thresh_rv,
                                                temp_calculator)

        expected_fw = SSMPrimerSpec(offset=len(prefix),
                                    length=13,
                                    three_end_size=10,
                                    three_end_temp=29.0)
        expected_rv = SSMPrimerSpec(offset=5,
                                    length=15,
                                    three_end_size=11,
                                    three_end_temp=28.0)

        self.assertEqual(expected_fw, fw_computed[0])
        self.assertEqual(expected_rv, rv_computed[0])
    def test_grow_reverse_offset(self):
        temp_calculator = TemperatureConfig().create_calculator()
        prefix = "TTTTTTTTTTAAAAAC"
        overlap = "CGCC"

        sequence = prefix + overlap
        print(sequence)

        rv_computed = grow_reverse_primer(
            30, 0, sequence, AminoMutation(len(prefix), "", "", "", 0),
            SSMPrimerSpec(len(prefix), len(overlap), 0, 0), 25.0,
            temp_calculator)

        expected_rv = SSMPrimerSpec(offset=5,
                                    length=15,
                                    three_end_size=11,
                                    three_end_temp=28.0)

        self.assertEqual(expected_rv, rv_computed)
    def test_grow_reverse_primer(self):
        temp_calculator = TemperatureConfig().create_calculator()

        max_primer_size = 12
        sequence = "AAACCCGGGTTTAAAGGGCCC"
        mutation = AminoMutation(10, "", "", "", 0)
        overlap = SSMPrimerSpec(10, 3, 0, 0)

        primer = grow_reverse_primer(max_primer_size, 0, sequence, mutation,
                                     overlap, 10, temp_calculator)

        self.assertEqual(primer.three_end_temp, temp_calculator("CCGGGT"))
        self.assertEqual(primer.three_end_size, 6)
        self.assertEqual(primer.offset, 4)
def grow_forward_primer(max_primer_size: int, min_three_end_size: int, sequence: str, mutation: AminoMutation,
                        overlap: SSMPrimerSpec, temp_threshold: float,
                        temp_calculator: TemperatureCalculator) -> SSMPrimerSpec:
    """
    Grows a forward primer from a given overlap, and returns the shortest primer which has
    3' Tm above the temperature threshold. The 3' is defined by the given mutation.
    """
    for length in range(overlap.length + 1, max_primer_size):
        mutation_end = mutation.position + mutation.length
        fw_three_end_size = (overlap.offset + length) - mutation_end
        fw_three_end_sequence = sequence[mutation_end:(mutation_end + fw_three_end_size)]
        fw_three_end_temp = temp_calculator(fw_three_end_sequence)

        if fw_three_end_temp > temp_threshold and fw_three_end_size >= min_three_end_size:
            break

    return SSMPrimerSpec(overlap.offset, length, fw_three_end_size, fw_three_end_temp)
def grow_reverse_primer(max_primer_size: int, min_three_end_size: int, sequence: str, mutation: AminoMutation,
                        overlap: SSMPrimerSpec, temp_threshold: float,
                        temp_calculator: TemperatureCalculator) -> SSMPrimerSpec:
    """
    Grows a reverse primer from a given overlap, and returns the shortest primer which has
    3' Tm above the temperature threshold. The 3' is defined by the given mutation.
    """
    overlap_end = overlap.offset + overlap.length
    min_offset = max(0, overlap_end - max_primer_size)

    for offset in reversed(range(min_offset, overlap.offset)):
        rw_three_end_sequence = sequence[offset:mutation.position]
        rw_three_end_temp = temp_calculator(rw_three_end_sequence)

        if rw_three_end_temp > temp_threshold and len(rw_three_end_sequence) >= min_three_end_size:
            break

    return SSMPrimerSpec(offset, overlap_end - offset, mutation.position - offset, rw_three_end_temp)
def find_best_overlaps(sequence: str, min_five_end_size: int, min_overlap_size: int, max_overlap_size: int,
                       mutations: List[AminoMutation], overlap_temp: float,
                       temp_calculator: TemperatureCalculator, half_temp_range: float) \
        -> List[SSMPrimerSpec]:
    """
    Takes a list of mutations and a target overlap temperature and finds one overlap at each site
    such that they are all close to the given overlap temperature.
    """
    results: List[SSMPrimerSpec] = []

    overlap_sizes = range(min_overlap_size, max_overlap_size - 1)

    for mutation in mutations:
        best_offset = None
        best_length = None
        best_tm = None

        for length in overlap_sizes:
            for offset in range((mutation.position + mutation.length) - length, mutation.position - 1):
                right_padding = (offset + length) <= (mutation.position + mutation.length + min_five_end_size)
                left_padding = (offset + min_five_end_size) >= mutation.position

                if right_padding or left_padding:
                    continue

                overlap = sequence[offset:(offset + length)]
                tm = temp_calculator(overlap)

                if (best_tm is None) or (tm < best_tm):
                    best_offset = offset
                    best_length = length
                    best_tm = tm

                if abs(best_tm - overlap_temp) < half_temp_range:
                    break

        if best_offset is None:
            raise RuntimeError("Primer parameters are too restrictive and resulted in no possible overlap." +
                               "Consider lowering min 5' size.")

        results.append(SSMPrimerSpec(best_offset, best_length, 0, best_tm))

    return results