# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.


"""Tests for the PairwiseAligner in Bio.Align."""

import array
import os
import random
import subprocess
import sys
import threading
import unittest
from concurrent.futures import ThreadPoolExecutor

try:
    import numpy as np
except ImportError:
    from Bio import MissingPythonDependencyError

    raise MissingPythonDependencyError(
        "Install numpy if you want to use Bio.Align."
    ) from None

from Bio import BiopythonDeprecationWarning
from Bio import BiopythonWarning
from Bio import Align
from Bio.Align.substitution_matrices import Array
from Bio import SeqIO
from Bio.Seq import reverse_complement
from Bio.Seq import Seq
from Bio.SeqRecord import SeqRecord


class TestAlignerProperties(unittest.TestCase):
    def test_aligner_property_epsilon(self):
        aligner = Align.PairwiseAligner()
        self.assertAlmostEqual(aligner.epsilon, 1.0e-6)
        aligner.epsilon = 1.0e-4
        self.assertAlmostEqual(aligner.epsilon, 1.0e-4)
        aligner.epsilon = 1.0e-8
        self.assertAlmostEqual(aligner.epsilon, 1.0e-8, places=8)
        with self.assertRaises(TypeError):
            aligner.epsilon = "not a number"
        with self.assertRaises(TypeError):
            aligner.epsilon = None

    def test_aligner_property_mode(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        self.assertEqual(aligner.mode, "global")
        aligner.mode = "local"
        self.assertEqual(aligner.mode, "local")
        with self.assertRaises(ValueError):
            aligner.mode = "wrong"

    def test_aligner_property_match_mismatch(self):
        aligner = Align.PairwiseAligner()
        aligner.match_score = 3.0
        self.assertAlmostEqual(aligner.match_score, 3.0)
        aligner.mismatch_score = -2.0
        self.assertAlmostEqual(aligner.mismatch_score, -2.0)
        with self.assertRaises(ValueError):
            aligner.match_score = "not a number"
        with self.assertRaises(ValueError):
            aligner.mismatch_score = "not a number"
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 3.000000
  mismatch_score: -2.000000
  open_internal_insertion_score: -1.000000
  extend_internal_insertion_score: -1.000000
  open_left_insertion_score: -1.000000
  extend_left_insertion_score: -1.000000
  open_right_insertion_score: -1.000000
  extend_right_insertion_score: -1.000000
  open_internal_deletion_score: -1.000000
  extend_internal_deletion_score: -1.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: -1.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: -1.000000
  mode: global
""",
        )

    def test_aligner_property_gapscores(self):
        aligner = Align.PairwiseAligner()
        open_score, extend_score = (-5, -1)
        aligner.open_insertion_score = open_score
        aligner.extend_insertion_score = extend_score
        self.assertAlmostEqual(aligner.open_insertion_score, open_score)
        self.assertAlmostEqual(aligner.extend_insertion_score, extend_score)
        open_score, extend_score = (-6, -7)
        aligner.open_deletion_score = open_score
        aligner.extend_deletion_score = extend_score
        self.assertAlmostEqual(aligner.open_deletion_score, open_score)
        self.assertAlmostEqual(aligner.extend_deletion_score, extend_score)
        open_score, extend_score = (-3, -9)
        aligner.open_end_insertion_score = open_score
        aligner.extend_end_insertion_score = extend_score
        self.assertAlmostEqual(aligner.open_end_insertion_score, open_score)
        self.assertAlmostEqual(aligner.extend_end_insertion_score, extend_score)
        open_score, extend_score = (-1, -2)
        aligner.open_end_deletion_score = open_score
        aligner.extend_end_deletion_score = extend_score
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -5.000000
  extend_internal_insertion_score: -1.000000
  open_left_insertion_score: -3.000000
  extend_left_insertion_score: -9.000000
  open_right_insertion_score: -3.000000
  extend_right_insertion_score: -9.000000
  open_internal_deletion_score: -6.000000
  extend_internal_deletion_score: -7.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: -2.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: -2.000000
  mode: global
""",
        )
        self.assertAlmostEqual(aligner.open_end_deletion_score, open_score)
        self.assertAlmostEqual(aligner.extend_end_deletion_score, extend_score)
        score = -3
        aligner.insertion_score = score
        self.assertAlmostEqual(aligner.insertion_score, score)
        self.assertAlmostEqual(aligner.open_insertion_score, score)
        self.assertAlmostEqual(aligner.extend_insertion_score, score)
        score = -2
        aligner.deletion_score = score
        self.assertAlmostEqual(aligner.deletion_score, score)
        self.assertAlmostEqual(aligner.open_deletion_score, score)
        self.assertAlmostEqual(aligner.extend_deletion_score, score)
        score = -4
        aligner.end_insertion_score = score
        self.assertAlmostEqual(aligner.end_insertion_score, score)
        self.assertAlmostEqual(aligner.open_end_insertion_score, score)
        self.assertAlmostEqual(aligner.extend_end_insertion_score, score)
        self.assertAlmostEqual(aligner.left_insertion_score, score)
        self.assertAlmostEqual(aligner.open_left_insertion_score, score)
        self.assertAlmostEqual(aligner.extend_left_insertion_score, score)
        self.assertAlmostEqual(aligner.right_insertion_score, score)
        self.assertAlmostEqual(aligner.open_right_insertion_score, score)
        self.assertAlmostEqual(aligner.extend_right_insertion_score, score)
        score = -5
        aligner.end_deletion_score = score
        self.assertAlmostEqual(aligner.end_deletion_score, score)
        self.assertAlmostEqual(aligner.open_end_deletion_score, score)
        self.assertAlmostEqual(aligner.extend_end_deletion_score, score)
        self.assertAlmostEqual(aligner.left_deletion_score, score)
        self.assertAlmostEqual(aligner.open_left_deletion_score, score)
        self.assertAlmostEqual(aligner.extend_left_deletion_score, score)
        self.assertAlmostEqual(aligner.right_deletion_score, score)
        self.assertAlmostEqual(aligner.open_right_deletion_score, score)
        self.assertAlmostEqual(aligner.extend_right_deletion_score, score)
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -3.000000
  extend_internal_insertion_score: -3.000000
  open_left_insertion_score: -4.000000
  extend_left_insertion_score: -4.000000
  open_right_insertion_score: -4.000000
  extend_right_insertion_score: -4.000000
  open_internal_deletion_score: -2.000000
  extend_internal_deletion_score: -2.000000
  open_left_deletion_score: -5.000000
  extend_left_deletion_score: -5.000000
  open_right_deletion_score: -5.000000
  extend_right_deletion_score: -5.000000
  mode: global
""",
        )
        with self.assertRaises(ValueError):
            aligner.insertion_score = "wrong"
        with self.assertRaises(ValueError):
            aligner.deletion_score = "wrong"
        with self.assertRaises(TypeError):
            aligner.end_insertion_score = "wrong"
        with self.assertRaises(TypeError):
            aligner.end_deletion_score = "wrong"

    def test_aligner_property_gapscores_deprecated(self):
        aligner = Align.PairwiseAligner()
        value = 1
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_left_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_left_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 2
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_left_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_left_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 3
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.left_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.left_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 4
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_internal_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_internal_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 5
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_internal_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_internal_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 6
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.internal_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.internal_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 7
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_right_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_right_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 8
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_right_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_right_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 9
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.right_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.right_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 10
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_end_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_end_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 11
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_end_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_end_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 12
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.end_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.end_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 13
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 14
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_open_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 15
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_left_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_left_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 16
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_left_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_left_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 17
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.left_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.left_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 18
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_internal_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_internal_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 19
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_internal_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_internal_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 20
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.internal_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.internal_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 21
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_right_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_right_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 22
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_right_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_right_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 23
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.right_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.right_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 24
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_end_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_end_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 25
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_end_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_end_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 26
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.end_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.end_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 27
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 28
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_extend_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 29
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_left_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_left_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 30
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_left_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_left_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 31
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_internal_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_internal_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 32
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_internal_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_internal_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 33
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_right_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_right_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 34
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_right_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_right_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 35
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_end_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_end_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 36
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_end_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_end_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 37
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 38
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 39
        aligner.open_left_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_left_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 40
        aligner.open_left_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_left_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 41
        aligner.open_left_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.left_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 42
        aligner.open_internal_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_internal_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 43
        aligner.open_internal_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_internal_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 44
        aligner.open_internal_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.internal_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 45
        aligner.open_right_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_right_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 46
        aligner.open_right_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_right_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 47
        aligner.open_right_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.right_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 48
        aligner.open_end_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_end_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 49
        aligner.open_end_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_end_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 50
        aligner.open_end_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.end_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 51
        aligner.open_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 52
        aligner.open_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_open_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 53
        aligner.extend_left_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_left_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 54
        aligner.extend_left_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_left_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 55
        aligner.extend_left_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.left_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 56
        aligner.extend_internal_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_internal_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 57
        aligner.extend_internal_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_internal_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 58
        aligner.extend_internal_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.internal_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 59
        aligner.extend_right_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_right_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 60
        aligner.extend_right_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_right_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 61
        aligner.extend_right_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.right_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 62
        aligner.extend_end_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_end_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 63
        aligner.extend_end_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_end_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 64
        aligner.extend_end_gap_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.end_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 65
        aligner.extend_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 66
        aligner.extend_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_extend_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 67
        aligner.left_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_left_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 68
        aligner.left_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_left_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 69
        aligner.internal_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_internal_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 70
        aligner.internal_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_internal_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 71
        aligner.right_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_right_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 72
        aligner.right_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_right_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 73
        aligner.end_insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_end_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 74
        aligner.end_deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_end_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 75
        aligner.insertion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.target_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 76
        aligner.deletion_score = value
        with self.assertWarns(BiopythonDeprecationWarning):
            stored_value = aligner.query_gap_score
        self.assertAlmostEqual(stored_value, value)
        value = 77
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_left_open_gap_score = value
        self.assertAlmostEqual(aligner.open_left_insertion_score, value)
        value = 78
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_left_open_gap_score = value
        self.assertAlmostEqual(aligner.open_left_deletion_score, value)
        value = 79
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.left_open_gap_score = value
        self.assertAlmostEqual(aligner.open_left_gap_score, value)
        value = 80
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_internal_open_gap_score = value
        self.assertAlmostEqual(aligner.open_internal_insertion_score, value)
        value = 81
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_internal_open_gap_score = value
        self.assertAlmostEqual(aligner.open_internal_deletion_score, value)
        value = 82
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.internal_open_gap_score = value
        self.assertAlmostEqual(aligner.open_internal_gap_score, value)
        value = 83
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_right_open_gap_score = value
        self.assertAlmostEqual(aligner.open_right_insertion_score, value)
        value = 84
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_right_open_gap_score = value
        self.assertAlmostEqual(aligner.open_right_deletion_score, value)
        value = 85
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.right_open_gap_score = value
        self.assertAlmostEqual(aligner.open_right_gap_score, value)
        value = 86
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_end_open_gap_score = value
        self.assertAlmostEqual(aligner.open_end_insertion_score, value)
        value = 87
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_end_open_gap_score = value
        self.assertAlmostEqual(aligner.open_end_deletion_score, value)
        value = 88
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.end_open_gap_score = value
        self.assertAlmostEqual(aligner.open_end_gap_score, value)
        value = 89
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_open_gap_score = value
        self.assertAlmostEqual(aligner.open_insertion_score, value)
        value = 90
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_open_gap_score = value
        self.assertAlmostEqual(aligner.open_deletion_score, value)
        value = 91
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_left_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_left_insertion_score, value)
        value = 92
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_left_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_left_deletion_score, value)
        value = 93
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.left_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_left_gap_score, value)
        value = 94
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_internal_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_internal_insertion_score, value)
        value = 95
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_internal_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_internal_deletion_score, value)
        value = 96
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.internal_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_internal_gap_score, value)
        value = 97
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_right_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_right_insertion_score, value)
        value = 98
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_right_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_right_deletion_score, value)
        value = 99
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.right_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_right_gap_score, value)
        value = 100
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_end_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_end_insertion_score, value)
        value = 101
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_end_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_end_deletion_score, value)
        value = 102
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.end_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_end_gap_score, value)
        value = 103
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_insertion_score, value)
        value = 104
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_extend_gap_score = value
        self.assertAlmostEqual(aligner.extend_deletion_score, value)
        value = 105
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_left_gap_score = value
        self.assertAlmostEqual(aligner.left_insertion_score, value)
        value = 106
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_left_gap_score = value
        self.assertAlmostEqual(aligner.left_deletion_score, value)
        value = 107
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_internal_gap_score = value
        self.assertAlmostEqual(aligner.internal_insertion_score, value)
        value = 108
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_internal_gap_score = value
        self.assertAlmostEqual(aligner.internal_deletion_score, value)
        value = 109
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_right_gap_score = value
        self.assertAlmostEqual(aligner.right_insertion_score, value)
        value = 110
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_right_gap_score = value
        self.assertAlmostEqual(aligner.right_deletion_score, value)
        value = 111
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_end_gap_score = value
        self.assertAlmostEqual(aligner.end_insertion_score, value)
        value = 112
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_end_gap_score = value
        self.assertAlmostEqual(aligner.end_deletion_score, value)
        value = 113
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_gap_score = value
        self.assertAlmostEqual(aligner.insertion_score, value)
        value = 114
        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_gap_score = value
        self.assertAlmostEqual(aligner.deletion_score, value)

        def gap_function1(x, y):
            return x + y

        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_gap_score = gap_function1
        gap_function = aligner.deletion_score
        self.assertEqual(gap_function, gap_function1)

        def gap_function2(x, y):
            return x * y

        aligner.deletion_score = gap_function2
        self.assertEqual(aligner.deletion_score, gap_function2)

        def gap_function3(x, y):
            return x / y

        aligner.deletion_score = gap_function3
        with self.assertWarns(BiopythonDeprecationWarning):
            gap_function = aligner.query_gap_score
        self.assertEqual(gap_function, gap_function3)

        def gap_function4(x, y):
            return x / y - 9

        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.query_gap_score = gap_function4
        with self.assertWarns(BiopythonDeprecationWarning):
            gap_function = aligner.query_gap_score
        self.assertEqual(gap_function, gap_function4)

        def gap_function5(x, y):
            return x + 2 * y

        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_gap_score = gap_function5
        gap_function = aligner.insertion_score
        self.assertEqual(gap_function, gap_function5)

        def gap_function6(x, y):
            return x * y + 2

        aligner.insertion_score = gap_function6
        self.assertEqual(aligner.insertion_score, gap_function6)

        def gap_function7(x, y):
            return x / y - 2

        aligner.insertion_score = gap_function7
        with self.assertWarns(BiopythonDeprecationWarning):
            gap_function = aligner.target_gap_score
        self.assertEqual(gap_function, gap_function7)

        def gap_function8(x, y):
            return x / y * 2

        with self.assertWarns(BiopythonDeprecationWarning):
            aligner.target_gap_score = gap_function8
        with self.assertWarns(BiopythonDeprecationWarning):
            gap_function = aligner.target_gap_score
        self.assertEqual(gap_function, gap_function8)

        def gap_function9(x, y):
            return x + 9 * y

        aligner.gap_score = gap_function9
        gap_function = aligner.gap_score
        self.assertEqual(gap_function, gap_function9)

    def test_aligner_nonexisting_property(self):
        aligner = Align.PairwiseAligner()
        with self.assertRaises(AttributeError) as cm:
            aligner.no_such_property
        self.assertEqual(
            str(cm.exception),
            "'PairwiseAligner' object has no attribute 'no_such_property'",
        )
        with self.assertRaises(AttributeError) as cm:
            aligner.no_such_property = 1
        self.assertEqual(
            str(cm.exception),
            "'PairwiseAligner' object has no attribute 'no_such_property'",
        )


class TestPairwiseGlobal(unittest.TestCase):
    def test_needlemanwunsch_simple1(self):
        seq1 = "GAACT"
        seq2 = "GAT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -1.000000
  extend_internal_insertion_score: -1.000000
  open_left_insertion_score: -1.000000
  extend_left_insertion_score: -1.000000
  open_right_insertion_score: -1.000000
  extend_right_insertion_score: -1.000000
  open_internal_deletion_score: -1.000000
  extend_internal_deletion_score: -1.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: -1.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: -1.000000
  mode: global
""",
        )
        self.assertEqual(aligner.algorithm, "Needleman-Wunsch")
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.0)
        score = aligner.score(seq1, reverse_complement(seq2), "-")
        self.assertAlmostEqual(score, 1.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             0 GA--T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 |-|-| 5
query             0 G-A-T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 1], [2, 3], [4, 5]], [[0, 1], [1, 2], [2, 3]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             3 GA--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[3, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 |-|-| 5
query             3 G-A-T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 1], [2, 3], [4, 5]], [[3, 2], [2, 1], [1, 0]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)

    def test_align_affine1_score(self):
        seq1 = "CC"
        seq2 = "ACCT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 0
        aligner.mismatch_score = -1
        aligner.open_gap_score = -5
        aligner.extend_gap_score = -1
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 0.000000
  mismatch_score: -1.000000
  open_internal_insertion_score: -5.000000
  extend_internal_insertion_score: -1.000000
  open_left_insertion_score: -5.000000
  extend_left_insertion_score: -1.000000
  open_right_insertion_score: -5.000000
  extend_right_insertion_score: -1.000000
  open_internal_deletion_score: -5.000000
  extend_internal_deletion_score: -1.000000
  open_left_deletion_score: -5.000000
  extend_left_deletion_score: -1.000000
  open_right_deletion_score: -5.000000
  extend_right_deletion_score: -1.000000
  mode: global
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, -7.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, -7.0)

    def test_fogsaa_simple1(self):
        seq1 = "GAACT"
        seq2 = "GAT"
        aligner = Align.PairwiseAligner(mode="fogsaa")
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.0)
        score = aligner.score(seq1, reverse_complement(seq2), "-")
        self.assertAlmostEqual(score, 1.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             0 GA--T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)

        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             3 GA--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[3, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)

    def test_fogsaa_affine1(self):
        seq1 = "CC"
        seq2 = "ACCT"
        aligner = Align.PairwiseAligner(mode="fogsaa")
        aligner.match_score = 0
        aligner.mismatch_score = -1
        aligner.open_gap_score = -5
        aligner.extend_gap_score = -1
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, -7.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, -7.0)

    def test_fogsaa_confirms_needleman_wunsch(self):
        seq1 = "CCCCC"
        seq2 = "ACCCCCT"

        aligner_fogsaa = Align.PairwiseAligner(mode="fogsaa")
        aligner_fogsaa.match_score = 1.1
        aligner_fogsaa.mismatch_score = -1.83
        aligner_fogsaa.gap_score = -2
        self.assertEqual(
            aligner_fogsaa.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )

        aligner_nw = Align.PairwiseAligner(mode="global")
        aligner_nw.match_score = 1.1
        aligner_nw.mismatch_score = -1.83
        aligner_nw.gap_score = -2
        self.assertEqual(aligner_nw.algorithm, "Needleman-Wunsch")

        score_fogsaa = aligner_fogsaa.score(seq1, seq2)
        score_nw = aligner_nw.score(seq1, seq2)
        self.assertAlmostEqual(score_fogsaa, score_nw)

    def test_fogsaa_matrix_scoring(self):
        seq1 = "AAAAAAAAAAA"
        seq2 = "AAAAAAATAAA"
        aligner = Align.PairwiseAligner(mode="fogsaa", scoring="blastn")
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        with self.assertWarns(BiopythonWarning):
            score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 17.0)


class TestPairwiseLocal(unittest.TestCase):
    def test_smithwaterman(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.gap_score = -0.1
        self.assertEqual(aligner.algorithm, "Smith-Waterman")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.100000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.100000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.100000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: -0.100000
  mode: local
""",
        )
        score = aligner.score("AwBw", "zABz")
        self.assertAlmostEqual(score, 1.9)
        alignments = aligner.align("AwBw", "zABz")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 AwB 3
                  0 |-| 3
query             1 A-B 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[1, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)

    def test_gotoh_local(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.open_gap_score = -0.1
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.100000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.100000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.100000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: 0.000000
  mode: local
""",
        )
        score = aligner.score("AwBw", "zABz")
        self.assertAlmostEqual(score, 1.9)
        alignments = aligner.align("AwBw", "zABz")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 AwB 3
                  0 |-| 3
query             1 A-B 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[1, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)


class TestUnknownCharacter(unittest.TestCase):
    def test_needlemanwunsch_simple1(self):
        seq1 = "GACT"
        seq2 = "GA?T"
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.gap_score = -1.0
        aligner.mismatch_score = -1.0
        aligner.wildcard = "?"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 ||.| 4
query             0 GA?T 4
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 4]], [[0, 4]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 ||.| 4
query             4 GA?T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 4]], [[4, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        seq2 = "GAXT"
        aligner.wildcard = "X"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 ||.| 4
query             0 GAXT 4
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 4]], [[0, 4]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts("X")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 ||.| 4
query             4 GAXT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 4]], [[4, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts("X")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        aligner.wildcard = None
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 ||.| 4
query             0 GAXT 4
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 4]], [[0, 4]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts("X")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 2.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 ||.| 4
query             4 GAXT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 4]], [[4, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts("X")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 4 aligned letters; 3 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 2.0)

    def test_needlemanwunsch_simple2(self):
        seq1 = "GA?AT"
        seq2 = "GAA?T"
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.wildcard = "?"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA?AT 5
                  0 ||..| 5
query             0 GAA?T 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 5]], [[0, 5]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA?AT 5
                  0 ||..| 5
query             5 GAA?T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 5]], [[5, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        seq1 = "GAXAT"
        seq2 = "GAAXT"
        aligner.wildcard = "X"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAXAT 5
                  0 ||..| 5
query             0 GAAXT 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 5]], [[0, 5]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAXAT 5
                  0 ||..| 5
query             5 GAAXT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 5]], [[5, 0]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_fogsaa_simple2(self):
        seq1 = "GA?AT"
        seq2 = "GAA?T"
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.wildcard = "?"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA?AT 5
                  0 ||..| 5
query             0 GAA?T 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 5]], [[0, 5]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA?AT 5
                  0 ||..| 5
query             5 GAA?T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 5]], [[5, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("?")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        seq1 = "GAXAT"
        seq2 = "GAAXT"
        aligner.wildcard = "X"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAXAT 5
                  0 ||..| 5
query             0 GAAXT 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 5]], [[0, 5]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("X")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAXAT 5
                  0 ||..| 5
query             5 GAAXT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 5]], [[5, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts("X")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 5 aligned letters; 3 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 3,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)


class TestPairwiseOpenPenalty(unittest.TestCase):
    def test_match_score_open_penalty1(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 2
        aligner.mismatch_score = -1
        aligner.open_gap_score = -0.1
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 2.000000
  mismatch_score: -1.000000
  open_internal_insertion_score: -0.100000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.100000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.100000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: 0.000000
  mode: global
""",
        )
        seq1 = "AA"
        seq2 = "A"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.9)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.9)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 AA 2
                  0 -| 2
query             0 -A 1
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[1, 2]], [[0, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.9)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 AA 2
                  0 |- 2
query             0 A- 1
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 1]], [[0, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.9)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 AA 2
                  0 -| 2
query             1 -A 0
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[1, 2]], [[1, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.9)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 AA 2
                  0 |- 2
query             1 A- 0
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 1]], [[1, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 1 aligned letters; 1 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.9)

    def test_match_score_open_penalty2(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 1.5
        aligner.mismatch_score = 0.0
        aligner.open_gap_score = -0.1
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.500000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.100000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.100000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.100000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: 0.000000
  mode: global
""",
        )
        seq1 = "GAA"
        seq2 = "GA"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.9)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.9)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=2.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAA 3
                  0 |-| 3
query             0 G-A 2
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAA 3
                  0 ||- 3
query             0 GA- 2
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[0, 2]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=2.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAA 3
                  0 |-| 3
query             2 G-A 0
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAA 3
                  0 ||- 3
query             2 GA- 0
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[2, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)

    def test_match_score_open_penalty3(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_deletion_score = -0.1
        aligner.extend_deletion_score = 0.0
        aligner.insertion_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: 0.000000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: 0.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: 0.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: 0.000000
  mode: global
""",
        )
        seq1 = "GAACT"
        seq2 = "GAT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.9)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.9)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             0 GA--T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             3 GA--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[3, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)

    def test_match_score_open_penalty3_fogsaa(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.open_deletion_score = -0.1
        aligner.extend_deletion_score = 0.0
        aligner.insertion_score = 0.0
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: 0.000000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: 0.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: 0.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: 0.000000
  mode: fogsaa
""",
        )
        seq1 = "GAACT"
        seq2 = "GAT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.9)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.9)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             0 GA--T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAACT 5
                  0 ||--| 5
query             3 GA--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[3, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.9; substitution score = 3.0; gap score = -0.1; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.9:
        substitution_score = 3.0,
        gap_score = -0.1.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.9)

    def test_match_score_open_penalty4(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.mismatch_score = -2.0
        aligner.open_gap_score = -0.1
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -2.000000
  open_internal_insertion_score: -0.100000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.100000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.100000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: 0.000000
  mode: global
""",
        )
        seq1 = "GCT"
        seq2 = "GATA"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.7)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.7)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1.7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 G-CT- 3
                  0 |--|- 5
query             0 GA-TA 4
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[0, 1], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.30000000000000004; 2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.30000000000000004.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.7)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 GC-T- 3
                  0 |--|- 5
query             0 G-ATA 4
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[0, 1], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.30000000000000004; 2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.30000000000000004.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.7)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1.7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 G-CT- 3
                  0 |--|- 5
query             4 GA-TA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[4, 3], [2, 1]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.30000000000000004; 2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.30000000000000004.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.7)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 GC-T- 3
                  0 |--|- 5
query             4 G-ATA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[4, 3], [2, 1]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.30000000000000004; 2 aligned letters; 2 identities; 0 mismatches; 3 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.30000000000000004.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 3:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 1:
                open_right_insertions = 1,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.7)


class TestPairwiseExtendPenalty(unittest.TestCase):
    def test_extend_penalty1(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_gap_score = -0.2
        aligner.extend_gap_score = -0.5
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.200000
  extend_internal_insertion_score: -0.500000
  open_left_insertion_score: -0.200000
  extend_left_insertion_score: -0.500000
  open_right_insertion_score: -0.200000
  extend_right_insertion_score: -0.500000
  open_internal_deletion_score: -0.200000
  extend_internal_deletion_score: -0.500000
  open_left_deletion_score: -0.200000
  extend_left_deletion_score: -0.500000
  open_right_deletion_score: -0.200000
  extend_right_deletion_score: -0.500000
  mode: global
""",
        )
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.3)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.3)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.3)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             0 G--T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.3; substitution score = 2.0; gap score = -0.7; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.3:
        substitution_score = 2.0,
        gap_score = -0.7.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.3)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.3)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             2 G--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.3; substitution score = 2.0; gap score = -0.7; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.3:
        substitution_score = 2.0,
        gap_score = -0.7.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.3)

    def test_extend_penalty2(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_gap_score = -0.2
        aligner.extend_gap_score = -1.5
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.200000
  extend_internal_insertion_score: -1.500000
  open_left_insertion_score: -0.200000
  extend_left_insertion_score: -1.500000
  open_right_insertion_score: -0.200000
  extend_right_insertion_score: -1.500000
  open_internal_deletion_score: -0.200000
  extend_internal_deletion_score: -1.500000
  open_left_deletion_score: -0.200000
  extend_left_deletion_score: -1.500000
  open_right_deletion_score: -0.200000
  extend_right_deletion_score: -1.500000
  mode: global
""",
        )
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 0.6)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 0.6)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=0.6) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 0.6)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 -.-| 4
query             0 -G-T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[1, 2], [3, 4]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.6; substitution score = 1.0; gap score = -0.4; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.6:
        substitution_score = 1.0,
        gap_score = -0.4.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 0.6)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 0.6)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |-.- 4
query             0 G-T- 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.6; substitution score = 1.0; gap score = -0.4; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.6:
        substitution_score = 1.0,
        gap_score = -0.4.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 0.6)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=0.6) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 0.6)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 -.-| 4
query             2 -G-T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[1, 2], [3, 4]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.6; substitution score = 1.0; gap score = -0.4; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.6:
        substitution_score = 1.0,
        gap_score = -0.4.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 0.6)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 0.6)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |-.- 4
query             2 G-T- 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.6; substitution score = 1.0; gap score = -0.4; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.6:
        substitution_score = 1.0,
        gap_score = -0.4.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 0.6)

    def test_extend_penalty2_fogsaa(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.open_gap_score = -0.2
        aligner.extend_gap_score = -1.5
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.200000
  extend_internal_insertion_score: -1.500000
  open_left_insertion_score: -0.200000
  extend_left_insertion_score: -1.500000
  open_right_insertion_score: -0.200000
  extend_right_insertion_score: -1.500000
  open_internal_deletion_score: -0.200000
  extend_internal_deletion_score: -1.500000
  open_left_deletion_score: -0.200000
  extend_left_deletion_score: -1.500000
  open_right_deletion_score: -0.200000
  extend_right_deletion_score: -1.500000
  mode: fogsaa
""",
        )
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 0.6)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 0.6)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=0.6) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 0.6)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 -.-| 4
query             0 -G-T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[1, 2], [3, 4]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.6; substitution score = 1.0; gap score = -0.4; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.6:
        substitution_score = 1.0,
        gap_score = -0.4.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 0.6)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=0.6) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 0.6)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 -.-| 4
query             2 -G-T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[1, 2], [3, 4]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.6; substitution score = 1.0; gap score = -0.4; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.6:
        substitution_score = 1.0,
        gap_score = -0.4.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 0.6)


class TestPairwisePenalizeExtendWhenOpening(unittest.TestCase):
    def test_penalize_extend_when_opening(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_gap_score = -1.7
        aligner.extend_gap_score = -1.5
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -1.700000
  extend_internal_insertion_score: -1.500000
  open_left_insertion_score: -1.700000
  extend_left_insertion_score: -1.500000
  open_right_insertion_score: -1.700000
  extend_right_insertion_score: -1.500000
  open_internal_deletion_score: -1.700000
  extend_internal_deletion_score: -1.500000
  open_left_deletion_score: -1.700000
  extend_left_deletion_score: -1.500000
  open_right_deletion_score: -1.700000
  extend_right_deletion_score: -1.500000
  mode: global
""",
        )
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, -1.2)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, -1.2)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-1.2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -1.2)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             0 G--T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -1.2000000000000002; substitution score = 2.0; gap score = -3.2; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -1.2000000000000002:
        substitution_score = 2.0,
        gap_score = -3.2.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -1.2)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-1.2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -1.2)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             2 G--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -1.2000000000000002; substitution score = 2.0; gap score = -3.2; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -1.2000000000000002:
        substitution_score = 2.0,
        gap_score = -3.2.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -1.2)

    def test_penalize_extend_when_opening_fogsaa(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.open_gap_score = -1.7
        aligner.extend_gap_score = -1.5
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -1.700000
  extend_internal_insertion_score: -1.500000
  open_left_insertion_score: -1.700000
  extend_left_insertion_score: -1.500000
  open_right_insertion_score: -1.700000
  extend_right_insertion_score: -1.500000
  open_internal_deletion_score: -1.700000
  extend_internal_deletion_score: -1.500000
  open_left_deletion_score: -1.700000
  extend_left_deletion_score: -1.500000
  open_right_deletion_score: -1.700000
  extend_right_deletion_score: -1.500000
  mode: fogsaa
""",
        )
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, -1.2)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, -1.2)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-1.2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -1.2)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             0 G--T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -1.2000000000000002; substitution score = 2.0; gap score = -3.2; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -1.2000000000000002:
        substitution_score = 2.0,
        gap_score = -3.2.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -1.2)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-1.2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -1.2)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             2 G--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -1.2000000000000002; substitution score = 2.0; gap score = -3.2; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -1.2000000000000002:
        substitution_score = 2.0,
        gap_score = -3.2.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -1.2)


class TestPairwisePenalizeEndgaps(unittest.TestCase):
    def test_penalize_end_gaps(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_gap_score = -0.2
        aligner.extend_gap_score = -0.8
        aligner.end_gap_score = 0.0
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.200000
  extend_internal_insertion_score: -0.800000
  open_left_insertion_score: 0.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: 0.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.200000
  extend_internal_deletion_score: -0.800000
  open_left_deletion_score: 0.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: 0.000000
  extend_right_deletion_score: 0.000000
  mode: global
""",
        )
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (3 alignments; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 3)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 --.| 4
query             0 --GT 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 4]], [[0, 2]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             0 G--T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 2.0; gap score = -1.0; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 2.0,
        gap_score = -1.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.0)
        alignment = alignments[2]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |.-- 4
query             0 GT-- 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[0, 2]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (3 alignments; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 3)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 --.| 4
query             2 --GT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 4]], [[2, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |--| 4
query             2 G--T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [3, 4]], [[2, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 2.0; gap score = -1.0; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 2.0,
        gap_score = -1.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.0)
        alignment = alignments[2]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |.-- 4
query             2 GT-- 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[2, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.0)

    def test_penalize_end_gaps_fogsaa(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.open_gap_score = -0.2
        aligner.extend_gap_score = -0.8
        aligner.end_gap_score = 0.0
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.200000
  extend_internal_insertion_score: -0.800000
  open_left_insertion_score: 0.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: 0.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.200000
  extend_internal_deletion_score: -0.800000
  open_left_deletion_score: 0.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: 0.000000
  extend_right_deletion_score: 0.000000
  mode: fogsaa
""",
        )
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        seq1 = "GACT"
        seq2 = "GT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.0)

        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |.-- 4
query             0 GT-- 2
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[0, 2]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.0)

        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 GACT 4
                  0 |.-- 4
query             2 GT-- 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[2, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.0)


class TestPairwiseSeparateGapPenalties(unittest.TestCase):
    def test_separate_gap_penalties1(self):
        seq1 = "GAT"
        seq2 = "GTCT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        open_score, extend_score = (-0.3, 0)
        aligner.open_insertion_score = open_score
        aligner.extend_insertion_score = extend_score
        open_score, extend_score = (-0.8, 0)
        aligner.open_deletion_score = open_score
        aligner.extend_deletion_score = extend_score
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.800000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.800000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.800000
  extend_right_deletion_score: 0.000000
  mode: local
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.7)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.7)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1.7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 G-AT 3
                  0 |-.| 4
query             0 GTCT 4
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [1, 3]], [[0, 1], [2, 4]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.3; 3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.3.
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.7)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA-T 3
                  0 |.-| 4
query             0 GTCT 4
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [2, 3]], [[0, 2], [3, 4]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.3; 3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.3.
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.7)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1.7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 G-AT 3
                  0 |-.| 4
query             4 GTCT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [1, 3]], [[4, 3], [2, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.3; 3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.3.
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.7)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.7)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA-T 3
                  0 |.-| 4
query             4 GTCT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [2, 3]], [[4, 2], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.7; substitution score = 2.0; gap score = -0.3; 3 aligned letters; 2 identities; 1 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.7:
        substitution_score = 2.0,
        gap_score = -0.3.
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 1.7)

    def test_separate_gap_penalties2(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.open_insertion_score = -0.3
        aligner.extend_insertion_score = 0.0
        aligner.open_deletion_score = -0.2
        aligner.extend_deletion_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.200000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.200000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.200000
  extend_right_deletion_score: 0.000000
  mode: local
""",
        )
        seq1 = "GAT"
        seq2 = "GTCT"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.8)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.8)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.8) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.8)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAT 3
                  0 |-| 3
query             0 G-T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[0, 1], [1, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.8; substitution score = 2.0; gap score = -0.2; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.8:
        substitution_score = 2.0,
        gap_score = -0.2.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.8)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.8) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.8)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAT 3
                  0 |-| 3
query             4 G-T 2
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[4, 3], [3, 2]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.8; substitution score = 2.0; gap score = -0.2; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.8:
        substitution_score = 2.0,
        gap_score = -0.2.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.8)


class TestPairwiseSeparateGapPenaltiesWithExtension(unittest.TestCase):
    def test_separate_gap_penalties_with_extension(self):
        seq1 = "GAAT"
        seq2 = "GTCCT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        open_score, extend_score = (-0.1, 0)
        aligner.open_insertion_score = open_score
        aligner.extend_insertion_score = extend_score
        score = -0.1
        aligner.deletion_score = score
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.100000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.100000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.100000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.100000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.100000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.100000
  extend_right_deletion_score: -0.100000
  mode: local
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.9)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 1.9)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (3 alignments; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 3)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 G-AAT 4
                  0 |-..| 5
query             0 GTCCT 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [1, 4]], [[0, 1], [2, 5]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertAlmostEqual(counts.score, 1.9)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA-AT 4
                  0 |.-.| 5
query             0 GTCCT 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [2, 4]], [[0, 2], [3, 5]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertAlmostEqual(counts.score, 1.9)
        alignment = alignments[2]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAA-T 4
                  0 |..-| 5
query             0 GTCCT 5
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 3], [3, 4]], [[0, 3], [4, 5]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertAlmostEqual(counts.score, 1.9)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (3 alignments; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 3)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 G-AAT 4
                  0 |-..| 5
query             5 GTCCT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [1, 4]], [[5, 4], [3, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertAlmostEqual(counts.score, 1.9)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GA-AT 4
                  0 |.-.| 5
query             5 GTCCT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [2, 4]], [[5, 3], [2, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertAlmostEqual(counts.score, 1.9)
        alignment = alignments[2]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
target            0 GAA-T 4
                  0 |..-| 5
query             5 GTCCT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 3], [3, 4]], [[5, 2], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 4 aligned letters; 2 identities; 2 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 4:
        identities = 2,
        mismatches = 2.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 2)
        self.assertAlmostEqual(counts.score, 1.9)


class TestPairwiseMatchDictionary(unittest.TestCase):
    match_dict = {("A", "A"): 1.5, ("A", "T"): 0.5, ("T", "A"): 0.5, ("T", "T"): 1.0}

    def test_match_dictionary1(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        substitution_matrix = substitution_matrices.Array(data=self.match_dict)
        seq1 = "ATAT"
        seq2 = "ATT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.substitution_matrix = substitution_matrix
        aligner.open_gap_score = -0.5
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -0.500000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.500000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.500000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.500000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.500000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.500000
  extend_right_deletion_score: 0.000000
  mode: local
"""
            % id(substitution_matrix),
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             0 ATT 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[0, 3]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATAT 4
                  0 ||-| 4
query             0 AT-T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [3, 4]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.5,
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.5; gap score = -0.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.5,
        gap_score = -0.5.
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             3 ATT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[3, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATAT 4
                  0 ||-| 4
query             3 AT-T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [3, 4]], [[3, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.5,
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.5; gap score = -0.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.5,
        gap_score = -0.5.
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        self.assertEqual(alignment.sequences[0], "ATAT")
        alignment.sequences[0] = "ATCG"
        with self.assertRaises(ValueError) as cm:
            alignment.counts(aligner)
        self.assertEqual(
            str(cm.exception), "sequence contains letters not in the alphabet"
        )

    def test_match_dictionary2(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        substitution_matrix = substitution_matrices.Array(data=self.match_dict)
        seq1 = "ATAT"
        seq2 = "ATT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.substitution_matrix = substitution_matrix
        aligner.open_gap_score = -1.0
        aligner.extend_gap_score = 0.0
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -1.000000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -1.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -1.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -1.000000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: 0.000000
  mode: local
"""
            % id(substitution_matrix),
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             0 ATT 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[0, 3]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             3 ATT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[3, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_match_dictionary3(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        substitution_matrix = substitution_matrices.Array(data=self.match_dict)
        seq1 = "ATT"
        seq2 = "ATAT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.substitution_matrix = substitution_matrix
        aligner.open_gap_score = -1.0
        aligner.extend_gap_score = 0.0
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -1.000000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -1.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -1.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -1.000000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: 0.000000
  mode: local
"""
            % id(substitution_matrix),
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATT 3
                  0 ||. 3
query             0 ATA 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[0, 3]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATT 3
                  0 ||. 3
query             4 ATA 1
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[4, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_match_dictionary4(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        substitution_matrix = substitution_matrices.Array(alphabet="AT", dims=2)
        self.assertEqual(substitution_matrix.shape, (2, 2))
        substitution_matrix.update(self.match_dict)
        seq1 = "ATAT"
        seq2 = "ATT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.substitution_matrix = substitution_matrix
        aligner.open_gap_score = -0.5
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -0.500000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -0.500000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -0.500000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -0.500000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -0.500000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -0.500000
  extend_right_deletion_score: 0.000000
  mode: local
"""
            % id(substitution_matrix),
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             0 ATT 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[0, 3]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATAT 4
                  0 ||-| 4
query             0 AT-T 3
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [3, 4]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.5,
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.5; gap score = -0.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.5,
        gap_score = -0.5.
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             3 ATT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[3, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATAT 4
                  0 ||-| 4
query             3 AT-T 0
""",
        )
        self.assertEqual(alignment.shape, (2, 4))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [3, 4]], [[3, 1], [1, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.5,
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.5; gap score = -0.5; 3 aligned letters; 3 identities; 0 mismatches; 3 positives; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.5,
        gap_score = -0.5.
    aligned = 3:
        identities = 3,
        positives = 3,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_match_dictionary5(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        substitution_matrix = substitution_matrices.Array(alphabet="AT", dims=2)
        self.assertEqual(substitution_matrix.shape, (2, 2))
        substitution_matrix.update(self.match_dict)
        seq1 = "ATAT"
        seq2 = "ATT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.substitution_matrix = substitution_matrix
        aligner.open_gap_score = -1.0
        aligner.extend_gap_score = 0.0
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -1.000000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -1.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -1.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -1.000000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: 0.000000
  mode: local
"""
            % id(substitution_matrix),
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             0 ATT 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[0, 3]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATA 3
                  0 ||. 3
query             3 ATT 0
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[3, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_match_dictionary6(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        substitution_matrix = substitution_matrices.Array(alphabet="AT", dims=2)
        self.assertEqual(substitution_matrix.shape, (2, 2))
        substitution_matrix.update(self.match_dict)
        seq1 = "ATT"
        seq2 = "ATAT"
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.substitution_matrix = substitution_matrix
        aligner.open_gap_score = -1.0
        aligner.extend_gap_score = 0.0
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -1.000000
  extend_internal_insertion_score: 0.000000
  open_left_insertion_score: -1.000000
  extend_left_insertion_score: 0.000000
  open_right_insertion_score: -1.000000
  extend_right_insertion_score: 0.000000
  open_internal_deletion_score: -1.000000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: -1.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: -1.000000
  extend_right_deletion_score: 0.000000
  mode: local
"""
            % id(substitution_matrix),
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATT 3
                  0 ||. 3
query             0 ATA 3
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[0, 3]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ATT 3
                  0 ||. 3
query             4 ATA 1
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 3]], [[4, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 3.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 3.0,
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 3 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 3,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 3.0)


class TestPairwiseOneCharacter(unittest.TestCase):
    def test_align_one_char1(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.open_gap_score = -0.3
        aligner.extend_gap_score = -0.1
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.300000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.300000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.300000
  extend_right_deletion_score: -0.100000
  mode: local
""",
        )
        score = aligner.score("abcde", "c")
        self.assertAlmostEqual(score, 1)
        alignments = aligner.align("abcde", "c")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            2 c 3
                  0 | 1
query             0 c 1
""",
        )
        self.assertEqual(alignment.shape, (2, 1))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 3]], [[0, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 1 aligned letters; 1 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.0)

    def test_align_one_char2(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.open_gap_score = -0.3
        aligner.extend_gap_score = -0.1
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.300000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.300000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.300000
  extend_right_deletion_score: -0.100000
  mode: local
""",
        )
        score = aligner.score("abcce", "c")
        self.assertAlmostEqual(score, 1)
        alignments = aligner.align("abcce", "c")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            2 c 3
                  0 | 1
query             0 c 1
""",
        )
        self.assertEqual(alignment.shape, (2, 1))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 3]], [[0, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 1 aligned letters; 1 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
target            3 c 4
                  0 | 1
query             0 c 1
""",
        )
        self.assertEqual(alignment.shape, (2, 1))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[3, 4]], [[0, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 1 aligned letters; 1 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 1.0)

    def test_align_one_char3(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_gap_score = -0.3
        aligner.extend_gap_score = -0.1
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.300000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.300000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.300000
  extend_right_deletion_score: -0.100000
  mode: global
""",
        )
        seq1 = "abcde"
        seq2 = "c"
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 0.2)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=0.2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 0.2)
        self.assertEqual(
            str(alignment),
            """\
target            0 abcde 5
                  0 --|-- 5
query             0 --c-- 1
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 3]], [[0, 1]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (1 aligned letters; 1 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 4:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 0.20000000000000007; substitution score = 1.0; gap score = -0.7999999999999999; 1 aligned letters; 1 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 0.20000000000000007:
        substitution_score = 1.0,
        gap_score = -0.7999999999999999.
    aligned = 1:
        identities = 1,
        mismatches = 0.
    gaps = 4:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 1)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 0.2)

    def test_align_one_char_score3(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.open_gap_score = -0.3
        aligner.extend_gap_score = -0.1
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.300000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.300000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.300000
  extend_right_deletion_score: -0.100000
  mode: global
""",
        )
        score = aligner.score("abcde", "c")
        self.assertAlmostEqual(score, 0.2)

    def test_align_one_char_score3_fogsaa(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.open_gap_score = -0.3
        aligner.extend_gap_score = -0.1
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.300000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.300000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.300000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.300000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.300000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.300000
  extend_right_deletion_score: -0.100000
  mode: fogsaa
""",
        )
        score = aligner.score("abcde", "c")
        self.assertAlmostEqual(score, 0.2)


class TestPerSiteGapPenalties(unittest.TestCase):
    """Check gap penalty callbacks use correct gap opening position.

    This tests that the gap penalty callbacks are really being used
    with the correct gap opening position.
    """

    def test_gap_here_only_1(self):
        seq1 = "AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA"
        seq2 = "AABBBAAAACCCCAAAABBBAA"
        breaks = [0, 11, len(seq2)]

        # Very expensive to open a gap in seq1:
        def nogaps(x, y):
            return -2000 - y

        # Very expensive to open a gap in seq2 unless it is in one of the allowed positions:
        def specificgaps(x, y):
            if x in breaks:
                return -2 - y
            else:
                return -2000 - y

        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 1
        aligner.mismatch_score = -1
        aligner.insertion_score = nogaps
        aligner.deletion_score = specificgaps
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -1.000000
  insertion_score_function: {nogaps}
  deletion_score_function: {specificgaps}
  mode: global
""",
        )
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer global alignment algorithm"
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA 36
                  0 --|||||||||||----------|||||||||||-- 36
query             0 --AABBBAAAACC----------CCAAAABBBAA-- 22
""",
        )
        self.assertEqual(alignment.shape, (2, 36))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[2, 13], [23, 34]], [[0, 11], [11, 22]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (22 aligned letters; 22 identities; 0 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 22:
        identities = 22,
        mismatches = 0.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 22)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 22.0; gap score = -20.0; 22 aligned letters; 22 identities; 0 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 22.0,
        gap_score = -20.0.
    aligned = 22:
        identities = 22,
        mismatches = 0.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 22)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA 36
                  0 --|||||||||||----------|||||||||||-- 36
query            22 --AABBBAAAACC----------CCAAAABBBAA--  0
""",
        )
        self.assertEqual(alignment.shape, (2, 36))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[2, 13], [23, 34]], [[22, 11], [11, 0]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (22 aligned letters; 22 identities; 0 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 22:
        identities = 22,
        mismatches = 0.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 22)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 22.0; gap score = -20.0; 22 aligned letters; 22 identities; 0 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 22.0,
        gap_score = -20.0.
    aligned = 22:
        identities = 22,
        mismatches = 0.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 22)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)

    def test_gap_here_only_2(self):
        # Force a bad alignment.
        #
        # Forces a bad alignment by having a very expensive gap penalty
        # where one would normally expect a gap, and a cheap gap penalty
        # in another place.
        seq1 = "AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA"
        seq2 = "AABBBAAAACCCCAAAABBBAA"
        breaks = [0, 3, len(seq2)]

        # Very expensive to open a gap in seq1:
        def nogaps(x, y):
            return -2000 - y

        # Very expensive to open a gap in seq2 unless it is in one of the allowed positions:
        def specificgaps(x, y):
            if x in breaks:
                return -2 - y
            else:
                return -2000 - y

        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 1
        aligner.mismatch_score = -1
        aligner.insertion_score = nogaps
        aligner.deletion_score = specificgaps
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -1.000000
  insertion_score_function: {nogaps}
  deletion_score_function: {specificgaps}
  mode: global
""",
        )
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer global alignment algorithm"
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, -10)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, -10)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=-10) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -10.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA 36
                  0 --|||----------......|||||||||||||-- 36
query             0 --AAB----------BBAAAACCCCAAAABBBAA-- 22
""",
        )
        self.assertEqual(alignment.shape, (2, 36))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[2, 5], [15, 34]], [[0, 3], [3, 22]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (22 aligned letters; 16 identities; 6 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 22:
        identities = 16,
        mismatches = 6.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 16)
        self.assertEqual(counts.mismatches, 6)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -10.0; substitution score = 10.0; gap score = -20.0; 22 aligned letters; 16 identities; 6 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -10.0:
        substitution_score = 10.0,
        gap_score = -20.0.
    aligned = 22:
        identities = 16,
        mismatches = 6.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 16)
        self.assertEqual(counts.mismatches, 6)
        self.assertAlmostEqual(counts.score, -10.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, -10.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA 36
                  0 ||.------------......|||||||||||||-- 36
query             0 AAB------------BBAAAACCCCAAAABBBAA-- 22
""",
        )
        self.assertEqual(alignment.shape, (2, 36))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 3], [15, 34]], [[0, 3], [3, 22]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (22 aligned letters; 15 identities; 7 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 22:
        identities = 15,
        mismatches = 7.
    gaps = 14:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 12:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 12:
                open_internal_deletions = 1,
                extend_internal_deletions = 11;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 7)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -10.0; substitution score = 8.0; gap score = -18.0; 22 aligned letters; 15 identities; 7 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -10.0:
        substitution_score = 8.0,
        gap_score = -18.0.
    aligned = 22:
        identities = 15,
        mismatches = 7.
    gaps = 14:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 12:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 12:
                open_internal_deletions = 1,
                extend_internal_deletions = 11;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 7)
        self.assertAlmostEqual(counts.score, -10.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=-10) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -10.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA 36
                  0 --|||||||||||||......------------.|| 36
query            22 --AABBBAAAACCCCAAAABB------------BAA  0
""",
        )
        self.assertEqual(alignment.shape, (2, 36))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[2, 21], [33, 36]], [[22, 3], [3, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (22 aligned letters; 15 identities; 7 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 22:
        identities = 15,
        mismatches = 7.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 12:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 12:
                open_internal_deletions = 1,
                extend_internal_deletions = 11;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 7)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -10.0; substitution score = 8.0; gap score = -18.0; 22 aligned letters; 15 identities; 7 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -10.0:
        substitution_score = 8.0,
        gap_score = -18.0.
    aligned = 22:
        identities = 15,
        mismatches = 7.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 12:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 12:
                open_internal_deletions = 1,
                extend_internal_deletions = 11;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 7)
        self.assertAlmostEqual(counts.score, -10.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, -10.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA 36
                  0 --|||||||||||||......----------|||-- 36
query            22 --AABBBAAAACCCCAAAABB----------BAA--  0
""",
        )
        self.assertEqual(alignment.shape, (2, 36))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[2, 21], [31, 34]], [[22, 3], [3, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (22 aligned letters; 16 identities; 6 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 22:
        identities = 16,
        mismatches = 6.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 16)
        self.assertEqual(counts.mismatches, 6)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -10.0; substitution score = 10.0; gap score = -20.0; 22 aligned letters; 16 identities; 6 mismatches; 14 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -10.0:
        substitution_score = 10.0,
        gap_score = -20.0.
    aligned = 22:
        identities = 16,
        mismatches = 6.
    gaps = 14:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 10:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 10:
                open_internal_deletions = 1,
                extend_internal_deletions = 9;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 22)
        self.assertEqual(counts.identities, 16)
        self.assertEqual(counts.mismatches, 6)
        self.assertAlmostEqual(counts.score, -10.0)

    def test_gap_here_only_3(self):
        # Check if gap open and gap extend penalties are handled correctly.
        seq1 = "TTCCAA"
        seq2 = "TTGGAA"

        def gap_score(i, n):
            if i == 3:
                return -10
            if n == 1:
                return -1
            return -10

        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 1
        aligner.mismatch_score = -10
        aligner.insertion_score = gap_score
        aligner.deletion_score = 0
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer global alignment algorithm"
        )
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -10.000000
  insertion_score_function: {gap_score}
  open_internal_deletion_score: 0.000000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: 0.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: 0.000000
  extend_right_deletion_score: 0.000000
  mode: global
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT-CC-AA 6
                  0 ||----|| 8
query             0 TTG--GAA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 8))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 6]], [[0, 2], [4, 6]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 4.0; gap score = -2.0; 4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 4.0,
        gap_score = -2.0.
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT-CC-AA 6
                  0 ||----|| 8
query             6 TTG--GAA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 8))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 6]], [[6, 4], [2, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 4.0; gap score = -2.0; 4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 4.0,
        gap_score = -2.0.
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        aligner.deletion_score = gap_score
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -10.000000
  insertion_score_function: {gap_score}
  deletion_score_function: {gap_score}
  mode: global
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, -8.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, -8.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (4 alignments; score=-8) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 4)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT-CCAA 6
                  0 ||-.-|| 7
query             0 TTGG-AA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 7))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 2], [2, 3], [4, 6]], [[0, 2], [3, 4], [4, 6]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = -6.0; gap score = -2.0; 5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = -6.0,
        gap_score = -2.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, -8.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TTC--CAA 6
                  0 ||----|| 8
query             0 TT-GG-AA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 8))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 6]], [[0, 2], [4, 6]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = 4.0; gap score = -12.0; 4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = 4.0,
        gap_score = -12.0.
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -8.0)
        alignment = alignments[2]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TTCC-AA 6
                  0 ||-.-|| 7
query             0 TT-GGAA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 7))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 2], [3, 4], [4, 6]], [[0, 2], [2, 3], [4, 6]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = -6.0; gap score = -2.0; 5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = -6.0,
        gap_score = -2.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, -8.0)
        alignment = alignments[3]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT-CC-AA 6
                  0 ||----|| 8
query             0 TTG--GAA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 8))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 6]], [[0, 2], [4, 6]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = 4.0; gap score = -12.0; 4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = 4.0,
        gap_score = -12.0.
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -8.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (4 alignments; score=-8) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 4)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT-CCAA 6
                  0 ||-.-|| 7
query             6 TTGG-AA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 7))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 2], [2, 3], [4, 6]], [[6, 4], [3, 2], [2, 0]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = -6.0; gap score = -2.0; 5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = -6.0,
        gap_score = -2.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, -8.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TTC--CAA 6
                  0 ||----|| 8
query             6 TT-GG-AA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 8))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 6]], [[6, 4], [2, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = 4.0; gap score = -12.0; 4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = 4.0,
        gap_score = -12.0.
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -8.0)
        alignment = alignments[2]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TTCC-AA 6
                  0 ||-.-|| 7
query             6 TT-GGAA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 7))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 2], [3, 4], [4, 6]], [[6, 4], [4, 3], [2, 0]]]),
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = -6.0; gap score = -2.0; 5 aligned letters; 4 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = -6.0,
        gap_score = -2.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, -8.0)
        alignment = alignments[3]
        self.assertAlmostEqual(alignment.score, -8.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT-CC-AA 6
                  0 ||----|| 8
query             6 TTG--GAA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 8))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 6]], [[6, 4], [2, 0]]])
            )
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -8.0; substitution score = 4.0; gap score = -12.0; 4 aligned letters; 4 identities; 0 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -8.0:
        substitution_score = 4.0,
        gap_score = -12.0.
    aligned = 4:
        identities = 4,
        mismatches = 0.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, -8.0)

    def test_gap_here_only_local_1(self):
        seq1 = "AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA"
        seq2 = "AABBBAAAACCCCAAAABBBAA"
        breaks = [0, 11, len(seq2)]

        # Very expensive to open a gap in seq1:
        def nogaps(x, y):
            return -2000 - y

        # Very expensive to open a gap in seq2 unless it is in one of the allowed positions:
        def specificgaps(x, y):
            if x in breaks:
                return -2 - y
            else:
                return -2000 - y

        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.match_score = 1
        aligner.mismatch_score = -1
        aligner.insertion_score = nogaps
        aligner.deletion_score = specificgaps
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer local alignment algorithm"
        )
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -1.000000
  insertion_score_function: {nogaps}
  deletion_score_function: {specificgaps}
  mode: local
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 13)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 13)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13)
        self.assertEqual(
            str(alignment),
            """\
target            2 AABBBAAAACCCC 15
                  0 ||||||||||||| 13
query             0 AABBBAAAACCCC 13
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 15]], [[0, 13]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 13.0)
        self.assertEqual(
            str(alignment),
            """\
target           21 CCCCAAAABBBAA 34
                  0 ||||||||||||| 13
query             9 CCCCAAAABBBAA 22
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[21, 34]], [[9, 22]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13)
        self.assertEqual(
            str(alignment),
            """\
target            2 AABBBAAAACCCC 15
                  0 ||||||||||||| 13
query            22 AABBBAAAACCCC  9
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 15]], [[22, 9]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 13)
        self.assertEqual(
            str(alignment),
            """\
target           21 CCCCAAAABBBAA 34
                  0 ||||||||||||| 13
query            13 CCCCAAAABBBAA  0
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[21, 34]], [[13, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)

    def test_gap_here_only_local_2(self):
        # Force a bad alignment.
        #
        # Forces a bad alignment by having a very expensive gap penalty
        # where one would normally expect a gap, and a cheap gap penalty
        # in another place.
        seq1 = "AAAABBBAAAACCCCCCCCCCCCCCAAAABBBAAAA"
        seq2 = "AABBBAAAACCCCAAAABBBAA"
        breaks = [0, 3, len(seq2)]

        # Very expensive to open a gap in seq1:
        def nogaps(x, y):
            return -2000 - y

        # Very expensive to open a gap in seq2 unless it is in one of the allowed positions:
        def specificgaps(x, y):
            if x in breaks:
                return -2 - y
            else:
                return -2000 - y

        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.match_score = 1
        aligner.mismatch_score = -1
        aligner.insertion_score = nogaps
        aligner.deletion_score = specificgaps
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -1.000000
  insertion_score_function: {nogaps}
  deletion_score_function: {specificgaps}
  mode: local
""",
        )
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer local alignment algorithm"
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 13)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 13)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13)
        self.assertEqual(
            str(alignment),
            """\
target            2 AABBBAAAACCCC 15
                  0 ||||||||||||| 13
query             0 AABBBAAAACCCC 13
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 15]], [[0, 13]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 13.0)
        self.assertEqual(
            str(alignment),
            """\
target           21 CCCCAAAABBBAA 34
                  0 ||||||||||||| 13
query             9 CCCCAAAABBBAA 22
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[21, 34]], [[9, 22]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13)
        self.assertEqual(
            str(alignment),
            """\
target            2 AABBBAAAACCCC 15
                  0 ||||||||||||| 13
query            22 AABBBAAAACCCC  9
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[2, 15]], [[22, 9]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 13)
        self.assertEqual(
            str(alignment),
            """\
target           21 CCCCAAAABBBAA 34
                  0 ||||||||||||| 13
query            13 CCCCAAAABBBAA  0
""",
        )
        self.assertEqual(alignment.shape, (2, 13))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[21, 34]], [[13, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 13 aligned letters; 13 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 13:
        identities = 13,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 13)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 13.0)

    def test_gap_here_only_local_3(self):
        # Check if gap open and gap extend penalties are handled correctly.
        seq1 = "TTCCAA"
        seq2 = "TTGGAA"

        def gap_score(i, n):
            if i == 3:
                return -10
            if n == 1:
                return -1
            return -10

        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.match_score = 1
        aligner.mismatch_score = -10
        aligner.insertion_score = gap_score
        aligner.deletion_score = 0
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer local alignment algorithm"
        )
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -10.000000
  insertion_score_function: {gap_score}
  open_internal_deletion_score: 0.000000
  extend_internal_deletion_score: 0.000000
  open_left_deletion_score: 0.000000
  extend_left_deletion_score: 0.000000
  open_right_deletion_score: 0.000000
  extend_right_deletion_score: 0.000000
  mode: local
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT 2
                  0 || 2
query             0 TT 2
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[0, 2]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            4 AA 6
                  0 || 2
query             4 AA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[4, 6]], [[4, 6]]]))
        )
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT 2
                  0 || 2
query             6 TT 4
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[6, 4]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            4 AA 6
                  0 || 2
query             2 AA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[4, 6]], [[2, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        aligner.deletion_score = gap_score
        self.assertEqual(
            str(aligner),
            f"""Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: -10.000000
  insertion_score_function: {gap_score}
  deletion_score_function: {gap_score}
  mode: local
""",
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 2.0)
        score = aligner.score(seq1, reverse_complement(seq2), strand="-")
        self.assertAlmostEqual(score, 2.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT 2
                  0 || 2
query             0 TT 2
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[0, 2]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            4 AA 6
                  0 || 2
query             4 AA 6
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[4, 6]], [[4, 6]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=2) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TT 2
                  0 || 2
query             6 TT 4
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[0, 2]], [[6, 4]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 2.0)
        self.assertEqual(
            str(alignment),
            """\
target            4 AA 6
                  0 || 2
query             2 AA 0
""",
        )
        self.assertEqual(alignment.shape, (2, 2))
        self.assertTrue(
            np.array_equal(alignment.aligned, np.array([[[4, 6]], [[2, 0]]]))
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 2.0)

    def test_broken_gap_function(self):
        # Check if an Exception is propagated if the gap function raises one
        seq1 = "TTCCAA"
        seq2 = "TTGGAA"

        def gap_score(i, n):
            raise RuntimeError("broken gap function")

        aligner = Align.PairwiseAligner()
        aligner.insertion_score = gap_score
        aligner.deletion_score = -1
        aligner.mode = "global"
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, seq2)
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, reverse_complement(seq2), strand="-")
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, seq2)
            alignments = list(alignments)
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
            alignments = list(alignments)
        aligner.mode = "local"
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, seq2)
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, reverse_complement(seq2), strand="-")
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, seq2)
            alignments = list(alignments)
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
            alignments = list(alignments)
        aligner.insertion_score = -1
        aligner.deletion_score = gap_score
        aligner.mode = "global"
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, seq2)
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, reverse_complement(seq2), strand="-")
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, seq2)
            alignments = list(alignments)
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
            alignments = list(alignments)
        aligner.mode = "local"
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, seq2)
        with self.assertRaises(RuntimeError):
            aligner.score(seq1, reverse_complement(seq2), strand="-")
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, seq2)
            alignments = list(alignments)
        with self.assertRaises(RuntimeError):
            alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
            alignments = list(alignments)

    def test_mixing_affine_and_function(self):
        # Check if we can use a gap function for one sequence, and an affine
        # gap score for the other sequence.

        def gap_score_function(position, length):
            if position == 4:
                return 0
            else:
                return -100

        aligner = Align.PairwiseAligner()
        aligner.mismatch_score = -1
        aligner.deletion_score = gap_score_function
        aligner.insertion_score = 0
        aligner.internal_insertion_score = -10
        seqA = "AAAAAAAAAAA"
        seqB = "TTAAAAA"
        alignments = aligner.align(seqA, seqB)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 5.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 --AAAAAAAAAAA 11
                  0 --||------||| 13
query             0 TTAA------AAA  7
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 5.0; substitution score = 5.0; gap score = 0.0; 5 aligned letters; 5 identities; 0 mismatches; 8 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 5.0:
        substitution_score = 5.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 5,
        mismatches = 0.
    gaps = 8:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 6:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 6:
                open_internal_deletions = 1,
                extend_internal_deletions = 5;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 5)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.insertions, 2)
        self.assertEqual(counts.deletions, 6)
        self.assertAlmostEqual(counts.score, 5.0)
        seqA = "AAAAAAAAAAA"
        seqB = "AAAAATT"
        alignments = aligner.align(seqA, seqB)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 5.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAAAAAAAAA-- 11
                  0 ||||------|-- 13
query             0 AAAA------ATT  7
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 5.0; substitution score = 5.0; gap score = 0.0; 5 aligned letters; 5 identities; 0 mismatches; 8 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 5.0:
        substitution_score = 5.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 5,
        mismatches = 0.
    gaps = 8:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 6:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 6:
                open_internal_deletions = 1,
                extend_internal_deletions = 5;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 5)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.insertions, 2)
        self.assertEqual(counts.deletions, 6)
        self.assertAlmostEqual(counts.score, 5.0)

        # Same thing, switching A and B:
        aligner = Align.PairwiseAligner()
        aligner.mismatch_score = -1
        aligner.insertion_score = gap_score_function
        aligner.deletion_score = 0
        aligner.internal_deletion_score = -10
        seqA = "TTAAAAA"
        seqB = "AAAAAAAAAAA"
        alignments = aligner.align(seqA, seqB)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 5.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 TTAA------AAA  7
                  0 --||------||| 13
query             0 --AAAAAAAAAAA 11
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 5.0; substitution score = 5.0; gap score = 0.0; 5 aligned letters; 5 identities; 0 mismatches; 8 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 5.0:
        substitution_score = 5.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 5,
        mismatches = 0.
    gaps = 8:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 6:
            internal_insertions = 6:
                open_internal_insertions = 1,
                extend_internal_insertions = 5;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 5)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.insertions, 6)
        self.assertEqual(counts.deletions, 2)
        self.assertAlmostEqual(counts.score, 5.0)
        seqA = "AAAAATT"
        seqB = "AAAAAAAAAAA"
        alignments = aligner.align(seqA, seqB)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 5.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 AAAA------ATT  7
                  0 ||||------|-- 13
query             0 AAAAAAAAAAA-- 11
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 5.0; substitution score = 5.0; gap score = 0.0; 5 aligned letters; 5 identities; 0 mismatches; 8 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 5.0:
        substitution_score = 5.0,
        gap_score = 0.0.
    aligned = 5:
        identities = 5,
        mismatches = 0.
    gaps = 8:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 6:
            internal_insertions = 6:
                open_internal_insertions = 1,
                extend_internal_insertions = 5;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 2:
                open_right_deletions = 1,
                extend_right_deletions = 1.
""",
        )
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 5)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.insertions, 6)
        self.assertEqual(counts.deletions, 2)
        self.assertAlmostEqual(counts.score, 5.0)


class TestAlignerInput(unittest.TestCase):
    """Check aligning sequences provided as lists, str, Seq, or SeqRecord objects."""

    def test_three_letter_amino_acids_global(self):
        """Test aligning sequences provided as lists of three-letter amino acids."""
        seq1 = ["Gly", "Ala", "Thr"]
        seq2 = ["Gly", "Ala", "Ala", "Cys", "Thr"]
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.gap_score = 0.0
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        self.assertEqual(
            str(alignments[0]),
            """\
Gly Ala --- --- Thr
||| ||| --- --- |||
Gly Ala Ala Cys Thr
""",
        )
        self.assertEqual(
            str(alignments[1]),
            """\
Gly --- Ala --- Thr
||| --- ||| --- |||
Gly Ala Ala Cys Thr
""",
        )
        self.assertAlmostEqual(alignments[0].score, 3.0)
        self.assertAlmostEqual(alignments[1].score, 3.0)
        counts = alignments[0].counts()
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignments[0].counts(aligner)
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)
        counts = alignments[1].counts(aligner)
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertAlmostEqual(counts.score, 3.0)

        seq1 = ["Pro", "Pro", "Gly", "Ala", "Thr"]
        seq2 = ["Gly", "Ala", "Ala", "Cys", "Thr", "Asn", "Asn"]
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
Pro Pro Gly Ala --- --- Thr --- ---
--- --- ||| ||| --- --- ||| --- ---
--- --- Gly Ala Ala Cys Thr Asn Asn
""",
        )
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            alignment[0], ["Pro", "Pro", "Gly", "Ala", None, None, "Thr", None, None]
        )
        self.assertEqual(
            alignment[0, :], ["Pro", "Pro", "Gly", "Ala", None, None, "Thr", None, None]
        )
        self.assertEqual(
            alignment[0, 1:], ["Pro", "Gly", "Ala", None, None, "Thr", None, None]
        )
        self.assertEqual(alignment[0, ::2], ["Pro", "Gly", None, "Thr", None])
        self.assertEqual(
            alignment[1], [None, None, "Gly", "Ala", "Ala", "Cys", "Thr", "Asn", "Asn"]
        )
        self.assertEqual(
            alignment[1, :],
            [None, None, "Gly", "Ala", "Ala", "Cys", "Thr", "Asn", "Asn"],
        )
        self.assertEqual(
            alignment[1, 1:], [None, "Gly", "Ala", "Ala", "Cys", "Thr", "Asn", "Asn"]
        )
        self.assertEqual(alignment[1, ::2], [None, "Gly", "Ala", "Thr", "Asn"])
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (3 aligned letters; 3 identities; 0 mismatches; 6 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 6:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 2:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.score)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 3 identities; 0 mismatches; 6 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 6:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 2:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 2)
        self.assertEqual(counts.insertions, 4)
        self.assertAlmostEqual(counts.score, 3.0)
        alignment = alignments[1]
        self.assertEqual(
            str(alignment),
            """\
Pro Pro Gly --- Ala --- Thr --- ---
--- --- ||| --- ||| --- ||| --- ---
--- --- Gly Ala Ala Cys Thr Asn Asn
""",
        )
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(
            alignment[0], ["Pro", "Pro", "Gly", None, "Ala", None, "Thr", None, None]
        )
        self.assertEqual(
            alignment[0, :], ["Pro", "Pro", "Gly", None, "Ala", None, "Thr", None, None]
        )
        self.assertEqual(
            alignment[0, 1:-1], ["Pro", "Gly", None, "Ala", None, "Thr", None]
        )
        self.assertEqual(alignment[0, 1::2], ["Pro", None, None, None])
        self.assertEqual(
            alignment[1], [None, None, "Gly", "Ala", "Ala", "Cys", "Thr", "Asn", "Asn"]
        )
        self.assertEqual(
            alignment[1, :],
            [None, None, "Gly", "Ala", "Ala", "Cys", "Thr", "Asn", "Asn"],
        )
        self.assertEqual(
            alignment[1, 1:-1], [None, "Gly", "Ala", "Ala", "Cys", "Thr", "Asn"]
        )
        self.assertEqual(alignment[1, 1::2], [None, "Ala", "Cys", "Asn"])
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 3 identities; 0 mismatches; 6 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 6:
        left_gaps = 2:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 2:
                open_left_deletions = 1,
                extend_left_deletions = 1;
        internal_gaps = 2:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 2)
        self.assertEqual(counts.insertions, 4)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_three_letter_amino_acids_local(self):
        seq1 = ["Asn", "Asn", "Gly", "Ala", "Thr", "Glu", "Glu"]
        seq2 = ["Pro", "Pro", "Gly", "Ala", "Ala", "Cys", "Thr", "Leu"]
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.gap_score = 0.0
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 3.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=3) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
Gly Ala --- --- Thr
||| ||| --- --- |||
Gly Ala Ala Cys Thr
""",
        )
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(alignment[0], ["Gly", "Ala", None, None, "Thr"])
        self.assertEqual(alignment[0, :], ["Gly", "Ala", None, None, "Thr"])
        self.assertEqual(alignment[0, 1:], ["Ala", None, None, "Thr"])
        self.assertEqual(alignment[0, :-1], ["Gly", "Ala", None, None])
        self.assertEqual(alignment[0, ::2], ["Gly", None, "Thr"])
        self.assertEqual(alignment[1], ["Gly", "Ala", "Ala", "Cys", "Thr"])
        self.assertEqual(alignment[1, :], ["Gly", "Ala", "Ala", "Cys", "Thr"])
        self.assertEqual(alignment[1, 1:], ["Ala", "Ala", "Cys", "Thr"])
        self.assertEqual(alignment[1, :-1], ["Gly", "Ala", "Ala", "Cys"])
        self.assertEqual(alignment[1, ::2], ["Gly", "Ala", "Thr"])
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 2)
        self.assertAlmostEqual(counts.score, 3.0)
        alignment = alignments[1]
        self.assertEqual(
            str(alignment),
            """\
Gly --- Ala --- Thr
||| --- ||| --- |||
Gly Ala Ala Cys Thr
""",
        )
        self.assertAlmostEqual(alignment.score, 3.0)
        self.assertEqual(alignment[0], ["Gly", None, "Ala", None, "Thr"])
        self.assertEqual(alignment[0, :], ["Gly", None, "Ala", None, "Thr"])
        self.assertEqual(alignment[0, 1:], [None, "Ala", None, "Thr"])
        self.assertEqual(alignment[0, :-1], ["Gly", None, "Ala", None])
        self.assertEqual(alignment[0, ::2], ["Gly", "Ala", "Thr"])
        self.assertEqual(alignment[1], ["Gly", "Ala", "Ala", "Cys", "Thr"])
        self.assertEqual(alignment[1, :], ["Gly", "Ala", "Ala", "Cys", "Thr"])
        self.assertEqual(alignment[1, 1:], ["Ala", "Ala", "Cys", "Thr"])
        self.assertEqual(alignment[1, :-1], ["Gly", "Ala", "Ala", "Cys"])
        self.assertEqual(alignment[1, ::2], ["Gly", "Ala", "Thr"])
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 2:
                open_internal_insertions = 2,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 2)
        self.assertAlmostEqual(counts.score, 3.0)

    def test_str_seq_seqrecord(self):
        """Test aligning sequences provided as str, Seq, or SeqRecord objects."""
        aligner = Align.PairwiseAligner("blastn")
        t1 = "ACGT"
        t2 = "CGTT"
        s1 = Seq(t1)
        s2 = Seq(t2)
        r1 = SeqRecord(s1, id="first", description="1st sequence")
        r2 = SeqRecord(s2, id="second", description="2nd sequence")
        alignments = aligner.align(t1, t2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGT 4
                  0 ...| 4
query             0 CGTT 4
""",
        )
        self.assertEqual(
            format(alignment, "fasta"),
            """\
>
ACGT
>
CGTT
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = -7.0; gap score = 0.0; 4 aligned letters; 1 identities; 3 mismatches; 1 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = -7.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 1,
        positives = 1,
        mismatches = 3.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 3)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertEqual(counts.positives, 1)
        self.assertAlmostEqual(counts.score, -7.0)
        alignments = aligner.align(s1, s2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGT 4
                  0 ...| 4
query             0 CGTT 4
""",
        )
        self.assertEqual(
            format(alignment, "fasta"),
            """\
>
ACGT
>
CGTT
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = -7.0; gap score = 0.0; 4 aligned letters; 1 identities; 3 mismatches; 1 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = -7.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 1,
        positives = 1,
        mismatches = 3.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 3)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertEqual(counts.positives, 1)
        self.assertAlmostEqual(counts.score, -7.0)
        alignments = aligner.align(r1, r2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=-7) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
first             0 ACGT 4
                  0 ...| 4
second            0 CGTT 4
""",
        )
        self.assertEqual(
            format(alignment, "fasta"),
            """\
>first 1st sequence
ACGT
>second 2nd sequence
CGTT
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = -7.0; gap score = 0.0; 4 aligned letters; 1 identities; 3 mismatches; 1 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = -7.0,
        gap_score = 0.0.
    aligned = 4:
        identities = 1,
        positives = 1,
        mismatches = 3.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 4)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 3)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, -7.0)


class TestArgumentErrors(unittest.TestCase):
    def test_aligner_string_errors(self):
        aligner = Align.PairwiseAligner()
        message = "^'int' object is not iterable$"
        with self.assertRaisesRegex(TypeError, message):
            aligner.score("AAA", 3)
        message = "^sequence has zero length$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score("AAA", "")
        with self.assertRaisesRegex(ValueError, message):
            aligner.score("AAA", "", strand="-")

    def test_aligner_array_errors(self):
        aligner = Align.PairwiseAligner()
        s1 = "GGG"
        s2 = array.array("i", [ord("G"), ord("A"), ord("G")])
        score = aligner.score(s1, s2)
        self.assertAlmostEqual(score, 2.0)
        s2 = array.array("f", [1.0, 0.0, 1.0])
        message = "^sequence has incorrect data type 'f'$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score(s1, s2)
        aligner.wildcard = chr(99)
        s1 = array.array("i", [1, 5, 6])
        s2 = array.array("i", [1, 8, 6])
        s2a = array.array("i", [1, 8, 99])
        s2b = array.array("i", [1, 28, 6])
        aligner.match = 3.0
        aligner.mismatch = -2.0
        aligner.gap_score = -10.0
        score = aligner.score(s1, s2)
        self.assertAlmostEqual(score, 4.0)
        # the following two are valid as we are using match/mismatch scores
        # instead of a substitution matrix:
        score = aligner.score(s1, s2a)
        # since we set the wildcard character to chr(99), the number 99
        # is interpreted as an unknown character, and gets a zero score:
        self.assertAlmostEqual(score, 1.0)
        score = aligner.score(s1, s2b)
        self.assertAlmostEqual(score, 4.0)
        try:
            import numpy as np
        except ImportError:
            return
        aligner = Align.PairwiseAligner()
        aligner.wildcard = chr(99)
        aligner.gap_score = 0.0
        s1 = "GGG"
        s2 = np.array([ord("G"), ord("A"), ord("G")], np.int32)
        score = aligner.score(s1, s2)
        self.assertAlmostEqual(score, 2.0)
        alignments = aligner.align(s1, s2)
        self.assertEqual(len(alignments), 5)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
G  -- G  G
.  -- .  -
71 65 71 -
""",
        )
        self.assertAlmostEqual(alignment.score, 2.0)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 2.0; substitution score = 2.0; gap score = 0.0; 2 aligned letters; 2 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 2.0:
        substitution_score = 2.0,
        gap_score = 0.0.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 1)
        self.assertEqual(counts.insertions, 1)
        self.assertAlmostEqual(counts.score, 2.0)

        s2 = np.array([1.0, 0.0, 1.0])
        message = "^sequence has incorrect data type 'd'$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score(s1, s2)
        with self.assertRaisesRegex(ValueError, message):
            aligner.align(s1, s2)
        s2 = np.zeros((3, 2), np.int32)
        message = "^sequence has incorrect rank \\(2 expected 1\\)$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score(s1, s2)
        with self.assertRaisesRegex(ValueError, message):
            aligner.align(s1, s2)
        s1 = np.array([1, 5, 6], np.int32)
        s2 = np.array([1, 8, 6], np.int32)
        s2a = np.array([1, 8, 99], np.int32)
        s2b = np.array([1, 28, 6], np.int32)
        s2c = np.array([1, 8, -6], np.int32)
        aligner.match = 3.0
        aligner.mismatch = -2.0
        aligner.gap_score = -10.0
        score = aligner.score(s1, s2)
        self.assertAlmostEqual(score, 4.0)
        alignments = aligner.align(s1, s2)
        self.assertAlmostEqual(alignments.score, 4.0)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 4.0)
        self.assertEqual(
            str(alignment),
            """\
1 5 6
| . |
1 8 6
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 4.0; substitution score = 4.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 4.0:
        substitution_score = 4.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 4.0)
        # alignments are valid as we are using match/mismatch scores
        # instead of a substitution matrix:
        score = aligner.score(s1, s2a)
        self.assertAlmostEqual(score, 1.0)
        alignments = aligner.align(s1, s2a)
        self.assertEqual(len(alignments), 1)
        self.assertAlmostEqual(alignments.score, 1.0)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
1 5 6 
| . . 
1 8 99
""",
        )
        score = aligner.score(s1, s2b)
        self.assertAlmostEqual(score, 4.0)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 1.0; gap score = 0.0; 3 aligned letters; 1 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 1.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 1,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 1.0)
        alignments = aligner.align(s1, s2b)
        self.assertEqual(len(alignments), 1)
        self.assertAlmostEqual(alignments.score, 4.0)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 4.0)
        self.assertEqual(
            str(alignment),
            """\
1 5  6
| .  |
1 28 6
""",
        )
        self.assertAlmostEqual(score, 4.0)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 4.0; substitution score = 4.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 4.0:
        substitution_score = 4.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 4.0)
        # when using a substitution matrix, all indices should be between 0
        # and the size of the substitution matrix:
        m = 5 * np.eye(10)
        aligner.substitution_matrix = m
        score = aligner.score(s1, s2)  # no ValueError
        self.assertAlmostEqual(score, 10.0)
        alignments = aligner.align(s1, s2)
        self.assertAlmostEqual(alignments.score, 10.0)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 10.0)
        self.assertEqual(
            str(alignment),
            """\
1 5 6
| . |
1 8 6
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 10.0; substitution score = 10.0; gap score = 0.0; 3 aligned letters; 2 identities; 1 mismatches; 2 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 10.0:
        substitution_score = 10.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 2,
        positives = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 1)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 10.0)
        counts = alignment.counts(m)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 10.0; 3 aligned letters; 2 identities; 1 mismatches; 2 positives; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 10.0,
    aligned = 3:
        identities = 2,
        positives = 2,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        alignment.sequences[1] = s2b
        with self.assertRaises(ValueError) as cm:
            alignment.counts(m)
        self.assertEqual(
            str(cm.exception), "sequence[1][1] is out of bound (28, should be < 10)"
        )
        with self.assertRaises(ValueError) as cm:
            alignment.counts(aligner)
        self.assertEqual(
            str(cm.exception), "sequence[1][1] is out of bound (28, should be < 10)"
        )
        alignment.sequences[1] = s2c
        with self.assertRaises(ValueError) as cm:
            alignment.counts(m)
        self.assertEqual(str(cm.exception), "sequences[1][2] is negative (-6)")
        with self.assertRaises(ValueError) as cm:
            alignment.counts(aligner)
        self.assertEqual(str(cm.exception), "sequences[1][2] is negative (-6)")
        message = "^sequence item 2 is negative \\(-6\\)$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score(s1, s2c)
        with self.assertRaisesRegex(ValueError, message):
            aligner.align(s1, s2c)
        message = "^sequence item 1 is out of bound \\(28, should be < 10\\)$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score(s1, s2b)
        with self.assertRaisesRegex(ValueError, message):
            aligner.align(s1, s2b)
        # note that the wildcard character is ignored when using a substitution
        # matrix, so 99 is interpreted as an index here:
        message = "^sequence item 2 is out of bound \\(99, should be < 10\\)$"
        with self.assertRaisesRegex(ValueError, message):
            aligner.score(s1, s2a)
        with self.assertRaisesRegex(ValueError, message):
            aligner.align(s1, s2a)


class TestOverflowError(unittest.TestCase):
    def test_align_overflow_error(self):
        aligner = Align.PairwiseAligner()
        aligner.gap_score = 0
        path = os.path.join("Align", "bsubtilis.fa")
        record = SeqIO.read(path, "fasta")
        seq1 = record.seq
        path = os.path.join("Align", "ecoli.fa")
        record = SeqIO.read(path, "fasta")
        seq2 = record.seq
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (>{sys.maxsize} alignments; score=1286) at {hex(id(alignments))}>""",
        )
        self.assertAlmostEqual(alignments.score, 1286.0)
        message = "^number of optimal alignments is larger than (%d|%d)$" % (
            2147483647,  # on 32-bit systems
            9223372036854775807,
        )  # on 64-bit systems
        with self.assertRaisesRegex(OverflowError, message):
            n = len(alignments)
        # confirm that we can still pull out individual alignments
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ATTTA-TC-GGA-GAGTTTGATCC-TGGCTCAGGAC--GAACGCTGGCGGC-GTGCCTAA
                  0 |---|-|--|-|-||||||||||--||||||||-|---|||||||||||||-|-||||||
query             0 A---AAT-TG-AAGAGTTTGATC-ATGGCTCAG-A-TTGAACGCTGGCGGCAG-GCCTAA

target           53 T-ACATGCAAGTCGAG-CGG-A-CAG-AT-GGGA-GCTTGCT-C----CCTGAT-GTTAG
                 60 --|||||||||||||--|||-|-|||-|--|--|-|||||||-|----|-|||--|--||
query            51 -CACATGCAAGTCGA-ACGGTAACAGGA-AG--AAGCTTGCTTCTTTGC-TGA-CG--AG

target          100 C-GGCGGACGGGTGAGTAACAC-GT--GGGTAA-CCTGCCTGTAA-G-ACTGGG--ATAA
                120 --|||||||||||||||||----||--|||-||-|-||||||-|--|-|--|||--||||
query           102 -TGGCGGACGGGTGAGTAA---TGTCTGGG-AAAC-TGCCTG-A-TGGA--GGGGGATAA

target          151 CT-CC-GGGAAACCGG--GGCTAATACCGG-ATGGTTGTTTGAACCGCAT-GGTTCAA-A
                180 ||-|--||-||||-||--|-|||||||||--||---------|||-|--|-|---|||-|
query           152 CTAC-TGG-AAAC-GGTAG-CTAATACCG-CAT---------AAC-G--TCG---CAAGA

target          204 C-ATAA-AAGGTGG--C-TTCGG-C-TACCACTTA-C-A--G-ATG-GACCC-GC--GGC
                240 |-|-||-|-||-||--|-|||||-|-|-|---||--|-|--|-|||-|-|||-|---||-
query           192 CCA-AAGA-GG-GGGACCTTCGGGCCT-C---TT-GCCATCGGATGTG-CCCAG-ATGG-

target          248 GCATTAGCTAGTT-GGTGAGG-TAACGGCTCACC-AAGGCGACGATGCG--TAGCC-GA-
                300 |-||||||||||--||||-||-||||||||||||-|-|||||||||-|---||||--|--
query           241 G-ATTAGCTAGT-AGGTG-GGGTAACGGCTCACCTA-GGCGACGAT-C-CCTAGC-TG-G

target          301 -CCTGAGAGGG-TGATC--GGCCACACTGGGA-CTGAGACACGG-CCCAGACTCCTACGG
                360 -|-|||||||--|||-|--|-|||||||||-|-|||||||||||-||-||||||||||||
query           293 TC-TGAGAGG-ATGA-CCAG-CCACACTGG-AACTGAGACACGGTCC-AGACTCCTACGG

target          355 GAGGCAGCAGTAGGG-AATC-TTCCGCA-A-TGGA-CG-AAAGTC-TGAC-GG-AGCAAC
                420 |||||||||||-|||-|||--||--|||-|-|||--||-||-|-|-|||--|--|||--|
query           347 GAGGCAGCAGT-GGGGAAT-ATT--GCACAATGG-GCGCAA-G-CCTGA-TG-CAGC--C

target          406 --GCCGCGTG-AGTGAT-GAAGG--TTTTCGGA-TC-GTAAAGCT-CTGTTGTT-AG-GG
                480 --||||||||-|-|||--|||||--||--|||--|--||||||-|-||-||----||-||
query           396 ATGCCGCGTGTA-TGA-AGAAGGCCTT--CGG-GT-TGTAAAG-TACT-TT---CAGCGG

target          455 --A--A-G--A--ACAAGTGCCGTTCGAATAGGGC----GG-TACC-TTGACGGT-ACCT
                540 --|--|-|--|--|-||||----|---||||---|----|--|-|--||||||-|-|||-
query           445 GGAGGAAGGGAGTA-AAGT----T---AATA---CCTTTG-CT-C-ATTGACG-TTACC-

target          499 AAC-CAGAA-A-GCCAC-GGCTAACTAC-GTGCCAGCAGCCGCGGTAATACGT-AGG-TG
                600 --|-|||||-|-||-||-||||||||-|-|||||||||||||||||||||||--|||-||
query           489 --CGCAGAAGAAGC-ACCGGCTAACT-CCGTGCCAGCAGCCGCGGTAATACG-GAGGGTG

target          552 GCAAGCGTTG--TCCGGAATTA-TTGGGCGTAAAG-GGCT-CGCAGGCGGTTTC-TTAAG
                660 -||||||||---||-|||||||-|-||||||||||-|-|--||||||||||||--|||||
query           544 -CAAGCGTT-AATC-GGAATTACT-GGGCGTAAAGCG-C-ACGCAGGCGGTTT-GTTAAG

target          606 TCT-GATGTGAAAG-CCCCCGG-CTCAACC-GGGGAGGG--T-CAT-TGGA-AACTGGGG
                720 ||--|||||||||--||||-||-|||||||-|||-|-----|-|||-||-|-|-||||--
query           597 TC-AGATGTGAAA-TCCCC-GGGCTCAACCTGGG-A---ACTGCATCTG-ATA-CTGG--

target          657 -AA-CTTGAGTGCA--G-AAGAGGAGAGTGG-A-A-TTCCACG-TGTAGCGGTGAAATGC
                780 -||-|||||||-|---|-|-||||-|-|-||-|-|-|||||-|-||||||||||||||||
query           646 CAAGCTTGAGT-C-TCGTA-GAGG-G-G-GGTAGAATTCCA-GGTGTAGCGGTGAAATGC

target          708 GTAGAGATG-TGGAGGAAC-ACCAG-TGGCGAAGGCGA-CTCTC--TGGT-CTGTAA--C
                840 ||||||||--||||||||--|||-|-|||||||||||--|-|-|--|||--|-|-||--|
query           699 GTAGAGAT-CTGGAGGAA-TACC-GGTGGCGAAGGCG-GC-C-CCCTGG-AC-G-AAGAC

target          759 TGACGCTG-AGGA-GCGAAAGCGTGGGGAGCGAA-CAGGATTAGATACCCTGGTAGTCCA
                900 |||||||--|||--|||||||||||||||||-||-|||||||||||||||||||||||||
query           750 TGACGCT-CAGG-TGCGAAAGCGTGGGGAGC-AAACAGGATTAGATACCCTGGTAGTCCA

target          816 CGCCGTAAACGATGAGT-G-CTAAGTGTT-AGGGGGTT-TCCGCCCCTT-AGTGC-TG-C
                960 ||||||||||||||--|-|-||---||---|||---||-|--||||-||-||-||-||-|
query           807 CGCCGTAAACGATG--TCGACT---TG--GAGG---TTGT--GCCC-TTGAG-GCGTGGC

target          869 ------AGCTAACGCA-TTAAG-C-ACTCCGCCTGGGGAGTACGGTC-GCAAGACTG--A
               1020 ------|||||||||--|||||-|-||-|-|||||||||||||||-|-|||||---|--|
query           853 TTCCGGAGCTAACGC-GTTAAGTCGAC-C-GCCTGGGGAGTACGG-CCGCAAG---GTTA

target          917 AA-CTCAAA-GGAATTGACGGGGGCCCGCACAAGCGGTGGAGCATGTGGTTTAATTCGAA
               1080 ||-||||||-|-|||||||||||||||||||||||||||||||||||||||||||||||-
query           906 AAACTCAAATG-AATTGACGGGGGCCCGCACAAGCGGTGGAGCATGTGGTTTAATTCGA-

target          975 -GCAACGCGAAGAACCTTACCA-GGTCTTGACATCCTCTGACA-A--T--CCTAGAGATA
               1140 -||||||||||||||||||||--|||||||||||||----|||-|--|--||-||||||-
query           964 TGCAACGCGAAGAACCTTACC-TGGTCTTGACATCC----ACAGAACTTTCC-AGAGAT-

target         1028 GGAC--G-T-CCCCTTCGGGGGCAGA--GTGA--CAGGTGG-TGCATGG-TTGTCGTCAG
               1200 |||---|-|-||--||||||---|-|--||||--||||||--|||||||-|-||||||||
query          1017 GGA-TTGGTGCC--TTCGGG---A-ACTGTGAGACAGGTG-CTGCATGGCT-GTCGTCAG

target         1078 CTCGTGTC-GTGAGA-TGTTGGGTTAAGTCCCGCAACGAGCGCAACCCTTGATCTTA--G
               1260 |||||||--||||-|-||||||||||||||||||||||||||||||||||-|||||---|
query          1068 CTCGTGT-TGTGA-AATGTTGGGTTAAGTCCCGCAACGAGCGCAACCCTT-ATCTT-TTG

target         1134 TTGCCAGCA--TTCA-GTTG--GGC-A-CTCTAA-GGT-GACTGCC-GGTGAC-AAACC-
               1320 ||||||||---|-|--|--|--||--|-|||-||-||--|||||||-|-|||--||||--
query          1124 TTGCCAGC-GGT-C-CG--GCCGG-GAACTC-AAAGG-AGACTGCCAG-TGA-TAAAC-T

target         1182 GGAGGAAGGTGGGGATGACGTCAAA-TCATCATG-CCCCTTAT-GACCT-GGGCTACACA
               1380 ||||||||||||||||||||||||--||||||||-|||-|||--||||--||||||||||
query          1173 GGAGGAAGGTGGGGATGACGTCAA-GTCATCATGGCCC-TTA-CGACC-AGGGCTACACA

target         1238 CGTGCTACAATGGACAG-A-ACAAAG-GGCA-GCGAAACC--GCGAG-GTT-AAGCC--A
               1440 |||||||||||||-|-|-|-||||||-|--|-||||--||--|||||-|---||||---|
query          1229 CGTGCTACAATGG-C-GCATACAAAGAG--AAGCGA--CCTCGCGAGAG--CAAGC-GGA

target         1288 ATCC-CAC-AAA-T-CTGTTC-TCAGTTC-GGATC-GC-AGTCTGCAACTCGACTGCG--
               1500 --||-||--|||-|-|-||-|-|-|||-|-||||--|--||||||||||||||||-|---
query          1280 --CCTCA-TAAAGTGC-GT-CGT-AGT-CCGGAT-TG-GAGTCTGCAACTCGACT-C-CA

target         1338 TGAAGCT-GGAATCGCTAGTAATCGC-GGATCAGCA-TGCCG-CGGTGAATACGTTCCCG
               1560 |||||-|-|||||||||||||||||--|||||||-|-||||--|||||||||||||||||
query          1329 TGAAG-TCGGAATCGCTAGTAATCG-TGGATCAG-AATGCC-ACGGTGAATACGTTCCCG

target         1394 GGCCTTGTACACACCGCCCGTCACACCAC-GAG-AGT---TTGT-AACACCC-GAAGTC-
               1620 ||||||||||||||||||||||||||||--|-|-|||---|||--||-|----|||||--
query          1385 GGCCTTGTACACACCGCCCGTCACACCA-TG-GGAGTGGGTTG-CAA-A---AGAAGT-A

target         1446 GGTGAGG-T-AACCTTTTA-GG-AG--C-C--AGCCG-CC---GAAGGTGGGA--CAGAT
               1680 |||-||--|-||||||----||-||--|-|--|-||--|----|----||--|--||--|
query          1437 GGT-AG-CTTAACCTT---CGGGAGGGCGCTTA-CC-AC-TTTG----TG--ATTCA--T

target         1491 GA-TTGGGGTGAAGTCGTAACAAGGTAG-CCGTATCGGAAGG----TGCGGCT-GGATCA
               1740 ||-|-||||||||||||||||||||||--|||||--||--||----|||||-|-||||||
query          1481 GACT-GGGGTGAAGTCGTAACAAGGTA-ACCGTA--GG--GGAACCTGCGG-TTGGATCA

target         1544 CCTCCTTTCTA 1555
               1800 |||||||---| 1811
query          1534 CCTCCTT---A 1542
""",
        )
        self.assertEqual(alignment.shape, (2, 1811))
        self.assertAlmostEqual(alignment.score, 1286.0)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1286.0; substitution score = 1286.0; gap score = 0.0; 1286 aligned letters; 1286 identities; 0 mismatches; 525 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1286.0:
        substitution_score = 1286.0,
        gap_score = 0.0.
    aligned = 1286:
        identities = 1286,
        mismatches = 0.
    gaps = 525:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 525:
            internal_insertions = 256:
                open_internal_insertions = 202,
                extend_internal_insertions = 54;
            internal_deletions = 269:
                open_internal_deletions = 199,
                extend_internal_deletions = 70;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1286)
        self.assertEqual(counts.identities, 1286)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 269)
        self.assertEqual(counts.insertions, 256)
        self.assertAlmostEqual(counts.score, 1286.0)
        alignments = aligner.align(seq1, reverse_complement(seq2), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (>{sys.maxsize} alignments; score=1286) at {hex(id(alignments))}>""",
        )
        self.assertAlmostEqual(alignments.score, 1286.0)
        message = "^number of optimal alignments is larger than (%d|%d)$" % (
            2147483647,  # on 32-bit systems
            9223372036854775807,
        )  # on 64-bit systems
        with self.assertRaisesRegex(OverflowError, message):
            n = len(alignments)
        # confirm that we can still pull out individual alignments
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ATTTA-TC-GGA-GAGTTTGATCC-TGGCTCAGGAC--GAACGCTGGCGGC-GTGCCTAA
                  0 |---|-|--|-|-||||||||||--||||||||-|---|||||||||||||-|-||||||
query          1542 A---AAT-TG-AAGAGTTTGATC-ATGGCTCAG-A-TTGAACGCTGGCGGCAG-GCCTAA

target           53 T-ACATGCAAGTCGAG-CGG-A-CAG-AT-GGGA-GCTTGCT-C----CCTGAT-GTTAG
                 60 --|||||||||||||--|||-|-|||-|--|--|-|||||||-|----|-|||--|--||
query          1491 -CACATGCAAGTCGA-ACGGTAACAGGA-AG--AAGCTTGCTTCTTTGC-TGA-CG--AG

target          100 C-GGCGGACGGGTGAGTAACAC-GT--GGGTAA-CCTGCCTGTAA-G-ACTGGG--ATAA
                120 --|||||||||||||||||----||--|||-||-|-||||||-|--|-|--|||--||||
query          1440 -TGGCGGACGGGTGAGTAA---TGTCTGGG-AAAC-TGCCTG-A-TGGA--GGGGGATAA

target          151 CT-CC-GGGAAACCGG--GGCTAATACCGG-ATGGTTGTTTGAACCGCAT-GGTTCAA-A
                180 ||-|--||-||||-||--|-|||||||||--||---------|||-|--|-|---|||-|
query          1390 CTAC-TGG-AAAC-GGTAG-CTAATACCG-CAT---------AAC-G--TCG---CAAGA

target          204 C-ATAA-AAGGTGG--C-TTCGG-C-TACCACTTA-C-A--G-ATG-GACCC-GC--GGC
                240 |-|-||-|-||-||--|-|||||-|-|-|---||--|-|--|-|||-|-|||-|---||-
query          1350 CCA-AAGA-GG-GGGACCTTCGGGCCT-C---TT-GCCATCGGATGTG-CCCAG-ATGG-

target          248 GCATTAGCTAGTT-GGTGAGG-TAACGGCTCACC-AAGGCGACGATGCG--TAGCC-GA-
                300 |-||||||||||--||||-||-||||||||||||-|-|||||||||-|---||||--|--
query          1301 G-ATTAGCTAGT-AGGTG-GGGTAACGGCTCACCTA-GGCGACGAT-C-CCTAGC-TG-G

target          301 -CCTGAGAGGG-TGATC--GGCCACACTGGGA-CTGAGACACGG-CCCAGACTCCTACGG
                360 -|-|||||||--|||-|--|-|||||||||-|-|||||||||||-||-||||||||||||
query          1249 TC-TGAGAGG-ATGA-CCAG-CCACACTGG-AACTGAGACACGGTCC-AGACTCCTACGG

target          355 GAGGCAGCAGTAGGG-AATC-TTCCGCA-A-TGGA-CG-AAAGTC-TGAC-GG-AGCAAC
                420 |||||||||||-|||-|||--||--|||-|-|||--||-||-|-|-|||--|--|||--|
query          1195 GAGGCAGCAGT-GGGGAAT-ATT--GCACAATGG-GCGCAA-G-CCTGA-TG-CAGC--C

target          406 --GCCGCGTG-AGTGAT-GAAGG--TTTTCGGA-TC-GTAAAGCT-CTGTTGTT-AG-GG
                480 --||||||||-|-|||--|||||--||--|||--|--||||||-|-||-||----||-||
query          1146 ATGCCGCGTGTA-TGA-AGAAGGCCTT--CGG-GT-TGTAAAG-TACT-TT---CAGCGG

target          455 --A--A-G--A--ACAAGTGCCGTTCGAATAGGGC----GG-TACC-TTGACGGT-ACCT
                540 --|--|-|--|--|-||||----|---||||---|----|--|-|--||||||-|-|||-
query          1097 GGAGGAAGGGAGTA-AAGT----T---AATA---CCTTTG-CT-C-ATTGACG-TTACC-

target          499 AAC-CAGAA-A-GCCAC-GGCTAACTAC-GTGCCAGCAGCCGCGGTAATACGT-AGG-TG
                600 --|-|||||-|-||-||-||||||||-|-|||||||||||||||||||||||--|||-||
query          1053 --CGCAGAAGAAGC-ACCGGCTAACT-CCGTGCCAGCAGCCGCGGTAATACG-GAGGGTG

target          552 GCAAGCGTTG--TCCGGAATTA-TTGGGCGTAAAG-GGCT-CGCAGGCGGTTTC-TTAAG
                660 -||||||||---||-|||||||-|-||||||||||-|-|--||||||||||||--|||||
query           998 -CAAGCGTT-AATC-GGAATTACT-GGGCGTAAAGCG-C-ACGCAGGCGGTTT-GTTAAG

target          606 TCT-GATGTGAAAG-CCCCCGG-CTCAACC-GGGGAGGG--T-CAT-TGGA-AACTGGGG
                720 ||--|||||||||--||||-||-|||||||-|||-|-----|-|||-||-|-|-||||--
query           945 TC-AGATGTGAAA-TCCCC-GGGCTCAACCTGGG-A---ACTGCATCTG-ATA-CTGG--

target          657 -AA-CTTGAGTGCA--G-AAGAGGAGAGTGG-A-A-TTCCACG-TGTAGCGGTGAAATGC
                780 -||-|||||||-|---|-|-||||-|-|-||-|-|-|||||-|-||||||||||||||||
query           896 CAAGCTTGAGT-C-TCGTA-GAGG-G-G-GGTAGAATTCCA-GGTGTAGCGGTGAAATGC

target          708 GTAGAGATG-TGGAGGAAC-ACCAG-TGGCGAAGGCGA-CTCTC--TGGT-CTGTAA--C
                840 ||||||||--||||||||--|||-|-|||||||||||--|-|-|--|||--|-|-||--|
query           843 GTAGAGAT-CTGGAGGAA-TACC-GGTGGCGAAGGCG-GC-C-CCCTGG-AC-G-AAGAC

target          759 TGACGCTG-AGGA-GCGAAAGCGTGGGGAGCGAA-CAGGATTAGATACCCTGGTAGTCCA
                900 |||||||--|||--|||||||||||||||||-||-|||||||||||||||||||||||||
query           792 TGACGCT-CAGG-TGCGAAAGCGTGGGGAGC-AAACAGGATTAGATACCCTGGTAGTCCA

target          816 CGCCGTAAACGATGAGT-G-CTAAGTGTT-AGGGGGTT-TCCGCCCCTT-AGTGC-TG-C
                960 ||||||||||||||--|-|-||---||---|||---||-|--||||-||-||-||-||-|
query           735 CGCCGTAAACGATG--TCGACT---TG--GAGG---TTGT--GCCC-TTGAG-GCGTGGC

target          869 ------AGCTAACGCA-TTAAG-C-ACTCCGCCTGGGGAGTACGGTC-GCAAGACTG--A
               1020 ------|||||||||--|||||-|-||-|-|||||||||||||||-|-|||||---|--|
query           689 TTCCGGAGCTAACGC-GTTAAGTCGAC-C-GCCTGGGGAGTACGG-CCGCAAG---GTTA

target          917 AA-CTCAAA-GGAATTGACGGGGGCCCGCACAAGCGGTGGAGCATGTGGTTTAATTCGAA
               1080 ||-||||||-|-|||||||||||||||||||||||||||||||||||||||||||||||-
query           636 AAACTCAAATG-AATTGACGGGGGCCCGCACAAGCGGTGGAGCATGTGGTTTAATTCGA-

target          975 -GCAACGCGAAGAACCTTACCA-GGTCTTGACATCCTCTGACA-A--T--CCTAGAGATA
               1140 -||||||||||||||||||||--|||||||||||||----|||-|--|--||-||||||-
query           578 TGCAACGCGAAGAACCTTACC-TGGTCTTGACATCC----ACAGAACTTTCC-AGAGAT-

target         1028 GGAC--G-T-CCCCTTCGGGGGCAGA--GTGA--CAGGTGG-TGCATGG-TTGTCGTCAG
               1200 |||---|-|-||--||||||---|-|--||||--||||||--|||||||-|-||||||||
query           525 GGA-TTGGTGCC--TTCGGG---A-ACTGTGAGACAGGTG-CTGCATGGCT-GTCGTCAG

target         1078 CTCGTGTC-GTGAGA-TGTTGGGTTAAGTCCCGCAACGAGCGCAACCCTTGATCTTA--G
               1260 |||||||--||||-|-||||||||||||||||||||||||||||||||||-|||||---|
query           474 CTCGTGT-TGTGA-AATGTTGGGTTAAGTCCCGCAACGAGCGCAACCCTT-ATCTT-TTG

target         1134 TTGCCAGCA--TTCA-GTTG--GGC-A-CTCTAA-GGT-GACTGCC-GGTGAC-AAACC-
               1320 ||||||||---|-|--|--|--||--|-|||-||-||--|||||||-|-|||--||||--
query           418 TTGCCAGC-GGT-C-CG--GCCGG-GAACTC-AAAGG-AGACTGCCAG-TGA-TAAAC-T

target         1182 GGAGGAAGGTGGGGATGACGTCAAA-TCATCATG-CCCCTTAT-GACCT-GGGCTACACA
               1380 ||||||||||||||||||||||||--||||||||-|||-|||--||||--||||||||||
query           369 GGAGGAAGGTGGGGATGACGTCAA-GTCATCATGGCCC-TTA-CGACC-AGGGCTACACA

target         1238 CGTGCTACAATGGACAG-A-ACAAAG-GGCA-GCGAAACC--GCGAG-GTT-AAGCC--A
               1440 |||||||||||||-|-|-|-||||||-|--|-||||--||--|||||-|---||||---|
query           313 CGTGCTACAATGG-C-GCATACAAAGAG--AAGCGA--CCTCGCGAGAG--CAAGC-GGA

target         1288 ATCC-CAC-AAA-T-CTGTTC-TCAGTTC-GGATC-GC-AGTCTGCAACTCGACTGCG--
               1500 --||-||--|||-|-|-||-|-|-|||-|-||||--|--||||||||||||||||-|---
query           262 --CCTCA-TAAAGTGC-GT-CGT-AGT-CCGGAT-TG-GAGTCTGCAACTCGACT-C-CA

target         1338 TGAAGCT-GGAATCGCTAGTAATCGC-GGATCAGCA-TGCCG-CGGTGAATACGTTCCCG
               1560 |||||-|-|||||||||||||||||--|||||||-|-||||--|||||||||||||||||
query           213 TGAAG-TCGGAATCGCTAGTAATCG-TGGATCAG-AATGCC-ACGGTGAATACGTTCCCG

target         1394 GGCCTTGTACACACCGCCCGTCACACCAC-GAG-AGT---TTGT-AACACCC-GAAGTC-
               1620 ||||||||||||||||||||||||||||--|-|-|||---|||--||-|----|||||--
query           157 GGCCTTGTACACACCGCCCGTCACACCA-TG-GGAGTGGGTTG-CAA-A---AGAAGT-A

target         1446 GGTGAGG-T-AACCTTTTA-GG-AG--C-C--AGCCG-CC---GAAGGTGGGA--CAGAT
               1680 |||-||--|-||||||----||-||--|-|--|-||--|----|----||--|--||--|
query           105 GGT-AG-CTTAACCTT---CGGGAGGGCGCTTA-CC-AC-TTTG----TG--ATTCA--T

target         1491 GA-TTGGGGTGAAGTCGTAACAAGGTAG-CCGTATCGGAAGG----TGCGGCT-GGATCA
               1740 ||-|-||||||||||||||||||||||--|||||--||--||----|||||-|-||||||
query            61 GACT-GGGGTGAAGTCGTAACAAGGTA-ACCGTA--GG--GGAACCTGCGG-TTGGATCA

target         1544 CCTCCTTTCTA 1555
               1800 |||||||---| 1811
query             8 CCTCCTT---A    0
""",
        )
        self.assertAlmostEqual(alignment.score, 1286.0)
        self.assertEqual(alignment.shape, (2, 1811))
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1286.0; substitution score = 1286.0; gap score = 0.0; 1286 aligned letters; 1286 identities; 0 mismatches; 525 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1286.0:
        substitution_score = 1286.0,
        gap_score = 0.0.
    aligned = 1286:
        identities = 1286,
        mismatches = 0.
    gaps = 525:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 525:
            internal_insertions = 256:
                open_internal_insertions = 202,
                extend_internal_insertions = 54;
            internal_deletions = 269:
                open_internal_deletions = 199,
                extend_internal_deletions = 70;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 1286)
        self.assertEqual(counts.identities, 1286)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 269)
        self.assertEqual(counts.insertions, 256)
        self.assertAlmostEqual(counts.score, 1286.0)


class TestKeywordArgumentsConstructor(unittest.TestCase):
    def test_confusing_arguments(self):
        aligner = Align.PairwiseAligner(
            mode="local",
            open_gap_score=-0.3,
            extend_gap_score=-0.1,
            open_insertion_score=-0.2,
        )
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  wildcard: None
  match_score: 1.000000
  mismatch_score: 0.000000
  open_internal_insertion_score: -0.200000
  extend_internal_insertion_score: -0.100000
  open_left_insertion_score: -0.200000
  extend_left_insertion_score: -0.100000
  open_right_insertion_score: -0.200000
  extend_right_insertion_score: -0.100000
  open_internal_deletion_score: -0.300000
  extend_internal_deletion_score: -0.100000
  open_left_deletion_score: -0.300000
  extend_left_deletion_score: -0.100000
  open_right_deletion_score: -0.300000
  extend_right_deletion_score: -0.100000
  mode: local
""",
        )


class TestPredefinedScoringSchemes(unittest.TestCase):
    def test_blastn(self):
        aligner = Align.PairwiseAligner(scoring="blastn")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -7.000000
  extend_internal_insertion_score: -2.000000
  open_left_insertion_score: -7.000000
  extend_left_insertion_score: -2.000000
  open_right_insertion_score: -7.000000
  extend_right_insertion_score: -2.000000
  open_internal_deletion_score: -7.000000
  extend_internal_deletion_score: -2.000000
  open_left_deletion_score: -7.000000
  extend_left_deletion_score: -2.000000
  open_right_deletion_score: -7.000000
  extend_right_deletion_score: -2.000000
  mode: global
"""
            % id(aligner.substitution_matrix),
        )
        self.assertEqual(
            str(aligner.substitution_matrix[:, :]),
            """\
     A    T    G    C    S    W    R    Y    K    M    B    V    H    D    N
A  2.0 -3.0 -3.0 -3.0 -3.0 -1.0 -1.0 -3.0 -3.0 -1.0 -3.0 -1.0 -1.0 -1.0 -2.0
T -3.0  2.0 -3.0 -3.0 -3.0 -1.0 -3.0 -1.0 -1.0 -3.0 -1.0 -3.0 -1.0 -1.0 -2.0
G -3.0 -3.0  2.0 -3.0 -1.0 -3.0 -1.0 -3.0 -1.0 -3.0 -1.0 -1.0 -3.0 -1.0 -2.0
C -3.0 -3.0 -3.0  2.0 -1.0 -3.0 -3.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -3.0 -2.0
S -3.0 -3.0 -1.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
W -1.0 -1.0 -3.0 -3.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
R -1.0 -3.0 -1.0 -3.0 -1.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
Y -3.0 -1.0 -3.0 -1.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
K -3.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -2.0
M -1.0 -3.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
B -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
V -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
H -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
D -1.0 -1.0 -1.0 -3.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0
N -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0 -2.0
""",
        )

    def test_megablast(self):
        aligner = Align.PairwiseAligner(scoring="megablast")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -2.500000
  extend_internal_insertion_score: -2.500000
  open_left_insertion_score: -2.500000
  extend_left_insertion_score: -2.500000
  open_right_insertion_score: -2.500000
  extend_right_insertion_score: -2.500000
  open_internal_deletion_score: -2.500000
  extend_internal_deletion_score: -2.500000
  open_left_deletion_score: -2.500000
  extend_left_deletion_score: -2.500000
  open_right_deletion_score: -2.500000
  extend_right_deletion_score: -2.500000
  mode: global
"""
            % id(aligner.substitution_matrix),
        )
        self.assertEqual(
            str(aligner.substitution_matrix[:, :]),
            """\
     A    T    G    C    S    W    R    Y    K    M    B    V    H    D    N
A  1.0 -2.0 -2.0 -2.0 -2.0 -1.0 -1.0 -2.0 -2.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0
T -2.0  1.0 -2.0 -2.0 -2.0 -1.0 -2.0 -1.0 -1.0 -2.0 -1.0 -2.0 -1.0 -1.0 -1.0
G -2.0 -2.0  1.0 -2.0 -1.0 -2.0 -1.0 -2.0 -1.0 -2.0 -1.0 -1.0 -2.0 -1.0 -1.0
C -2.0 -2.0 -2.0  1.0 -1.0 -2.0 -2.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -2.0 -1.0
S -2.0 -2.0 -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
W -1.0 -1.0 -2.0 -2.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
R -1.0 -2.0 -1.0 -2.0 -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
Y -2.0 -1.0 -2.0 -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
K -2.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0
M -1.0 -2.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
B -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
V -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
H -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
D -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
N -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0
""",
        )

    def test_blastp(self):
        aligner = Align.PairwiseAligner(scoring="blastp")
        self.assertEqual(
            str(aligner),
            """\
Pairwise sequence aligner with parameters
  substitution_matrix: <Array object at 0x%x>
  open_internal_insertion_score: -12.000000
  extend_internal_insertion_score: -1.000000
  open_left_insertion_score: -12.000000
  extend_left_insertion_score: -1.000000
  open_right_insertion_score: -12.000000
  extend_right_insertion_score: -1.000000
  open_internal_deletion_score: -12.000000
  extend_internal_deletion_score: -1.000000
  open_left_deletion_score: -12.000000
  extend_left_deletion_score: -1.000000
  open_right_deletion_score: -12.000000
  extend_right_deletion_score: -1.000000
  mode: global
"""
            % id(aligner.substitution_matrix),
        )
        self.assertEqual(
            str(aligner.substitution_matrix[:, :]),
            """\
     A    B    C    D    E    F    G    H    I    J    K    L    M    N    O    P    Q    R    S    T    U    V    W    X    Y    Z    *
A  4.0 -2.0  0.0 -2.0 -1.0 -2.0  0.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0 -2.0 -1.0 -1.0 -1.0 -1.0  1.0  0.0  0.0  0.0 -3.0 -1.0 -2.0 -1.0 -4.0
B -2.0  4.0 -3.0  4.0  1.0 -3.0 -1.0  0.0 -3.0 -3.0  0.0 -4.0 -3.0  4.0 -1.0 -2.0  0.0 -1.0  0.0 -1.0 -3.0 -3.0 -4.0 -1.0 -3.0  0.0 -4.0
C  0.0 -3.0  9.0 -3.0 -4.0 -2.0 -3.0 -3.0 -1.0 -1.0 -3.0 -1.0 -1.0 -3.0 -1.0 -3.0 -3.0 -3.0 -1.0 -1.0  9.0 -1.0 -2.0 -1.0 -2.0 -3.0 -4.0
D -2.0  4.0 -3.0  6.0  2.0 -3.0 -1.0 -1.0 -3.0 -3.0 -1.0 -4.0 -3.0  1.0 -1.0 -1.0  0.0 -2.0  0.0 -1.0 -3.0 -3.0 -4.0 -1.0 -3.0  1.0 -4.0
E -1.0  1.0 -4.0  2.0  5.0 -3.0 -2.0  0.0 -3.0 -3.0  1.0 -3.0 -2.0  0.0 -1.0 -1.0  2.0  0.0  0.0 -1.0 -4.0 -2.0 -3.0 -1.0 -2.0  4.0 -4.0
F -2.0 -3.0 -2.0 -3.0 -3.0  6.0 -3.0 -1.0  0.0  0.0 -3.0  0.0  0.0 -3.0 -1.0 -4.0 -3.0 -3.0 -2.0 -2.0 -2.0 -1.0  1.0 -1.0  3.0 -3.0 -4.0
G  0.0 -1.0 -3.0 -1.0 -2.0 -3.0  6.0 -2.0 -4.0 -4.0 -2.0 -4.0 -3.0  0.0 -1.0 -2.0 -2.0 -2.0  0.0 -2.0 -3.0 -3.0 -2.0 -1.0 -3.0 -2.0 -4.0
H -2.0  0.0 -3.0 -1.0  0.0 -1.0 -2.0  8.0 -3.0 -3.0 -1.0 -3.0 -2.0  1.0 -1.0 -2.0  0.0  0.0 -1.0 -2.0 -3.0 -3.0 -2.0 -1.0  2.0  0.0 -4.0
I -1.0 -3.0 -1.0 -3.0 -3.0  0.0 -4.0 -3.0  4.0  3.0 -3.0  2.0  1.0 -3.0 -1.0 -3.0 -3.0 -3.0 -2.0 -1.0 -1.0  3.0 -3.0 -1.0 -1.0 -3.0 -4.0
J -1.0 -3.0 -1.0 -3.0 -3.0  0.0 -4.0 -3.0  3.0  3.0 -3.0  3.0  2.0 -3.0 -1.0 -3.0 -2.0 -2.0 -2.0 -1.0 -1.0  2.0 -2.0 -1.0 -1.0 -3.0 -4.0
K -1.0  0.0 -3.0 -1.0  1.0 -3.0 -2.0 -1.0 -3.0 -3.0  5.0 -2.0 -1.0  0.0 -1.0 -1.0  1.0  2.0  0.0 -1.0 -3.0 -2.0 -3.0 -1.0 -2.0  1.0 -4.0
L -1.0 -4.0 -1.0 -4.0 -3.0  0.0 -4.0 -3.0  2.0  3.0 -2.0  4.0  2.0 -3.0 -1.0 -3.0 -2.0 -2.0 -2.0 -1.0 -1.0  1.0 -2.0 -1.0 -1.0 -3.0 -4.0
M -1.0 -3.0 -1.0 -3.0 -2.0  0.0 -3.0 -2.0  1.0  2.0 -1.0  2.0  5.0 -2.0 -1.0 -2.0  0.0 -1.0 -1.0 -1.0 -1.0  1.0 -1.0 -1.0 -1.0 -1.0 -4.0
N -2.0  4.0 -3.0  1.0  0.0 -3.0  0.0  1.0 -3.0 -3.0  0.0 -3.0 -2.0  6.0 -1.0 -2.0  0.0  0.0  1.0  0.0 -3.0 -3.0 -4.0 -1.0 -2.0  0.0 -4.0
O -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -4.0
P -1.0 -2.0 -3.0 -1.0 -1.0 -4.0 -2.0 -2.0 -3.0 -3.0 -1.0 -3.0 -2.0 -2.0 -1.0  7.0 -1.0 -2.0 -1.0 -1.0 -3.0 -2.0 -4.0 -1.0 -3.0 -1.0 -4.0
Q -1.0  0.0 -3.0  0.0  2.0 -3.0 -2.0  0.0 -3.0 -2.0  1.0 -2.0  0.0  0.0 -1.0 -1.0  5.0  1.0  0.0 -1.0 -3.0 -2.0 -2.0 -1.0 -1.0  4.0 -4.0
R -1.0 -1.0 -3.0 -2.0  0.0 -3.0 -2.0  0.0 -3.0 -2.0  2.0 -2.0 -1.0  0.0 -1.0 -2.0  1.0  5.0 -1.0 -1.0 -3.0 -3.0 -3.0 -1.0 -2.0  0.0 -4.0
S  1.0  0.0 -1.0  0.0  0.0 -2.0  0.0 -1.0 -2.0 -2.0  0.0 -2.0 -1.0  1.0 -1.0 -1.0  0.0 -1.0  4.0  1.0 -1.0 -2.0 -3.0 -1.0 -2.0  0.0 -4.0
T  0.0 -1.0 -1.0 -1.0 -1.0 -2.0 -2.0 -2.0 -1.0 -1.0 -1.0 -1.0 -1.0  0.0 -1.0 -1.0 -1.0 -1.0  1.0  5.0 -1.0  0.0 -2.0 -1.0 -2.0 -1.0 -4.0
U  0.0 -3.0  9.0 -3.0 -4.0 -2.0 -3.0 -3.0 -1.0 -1.0 -3.0 -1.0 -1.0 -3.0 -1.0 -3.0 -3.0 -3.0 -1.0 -1.0  9.0 -1.0 -2.0 -1.0 -2.0 -3.0 -4.0
V  0.0 -3.0 -1.0 -3.0 -2.0 -1.0 -3.0 -3.0  3.0  2.0 -2.0  1.0  1.0 -3.0 -1.0 -2.0 -2.0 -3.0 -2.0  0.0 -1.0  4.0 -3.0 -1.0 -1.0 -2.0 -4.0
W -3.0 -4.0 -2.0 -4.0 -3.0  1.0 -2.0 -2.0 -3.0 -2.0 -3.0 -2.0 -1.0 -4.0 -1.0 -4.0 -2.0 -3.0 -3.0 -2.0 -2.0 -3.0 11.0 -1.0  2.0 -2.0 -4.0
X -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -4.0
Y -2.0 -3.0 -2.0 -3.0 -2.0  3.0 -3.0  2.0 -1.0 -1.0 -2.0 -1.0 -1.0 -2.0 -1.0 -3.0 -1.0 -2.0 -2.0 -2.0 -2.0 -1.0  2.0 -1.0  7.0 -2.0 -4.0
Z -1.0  0.0 -3.0  1.0  4.0 -3.0 -2.0  0.0 -3.0 -3.0  1.0 -3.0 -1.0  0.0 -1.0 -1.0  4.0  0.0  0.0 -1.0 -3.0 -2.0 -2.0 -1.0 -2.0  4.0 -4.0
* -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0 -4.0  1.0
""",
        )


class TestUnicodeStrings(unittest.TestCase):
    def test_needlemanwunsch_simple1(self):
        seq1 = "ĞĀĀČŦ"
        seq2 = "ĞĀŦ"
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        self.assertEqual(aligner.algorithm, "Needleman-Wunsch")
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (2 alignments; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
ĞĀĀČŦ
||--|
ĞĀ--Ŧ
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 3.0; gap score = -2.0; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 3.0,
        gap_score = -2.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 2)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 1.0)
        alignment = alignments[1]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
ĞĀĀČŦ
|-|-|
Ğ-Ā-Ŧ
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned,
                np.array([[[0, 1], [2, 3], [4, 5]], [[0, 1], [1, 2], [2, 3]]]),
            )
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 3.0; gap score = -2.0; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 3.0,
        gap_score = -2.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 2,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 2)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 1.0)

    def test_needlemanwunsch_simple1_fogsaa(self):
        seq1 = "ĞĀĀČŦ"
        seq2 = "ĞĀŦ"
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        score = aligner.score(seq1, seq2)
        self.assertAlmostEqual(score, 1.0)
        alignments = aligner.align(seq1, seq2)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.0)
        self.assertEqual(
            str(alignment),
            """\
ĞĀĀČŦ
||--|
ĞĀ--Ŧ
""",
        )
        self.assertEqual(alignment.shape, (2, 5))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 2], [4, 5]], [[0, 2], [2, 3]]])
            )
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.0; substitution score = 3.0; gap score = -2.0; 3 aligned letters; 3 identities; 0 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.0:
        substitution_score = 3.0,
        gap_score = -2.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 2:
                open_internal_deletions = 1,
                extend_internal_deletions = 1;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 2)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 1.0)

    def test_align_affine1_score(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "global"
        aligner.match_score = 0
        aligner.mismatch_score = -1
        aligner.open_gap_score = -5
        aligner.extend_gap_score = -1
        self.assertEqual(aligner.algorithm, "Gotoh global alignment algorithm")
        score = aligner.score("いい", "あいいう")
        self.assertAlmostEqual(score, -7.0)
        alignments = aligner.align("いい", "あいいう")
        self.assertEqual(len(alignments), 2)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
--いい
--|.
あいいう
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = -1.0; gap score = -6.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = -1.0,
        gap_score = -6.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 2)
        self.assertAlmostEqual(counts.score, -7.0)
        alignment = alignments[1]
        self.assertEqual(
            str(alignment),
            """\
いい--
.|--
あいいう
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = -1.0; gap score = -6.0; 2 aligned letters; 1 identities; 1 mismatches; 2 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = -1.0,
        gap_score = -6.0.
    aligned = 2:
        identities = 1,
        mismatches = 1.
    gaps = 2:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 1)
        self.assertEqual(counts.deletions, 0)
        self.assertEqual(counts.insertions, 2)
        self.assertAlmostEqual(counts.score, -7.0)

    def test_align_affine1_score_fogsaa(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "fogsaa"
        aligner.match_score = 0
        aligner.mismatch_score = -1
        aligner.open_gap_score = -5
        aligner.extend_gap_score = -1
        self.assertEqual(
            aligner.algorithm, "Fast Optimal Global Sequence Alignment Algorithm"
        )
        score = aligner.score("いい", "あいいう")
        self.assertAlmostEqual(score, -7.0)

    def test_smithwaterman(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.gap_score = -0.1
        self.assertEqual(aligner.algorithm, "Smith-Waterman")
        score = aligner.score("ℵℷℶℷ", "ℸℵℶℸ")
        self.assertAlmostEqual(score, 1.9)
        alignments = aligner.align("ℵℷℶℷ", "ℸℵℶℸ")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
ℵℷℶ
|-|
ℵ-ℶ
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[1, 2], [2, 3]]])
            )
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 1)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 1.9)

    def test_gotoh_local(self):
        aligner = Align.PairwiseAligner()
        aligner.mode = "local"
        aligner.open_gap_score = -0.1
        aligner.extend_gap_score = 0.0
        self.assertEqual(aligner.algorithm, "Gotoh local alignment algorithm")
        score = aligner.score("生物科物", "学生科学")
        self.assertAlmostEqual(score, 1.9)
        alignments = aligner.align("生物科物", "学生科学")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=1.9) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 1.9)
        self.assertEqual(
            str(alignment),
            """\
生物科
|-|
生-科
""",
        )
        self.assertEqual(alignment.shape, (2, 3))
        self.assertTrue(
            np.array_equal(
                alignment.aligned, np.array([[[0, 1], [2, 3]], [[1, 2], [2, 3]]])
            )
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1.9; substitution score = 2.0; gap score = -0.1; 2 aligned letters; 2 identities; 0 mismatches; 1 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1.9:
        substitution_score = 2.0,
        gap_score = -0.1.
    aligned = 2:
        identities = 2,
        mismatches = 0.
    gaps = 1:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 2)
        self.assertEqual(counts.identities, 2)
        self.assertEqual(counts.mismatches, 0)
        self.assertEqual(counts.deletions, 1)
        self.assertEqual(counts.insertions, 0)
        self.assertAlmostEqual(counts.score, 1.9)


class TestAlignerPickling(unittest.TestCase):
    def test_pickle_aligner_match_mismatch(self):
        import pickle

        aligner = Align.PairwiseAligner()
        aligner.wildcard = "X"
        aligner.match_score = 3
        aligner.mismatch_score = -2
        aligner.open_internal_insertion_score = -2.5
        aligner.extend_internal_insertion_score = -3.5
        aligner.open_left_insertion_score = -2.5
        aligner.extend_left_insertion_score = -3.5
        aligner.open_right_insertion_score = -4
        aligner.extend_right_insertion_score = -4
        aligner.open_internal_deletion_score = -0.1
        aligner.extend_internal_deletion_score = -2
        aligner.open_left_deletion_score = -9
        aligner.extend_left_deletion_score = +1
        aligner.open_right_deletion_score = -1
        aligner.extend_right_deletion_score = -2
        aligner.epsilon = 0.5e-6
        aligner.mode = "local"
        state = pickle.dumps(aligner)
        pickled_aligner = pickle.loads(state)
        self.assertEqual(aligner.wildcard, pickled_aligner.wildcard)
        self.assertAlmostEqual(aligner.match_score, pickled_aligner.match_score)
        self.assertAlmostEqual(aligner.mismatch_score, pickled_aligner.mismatch_score)
        self.assertIsNone(pickled_aligner.substitution_matrix)
        self.assertAlmostEqual(
            aligner.open_internal_insertion_score,
            pickled_aligner.open_internal_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_internal_insertion_score,
            pickled_aligner.extend_internal_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.open_left_insertion_score,
            pickled_aligner.open_left_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_left_insertion_score,
            pickled_aligner.extend_left_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.open_right_insertion_score,
            pickled_aligner.open_right_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_right_insertion_score,
            pickled_aligner.extend_right_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.open_internal_deletion_score,
            pickled_aligner.open_internal_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_internal_deletion_score,
            pickled_aligner.extend_internal_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.open_left_deletion_score, pickled_aligner.open_left_deletion_score
        )
        self.assertAlmostEqual(
            aligner.extend_left_deletion_score,
            pickled_aligner.extend_left_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.open_right_deletion_score,
            pickled_aligner.open_right_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_right_deletion_score,
            pickled_aligner.extend_right_deletion_score,
        )
        self.assertEqual(aligner.mode, pickled_aligner.mode)
        self.assertAlmostEqual(aligner.epsilon, pickled_aligner.epsilon)
        self.assertEqual(aligner.algorithm, pickled_aligner.algorithm)

    def test_pickle_aligner_substitution_matrix(self):
        try:
            from Bio.Align import substitution_matrices
        except ImportError:
            return
        import pickle

        aligner = Align.PairwiseAligner()
        aligner.wildcard = "N"
        aligner.substitution_matrix = substitution_matrices.load("BLOSUM80")
        aligner.open_internal_insertion_score = -5
        aligner.extend_internal_insertion_score = -3
        aligner.open_left_insertion_score = -2
        aligner.extend_left_insertion_score = -3
        aligner.open_right_insertion_score = -4.5
        aligner.extend_right_insertion_score = -4.3
        aligner.open_internal_deletion_score = -2
        aligner.extend_internal_deletion_score = -2.5
        aligner.open_left_deletion_score = -9.1
        aligner.extend_left_deletion_score = +1.7
        aligner.open_right_deletion_score = -1.9
        aligner.extend_right_deletion_score = -2.0
        aligner.mode = "global"
        state = pickle.dumps(aligner)
        pickled_aligner = pickle.loads(state)
        self.assertEqual(aligner.wildcard, pickled_aligner.wildcard)
        self.assertIsNone(pickled_aligner.match_score)
        self.assertIsNone(pickled_aligner.mismatch_score)
        self.assertTrue(
            (aligner.substitution_matrix == pickled_aligner.substitution_matrix).all()
        )
        self.assertEqual(
            aligner.substitution_matrix.alphabet,
            pickled_aligner.substitution_matrix.alphabet,
        )
        self.assertAlmostEqual(
            aligner.open_internal_insertion_score,
            pickled_aligner.open_internal_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_internal_insertion_score,
            pickled_aligner.extend_internal_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.open_left_insertion_score,
            pickled_aligner.open_left_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_left_insertion_score,
            pickled_aligner.extend_left_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.open_right_insertion_score,
            pickled_aligner.open_right_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_right_insertion_score,
            pickled_aligner.extend_right_insertion_score,
        )
        self.assertAlmostEqual(
            aligner.open_internal_deletion_score,
            pickled_aligner.open_internal_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_internal_deletion_score,
            pickled_aligner.extend_internal_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.open_left_deletion_score, pickled_aligner.open_left_deletion_score
        )
        self.assertAlmostEqual(
            aligner.extend_left_deletion_score,
            pickled_aligner.extend_left_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.open_right_deletion_score,
            pickled_aligner.open_right_deletion_score,
        )
        self.assertAlmostEqual(
            aligner.extend_right_deletion_score,
            pickled_aligner.extend_right_deletion_score,
        )
        self.assertEqual(aligner.mode, pickled_aligner.mode)
        self.assertAlmostEqual(aligner.epsilon, pickled_aligner.epsilon)
        self.assertEqual(aligner.algorithm, pickled_aligner.algorithm)

    def test_pickle_aligner_alignment_consistent(self):
        import pickle

        targ = "MTPSDISGYDYGRVEKSPITDLEFDLLKKTVMLGEEDVMYLKKAADVLKDQVDEILDLAGGWAASNEHLIYYGSNPDTGAPIKEYLERVRARIGAWVL"
        query = "MTIKEMPQPKTFGELKNLPLLNTDKPVQALMKIADELGEIFKFEAPGRVTRYLSSQRLIKEACDESRFDKNLSQALKFARDFAGDGLVTSWTHEKNWKKAHNIL"
        aligner = Align.PairwiseAligner(scoring="blastp")
        aligner_alignments = aligner.align(targ, query)
        pickled_aligner = pickle.loads(pickle.dumps(aligner))
        pickled_aligner_alignments = pickled_aligner.align(targ, query)
        self.assertEqual(len(aligner_alignments), len(pickled_aligner_alignments))
        for i in range(len(aligner_alignments)):
            self.assertEqual(aligner_alignments[i], pickled_aligner_alignments[i])


class TestAlignmentFormat(unittest.TestCase):
    def test_alignment_simple(self):
        chromosome = "ACGATCAGCGAGCATNGAGCACTACGACAGCGAGTGACCACTATTCGCGATCAGGAGCAGATACTTTACGAGCATCGGC"
        transcript = "AGCATCGAGCGACTTGAGTACTATTCATACTTTCGAGC"
        aligner = Align.PairwiseAligner()
        aligner.extend_deletion_score = 0
        aligner.open_deletion_score = -3
        aligner.insertion_score = -3
        aligner.end_gap_score = 0
        aligner.mismatch = -1
        alignments = aligner.align(chromosome, transcript)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=19) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 19.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGATCAGCGAGCATNGAGC-ACTACGACAGCGAGTGACCACTATTCGCGATCAGGAGCA
                  0 ----------|||||.||||-|||-----------|||..|||||||-------------
query             0 ----------AGCATCGAGCGACT-----------TGAGTACTATTC-------------

target           59 GATACTTTACGAGCATCGGC 79
                 60 -|||||||-|||||------ 80
query            26 -ATACTTT-CGAGC------ 38
""",
        )
        self.assertEqual(alignment.shape, (2, 80))
        self.assertEqual(
            alignment.format("psl"),
            """\
34	2	0	1	1	1	3	26	+	query	38	0	38	target	79	10	73	5	10,3,12,7,5,	0,11,14,26,33,	10,20,34,60,68,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	10	73	query	19	+	10	73	0	5	10,3,12,7,5,	0,10,24,50,58,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	10D10M1I3M11D12M14D7M1D5M6D	*	0	0	AGCATCGAGCGACTTGAGTACTATTCATACTTTCGAGC	*	AS:i:19
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 19.0)
        alignments = aligner.align(
            chromosome, reverse_complement(transcript), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=19) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 19.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGATCAGCGAGCATNGAGC-ACTACGACAGCGAGTGACCACTATTCGCGATCAGGAGCA
                  0 ----------|||||.||||-|||-----------|||..|||||||-------------
query            38 ----------AGCATCGAGCGACT-----------TGAGTACTATTC-------------

target           59 GATACTTTACGAGCATCGGC 79
                 60 -|||||||-|||||------ 80
query            12 -ATACTTT-CGAGC------  0
""",
        )
        self.assertEqual(alignment.shape, (2, 80))
        self.assertEqual(
            alignment.format("psl"),
            """\
34	2	0	1	1	1	3	26	-	query	38	0	38	target	79	10	73	5	10,3,12,7,5,	0,11,14,26,33,	10,20,34,60,68,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	10	73	query	19	-	10	73	0	5	10,3,12,7,5,	0,10,24,50,58,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	10D10M1I3M11D12M14D7M1D5M6D	*	0	0	AGCATCGAGCGACTTGAGTACTATTCATACTTTCGAGC	*	AS:i:19
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 19.0)

    def test_alignment_end_gap(self):
        aligner = Align.PairwiseAligner()
        aligner.gap_score = -1
        aligner.end_gap_score = 0
        aligner.mismatch = -10
        alignments = aligner.align("ACGTAGCATCAGC", "CCCCACGTAGCATCAGC")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        self.assertAlmostEqual(alignments.score, 13.0)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ----ACGTAGCATCAGC 13
                  0 ----||||||||||||| 17
query             0 CCCCACGTAGCATCAGC 17
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	+	query	17	4	17	target	13	0	13	1	13,	4,	0,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	0	13	query	13	+	0	13	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	4I13M	*	0	0	CCCCACGTAGCATCAGC	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align(
            "ACGTAGCATCAGC", reverse_complement("CCCCACGTAGCATCAGC"), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        self.assertAlmostEqual(alignments.score, 13.0)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ----ACGTAGCATCAGC 13
                  0 ----||||||||||||| 17
query            17 CCCCACGTAGCATCAGC  0
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	-	query	17	0	13	target	13	0	13	1	13,	4,	0,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	0	13	query	13	-	0	13	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	4I13M	*	0	0	CCCCACGTAGCATCAGC	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align("CCCCACGTAGCATCAGC", "ACGTAGCATCAGC")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 CCCCACGTAGCATCAGC 17
                  0 ----||||||||||||| 17
query             0 ----ACGTAGCATCAGC 13
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	+	query	13	0	13	target	17	4	17	1	13,	0,	4,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	4	17	query	13	+	4	17	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	4D13M	*	0	0	ACGTAGCATCAGC	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align(
            "CCCCACGTAGCATCAGC", reverse_complement("ACGTAGCATCAGC"), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 CCCCACGTAGCATCAGC 17
                  0 ----||||||||||||| 17
query            13 ----ACGTAGCATCAGC  0
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	-	query	13	0	13	target	17	4	17	1	13,	0,	4,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	4	17	query	13	-	4	17	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	4D13M	*	0	0	ACGTAGCATCAGC	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align("ACGTAGCATCAGC", "ACGTAGCATCAGCGGGG")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGTAGCATCAGC---- 13
                  0 |||||||||||||---- 17
query             0 ACGTAGCATCAGCGGGG 17
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	+	query	17	0	13	target	13	0	13	1	13,	0,	0,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	0	13	query	13	+	0	13	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	13M4I	*	0	0	ACGTAGCATCAGCGGGG	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align(
            "ACGTAGCATCAGC", reverse_complement("ACGTAGCATCAGCGGGG"), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGTAGCATCAGC---- 13
                  0 |||||||||||||---- 17
query            17 ACGTAGCATCAGCGGGG  0
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	-	query	17	4	17	target	13	0	13	1	13,	0,	0,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	0	13	query	13	-	0	13	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	13M4I	*	0	0	ACGTAGCATCAGCGGGG	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align("ACGTAGCATCAGCGGGG", "ACGTAGCATCAGC")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGTAGCATCAGCGGGG 17
                  0 |||||||||||||---- 17
query             0 ACGTAGCATCAGC---- 13
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	+	query	13	0	13	target	17	0	13	1	13,	0,	0,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	0	13	query	13	+	0	13	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	13M4D	*	0	0	ACGTAGCATCAGC	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)
        alignments = aligner.align(
            "ACGTAGCATCAGCGGGG", reverse_complement("ACGTAGCATCAGC"), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertAlmostEqual(alignment.score, 13.0)
        self.assertEqual(
            str(alignment),
            """\
target            0 ACGTAGCATCAGCGGGG 17
                  0 |||||||||||||---- 17
query            13 ACGTAGCATCAGC----  0
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
13	0	0	0	0	0	0	0	-	query	13	0	13	target	17	0	13	1	13,	0,	0,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	0	13	query	13	-	0	13	0	1	13,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	13M4D	*	0	0	ACGTAGCATCAGC	*	AS:i:13
""",
        )
        self.assertAlmostEqual(alignment.counts(aligner).score, 13.0)

    def test_alignment_wildcard(self):
        aligner = Align.PairwiseAligner()
        aligner.gap_score = -10
        aligner.mismatch = -2
        aligner.wildcard = "N"
        target = "TTTTTNACGCTCGAGCAGCTACG"
        query = "ACGATCGAGCNGCTACGCCCNC"
        # local alignment
        aligner.mode = "local"
        # use strings for target and query
        alignments = aligner.align(target, query)
        self.assertAlmostEqual(alignments.score, 13.0)
        self.assertEqual(len(alignments), 1)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            6 ACGCTCGAGCAGCTACG 23
                  0 |||.||||||.|||||| 17
query             0 ACGATCGAGCNGCTACG 17
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	+	query	22	0	17	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	+	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	7	255	17M5S	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        alignments = aligner.align(target, reverse_complement(query), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            6 ACGCTCGAGCAGCTACG 23
                  0 |||.||||||.|||||| 17
query            22 ACGATCGAGCNGCTACG  5
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	-	query	22	5	22	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	-	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	7	255	17M5S	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        # use Seq objects for target and query
        alignments = aligner.align(Seq(target), Seq(query))
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            6 ACGCTCGAGCAGCTACG 23
                  0 |||.||||||.|||||| 17
query             0 ACGATCGAGCNGCTACG 17
""",
        )
        self.assertEqual(alignment.shape, (2, 17))
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	+	query	22	0	17	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	+	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	7	255	17M5S	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        alignments = aligner.align(
            Seq(target), Seq(query).reverse_complement(), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            6 ACGCTCGAGCAGCTACG 23
                  0 |||.||||||.|||||| 17
query            22 ACGATCGAGCNGCTACG  5
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 0 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 0:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(alignment.shape, (2, 17))
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	-	query	22	5	22	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	-	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	7	255	17M5S	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        # global alignment
        aligner.mode = "global"
        aligner.end_gap_score = 0
        # use strings for target and query
        alignments = aligner.align(target, query)
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 TTTTTNACGCTCGAGCAGCTACG----- 23
                  0 ------|||.||||||.||||||----- 28
query             0 ------ACGATCGAGCNGCTACGCCCNC 22
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(alignment.shape, (2, 28))
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	+	query	22	0	17	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	+	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	6D17M5I	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        alignments = aligner.align(target, reverse_complement(query), strand="-")
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 TTTTTNACGCTCGAGCAGCTACG----- 23
                  0 ------|||.||||||.||||||----- 28
query            22 ------ACGATCGAGCNGCTACGCCCNC  0
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(alignment.shape, (2, 28))
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	-	query	22	5	22	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	-	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	6D17M5I	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        # use Seq objects for target and query
        alignments = aligner.align(Seq(target), Seq(query))
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 TTTTTNACGCTCGAGCAGCTACG----- 23
                  0 ------|||.||||||.||||||----- 28
query             0 ------ACGATCGAGCNGCTACGCCCNC 22
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(alignment.shape, (2, 28))
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	+	query	22	0	17	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	+	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	0	target	1	255	6D17M5I	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )
        alignments = aligner.align(
            Seq(target), Seq(query).reverse_complement(), strand="-"
        )
        self.assertEqual(
            repr(alignments),
            f"""\
<PairwiseAlignments object (1 alignment; score=13) at {hex(id(alignments))}>""",
        )
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 TTTTTNACGCTCGAGCAGCTACG----- 23
                  0 ------|||.||||||.||||||----- 28
query            22 ------ACGATCGAGCNGCTACGCCCNC  0
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 2 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 2.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 2)
        counts = alignment.counts("N")
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 13.0; substitution score = 13.0; gap score = 0.0; 17 aligned letters; 15 identities; 1 mismatches; 11 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 13.0:
        substitution_score = 13.0,
        gap_score = 0.0.
    aligned = 17:
        identities = 15,
        mismatches = 1.
    gaps = 11:
        left_gaps = 6:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 6:
                open_left_deletions = 1,
                extend_left_deletions = 5;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 5:
            right_insertions = 5:
                open_right_insertions = 1,
                extend_right_insertions = 4;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.aligned, 17)
        self.assertEqual(counts.identities, 15)
        self.assertEqual(counts.mismatches, 1)
        self.assertAlmostEqual(counts.score, 13.0)
        self.assertEqual(alignment.shape, (2, 28))
        self.assertEqual(
            alignment.format("psl"),
            """\
15	1	0	1	0	0	0	0	-	query	22	5	22	target	23	6	23	1	17,	0,	6,
""",
        )
        self.assertEqual(
            alignment.format("bed"),
            """\
target	6	23	query	13	-	6	23	0	1	17,	0,
""",
        )
        self.assertEqual(
            alignment.format("sam"),
            """\
query	16	target	1	255	6D17M5I	*	0	0	ACGATCGAGCNGCTACGCCCNC	*	AS:i:13
""",
        )


class TestGILRelease(unittest.TestCase):
    """GIL release and signal handling in the pairwise alignment kernels.

    The Needleman-Wunsch, Smith-Waterman, and Gotoh kernels release the
    GIL around their dynamic programming loops and briefly reacquire it
    at intervals to check for signals.  These tests check that concurrent
    calls still compute correct results (a data race in the kernels would
    corrupt scores or crash the interpreter), and that SIGINT interrupts
    a long score() call long before it would complete.
    """

    def make_sequences(self, count, length, seed):
        rng = random.Random(seed)
        return [
            "".join(rng.choice("ACGT") for _ in range(length)) for _ in range(count)
        ]

    def check_threaded_matches_serial(self, aligner):
        seqs = self.make_sequences(8, 400, seed=5)
        pairs = [(seqs[2 * i], seqs[2 * i + 1]) for i in range(4)]
        expected = [
            (aligner.score(sA, sB), aligner.align(sA, sB).score) for sA, sB in pairs
        ]

        def work(pair):
            sA, sB = pair
            return (aligner.score(sA, sB), aligner.align(sA, sB).score)

        for _ in range(10):
            with ThreadPoolExecutor(max_workers=4) as executor:
                results = list(executor.map(work, pairs))
            self.assertEqual(results, expected)

    def test_needlemanwunsch_smithwaterman_threaded(self):
        for mode in ("global", "local"):
            aligner = Align.PairwiseAligner()
            aligner.mode = mode
            aligner.open_gap_score = -1
            aligner.extend_gap_score = -1
            self.check_threaded_matches_serial(aligner)

    def test_gotoh_threaded(self):
        for mode in ("global", "local"):
            aligner = Align.PairwiseAligner()
            aligner.mode = mode
            aligner.open_gap_score = -10
            aligner.extend_gap_score = -0.5
            self.check_threaded_matches_serial(aligner)

    def test_inplace_matrix_mutation(self):
        # substitution_matrices.Array supports item assignment, so
        # another thread may mutate the matrix in place while a kernel
        # runs.  The kernels snapshot the matrix at call start, so each
        # call must see one consistent matrix: aligning n letters of
        # "A" against themselves scores exactly n * matrix["A", "A"],
        # never a mixture of the old and new values.
        n = 2000
        seq = "A" * n
        matrix = Array("AC", dims=2)
        matrix["A", "A"] = 1.0
        aligner = Align.PairwiseAligner()
        aligner.substitution_matrix = matrix
        allowed = (1.0 * n, 2.0 * n)
        stop = threading.Event()

        def mutate():
            value = 1.0
            while not stop.is_set():
                value = 3.0 - value  # toggle between 1.0 and 2.0
                matrix["A", "A"] = value

        def work(_):
            scores = [aligner.score(seq, seq) for _ in range(20)]
            self.assertTrue(all(score in allowed for score in scores), scores)

        mutator = threading.Thread(target=mutate)
        mutator.start()
        try:
            with ThreadPoolExecutor(max_workers=4) as executor:
                list(executor.map(work, range(4)))
        finally:
            stop.set()
            mutator.join()

    @unittest.skipUnless(os.name == "posix", "sends SIGINT; requires POSIX")
    def test_sigint_interrupts_score(self):
        # A 500k x 500k alignment needs 2.5e11 matrix cells (many minutes
        # of work on any current machine); if SIGINT is only serviced
        # after the C kernel returns, the 30-second timeout below expires
        # and the test fails.  With the kernels checking signals, the
        # child dies within milliseconds of the signal.
        import signal
        import time

        child_code = """
import random, sys
from Bio import Align
rng = random.Random(11)
n = 500000
sA = "".join(rng.choice("ACGT") for _ in range(n))
sB = "".join(rng.choice("ACGT") for _ in range(n))
aligner = Align.PairwiseAligner()
print("started", flush=True)
try:
    aligner.score(sA, sB)
except KeyboardInterrupt:
    sys.exit(42)
"""
        process = subprocess.Popen(
            [sys.executable, "-c", child_code],
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            text=True,
        )
        try:
            self.assertEqual(process.stdout.readline().strip(), "started")
            time.sleep(1.0)  # let the child enter the kernel
            process.send_signal(signal.SIGINT)
            try:
                process.communicate(timeout=30)
            except subprocess.TimeoutExpired:
                process.kill()
                process.communicate()
                self.fail("SIGINT did not interrupt aligner.score()")
            self.assertEqual(process.returncode, 42)
        finally:
            if process.poll() is None:
                process.kill()
                process.communicate()
            process.stdout.close()


class TestAlgorithmRestrictions(unittest.TestCase):
    def test_fogsaa_restrictions(self):
        aligner = Align.PairwiseAligner(mode="fogsaa")
        aligner.match_score = -1
        with self.assertWarns(BiopythonWarning):
            aligner.score("AAAAAAAAAAAA", "AAAAATAAAAAA")
        aligner.mismatch_score = 1
        with self.assertWarns(BiopythonWarning):
            aligner.score("AAAAAAAAAAAA", "AAAAATAAAAAA")
        aligner.gap_score = 1
        with self.assertWarns(BiopythonWarning):
            aligner.score("AAAAAAAAAAAA", "AAAAATAAAAAA")


class TestCounts(unittest.TestCase):
    def check_counts(self, counts):
        self.assertEqual(counts.left_insertions, 2)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 0)
        self.assertEqual(counts.internal_deletions, 1)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 7)
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)

    def check_counts_and_score(self, counts):
        self.check_counts(counts)
        self.assertAlmostEqual(counts.score, -7.0)

    def test_string_bytes(self):
        aligner = Align.PairwiseAligner()
        aligner.mismatch_score = -1
        aligner.gap_score = -1
        alignments = aligner.align("TTACGTCCCCCCC", "ACTTTGT")
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 --TTACGTCCCCCCC 13
                  0 --||.-||------- 15
query             0 ACTTT-GT-------  7
""",
        )
        self.assertAlmostEqual(alignment.score, -7.0)
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align("TTACGTCCCCCCC", b"ACTTTGT")
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
-- -- T  T  A  C G  T  C C C C C C C
-- -- .  .  .  - .  .  - - - - - - -
65 67 84 84 84 - 71 84 - - - - - - -
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align(b"TTACGTCCCCCCC", "ACTTTGT")
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
- - 84 84 65 67 71 84 67 67 67 67 67 67 67
- - .  .  .  -- .  .  -- -- -- -- -- -- --
A C T  T  T  -- G  T  -- -- -- -- -- -- --
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align(b"TTACGTCCCCCCC", b"ACTTTGT")
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
-- -- 84 84 65 67 71 84 67 67 67 67 67 67 67
-- -- || || .. -- || || -- -- -- -- -- -- --
65 67 84 84 84 -- 71 84 -- -- -- -- -- -- --
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align(Seq("TTACGTCCCCCCC"), Seq("ACTTTGT"))
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 --TTACGTCCCCCCC 13
                  0 --||.-||------- 15
query             0 ACTTT-GT-------  7
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align(Seq("TTACGTCCCCCCC"), "ACTTTGT")
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 --TTACGTCCCCCCC 13
                  0 --||.-||------- 15
query             0 ACTTT-GT-------  7
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align("TTACGTCCCCCCC", Seq("ACTTTGT"))
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
target            0 --TTACGTCCCCCCC 13
                  0 --||.-||------- 15
query             0 ACTTT-GT-------  7
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align(Seq("TTACGTCCCCCCC"), b"ACTTTGT")
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
-- -- T  T  A  C G  T  C C C C C C C
-- -- .  .  .  - .  .  - - - - - - -
65 67 84 84 84 - 71 84 - - - - - - -
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)
        alignments = aligner.align(b"TTACGTCCCCCCC", Seq("ACTTTGT"))
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
- - 84 84 65 67 71 84 67 67 67 67 67 67 67
- - .  .  .  -- .  .  -- -- -- -- -- -- --
A C T  T  T  -- G  T  -- -- -- -- -- -- --
""",
        )
        counts = alignment.counts()
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts(counts)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -7.0; substitution score = 3.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 10 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -7.0:
        substitution_score = 3.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 10:
        left_gaps = 2:
            left_insertions = 2:
                open_left_insertions = 1,
                extend_left_insertions = 1;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 1:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 7:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 7:
                open_right_deletions = 1,
                extend_right_deletions = 6.
""",
        )
        self.check_counts_and_score(counts)

    def check_incomplete_nucleotide_sequence(self, counts):
        self.assertEqual(counts.left_insertions, 0)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 0)
        self.assertEqual(counts.internal_deletions, 4)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 0)
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 12)
        self.assertEqual(counts.mismatches, 1)

    def check_incomplete_nucleotide_sequence_switched(self, counts):
        self.assertEqual(counts.left_insertions, 0)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 4)
        self.assertEqual(counts.internal_deletions, 0)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 0)
        self.assertEqual(counts.aligned, 13)
        self.assertEqual(counts.identities, 12)
        self.assertEqual(counts.mismatches, 1)

    def test_incomplete_nucleotide_sequence(self):
        aligner = Align.PairwiseAligner()
        aligner_blastn = Align.PairwiseAligner("blastn")
        seqA = Seq({10: "TTACGT", 20: "CCCCCCC"}, length=50)
        seqB = Seq("TTACGTCCCTCCC")
        coordinates = np.array([[10, 16, 20, 27], [0, 6, 6, 13]])
        alignment = Align.Alignment([seqA, seqB], coordinates)
        self.assertEqual(
            str(alignment),
            """\
target           10 TTACGT????CCCCCCC 27
                  0 ||||||----|||.||| 17
query             0 TTACGT----CCCTCCC 13
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 12.0; gap score = -4.0; 13 aligned letters; 12 identities; 1 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 12.0,
        gap_score = -4.0.
    aligned = 13:
        identities = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 8.0)
        counts = alignment.counts(aligner_blastn)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 21.0; gap score = -13.0; 13 aligned letters; 12 identities; 1 mismatches; 12 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 21.0,
        gap_score = -13.0.
    aligned = 13:
        identities = 12,
        positives = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertEqual(counts.positives, 12)
        self.assertAlmostEqual(counts.score, 8.0)
        alignment = Align.Alignment([seqB, seqA], coordinates[::-1])
        self.assertEqual(
            str(alignment),
            """\
target            0 TTACGT----CCCTCCC 13
                  0 ||||||----|||.||| 17
query            10 TTACGT????CCCCCCC 27
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 12.0; gap score = -4.0; 13 aligned letters; 12 identities; 1 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 12.0,
        gap_score = -4.0.
    aligned = 13:
        identities = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 4:
                open_internal_insertions = 1,
                extend_internal_insertions = 3;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence_switched(counts)
        self.assertIsNone(counts.positives)
        counts = alignment.counts(aligner_blastn)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 21.0; gap score = -13.0; 13 aligned letters; 12 identities; 1 mismatches; 12 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 21.0,
        gap_score = -13.0.
    aligned = 13:
        identities = 12,
        positives = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 4:
                open_internal_insertions = 1,
                extend_internal_insertions = 3;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence_switched(counts)
        self.assertEqual(counts.positives, 12)
        seqA = Seq({10: "TTACGT", 20: "CCCTCCC"}, length=50)
        seqB = Seq({100: "TTACGTCCCCCCC"}, length=200)
        coordinates = np.array([[10, 16, 20, 27], [100, 106, 106, 113]])
        alignment = Align.Alignment([seqA, seqB], coordinates)
        self.assertEqual(
            str(alignment),
            """\
target           10 TTACGT????CCCTCCC  27
                  0 ||||||----|||.|||  17
query           100 TTACGT----CCCCCCC 113
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 12.0; gap score = -4.0; 13 aligned letters; 12 identities; 1 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 12.0,
        gap_score = -4.0.
    aligned = 13:
        identities = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 8.0)
        counts = alignment.counts(aligner_blastn)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 21.0; gap score = -13.0; 13 aligned letters; 12 identities; 1 mismatches; 12 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 21.0,
        gap_score = -13.0.
    aligned = 13:
        identities = 12,
        positives = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertEqual(counts.positives, 12)
        self.assertAlmostEqual(counts.score, 8.0)
        seqA_rc = seqA.reverse_complement()
        seqB_rc = seqB.reverse_complement()
        coordinates_rc = np.array(coordinates)
        coordinates_rc[0, :] = len(seqA) - coordinates[0, :]
        alignment = Align.Alignment([seqA_rc, seqB], coordinates_rc)
        self.assertEqual(
            str(alignment),
            """\
target           40 TTACGT????CCCTCCC  23
                  0 ||||||----|||.|||  17
query           100 TTACGT----CCCCCCC 113
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 12.0; gap score = -4.0; 13 aligned letters; 12 identities; 1 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 12.0,
        gap_score = -4.0.
    aligned = 13:
        identities = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 8.0)
        counts = alignment.counts(aligner_blastn)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 21.0; gap score = -13.0; 13 aligned letters; 12 identities; 1 mismatches; 12 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 21.0,
        gap_score = -13.0.
    aligned = 13:
        identities = 12,
        positives = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertEqual(counts.positives, 12)
        self.assertAlmostEqual(counts.score, 8.0)
        coordinates_rc = np.array(coordinates)
        coordinates_rc[1, :] = len(seqB) - coordinates[1, :]
        alignment = Align.Alignment([seqA, seqB_rc], coordinates_rc)
        self.assertEqual(
            str(alignment),
            """\
target           10 TTACGT????CCCTCCC 27
                  0 ||||||----|||.||| 17
query           100 TTACGT----CCCCCCC 87
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 12.0; gap score = -4.0; 13 aligned letters; 12 identities; 1 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 12.0,
        gap_score = -4.0.
    aligned = 13:
        identities = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 8.0)
        counts = alignment.counts(aligner_blastn)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 21.0; gap score = -13.0; 13 aligned letters; 12 identities; 1 mismatches; 12 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 21.0,
        gap_score = -13.0.
    aligned = 13:
        identities = 12,
        positives = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertEqual(counts.positives, 12)
        self.assertAlmostEqual(counts.score, 8.0)
        coordinates_rc = np.array(coordinates)
        coordinates_rc[0, :] = len(seqA) - coordinates[0, :]
        coordinates_rc[1, :] = len(seqB) - coordinates[1, :]
        alignment = Align.Alignment([seqA_rc, seqB_rc], coordinates_rc)
        self.assertEqual(
            str(alignment),
            """\
target           40 TTACGT????CCCTCCC 23
                  0 ||||||----|||.||| 17
query           100 TTACGT----CCCCCCC 87
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 12.0; gap score = -4.0; 13 aligned letters; 12 identities; 1 mismatches; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 12.0,
        gap_score = -4.0.
    aligned = 13:
        identities = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 8.0)
        counts = alignment.counts(aligner_blastn)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.0; substitution score = 21.0; gap score = -13.0; 13 aligned letters; 12 identities; 1 mismatches; 12 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.0:
        substitution_score = 21.0,
        gap_score = -13.0.
    aligned = 13:
        identities = 12,
        positives = 12,
        mismatches = 1.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 4:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 4:
                open_internal_deletions = 1,
                extend_internal_deletions = 3;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_incomplete_nucleotide_sequence(counts)
        self.assertEqual(counts.positives, 12)
        self.assertAlmostEqual(counts.score, 8.0)

    def check_blastp(self, counts):
        self.assertEqual(counts.left_insertions, 0)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 2)
        self.assertEqual(counts.internal_deletions, 34)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 0)
        self.assertEqual(counts.aligned, 444)
        self.assertEqual(counts.identities, 237)
        self.assertEqual(counts.mismatches, 207)
        self.assertEqual(counts.gaps, 36)

    def test_blastp(self):
        aligner = Align.PairwiseAligner()
        aligner_blastp = Align.PairwiseAligner("blastp")
        seqA = "MRPRPRSAPGKPRRRSRARLRSSRTPSGGASGGGGSSSSSSTATGGSGSSTGSPGGAASAPAPAPAGMYRSGERLLGSHALPAEQRDFLPLETTNNNNNHHQPGAWARRAGSSASSPPSASSSPHPSAAVPAADPADSASGSSNKRKRDNKASTYGLNYSLLQPSGGRAAGGGRADGGGVVYSGTPWKRRNYNQGVVGLHEEISDFYEYMSPRPEEEKMRMEVVNRIESVIKELWPSADVQIFGSFKTGLYLPTSDIDLVVFGKWENLPLWTLEEALRKHKVADEDSVKVLDKATVPIIKLTDSFTEVKVDISFNVQNGVRAADLIKDFTKKYPVLPYLVLVLKQFLLQRDLNEVFTGGIGSYSLFLMAVSFLQLHPREDACIPNTNYGVLLIEFFELYGRHFNYLKTGIRIKDGGSYVAKDEVQKNMLDGYRPSMLYIEDPLQPGNDVGRSSYGAMQVKQAFDYAYVVLSHAVSPIAKYYPNNETESILGRIIRVTDEVATYRDWISKQWGLKNRPEPSCNGNGVTLIVDTQQLDKCNNNLSEENEALGKCRSKTSESLSKHSSNSSSGPVSSSSATQSSSSDVDSDATPCKTPKQLLCRPSTGNRVGSQDVSLESSQAVGKMQSTQTTNTSNSTNKSQHGSARLFRSSSKGFQGTTQTSHGSLMTNKQHQGKSNNQYYHGKKRKHKRDAPLSDLCR"
        seqB = "MDPFVGWFQKEQEGPSLCTWLKIWETNAQEMGALLNQQLQQATTINGSTSSSSSSSNSGNNNNNNNNNIINNTITNTTNNTGNNSSAKPYLSRPYSSLNRVLNFRADSLEILQQQQQQQQLNGTTQRNSTNINTTSGGSTSSSADSTTNRDNNSPANSSSTNGPGAGTGTSTGAGGTGTNSPATTASSTAATTTGPATSMSDTSNNPPQSTTTPASRTNSIYYNPSRKKRPENKAGGAHYYMNNHMEMIAKYKGEPWRKPDYPYGEGVIGLHEEIEHFYQYVLPTPCEHAIRNEVVKRIEAVVHSIWPQAVVEIFGSFRTGLFLPTSDIDLVVLGLWEKLPLRTLEFELVSRGIAEACTVRVLDKASVPIIKLTDRETQVKVDISFNMQSGVQSAELIKKFKRDYPVLEKLVLVLKQFLLLRDLNEVFTGGISSYSLILMCISFLQMHPRGIYHDTANLGVLLLEFFELYGRRFNYMKIGISIKNGGRYMPKDELQRDMVDGHRPSLLCIEDPLTPGNDIGRSSYGVFQVQQAFKCAYRVLALAVSPLNLLGIDPRVNSILGRIIHITDDVIDYREWIRENFEHLVVVDRISPLPTAAPTAYATANGAPKYVIMPSGAVVQQLYHHPVQVPTAHGHSHAHSHSHGHAHPGAHLCQPYVTGTTVSAVTTTTTMAVVTVGVSAGGVQQQQQQQNATAHTHSQQQTQNQSQSRHRRGSTSSGDDSEDSKDGDVVETTSSAQEVVDIALSTPNGLANMSMPMPVHAVGMPASNSWSGNGNGNGNSSSSTGSSPEIAHIAAQEMDPELEDQQQQQQHQETSGGNGFIRPGDVGTGSNRGGGDGSGGRNYNQRNNHNSSGYYHQQYYVPPPMQQQLSKSNSSSNYHQQHHHSHSHGNHSHRQQHHHQQQHHHQQRPQHLRVGGGNRYQKSLGGSPIISAGNASNSSSNCSNSSSSSGSNNSRLPPLRGTLVNSSSAISIISISSESSIASSSSSSSRSGQDQQRDER"
        sA = {33: seqA[33:511]}
        sB = {136: seqB[136:582]}
        # fmt: off
        coordinates = np.array([[ 33,  44,  46,  80,  89,  97, 106, 153, 155,
                                 170, 181, 192, 192, 378, 379, 511],
                                [136, 147, 147, 181, 181, 189, 189, 236, 236,
                                 251, 251, 262, 264, 450, 450, 582]])
        # fmt: on
        sequences = [Seq(seqA), Seq(seqB)]
        alignment = Align.Alignment(sequences, coordinates)
        self.assertEqual(
            str(alignment),
            """\
target           33 GGSSSSSSTATGGSGSSTGSPGGAASAPAPAPAGMYRSGERLLGSHALPAEQRDFLPLET
                  0 |||.|||...|--......||....|...|........|....|...---------|..|
query           136 GGSTSSSADST--TNRDNNSPANSSSTNGPGAGTGTSTGAGGTGTNS---------PATT

target           93 TNNNNNHHQPGAWARRAGSSASSPPSASSSPHPSAAVPAADPADSASGSSNKRKRDNKAS
                 60 ....---------|......|.|....|..|..|...||..........|.|....|||.
query           185 ASST---------AATTTGPATSMSDTSNNPPQSTTTPASRTNSIYYNPSRKKRPENKAG

target          153 TYGLNYSLLQPSGGRAAGGGRADGGGVVYSGTPWKRRNY--NQGVVGLHEEISDFYEYMS
                120 --|..|.........|.-----------|.|.||....|--..||.||||||..||.|..
query           236 --GAHYYMNNHMEMIAK-----------YKGEPWRKPDYPYGEGVIGLHEEIEHFYQYVL

target          211 PRPEEEKMRMEVVNRIESVIKELWPSADVQIFGSFKTGLYLPTSDIDLVVFGKWENLPLW
                180 |.|.|...|.|||.|||.|....||.|.|.|||||.|||.||||||||||.|.||.|||.
query           283 PTPCEHAIRNEVVKRIEAVVHSIWPQAVVEIFGSFRTGLFLPTSDIDLVVLGLWEKLPLR

target          271 TLEEALRKHKVADEDSVKVLDKATVPIIKLTDSFTEVKVDISFNVQNGVRAADLIKDFTK
                240 |||..|.....|....|.|||||.||||||||..|.||||||||.|.||..|.|||.|..
query           343 TLEFELVSRGIAEACTVRVLDKASVPIIKLTDRETQVKVDISFNMQSGVQSAELIKKFKR

target          331 KYPVLPYLVLVLKQFLLQRDLNEVFTGGIGSYSLFLMAVSFLQLHPREDACIPNTNYGVL
                300 .||||..||||||||||.|||||||||||.||||.||..||||.|||-.......|.|||
query           403 DYPVLEKLVLVLKQFLLLRDLNEVFTGGISSYSLILMCISFLQMHPR-GIYHDTANLGVL

target          391 LIEFFELYGRHFNYLKTGIRIKDGGSYVAKDEVQKNMLDGYRPSMLYIEDPLQPGNDVGR
                360 |.||||||||.|||.|.||.||.||.|..|||.|..|.||.|||.|.|||||.||||.||
query           462 LLEFFELYGRRFNYMKIGISIKNGGRYMPKDELQRDMVDGHRPSLLCIEDPLTPGNDIGR

target          451 SSYGAMQVKQAFDYAYVVLSHAVSPIAKYYPNNETESILGRIIRVTDEVATYRDWISKQW
                420 ||||..||.|||..||.||..||||...........|||||||..||.|..||.||....
query           522 SSYGVFQVQQAFKCAYRVLALAVSPLNLLGIDPRVNSILGRIIHITDDVIDYREWIRENF

target          511 
                480 
query           582 
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 201.0; substitution score = 237.0; gap score = -36.0; 444 aligned letters; 237 identities; 207 mismatches; 36 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 201.0:
        substitution_score = 237.0,
        gap_score = -36.0.
    aligned = 444:
        identities = 237,
        mismatches = 207.
    gaps = 36:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 36:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 34:
                open_internal_deletions = 6,
                extend_internal_deletions = 28;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_blastp(counts)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 201.0)
        counts = alignment.counts(aligner_blastp)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1071.0; substitution score = 1184.0; gap score = -113.0; 444 aligned letters; 237 identities; 207 mismatches; 306 positives; 36 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1071.0:
        substitution_score = 1184.0,
        gap_score = -113.0.
    aligned = 444:
        identities = 237,
        positives = 306,
        mismatches = 207.
    gaps = 36:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 36:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 34:
                open_internal_deletions = 6,
                extend_internal_deletions = 28;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_blastp(counts)
        self.assertEqual(counts.positives, 306)
        self.assertAlmostEqual(counts.score, 1071.0)
        sequences = [Seq(seqA), Seq(seqB)]
        alignment = Align.Alignment(sequences, coordinates)
        counts = alignment.counts(aligner_blastp)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1071.0; substitution score = 1184.0; gap score = -113.0; 444 aligned letters; 237 identities; 207 mismatches; 306 positives; 36 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1071.0:
        substitution_score = 1184.0,
        gap_score = -113.0.
    aligned = 444:
        identities = 237,
        positives = 306,
        mismatches = 207.
    gaps = 36:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 36:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 34:
                open_internal_deletions = 6,
                extend_internal_deletions = 28;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_blastp(counts)
        sequences = [Seq(seqA), Seq(sB, length=len(seqB))]
        alignment = Align.Alignment(sequences, coordinates)
        counts = alignment.counts(aligner_blastp)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1071.0; substitution score = 1184.0; gap score = -113.0; 444 aligned letters; 237 identities; 207 mismatches; 306 positives; 36 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1071.0:
        substitution_score = 1184.0,
        gap_score = -113.0.
    aligned = 444:
        identities = 237,
        positives = 306,
        mismatches = 207.
    gaps = 36:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 36:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 34:
                open_internal_deletions = 6,
                extend_internal_deletions = 28;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_blastp(counts)
        sequences = [Seq(sA, length=len(seqA)), Seq(seqB)]
        alignment = Align.Alignment(sequences, coordinates)
        counts = alignment.counts(aligner_blastp)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1071.0; substitution score = 1184.0; gap score = -113.0; 444 aligned letters; 237 identities; 207 mismatches; 306 positives; 36 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1071.0:
        substitution_score = 1184.0,
        gap_score = -113.0.
    aligned = 444:
        identities = 237,
        positives = 306,
        mismatches = 207.
    gaps = 36:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 36:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 34:
                open_internal_deletions = 6,
                extend_internal_deletions = 28;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_blastp(counts)
        sequences = [Seq(sA, length=len(seqA)), Seq(sB, length=len(seqB))]
        alignment = Align.Alignment(sequences, coordinates)
        counts = alignment.counts(aligner_blastp)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 1071.0; substitution score = 1184.0; gap score = -113.0; 444 aligned letters; 237 identities; 207 mismatches; 306 positives; 36 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 1071.0:
        substitution_score = 1184.0,
        gap_score = -113.0.
    aligned = 444:
        identities = 237,
        positives = 306,
        mismatches = 207.
    gaps = 36:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 36:
            internal_insertions = 2:
                open_internal_insertions = 1,
                extend_internal_insertions = 1;
            internal_deletions = 34:
                open_internal_deletions = 6,
                extend_internal_deletions = 28;
        right_gaps = 0:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.check_blastp(counts)

    def test_string(self):
        aligner = Align.PairwiseAligner()
        aligner.gap_score = -2
        seqA = "あいうえおかきくけこ"
        seqB = "いうきあけ"
        alignments = aligner.align(seqA, seqB)
        self.assertEqual(len(alignments), 1)
        alignment = alignments[0]
        self.assertEqual(alignment[0], "あいうえおかきくけこ")
        self.assertEqual(alignment[1], "-いう---きあけ-")
        self.assertAlmostEqual(alignment.score, -6.0)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = -6.0; substitution score = 4.0; gap score = -10.0; 5 aligned letters; 4 identities; 1 mismatches; 5 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = -6.0:
        substitution_score = 4.0,
        gap_score = -10.0.
    aligned = 5:
        identities = 4,
        mismatches = 1.
    gaps = 5:
        left_gaps = 1:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 1:
                open_left_deletions = 1,
                extend_left_deletions = 0;
        internal_gaps = 3:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 3:
                open_internal_deletions = 1,
                extend_internal_deletions = 2;
        right_gaps = 1:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 1:
                open_right_deletions = 1,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.left_insertions, 0)
        self.assertEqual(counts.left_deletions, 1)
        self.assertEqual(counts.internal_insertions, 0)
        self.assertEqual(counts.internal_deletions, 3)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 1)
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 4)
        self.assertEqual(counts.mismatches, 1)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, -6.0)

    def test_greek(self):
        aligner = Align.PairwiseAligner()
        aligner.gap_score = 0
        seqA = "αβγγβα"
        seqB = "ABCBAαβγ"
        alignments = aligner.align(seqA, seqB)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
-----αβγγβα
-----|||---
ABCBAαβγ---
""",
        )
        self.assertAlmostEqual(alignment.score, 3.0)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 3 identities; 0 mismatches; 8 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 8:
        left_gaps = 5:
            left_insertions = 5:
                open_left_insertions = 1,
                extend_left_insertions = 4;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 3:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 3:
                open_right_deletions = 1,
                extend_right_deletions = 2.
""",
        )
        self.assertEqual(counts.left_insertions, 5)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 0)
        self.assertEqual(counts.internal_deletions, 0)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 3)
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 3.0)
        alignments = aligner.align(seqA, seqB)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
-----αβγγβα
-----|||---
ABCBAαβγ---
""",
        )
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 3.0; substitution score = 3.0; gap score = 0.0; 3 aligned letters; 3 identities; 0 mismatches; 8 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 3.0:
        substitution_score = 3.0,
        gap_score = 0.0.
    aligned = 3:
        identities = 3,
        mismatches = 0.
    gaps = 8:
        left_gaps = 5:
            left_insertions = 5:
                open_left_insertions = 1,
                extend_left_insertions = 4;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 0:
            internal_insertions = 0:
                open_internal_insertions = 0,
                extend_internal_insertions = 0;
            internal_deletions = 0:
                open_internal_deletions = 0,
                extend_internal_deletions = 0;
        right_gaps = 3:
            right_insertions = 0:
                open_right_insertions = 0,
                extend_right_insertions = 0;
            right_deletions = 3:
                open_right_deletions = 1,
                extend_right_deletions = 2.
""",
        )
        self.assertEqual(counts.left_insertions, 5)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 0)
        self.assertEqual(counts.internal_deletions, 0)
        self.assertEqual(counts.right_insertions, 0)
        self.assertEqual(counts.right_deletions, 3)
        self.assertEqual(counts.aligned, 3)
        self.assertEqual(counts.identities, 3)
        self.assertEqual(counts.mismatches, 0)
        self.assertIsNone(counts.positives)
        self.assertAlmostEqual(counts.score, 3.0)
        substitution_matrix = Array("ABCαβγ", dims=2)
        alphabet = substitution_matrix.alphabet
        for c in alphabet:
            substitution_matrix[c, c] = 2.0
        for c1, c2 in zip(alphabet[:3], alphabet[3:]):
            substitution_matrix[c1, c2] = 1.0
            substitution_matrix[c2, c1] = 1.0
        substitution_matrix["B", "β"] = 5.0
        substitution_matrix["β", "B"] = 5.0
        substitution_matrix["C", "γ"] = -0.1
        substitution_matrix["γ", "C"] = -0.1
        self.assertEqual(
            str(substitution_matrix),
            """\
    A   B    C   α   β    γ
A 2.0 0.0  0.0 1.0 0.0  0.0
B 0.0 2.0  0.0 0.0 5.0  0.0
C 0.0 0.0  2.0 0.0 0.0 -0.1
α 1.0 0.0  0.0 2.0 0.0  0.0
β 0.0 5.0  0.0 0.0 2.0  0.0
γ 0.0 0.0 -0.1 0.0 0.0  2.0
""",
        )
        aligner.substitution_matrix = substitution_matrix
        aligner.gap_score = -1.0
        alignments = aligner.align(seqA, seqB)
        alignment = alignments[0]
        self.assertEqual(
            str(alignment),
            """\
αβγγβ-α--
...-.-|--
ABC-BAαβγ
""",
        )
        self.assertAlmostEqual(alignment.score, 8.9)
        counts = alignment.counts(aligner)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (score = 8.9; substitution score = 12.9; gap score = -4.0; 5 aligned letters; 1 identities; 4 mismatches; 4 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    score = 8.9:
        substitution_score = 12.9,
        gap_score = -4.0.
    aligned = 5:
        identities = 1,
        positives = 4,
        mismatches = 4.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )
        self.assertEqual(counts.left_insertions, 0)
        self.assertEqual(counts.left_deletions, 0)
        self.assertEqual(counts.internal_insertions, 1)
        self.assertEqual(counts.internal_deletions, 1)
        self.assertEqual(counts.right_insertions, 2)
        self.assertEqual(counts.right_deletions, 0)
        self.assertEqual(counts.aligned, 5)
        self.assertEqual(counts.identities, 1)
        self.assertEqual(counts.mismatches, 4)
        self.assertEqual(counts.positives, 4)
        self.assertAlmostEqual(counts.score, 8.9)
        counts = alignment.counts(substitution_matrix)
        self.assertEqual(
            repr(counts),
            "<AlignmentCounts object (substitution score = 12.9; 5 aligned letters; 1 identities; 4 mismatches; 4 positives; 4 gaps) at 0x%x>"
            % id(counts),
        )
        self.assertEqual(
            str(counts),
            """\
AlignmentCounts object with
    substitution_score = 12.9,
    aligned = 5:
        identities = 1,
        positives = 4,
        mismatches = 4.
    gaps = 4:
        left_gaps = 0:
            left_insertions = 0:
                open_left_insertions = 0,
                extend_left_insertions = 0;
            left_deletions = 0:
                open_left_deletions = 0,
                extend_left_deletions = 0;
        internal_gaps = 2:
            internal_insertions = 1:
                open_internal_insertions = 1,
                extend_internal_insertions = 0;
            internal_deletions = 1:
                open_internal_deletions = 1,
                extend_internal_deletions = 0;
        right_gaps = 2:
            right_insertions = 2:
                open_right_insertions = 1,
                extend_right_insertions = 1;
            right_deletions = 0:
                open_right_deletions = 0,
                extend_right_deletions = 0.
""",
        )


class TestMatrixAllocationGuards(unittest.TestCase):
    """Length validation and matrix-size guards for align() and score().

    The aligner stores sequence lengths as C int and allocates an
    O(len(A) * len(B)) traceback matrix; these tests check that lengths
    that cannot be handled are rejected with a clear error instead of
    being silently truncated or dying on an unexplained out-of-memory.
    """

    def huge_zeros(self, length):
        # An untouched np.zeros only reserves address space (the pages
        # are lazily zero-filled), so on 64-bit systems these gigabytes
        # are virtual; skip on platforms that refuse the reservation.
        try:
            return np.zeros(length, dtype=np.int32)
        except MemoryError:
            self.skipTest(f"cannot reserve address space for {length} letters")

    @unittest.skipUnless(sys.maxsize > 2**32, "requires a 64-bit build")
    def test_sequence_length_rejected(self):
        # Lengths that do not fit the aligner's int storage must raise a
        # ValueError naming both lengths, for score() and align() alike.
        aligner = Align.PairwiseAligner()
        big = self.huge_zeros(2**31)  # beyond the maximum of INT_MAX - 1
        small = np.zeros(4, dtype=np.int32)
        for method in (aligner.score, aligner.align):
            with self.assertRaises(ValueError) as cm:
                method(big, small)
            message = str(cm.exception)
            self.assertIn("sequences too long", message)
            self.assertIn(str(2**31), message)
            self.assertIn("at most", message)

    @unittest.skipUnless(sys.maxsize > 2**32, "requires a 64-bit build")
    def test_traceback_matrix_size_overflow(self):
        # Two maximum-length sequences pass the length check, but the
        # Waterman-Smith-Beyer traceback matrix would overflow size_t;
        # the guard must fail cleanly before anything quadratic is
        # allocated (this returns quickly, nothing large is touched).
        aligner = Align.PairwiseAligner()
        aligner.insertion_score = lambda index, length: -length
        aligner.deletion_score = lambda index, length: -length
        self.assertEqual(
            aligner.algorithm, "Waterman-Smith-Beyer global alignment algorithm"
        )
        big = self.huge_zeros(2**31 - 2)  # exactly the maximum length
        with self.assertRaises(MemoryError) as cm:
            aligner.align(big, big)
        message = str(cm.exception)
        self.assertIn(str(2**31 - 2), message)
        self.assertIn("more than this platform can address", message)

    def test_fogsaa_matrix_int_overflow(self):
        # Regression test: (nA+1)*(nB+1) was computed in int arithmetic
        # when allocating the FOGSAA matrix, so two ~2-megabase sequences
        # wrapped the product and under-allocated the matrix, corrupting
        # the heap. Now the predicted 176 TB allocation must fail with a
        # MemoryError naming the size and the sequence lengths.
        #
        # Run this in a child process. AddressSanitizer aborts by default
        # when an allocation exceeds its own limit instead of returning
        # NULL to the caller; allocator_may_return_null lets this test reach
        # the extension's normal MemoryError path without taking down the
        # complete test run.
        code = """
import numpy as np
from Bio import Align

aligner = Align.PairwiseAligner()
aligner.mode = "fogsaa"
n = 2**21
sequence = np.zeros(n, dtype=np.int32)
for method in (aligner.score, aligner.align):
    try:
        method(sequence, sequence)
    except MemoryError as error:
        message = str(error)
        assert str(n) in message
        assert "bytes" in message
    else:
        raise AssertionError("impossible FOGSAA allocation did not fail")
"""
        env = os.environ.copy()
        options = env.get("ASAN_OPTIONS", "")
        env["ASAN_OPTIONS"] = f"{options}:allocator_may_return_null=1"
        process = subprocess.run(
            [sys.executable, "-c", code],
            capture_output=True,
            env=env,
            text=True,
            timeout=60,
        )
        self.assertEqual(process.returncode, 0, process.stdout + process.stderr)


if __name__ == "__main__":
    runner = unittest.TextTestRunner(verbosity=2)
    unittest.main(testRunner=runner)
