Use the number of alignment columns, including gap columns, as the denominator. A column contributes to sequence identity when its two nongap letters are equal, to sequence similarity when its two nongap letters have a strictly positive BLOSUM62 score, and to the gap fraction when either letter is a gap. A zero substitution score is not counted as similar.
The implementation in part (a) computes each numerator directly during its final pass through the aligned strings:
for name, a in alignments.items():
print(name, a["alignment_length"], a["identities"],
a["similarities"], a["gaps"])
print(a["identity_percent"], a["similarity_percent"], a["gap_percent"])Identical columns also count as similar for these data, so these percentages are overlapping summaries rather than three categories summing to 100%.
The maximum-scoring cell records the inclusive end position in each sequence. During traceback, moving left consumes a reference residue, moving up consumes a query residue, and moving diagonally consumes both; when traceback stops, the remaining prefix lengths are one less than the corresponding one-based starts. Consequently
i+1 and j+1 are the correct starts returned in part (a), without counting any inserted gaps as sequence residues.The following report joins these coordinates to the alignments and sequence identity/sequence similarity values already calculated:
for name, a in alignments.items():
print(f"{name}: query length={a['query_length']}, "
f"reference length={a['reference_length']}, "
f"alignment columns={a['alignment_length']}, score={a['score']}")
print(f"Query {a['query_start']} {a['query']} {a['query_end']}")
print(f"Ref {a['reference_start']} {a['reference']} {a['reference_end']}")
print(f"Identity {a['identity_percent']:.2f}%; "
f"similarity {a['similarity_percent']:.2f}%; "
f"gaps {a['gap_percent']:.2f}%")All reference sequences have length 508. The following are one-based inclusive coordinates; the aligned strings and all percentages are given in parts (a) and (b).