3 # calibcant - tools for thermally calibrating AFM cantilevers
5 # Copyright (C) 2007,2008, William Trevor King
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 # See the GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 # The author may be contacted at <wking@drexel.edu> on the Internet, or
23 # write to Trevor King, Drexel University, Physics Dept., 3141 Chestnut St.,
24 # Philadelphia PA 19104, USA.
27 Separate the more general calib_analyze() from the other calib_*()
28 functions in calibcant. Also provide a command line interface
29 for analyzing data acquired through other workflows.
31 The relevent physical quantities are :
32 Vzp_out Output z-piezo voltage (what we generate)
33 Vzp Applied z-piezo voltage (after external ZPGAIN)
34 Zp The z-piezo position
35 Zcant The cantilever vertical deflection
36 Vphoto The photodiode vertical deflection voltage (what we measure)
37 Fcant The force on the cantilever
38 T The temperature of the cantilever and surrounding solution
39 (another thing we measure)
40 k_b Boltzmann's constant
42 Which are related by the parameters :
44 zpSensitivity Zp / Vzp
45 photoSensitivity Vphoto / Zcant
50 from splittable_kwargs import splittableKwargsFunction, \
51 make_splittable_kwargs_function
52 import common # common module for the calibcant package
53 import config # config module for the calibcant package
54 import T_analyze # T_analyze module for the calibcant package
56 kb = 1.3806504e-23 # Boltzmann's constant in J/K
58 #@splittableKwargsFunction((calib_plot, 'bumps', 'Ts', 'vibs'))
59 # Some of the child functions aren't yet defined, so postpone
60 # make-splittable until later in the module.
61 def calib_analyze(bumps, Ts, vibs, **kwargs) :
63 Analyze data from get_calibration_data()
64 return (k, k_s, !!!a2_r, T_r, one_o_Vp2_r)
65 Inputs (all are arrays of recorded data) :
66 bumps measured (V_photodiode / nm_tip) proportionality constant
67 Ts measured temperature (K)
68 vibs measured V_photodiode variance in free solution (V**2)
70 k cantilever spring constant (in N/m, or equivalently nN/nm)
71 k_s standard deviation in our estimate of k
72 !!!a2_r relative error in a**2
73 !!!T_r relative error in T
74 !!!one_o_Vp2_r relative error in 1/Vphotodiode_variance
76 We're assuming vib is mostly from thermal cantilever vibrations
77 (and then only from vibrations in the single vertical degree of freedom),
78 and not from other noise sources.
79 The various relative errors are returned to help you gauge the
80 largest source of random error in your measurement of k.
81 If one of them is small, don't bother repeating that measurment too often.
82 If one is large, try repeating that measurement more.
83 Remember that you need enough samples to have a valid error estimate in
84 the first place, and that none of this addresses any systematic errors.
86 calib_plot_kwargs, = calib_analyze._splitargs(calib_analyze, kwargs)
87 photoSensitivity2 = bumps**2
88 one_o_Vphoto2 = 1/vibs
90 photoSensitivity2_m = photoSensitivity2.mean()
92 one_o_Vphoto2_m = one_o_Vphoto2.mean()
93 # Vphoto / photoSensitivity = x
94 # k = kb T / <x**2> = kb T photoSensitiviy**2 * (1e9nm/m)**2 /
97 # units, photoSensitivity = Vphoto/(Zcant in nm),
98 # so Vphoto/photoSensitivity = Zcant in nm
99 # so k = J/K * K / nm^2 * (1e9nm/m)**2 = N/m
100 k = kb * T_m * photoSensitivity2_m * one_o_Vphoto2_m * 1e18
102 # propogation of errors !!!
103 # first, get standard deviations
104 photoSensitivity2_s = photoSensitivity2.std()
106 one_o_Vphoto2_s = one_o_Vphoto2.std()
107 # !!!!now, get relative errors
108 photoSensitivity2_r = photoSensitivity2_s / photoSensitivity2_m
110 one_o_Vphoto2_r = one_o_Vphoto2_s / one_o_Vphoto2_m
112 k_s = k*(photoSensitivity2_r**2 + T_r**2 + one_o_Vphoto2_r**2)**0.5
114 calib_plot(bumps, Ts, vibs, **calib_plot_kwargs)
117 photoSensitivity2_m, photoSensitivity2_s,
118 T_m, T_s, one_o_Vphoto2_m, one_o_Vphoto2_s)
120 @splittableKwargsFunction()
121 def string_errors(k_m, k_s,
122 photoSensitivity2_m, photoSensitivity2_s,
124 one_o_Vphoto2_m, one_o_Vphoto2_s) :
126 photoSensitivity2_r = photoSensitivity2_s / photoSensitivity2_m
128 one_o_Vphoto2_r = one_o_Vphoto2_s / one_o_Vphoto2_m
129 string = "Variable (units) : mean +/- std. dev. (relative error)\n"
130 string += "Cantilever k (N/m) : %g +/- %g (%g)\n" \
132 string += "photoSensitivity**2 (V/nm)**2 : %g +/- %g (%g)\n" \
133 % (photoSensitivity2_m, photoSensitivity2_s, photoSensitivity2_r)
134 string += "T (K) : %g +/- %g (%g)\n" \
136 string += "1/Vphoto**2 (1/V)**2 : %g +/- %g (%g)" \
137 % (one_o_Vphoto2_m, one_o_Vphoto2_s, one_o_Vphoto2_r)
140 @splittableKwargsFunction()
141 def calib_save(bumps, Ts, vibs, log_dir=None) :
143 Save a dictonary with the bump, T, and vib data.
146 data = {'bump':bumps, 'T':Ts, 'vib':vibs}
147 log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
149 log.write_dict_of_arrays(data)
151 def calib_load(datafile) :
152 "Load the dictionary data, using data_logger.date_load()"
153 dl = data_logger.data_load()
154 data = dl.read_dict_of_arrays(path)
155 return (data['bump'], data['T'], data['vib'])
157 def calib_save_analysis(k, k_s,
158 photoSensitivity2_m, photoSensitivity2_s,
159 T_m, T_s, one_o_Vphoto2_m, one_o_Vphoto2_s,
162 log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
163 log_name="calib_analysis_text")
165 "k (N/m) : %g +/- %g\n" % (k, k_s)
166 + "photoSensitivity**2 (V/nm)**2 : %g +/- %g\n" % \
167 (photoSensitivity2_m, photoSensitivity2_s)
168 + "T (K) : %g +/- %g\n" % (T_m, T_s)
169 + "1/Vp**2 (1/V)**2 : %g +/- %g\n" % \
170 (one_o_Vphoto2_m, one_o_Vphoto2_s)
173 @splittableKwargsFunction()
174 def calib_plot(bumps, Ts, vibs, plotVerbose=False) :
175 if plotVerbose or config.PYLAB_VERBOSE :
176 common.import_pylab()
177 common._pylab.figure(config.BASE_FIGNUM+4)
178 common._pylab.subplot(311)
179 common._pylab.plot(bumps, 'g.')
180 common._pylab.title('Photodiode sensitivity (V/nm)')
181 common._pylab.subplot(312)
182 common._pylab.plot(Ts, 'r.')
183 common._pylab.title('Temperature (K)')
184 common._pylab.subplot(313)
185 common._pylab.plot(vibs, 'b.')
186 common._pylab.title('Thermal deflection variance (Volts**2)')
189 make_splittable_kwargs_function(calib_analyze,
190 (calib_plot, 'bumps', 'Ts', 'vibs'))
192 @splittableKwargsFunction((calib_analyze, 'bumps', 'Ts', 'vibs'))
193 def calib_load_analyze_tweaks(bump_tweaks, vib_tweaks, T_tweaks=None) :
194 raise NotImplementedError
195 a = read_tweaked_bumps(bump_tweaks)
196 vib = V_photo_variance_from_file(vib_tweaks)
199 return analyze_calibration_data(a, T, vib, log_dir=log_dir)
201 # commandline interface functions
204 def array_from_string(string):
206 for num in string.split(',') :
207 ret.append(float(num))
209 return numpy.array(ret)
211 def read_data(ifile):
212 "ifile can be a filename string or open (seekable) file object"
213 unlabeled_data=scipy.io.read_array(file)
214 return unlabeled_data
216 def get_array(string, filename, name) :
217 "get an array from supplied command line options"
219 array = array_from_string(string)
220 elif filename != None :
221 array = read_data(filename)
223 raise Exception, "no %s information given" % (name)
226 if __name__ == '__main__' :
227 # command line interface
228 from optparse import OptionParser
230 usage_string = ('%prog <bumps> <temps> <vibs>\n'
231 '2008, W. Trevor King.\n'
233 'Takes arrays of Vphotodiode sensitivity (V/nm), Temperature (K), \n'
234 'and Vibration variance (V**2) as comma seperated lists.\n'
235 'Returns the cantilever spring constant (pN/nm).\n'
237 ' $ %prog -b 0.02,0.03,0.025 -t 298.2,300.1 -v 6e-9,5.5e-9\n'
239 parser = OptionParser(usage=usage_string, version='%prog '+common.VERSION)
240 parser.add_option('-b', '--bump-string', dest='bump_string',
241 help='comma seperated photodiode sensitivities (V/nm)',
242 type='string', metavar='BUMPS')
243 parser.add_option('-t', '--temp-string', dest='temp_string',
244 help='comma seperated temperatures (K)',
245 type='string', metavar='TEMPS')
246 parser.add_option('-v', '--vib-string', dest='vib_string',
247 help='comma seperated vibration variances (V**2)',
248 type='string', metavar='VIBS')
249 parser.add_option('-B', '--bump-file', dest='bump_file',
250 help='comma seperated photodiode sensitivities (V/nm)',
251 type='string', metavar='BUMPFILE')
252 parser.add_option('-T', '--temp-file', dest='temp_file',
253 help='comma seperated temperatures (K)',
254 type='string', metavar='TEMPFILE')
255 parser.add_option('-V', '--vib-file', dest='vib_file',
256 help='comma seperated vibration variances (V**2)',
257 type='string', metavar='VIBFILE')
258 parser.add_option('-C', '--celsius', dest='celsius',
259 help='Use Celsius input temperatures instead of Kelvin (defaul %default)\n',
260 action='store_true', default=False)
261 parser.add_option('-o', '--output-file', dest='ofilename',
262 help='write output to FILE (default stdout)',
263 type='string', metavar='FILE')
264 parser.add_option('-p', '--plot-inputs', dest='plot',
265 help='plot the input arrays to check their distribution',
266 action='store_true', default=False)
267 parser.add_option('', '--verbose', dest='verbose', action='store_true',
268 help='print lots of debugging information',
271 options,args = parser.parse_args()
274 bumps = get_array(options.bump_string, options.bump_file, "bump")
275 temps = get_array(options.temp_string, options.temp_file, "temp")
276 vibs = get_array(options.vib_string, options.vib_file, "vib")
279 calib_plot(bumps, temps, vibs)
282 for i in range(len(temps)) :
283 temps[i] = T_analyze.C_to_K(temps[i])
285 km,ks,ps2m,ps2s,Tm,Ts,ooVp2m,ooVp2s = calib_analyze(bumps, temps, vibs)
288 print >> sys.stderr, \
289 string_errors(km,ks,ps2m,ps2s,Tm,Ts,ooVp2m,ooVp2s)
291 if options.ofilename != None :
292 print >> file(options.ofilename, 'w'), km