Created
March 22, 2021 15:20
-
-
Save flashton2003/d6014412bf6345a17331abeace97ddce to your computer and use it in GitHub Desktop.
script to calculate costs for nanopore sequencing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
from copy import deepcopy | |
def how_many_packs(number_flow_cells, option1): | |
# pack_sizes is nanopore flowcell pack sizes, sorted from high to low | |
pack_sizes = sorted([300, 48, 24, 12, 1], reverse = True) | |
# pack to buy is the largest pack which is less than or equal to the number we need | |
pack_to_buy = [x for x in pack_sizes if x <= number_flow_cells][0] | |
# make a note that we need one of that pack size in the option1 dict | |
if pack_to_buy in option1: | |
option1[pack_to_buy] += 1 | |
else: | |
option1[pack_to_buy] = 1 | |
# since we have "purhcased" that many flowcells, remove them from the | |
# total we need | |
number_flow_cells -= pack_to_buy | |
return number_flow_cells, option1 | |
def get_option2(packs_to_buy): | |
option_2 = deepcopy(packs_to_buy) | |
# get the smallest pack size in our current distribution | |
smallest = sorted(list(option_2.keys()))[0] | |
pack_sizes = sorted([300, 48, 24, 12, 1], reverse = True) | |
next_up = None | |
# 300 is biggest, so cant get bigger obvs | |
if smallest != 300: | |
# get the pack size up | |
next_up = pack_sizes[pack_sizes.index(smallest) - 1] | |
# delete the smallest | |
del option_2[smallest] | |
# add another pack of the size above (next_up) | |
if next_up in option_2: | |
option_2[next_up] += 1 | |
else: | |
option_2[next_up] = 1 | |
return option_2 | |
def calculate_cost(packs): | |
total_cost = 0 | |
total_number_flowcells = 0 | |
pack_cost = {1:650, 12:6857, 24:11718, 48:17361, 300:103048} | |
# print(packs) | |
for pack in packs: | |
total_cost += pack_cost[pack] * packs[pack] | |
total_number_flowcells += pack * packs[pack] | |
return total_cost, total_number_flowcells | |
def calc_flow_cell_cost(number_flow_cells): | |
# going to "chip away" at the number of flow cells to figure out the | |
# pack size distribution we need, so take a deepcopy, so that we have original | |
i = deepcopy(number_flow_cells) | |
# use how_many_packs to figure out how many packs and of what size we need | |
option1 = {} | |
while i > 0: | |
i, option1 = how_many_packs(i, option1) | |
# give an option to pay a larger pack size, with more flowcells than we need, | |
# but might be a good idea because of the packsize discount | |
option2 = get_option2(option1) | |
# convert pack size distribution to actual cost, and the total number of flowcells with each option | |
option1_cost, option1_packs = calculate_cost(option1) | |
option2_cost, option2_packs = calculate_cost(option2) | |
# try: | |
option2_cost_per_extra_flowcell = (option2_cost - option1_cost) / (option2_packs - number_flow_cells) | |
print('*********** Flow cell costs ********************') | |
print('There are two options for flow cells (format is {flowcell_batch_size: number_of_packs_of_that_batch_size_required}):') | |
print(f'\tOption1: {option1}, which costs {option1_cost} and leaves us {option1_packs - number_flow_cells} spare flow cells') | |
print(f'\tOption2: {option2}, which costs {option2_cost} and leaves us {option2_packs - number_flow_cells} spare flow cells, cost per extra flowcell is {option2_cost_per_extra_flowcell}') | |
print('************************************************') | |
return option1_cost, option2_cost | |
def main(): | |
# per_sample_costs = ((reagent_name, number_of_samples_in_that_pack, cost_per_pack)) | |
# all costs are in GBP | |
# per sample costs are for everything that scales perfectly with number of samples | |
per_sample_costs = ( | |
('qubit_reagents', 500, 195), | |
('qubit_tubes', 500, 51.50), | |
('genfind_v3_dna_extraction', 50, 235), | |
('nanopore_rapid_library_prep', 72, 469), | |
['nanopore_flow_cells', 12, 650], | |
('xld_plates', 1, 2) | |
) | |
# other_costs are for other bits and bobs you need around, but not | |
# for every sample | |
other_costs = (('ampure', 1200, 992),) | |
number_of_samples_todo = 220 | |
total_cost = 0 | |
for reagent in per_sample_costs: | |
# math.ceil finds integer above number, because if we need 4.1 packs, then we need to buy 5 packs | |
# reagent[1] is how many samples can process per pack | |
# pack_count is how many packs we need | |
pack_count = math.ceil(number_of_samples_todo / reagent[1]) | |
# flowcells are complex because of discounted pricing with larger packs | |
if reagent[0] == 'nanopore_flow_cells': | |
option1_cost, option2_cost = calc_flow_cell_cost(pack_count) | |
# non-flowcells pretty straightforward | |
else: | |
# reagent[2] is the per pack price, pack_count is how mnay packs we need | |
reagent_cost = pack_count * reagent[2] | |
# how much leftover reagent will we have? | |
left_over_reagent = (pack_count * reagent[1]) - number_of_samples_todo | |
print(f'We need {pack_count} {reagent[0]}, at a cost of {reagent_cost}. We will have {left_over_reagent} samples worth of reagent left.') | |
# add to total cost | |
total_cost += reagent_cost | |
for reagent in other_costs: | |
print(f'We also need {reagent[0]}, which costs {reagent[2]}') | |
total_cost += reagent[2] | |
print(f'\nTotal cost of option 1 is {total_cost + option1_cost}') | |
print(f'Total cost of option 2 is {total_cost + option2_cost}') | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment