From 673cbb7a79d8613ee5a2e480a49df69ce9607c33 Mon Sep 17 00:00:00 2001 From: Constantine Evans Date: Fri, 9 Sep 2022 23:24:35 +0100 Subject: [PATCH 1/2] Don't warn about duplicate strand names if could be the same. --- scadnano/scadnano.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/scadnano/scadnano.py b/scadnano/scadnano.py index 72eef28e..48b3c8d8 100644 --- a/scadnano/scadnano.py +++ b/scadnano/scadnano.py @@ -3283,6 +3283,31 @@ def __eq__(self, other: Any) -> bool: # remove quotes when Py3.6 support droppe return False return self.domains == other.domains + def equiv(self, other: Any) -> bool: + """ + Determines whether this strand could be a copy of `other`, up to the currently + defined sequences for each. This method does not require that the names, domain names, + labels, colors, etc. be the same. + """ + if not isinstance(other, Strand): + return False + elif self.modification_3p != other.modification_3p: + return False + elif self.modification_5p != other.modification_5p: + return False + elif self.modifications_int != other.modifications_int: + return False + elif self.circular != other.circular: + return False + if len(self.domains) != len(other.domains): + return False + for d1, d2 in zip(self.domains, other.domains): + if d1.dna_sequence != d2.dna_sequence: + return False + if d1.dna_length() != d2.dna_length(): + return False + return True + def __hash__(self) -> int: return hash(self.domains) @@ -7686,9 +7711,9 @@ def _assign_default_helices_view_orders_to_groups(self) -> None: def _warn_if_strand_names_not_unique(self) -> None: names = [strand.name for strand in self.strands if strand.name is not None] if len(names) > len(set(names)): - for name1, name2 in itertools.combinations(names, 2): - if name1 == name2: - print(f'WARNING: there are two strands with name {name1}') + for strand1, strand2 in itertools.combinations(self.strands, 2): + if (strand1.name == strand2.name) and not strand1.equiv(strand2): + print(f'WARNING: there are two non-equivalent strands with name {strand1.name}.') def strand_with_name(self, name: str) -> Optional[Strand]: """ From 6637da959cdc175e351c846b16c738718c76b1fe Mon Sep 17 00:00:00 2001 From: Constantine Evans Date: Tue, 13 Sep 2022 17:11:55 +0100 Subject: [PATCH 2/2] add tests --- tests/scadnano_tests.py | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/scadnano_tests.py b/tests/scadnano_tests.py index cd41d2e2..7fb83264 100644 --- a/tests/scadnano_tests.py +++ b/tests/scadnano_tests.py @@ -4078,6 +4078,53 @@ def test_JSON_bad_no_groups_but_helices_reference_groups(self) -> None: with self.assertRaises(sc.IllegalDesignError): sc.Design.from_scadnano_json_str(json_str) +class TestDuplicates(unittest.TestCase): + def test_warn_duplicate(self) -> None: + import contextlib + import io + + # Same name, but all equivalent: + out = io.StringIO() + helices = [sc.Helix(max_offset=30) for _ in range(10)] + des = sc.Design(helices = helices, grid=sc.square) + des.draw_strand(0, 0).to(10).cross(1,9).to(-1).with_name("strand_ name!") + des.draw_strand(2, 1).to(11).to(21).with_name("strand_ name!") + des.draw_strand(3, 22).to(12).to(2).with_name("strand_ name!") + with contextlib.redirect_stdout(out): + des._warn_if_strand_names_not_unique() + self.assertNotRegex(out.getvalue(), "WARNING: there are two ") + + # One sequence assigned, one not (should warn): + out = io.StringIO() + helices = [sc.Helix(max_offset=30) for _ in range(10)] + des = sc.Design(helices = helices, grid=sc.square) + des.draw_strand(2, 1).to(11).to(21).with_name("strand_ name!") + des.draw_strand(3, 22).to(12).to(2).with_name("strand_ name!").with_sequence("GTCCGTAGAGGCTACTGTCT") + with contextlib.redirect_stdout(out): + des._warn_if_strand_names_not_unique() + self.assertRegex(out.getvalue(), "WARNING: there are two non-equivalent strands with name strand_ name!") + + # One more domain: + out = io.StringIO() + helices = [sc.Helix(max_offset=30) for _ in range(10)] + des = sc.Design(helices = helices, grid=sc.square) + des.draw_strand(2, 1).to(11).to(21).to(30).with_name("strand_ name!") + des.draw_strand(3, 22).to(12).to(2).with_name("strand_ name!") + with contextlib.redirect_stdout(out): + des._warn_if_strand_names_not_unique() + self.assertRegex(out.getvalue(), "WARNING: there are two non-equivalent strands with name strand_ name!") + + # Modification: + biotin_5p = sc.Modification5Prime(display_text='B', idt_text='/5Biosg/') + out = io.StringIO() + helices = [sc.Helix(max_offset=30) for _ in range(10)] + des = sc.Design(helices = helices, grid=sc.square) + des.draw_strand(2, 1).to(11).to(21).to(30).with_name("strand_ name!").with_modification_5p(biotin_5p) + des.draw_strand(3, 22).to(12).to(2).with_name("strand_ name!") + with contextlib.redirect_stdout(out): + des._warn_if_strand_names_not_unique() + self.assertRegex(out.getvalue(), "WARNING: there are two non-equivalent strands with name strand_ name!") + class TestNames(unittest.TestCase):