fddcbde22f065203ce0373c8b9e04fa8c9e6422c
[unfold-protein.git] / unfold.py
1 TEXT_VERBOSE = False
2 PYLAB_INTERACTIVE_VERBOSE = True
3 BASE_FIG_NUM = 40
4 LOG_DATA = True
5 LOG_DIR = '$DEFAULT$/unfold'
6
7 import stepper
8 import z_piezo
9 import z_piezo_utils
10 import x_piezo
11 import temperature
12 import threading
13 import time
14 import os, os.path
15 from numpy import arange, sin, pi, isnan
16 import data_logger
17
18 if PYLAB_INTERACTIVE_VERBOSE :
19     from pylab import figure, plot, title, legend, hold, subplot, draw
20     import time # for timestamping lines on plots
21
22 EFILE = file(os.path.expanduser('~/rsrch/debug.unfold'), 'a')
23
24 class ExceptionTooClose (Exception) :
25     """
26     The piezo is too close to the surface,
27     an unfolding would extend past pzMin
28     """
29 class ExceptionTooFar (Exception) :
30     """
31     The piezo has reached pzMax without
32     reaching the desired deflection setpoint.
33     """
34
35 def plot_dict(d, label) :
36     try :
37         plot(d["Z piezo output"], d["Deflection input"], '.',label=label)
38     except KeyError, string:
39         print d.keys()
40         raise KeyError, string
41
42 class unfold_data_log (data_logger.data_log) :
43     def save(self, unfold, timestamp, CTemp, VSetpoint, nmBindPos,
44              sBindTime,
45              nmDist, nmStep, ppStep, nmPsRate, freq,
46              approach_data, unfold_data) :
47         filepath,timestamp = self.get_filename(timestamp)
48         # save the unfolding data
49         dataFile = open(filepath, "w")
50         for i in range(0, len(unfold_data["Z piezo output"])) :
51                 dataFile.write("%d\t%d\t%d\n" % (unfold_data["Z piezo output"][i],
52                                                  unfold_data["Deflection input"][i],
53                                                  unfold_data["Z piezo input"][i]))
54         dataFile.close()
55         dataFile = open(filepath+"_approach", "w")
56         for i in range(0, len(approach_data["Z piezo output"])) :
57                 dataFile.write("%d\t%d\n" % (approach_data["Z piezo output"][i],
58                                                  approach_data["Deflection input"][i]))
59         dataFile.close()
60
61         # save parameters
62         paramFile = open(filepath+"_param", "w")
63         paramFile.write("Environment\n")
64         paramFile.write("Time:\t"+timestamp+"\n")
65         if CTemp != None :
66             paramFile.write("Temperature (C):\t"+str(CTemp)+"\n")
67
68         paramFile.write("\nPiezo parameters\n")
69         paramFile.write("Z piezo sensitivity (nm/Vp):\t"+str(unfold.zp.sensitivity)+"\n")
70         paramFile.write("Z piezo gain (Vp/Vo):\t"+str(unfold.zp.gain)+"\n")
71         paramFile.write("X piezo sensitivity (nm/Vp):\t"+str(unfold.xp.xpSensitivity)+"\n")
72         paramFile.write("X piezo gain (Vp/Vo):\t"+str(unfold.xp.gain)+"\n")
73         paramFile.write("X piezo position (nm):\t"+str(unfold.xp.out2nm(unfold.xp.curPos()))+"\n")
74
75         paramFile.write("\nApproach parameters\n")
76         paramFile.write("Setpoint (V):\t"+str(VSetpoint)+"\n")
77         paramFile.write("Bind pos (nm):\t"+str(nmBindPos)+"\n")
78         
79         paramFile.write("\nBinding parameters\n")
80         paramFile.write("Bind time (s):\t"+str(sBindTime)+"\n")
81
82         paramFile.write("\nUnfolding parameters\n")
83         paramFile.write("Distance (nm):\t"+str(nmDist)+"\n")
84         paramFile.write("Step size (nm):\t"+str(nmStep)+"\n")
85         paramFile.write("Points per step:\t"+str(ppStep)+"\n")
86         paramFile.write("Unfold rate (nm/s):\t"+str(nmPsRate)+"\n")
87         paramFile.write("Sample rate (Hz):\t"+str(freq)+"\n")
88
89         paramFile.write("\nData fields:\tZ_piezo_out Deflection_in Z_piezo_in\n")
90         paramFile.close()
91
92         return timestamp
93
94 class unfold :
95     def __init__(self, controlTemp=True, dataDirectory=LOG_DIR) :
96         self.step = stepper.stepper_obj()
97         self.zp = z_piezo.z_piezo()
98         self.xp = x_piezo.x_piezo()
99         #self.zp = z_piezo.z_piezo(zp_chan = '/Dev1/ao0',
100         #                          zp_mon_chan = '/Dev1/ai1',
101         #                          def_chan = '/Dev1/ai0')
102         #self.xp = x_piezo.x_piezo(xp_chan = '/Dev1/ao1')
103         self.zp.jumpToPos(self.zp.pos_nm2out(0))
104         self.xp.jumpToPos(self.xp.nm2out(0))
105         if controlTemp == True :
106             self.T = temperature.tempController(maxCurrent=1.0)
107         else: self.T = None
108         self.log = unfold_data_log(dataDirectory, log_name="unfold")
109     def unfold(self, setpoint=None, rel_setpoint=1.0, sBindTime = 10.0,
110                nmDist=600, nmStep=0.5, ppStep=10, nmPsRate=1000,
111                dataDirectory=LOG_DIR, fileID=None) :
112         while True :
113             try :
114                 data = self.unfold_cycle(setpoint, rel_setpoint, sBindTime,
115                                          nmDist, nmStep, ppStep, nmPsRate,
116                                          dataDirectory, fileID)
117                 break
118             except ExceptionTooFar :
119                 EFILE.write('caught ExceptionTooFar\n'); EFILE.flush()
120                 try : # try for a useful surface distance...
121                     if setpoint == None : # HACK! redundant!
122                         assert rel_setpoint != None, "must have some sort of setpoint"
123                         setpoint = self.curDef() + rel_setpoint
124                         print "setpoint = %g" % setpoint
125                     EFILE.write('attempt getSurfPos\n'); EFILE.flush()
126                     surfPos = self.getSurfPos(setpoint)
127                     EFILE.write('getSurfPos succeeded\n'); EFILE.flush()
128                     print "Too far (surface at %g nm), stepping closer" % surfPos
129                 except z_piezo_utils.poorFit, string : # ... oh well, print what we know
130                     EFILE.write('getSurfPos failed\n'); EFILE.flush()
131                     print "Too far, stepping closer"
132                     print "(Fit failed with: %s)" % string
133                 EFILE.write('zero Z piezo\n'); EFILE.flush()
134                 self.zp.jumpToPos(self.zp.pos_nm2out(0))
135                 EFILE.write('step closer\n'); EFILE.flush()
136                 self.stepCloser()
137                 EFILE.write('step closer successful\n'); EFILE.flush()
138             except ExceptionTooClose :
139                 try : # try for a useful surface distance...
140                     if setpoint == None : # !HACK !redundant!
141                         assert rel_setpoint != None, "must have some sort of setpoint"
142                         setpoint = self.curDef() + rel_setpoint
143                         print "setpoint = %g" % setpoint
144                     surfPos = self.getSurfPos(setpoint)
145                     print "Too close (surface at %g nm), stepping away" % surfPos
146                 except z_piezo_utils.poorFit, string : # ... oh well, print what we know
147                     print "Too close, stepping away"
148                     print "(Fit failed with: %s)" % string
149                 self.zp.jumpToPos(self.zp.pos_nm2out(0))
150                 self.stepAway()
151             print "Too close, stepping away"
152         return data
153     def stepApproach(self, setpoint) :
154         cd = self.curDef()
155         while cd < setpoint or isnan(cd) : # sometimes during approach, we get negative nan values
156             print "deflection %g < setpoint %g.  step closer" % (cd, setpoint)
157             self.stepCloser()
158             cd = self.curDef()
159     def stepApproachRetractCurve(self, setpoint) :
160         def_array = []
161         step_array = []
162         orig_step = self.step.get_cur_pos()
163         def store_point() :
164             cd = self.curDef()
165             def_array.append(cd)
166             step_array.append(self.step.get_cur_pos())
167             return cd        
168         cd = store_point()
169         while cd < setpoint or isnan(cd) : # sometimes during approach, we get negative nan values
170             print "deflection %g < setpoint %g.  step closer" % (cd, setpoint)
171             self.step.step_rel(2)
172             cd = store_point()
173         while self.step.get_cur_pos() > orig_step :
174             self.step.step_rel(-2)
175             store_point()
176         return {"Deflection":def_array, "Stepper position":step_array}
177     def piezoApproach(self, setpoint, return_data=False) :
178         startPos = self.zp.curPos()
179         curVals,data = z_piezo_utils.moveToPosOrDef(self.zp,
180                            self.zp.zpMax,
181                            self.zp.def_V2in(setpoint),
182                            step=10,
183                            return_data=return_data)
184         if curVals["Deflection input"] < self.zp.def_V2in(setpoint) :
185             EFILE.write('Unfolding too far\n'); EFILE.flush()
186             self.zp.jumpToPos(startPos)
187             self.zp.updateInputs()
188             if PYLAB_INTERACTIVE_VERBOSE == True :
189                 figure(BASE_FIG_NUM+1)
190                 hold(False)
191                 plot_dict(data, 'Approach')
192                 hold(True)
193                 title('Unfolding too far')
194             EFILE.write('Raising ExceptionTooFar\n'); EFILE.flush()
195             raise ExceptionTooFar
196         if return_data == True :
197             return (curVals, data)
198         else :
199             return curVals
200     def bind(self, sTime=10.0) :
201         time.sleep(sTime)
202     def unfold_pull(self, nmDist=600, nmStep=0.5, ppStep=10, nmPsRate=1000) :
203         if nmStep < 0 :
204                 nmStep = -nmStep
205         numSteps = int(nmDist/nmStep+1)
206         out = [0.0]*numSteps*ppStep
207         pos = self.zp.curPos()
208         startPos = pos
209         step = int(self.zp.pos_nm2out(nmStep)-self.zp.pos_nm2out(0))
210         for i in range(0, numSteps) :
211                 for j in range(0, ppStep) :
212                         out[i*ppStep + j] = pos
213                 pos -= step
214         # points/step * step/nm * nm/s  =  points/s
215         freq = ppStep / nmStep * nmPsRate
216         print "unfolding %d points per %d steps from %d to %d at freq of %g" % (ppStep, numSteps, startPos, pos, freq) 
217         return {'freq':freq, 'data':self.zp.ramp(out, freq)}
218     def generate_refold_bind_pull_output(self, nmSurfPos=0,
219             sBindTime=2, nmUnfoldDist=160, nmRefoldDist=145, sPauseTime=2,
220             cycles=50, nmStep=0.5, ppStepUnfold=10, ppStepRefold=1,
221             nmPsRateUnfold=1000) :
222         
223         if nmStep < 0 :##
224             nmStep = -nmStep
225         numSteps = int(nmDist/nmStep+1)
226         out = [0.0]*numSteps*ppStep
227         pos = self.zp.curPos()
228         startPos = pos
229         step = int(self.zp.pos_nm2out(nmStep)-self.zp.pos_nm2out(0))
230         for i in range(0, numSteps) :
231                 for j in range(0, ppStep) :
232                         out[i*ppStep + j] = pos
233                 pos -= step
234         # points/step * step/nm * nm/s  =  points/s
235         freq = ppStep / nmStep * nmPsRate
236         print "unfolding %d points per %d steps from %d to %d at freq of %g" % (ppStep, numSteps, startPos, pos, freq) 
237         return {'freq':freq, 'data':self.zp.ramp(out, freq)}
238     def unfold_cycle(self, setpoint=None, rel_setpoint=1.0, sBindTime = 10.0,
239                      nmDist=600, nmStep=0.5, ppStep=10, nmPsRate=1000,
240                      dataDirectory=LOG_DIR, fileID=None) :
241         if setpoint == None :
242             assert rel_setpoint != None, "must have some sort of setpoint"
243             setpoint = self.curDef() + rel_setpoint
244             print "setpoint = %g" % setpoint
245         timestamp = time.strftime("%Y%m%d%H%M%S")
246         temp = None
247         if self.T != None : temp = self.T.getTemp()
248         print "approaching"
249         startPos = self.zp.curPos()
250         curVals,approach_data = self.piezoApproach(setpoint, return_data=True)
251         bindPos = curVals['Z piezo output'] # in output units
252         finalPos = bindPos - (self.zp.pos_nm2out(nmDist)-self.zp.pos_nm2out(0))
253         try :
254             self.zp._check_range(finalPos)
255         except z_piezo.outOfRange :
256             self.zp.jumpToPos(startPos)
257             self.zp.updateInputs()
258             if PYLAB_INTERACTIVE_VERBOSE == True :
259                 figure(BASE_FIG_NUM+1)
260                 hold(False)
261                 plot_dict(approach_data, 'Approach')
262                 hold(True)
263                 title('Unfolding too close')
264             raise ExceptionTooClose
265         print "binding for %.3f seconds" % sBindTime
266         self.bind(sBindTime)
267         out = self.unfold_pull(nmDist, nmStep, ppStep, nmPsRate)
268
269         if PYLAB_INTERACTIVE_VERBOSE == True :
270             figure(BASE_FIG_NUM)
271             hold(False)
272             plot_dict(approach_data, 'Approach')
273             hold(True)
274             plot_dict(out['data'], 'Unfold')
275             legend(loc='best')
276             title('Unfolding')
277             draw()
278
279         if LOG_DATA == True:
280             print "saving"
281             timestamp = self.log.save(self, timestamp, temp, setpoint,
282                                       int(self.zp.pos_out2nm(bindPos)), # don't need lots of precision...
283                                       sBindTime,
284                                       nmDist, nmStep, ppStep, nmPsRate, out['freq'],
285                                       approach_data, out['data'])
286         return out
287     def getSurfPos(self, setpoint=2, textVerbose=False, plotVerbose=False) :
288         return self.zp.pos_out2nm(z_piezo_utils.getSurfPos(self.zp, self.zp.def_V2in(setpoint), textVerbose, plotVerbose))
289     def curDef(self) :
290         self.zp.updateInputs()
291         return self.zp.def_in2V(self.zp.curDef())
292     def stepCloser(self) :
293         "Backlash-robust stepping"
294         self.step.step_rel(2) # two half-steps in full step mode
295     def stepAway(self) :
296         "Backlash-robust stepping"
297         self.step.step_rel(-120)
298         self.step.step_rel(110) # HACK, should come back 118
299     def xpWander(self) :
300         self.xp.wander()
301
302 def _test_unfold(controlTemp=True) :
303     print "Test unfold"
304     u = unfold(controlTemp=controlTemp)
305     u.unfold(setpoint=0.5, sBindTime=1.0)
306     del u
307     print "unfold successful\n"
308
309
310 class unfold_expt :
311     def __init__(self, controlTemp=True) :
312         self.u = unfold(controlTemp=controlTemp)
313         self.runUnfolds = False
314         self.bgRun = None
315         self.getExternalTempSetpoint = None
316         if self.u.T != None :
317             self.tempSetpoint = self.u.T.getTemp()
318         self.getExternalDeflectionSetpoint = None
319         self.deflectionSetpoint = 0.5
320         self.getExternalBindTime = None
321         self.bindTime = 1.0
322         self.getExternalnmDist = None
323         self.nmDist = 600.0
324         self.getExternalnmStep = None
325         self.nmStep = 0.5
326         self.getExternalppStep = None
327         self.ppStep = 10
328         self.getExternalnmPsRate = None
329         self.nmPsRate = 1000.0
330         self.getExternalDataDirectory = None
331         self.dataDirectory = LOG_DIR
332         self.plotData = None
333         self.i=0
334         self.showUnfoldingIndex = None
335     def run(self) :
336         print "running"
337         if self.bgRun != None :
338             raise Exception, "Can't run two backgrounds simultaneously"
339         self.runUnfolds = True
340         print "unfolding in the background"
341         self.bgRun = bg_run( self._run)
342     def stepApproach(self) :
343         print "approaching"
344         if self.getExternalDeflectionSetpoint != None :
345             self.deflectionSetpoint = self.getExternalDeflectionSetpoint()
346         self.u.stepApproach(self.deflectionSetpoint)
347     def _run(self) :
348         print "starting unfold loop"
349         while self.runUnfolds == True :
350             print "get unfold parameters"
351             if self.u.T != None and self.getExternalTempSetpoint != None :
352                 setpoint = self.getExternalTempSetpoint()
353                 if setpoint != self.tempSetpoint :
354                     self.u.T.setTemp(setpoint)
355                     self.tempSetpoint = setpoint
356             if self.getExternalDeflectionSetpoint != None :
357                 self.deflectionSetpoint = self.getExternalDeflectionSetpoint()
358             if self.getExternalBindTime != None :
359                 self.bindTime = self.getExternalBindTime()
360             if self.getExternalnmDist != None :
361                 self.nmDist = self.getExternalnmDist()
362             if self.getExternalnmStep != None :
363                 self.nmStep = self.getExternalnmStep()
364             if self.getExternalppStep != None :
365                 self.ppStep = self.getExternalppStep()
366             if self.getExternalnmPsRate != None :
367                 self.nmPsRate = self.getExternalnmPsRate()
368             if self.getExternalDataDirectory != None :
369                 self.dataDirectory = self.getExternalDataDirectory()
370             print "run unfold"
371             #print "setpoint  ", self.deflectionSetpoint
372             #print "bind time ", self.bindTime
373             #print "nmDist    ", self.nmDist
374             #print "nmStep    ", self.nmStep
375             #print "ppStep    ", self.ppStep
376             #print "nmPsRate  ", self.nmPsRate
377             #print "fileID    ", self.i
378             #print "dataDir   ", self.dataDirectory
379             data = self.u.unfold(setpoint=self.deflectionSetpoint,
380                                  sBindTime=self.bindTime,
381                                  nmDist=self.nmDist,
382                                  nmStep=self.nmStep,
383                                  ppStep=self.ppStep,
384                                  nmPsRate=self.nmPsRate,
385                                  fileID=str(self.i),
386                                  dataDirectory=self.dataDirectory)
387             print "plot data"
388             if self.plotData != None :
389                 self.plotData(data["data"]["Z piezo output"],
390                               data["data"]["Deflection input"])
391             if self.showUnfoldingIndex != None :
392                 self.showUnfoldingIndex(self.i)
393             self.i += 1
394         return False
395     def stop(self) :
396         if self.bgRun != None :
397             self.runUnfolds = False
398             self.bgRun.wait()
399             self.bgRun = None
400
401 def _test_unfold_expt(controlTemp=True) :
402     print "Test unfold_expt"
403     u_expt = unfold.unfold_expt(controlTemp=controlTemp)
404     u_expt.run()
405     time.sleep(100)
406     u_expt.stop()
407     print "unfold_expt working\n"
408
409 class bg_run :
410     def __init__(self, function, args=None, finishCallback=None, interruptCallback=None) :
411         print "init bg"
412         def tempFn() :
413             print "temp fn started"
414             complete = False
415             if args == None :
416                 complete = function()
417             else :
418                 complete = function(args)
419             print "temp Fn finished"
420             if finishCallback != None and complete == True :
421                 finishCallback()
422             elif interruptCallback != None and complete == False :
423                 interruptCallback()
424         print "tempFn defined"
425         self.thd = threading.Thread(target=tempFn, name="Background Fn")
426         print "starting thread"
427         self.thd.start()
428         print "thread started"
429     def wait(self) :
430         print "joining thread"
431         self.thd.join()
432         print "thread joined"
433
434 def _test_bg_run() :
435     print "Test bg_run"
436     print "test without args or callbacks"
437     def say_hello() :
438         for i in range(10) :
439             print "Hello"
440             time.sleep(0.1)
441         return True
442     bg = unfold.bg_run(say_hello)
443     time.sleep(.2)
444     print "The main thread's still going"
445     bg.wait()
446     print "The background process is done"
447     
448     print "test without args, but with callbacks"
449     def inter() :
450         print "I was interrupted"
451     def fin() :
452         print "I'm finished"
453     bg = unfold.bg_run(say_hello, finishCallback=fin, interruptCallback=inter)
454     time.sleep(.2)
455     print "The main thread's still going"
456     bg.wait()
457     print "The background process is done"
458
459     print "test with args and callbacks"
460     del say_hello
461     def say_hello(stop=[False]) :
462         for i in range(10) :
463             if stop[0] == True : return False
464             print "Hello"
465             time.sleep(0.1)
466         return True
467     stop = [False]
468     bg = unfold.bg_run(say_hello, args=stop, finishCallback=fin, interruptCallback=inter)
469     time.sleep(.2)
470     print "The main thread's still going, stop it"
471     stop[0] = True
472     bg.wait()
473     del bg
474     del stop
475     del say_hello
476     del inter
477     del fin
478     print "The background process is done"
479     
480     print "bg_run working\n"
481
482 def loop_rates(u, rates, num_loops=10, die_file=None, song=None, **kwargs):
483     """
484     loop_rates(u, rates, num_loops=10, die_file=None, song=None, **kwargs)
485     
486     Constant speed unfolding using the unfold instance u for num_loops
487     through the series of rates listed in rates.  You may set die_file
488     to a path, loop_rates() will check that location before each
489     unfolding attempt, and cleanly stop unfolding if the file exists.
490     **kwargs are passed on to u.unfold(), so a full call might look like
491     
492     loop_rates(u, rates=[20,200,2e3], num_loops=5, die_file='~/die', song='~wking/Music/system/towerclo.wav', rel_setpoint=1, nmDist=800, sBindTime=2)
493     """
494     if die_file != None:
495         die_file = os.path.expanduser(die_file)
496     if song != None:
497         song = os.path.expanduser(song)
498     for i in range(num_loops):
499         for nmPsRate in rates:
500             if die_file != None and os.path.exists(die_file):
501                 return None
502             u.unfold(nmPsRate=nmPsRate, **kwargs)
503             u.xpWander()
504     if song != None:
505         os.system("aplay '%s'" % song)
506
507 def test() :
508     _test_unfold(controlTemp=False)
509     _test_bg_run()
510     _test_unfold_expt(controlTemp=False)
511
512 if __name__ == "__main__" :
513     test()