diff --git a/fgpyo/sam/__init__.py b/fgpyo/sam/__init__.py index 8fa2c19..a96d14e 100644 --- a/fgpyo/sam/__init__.py +++ b/fgpyo/sam/__init__.py @@ -178,6 +178,7 @@ from pysam import AlignedSegment from pysam import AlignmentFile as SamFile from pysam import AlignmentHeader as SamHeader +from typing_extensions import Self from typing_extensions import deprecated import fgpyo.io @@ -863,40 +864,107 @@ def sum_of_base_qualities(rec: AlignedSegment, min_quality_score: int = 15) -> i return score +def _set_common_mate_fields(dest: AlignedSegment, mate_primary: AlignedSegment) -> None: + """Set common mate info on a destination alignment from its mate's primary alignment. + + Args: + dest: The alignment to set the mate info upon. + mate_primary: The primary alignment to use as a mate reference. + + Raises: + ValueError: If dest and mate_primary are of the same read ordinal. + ValueError: If mate_primary is secondary or supplementary. + ValueError: If dest and mate_primary do not share the same query name. + """ + if mate_primary.is_read1 is dest.is_read1: + raise ValueError("mate_primary and dest records must be of different read ordinals!") + if mate_primary.is_supplementary or mate_primary.is_secondary: + raise ValueError("Mate info must be set from a non-supplementary non-secondary record!") + if mate_primary.query_name != dest.query_name: + raise ValueError("Cannot set mate info on alignments with different query names!") + + dest.next_reference_id = mate_primary.reference_id + dest.next_reference_name = mate_primary.reference_name + dest.next_reference_start = mate_primary.reference_start + dest.mate_is_forward = mate_primary.is_forward + dest.mate_is_mapped = mate_primary.is_mapped + dest.set_tag("MC", mate_primary.cigarstring) + dest.set_tag("MQ", mate_primary.mapping_quality) + dest.set_tag("ms", sum_of_base_qualities(mate_primary)) + + def set_mate_info( - r1: AlignedSegment, - r2: AlignedSegment, + rec1: AlignedSegment, + rec2: AlignedSegment, is_proper_pair: Callable[[AlignedSegment, AlignedSegment], bool] = is_proper_pair, + isize: Callable[[AlignedSegment, AlignedSegment], int] = isize, ) -> None: - """Resets mate pair information between reads in a pair. + """Resets mate pair information between two primary alignments that share a query name. Args: - r1: Read 1 (first read in the template). - r2: Read 2 with the same query name as r1 (second read in the template). + rec1: The first record in the pair. + rec2: The second record in the pair. is_proper_pair: A function that takes the two alignments and determines proper pair status. + isize: A function that takes the two alignments and calculates their isize. + + Raises: + ValueError: If rec1 and rec2 are of the same read ordinal. + ValueError: If either rec1 or rec2 is secondary or supplementary. + ValueError: If rec1 and rec2 do not share the same query name. """ - if r1.query_name != r2.query_name: - raise ValueError("Cannot set mate info on alignments with different query names!") + for dest, source in [(rec1, rec2), (rec2, rec1)]: + _set_common_mate_fields(dest=dest, mate_primary=source) - for src, dest in [(r1, r2), (r2, r1)]: - dest.next_reference_id = src.reference_id - dest.next_reference_name = src.reference_name - dest.next_reference_start = src.reference_start - dest.mate_is_forward = src.is_forward - dest.mate_is_mapped = src.is_mapped - dest.set_tag("MC", src.cigarstring) - dest.set_tag("MQ", src.mapping_quality) + template_length = isize(rec1, rec2) + rec1.template_length = template_length + rec2.template_length = -template_length - r1.set_tag("ms", sum_of_base_qualities(r2)) - r2.set_tag("ms", sum_of_base_qualities(r1)) + proper_pair = is_proper_pair(rec1, rec2) + rec1.is_proper_pair = proper_pair + rec2.is_proper_pair = proper_pair - template_length = isize(r1, r2) - r1.template_length = template_length - r2.template_length = -template_length - proper_pair = is_proper_pair(r1, r2) - r1.is_proper_pair = proper_pair - r2.is_proper_pair = proper_pair +def set_mate_info_on_secondary(secondary: AlignedSegment, mate_primary: AlignedSegment) -> None: + """Set mate info on a secondary alignment from its mate's primary alignment. + + Args: + secondary: The secondary alignment to set mate information upon. + mate_primary: The primary alignment of the secondary's mate. + + Raises: + ValueError: If secondary and mate_primary are of the same read ordinal. + ValueError: If secondary and mate_primary do not share the same query name. + ValueError: If mate_primary is secondary or supplementary. + ValueError: If secondary is not marked as a secondary alignment. + """ + if not secondary.is_secondary: + raise ValueError("Cannot set mate info on an alignment not marked as secondary!") + + _set_common_mate_fields(dest=secondary, mate_primary=mate_primary) + + +def set_mate_info_on_supplementary(supp: AlignedSegment, mate_primary: AlignedSegment) -> None: + """Set mate info on a supplementary alignment from its mate's primary alignment. + + Args: + supp: The supplementary alignment to set mate information upon. + mate_primary: The primary alignment of the supplementary's mate. + + Raises: + ValueError: If supp and mate_primary are of the same read ordinal. + ValueError: If supp and mate_primary do not share the same query name. + ValueError: If mate_primary is secondary or supplementary. + ValueError: If supp is not marked as a supplementary alignment. + """ + if not supp.is_supplementary: + raise ValueError("Cannot set mate info on an alignment not marked as supplementary!") + + _set_common_mate_fields(dest=supp, mate_primary=mate_primary) + + # NB: for a non-secondary supplemental alignment, set the following to the same as the primary. + if not supp.is_secondary: + supp.is_proper_pair = mate_primary.is_proper_pair + supp.template_length = -mate_primary.template_length @deprecated("Use `set_mate_info()` instead. Deprecated after fgpyo 0.8.0.") @@ -922,7 +990,7 @@ def set_pair_info(r1: AlignedSegment, r2: AlignedSegment, proper_pair: bool = Tr r2.is_read2 = True r2.is_read1 = False - set_mate_info(r1=r1, r2=r2, is_proper_pair=lambda a, b: proper_pair) + set_mate_info(rec1=r1, rec2=r2, is_proper_pair=lambda a, b: proper_pair) @attr.s(frozen=True, auto_attribs=True) @@ -1164,6 +1232,31 @@ def all_recs(self) -> Iterator[AlignedSegment]: for rec in recs: yield rec + def set_mate_info( + self, + is_proper_pair: Callable[[AlignedSegment, AlignedSegment], bool] = is_proper_pair, + isize: Callable[[AlignedSegment, AlignedSegment], int] = isize, + ) -> Self: + """Reset all mate information on every alignment in the template. + + Args: + is_proper_pair: A function that takes two alignments and determines proper pair status. + isize: A function that takes the two alignments and calculates their isize. + """ + if self.r1 is not None and self.r2 is not None: + set_mate_info(self.r1, self.r2, is_proper_pair=is_proper_pair, isize=isize) + if self.r1 is not None: + for rec in self.r2_secondaries: + set_mate_info_on_secondary(secondary=rec, mate_primary=self.r1) + for rec in self.r2_supplementals: + set_mate_info_on_supplementary(supp=rec, mate_primary=self.r1) + if self.r2 is not None: + for rec in self.r1_secondaries: + set_mate_info_on_secondary(secondary=rec, mate_primary=self.r2) + for rec in self.r1_supplementals: + set_mate_info_on_supplementary(supp=rec, mate_primary=self.r2) + return self + def write_to( self, writer: SamFile, diff --git a/tests/fgpyo/sam/test_sam.py b/tests/fgpyo/sam/test_sam.py index e822ee5..6d3dd92 100755 --- a/tests/fgpyo/sam/test_sam.py +++ b/tests/fgpyo/sam/test_sam.py @@ -21,6 +21,9 @@ from fgpyo.sam import SamFileType from fgpyo.sam import is_proper_pair from fgpyo.sam import set_mate_info +from fgpyo.sam import set_mate_info_on_secondary +from fgpyo.sam import set_mate_info_on_supplementary +from fgpyo.sam import set_pair_info from fgpyo.sam import sum_of_base_qualities from fgpyo.sam.builder import SamBuilder @@ -683,12 +686,34 @@ def test_calc_edit_info_with_aligned_Ns() -> None: assert info.nm == 5 -def test_set_mate_info_raises_mimatched_query_names() -> None: +def test_set_mate_info_raises_not_opposite_read_ordinals() -> None: + """Test set_mate_info raises an exception for mismatched read ordinals.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=1) + with pytest.raises( + ValueError, match="mate_primary and dest records must be of different read ordinals!" + ): + set_mate_info(r1, r2) + + +def test_set_mate_info_raises_when_second_rec_is_supplementary() -> None: + """Test set_mate_info raises an exception when the second record is supplementary.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + r2.is_supplementary = True + with pytest.raises( + ValueError, match="Mate info must be set from a non-supplementary non-secondary record!" + ): + set_mate_info(r1, r2) + + +def test_set_mate_info_raises_mismatched_query_names() -> None: """Test set_mate_info raises an exception for mismatched query names.""" builder = SamBuilder() - r1 = builder.add_single(read_num=1) - r2 = builder.add_single(read_num=2) - assert r1.query_name != r2.query_name + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="y", read_num=2) with pytest.raises( ValueError, match="Cannot set mate info on alignments with different query names!" ): @@ -804,3 +829,204 @@ def test_set_mate_info_both_mapped() -> None: assert r2.is_reverse is True assert r1.mate_is_reverse is True assert r2.mate_is_forward is True + + +def test_set_mate_info_on_secondary() -> None: + """Test set_mate_info_on_secondary sets mate info for a secondary record.""" + builder = SamBuilder() + secondary, primary = builder.add_pair() + secondary.is_secondary = True + + assert secondary.is_unmapped is True + assert primary.is_unmapped is True + + set_mate_info_on_secondary(secondary, primary) + + assert secondary.reference_id == sam.NO_REF_INDEX + assert secondary.reference_name is None + assert secondary.reference_start == sam.NO_REF_POS + assert secondary.next_reference_id == sam.NO_REF_INDEX + assert secondary.next_reference_name is None + assert secondary.next_reference_start == sam.NO_REF_POS + assert not secondary.has_tag("MC") + assert secondary.has_tag("MQ") + assert secondary.get_tag("MQ") == 0 + assert secondary.has_tag("ms") + assert secondary.get_tag("ms") == 3000 + assert secondary.template_length == 0 + assert secondary.is_proper_pair is False + + # NB: unmapped records are forward until proven otherwise + assert secondary.is_forward is True + assert secondary.mate_is_forward is True + + +def test_set_mate_info_on_secondary_raises_for_secondary_or_supp_rec2() -> None: + """Test that set_mate_info_on_secondary raises an exception if rec2 is secondary or supp.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + r1.is_secondary = True + r2.is_secondary = True + with pytest.raises( + ValueError, match="Mate info must be set from a non-supplementary non-secondary record!" + ): + set_mate_info_on_secondary(r1, r2) + r2.is_secondary = False + r2.is_supplementary = True + with pytest.raises( + ValueError, match="Mate info must be set from a non-supplementary non-secondary record!" + ): + set_mate_info_on_secondary(r1, r2) + + +def test_set_mate_info_on_secondary_raises_for_non_secondary_rec1() -> None: + """Test that set_mate_info_on_secondary raises an exception if rec1 is not secondary.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + with pytest.raises( + ValueError, match="Cannot set mate info on an alignment not marked as secondary!" + ): + set_mate_info_on_secondary(r1, r2) + + +def test_set_mate_info_on_supplementary() -> None: + """Test set_mate_info_on_supplementary sets mate info for a supplementary record.""" + builder = SamBuilder() + supplementary, primary = builder.add_pair() + supplementary.is_supplementary = True + + assert supplementary.is_unmapped is True + assert primary.is_unmapped is True + + set_mate_info_on_supplementary(supplementary, primary) + + assert supplementary.reference_id == sam.NO_REF_INDEX + assert supplementary.reference_name is None + assert supplementary.reference_start == sam.NO_REF_POS + assert supplementary.next_reference_id == sam.NO_REF_INDEX + assert supplementary.next_reference_name is None + assert supplementary.next_reference_start == sam.NO_REF_POS + assert not supplementary.has_tag("MC") + assert supplementary.has_tag("MQ") + assert supplementary.get_tag("MQ") == 0 + assert supplementary.has_tag("ms") + assert supplementary.get_tag("ms") == 3000 + assert supplementary.template_length == 0 + assert supplementary.is_proper_pair is False + + # NB: unmapped records are forward until proven otherwise + assert supplementary.is_forward is True + assert supplementary.mate_is_forward is True + + +def test_set_mate_info_on_supplementary_raises_for_secondary_or_supp_rec2() -> None: + """Test that set_mate_info_on_supplementary raises an exception if rec2 is secondary or supp.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + r1.is_supplementary = True + r2.is_secondary = True + with pytest.raises( + ValueError, match="Mate info must be set from a non-supplementary non-secondary record!" + ): + set_mate_info_on_supplementary(r1, r2) + r2.is_secondary = False + r2.is_supplementary = True + with pytest.raises( + ValueError, match="Mate info must be set from a non-supplementary non-secondary record!" + ): + set_mate_info_on_supplementary(r1, r2) + + +def test_set_mate_info_on_supplementary_raises_for_non_secondary_rec1() -> None: + """Test that set_mate_info_on_supplementary raises an exception if rec1 is not supplementary.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + with pytest.raises( + ValueError, match="Cannot set mate info on an alignment not marked as supplementary!" + ): + set_mate_info_on_supplementary(r1, r2) + + +def test_set_mate_info_on_supplementary_sets_additional_fields_for_primary_supplemental() -> None: + """Tests that set_mate_info_on_supplementary sets additional fields for primary supplements.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + r1.is_supplementary = True + r2.is_proper_pair = True + r2.template_length = 100 + + assert r1.is_proper_pair is False + assert r1.template_length == 0 + set_mate_info_on_supplementary(r1, r2) + assert r1.template_length == -100 + assert r1.is_proper_pair is True + + +def test_set_mate_info_on_supplementary_does_not_set_fields_for_secondary_supplemental() -> None: + """Tests that set_mate_info_on_supplementary does not set fields for secondary supplements.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="x", read_num=2) + r1.is_secondary = True + r1.is_supplementary = True + r2.is_proper_pair = True + r2.template_length = 100 + + assert not r1.is_proper_pair + assert r1.template_length == 0 + set_mate_info_on_supplementary(r1, r2) + assert not r1.is_proper_pair + assert r1.template_length == 0 + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_set_pair_info_raises_exception_for_mismatched_query_names() -> None: + """Test that set_pair_info raises an exception for mismatched query names.""" + builder = SamBuilder() + r1 = builder.add_single(name="x", read_num=1) + r2 = builder.add_single(name="y", read_num=2) + with pytest.raises( + ValueError, + match="Cannot set pair info on reads with different query names!", + ): + set_pair_info(r1, r2) + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_set_pair_info_both_mapped() -> None: + """Test set_pair_info sets mate info for two mapped records.""" + builder = SamBuilder() + r1, r2 = builder.add_pair(chrom="chr1", start1=200, start2=300) + assert r1.is_mapped is True + assert r2.is_mapped is True + + set_pair_info(r1, r2, proper_pair=False) + + for rec in (r1, r2): + assert rec.reference_id == builder.header.get_tid("chr1") + assert rec.reference_name == "chr1" + assert rec.next_reference_id == builder.header.get_tid("chr1") + assert rec.next_reference_name == "chr1" + assert rec.has_tag("MC") + assert rec.get_tag("MC") == "100M" + assert rec.has_tag("MQ") + assert rec.get_tag("MQ") == 60 + assert rec.has_tag("ms") + assert rec.get_tag("ms") == 3000 + assert rec.is_proper_pair is False + + assert r1.reference_start == 200 + assert r1.next_reference_start == 300 + assert r2.reference_start == 300 + assert r2.next_reference_start == 200 + assert r1.template_length == 200 + assert r2.template_length == -200 + assert r1.is_forward is True + assert r2.is_reverse is True + assert r1.mate_is_reverse is True + assert r2.mate_is_forward is True diff --git a/tests/fgpyo/sam/test_template_iterator.py b/tests/fgpyo/sam/test_template_iterator.py index d2f44f7..b500032 100644 --- a/tests/fgpyo/sam/test_template_iterator.py +++ b/tests/fgpyo/sam/test_template_iterator.py @@ -219,3 +219,171 @@ def test_set_tag() -> None: for bad_tag in ["", "A", "ABC", "ABCD"]: with pytest.raises(AssertionError, match="Tags must be 2 characters"): template.set_tag(bad_tag, VALUE) + + +def test_template_set_mate_info() -> None: + """Test that Template set_mate_info fixes up all records in a template.""" + builder = SamBuilder() + + r1, r2 = builder.add_pair(name="x", chrom="chr1", start1=200, start2=300) + r1_secondary = builder.add_single(name="x", read_num=1, chrom="chr1", start=2) + r2_secondary = builder.add_single(name="x", read_num=2, chrom="chr1", start=5) + r1_secondary.is_secondary = True + r2_secondary.is_secondary = True + + r1_supp = builder.add_single(name="x", read_num=1, chrom="chr1", start=4) + r2_supp = builder.add_single(name="x", read_num=2, chrom="chr1", start=5) + r1_supp.is_supplementary = True + r2_supp.is_supplementary = True + + r1_secondary_supp = builder.add_single(name="x", read_num=1, chrom="chr1", start=6) + r2_secondary_supp = builder.add_single(name="x", read_num=2, chrom="chr1", start=7) + r1_secondary_supp.is_secondary = True + r2_secondary_supp.is_secondary = True + r1_secondary_supp.is_supplementary = True + r2_secondary_supp.is_supplementary = True + + template = Template.build( + [ + r1, + r2, + r1_secondary, + r2_secondary, + r1_supp, + r2_supp, + r1_secondary_supp, + r2_secondary_supp, + ] + ) + + template.set_mate_info() + + # Assert the state of both the R1 and R2 alignments + for rec in (template.r1, template.r2): + assert rec.reference_id == builder.header.get_tid("chr1") + assert rec.reference_name == "chr1" + assert rec.next_reference_id == builder.header.get_tid("chr1") + assert rec.next_reference_name == "chr1" + assert rec.has_tag("MC") + assert rec.get_tag("MC") == "100M" + assert rec.has_tag("MQ") + assert rec.get_tag("MQ") == 60 + assert rec.has_tag("ms") + assert rec.get_tag("ms") == 3000 + assert rec.is_proper_pair is True + + assert template.r1.reference_start == 200 + assert template.r1.next_reference_start == 300 + assert template.r2.reference_start == 300 + assert template.r2.next_reference_start == 200 + assert template.r1.template_length == 200 + assert template.r2.template_length == -200 + assert template.r1.is_forward is True + assert template.r2.is_reverse is True + assert template.r1.mate_is_reverse is True + assert template.r2.mate_is_forward is True + + # Assert the state of the two secondary non-supplementary alignments + assert template.r1_secondaries[0].reference_id == builder.header.get_tid("chr1") + assert template.r1_secondaries[0].reference_name == "chr1" + assert template.r1_secondaries[0].reference_start == 2 + assert template.r1_secondaries[0].next_reference_id == template.r2.reference_id + assert template.r1_secondaries[0].next_reference_name == template.r2.reference_name + assert template.r1_secondaries[0].next_reference_start == template.r2.reference_start + assert template.r1_secondaries[0].has_tag("MC") + assert template.r1_secondaries[0].get_tag("MC") == template.r2.cigarstring + assert template.r1_secondaries[0].has_tag("MQ") + assert template.r1_secondaries[0].get_tag("MQ") == template.r2.mapping_quality + assert template.r1_secondaries[0].has_tag("ms") + assert template.r1_secondaries[0].get_tag("ms") == 3000 + assert template.r1_secondaries[0].template_length == 0 + assert template.r1_secondaries[0].is_proper_pair is False + assert template.r1_secondaries[0].is_forward is True + assert template.r1_secondaries[0].mate_is_forward is template.r2.is_forward + + assert template.r2_secondaries[0].reference_id == builder.header.get_tid("chr1") + assert template.r2_secondaries[0].reference_name == "chr1" + assert template.r2_secondaries[0].reference_start == 5 + assert template.r2_secondaries[0].next_reference_id == template.r1.reference_id + assert template.r2_secondaries[0].next_reference_name == template.r1.reference_name + assert template.r2_secondaries[0].next_reference_start == template.r1.reference_start + assert template.r2_secondaries[0].has_tag("MC") + assert template.r2_secondaries[0].get_tag("MC") == template.r1.cigarstring + assert template.r2_secondaries[0].has_tag("MQ") + assert template.r2_secondaries[0].get_tag("MQ") == template.r1.mapping_quality + assert template.r2_secondaries[0].has_tag("ms") + assert template.r2_secondaries[0].get_tag("ms") == 3000 + assert template.r2_secondaries[0].template_length == 0 + assert template.r2_secondaries[0].is_proper_pair is False + assert template.r2_secondaries[0].is_forward is True + assert template.r2_secondaries[0].mate_is_forward is template.r1.is_forward + + # Assert the state of the two non-secondary supplemental alignments + assert template.r1_supplementals[0].reference_id == builder.header.get_tid("chr1") + assert template.r1_supplementals[0].reference_name == "chr1" + assert template.r1_supplementals[0].reference_start == 4 + assert template.r1_supplementals[0].next_reference_id == template.r2.reference_id + assert template.r1_supplementals[0].next_reference_name == template.r2.reference_name + assert template.r1_supplementals[0].next_reference_start == template.r2.reference_start + assert template.r1_supplementals[0].has_tag("MC") + assert template.r1_supplementals[0].get_tag("MC") == template.r2.cigarstring + assert template.r1_supplementals[0].has_tag("MQ") + assert template.r1_supplementals[0].get_tag("MQ") == template.r2.mapping_quality + assert template.r1_supplementals[0].has_tag("ms") + assert template.r1_supplementals[0].get_tag("ms") == 3000 + assert template.r1_supplementals[0].template_length == 200 + assert template.r1_supplementals[0].is_proper_pair is True + assert template.r1_supplementals[0].is_forward is True + assert template.r1_supplementals[0].mate_is_forward is template.r2.is_forward + + assert template.r2_supplementals[0].reference_id == builder.header.get_tid("chr1") + assert template.r2_supplementals[0].reference_name == "chr1" + assert template.r2_supplementals[0].reference_start == 5 + assert template.r2_supplementals[0].next_reference_id == template.r1.reference_id + assert template.r2_supplementals[0].next_reference_name == template.r1.reference_name + assert template.r2_supplementals[0].next_reference_start == template.r1.reference_start + assert template.r2_supplementals[0].has_tag("MC") + assert template.r2_supplementals[0].get_tag("MC") == template.r1.cigarstring + assert template.r2_supplementals[0].has_tag("MQ") + assert template.r2_supplementals[0].get_tag("MQ") == template.r1.mapping_quality + assert template.r2_supplementals[0].has_tag("ms") + assert template.r2_supplementals[0].get_tag("ms") == 3000 + assert template.r2_supplementals[0].template_length == -200 + assert template.r2_supplementals[0].is_proper_pair is True + assert template.r2_supplementals[0].is_forward is True + assert template.r2_supplementals[0].mate_is_forward is template.r1.is_forward + + # Assert the state of the two secondary supplemental alignments + assert template.r1_supplementals[1].reference_id == builder.header.get_tid("chr1") + assert template.r1_supplementals[1].reference_name == "chr1" + assert template.r1_supplementals[1].reference_start == 6 + assert template.r1_supplementals[1].next_reference_id == template.r2.reference_id + assert template.r1_supplementals[1].next_reference_name == template.r2.reference_name + assert template.r1_supplementals[1].next_reference_start == template.r2.reference_start + assert template.r1_supplementals[1].has_tag("MC") + assert template.r1_supplementals[1].get_tag("MC") == template.r2.cigarstring + assert template.r1_supplementals[1].has_tag("MQ") + assert template.r1_supplementals[1].get_tag("MQ") == template.r2.mapping_quality + assert template.r1_supplementals[1].has_tag("ms") + assert template.r1_supplementals[1].get_tag("ms") == 3000 + assert template.r1_supplementals[1].template_length == 0 + assert template.r1_supplementals[1].is_proper_pair is False + assert template.r1_supplementals[1].is_forward is True + assert template.r1_supplementals[1].mate_is_forward is template.r2.is_forward + + assert template.r2_supplementals[1].reference_id == builder.header.get_tid("chr1") + assert template.r2_supplementals[1].reference_name == "chr1" + assert template.r2_supplementals[1].reference_start == 7 + assert template.r2_supplementals[1].next_reference_id == template.r1.reference_id + assert template.r2_supplementals[1].next_reference_name == template.r1.reference_name + assert template.r2_supplementals[1].next_reference_start == template.r1.reference_start + assert template.r2_supplementals[1].has_tag("MC") + assert template.r2_supplementals[1].get_tag("MC") == template.r1.cigarstring + assert template.r2_supplementals[1].has_tag("MQ") + assert template.r2_supplementals[1].get_tag("MQ") == template.r1.mapping_quality + assert template.r2_supplementals[1].has_tag("ms") + assert template.r2_supplementals[1].get_tag("ms") == 3000 + assert template.r2_supplementals[1].template_length == 0 + assert template.r2_supplementals[1].is_proper_pair is False + assert template.r2_supplementals[1].is_forward is True + assert template.r2_supplementals[1].mate_is_forward is template.r1.is_forward