Skip to content

Commit

Permalink
Merge pull request #181 from UC-Davis-molecular-computing/177-export-…
Browse files Browse the repository at this point in the history
…IDT-plates-rebalance

closes #177; export to IDT plates rebalances strands among last two plates if the last plate has too few strands
  • Loading branch information
dave-doty authored Jun 30, 2021
2 parents a7b9e38 + 92699a7 commit 4a091ac
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 63 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
uses: s-weigand/setup-conda@v1
with:
activate-conda: true
- name: Install xlwt with conda
run: conda install xlwt=1.3.0
- name: Install xlwt and xlrd with conda
run: conda install xlwt=1.3.0 xlrd=2.0.1
- name: Install docutils with conda
run: conda install docutils=0.16
- name: Test with unittest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ __pyache__/*
*.egg-info
/scadnano.zip
.pypirc
tests/tests_outputs/
tests/tests_outputs/cadnano_v2_export/*
tests/tests_outputs/cadnano_v2_export/test_16_helix_origami_rectangle_no_twist.dna
tests/tests_outputs/cadnano_v2_export/test_16_helix_origami_rectangle_no_twist.json
Expand Down
69 changes: 65 additions & 4 deletions scadnano/scadnano.py
Original file line number Diff line number Diff line change
Expand Up @@ -3435,6 +3435,30 @@ def rows(self) -> List[str]:
def cols(self) -> List[int]:
return _96WELL_PLATE_COLS if self is PlateType.wells96 else _384WELL_PLATE_COLS

def num_wells_per_plate(self) -> int:
"""
:return:
number of wells in this plate type
"""
if self is PlateType.wells96:
return 96
elif self is PlateType.wells384:
return 384
else:
raise AssertionError('unreachable')

def min_wells_per_plate(self) -> int:
"""
:return:
minimum number of wells in this plate type to avoid extra charge by IDT
"""
if self is PlateType.wells96:
return 24
elif self is PlateType.wells384:
return 96
else:
raise AssertionError('unreachable')


class _PlateCoordinate:

Expand Down Expand Up @@ -3465,6 +3489,11 @@ def col(self) -> int:
def well(self) -> str:
return f'{self.row()}{self.col()}'

def advance_to_next_plate(self):
self._row_idx = 0
self._col_idx = 0
self._plate += 1


def remove_helix_idxs_if_default(helices: List[Dict]) -> None:
# removes indices from each helix if they are the default (order of appearance in list)
Expand Down Expand Up @@ -5486,6 +5515,11 @@ def write_idt_plate_excel_file(self, *, directory: str = '.', filename: str = No
For instance, if the script is named ``my_origami.py``,
then the sequences will be written to ``my_origami.xls``.
If the last plate as fewer than 24 strands for a 96-well plate, or fewer than 96 strands for a
384-well plate, then the last two plates are rebalanced to ensure that each plate has at least
that number of strands, because IDT charges extra for a plate with too few strands:
https://www.idtdna.com/pages/products/custom-dna-rna/dna-oligos/custom-dna-oligos
:param directory:
specifies a directory in which to place the file, either absolute or relative to
the current working directory. Default is the current working directory.
Expand Down Expand Up @@ -5543,7 +5577,7 @@ def write_idt_plate_excel_file(self, *, directory: str = '.', filename: str = No
self._write_plates_assuming_explicit_plates_in_each_strand(directory, filename, strands_to_export)
else:
self._write_plates_default(directory=directory, filename=filename,
strands_to_export=strands_to_export,
strands=strands_to_export,
plate_type=plate_type,
warn_using_default_plates=warn_using_default_plates)

Expand Down Expand Up @@ -5596,7 +5630,7 @@ def _setup_excel_file(directory: str, filename: Optional[str]) -> Tuple[str, Any
workbook = xlwt.Workbook()
return filename_plate, workbook

def _write_plates_default(self, directory: str, filename: Optional[str], strands_to_export: List[Strand],
def _write_plates_default(self, directory: str, filename: Optional[str], strands: List[Strand],
plate_type: PlateType = PlateType.wells96,
warn_using_default_plates: bool = True) -> None:
plate_coord = _PlateCoordinate(plate_type=plate_type)
Expand All @@ -5605,7 +5639,22 @@ def _write_plates_default(self, directory: str, filename: Optional[str], strands
filename_plate, workbook = self._setup_excel_file(directory, filename)
worksheet = self._add_new_excel_plate_sheet(f'plate{plate}', workbook)

for strand in strands_to_export:

num_strands_per_plate = plate_type.num_wells_per_plate()
num_plates_needed = len(strands) // num_strands_per_plate
if len(strands) % num_strands_per_plate != 0:
num_plates_needed += 1

min_strands_per_plate = plate_type.min_wells_per_plate()

num_strands_plates_except_final = max(0, (num_plates_needed - 1) * num_strands_per_plate)
num_strands_final_plate = len(strands) - num_strands_plates_except_final
final_plate_less_than_min_required = num_strands_final_plate < min_strands_per_plate

num_strands_remaining = len(strands)
on_final_plate = num_plates_needed == 1

for strand in strands:
if strand.idt is not None:
if warn_using_default_plates and strand.idt.plate is not None:
print(
Expand All @@ -5620,7 +5669,19 @@ def _write_plates_default(self, directory: str, filename: Optional[str], strands
worksheet.write(excel_row, 0, well)
worksheet.write(excel_row, 1, strand.idt_export_name())
worksheet.write(excel_row, 2, strand.idt_dna_sequence())
plate_coord.increment()
num_strands_remaining -= 1

# IDT charges extra for a plate with < 24 strands for 96-well plate
# or < 96 strands for 384-well plate.
# So if we would have fewer than that many on the last plate,
# shift some from the penultimate plate.
if not on_final_plate and \
final_plate_less_than_min_required and \
num_strands_remaining == min_strands_per_plate:
plate_coord.advance_to_next_plate()
else:
plate_coord.increment()

if plate != plate_coord.plate():
workbook.save(filename_plate)
plate = plate_coord.plate()
Expand Down
Loading

0 comments on commit 4a091ac

Please sign in to comment.