1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008-2010 Alberto Gomez-Casado
5 # Massimo Sandal <devicerandom@gmail.com>
6 # W. Trevor King <wking@drexel.edu>
8 # This file is part of Hooke.
10 # Hooke is free software: you can redistribute it and/or modify it
11 # under the terms of the GNU Lesser General Public License as
12 # published by the Free Software Foundation, either version 3 of the
13 # License, or (at your option) any later version.
15 # Hooke is distributed in the hope that it will be useful, but WITHOUT
16 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
18 # Public License for more details.
20 # You should have received a copy of the GNU Lesser General Public
21 # License along with Hooke. If not, see
22 # <http://www.gnu.org/licenses/>.
24 """The ``polymer_fit`` module proviews :class:`PolymerFitPlugin` and
25 serveral associated :class:`hooke.command.Command`\s for Fitting
26 velocity-clamp data to various polymer models (WLC, FJC, etc.).
30 from Queue import Queue
35 from scipy.optimize import newton
37 from ..command import Command, Argument, Success, Failure
38 from ..config import Setting
39 from ..curve import Data
40 from ..plugin import Plugin, argument_to_setting
41 from ..util.callback import is_iterable
42 from ..util.fit import PoorFit, ModelFitter
43 from ..util.peak import Peak
44 from ..util.si import join_data_label, split_data_label
45 from .curve import CurveArgument
46 from .vclamp import scale
49 kB = 1.3806503e-23 # Joules/Kelvin
53 """Hyperbolic cotangent.
57 >>> coth(1.19967874) # doctest: +ELLIPSIS
65 \coth(z) \equiv \frac{\exp(z) + \exp(-z)}{\exp(z) - \exp(-z)}
69 http://mathworld.wolfram.com/HyperbolicCotangent.html
71 return 1.0/numpy.tanh(z)
74 """Inverse hyperbolic cotangent.
78 >>> arccoth(1.19967874) # doctest: +ELLIPSIS
80 >>> arccoth(coth(numpy.pi)) # doctest: +ELLIPSIS
85 Inverting from the :func:`definition <coth>`.
88 z \equiv \atanh\left( \frac{1}{\coth(z)} \right)
90 return numpy.arctanh(1.0/z)
97 >>> Langevin(numpy.pi) # doctest: +ELLIPSIS
102 From `Wikipedia`_ or Hatfield [#]_
105 L(z) \equiv \coth(z) - \frac{1}{z}
108 http://en.wikipedia.org/wiki/Brillouin_and_Langevin_functions#Langevin_Function
110 .. [#]: J.W. Hatfield and S.R. Quake
111 "Dynamic Properties of an Extended Polymer in Solution."
112 Phys. Rev. Lett. 1999.
113 doi: `10.1103/PhysRevLett.82.3548 <http://dx.doi.org/10.1103/PhysRevLett.82.3548>`
115 return coth(z) - 1.0/z
117 def inverse_Langevin(z, extreme=1.0 - 1e-8):
118 """Inverse Langevin function.
122 z : float or array_like
123 object whose inverse Langevin will be returned
125 value (close to one) for which we assume the inverse is +/-
126 infinity. This avoids problems with Newton-Raphson
127 convergence in regions with low slope.
131 >>> inverse_Langevin(Langevin(numpy.pi)) # doctest: +ELLIPSIS
133 >>> inverse_Langevin(Langevin(numpy.array([1,2,3]))) # doctest: +ELLIPSIS
138 We approximate the inverse Langevin via Newton's method
139 (:func:`scipy.optimize.newton`). For the starting point, we take
140 the first three terms from the Taylor series (from `Wikipedia`_).
143 L^{-1}(z) = 3z + \frac{9}{5}z^3 + \frac{297}{175}z^5 + \dots
146 http://en.wikipedia.org/wiki/Brillouin_and_Langevin_functions#Langevin_Function
149 ret = numpy.ndarray(shape=z.shape, dtype=z.dtype)
150 for i,z_i in enumerate(z):
151 ret[i] = inverse_Langevin(z_i)
157 # Catch stdout since sometimes the newton function prints exit
158 # messages to stdout, e.g. "Tolerance of %s reached".
160 tmp_stdout = StringIO.StringIO()
161 sys.stdout = tmp_stdout
162 ret = newton(func=lambda x: Langevin(x)-z,
163 x0=3*z + 9./5.*z**3 + 297./175.*z**5,)
165 output = tmp_stdout.getvalue()
168 def FJC_fn(x_data, T, L, a):
169 """The freely jointed chain model.
174 x values for which the WLC tension should be calculated.
176 temperature in Kelvin.
178 contour length in meters.
180 Kuhn length in meters.
189 >>> FJC_fn(numpy.array([1e-9, 5e-9, 10e-9], dtype=numpy.float),
190 ... T=300, L=15e-9, a=2.5e-10) # doctest: +ELLIPSIS
191 array([ 3.322...-12, 1.78...e-11, 4.889...e-11])
195 The freely jointed chain model is
198 F(x) = \frac{k_B T}{a} L^{-1}\left( \frac{x}{L} \right)
200 where :math:`L^{-1}` is the :func:`inverse_Langevin`, :math:`a` is
201 the Kuhn length, and :math:`L` is the contour length [#]_. For
202 the inverse formulation (:math:`x(F)`), see Ray and Akhremitchev [#]_.
205 .. [#]: J.W. Hatfield and S.R. Quake
206 "Dynamic Properties of an Extended Polymer in Solution."
207 Phys. Rev. Lett. 1999.
208 doi: `10.1103/PhysRevLett.82.3548 <http://dx.doi.org/10.1103/PhysRevLett.82.3548>`
210 .. [#] C. Ray and B.B. Akhremitchev.
211 `"Fitting Force Curves by the Extended Freely Jointed Chain Model" <http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf>`
213 return kB*T / a * inverse_Langevin(x_data/L)
215 class FJC (ModelFitter):
216 """Fit the data to a freely jointed chain.
220 Generate some example data
223 >>> L = 35e-9 # meters
224 >>> a = 2.5e-10 # meters
225 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
226 >>> d_data = FJC_fn(x_data, T=T, L=L, a=a)
228 Fit the example data with a two-parameter fit (`L` and `a`).
231 ... 'temperature (K)': T,
232 ... 'x data (m)': x_data,
234 >>> model = FJC(d_data, info=info, rescale=True)
235 >>> outqueue = Queue()
236 >>> Lp,a = model.fit(outqueue=outqueue)
237 >>> fit_info = outqueue.get(block=False)
238 >>> model.L(Lp) # doctest: +ELLIPSIS
240 >>> a # doctest: +ELLIPSIS
243 Fit the example data with a one-parameter fit (`L`). We introduce
244 some error in our fixed Kuhn length for fun.
246 >>> info['Kuhn length (m)'] = 2*a
247 >>> model = FJC(d_data, info=info, rescale=True)
248 >>> Lp = model.fit(outqueue=outqueue)
249 >>> fit_info = outqueue.get(block=False)
250 >>> model.L(Lp) # doctest: +ELLIPSIS
254 """To avoid invalid values of `L`, we reparametrize with `Lp`.
259 contour length in meters
264 rescaled version of `L`.
268 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
269 >>> model = FJC(data=x_data, info={'x data (m)':x_data})
270 >>> model.Lp(20e-9) # doctest: +ELLIPSIS
272 >>> model.Lp(19e-9) # doctest: +ELLIPSIS
274 >>> model.Lp(21e-9) # doctest: +ELLIPSIS
276 >>> model.Lp(100e-9) # doctest: +ELLIPSIS
281 The rescaling is designed to limit `L` to values strictly
282 greater than the maximum `x` data value, so we use
285 L = [\exp(L_p))+1] * x_\text{max}
287 which has the inverse
290 L_p = \ln(L/x_\text{max}-1)
292 This will obviously effect the interpretation of the fit's covariance matrix.
294 x_max = self.info['x data (m)'].max()
295 return numpy.log(L/x_max - 1)
298 """Inverse of :meth:`Lp`.
303 rescaled version of `L`
308 contour length in meters
312 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
313 >>> model = FJC(data=x_data, info={'x data (m)':x_data})
314 >>> model.L(model.Lp(21e-9)) # doctest: +ELLIPSIS
316 >>> model.L(model.Lp(100e-9)) # doctest: +ELLIPSIS
319 x_max = self.info['x data (m)'].max()
320 return (numpy.exp(Lp)+1)*x_max
322 def model(self, params):
323 """Extract the relavant arguments and use :func:`FJC_fn`.
327 params : list of floats
328 `[Lp, a]`, where the presence of `a` is optional.
330 # setup convenient aliases
331 T = self.info['temperature (K)']
332 x_data = self.info['x data (m)']
333 L = self.L(params[0])
337 a = self.info['Kuhn length (m)']
339 self._model_data[:] = FJC_fn(x_data, T, L, a)
340 return self._model_data
342 def guess_initial_params(self, outqueue=None):
343 """Guess initial fitting parameters.
348 A guess at the reparameterized contour length in meters.
350 A guess at the Kuhn length in meters. If `.info` has a
351 setting for `'Kuhn length (m)'`, `a` is not returned.
353 x_max = self.info['x data (m)'].max()
354 a_given = 'Kuhn length (m)' in self.info
356 a = self.info['Kuhn length (m)']
365 def guess_scale(self, params, outqueue=None):
366 """Guess parameter scales.
371 A guess at the reparameterized contour length scale in meters.
372 a_scale : float (optional)
373 A guess at the Kuhn length in meters. If the length of
374 `params` is less than 2, `a_scale` is not returned.
379 return [Lp_scale] + [x/10.0 for x in params[1:]]
382 def inverse_FJC_PEG_fn(F_data, T=300, N=1, k=150., Lp=3.58e-10, Lh=2.8e-10, dG=3., a=7e-10):
383 """Inverse poly(ethylene-glycol) adjusted extended FJC model.
387 >>> kwargs = {'T':300.0, 'N':1, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
388 ... 'dG':3.0, 'a':7e-10}
389 >>> inverse_FJC_PEG_fn(F_data=200e-12, **kwargs) # doctest: +ELLIPSIS
394 The function is that proposed by F. Oesterhelt, et al. [#]_.
397 x(F) = N_\text{S} \cdot \left[
399 \frac{L_\text{planar}}{
400 \exp\left(-\Delta G/k_B T\right) + 1}
401 + \frac{L_\text{helical}}{
402 \exp\left(+\Delta G/k_B T\right) + 1}
404 \coth\left(\frac{Fa}{k_B T}\right)
407 + \frac{F}{K_\text{S}}
409 where :math:`N_\text{S}` is the number of segments,
410 :math:`K_\text{S}` is the segment elasticicty,
411 :math:`L_\text{planar}` is the ttt contour length,
412 :math:`L_\text{helical}` is the ttg contour length,
413 :math:`\Delta G` is the Gibbs free energy difference
414 :math:`G_\text{planar}-G_\text{helical}`,
415 :math:`a` is the Kuhn length,
416 and :math:`F` is the chain tension.
418 .. [#]: F. Oesterhelt, M. Rief, and H.E. Gaub.
419 "Single molecule force spectroscopy by AFM indicates helical
420 structure of poly(ethylene-glycol) in water."
421 New Journal of Physics. 1999.
422 doi: `10.1088/1367-2630/1/1/006 <http://dx.doi.org/10.1088/1367-2630/1/1/006>`
426 g = (dG - F_data*(Lp-Lh)) / kBT
428 return N * ((Lp/(numpy.exp(-g)+1) + Lh/(numpy.exp(+g)+1)) * (coth(z)-1./z)
431 def FJC_PEG_fn(x_data, T, N, k, Lp, Lh, dG, a):
432 """Poly(ethylene-glycol) adjusted extended FJC model.
436 z : float or array_like
437 object whose inverse Langevin will be returned
439 value (close to one) for which we assume the inverse is +/-
440 infinity. This avoids problems with Newton-Raphson
441 convergence in regions with low slope.
445 >>> kwargs = {'T':300.0, 'N':1, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
446 ... 'dG':3.0, 'a':7e-10}
447 >>> FJC_PEG_fn(inverse_FJC_PEG_fn(1e-11, **kwargs), **kwargs) # doctest: +ELLIPSIS
449 >>> FJC_PEG_fn(inverse_FJC_PEG_fn(3.4e-10, **kwargs), **kwargs) # doctest: +ELLIPSIS
451 >>> FJC_PEG_fn(numpy.array([1e-10,2e-10,3e-10]), **kwargs) # doctest: +ELLIPSIS
452 array([ 5.207...e-12, 1.257...e-11, 3.636...e-11])
453 >>> kwargs['N'] = 123
454 >>> FJC_PEG_fn(numpy.array([1e-8,2e-8,3e-8]), **kwargs) # doctest: +ELLIPSIS
455 array([ 4.160...e-12, 9.302...e-12, 1.830...e-11])
459 We approximate the PEG adjusted eFJC via Newton's method
460 (:func:`scipy.optimize.newton`). For the starting point, we use
461 the standard FJC function with an averaged contour length.
463 kwargs = {'T':T, 'N':N, 'k':k, 'Lp':Lp, 'Lh':Lh, 'dG':dG, 'a':a}
464 if is_iterable(x_data):
465 ret = numpy.ndarray(shape=x_data.shape, dtype=x_data.dtype)
466 for i,x in enumerate(x_data):
467 ret[i] = FJC_PEG_fn(x, **kwargs)
471 # Approximate with the simple FJC_fn()
474 while guess == numpy.inf:
475 guess = FJC_fn(x_data, T=T, L=L, a=a)
478 fn = lambda f: inverse_FJC_PEG_fn(guess*abs(f), **kwargs) - x_data
479 ret = guess*abs(newton(func=fn, x0=1.0))
482 class FJC_PEG (ModelFitter):
483 """Fit the data to an poly(ethylene-glycol) adjusted extended FJC.
487 Generate some example data
489 >>> kwargs = {'T':300.0, 'N':123, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
490 ... 'dG':3.0, 'a':7e-10}
491 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
492 >>> d_data = FJC_PEG_fn(x_data, **kwargs)
494 Fit the example data with a two-parameter fit (`N` and `a`).
497 ... 'temperature (K)': kwargs['T'],
498 ... 'x data (m)': x_data,
499 ... 'section elasticity (N/m)': kwargs['k'],
500 ... 'planar section length (m)': kwargs['Lp'],
501 ... 'helical section length (m)': kwargs['Lh'],
502 ... 'Gibbs free energy difference (Gp - Gh) (kBT)': kwargs['dG'],
504 >>> model = FJC_PEG(d_data, info=info, rescale=True)
505 >>> outqueue = Queue()
506 >>> Nr,a = model.fit(outqueue=outqueue)
507 >>> fit_info = outqueue.get(block=False)
508 >>> model.L(Nr) # doctest: +ELLIPSIS
510 >>> a # doctest: +ELLIPSIS
513 Fit the example data with a one-parameter fit (`N`). We introduce
514 some error in our fixed Kuhn length for fun.
516 >>> info['Kuhn length (m)'] = 2*kwargs['a']
517 >>> model = FJC_PEG(d_data, info=info, rescale=True)
518 >>> Nr = model.fit(outqueue=outqueue)
519 >>> fit_info = outqueue.get(block=False)
520 >>> model.L(Nr) # doctest: +ELLIPSIS
524 """To avoid invalid values of `L`, we reparametrize with `Lr`.
529 contour length in meters
534 rescaled version of `L`.
538 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
539 >>> model = FJC_PEG(data=x_data, info={'x data (m)':x_data})
540 >>> model.Lr(20e-9) # doctest: +ELLIPSIS
542 >>> model.Lr(19e-9) # doctest: +ELLIPSIS
544 >>> model.Lr(21e-9) # doctest: +ELLIPSIS
546 >>> model.Lr(100e-9) # doctest: +ELLIPSIS
551 The rescaling is designed to limit `L` to values strictly
552 greater than zero, so we use
555 L = \exp(L_p) * x_\text{max}
557 which has the inverse
560 L_p = \ln(L/x_\text{max})
562 This will obviously effect the interpretation of the fit's covariance matrix.
564 x_max = self.info['x data (m)'].max()
565 return numpy.log(L/x_max)
568 """Inverse of :meth:`Lr`.
573 rescaled version of `L`
578 contour length in meters
582 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
583 >>> model = FJC_PEG(data=x_data, info={'x data (m)':x_data})
584 >>> model.L(model.Lr(21e-9)) # doctest: +ELLIPSIS
586 >>> model.L(model.Lr(100e-9)) # doctest: +ELLIPSIS
589 x_max = self.info['x data (m)'].max()
590 return numpy.exp(Lr)*x_max
594 'T': self.info['temperature (K)'],
595 'k': self.info['section elasticity (N/m)'],
596 'Lp': self.info['planar section length (m)'],
597 'Lh': self.info['helical section length (m)'],
598 'dG': self.info['Gibbs free energy difference (Gp - Gh) (kBT)'],
601 def model(self, params):
602 """Extract the relavant arguments and use :func:`FJC_PEG_fn`.
606 params : list of floats
607 `[N, a]`, where the presence of `a` is optional.
609 # setup convenient aliases
610 T = self.info['temperature (K)']
611 x_data = self.info['x data (m)']
612 N = self.L(params[0])
616 a = self.info['Kuhn length (m)']
617 kwargs = self._kwargs()
619 self._model_data[:] = FJC_PEG_fn(x_data, N=N, a=a, **kwargs)
620 return self._model_data
622 def guess_initial_params(self, outqueue=None):
623 """Guess initial fitting parameters.
628 A guess at the section count.
630 A guess at the Kuhn length in meters. If `.info` has a
631 setting for `'Kuhn length (m)'`, `a` is not returned.
633 x_max = self.info['x data (m)'].max()
634 a_given = 'Kuhn length (m)' in self.info
636 a = self.info['Kuhn length (m)']
639 f_max = self._data.max()
640 kwargs = self._kwargs()
642 x_section = inverse_FJC_PEG_fn(F_data=f_max, **kwargs)
643 N = x_max / x_section;
649 def guess_scale(self, params, outqueue=None):
650 """Guess parameter scales.
655 A guess at the section count scale in meters.
656 a_scale : float (optional)
657 A guess at the Kuhn length in meters. If the length of
658 `params` is less than 2, `a_scale` is not returned.
660 return [x/10.0 for x in params]
663 def WLC_fn(x_data, T, L, p):
664 """The worm like chain interpolation model.
669 x values for which the WLC tension should be calculated.
671 temperature in Kelvin.
673 contour length in meters.
675 persistence length in meters.
684 >>> WLC_fn(numpy.array([1e-9, 5e-9, 10e-9], dtype=numpy.float),
685 ... T=300, L=15e-9, p=2.5e-10) # doctest: +ELLIPSIS
686 array([ 1.717...e-12, 1.070...e-11, 4.418...e-11])
690 The function is the simple polynomial worm like chain
691 interpolation forumula proposed by C. Bustamante, et al. [#]_.
694 F(x) = \frac{k_B T}{p} \left[
695 \frac{1}{4}\left( \frac{1}{(1-x/L)^2} - 1 \right)
699 .. [#] C. Bustamante, J.F. Marko, E.D. Siggia, and S.Smith.
700 "Entropic elasticity of lambda-phage DNA."
702 doi: `10.1126/science.8079175 <http://dx.doi.org/10.1126/science.8079175>`
705 scaled_data = x_data / L
706 return a * (0.25*((1-scaled_data)**-2 - 1) + scaled_data)
708 class WLC (ModelFitter):
709 """Fit the data to a worm like chain.
713 Generate some example data
716 >>> L = 35e-9 # meters
717 >>> p = 2.5e-10 # meters
718 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
719 >>> d_data = WLC_fn(x_data, T=T, L=L, p=p)
721 Fit the example data with a two-parameter fit (`L` and `p`).
724 ... 'temperature (K)': T,
725 ... 'x data (m)': x_data,
727 >>> model = WLC(d_data, info=info, rescale=True)
728 >>> outqueue = Queue()
729 >>> Lp,p = model.fit(outqueue=outqueue)
730 >>> fit_info = outqueue.get(block=False)
731 >>> model.L(Lp) # doctest: +ELLIPSIS
733 >>> p # doctest: +ELLIPSIS
736 Fit the example data with a one-parameter fit (`L`). We introduce
737 some error in our fixed persistence length for fun.
739 >>> info['persistence length (m)'] = 2*p
740 >>> model = WLC(d_data, info=info, rescale=True)
741 >>> Lp = model.fit(outqueue=outqueue)
742 >>> fit_info = outqueue.get(block=False)
743 >>> model.L(Lp) # doctest: +ELLIPSIS
747 """To avoid invalid values of `L`, we reparametrize with `Lp`.
752 contour length in meters
757 rescaled version of `L`.
761 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
762 >>> model = WLC(data=x_data, info={'x data (m)':x_data})
763 >>> model.Lp(20e-9) # doctest: +ELLIPSIS
765 >>> model.Lp(19e-9) # doctest: +ELLIPSIS
767 >>> model.Lp(21e-9) # doctest: +ELLIPSIS
769 >>> model.Lp(100e-9) # doctest: +ELLIPSIS
774 The rescaling is designed to limit `L` to values strictly
775 greater than the maximum `x` data value, so we use
778 L = [\exp(L_p))+1] * x_\text{max}
780 which has the inverse
783 L_p = \ln(L/x_\text{max}-1)
785 This will obviously effect the interpretation of the fit's covariance matrix.
787 x_max = self.info['x data (m)'].max()
788 return numpy.log(L/x_max - 1)
791 """Inverse of :meth:`Lp`.
796 rescaled version of `L`
801 contour length in meters
805 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
806 >>> model = WLC(data=x_data, info={'x data (m)':x_data})
807 >>> model.L(model.Lp(21e-9)) # doctest: +ELLIPSIS
809 >>> model.L(model.Lp(100e-9)) # doctest: +ELLIPSIS
812 x_max = self.info['x data (m)'].max()
813 return (numpy.exp(Lp)+1)*x_max
815 def model(self, params):
816 """Extract the relavant arguments and use :func:`WLC_fn`.
820 params : list of floats
821 `[Lp, p]`, where the presence of `p` is optional.
823 # setup convenient aliases
824 T = self.info['temperature (K)']
825 x_data = self.info['x data (m)']
826 L = self.L(params[0])
830 p = self.info['persistence length (m)']
832 self._model_data[:] = WLC_fn(x_data, T, L, p)
833 return self._model_data
835 def guess_initial_params(self, outqueue=None):
836 """Guess initial fitting parameters.
841 A guess at the reparameterized contour length in meters.
843 A guess at the persistence length in meters. If `.info`
844 has a setting for `'persistence length (m)'`, `p` is not
847 x_max = self.info['x data (m)'].max()
848 p_given = 'persistence length (m)' in self.info
850 p = self.info['persistence length (m)']
859 def guess_scale(self, params, outqueue=None):
860 """Guess parameter scales.
865 A guess at the reparameterized contour length scale in meters.
866 p_scale : float (optional)
867 A guess at the persistence length in meters. If the
868 length of `params` is less than 2, `p_scale` is not
874 return [Lp_scale] + [x/10.0 for x in params[1:]]
877 class PolymerFitPlugin (Plugin):
878 """Polymer model (WLC, FJC, etc.) fitting.
881 super(PolymerFitPlugin, self).__init__(name='polymer_fit')
882 self._arguments = [ # For Command initialization
883 Argument('polymer model', default='WLC', help="""
884 Select the default polymer model for 'polymer fit'. See the
885 documentation for descriptions of available algorithms.
887 Argument('FJC Kuhn length', type='float', default=4e-10,
888 help='Kuhn length in meters'),
889 Argument('FJC_PEG Kuhn length', type='float', default=4e-10,
890 help='Kuhn length in meters'),
891 Argument('FJC_PEG elasticity', type='float', default=150.0,
892 help='Elasticity of a PEG segment in Newtons per meter.'),
893 Argument('FJC_PEG delta G', type='float', default=3.0, help="""
894 Gibbs free energy difference between trans-trans-trans (ttt) and
895 trans-trans-gauche (ttg) PEG states in units of kBT.
897 Argument('FJC_PEG L_helical', type='float', default=2.8e-10,
898 help='Contour length of PEG in the ttg state.'),
899 Argument('FJC_PEG L_planar', type='float', default=3.58e-10,
900 help='Contour length of PEG in the ttt state.'),
901 Argument('WLC persistence length', type='float', default=4e-10,
902 help='Persistence length in meters'),
905 Setting(section=self.setting_section, help=self.__doc__)]
906 for argument in self._arguments:
907 self._settings.append(argument_to_setting(
908 self.setting_section, argument))
909 argument.default = None # if argument isn't given, use the config.
910 self._arguments.extend([ # Non-configurable arguments
911 Argument(name='input distance column', type='string',
912 default='cantilever adjusted extension (m)',
914 Name of the column to use as the surface position input.
916 Argument(name='input deflection column', type='string',
917 default='deflection (N)',
919 Name of the column to use as the deflection input.
923 PolymerFitCommand(self), PolymerFitPeaksCommand(self),
924 TranslateFlatPeaksCommand(self),
927 def dependencies(self):
930 def default_settings(self):
931 return self._settings
934 class PolymerFitCommand (Command):
935 """Polymer model (WLC, FJC, etc.) fitting.
937 Fits an entropic elasticity function to a given chunk of the
938 curve. Fit quality compares the residual with the thermal noise
939 (a good fit should be 1 or less).
941 Because the models are complicated and varied, you should
942 configure the command by setting configuration options instead of
943 using command arguments. TODO: argument_to_setting()
945 def __init__(self, plugin):
946 super(PolymerFitCommand, self).__init__(
950 Argument(name='block', aliases=['set'], type='int', default=0,
952 Data block for which the fit should be calculated. For an
953 approach/retract force curve, `0` selects the approaching curve and
954 `1` selects the retracting curve.
956 Argument(name='bounds', type='point', optional=False, count=2,
958 Indicies of points bounding the selected data.
960 ] + plugin._arguments + [
961 Argument(name='output tension column', type='string',
962 default='polymer tension',
964 Name of the column (without units) to use as the polymer tension output.
966 Argument(name='fit parameters info name', type='string',
967 default='surface deflection offset',
969 Name (without units) for storing the fit parameters in the `.info` dictionary.
972 help=self.__doc__, plugin=plugin)
974 def _run(self, hooke, inqueue, outqueue, params):
975 for key,value in params.items():
976 if value == None: # Use configured default value.
977 params[key] = self.plugin.config[key]
978 scale(hooke, params['curve'], params['block']) # TODO: is autoscaling a good idea? (explicit is better than implicit)
979 data = params['curve'].data[params['block']]
980 # HACK? rely on params['curve'] being bound to the local hooke
981 # playlist (i.e. not a copy, as you would get by passing a
982 # curve through the queue). Ugh. Stupid queues. As an
983 # alternative, we could pass lookup information through the
985 model = params['polymer model']
986 new = Data((data.shape[0], data.shape[1]+1), dtype=data.dtype)
987 new.info = copy.deepcopy(data.info)
989 new.info['columns'].append(
990 join_data_label(params['output tension column'], 'N'))
991 z_data = data[:,data.info['columns'].index(
992 params['input distance column'])]
993 d_data = data[:,data.info['columns'].index(
994 params['input deflection column'])]
995 start,stop = params['bounds']
996 tension_data,ps = self.fit_polymer_model(
997 params, z_data, d_data, start, stop, outqueue)
998 new.info[params['fit parameters info name']] = ps
999 new.info[params['fit parameters info name']]['model'] = model
1000 new[:,-1] = tension_data
1001 params['curve'].data[params['block']] = new
1003 def fit_polymer_model(self, params, z_data, d_data, start, stop,
1005 """Railyard for the `fit_*_model` family.
1007 Uses the `polymer model` configuration setting to call the
1008 appropriate backend algorithm.
1010 fn = getattr(self, 'fit_%s_model'
1011 % params['polymer model'].replace('-','_'))
1012 return fn(params, z_data, d_data, start, stop, outqueue)
1014 def fit_FJC_model(self, params, z_data, d_data, start, stop,
1016 """Fit the data with :class:`FJC`.
1019 'temperature (K)': self.plugin.config['temperature'],
1020 'x data (m)': z_data[start:stop],
1022 if True: # TODO: optionally free persistence length
1023 info['Kuhn length (m)'] = (
1024 params['FJC Kuhn length'])
1025 model = FJC(d_data[start:stop], info=info, rescale=True)
1027 params = model.fit(outqueue=queue)
1028 if True: # TODO: if Kuhn length fixed
1030 a = info['Kuhn length (m)']
1035 T = info['temperature (K)']
1036 fit_info = queue.get(block=False)
1037 f_data = numpy.ones(z_data.shape, dtype=z_data.dtype) * numpy.nan
1038 f_data[start:stop] = FJC_fn(z_data[start:stop], T=T, L=L, a=a)
1039 return [f_data, fit_info]
1041 def fit_FJC_PEG_model(self, params, z_data, d_data, start, stop,
1043 """Fit the data with :class:`FJC_PEG`.
1046 'temperature (K)': self.plugin.config['temperature'],
1047 'x data (m)': z_data[start:stop],
1050 if True: # TODO: optionally free persistence length
1051 info['Kuhn length (m)'] = (
1052 params['FJC Kuhn length'])
1053 model = FJC_PEG(d_data[start:stop], info=info, rescale=True)
1055 params = model.fit(outqueue=queue)
1056 if True: # TODO: if Kuhn length fixed
1058 a = info['Kuhn length (m)']
1063 T = info['temperature (K)']
1064 fit_info = queue.get(block=False)
1065 f_data = numpy.ones(z_data.shape, dtype=z_data.dtype) * numpy.nan
1066 f_data[start:stop] = FJC_PEG_fn(z_data[start:stop], **kwargs)
1067 return [f_data, fit_info]
1069 def fit_WLC_model(self, params, z_data, d_data, start, stop,
1071 """Fit the data with :class:`WLC`.
1074 'temperature (K)': self.plugin.config['temperature'],
1075 'x data (m)': z_data[start:stop],
1077 if True: # TODO: optionally free persistence length
1078 info['persistence length (m)'] = (
1079 params['WLC persistence length'])
1080 model = WLC(d_data[start:stop], info=info, rescale=True)
1082 params = model.fit(outqueue=queue)
1083 if True: # TODO: if persistence length fixed
1085 p = info['persistence length (m)']
1090 T = info['temperature (K)']
1091 fit_info = queue.get(block=False)
1092 f_data = numpy.ones(z_data.shape, dtype=z_data.dtype) * numpy.nan
1093 f_data[start:stop] = WLC_fn(z_data[start:stop], T=T, L=L, p=p)
1094 return [f_data, fit_info]
1097 class PolymerFitPeaksCommand (Command):
1098 """Polymer model (WLC, FJC, etc.) fitting.
1100 Use :class:`PolymerFitCommand` to fit the each peak in a list of
1101 previously determined peaks.
1103 def __init__(self, plugin):
1104 super(PolymerFitPeaksCommand, self).__init__(
1105 name='polymer fit peaks',
1108 Argument(name='block', aliases=['set'], type='int', default=0,
1110 Data block for which the fit should be calculated. For an
1111 approach/retract force curve, `0` selects the approaching curve and
1112 `1` selects the retracting curve.
1114 Argument(name='peak info name', type='string',
1115 default='polymer peaks',
1117 Name for the list of peaks in the `.info` dictionary.
1119 Argument(name='peak index', type='int', count=-1, default=None,
1121 Index of the selected peak in the list of peaks. Use `None` to fit all peaks.
1123 ] + plugin._arguments,
1124 help=self.__doc__, plugin=plugin)
1126 def _run(self, hooke, inqueue, outqueue, params):
1127 data = params['curve'].data[params['block']]
1128 fit_command = [c for c in hooke.commands
1129 if c.name=='polymer fit'][0]
1132 p = copy.deepcopy(params)
1133 p['curve'] = params['curve']
1134 del(p['peak info name'])
1135 del(p['peak index'])
1136 for i,peak in enumerate(data.info[params['peak info name']]):
1137 if params['peak index'] == None or i in params['peak index']:
1138 p['bounds'] = [peak.index, peak.post_index()]
1139 p['output tension column'] = peak.name
1140 p['fit parameters info name'] = peak.name
1141 fit_command.run(hooke, inq, outq, **p)
1143 if not isinstance(ret, Success):
1147 class TranslateFlatPeaksCommand (Command):
1148 """Translate flat filter peaks into polymer peaks for fitting.
1150 Use :class:`~hooke.plugin.flatfilt.FlatPeaksCommand` creates a
1151 list of peaks for regions with large derivatives. For velocity
1152 clamp measurements, these regions are usually the rebound phase
1153 after a protein domain unfolds, the cantilever detaches, etc.
1154 Because these features occur after the polymer loading phase, we
1155 need to shift the selected regions back to align them with the
1156 polymer loading regions.
1158 def __init__(self, plugin):
1159 plugin_arguments = [a for a in plugin._arguments
1160 if a.name in ['input distance column',
1161 'input deflection column']]
1164 Argument(name='block', aliases=['set'], type='int', default=0,
1166 Data block for which the fit should be calculated. For an
1167 approach/retract force curve, `0` selects the approaching curve and
1168 `1` selects the retracting curve.
1170 ] + plugin_arguments + [
1171 Argument(name='input peak info name', type='string',
1172 default='flat filter peaks',
1174 Name for the input peaks in the `.info` dictionary.
1176 Argument(name='output peak info name', type='string',
1177 default='polymer peaks',
1179 Name for the ouptput peaks in the `.info` dictionary.
1181 Argument(name='end offset', type='int', default=-1,
1183 Number of points between the end of a new peak and the start of the old.
1185 Argument(name='start fraction', type='float', default=0.2,
1187 Place the start of the new peak at `start_fraction` from the end of
1188 the previous old peak to the end of the new peak. Because the first
1189 new peak will have no previous old peak, it uses a distance of zero
1193 super(TranslateFlatPeaksCommand, self).__init__(
1194 name='flat peaks to polymer peaks',
1195 arguments=arguments,
1196 help=self.__doc__, plugin=plugin)
1198 def _run(self, hooke, inqueue, outqueue, params):
1199 data = params['curve'].data[params['block']]
1200 z_data = data[:,data.info['columns'].index(
1201 params['input distance column'])]
1202 d_data = data[:,data.info['columns'].index(
1203 params['input deflection column'])]
1204 previous_old_stop = numpy.absolute(z_data).argmin()
1206 for i,peak in enumerate(data.info[params['input peak info name']]):
1207 next_old_start = peak.index
1208 stop = next_old_start + params['end offset']
1209 z_start = z_data[previous_old_stop] + params['start fraction']*(
1210 z_data[stop] - z_data[previous_old_stop])
1211 start = numpy.absolute(z_data - z_start).argmin()
1212 p = Peak('polymer peak %d' % i,
1214 values=d_data[start:stop])
1216 previous_old_stop = peak.post_index()
1217 data.info[params['output peak info name']] = new
1221 # def dist2fit(self):
1222 # '''Calculates the average distance from data to fit, scaled by
1223 # the standard deviation of the free cantilever area (thermal