Skip to content

Commit

Permalink
Merge pull request #98 from UC-Davis-molecular-computing/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
dave-doty authored Jul 3, 2020
2 parents 9b32874 + fed5149 commit 92d2a44
Show file tree
Hide file tree
Showing 34 changed files with 142 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ examples/run_all_examples.xls
__pycache__/
tests_outputs/
.vscode/
dist/
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ The scadnano Python module is a library for describing synthetic DNA nanostructu

If you find scadnano useful in a scientific project, please cite its associated paper:

> scadnano: A browser-based, easily scriptable tool for designing DNA nanostructures.
> <ins>scadnano: A browser-based, scriptable tool for designing DNA nanostructures</ins>.
David Doty, Benjamin L Lee, and Tristan Stérin.
*Technical Report 2005.11841, arXiv*, 2020.
[ [arXiv paper](https://arxiv.org/abs/2005.11841) | [BibTeX](https://web.cs.ucdavis.edu/~doty/papers/scadnano.bib) ]
DNA 2020: *Proceedings of the 26th International Conference on DNA Computing and Molecular Programming*
[ [paper](https://arxiv.org/abs/2005.11841) | [BibTeX](https://web.cs.ucdavis.edu/~doty/papers/scadnano.bib) ]


## Overview
Expand Down Expand Up @@ -64,7 +64,7 @@ Once Python is installed, there are two ways you can install the scadnano Python
* *optional*: [origami_rectangle.py](https://raw.githubusercontent.com/UC-Davis-molecular-computing/scadnano-python-package/master/scadnano/origami_rectangle.py); This can help create origami rectangles, but it is not necessary to use scadnano.
* *optional*: [_version.py](https://raw.githubusercontent.com/UC-Davis-molecular-computing/scadnano-python-package/master/scadnano/_version.py) This ensures that the current version number is written into any `.dna` files written by the library; otherwise it may be out of date. (Which should not matter for the most part.)
The scadnano package uses the Python package [xlwt](https://pypi.org/project/xlwt/) to write Excel files, so in order to call the method [`DNADesign.write_idt_plate_excel_file()`](https://scadnano-python-package.readthedocs.io/#scadnano.scadnano.DNADesign.write_idt_plate_excel_file) to export an Excel file with DNA sequences, xlwt must be installed. To install, type `pip install xlwt` at the command line.
The scadnano package uses the Python package [xlwt](https://pypi.org/project/xlwt/) to write Excel files, so in order to call the method [`DNADesign.write_idt_plate_excel_file()`](https://scadnano-python-package.readthedocs.io/#scadnano.DNADesign.write_idt_plate_excel_file) to export an Excel file with DNA sequences, xlwt must be installed. To install, type `pip install xlwt` at the command line.



Expand Down
Binary file removed dist/scadnano-0.1.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.1.1.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.1.2.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.2.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.3.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.4.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.5.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.1.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.2.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.3.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.4.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.5.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.6.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.7.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.6.8.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.7.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.7.1.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.7.2.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.7.3.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.7.4.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.8.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.8.1.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.8.2.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.8.3.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.9.0.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.9.1.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.9.2.tar.gz
Binary file not shown.
Binary file removed dist/scadnano-0.9.3.tar.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion scadnano/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

# The following line *must* be the last in the module, exactly as formatted:
# XXX: REMEMBER TO CHANGE VERSION IN scadnano.py also, for users who do not install from PyPI
__version__ = "0.9.3"
__version__ = "0.9.4"
50 changes: 46 additions & 4 deletions scadnano/scadnano.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
If you find scadnano useful in a scientific project, please cite its associated paper:
| scadnano: A browser-based, easily scriptable tool for designing DNA nanostructures.
| scadnano: A browser-based, scriptable tool for designing DNA nanostructures.
| David Doty, Benjamin L Lee, and Tristan Stérin.
| *Technical Report 2005.11841, arXiv*, 2020.
| [ `arXiv paper <https://arxiv.org/abs/2005.11841>`_ | `BibTeX <https://web.cs.ucdavis.edu/~doty/papers/scadnano.bib>`_ ]
| DNA 2020: *Proceedings of the 26th International Conference on DNA Computing and Molecular Programming*
| [ `paper <https://arxiv.org/abs/2005.11841>`_ | `BibTeX <https://web.cs.ucdavis.edu/~doty/papers/scadnano.bib>`_ ]
This library uses typing hints from the Python typing library.
(https://docs.python.org/3/library/typing.html)
Expand Down Expand Up @@ -63,7 +63,7 @@
from ._version import __version__
except ImportError:
# this is so scadnano.py file works without _version.py being present, in case user downloads it
__version__ = "0.9.3"
__version__ = "0.9.4"

StrandLabel = TypeVar('StrandLabel')
DomainLabel = TypeVar('DomainLabel')
Expand Down Expand Up @@ -1224,6 +1224,10 @@ def __str__(self):
def strand(self) -> Strand:
return self._parent_strand

def set_label(self, label: StrandLabel):
"""Sets label of this :any:`Domain`."""
self.label = label

def _check_start_end(self):
if self.start >= self.end:
raise StrandError(self._parent_strand,
Expand Down Expand Up @@ -1907,6 +1911,44 @@ def with_domain_sequence(self, sequence: str, assign_complement: bool = True) ->
assign_complement=assign_complement)
return self

def with_label(self, label: StrandLabel) -> StrandBuilder:
"""
Assigns `label` as label of the :any:`Strand` being built.
.. code-block:: Python
design.strand(0, 0).to(10).cross(1).to(5).with_label('scaffold')
:param label: label to assign to the :any:`Strand`
:return: self
"""
self.strand.set_label(label)
return self

def with_domain_label(self, label: DomainLabel) -> StrandBuilder:
"""
Assigns `label` as DNA sequence of the most recently created :any:`Domain` in
the :any:`Strand` being built. This should be called immediately after a :any:`Domain` is created
via a call to
:py:meth:`StrandBuilder.to`,
:py:meth:`StrandBuilder.update_to`,
or
:py:meth:`StrandBuilder.loopout`, e.g.,
.. code-block:: Python
design.strand(0, 5).to(8).with_domain_label('domain 1')\
.cross(1).to(5).with_domain_label('domain 2')\
.loopout(2, 4).with_domain_label('domain 3')\
.to(10).with_domain_label('domain 4')
:param label: label to assign to the :any:`Domain`
:return: self
"""
last_domain = self.strand.domains[-1]
last_domain.set_label(label)
return self


@dataclass
class Strand(_JSONSerializable, Generic[StrandLabel, DomainLabel]):
Expand Down
118 changes: 90 additions & 28 deletions tests/scadnano_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2202,8 +2202,9 @@ def test_domain_labels(self):
self.assertEqual(dom11_expected.label, dom11.label)

def test_nondefault_geometry(self):
geometry_expected = sc.Geometry(rise_per_base_pair=10.0, helix_radius=4.0, bases_per_turn=11.0, minor_groove_angle=10.0,
inter_helix_gap=5.0)
geometry_expected = sc.Geometry(rise_per_base_pair=10.0, helix_radius=4.0, bases_per_turn=11.0,
minor_groove_angle=10.0,
inter_helix_gap=5.0)
design = sc.DNADesign(helices=[], strands=[], geometry=geometry_expected)
json_str = design.to_json()
design_from_json = sc.DNADesign.from_scadnano_json_str(json_str)
Expand Down Expand Up @@ -2738,6 +2739,7 @@ def test_strand_references_nonexistent_helix(self):
with self.assertRaises(sc.IllegalDNADesignError):
sc.DNADesign(grid=sc.square, helices=[h1, h2], strands=strands)


class TestInsertRemoveDomains(unittest.TestCase):

def setUp(self) -> None:
Expand All @@ -2762,7 +2764,7 @@ def test_insert_domain_with_sequence(self):
sc.Domain(0, True, 0, 3),
sc.Domain(1, False, 0, 3),
sc.Domain(3, True, 0, 3),
]) #, dna_sequence='ACA TCT GTG'.replace(' ', ''))
]) # , dna_sequence='ACA TCT GTG'.replace(' ', ''))
self.assertEqual(expected_strand_before, design.strands[0])

domain = sc.Domain(2, True, 0, 3)
Expand All @@ -2783,7 +2785,7 @@ def test_append_domain_with_sequence(self):
sc.Domain(1, False, 0, 3),
sc.Domain(2, True, 0, 3),
sc.Domain(3, False, 0, 3),
], dna_sequence='ACA TCT GTG ???'.replace(' ',''))
], dna_sequence='ACA TCT GTG ???'.replace(' ', ''))
self.assertEqual(expected_strand, self.strand)

def test_remove_first_domain_with_sequence(self):
Expand Down Expand Up @@ -2811,6 +2813,69 @@ def test_remove_last_domain_with_sequence(self):
self.assertEqual(expected_strand, self.strand)


class TestLabels(unittest.TestCase):

def setUp(self) -> None:
helices = [sc.Helix(max_offset=100) for _ in range(10)]
self.design = sc.DNADesign(helices=helices, strands=[], grid=sc.square)

def test_with_label__str(self):
label = 'abc'
self.design.strand(0, 0).to(5).cross(1).to(0).with_label(label)
actual_strand = self.design.strands[0]
expected_strand = sc.Strand(domains=[
sc.Domain(0, True, 0, 5),
sc.Domain(0, False, 0, 5),
], label=label)

self.assertEqual(expected_strand.label, actual_strand.label)

def test_with_label__dict(self):
label = {'name': 'abc', 'type': 3}
self.design.strand(0, 0).to(5).cross(1).to(0).with_label(label)
actual_strand = self.design.strands[0]
expected_strand = sc.Strand(domains=[
sc.Domain(0, True, 0, 5),
sc.Domain(0, False, 0, 5),
], label=label)

self.assertDictEqual(expected_strand.label, actual_strand.label)

def test_with_domain_label(self):
label0 = 'abc'
label1 = {'name': 'abc', 'type': 3}
self.design.strand(0, 0).to(5).with_domain_label(label0).cross(1).to(0).with_domain_label(label1)
actual_strand = self.design.strands[0]
expected_strand = sc.Strand(domains=[
sc.Domain(0, True, 0, 5, label=label0),
sc.Domain(0, False, 0, 5, label=label1),
])

self.assertEqual(expected_strand.domains[0].label, actual_strand.domains[0].label)
self.assertDictEqual(expected_strand.domains[1].label, actual_strand.domains[1].label)

def test_with_domain_label__and__with_label(self):
strand_label = 'xyz'
label0 = 'abc'
label1 = {'name': 'abc', 'type': 3}
self.design.strand(0, 0).to(5).with_domain_label(label0).cross(1).to(0).with_domain_label(label1) \
.with_label(strand_label)
actual_strand = self.design.strands[0]
expected_strand = sc.Strand(domains=[
sc.Domain(0, True, 0, 5, label=label0),
sc.Domain(0, False, 0, 5, label=label1),
], label=strand_label)

self.assertEqual(expected_strand.label, actual_strand.label)
self.assertEqual(expected_strand.domains[0].label, actual_strand.domains[0].label)
self.assertDictEqual(expected_strand.domains[1].label, actual_strand.domains[1].label)


def set_colors_black(*strands):
for strand in strands:
strand.set_color(sc.Color(r=0, g=0, b=0))


class TestAddStrand(unittest.TestCase):

def test_add_strand__with_loopout(self):
Expand Down Expand Up @@ -3461,16 +3526,16 @@ def test_assign_dna__wildcards_multiple_overlaps(self):
098 765 432 109 876 543 210
"""
self.design.assign_dna(self.strand_top_big9, 'AACTTC')
self.assertEqual('??? ??? ??? GTT ??? GAA ???'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('??? ??? ??? GTT ??? GAA ???'.replace(' ', ''), self.strand_bot.dna_sequence)

self.design.assign_dna(self.strand_top_small12, 'TGC')
self.assertEqual('??? ??? GCA GTT ??? GAA ???'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('??? ??? GCA GTT ??? GAA ???'.replace(' ', ''), self.strand_bot.dna_sequence)

self.design.assign_dna(self.strand_top_small0, 'ACG')
self.assertEqual('??? ??? GCA GTT ??? GAA CGT'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('??? ??? GCA GTT ??? GAA CGT'.replace(' ', ''), self.strand_bot.dna_sequence)

self.design.assign_dna(self.strand_top_big6, 'GGATTGGCA')
self.assertEqual('TGC CAA GCA GTT TCC GAA CGT'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('TGC CAA GCA GTT TCC GAA CGT'.replace(' ', ''), self.strand_bot.dna_sequence)

def test_assign_dna__domain_sequence_too_long_error(self):
with self.assertRaises(sc.IllegalDNADesignError):
Expand Down Expand Up @@ -3499,8 +3564,8 @@ def test_assign_dna__to_individual_domains__wildcards_multiple_overlaps(self):
098 765 432 109 876 543 210
"""
self.design.assign_dna(self.strand_top_big9, 'AAC', domain=self.dom_top9)
self.assertEqual('AAC ???'.replace(' ',''), self.strand_top_big9.dna_sequence)
self.assertEqual('??? ??? ??? GTT ??? ??? ???'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('AAC ???'.replace(' ', ''), self.strand_top_big9.dna_sequence)
self.assertEqual('??? ??? ??? GTT ??? ??? ???'.replace(' ', ''), self.strand_bot.dna_sequence)

"""
012 345 678 901 234 567 890
Expand All @@ -3513,8 +3578,8 @@ def test_assign_dna__to_individual_domains__wildcards_multiple_overlaps(self):
098 765 432 109 876 543 210
"""
self.design.assign_dna(self.strand_top_big9, 'TTC', domain=self.dom_top3)
self.assertEqual('AAC TTC'.replace(' ',''), self.strand_top_big9.dna_sequence)
self.assertEqual('??? ??? ??? GTT ??? GAA ???'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('AAC TTC'.replace(' ', ''), self.strand_top_big9.dna_sequence)
self.assertEqual('??? ??? ??? GTT ??? GAA ???'.replace(' ', ''), self.strand_bot.dna_sequence)

"""
012 345 678 901 234 567 890
Expand Down Expand Up @@ -3542,7 +3607,7 @@ def test_assign_dna__to_individual_domains__wildcards_multiple_overlaps(self):
"""
self.design.assign_dna(self.strand_top_small0, 'ACG')
self.assertEqual('ACG', self.strand_top_small0.dna_sequence)
self.assertEqual('??? ??? GCA GTT ??? GAA CGT'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('??? ??? GCA GTT ??? GAA CGT'.replace(' ', ''), self.strand_bot.dna_sequence)

"""
012 345 678 901 234 567 890
Expand All @@ -3555,8 +3620,8 @@ def test_assign_dna__to_individual_domains__wildcards_multiple_overlaps(self):
098 765 432 109 876 543 210
"""
self.design.assign_dna(self.strand_top_big6, 'TTG', domain=self.dom_top15)
self.assertEqual('??? TTG ???'.replace(' ',''), self.strand_top_big6.dna_sequence)
self.assertEqual('??? CAA GCA GTT ??? GAA CGT'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('??? TTG ???'.replace(' ', ''), self.strand_top_big6.dna_sequence)
self.assertEqual('??? CAA GCA GTT ??? GAA CGT'.replace(' ', ''), self.strand_bot.dna_sequence)

"""
012 345 678 901 234 567 890
Expand All @@ -3569,8 +3634,8 @@ def test_assign_dna__to_individual_domains__wildcards_multiple_overlaps(self):
098 765 432 109 876 543 210
"""
self.design.assign_dna(self.strand_top_big6, 'GCA', domain=self.dom_top18)
self.assertEqual('??? TTG GCA'.replace(' ',''), self.strand_top_big6.dna_sequence)
self.assertEqual('TGC CAA GCA GTT ??? GAA CGT'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('??? TTG GCA'.replace(' ', ''), self.strand_top_big6.dna_sequence)
self.assertEqual('TGC CAA GCA GTT ??? GAA CGT'.replace(' ', ''), self.strand_bot.dna_sequence)

"""
012 345 678 901 234 567 890
Expand All @@ -3583,8 +3648,8 @@ def test_assign_dna__to_individual_domains__wildcards_multiple_overlaps(self):
098 765 432 109 876 543 210
"""
self.design.assign_dna(self.strand_top_big6, 'GGA', domain=self.dom_top6)
self.assertEqual('GGA TTG GCA'.replace(' ',''), self.strand_top_big6.dna_sequence)
self.assertEqual('TGC CAA GCA GTT TCC GAA CGT'.replace(' ',''), self.strand_bot.dna_sequence)
self.assertEqual('GGA TTG GCA'.replace(' ', ''), self.strand_top_big6.dna_sequence)
self.assertEqual('TGC CAA GCA GTT TCC GAA CGT'.replace(' ', ''), self.strand_bot.dna_sequence)

def test_method_chaining_with_domain_sequence(self):
"""
Expand Down Expand Up @@ -3649,7 +3714,6 @@ def test_method_chaining_with_domain_sequence(self):
self.assertEqual('AAC TTC'.replace(' ', ''), design.strands[-1].dna_sequence)
self.assertEqual('??? ??? GCA GTT ??? GAA ???'.replace(' ', ''), design.strands[0].dna_sequence)


"""
012 345 678 901 234 567 890
+---------------+
Expand All @@ -3660,19 +3724,17 @@ def test_method_chaining_with_domain_sequence(self):
<???---AAG---CCT---TTG---ACG---AAC---CGT-
098 765 432 109 876 543 210
"""
design.strand(0, 6).to(9)\
.with_domain_sequence('GGA')\
.cross(0, offset=15)\
.to(18)\
.with_domain_sequence('TTG')\
.to(21)\
design.strand(0, 6).to(9) \
.with_domain_sequence('GGA') \
.cross(0, offset=15) \
.to(18) \
.with_domain_sequence('TTG') \
.to(21) \
.with_domain_sequence('GCA')
self.assertEqual('GGA TTG GCA'.replace(' ', ''), design.strands[-1].dna_sequence)
self.assertEqual('TGC CAA GCA GTT TCC GAA ???'.replace(' ', ''), design.strands[0].dna_sequence)




TEST_OFFSETS_AT_DELETION_INSERTIONS = False


Expand Down

0 comments on commit 92d2a44

Please sign in to comment.