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.).
35 from scipy.optimize import newton, bisect
37 from ..command import Command, Argument, Failure
38 from ..config import Setting
39 from ..curve import Data
40 from ..plugin import Plugin
41 from ..util.fit import PoorFit, ModelFitter
42 from ..util.callback import is_iterable
43 from .curve import CurveArgument
44 from .vclamp import scale
47 kB = 1.3806503e-23 # Joules/Kelvin
51 """Hyperbolic cotangent.
55 >>> coth(1.19967874) # doctest: +ELLIPSIS
63 \coth(z) \equiv \frac{\exp(z) + \exp(-z)}{\exp(z) - \exp(-z)}
67 http://mathworld.wolfram.com/HyperbolicCotangent.html
69 return 1.0/numpy.tanh(z)
72 """Inverse hyperbolic cotangent.
76 >>> arccoth(1.19967874) # doctest: +ELLIPSIS
78 >>> arccoth(coth(numpy.pi)) # doctest: +ELLIPSIS
83 Inverting from the :func:`definition <coth>`.
86 z \equiv \atanh\left( \frac{1}{\coth(z)} \right)
88 return numpy.arctanh(1.0/z)
95 >>> Langevin(numpy.pi) # doctest: +ELLIPSIS
100 From `Wikipedia`_ or Hatfield [#]_
103 L(z) \equiv \coth(z) - \frac{1}{z}
106 http://en.wikipedia.org/wiki/Brillouin_and_Langevin_functions#Langevin_Function
108 .. [#]: J.W. Hatfield and S.R. Quake
109 "Dynamic Properties of an Extended Polymer in Solution."
110 Phys. Rev. Lett. 1999.
111 doi: `10.1103/PhysRevLett.82.3548 <http://dx.doi.org/10.1103/PhysRevLett.82.3548>`
113 return coth(z) - 1.0/z
115 def inverse_Langevin(z, extreme=1.0 - 1e-8):
116 """Inverse Langevin function.
120 z : float or array_like
121 object whose inverse Langevin will be returned
123 value (close to one) for which we assume the inverse is +/-
124 infinity. This avoids problems with Newton-Raphson
125 convergence in regions with low slope.
129 >>> inverse_Langevin(Langevin(numpy.pi)) # doctest: +ELLIPSIS
131 >>> inverse_Langevin(Langevin(numpy.array([1,2,3]))) # doctest: +ELLIPSIS
136 We approximate the inverse Langevin via Newton's method
137 (:func:`scipy.optimize.newton`). For the starting point, we take
138 the first three terms from the Taylor series (from `Wikipedia`_).
141 L^{-1}(z) = 3z + \frac{9}{5}z^3 + \frac{297}{175}z^5 + \dots
144 http://en.wikipedia.org/wiki/Brillouin_and_Langevin_functions#Langevin_Function
147 ret = numpy.ndarray(shape=z.shape, dtype=z.dtype)
148 for i,z_i in enumerate(z):
149 ret[i] = inverse_Langevin(z_i)
155 # Catch stdout since sometimes the newton function prints exit
156 # messages to stdout, e.g. "Tolerance of %s reached".
158 tmp_stdout = StringIO.StringIO()
159 sys.stdout = tmp_stdout
160 ret = newton(func=lambda x: Langevin(x)-z,
161 x0=3*z + 9./5.*z**3 + 297./175.*z**5,)
163 output = tmp_stdout.getvalue()
166 def FJC_fn(x_data, T, L, a):
167 """The freely jointed chain model.
172 x values for which the WLC tension should be calculated.
174 temperature in Kelvin.
176 contour length in meters.
178 Kuhn length in meters.
187 >>> FJC_fn(numpy.array([1e-9, 5e-9, 10e-9], dtype=numpy.float),
188 ... T=300, L=15e-9, a=2.5e-10) # doctest: +ELLIPSIS
189 array([ 3.322...-12, 1.78...e-11, 4.889...e-11])
193 The freely jointed chain model is
196 F(x) = \frac{k_B T}{a} L^{-1}\left( \frac{x}{L} \right)
198 where :math:`L^{-1}` is the :func:`inverse_Langevin`, :math:`a` is
199 the Kuhn length, and :math:`L` is the contour length [#]_. For
200 the inverse formulation (:math:`x(F)`), see Ray and Akhremitchev [#]_.
203 .. [#]: J.W. Hatfield and S.R. Quake
204 "Dynamic Properties of an Extended Polymer in Solution."
205 Phys. Rev. Lett. 1999.
206 doi: `10.1103/PhysRevLett.82.3548 <http://dx.doi.org/10.1103/PhysRevLett.82.3548>`
208 .. [#] C. Ray and B.B. Akhremitchev.
209 `"Fitting Force Curves by the Extended Freely Jointed Chain Model" <http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf>`
211 return kB*T / a * inverse_Langevin(x_data/L)
213 class FJC (ModelFitter):
214 """Fit the data to a freely jointed chain.
218 Generate some example data
221 >>> L = 35e-9 # meters
222 >>> a = 2.5e-10 # meters
223 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
224 >>> d_data = FJC_fn(x_data, T=T, L=L, a=a)
226 Fit the example data with a two-parameter fit (`L` and `a`).
229 ... 'temperature (K)': T,
230 ... 'x data (m)': x_data,
232 >>> model = FJC(d_data, info=info, rescale=True)
233 >>> outqueue = Queue.Queue()
234 >>> Lp,a = model.fit(outqueue=outqueue)
235 >>> fit_info = outqueue.get(block=False)
236 >>> model.L(Lp) # doctest: +ELLIPSIS
238 >>> a # doctest: +ELLIPSIS
241 Fit the example data with a one-parameter fit (`L`). We introduce
242 some error in our fixed Kuhn length for fun.
244 >>> info['Kuhn length (m)'] = 2*a
245 >>> model = FJC(d_data, info=info, rescale=True)
246 >>> Lp = model.fit(outqueue=outqueue)
247 >>> fit_info = outqueue.get(block=False)
248 >>> model.L(Lp) # doctest: +ELLIPSIS
252 """To avoid invalid values of `L`, we reparametrize with `Lp`.
257 contour length in meters
262 rescaled version of `L`.
266 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
267 >>> model = FJC(data=x_data, info={'x data (m)':x_data})
268 >>> model.Lp(20e-9) # doctest: +ELLIPSIS
270 >>> model.Lp(19e-9) # doctest: +ELLIPSIS
272 >>> model.Lp(21e-9) # doctest: +ELLIPSIS
274 >>> model.Lp(100e-9) # doctest: +ELLIPSIS
279 The rescaling is designed to limit `L` to values strictly
280 greater than the maximum `x` data value, so we use
283 L = [\exp(L_p))+1] * x_\text{max}
285 which has the inverse
288 L_p = \ln(L/x_\text{max}-1)
290 This will obviously effect the interpretation of the fit's covariance matrix.
292 x_max = self.info['x data (m)'].max()
293 return numpy.log(L/x_max - 1)
296 """Inverse of :meth:`Lp`.
301 rescaled version of `L`
306 contour length in meters
310 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
311 >>> model = FJC(data=x_data, info={'x data (m)':x_data})
312 >>> model.L(model.Lp(21e-9)) # doctest: +ELLIPSIS
314 >>> model.L(model.Lp(100e-9)) # doctest: +ELLIPSIS
317 x_max = self.info['x data (m)'].max()
318 return (numpy.exp(Lp)+1)*x_max
320 def model(self, params):
321 """Extract the relavant arguments and use :func:`FJC_fn`.
325 params : list of floats
326 `[Lp, a]`, where the presence of `a` is optional.
328 # setup convenient aliases
329 T = self.info['temperature (K)']
330 x_data = self.info['x data (m)']
331 L = self.L(params[0])
335 a = self.info['Kuhn length (m)']
337 self._model_data[:] = FJC_fn(x_data, T, L, a)
338 return self._model_data
340 def guess_initial_params(self, outqueue=None):
341 """Guess initial fitting parameters.
346 A guess at the reparameterized contour length in meters.
348 A guess at the Kuhn length in meters. If `.info` has a
349 setting for `'Kuhn length (m)'`, `a` is not returned.
351 x_max = self.info['x data (m)'].max()
352 a_given = 'Kuhn length (m)' in self.info
354 a = self.info['Kuhn length (m)']
363 def guess_scale(self, params, outqueue=None):
364 """Guess parameter scales.
369 A guess at the reparameterized contour length scale in meters.
370 a_scale : float (optional)
371 A guess at the Kuhn length in meters. If the length of
372 `params` is less than 2, `a_scale` is not returned.
377 return [Lp_scale] + [x/10.0 for x in params[1:]]
380 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):
381 """Inverse poly(ethylene-glycol) adjusted extended FJC model.
385 >>> kwargs = {'T':300.0, 'N':1, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
386 ... 'dG':3.0, 'a':7e-10}
387 >>> inverse_FJC_PEG_fn(F_data=200e-12, **kwargs) # doctest: +ELLIPSIS
392 The function is that proposed by F. Oesterhelt, et al. [#]_.
395 x(F) = N_\text{S} \cdot \left[
397 \frac{L_\text{planar}}{
398 \exp\left(-\Delta G/k_B T\right) + 1}
399 + \frac{L_\text{helical}}{
400 \exp\left(+\Delta G/k_B T\right) + 1}
402 \coth\left(\frac{Fa}{k_B T}\right)
405 + \frac{F}{K_\text{S}}
407 where :math:`N_\text{S}` is the number of segments,
408 :math:`K_\text{S}` is the segment elasticicty,
409 :math:`L_\text{planar}` is the ttt contour length,
410 :math:`L_\text{helical}` is the ttg contour length,
411 :math:`\Delta G` is the Gibbs free energy difference
412 :math:`G_\text{planar}-G_\text{helical}`,
413 :math:`a` is the Kuhn length,
414 and :math:`F` is the chain tension.
416 .. [#]: F. Oesterhelt, M. Rief, and H.E. Gaub.
417 "Single molecule force spectroscopy by AFM indicates helical
418 structure of poly(ethylene-glycol) in water."
419 New Journal of Physics. 1999.
420 doi: `10.1088/1367-2630/1/1/006 <http://dx.doi.org/10.1088/1367-2630/1/1/006>`
424 g = (dG - F_data*(Lp-Lh)) / kBT
426 return N * ((Lp/(numpy.exp(-g)+1) + Lh/(numpy.exp(+g)+1)) * (coth(z)-1./z)
429 def FJC_PEG_fn(x_data, T, N, k, Lp, Lh, dG, a):
430 """Poly(ethylene-glycol) adjusted extended FJC model.
434 z : float or array_like
435 object whose inverse Langevin will be returned
437 value (close to one) for which we assume the inverse is +/-
438 infinity. This avoids problems with Newton-Raphson
439 convergence in regions with low slope.
443 >>> kwargs = {'T':300.0, 'N':1, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
444 ... 'dG':3.0, 'a':7e-10}
445 >>> FJC_PEG_fn(inverse_FJC_PEG_fn(1e-11, **kwargs), **kwargs) # doctest: +ELLIPSIS
447 >>> FJC_PEG_fn(inverse_FJC_PEG_fn(3.4e-10, **kwargs), **kwargs) # doctest: +ELLIPSIS
449 >>> FJC_PEG_fn(numpy.array([1e-10,2e-10,3e-10]), **kwargs) # doctest: +ELLIPSIS
450 array([ 5.207...e-12, 1.257...e-11, 3.636...e-11])
451 >>> kwargs['N'] = 123
452 >>> FJC_PEG_fn(numpy.array([1e-8,2e-8,3e-8]), **kwargs) # doctest: +ELLIPSIS
453 array([ 4.160...e-12, 9.302...e-12, 1.830...e-11])
457 We approximate the PEG adjusted eJPG via bisection
458 (:func:`scipy.optimize.bisect`) Because it is more stable than
459 Newton's algorithm. For the starting point, we use the standard
460 JPG function with an averaged contour length.
462 kwargs = {'T':T, 'N':N, 'k':k, 'Lp':Lp, 'Lh':Lh, 'dG':dG, 'a':a}
463 if is_iterable(x_data):
464 ret = numpy.ndarray(shape=x_data.shape, dtype=x_data.dtype)
465 for i,x in enumerate(x_data):
466 ret[i] = FJC_PEG_fn(x, **kwargs)
470 # Approximate with the simple FJC_fn()
473 while guess == numpy.inf:
474 guess = FJC_fn(x_data, T=T, L=L, a=a)
477 if False: # Bisection
478 while inverse_FJC_PEG_fn(guess, **kwargs) < x_data:
481 while inverse_FJC_PEG_fn(lower, **kwargs) > x_data:
484 fn = lambda f: inverse_FJC_PEG_fn(guess*abs(f), **kwargs) - x_data
485 return guess*abs(bisect(f=fn, a=lower/guess, b=1.0))
487 fn = lambda f: inverse_FJC_PEG_fn(guess*abs(f), **kwargs) - x_data
488 ret = guess*abs(newton(func=fn, x0=1.0))
491 class FJC_PEG (ModelFitter):
492 """Fit the data to an poly(ethylene-glycol) adjusted extended FJC.
496 Generate some example data
498 >>> kwargs = {'T':300.0, 'N':123, 'k':150.0, 'Lp':3.58e-10, 'Lh':2.8e-10,
499 ... 'dG':3.0, 'a':7e-10}
500 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
501 >>> d_data = FJC_PEG_fn(x_data, **kwargs)
503 Fit the example data with a two-parameter fit (`N` and `a`).
506 ... 'temperature (K)': kwargs['T'],
507 ... 'x data (m)': x_data,
508 ... 'section elasticity (N/m)': kwargs['k'],
509 ... 'planar section length (m)': kwargs['Lp'],
510 ... 'helical section length (m)': kwargs['Lh'],
511 ... 'Gibbs free energy difference (Gp - Gh) (kBT)': kwargs['dG'],
513 >>> model = FJC_PEG(d_data, info=info, rescale=True)
514 >>> outqueue = Queue.Queue()
515 >>> Nr,a = model.fit(outqueue=outqueue)
516 >>> fit_info = outqueue.get(block=False)
517 >>> model.L(Nr) # doctest: +ELLIPSIS
519 >>> a # doctest: +ELLIPSIS
522 Fit the example data with a one-parameter fit (`N`). We introduce
523 some error in our fixed Kuhn length for fun.
525 >>> info['Kuhn length (m)'] = 2*kwargs['a']
526 >>> model = FJC_PEG(d_data, info=info, rescale=True)
527 >>> Nr = model.fit(outqueue=outqueue)
528 >>> fit_info = outqueue.get(block=False)
529 >>> model.L(Nr) # doctest: +ELLIPSIS
533 """To avoid invalid values of `L`, we reparametrize with `Lr`.
538 contour length in meters
543 rescaled version of `L`.
547 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
548 >>> model = FJC_PEG(data=x_data, info={'x data (m)':x_data})
549 >>> model.Lr(20e-9) # doctest: +ELLIPSIS
551 >>> model.Lr(19e-9) # doctest: +ELLIPSIS
553 >>> model.Lr(21e-9) # doctest: +ELLIPSIS
555 >>> model.Lr(100e-9) # doctest: +ELLIPSIS
560 The rescaling is designed to limit `L` to values strictly
561 greater than zero, so we use
564 L = \exp(L_p) * x_\text{max}
566 which has the inverse
569 L_p = \ln(L/x_\text{max})
571 This will obviously effect the interpretation of the fit's covariance matrix.
573 x_max = self.info['x data (m)'].max()
574 return numpy.log(L/x_max)
577 """Inverse of :meth:`Lr`.
582 rescaled version of `L`
587 contour length in meters
591 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
592 >>> model = FJC_PEG(data=x_data, info={'x data (m)':x_data})
593 >>> model.L(model.Lr(21e-9)) # doctest: +ELLIPSIS
595 >>> model.L(model.Lr(100e-9)) # doctest: +ELLIPSIS
598 x_max = self.info['x data (m)'].max()
599 return numpy.exp(Lr)*x_max
603 'T': self.info['temperature (K)'],
604 'k': self.info['section elasticity (N/m)'],
605 'Lp': self.info['planar section length (m)'],
606 'Lh': self.info['helical section length (m)'],
607 'dG': self.info['Gibbs free energy difference (Gp - Gh) (kBT)'],
610 def model(self, params):
611 """Extract the relavant arguments and use :func:`FJC_PEG_fn`.
615 params : list of floats
616 `[N, a]`, where the presence of `a` is optional.
618 # setup convenient aliases
619 T = self.info['temperature (K)']
620 x_data = self.info['x data (m)']
621 N = self.L(params[0])
625 a = self.info['Kuhn length (m)']
626 kwargs = self._kwargs()
628 self._model_data[:] = FJC_PEG_fn(x_data, N=N, a=a, **kwargs)
629 return self._model_data
631 def guess_initial_params(self, outqueue=None):
632 """Guess initial fitting parameters.
637 A guess at the section count.
639 A guess at the Kuhn length in meters. If `.info` has a
640 setting for `'Kuhn length (m)'`, `a` is not returned.
642 x_max = self.info['x data (m)'].max()
643 a_given = 'Kuhn length (m)' in self.info
645 a = self.info['Kuhn length (m)']
648 f_max = self._data.max()
649 kwargs = self._kwargs()
651 x_section = inverse_FJC_PEG_fn(F_data=f_max, **kwargs)
652 N = x_max / x_section;
658 def guess_scale(self, params, outqueue=None):
659 """Guess parameter scales.
664 A guess at the section count scale in meters.
665 a_scale : float (optional)
666 A guess at the Kuhn length in meters. If the length of
667 `params` is less than 2, `a_scale` is not returned.
669 return [x/10.0 for x in params]
672 def WLC_fn(x_data, T, L, p):
673 """The worm like chain interpolation model.
678 x values for which the WLC tension should be calculated.
680 temperature in Kelvin.
682 contour length in meters.
684 persistence length in meters.
693 >>> WLC_fn(numpy.array([1e-9, 5e-9, 10e-9], dtype=numpy.float),
694 ... T=300, L=15e-9, p=2.5e-10) # doctest: +ELLIPSIS
695 array([ 1.717...e-12, 1.070...e-11, 4.418...e-11])
699 The function is the simple polynomial worm like chain
700 interpolation forumula proposed by C. Bustamante, et al. [#]_.
703 F(x) = \frac{k_B T}{p} \left[
704 \frac{1}{4}\left( \frac{1}{(1-x/L)^2} - 1 \right)
708 .. [#] C. Bustamante, J.F. Marko, E.D. Siggia, and S.Smith.
709 "Entropic elasticity of lambda-phage DNA."
711 doi: `10.1126/science.8079175 <http://dx.doi.org/10.1126/science.8079175>`
714 scaled_data = x_data / L
715 return a * (0.25*((1-scaled_data)**-2 - 1) + scaled_data)
717 class WLC (ModelFitter):
718 """Fit the data to a worm like chain.
722 Generate some example data
725 >>> L = 35e-9 # meters
726 >>> p = 2.5e-10 # meters
727 >>> x_data = numpy.linspace(10e-9, 30e-9, num=20)
728 >>> d_data = WLC_fn(x_data, T=T, L=L, p=p)
730 Fit the example data with a two-parameter fit (`L` and `p`).
733 ... 'temperature (K)': T,
734 ... 'x data (m)': x_data,
736 >>> model = WLC(d_data, info=info, rescale=True)
737 >>> outqueue = Queue.Queue()
738 >>> Lp,p = model.fit(outqueue=outqueue)
739 >>> fit_info = outqueue.get(block=False)
740 >>> model.L(Lp) # doctest: +ELLIPSIS
742 >>> p # doctest: +ELLIPSIS
745 Fit the example data with a one-parameter fit (`L`). We introduce
746 some error in our fixed persistence length for fun.
748 >>> info['persistence length (m)'] = 2*p
749 >>> model = WLC(d_data, info=info, rescale=True)
750 >>> Lp = model.fit(outqueue=outqueue)
751 >>> fit_info = outqueue.get(block=False)
752 >>> model.L(Lp) # doctest: +ELLIPSIS
756 """To avoid invalid values of `L`, we reparametrize with `Lp`.
761 contour length in meters
766 rescaled version of `L`.
770 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
771 >>> model = WLC(data=x_data, info={'x data (m)':x_data})
772 >>> model.Lp(20e-9) # doctest: +ELLIPSIS
774 >>> model.Lp(19e-9) # doctest: +ELLIPSIS
776 >>> model.Lp(21e-9) # doctest: +ELLIPSIS
778 >>> model.Lp(100e-9) # doctest: +ELLIPSIS
783 The rescaling is designed to limit `L` to values strictly
784 greater than the maximum `x` data value, so we use
787 L = [\exp(L_p))+1] * x_\text{max}
789 which has the inverse
792 L_p = \ln(L/x_\text{max}-1)
794 This will obviously effect the interpretation of the fit's covariance matrix.
796 x_max = self.info['x data (m)'].max()
797 return numpy.log(L/x_max - 1)
800 """Inverse of :meth:`Lp`.
805 rescaled version of `L`
810 contour length in meters
814 >>> x_data = numpy.linspace(1e-9, 20e-9, num=10)
815 >>> model = WLC(data=x_data, info={'x data (m)':x_data})
816 >>> model.L(model.Lp(21e-9)) # doctest: +ELLIPSIS
818 >>> model.L(model.Lp(100e-9)) # doctest: +ELLIPSIS
821 x_max = self.info['x data (m)'].max()
822 return (numpy.exp(Lp)+1)*x_max
824 def model(self, params):
825 """Extract the relavant arguments and use :func:`WLC_fn`.
829 params : list of floats
830 `[Lp, p]`, where the presence of `p` is optional.
832 # setup convenient aliases
833 T = self.info['temperature (K)']
834 x_data = self.info['x data (m)']
835 L = self.L(params[0])
839 p = self.info['persistence length (m)']
841 self._model_data[:] = WLC_fn(x_data, T, L, p)
842 return self._model_data
844 def guess_initial_params(self, outqueue=None):
845 """Guess initial fitting parameters.
850 A guess at the reparameterized contour length in meters.
852 A guess at the persistence length in meters. If `.info`
853 has a setting for `'persistence length (m)'`, `p` is not
856 x_max = self.info['x data (m)'].max()
857 p_given = 'persistence length (m)' in self.info
859 p = self.info['persistence length (m)']
868 def guess_scale(self, params, outqueue=None):
869 """Guess parameter scales.
874 A guess at the reparameterized contour length scale in meters.
875 p_scale : float (optional)
876 A guess at the persistence length in meters. If the
877 length of `params` is less than 2, `p_scale` is not
883 return [Lp_scale] + [x/10.0 for x in params[1:]]
886 class PolymerFitPlugin (Plugin):
887 """Polymer model (WLC, FJC, etc.) fitting.
890 super(PolymerFitPlugin, self).__init__(name='polymer_fit')
891 self._commands = [PolymerFitCommand(self),]
893 def dependencies(self):
896 def default_settings(self):
898 Setting(section=self.setting_section, help=self.__doc__),
899 Setting(section=self.setting_section,
900 option='polymer model',
902 help="Select the default polymer model for 'polymer fit'. See the documentation for descriptions of available algorithms."),
903 Setting(section=self.setting_section,
904 option='FJC Kuhn length',
905 value=4e-10, type='float',
906 help='Kuhn length in meters'),
907 Setting(section=self.setting_section,
908 option='FJC-PEG Kuhn length',
909 value=4e-10, type='float',
910 help='Kuhn length in meters'),
911 Setting(section=self.setting_section,
912 option='FJC-PEG elasticity',
913 value=150.0, type='float',
914 help='Elasticity of a PEG segment in Newtons per meter.'),
915 Setting(section=self.setting_section,
916 option='FJC-PEG delta G',
917 value=3.0, type='float',
918 help='Gibbs free energy difference between trans-trans-trans (ttt) and trans-trans-gauche (ttg) PEG states in units of kBT.'),
919 Setting(section=self.setting_section,
920 option='FJC-PEG L_helical',
921 value=2.8e-10, type='float',
922 help='Contour length of PEG in the ttg state.'),
923 Setting(section=self.setting_section,
924 option='FJC-PEG L_planar',
925 value=3.58e-10, type='float',
926 help='Contour length of PEG in the ttt state.'),
927 Setting(section=self.setting_section,
928 option='WLC persistence length',
929 value=4e-10, type='float',
930 help='Persistence length in meters'),
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.
961 help=self.__doc__, plugin=plugin)
963 def _run(self, hooke, inqueue, outqueue, params):
964 scale(hooke, params['curve'], params['block']) # TODO: is autoscaling a good idea? (explicit is better than implicit)
965 data = params['curve'].data[params['block']]
966 # HACK? rely on params['curve'] being bound to the local hooke
967 # playlist (i.e. not a copy, as you would get by passing a
968 # curve through the queue). Ugh. Stupid queues. As an
969 # alternative, we could pass lookup information through the
971 model = self.plugin.config['polymer model']
972 new = Data((data.shape[0], data.shape[1]+1), dtype=data.dtype)
973 new.info = copy.deepcopy(data.info)
975 new.info['columns'].append('%s tension (N)' % model) # TODO: WLC fit for each peak, etc.
976 z_data = data[:,data.info['columns'].index('surface distance (m)')]
977 d_data = data[:,data.info['columns'].index('deflection (N)')]
978 start,stop = params['bounds']
979 tension_data,ps = self.fit_polymer_model(
980 params['curve'], z_data, d_data, start, stop, outqueue)
981 new.info['%s polymer fit parameters' % model] = ps
982 new[:,-1] = tension_data
983 params['curve'].data[params['block']] = new
985 def fit_polymer_model(self, curve, z_data, d_data, start, stop,
987 """Railyard for the `fit_*_model` family.
989 Uses the `polymer model` configuration setting to call the
990 appropriate backend algorithm.
992 fn = getattr(self, 'fit_%s_model'
993 % self.plugin.config['polymer model'].replace('-','_'))
994 return fn(curve, z_data, d_data, start, stop, outqueue)
996 def fit_FJC_model(self, curve, z_data, d_data, start, stop,
998 """Fit the data with :class:`FJC`.
1000 """Fit the data with :class:`WLC`.
1003 'temperature (K)': self.plugin.config['temperature'],
1004 'x data (m)': z_data,
1006 if True: # TODO: optionally free persistence length
1007 info['Kuhn length (m)'] = (
1008 self.plugin.config['FJC Kuhn length'])
1009 model = FJC(d_data, info=info)
1010 queue = Queue.Queue()
1011 params = model.fit(outqueue=queue)
1012 if True: # TODO: if Kuhn length fixed
1014 a = info['Kuhn length (m)']
1019 T = info['temperature (K)']
1020 fit_info = queue.get(block=False)
1021 mask = numpy.zeros(z_data.shape, dtype=numpy.bool)
1022 mask[start:stop] = True
1023 return [FJC_fn(z_data, T=T, L=L, a=a) * mask,
1026 def fit_FJC_PEG_model(self, curve, z_data, d_data, start, stop,
1028 """Fit the data with :class:`FJC_PEG`.
1032 def fit_WLC_model(self, curve, z_data, d_data, start, stop,
1034 """Fit the data with :class:`WLC`.
1037 'temperature (K)': self.plugin.config['temperature'],
1038 'x data (m)': z_data,
1040 if True: # TODO: optionally free persistence length
1041 info['persistence length (m)'] = (
1042 self.plugin.config['WLC persistence length'])
1043 model = WLC(d_data, info=info)
1044 queue = Queue.Queue()
1045 params = model.fit(outqueue=queue)
1046 if True: # TODO: if persistence length fixed
1048 p = info['persistence length (m)']
1053 T = info['temperature (K)']
1054 fit_info = queue.get(block=False)
1055 mask = numpy.zeros(z_data.shape, dtype=numpy.bool)
1056 mask[start:stop] = True
1057 return [WLC_fn(z_data, T=T, L=L, p=p) * mask,
1062 # def dist2fit(self):
1063 # '''Calculates the average distance from data to fit, scaled by
1064 # the standard deviation of the free cantilever area (thermal