1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008-2011 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 ..util.callback import is_iterable
41 from ..util.fit import PoorFit, ModelFitter
42 from ..util.peak import Peak
43 from ..util.si import join_data_label, split_data_label
44 from . import Plugin, argument_to_setting
45 from .curve import ColumnAccessCommand, ColumnAddingCommand
48 kB = 1.3806503e-23 # Joules/Kelvin
52 """Hyperbolic cotangent.
56 >>> coth(1.19967874) # doctest: +ELLIPSIS
64 \coth(z) \equiv \frac{\exp(z) + \exp(-z)}{\exp(z) - \exp(-z)}
68 http://mathworld.wolfram.com/HyperbolicCotangent.html
70 return 1.0/numpy.tanh(z)
73 """Inverse hyperbolic cotangent.
77 >>> arccoth(1.19967874) # doctest: +ELLIPSIS
79 >>> arccoth(coth(numpy.pi)) # doctest: +ELLIPSIS
84 Inverting from the :func:`definition <coth>`.
87 z \equiv \atanh\left( \frac{1}{\coth(z)} \right)
89 return numpy.arctanh(1.0/z)
96 >>> Langevin(numpy.pi) # doctest: +ELLIPSIS
101 From `Wikipedia`_ or Hatfield [#]_
104 L(z) \equiv \coth(z) - \frac{1}{z}
107 http://en.wikipedia.org/wiki/Brillouin_and_Langevin_functions#Langevin_Function
109 .. [#]: J.W. Hatfield and S.R. Quake
110 "Dynamic Properties of an Extended Polymer in Solution."
111 Phys. Rev. Lett. 1999.
112 doi: `10.1103/PhysRevLett.82.3548 <http://dx.doi.org/10.1103/PhysRevLett.82.3548>`
114 return coth(z) - 1.0/z
116 def inverse_Langevin(z, extreme=1.0 - 1e-8):
117 """Inverse Langevin function.
121 z : float or array_like
122 object whose inverse Langevin will be returned
124 value (close to one) for which we assume the inverse is +/-
125 infinity. This avoids problems with Newton-Raphson
126 convergence in regions with low slope.
130 >>> inverse_Langevin(Langevin(numpy.pi)) # doctest: +ELLIPSIS
132 >>> inverse_Langevin(Langevin(numpy.array([1,2,3]))) # doctest: +ELLIPSIS
137 We approximate the inverse Langevin via Newton's method
138 (:func:`scipy.optimize.newton`). For the starting point, we take
139 the first three terms from the Taylor series (from `Wikipedia`_).
142 L^{-1}(z) = 3z + \frac{9}{5}z^3 + \frac{297}{175}z^5 + \dots
145 http://en.wikipedia.org/wiki/Brillouin_and_Langevin_functions#Langevin_Function
148 ret = numpy.ndarray(shape=z.shape, dtype=z.dtype)
149 for i,z_i in enumerate(z):
150 ret[i] = inverse_Langevin(z_i)
156 # Catch stdout since sometimes the newton function prints exit
157 # messages to stdout, e.g. "Tolerance of %s reached".
159 tmp_stdout = StringIO.StringIO()
160 sys.stdout = tmp_stdout
161 ret = newton(func=lambda x: Langevin(x)-z,
162 x0=3*z + 9./5.*z**3 + 297./175.*z**5,)
164 output = tmp_stdout.getvalue()
167 def FJC_fn(x_data, T, L, a):
168 """The freely jointed chain model.
173 x values for which the WLC tension should be calculated.
175 temperature in Kelvin.
177 contour length in meters.
179 Kuhn length in meters.
188 >>> FJC_fn(numpy.array([1e-9, 5e-9, 10e-9], dtype=numpy.float),
189 ... T=300, L=15e-9, a=2.5e-10) # doctest: +ELLIPSIS
190 array([ 3.322...-12, 1.78...e-11, 4.889...e-11])
194 The freely jointed chain model is
197 F(x) = \frac{k_B T}{a} L^{-1}\left( \frac{x}{L} \right)
199 where :math:`L^{-1}` is the :func:`inverse_Langevin`, :math:`a` is
200 the Kuhn length, and :math:`L` is the contour length [#]_. For
201 the inverse formulation (:math:`x(F)`), see Ray and Akhremitchev [#]_.
204 .. [#]: J.W. Hatfield and S.R. Quake
205 "Dynamic Properties of an Extended Polymer in Solution."
206 Phys. Rev. Lett. 1999.
207 doi: `10.1103/PhysRevLett.82.3548 <http://dx.doi.org/10.1103/PhysRevLett.82.3548>`
209 .. [#] C. Ray and B.B. Akhremitchev.
210 `"Fitting Force Curves by the Extended Freely Jointed Chain Model" <http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf>`
212 return kB*T / a * inverse_Langevin(x_data/L)
214 class FJC (ModelFitter):
215 """Fit the data to a freely jointed chain.
219 Generate some example data
222 >>> L = 35e-9 # meters
223 >>> a = 2.5e-10 # meters
224 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
225 >>> d_data = FJC_fn(x_data, T=T, L=L, a=a)
227 Fit the example data with a two-parameter fit (`L` and `a`).
230 ... 'temperature (K)': T,
231 ... 'x data (m)': x_data,
233 >>> model = FJC(d_data, info=info, rescale=True)
234 >>> outqueue = Queue()
235 >>> L,a = model.fit(outqueue=outqueue)
236 >>> fit_info = outqueue.get(block=False)
242 Fit the example data with a one-parameter fit (`L`). We introduce
243 some error in our fixed Kuhn length for fun.
245 >>> info['Kuhn length (m)'] = 2*a
246 >>> model = FJC(d_data, info=info, rescale=True)
247 >>> L, = model.fit(outqueue=outqueue)
248 >>> fit_info = outqueue.get(block=False)
249 >>> print L # doctest: +ELLIPSIS
253 """To avoid invalid values of `L`, we reparametrize with `Lp`.
258 contour length in meters
263 rescaled version of `L`.
267 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
268 >>> model = FJC(data=x_data, info={'x data (m)':x_data})
269 >>> model.Lp(20e-9) # doctest: +ELLIPSIS
271 >>> model.Lp(19e-9) # doctest: +ELLIPSIS
273 >>> model.Lp(21e-9) # doctest: +ELLIPSIS
275 >>> model.Lp(100e-9) # doctest: +ELLIPSIS
280 The rescaling is designed to limit `L` to values strictly
281 greater than the maximum `x` data value, so we use
284 L = [\exp(L_p))+1] * x_\text{max}
286 which has the inverse
289 L_p = \ln(L/x_\text{max}-1)
291 This will obviously effect the interpretation of the fit's covariance matrix.
293 x_max = self.info['x data (m)'].max()
294 return numpy.log(L/x_max - 1)
297 """Inverse of :meth:`Lp`.
302 rescaled version of `L`
307 contour length in meters
311 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
312 >>> model = FJC(data=x_data, info={'x data (m)':x_data})
313 >>> model.L(model.Lp(21e-9)) # doctest: +ELLIPSIS
315 >>> model.L(model.Lp(100e-9)) # doctest: +ELLIPSIS
318 x_max = self.info['x data (m)'].max()
319 return (numpy.exp(Lp)+1)*x_max
321 def model(self, params):
322 """Extract the relavant arguments and use :func:`FJC_fn`.
326 params : list of floats
327 `[Lp, a]`, where the presence of `a` is optional.
329 # setup convenient aliases
330 T = self.info['temperature (K)']
331 x_data = self.info['x data (m)']
332 L = self.L(params[0])
336 a = self.info['Kuhn length (m)']
338 self._model_data[:] = FJC_fn(x_data, T, L, a)
339 return self._model_data
341 def fit(self, *args, **kwargs):
342 params = super(FJC, self).fit(*args, **kwargs)
343 params[0] = self.L(params[0]) # convert Lp -> L
345 params[1] = abs(params[1]) # take the absolute value of `a`
348 def guess_initial_params(self, outqueue=None):
349 """Guess initial fitting parameters.
354 A guess at the reparameterized contour length in meters.
356 A guess at the Kuhn length in meters. If `.info` has a
357 setting for `'Kuhn length (m)'`, `a` is not returned.
359 x_max = self.info['x data (m)'].max()
360 a_given = 'Kuhn length (m)' in self.info
362 a = self.info['Kuhn length (m)']
372 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):
373 """Inverse poly(ethylene-glycol) adjusted extended FJC model.
377 >>> kwargs = {'T':300.0, 'N':1, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
378 ... 'dG':3.0, 'a':7e-10}
379 >>> inverse_FJC_PEG_fn(F_data=200e-12, **kwargs) # doctest: +ELLIPSIS
384 The function is that proposed by F. Oesterhelt, et al. [#]_.
387 x(F) = N_\text{S} \cdot \left[
389 \frac{L_\text{planar}}{
390 \exp\left(-\Delta G/k_B T\right) + 1}
391 + \frac{L_\text{helical}}{
392 \exp\left(+\Delta G/k_B T\right) + 1}
394 \coth\left(\frac{Fa}{k_B T}\right)
397 + \frac{F}{K_\text{S}}
399 where :math:`N_\text{S}` is the number of segments,
400 :math:`K_\text{S}` is the segment elasticicty,
401 :math:`L_\text{planar}` is the ttt contour length,
402 :math:`L_\text{helical}` is the ttg contour length,
403 :math:`\Delta G` is the Gibbs free energy difference
404 :math:`G_\text{planar}-G_\text{helical}`,
405 :math:`a` is the Kuhn length,
406 and :math:`F` is the chain tension.
408 .. [#]: F. Oesterhelt, M. Rief, and H.E. Gaub.
409 "Single molecule force spectroscopy by AFM indicates helical
410 structure of poly(ethylene-glycol) in water."
411 New Journal of Physics. 1999.
412 doi: `10.1088/1367-2630/1/1/006 <http://dx.doi.org/10.1088/1367-2630/1/1/006>`
416 g = (dG - F_data*(Lp-Lh)) / kBT
418 return N * ((Lp/(numpy.exp(-g)+1) + Lh/(numpy.exp(+g)+1)) * (coth(z)-1./z)
421 def FJC_PEG_fn(x_data, T, N, k, Lp, Lh, dG, a):
422 """Poly(ethylene-glycol) adjusted extended FJC model.
426 z : float or array_like
427 object whose inverse Langevin will be returned
429 value (close to one) for which we assume the inverse is +/-
430 infinity. This avoids problems with Newton-Raphson
431 convergence in regions with low slope.
435 >>> kwargs = {'T':300.0, 'N':1, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
436 ... 'dG':3.0, 'a':7e-10}
437 >>> FJC_PEG_fn(inverse_FJC_PEG_fn(1e-11, **kwargs), **kwargs) # doctest: +ELLIPSIS
439 >>> FJC_PEG_fn(inverse_FJC_PEG_fn(3.4e-10, **kwargs), **kwargs) # doctest: +ELLIPSIS
441 >>> FJC_PEG_fn(numpy.array([1e-10,2e-10,3e-10]), **kwargs) # doctest: +ELLIPSIS
442 array([ 5.207...e-12, 1.257...e-11, 3.636...e-11])
443 >>> kwargs['N'] = 123
444 >>> FJC_PEG_fn(numpy.array([1e-8,2e-8,3e-8]), **kwargs) # doctest: +ELLIPSIS
445 array([ 4.160...e-12, 9.302...e-12, 1.830...e-11])
449 We approximate the PEG adjusted eFJC via Newton's method
450 (:func:`scipy.optimize.newton`). For the starting point, we use
451 the standard FJC function with an averaged contour length.
453 kwargs = {'T':T, 'N':N, 'k':k, 'Lp':Lp, 'Lh':Lh, 'dG':dG, 'a':a}
454 if is_iterable(x_data):
455 ret = numpy.ndarray(shape=x_data.shape, dtype=x_data.dtype)
456 for i,x in enumerate(x_data):
457 ret[i] = FJC_PEG_fn(x, **kwargs)
461 # Approximate with the simple FJC_fn()
464 while guess == numpy.inf:
465 guess = FJC_fn(x_data, T=T, L=L, a=a)
468 fn = lambda f: inverse_FJC_PEG_fn(guess*abs(f), **kwargs) - x_data
469 ret = guess*abs(newton(func=fn, x0=1.0))
472 class FJC_PEG (ModelFitter):
473 """Fit the data to an poly(ethylene-glycol) adjusted extended FJC.
477 Generate some example data
479 >>> kwargs = {'T':300.0, 'N':123, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
480 ... 'dG':3.0, 'a':7e-10}
481 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
482 >>> d_data = FJC_PEG_fn(x_data, **kwargs)
484 Fit the example data with a two-parameter fit (`N` and `a`).
487 ... 'temperature (K)': kwargs['T'],
488 ... 'x data (m)': x_data,
489 ... 'section elasticity (N/m)': kwargs['k'],
490 ... 'planar section length (m)': kwargs['Lp'],
491 ... 'helical section length (m)': kwargs['Lh'],
492 ... 'Gibbs free energy difference (Gp - Gh) (kBT)': kwargs['dG'],
494 >>> model = FJC_PEG(d_data, info=info, rescale=True)
495 >>> outqueue = Queue()
496 >>> N,a = model.fit(outqueue=outqueue)
497 >>> fit_info = outqueue.get(block=False)
503 Fit the example data with a one-parameter fit (`N`). We introduce
504 some error in our fixed Kuhn length for fun.
506 >>> info['Kuhn length (m)'] = 2*kwargs['a']
507 >>> model = FJC_PEG(d_data, info=info, rescale=True)
508 >>> N, = model.fit(outqueue=outqueue)
509 >>> fit_info = outqueue.get(block=False)
510 >>> print N # doctest: +ELLIPSIS
514 """To avoid invalid values of `L`, we reparametrize with `Lr`.
519 contour length in meters
524 rescaled version of `L`.
528 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
529 >>> model = FJC_PEG(data=x_data, info={'x data (m)':x_data})
530 >>> model.Lr(20e-9) # doctest: +ELLIPSIS
532 >>> model.Lr(19e-9) # doctest: +ELLIPSIS
534 >>> model.Lr(21e-9) # doctest: +ELLIPSIS
536 >>> model.Lr(100e-9) # doctest: +ELLIPSIS
541 The rescaling is designed to limit `L` to values strictly
542 greater than zero, so we use
545 L = \exp(L_p) * x_\text{max}
547 which has the inverse
550 L_p = \ln(L/x_\text{max})
552 This will obviously effect the interpretation of the fit's covariance matrix.
554 x_max = self.info['x data (m)'].max()
555 return numpy.log(L/x_max)
558 """Inverse of :meth:`Lr`.
563 rescaled version of `L`
568 contour length in meters
572 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
573 >>> model = FJC_PEG(data=x_data, info={'x data (m)':x_data})
574 >>> model.L(model.Lr(21e-9)) # doctest: +ELLIPSIS
576 >>> model.L(model.Lr(100e-9)) # doctest: +ELLIPSIS
579 x_max = self.info['x data (m)'].max()
580 return numpy.exp(Lr)*x_max
584 'T': self.info['temperature (K)'],
585 'k': self.info['section elasticity (N/m)'],
586 'Lp': self.info['planar section length (m)'],
587 'Lh': self.info['helical section length (m)'],
588 'dG': self.info['Gibbs free energy difference (Gp - Gh) (kBT)'],
591 def model(self, params):
592 """Extract the relavant arguments and use :func:`FJC_PEG_fn`.
596 params : list of floats
597 `[N, a]`, where the presence of `a` is optional.
599 # setup convenient aliases
600 T = self.info['temperature (K)']
601 x_data = self.info['x data (m)']
602 N = self.L(params[0])
606 a = self.info['Kuhn length (m)']
607 kwargs = self._kwargs()
609 self._model_data[:] = FJC_PEG_fn(x_data, N=N, a=a, **kwargs)
610 return self._model_data
612 def fit(self, *args, **kwargs):
613 params = super(FJC_PEG, self).fit(*args, **kwargs)
614 params[0] = self.L(params[0]) # convert Nr -> N
616 params[1] = abs(params[1]) # take the absolute value of `a`
619 def guess_initial_params(self, outqueue=None):
620 """Guess initial fitting parameters.
625 A guess at the section count.
627 A guess at the Kuhn length in meters. If `.info` has a
628 setting for `'Kuhn length (m)'`, `a` is not returned.
630 x_max = self.info['x data (m)'].max()
631 a_given = 'Kuhn length (m)' in self.info
633 a = self.info['Kuhn length (m)']
636 f_max = self._data.max()
637 kwargs = self._kwargs()
639 x_section = inverse_FJC_PEG_fn(F_data=f_max, **kwargs)
640 N = x_max / x_section;
647 def WLC_fn(x_data, T, L, p):
648 """The worm like chain interpolation model.
653 x values for which the WLC tension should be calculated.
655 temperature in Kelvin.
657 contour length in meters.
659 persistence length in meters.
668 >>> WLC_fn(numpy.array([1e-9, 5e-9, 10e-9], dtype=numpy.float),
669 ... T=300, L=15e-9, p=2.5e-10) # doctest: +ELLIPSIS
670 array([ 1.717...e-12, 1.070...e-11, 4.418...e-11])
674 The function is the simple polynomial worm like chain
675 interpolation forumula proposed by C. Bustamante, et al. [#]_.
678 F(x) = \frac{k_B T}{p} \left[
679 \frac{1}{4}\left( \frac{1}{(1-x/L)^2} - 1 \right)
683 .. [#] C. Bustamante, J.F. Marko, E.D. Siggia, and S.Smith.
684 "Entropic elasticity of lambda-phage DNA."
686 doi: `10.1126/science.8079175 <http://dx.doi.org/10.1126/science.8079175>`
689 scaled_data = x_data / L
690 return a * (0.25*((1-scaled_data)**-2 - 1) + scaled_data)
692 class WLC (ModelFitter):
693 """Fit the data to a worm like chain.
697 Generate some example data
700 >>> L = 35e-9 # meters
701 >>> p = 2.5e-10 # meters
702 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
703 >>> d_data = WLC_fn(x_data, T=T, L=L, p=p)
705 Fit the example data with a two-parameter fit (`L` and `p`).
708 ... 'temperature (K)': T,
709 ... 'x data (m)': x_data,
711 >>> model = WLC(d_data, info=info, rescale=True)
712 >>> outqueue = Queue()
713 >>> L,p = model.fit(outqueue=outqueue)
714 >>> fit_info = outqueue.get(block=False)
720 Fit the example data with a one-parameter fit (`L`). We introduce
721 some error in our fixed persistence length for fun.
723 >>> info['persistence length (m)'] = 2*p
724 >>> model = WLC(d_data, info=info, rescale=True)
725 >>> L, = model.fit(outqueue=outqueue)
726 >>> fit_info = outqueue.get(block=False)
727 >>> print L # doctest: +ELLIPSIS
731 """To avoid invalid values of `L`, we reparametrize with `Lp`.
736 contour length in meters
741 rescaled version of `L`.
745 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
746 >>> model = WLC(data=x_data, info={'x data (m)':x_data})
747 >>> model.Lp(20e-9) # doctest: +ELLIPSIS
749 >>> model.Lp(19e-9) # doctest: +ELLIPSIS
751 >>> model.Lp(21e-9) # doctest: +ELLIPSIS
753 >>> model.Lp(100e-9) # doctest: +ELLIPSIS
758 The rescaling is designed to limit `L` to values strictly
759 greater than the maximum `x` data value, so we use
762 L = [\exp(L_p))+1] * x_\text{max}
764 which has the inverse
767 L_p = \ln(L/x_\text{max}-1)
769 This will obviously effect the interpretation of the fit's covariance matrix.
771 x_max = self.info['x data (m)'].max()
772 return numpy.log(L/x_max - 1)
775 """Inverse of :meth:`Lp`.
780 rescaled version of `L`
785 contour length in meters
789 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
790 >>> model = WLC(data=x_data, info={'x data (m)':x_data})
791 >>> model.L(model.Lp(21e-9)) # doctest: +ELLIPSIS
793 >>> model.L(model.Lp(100e-9)) # doctest: +ELLIPSIS
796 x_max = self.info['x data (m)'].max()
797 return (numpy.exp(Lp)+1)*x_max
799 def model(self, params):
800 """Extract the relavant arguments and use :func:`WLC_fn`.
804 params : list of floats
805 `[Lp, p]`, where the presence of `p` is optional.
807 # setup convenient aliases
808 T = self.info['temperature (K)']
809 x_data = self.info['x data (m)']
810 L = self.L(params[0])
814 p = self.info['persistence length (m)']
816 self._model_data[:] = WLC_fn(x_data, T, L, p)
817 return self._model_data
819 def fit(self, *args, **kwargs):
820 params = super(WLC, self).fit(*args, **kwargs)
821 params[0] = self.L(params[0]) # convert Lp -> L
823 params[1] = abs(params[1]) # take the absolute value of `p`
826 def guess_initial_params(self, outqueue=None):
827 """Guess initial fitting parameters.
832 A guess at the reparameterized contour length in meters.
834 A guess at the persistence length in meters. If `.info`
835 has a setting for `'persistence length (m)'`, `p` is not
838 x_max = self.info['x data (m)'].max()
839 p_given = 'persistence length (m)' in self.info
841 p = self.info['persistence length (m)']
851 class PolymerFitPlugin (Plugin):
852 """Polymer model (WLC, FJC, etc.) fitting.
855 super(PolymerFitPlugin, self).__init__(name='polymer_fit')
856 self._arguments = [ # For Command initialization
857 Argument('polymer model', default='WLC', help="""
858 Select the default polymer model for 'polymer fit'. See the
859 documentation for descriptions of available algorithms.
861 Argument('FJC Kuhn length', type='float', default=4e-10,
862 help='Kuhn length in meters'),
863 Argument('FJC_PEG Kuhn length', type='float', default=4e-10,
864 help='Kuhn length in meters'),
865 Argument('FJC_PEG elasticity', type='float', default=150.0,
866 help='Elasticity of a PEG segment in Newtons per meter.'),
867 Argument('FJC_PEG delta G', type='float', default=3.0, help="""
868 Gibbs free energy difference between trans-trans-trans (ttt) and
869 trans-trans-gauche (ttg) PEG states in units of kBT.
871 Argument('FJC_PEG L_helical', type='float', default=2.8e-10,
872 help='Contour length of PEG in the ttg state.'),
873 Argument('FJC_PEG L_planar', type='float', default=3.58e-10,
874 help='Contour length of PEG in the ttt state.'),
875 Argument('WLC persistence length', type='float', default=4e-10,
876 help='Persistence length in meters'),
879 Setting(section=self.setting_section, help=self.__doc__)]
880 for argument in self._arguments:
881 self._settings.append(argument_to_setting(
882 self.setting_section, argument))
883 argument.default = None # if argument isn't given, use the config.
884 self._input_columns = [
885 ('distance column', 'cantilever adjusted extension (m)',
887 Name of the column to use as the surface position input.
889 ('deflection column', 'deflection (N)',
891 Name of the column to use as the deflection input.
895 PolymerFitCommand(self), PolymerFitPeaksCommand(self),
896 TranslateFlatPeaksCommand(self),
899 def dependencies(self):
902 def default_settings(self):
903 return self._settings
906 class PolymerFitCommand (ColumnAddingCommand):
907 """Polymer model (WLC, FJC, etc.) fitting.
909 Fits an entropic elasticity function to a given chunk of the
910 curve. Fit quality compares the residual with the thermal noise
911 (a good fit should be 1 or less).
913 Because the models are complicated and varied, you should
914 configure the command by setting configuration options instead of
915 using command arguments. TODO: argument_to_setting()
917 def __init__(self, plugin):
918 super(PolymerFitCommand, self).__init__(
920 columns=plugin._input_columns,
922 ('output tension column', 'polymer tension',
924 Name of the column (without units) to use as the polymer tension output.
928 Argument(name='fit parameters info name', type='string',
929 default='polymer fit',
931 Name (without units) for storing the fit parameters in the `.info` dictionary.
933 Argument(name='bounds', type='point', optional=False, count=2,
935 Indicies of points bounding the selected data.
937 ] + plugin._arguments,
938 help=self.__doc__, plugin=plugin)
940 def _run(self, hooke, inqueue, outqueue, params):
941 params = self._setup_params(hooke, params)
942 data = self._block(hooke, params)
943 model = params['polymer model']
944 dist_data = self._get_column(
945 hooke=hooke, params=params, column_name='distance column')
946 def_data = self._get_column(
947 hooke=hooke, params=params, column_name='deflection column')
948 start,stop = params['bounds']
949 tension_data,ps = self.fit_polymer_model(
950 params, dist_data, def_data, start, stop, outqueue)
951 data.info[params['fit parameters info name']] = ps
952 data.info[params['fit parameters info name']]['model'] = model
953 self._set_column(hooke=hooke, params=params,
954 column_name='output tension column',
957 def _setup_params(self, hooke, params):
958 for key,value in params.items():
959 if value == None: # Use configured default value.
960 params[key] = self.plugin.config[key]
961 name,def_unit = split_data_label(params['deflection column'])
962 params['output tension column'] = join_data_label(
963 params['output tension column'], def_unit)
966 def fit_polymer_model(self, params, dist_data, def_data, start, stop,
968 """Railyard for the `fit_*_model` family.
970 Uses the `polymer model` configuration setting to call the
971 appropriate backend algorithm.
973 fn = getattr(self, 'fit_%s_model'
974 % params['polymer model'].replace('-','_'))
975 return fn(params, dist_data, def_data, start, stop, outqueue)
977 def fit_FJC_model(self, params, dist_data, def_data, start, stop,
979 """Fit the data with :class:`FJC`.
982 'temperature (K)': self.plugin.config['temperature'],
983 'x data (m)': dist_data[start:stop],
985 if True: # TODO: optionally free persistence length
986 info['Kuhn length (m)'] = (
987 params['FJC Kuhn length'])
988 model = FJC(def_data[start:stop], info=info, rescale=True)
990 params = model.fit(outqueue=queue)
991 if True: # TODO: if Kuhn length fixed
993 a = info['Kuhn length (m)']
997 T = info['temperature (K)']
998 fit_info = queue.get(block=False)
999 f_data = numpy.ones(dist_data.shape, dtype=dist_data.dtype) * numpy.nan
1000 f_data[start:stop] = FJC_fn(dist_data[start:stop], T=T, L=L, a=a)
1001 return [f_data, fit_info]
1003 def fit_FJC_PEG_model(self, params, dist_data, def_data, start, stop,
1005 """Fit the data with :class:`FJC_PEG`.
1008 'temperature (K)': self.plugin.config['temperature'],
1009 'x data (m)': dist_data[start:stop],
1012 if True: # TODO: optionally free persistence length
1013 info['Kuhn length (m)'] = (
1014 params['FJC Kuhn length'])
1015 model = FJC_PEG(def_data[start:stop], info=info, rescale=True)
1017 params = model.fit(outqueue=queue)
1018 if True: # TODO: if Kuhn length fixed
1020 a = info['Kuhn length (m)']
1024 T = info['temperature (K)']
1025 fit_info = queue.get(block=False)
1026 f_data = numpy.ones(dist_data.shape, dtype=dist_data.dtype) * numpy.nan
1027 f_data[start:stop] = FJC_PEG_fn(dist_data[start:stop], **kwargs)
1028 return [f_data, fit_info]
1030 def fit_WLC_model(self, params, dist_data, def_data, start, stop,
1032 """Fit the data with :class:`WLC`.
1035 'temperature (K)': self.plugin.config['temperature'],
1036 'x data (m)': dist_data[start:stop],
1038 if True: # TODO: optionally free persistence length
1039 info['persistence length (m)'] = (
1040 params['WLC persistence length'])
1041 model = WLC(def_data[start:stop], info=info, rescale=True)
1043 params = model.fit(outqueue=queue)
1044 if True: # TODO: if persistence length fixed
1046 p = info['persistence length (m)']
1049 info['persistence length (m)'] = p
1051 info['contour length (m)'] = L
1052 T = info['temperature (K)']
1053 fit_info = queue.get(block=False)
1054 info['fit'] = fit_info
1055 info.pop('x data (m)')
1056 f_data = numpy.ones(dist_data.shape, dtype=dist_data.dtype) * numpy.nan
1057 f_data[start:stop] = WLC_fn(dist_data[start:stop], T=T, L=L, p=p)
1058 return [f_data, info]
1061 class PolymerFitPeaksCommand (ColumnAccessCommand):
1062 """Polymer model (WLC, FJC, etc.) fitting.
1064 Use :class:`PolymerFitCommand` to fit the each peak in a list of
1065 previously determined peaks.
1067 def __init__(self, plugin):
1068 super(PolymerFitPeaksCommand, self).__init__(
1069 name='polymer fit peaks',
1070 columns=plugin._input_columns,
1072 Argument(name='peak info name', type='string',
1073 default='polymer peaks',
1075 Name for the list of peaks in the `.info` dictionary.
1077 Argument(name='peak index', type='int', count=-1, default=None,
1079 Index of the selected peak in the list of peaks. Use `None` to fit all peaks.
1081 ] + plugin._arguments,
1082 help=self.__doc__, plugin=plugin)
1084 def _run(self, hooke, inqueue, outqueue, params):
1085 data = self._block(hooke, params)
1086 fit_command = hooke.command_by_name['polymer fit']
1089 curve = params['curve']
1090 params['curve'] = None
1091 p = copy.deepcopy(params)
1092 p['curve'] = params['curve']
1093 del(p['peak info name'])
1094 del(p['peak index'])
1095 for i,peak in enumerate(data.info[params['peak info name']]):
1096 if params['peak index'] == None or i in params['peak index']:
1097 p['bounds'] = [peak.index, peak.post_index()]
1098 p['output tension column'] = peak.name
1099 p['fit parameters info name'] = peak.name
1100 fit_command.run(hooke, inq, outq, **p)
1102 if not isinstance(ret, Success):
1106 class TranslateFlatPeaksCommand (ColumnAccessCommand):
1107 """Translate flat filter peaks into polymer peaks for fitting.
1109 Use :class:`~hooke.plugin.flatfilt.FlatPeaksCommand` creates a
1110 list of peaks for regions with large derivatives. For velocity
1111 clamp measurements, these regions are usually the rebound phase
1112 after a protein domain unfolds, the cantilever detaches, etc.
1113 Because these features occur after the polymer loading phase, we
1114 need to shift the selected regions back to align them with the
1115 polymer loading regions.
1117 def __init__(self, plugin):
1118 super(TranslateFlatPeaksCommand, self).__init__(
1119 name='flat peaks to polymer peaks',
1120 columns=plugin._input_columns,
1122 Argument(name='input peak info name', type='string',
1123 default='flat filter peaks',
1125 Name for the input peaks in the `.info` dictionary.
1127 Argument(name='output peak info name', type='string',
1128 default='polymer peaks',
1130 Name for the ouptput peaks in the `.info` dictionary.
1132 Argument(name='end offset', type='int', default=-1,
1134 Number of points between the end of a new peak and the start of the old.
1136 Argument(name='start fraction', type='float', default=0.2,
1138 Place the start of the new peak at `start_fraction` from the end of
1139 the previous old peak to the end of the new peak. Because the first
1140 new peak will have no previous old peak, it uses a distance of zero
1143 ] + plugin._arguments,
1144 help=self.__doc__, plugin=plugin)
1146 def _run(self, hooke, inqueue, outqueue, params):
1147 data = self._block(hooke, params)
1148 dist_data = self._get_column(
1149 hooke=hooke, params=params, column_name='distance column')
1150 def_data = self._get_column(
1151 hooke=hooke, params=params, column_name='deflection column')
1152 previous_old_stop = numpy.absolute(dist_data).argmin()
1155 peaks = data.info[params['input peak info name']]
1157 raise Failure('No value for %s in %s.info (%s): %s'
1158 % (params['input peak info name'], data.info['name'],
1159 sorted(data.info.keys()), e))
1160 for i,peak in enumerate(peaks):
1161 next_old_start = peak.index
1162 stop = next_old_start + params['end offset']
1164 dist_data[previous_old_stop]
1165 + params['start fraction']*(
1166 dist_data[stop] - dist_data[previous_old_stop]))
1167 start = numpy.absolute(dist_data - dist_start).argmin()
1168 p = Peak('polymer peak %d' % i,
1170 values=def_data[start:stop])
1172 previous_old_stop = peak.post_index()
1173 data.info[params['output peak info name']] = new
1177 # def dist2fit(self):
1178 # '''Calculates the average distance from data to fit, scaled by
1179 # the standard deviation of the free cantilever area (thermal