Last active
January 27, 2021 01:02
-
-
Save dpgoldenberg/ed9789eee59dfb9e187b8ef406e5ad19 to your computer and use it in GitHub Desktop.
Pipette Calibration Simulation
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
Gist title |
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
name: pipette_sim | |
channels: | |
- conda-forge | |
- defaults | |
- conda-forge/label/broken | |
dependencies: | |
- python=3.7.3 | |
- numpy=1.16.4 | |
- ipython=7.6.1 | |
- ipywidgets=7.5.0 | |
- voila = 0.1.21 |
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
## Module to simulate procedure for testing pipettes (and user) | |
## by weighing successive aliqots of water | |
## with widgets to allow interactive control in a Jupyter notebook | |
## David P. Goldenberg, December 2020 | |
## goldenberg@biology.utah.edu | |
import numpy as np | |
import ipywidgets as widgets | |
from IPython.display import display, HTML | |
from time import sleep | |
import threading | |
import random as rand | |
display(HTML("<style>div.output_scroll { height: 200ex; }</style>")) | |
#---------Global variables-----------------# | |
# global variables | |
curr_grams = 0.0 # current grams displayed | |
p_delta = 0.0 # inaccuracy of pipette calibration, in uL | |
p_sigma = 0.0 # std dev used to calculate both normal distr of p_delta | |
# and error in individual pipette events | |
#--------Functions for simulating pipette errors ------------# | |
def pip_err(pip_size,sigma,p_fail): | |
'''Sets global variables, p_delta and p_sigma. | |
input arguments: | |
pip_size: maximum volume delivered by pipette, in uL | |
sigma: relative value of standard deviation, in percent | |
p_fail: probability of a significant pipette malfunction | |
''' | |
global p_delta,p_sigma | |
sigma = 0.01*sigma*pip_size | |
if rand.random() < p_fail: | |
# if pipette malfunction, p_delta is set to random value | |
# uniformly distributed between +/- 0.2*pip_size | |
p_delta = 0.4*pip_size*(rand.random()-0.5) | |
else: | |
# if no malfunction, p_delta is randomly distributed | |
# about 0 | |
p_delta = rand.normalvariate(0,sigma) | |
if rand.random() < p_fail: | |
# if pipette malfunction, p_sigma is set to | |
# random value distributed uniformly between | |
# +/- 0.2*pip_size | |
p_sigma = 0.2*pip_size*rand.random() | |
else: | |
p_sigma = rand.normalvariate(0,sigma) | |
#print(p_delta,p_sigma) | |
def del_err(pip_size,delta,sigma): | |
# Calculates error for individual pipette operations | |
err = rand.normalvariate(delta, sigma) | |
return err | |
#---------------Widgets-----------------------# | |
header_html = """ | |
<h2> Biology 3515/Chemistry 3515 | |
<br> | |
Biological Chemistry Laboratory | |
<br> | |
University of Utah </h2> | |
<h3> Spring 2021 </h3> | |
<h3> Experiment 1, Part A: Pipette Calibration</h3> | |
<p style="font-size:16px"> | |
This web page simulates the procedure for testing pipettes (and their use) by weighing aliquots of water | |
added successively to a balance. To carry out the procedure, first tare (zero) the balance. Then, choose a pipette size | |
(P1000: 1 mL, P200: 200 µL, P20: 20 µL) and set the volume to be delivered. Pipette 5 aliquots of water and | |
record the mass after each aliquot. Be sure to wait for the balance to reach equilibrium before recording the mass! | |
<br> | |
If you find that the pipette displays unacceptable errors, you should obtain a new one, by first selecting a different-sized pipette and then selecting the original size again. | |
<hr border-width:6px, color:black> | |
""" | |
style = {'description_width': 'initial'} | |
header = widgets.HTML( | |
value=header_html | |
) | |
footer = widgets.HTML( | |
value=''' | |
<hr border-width:6px, color:black> | |
David P. Goldenberg, January 2021<br> | |
<a href="mailto:goldenberg@biology.utah.edu" target="new">goldenberg@biology.utah.edu</a><br> | |
School of Biological Sciences, University of Utah<br> | |
Salt Lake City, Utah 84112<br> | |
<a href="https://goldenberg.biology.utah.edu/courses/biol3515/index.shtml" | |
target="new">https://goldenberg.biology.utah.edu/courses/biol3550.</a> | |
''') | |
pipette = widgets.Dropdown( | |
# Choose pipette size | |
options = [('P1000',1000.0),('P200',200.0),('P20',20.0)], | |
style=style, | |
description = 'Pipette:', | |
layout=widgets.Layout(width='200px',margin='10px 100px 15px 0px') | |
) | |
volume = widgets.BoundedFloatText( | |
value = 1000.0, | |
min = 200.0, | |
max = 1000.0, | |
step = 10.0, | |
style=style, | |
description = 'Volume set (uL):', | |
layout=widgets.Layout(width='200px',margin='10px 100px 15px 0px') | |
) | |
grams = widgets.HTML( | |
# Grams display | |
value='<h1> gm: <span style="color:red;"> {:.4f}'.format(0) + '</span></h1>', | |
layout=widgets.Layout(width='250px',margin='25px 0px 15px 200px') | |
) | |
deliver_butt = widgets.Button( | |
# Button to deliver volume | |
description = 'Pipette!', | |
button_style = 'success', | |
style=style, | |
layout=widgets.Layout(width='150px',margin='25px 150px 15px 70px') | |
) | |
tare_butt = widgets.Button( | |
# Button to tare balance | |
description = 'Tare balance', | |
button_style = 'warning', | |
style=style, | |
layout=widgets.Layout(width='150px',margin='25px 0px 15px 0px') | |
) | |
#------------Functions to control widgets----------------# | |
def damp_sin(t,nu,tau): | |
'''damped sine function to simulate settling | |
of balance. Input arguments: | |
t: time | |
nu: sine-function frequency | |
tau: exponential-decay time constant | |
''' | |
d_sin = np.exp(-t/tau)*np.sin(t*nu*2*np.pi) | |
return d_sin | |
def adjust_grams(start,end,t_tot,steps): | |
'''Updates grams display after adding aliquot | |
Input arguments: | |
start: starting mass | |
end: mass after addition | |
t_tot: total time for balance to settle | |
steps: number of times to update balance display | |
as it settles. | |
''' | |
tau = 0.2*t_tot | |
nu = 5/t_tot | |
for i in range(steps+1): | |
t = t_tot*i/steps | |
gms = end + (end-start)*damp_sin(t,nu,tau) | |
sleep(t_tot/steps) | |
update_grams(gms) | |
def on_pipette_ch(change): | |
'''Changes volume and error parameters when pipette is changed.''' | |
volume.max = 1000.0 | |
if pipette.value == 1000.0: | |
volume.min = 200.0 | |
volume.max = 1000.0 | |
volume.step = 10.0 | |
elif pipette.value == 200.0: | |
volume.min = 20.0 | |
volume.max = 200.0 | |
volume.step = 1.0 | |
elif pipette.value == 20.0: | |
volume.min = 1.0 | |
volume.max = 20.0 | |
volume.step = 0.1 | |
volume.value = volume.max | |
p_fail = 0.1 | |
rel_sigma = 1 | |
pip_err(pipette.value,rel_sigma,p_fail) | |
def update_grams(gm): | |
'''Updates grams display''' | |
grams.value='<h1> gm: <span style="color:red;"> {:.4f}'.format(gm) + '</span></h1>' | |
def deliver(change): | |
# function for deliver button | |
# Updates curr_grams and calls adjust_grams | |
# to update grams display | |
global curr_grams | |
start = curr_grams | |
gram_incr = volume.value/1000.0 | |
gram_incr += del_err(pipette.value,p_delta,p_sigma)/1000.0 | |
curr_grams += gram_incr | |
t_tot =2 | |
steps=25 | |
adjust_grams(start,curr_grams,t_tot,steps) | |
def tare(change): | |
# Function for tare button. | |
# sets curr_grams to 0 and updates display | |
global curr_grams | |
curr_grams = 0 | |
update_grams(curr_grams) | |
#----------Monitor widgets--------------# | |
pipette.observe(on_pipette_ch,names=['value']) | |
deliver_butt.on_click(deliver) | |
tare_butt.on_click(tare) | |
#----------Display widgets--------------# | |
update_grams(curr_grams) | |
row1 = widgets.HBox([pipette,volume]) | |
row2 = widgets.HBox([deliver_butt,tare_butt]) | |
display(widgets.VBox([header,row1,row2,grams,footer])) | |
#------------Start everything-----------# | |
on_pipette_ch(0) | |
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
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<style>div.output_scroll { height: 200ex; }</style>" | |
], | |
"text/plain": [ | |
"<IPython.core.display.HTML object>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
}, | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "47b312dacc1c4db7a7421f644f792742", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"VBox(children=(HTML(value='\\n<h2> Biology 3515/Chemistry 3515\\n<br>\\nBiological Chemistry Laboratory \\n<br>\\nU…" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import pipette_cal_sim\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.7.0" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment