#!/usr/bin/env python
# MIT License
# Copyright (c) 2022, Technical University of Denmark (DTU)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
""" This part of the lab module is used for making transformations"""
# standard libraries
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
from pydna.dseqrecord import Dseqrecord
# for plotting with pyplot
import matplotlib.pyplot as plt
[docs]def ng_to_nmol(ng: float, bp: float):
"""Calculates nanogram to nanomol for transformation mixes.
To do a transformation it is important to have the right ratio
of plasmid to insert. In other words this is done by calculating
the nanomolar ratios and this tool can do that
Parameters
----------
ng : float
eg. nanogram
param: float
eg. number of basepairs. Can also be an int
Returns
-------
ng_to_nmol : float
Note
----
It calculates the nmol in the following way:
nmol = ng/(bp*650)
"""
if ng > 0 and bp > 0:
return ng / (bp * 650)
else:
return "non-valid_input"
[docs]def ODtime(initialOD: float, time: float, td: float = 0.5):
"""Calculates the OD based on doupling time.
Parameters
----------
initialOD : float
in OD
time : float
in hours
td : float
doupling time i.e. td in h^-1
Returns
-------
OD : float
the OD after a certain time()
"""
if initialOD >= 0 and time >= 0:
return round(initialOD * 2 ** (time * td), 3)
else:
return "non-valid_input"
[docs]def time_to_inculate(
initialOD=0.0025, td=0.4, verbose=False, transformation_time: int = 12
):
"""Calculates when a starter culture is ready to be inoculated
with a transformation mixture.
Parameters
----------
initialOD : float
td : float
is doubling time
transformation_time : int
The time you want to transform
verbose : Bool
Provides extra information
Returns
-------
A plot of cell growth at different td
Notes
-----
This is used to calculate when the cells should be used for transformation.
For example:
OD_1 = 1 * 10^7 cells / ml
For a succesfull S.cerevisiae transformation between 1 to 2 × 10^7 cells/ml should be used
Normal doupling time is between 3-6 hours
"""
if verbose:
print("GOAL: to get enough cells in exponential phase for transformation")
print("Assumed that: ")
print(
"- transformation time "
+ str(transformation_time)
+ " (reached OD=1 the day after)"
)
times = list(range(0, 37))
ods_025_3 = [ODtime(initialOD, time, td=0.3) for time in times]
ods_025_input = [ODtime(initialOD, time, td=td) for time in times]
ods_025_5 = [ODtime(initialOD, time, td=0.5) for time in times]
fig = plt.figure()
ax = plt.axes()
# ax.set_xlim(lims)
ax.set_ylim([0, 2.2])
ax.plot(times, [2] * len(times), "r-", label="end of exponential phase")
ax.plot(times, [1] * len(times), "k-", label="target")
ax.plot(times, ods_025_3, label="iOD=" + str(initialOD) + ", td=0.3")
ax.plot(times, ods_025_input, label="iOD=" + str(initialOD) + ", td=" + str(td))
ax.plot(times, ods_025_5, label="iOD=" + str(initialOD) + ", td=0.5")
plt.xlabel("time, h^-1")
plt.ylabel("OD")
plt.legend()
plt.show()
def inoculation_time(times, ods):
def find_closest(A, target):
# A must be sorted
idx = A.searchsorted(target)
idx = np.clip(idx, 1, len(A) - 1)
left = A[idx - 1]
right = A[idx]
idx -= target - left < right - target
return idx
# In how many hours will the cells have reached OD 1?
hours_to_OD1 = times[find_closest(np.array(ods), 1)]
print("Hours to OD = 1: \t" + str(hours_to_OD1) + " hours")
### When do u need to innoculate?
when_to_inoculate = transformation_time - hours_to_OD1
if when_to_inoculate < 0:
print("Transformation time has been set to ", transformation_time)
print(
"Time of inoculation: \t"
+ str(when_to_inoculate + 24)
+ " (the day before)"
)
else:
print("Transformation time has been set to ", transformation_time)
print(
"Time of inoculation: \t" + str(when_to_inoculate) + "(the day before)"
)
# If i innoculate now?
print(
"\nIf you innoculate now, the cells will have reached OD= 1 by: ",
datetime.now() + timedelta(hours=hours_to_OD1),
)
inoculation_time(times, ods_025_input)
if verbose:
print()
print(
"How to hit initialOD = 0.0025 (e.g. from colony)? Guess. Inoculate 9/10 + 1/10 'normal' colony per ~10 ml"
)
print("How much volume? ~2 ml per transformation")
[docs]def wanted_mass(wanted_moles, size):
"""
Parameters
----------
wanted_moles : int
wanted moles in nmol
size : int
size in bp
Returns
-------
w_mass_rounded : int
in ng. Mass wanted for the reaction.
"""
w_mass = wanted_moles * size * 650
w_mass_rounded = round(w_mass, 1)
return w_mass_rounded
[docs]def wanted_volume(wanted_mass, actual_concentration):
"""
Parameters
----------
wanted_mass : int
wanted mass in ng
actual_concentration : int
actual_concentration in ng/ul
Returns
-------
wanted_volume_rounded : int
return in ul
"""
wanted_volume = wanted_mass / actual_concentration
wanted_volume_rounded = round(wanted_volume, 1)
return wanted_volume_rounded
[docs]def calculate_volume_and_total_concentration(amplicons, amplicon_parts_amounts_total, n= 1):
"""
Calculates the volume and total concentration of a list of DNA parts.Parameters
----------
amplicons : list
A list of amplicon objects
amplicon_parts_amounts_total : dict
A dictionary of amplicon names and their respective total amounts
n : int (optional)
Gives the option of multiplying the volume is needed. Optional set to 1.
Returns
-------
volumes : list
List of volumes of each amplicon
ngs : list
List of ngs of each amplicon
total_conc : float
Total concentration of all amplicons
"""
print('name, volume, concentration, location')
volumes = []
ngs = []
for amp in amplicons:
w_moles = amplicon_parts_amounts_total[amp.name]
w_mass = wanted_mass(wanted_moles=w_moles, size=len(amp))
act_conc = amp.annotations['batches'][0]['concentration']
w_volume = wanted_volume(w_mass, act_conc)*n
volumes.append(w_volume)
ngs.append(w_volume * act_conc)
print(amp.name, w_volume, act_conc, '\t', amp.annotations['batches'][0]['location'])
#Count total concentrtaion expected
total_vol = sum(volumes)
total_ngs = sum(ngs)
total_conc = total_ngs/total_vol
print('total volume: ', sum(volumes))
print()
print('total ngs: ', sum(ngs))
print('total conc: ', total_conc)
return volumes, ngs, total_conc
[docs]def pool_parts(amplicons:list, part_names:list,part_amounts:list, pool_names:list, pool_lengths)->dict:
"""Pools amplicon parts and returns a dictionary of pooled volumes.
Parameters
----------
amplicons : list
List of amplicon objects.
part_names : list
List of part names.
part_amounts : list
List of amounts of each part.
pool_names : list
List of pool names.
pool_lengths : list
List of pool lengths.
Returns
-------
pooled_volumes : dict
Dictionary containing the pooled volumes for each amplicon part.
"""
# intialize
pooled_volumes = {}
#Iterate through the parts that are avilable
for amplicon in amplicons:
if amplicon.template.name in part_names:
# calculate volume needed
ind1 = part_names.index(amplicon.template.name)
amount = part_amounts[ind1]
ind2 = pool_names.index(amplicon.template.name)
vol = (pool_lengths[ind2]*650*amount)/amplicon.annotations['batches'][0]['concentration']
# add it to the dictionary
if amplicon.template.name in pooled_volumes:
pooled_volumes[amplicon.template.name][amplicon.name] = {'volume_to_mix':round(vol,1),'location':amplicon.annotations['batches'][0]['location'], 'concentration':amplicon.annotations['batches'][0]['concentration']}
else:
pooled_volumes[amplicon.template.name] = {amplicon.name: {'volume_to_mix':round(vol,1),'location':amplicon.annotations['batches'][0]['location'], 'concentration':amplicon.annotations['batches'][0]['concentration']}}
return pooled_volumes
[docs]def print_pooled_parts(pooled_volumes: dict) -> None:
"""
print_pooled_parts(pooled_volumes)
Prints the pooled parts and calculated concentrations.
Parameters
----------
pooled_volumes : dict
Dictionary containing the pooled volumes for each amplicon part.
Returns
-------
None
"""
print("To be pooled together")
con_per_part = {}
for key in pooled_volumes:
print(key)
total_vol = 0
total_con = 0
total_ng = 0
for ke in pooled_volumes[key]:
print(ke, pooled_volumes[key][ke])
total_vol += pooled_volumes[key][ke]['volume_to_mix']
total_con += pooled_volumes[key][ke]['concentration']
total_ng += pooled_volumes[key][ke]['concentration'] * pooled_volumes[key][ke]['volume_to_mix']
print("vol", round(total_vol, 1))
print("calculated con", total_ng / total_vol, '\n')
con_per_part[key] = round(total_ng / total_vol)
total_con = 0
total_vol = 0
total_ng = 0