From 876332502a864c52d93cc0081b6f43498ed3eb0f Mon Sep 17 00:00:00 2001 From: Luis Fabregas <48292540+luisfabib@users.noreply.github.com> Date: Fri, 23 Oct 2020 20:44:48 +0200 Subject: [PATCH] Documentation update for 0.12.0 (#45) * docs: major edits, included license, and beginners guide section * fitsignal: experiment parameter are now properly printed when verbose (bug fix) * docs: rewritten uncertainty section * docs: minor edits and spelling corrections * docs: rewriten Basics section, edits on other pages * docs: search engine highlighting no longer introduces spaces (bug fix) * docs: minor edits * docs: adapted phenomenological background model equations to #43 * docs: minor edits in frontpage * docs: fixed example in fitparamodel docstring (#47) --- README.md | 2 +- deerlab/fitparamodel.py | 6 +- deerlab/fitsignal.py | 16 +- docsrc/source/_static/theme_override.css | 2 +- docsrc/source/basics.rst | 221 ++++++++------- docsrc/source/beginners_guide.rst | 317 ++++++++++++++++++++++ docsrc/source/changelog.rst | 7 +- docsrc/source/examples.rst | 2 + docsrc/source/firstscript.rst | 45 ++- docsrc/source/functions.rst | 4 +- docsrc/source/images/beginners_guide1.png | Bin 0 -> 68193 bytes docsrc/source/index.rst | 72 +++-- docsrc/source/installation.rst | 8 +- docsrc/source/license.rst | 35 +++ docsrc/source/models/bg_exp.rst | 2 +- docsrc/source/models/bg_hom3dex.rst | 6 +- docsrc/source/models/bg_poly1.rst | 2 +- docsrc/source/models/bg_poly2.rst | 2 +- docsrc/source/models/bg_poly3.rst | 2 +- docsrc/source/models/bg_prodstrexp.rst | 2 +- docsrc/source/models/bg_strexp.rst | 2 +- docsrc/source/models/bg_sumstrexp.rst | 2 +- docsrc/source/uncertainty.rst | 167 +++++++----- examples/README.rst | 4 +- 24 files changed, 684 insertions(+), 244 deletions(-) create mode 100644 docsrc/source/beginners_guide.rst create mode 100644 docsrc/source/images/beginners_guide1.png create mode 100644 docsrc/source/license.rst diff --git a/README.md b/README.md index 988bd8524..9d5d0d212 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ More details on the installation of DeerLab can be found [here](https://jeschkel A paper about DeerLab is currently submitted for publication. When you use DeerLab in your work, for now, please cite the preprint -> Fábregas Ibáñez, L., Jeschke, G., and Stoll, S.: DeerLab: A comprehensive software package for analyzing dipolar EPR spectroscopy data, Magn. Reson. Discuss., https://doi.org/10.5194/mr-2020-13, 2020 +> Fábregas Ibáñez, L., Jeschke, G., and Stoll, S.: a comprehensive software package for analyzing dipolar electron paramagnetic resonance spectroscopy data, Magn. Reson., 1, 209–224, 2020, doi.org/10.5194/mr-1-209-2020 Please check back frequently for updated publication information. diff --git a/deerlab/fitparamodel.py b/deerlab/fitparamodel.py index 3d05cc393..78ef5e997 100644 --- a/deerlab/fitparamodel.py +++ b/deerlab/fitparamodel.py @@ -87,7 +87,7 @@ def Vmodel(par): rmean,fwhm,lam,conc = par B = dl.bg_hom3d(t,conc) P = dl.dd_gauss(r,[rmean,fwhm]) - V = (1-lam + lam*K0@P)*B[:,np,newaxis] + V = (1-lam + lam*K0@P)*B return V # par: rmean fwhm lam conc @@ -106,8 +106,8 @@ def Vmodel(par): rmean,fwhm,lam1,lam,2conc = par B = dl.bg_hom3d(t,conc) P = dl.dd_gauss(r,[rmean,fwhm]) - V1 = (1-lam1 + lam1*K1@P)*B[:,np,newaxis] - V2 = (1-lam2 + lam2*K2@P)*B[:,np,newaxis] + V1 = (1-lam1 + lam1*K1@P)*B + V2 = (1-lam2 + lam2*K2@P)*B return V # par: rmean fwhm lam1 lam2 conc diff --git a/deerlab/fitsignal.py b/deerlab/fitsignal.py index 25fb5d418..6f833ebf0 100644 --- a/deerlab/fitsignal.py +++ b/deerlab/fitsignal.py @@ -137,7 +137,7 @@ def fitsignal(Vexp, t, r, dd_model='P', bg_model=bg_hom3d, ex_model=ex_4pdeer, Array of weighting coefficients for the individual signals in global fitting, the default is all weighted equally. If not specified all datasets are weighted equally. - regParam (str or scalar): + regparam (str or scalar): Method for the automatic selection of the optimal regularization parameter: * ``'lr'`` - L-curve minimum-radius method (LR) @@ -309,14 +309,14 @@ def multiPathwayModel(par): # Get pathway information if includeExperiment[iSignal]: - pathinfo = ex_model[iSignal](ex_par) + pathways = ex_model[iSignal](ex_par) else: - pathinfo = 1 + pathways = 1 # Compute the multipathway-background - B_ = dl.dipolarbackground(t[iSignal],pathinfo,Bfcn) + B_ = dl.dipolarbackground(t[iSignal],pathways,Bfcn) # Compute the multipathway-kernel - K_ = dl.dipolarkernel(t[iSignal],r,pathinfo) + K_ = dl.dipolarkernel(t[iSignal],r,pathways) Ks.append(K_*B_[:,np.newaxis]) Bs.append(B_) @@ -613,9 +613,9 @@ def _display_results(): ci = paruq['ex'][i].ci(95) else: ci = np.full((len(parfit['ex'][i]),2),np.nan) - for j in range(len(parfit['ex'][i])): - c = parfit['ex'][i][j] - print(pstr.format('exparam',j,c,ci[j,0],ci[j,1],info['Parameters'][j],info['Units'][j])) + for j in range(len(parfit['ex'][i])): + c = parfit['ex'][i][j] + print(pstr.format('exparam',j,c,ci[j,0],ci[j,1],info['Parameters'][j],info['Units'][j])) print('----------------------------------------------------------------------------') diff --git a/docsrc/source/_static/theme_override.css b/docsrc/source/_static/theme_override.css index afb334665..a10a80db6 100644 --- a/docsrc/source/_static/theme_override.css +++ b/docsrc/source/_static/theme_override.css @@ -3288,7 +3288,7 @@ footer span.commit code,footer span.commit .rst-content tt,.rst-content footer s background: #F1C40F; display: inline-block; font-weight: bold; - padding: 0 6px} + padding: 0 0px} .rst-content .footnote-reference,.rst-content .citation-reference { vertical-align: baseline; position: relative; diff --git a/docsrc/source/basics.rst b/docsrc/source/basics.rst index 41766a6b1..e2f750f3b 100644 --- a/docsrc/source/basics.rst +++ b/docsrc/source/basics.rst @@ -3,159 +3,176 @@ Basics DeerLab relies on a few central quantities. They include distance distributions and model functions, background and their model functions, time-domain signals, and kernels. They are described in this section. -All functions in DeerLab use the same units: All distances are in units of **nanometers**, and all times in units of **microseconds**. +All functions in DeerLab use the same units: all distances are in units of **nanometers**, and all times in units of **microseconds**. Distance distributions ------------------------------------------- + A distance distribution `P(r)` between two spins is represented by a pair of vectors: a distance vector ``r`` (in nanometers) and a vector of densities ``P`` + (in inverse nanometers). The distance-vector ``r`` can be a linearly or non-linearly increasing vector, and it cannot have negative values. + The elements ``P[i]`` are the distance distribution values at ``r[i]`` and are all strictly non-negative. Outside of the range defined by ``r``, + the distribution ``P`` is assumed to be zero, i.e. the distribution is truncated to the range ``r``. + The distance distribution ``P`` is normalized such that the integral over the range of the provided ``r`` equals one: -A distance distribution between two spins is represented by a pair of vectors: a distance vector ``r`` (in nanometers) and a vector of densities ``P`` (in inverse nanometers). ``r`` can be a linearly or non-linearly increasing vector, and it cannot have negative values. The elements in ``P`` must be nonnegative. ``P[i]`` is the distribution value at ``r[i]``. Outside the range of ``r``, ``P`` is assumed to be zero, i.e. the distribution is truncated to the range ``r``. ``P`` is normalized such that the integral over the range of the provided ``r`` equals one: + .. math:: \int P(r) \mathrm{d}r = 1 -.. code-block:: python + Furthermore, DeerLab distinguishes between *non-parametric* and *parametric* distance distributions. - np.trapz(P,r) # integral of P over r, gives 1 + Non-parametric distance distributions + These provide the more general definition of distance distributions. They have no particular shape and are represented by the vectors ``P`` and ``r``. + For example, you can generate ``P`` and ``r`` by an external program for spin label rotamer modeling. + + In least-squares fitting, non-parametric distance distributions are preferred over parametric distance distributions, since + they make fewer assumptions about the distribution and are more flexible. They introduce less bias. + Parametric distance distributions + These have specific shapes that depend on a few parameters. DeerLab provides many parametric distance distribution :ref:`model functions `. All + these functions start with the prefix ``dd_`` (``dd`` stands for "distance distribution"). They take a distance vector ``r`` and a parameter vector ``param`` + as inputs and return the distance distribution as a vector ``P``. Here is an example: :: -DeerLab distinguishes between **non-parametric** and **parametric** distance distributions. + r = np.linspace(1.5,6,200) # distance-vector, in nm + rmean = 3 # nm, Gaussian mean distance + sigma = 0.2 # nm, Gaussian standard deviation + P = dl.dd_gauss(r,[rmean, sigma]) # Parametric Gaussian distribution + plt.plot(r,P) -**Non-parametric distance distributions** are the more general from. They have no particular shape and are represented by vectors ``P`` and ``r``. For example, you can generate ``P`` and ``r`` by an external program for spin label rotamer modeling. Non-parametric distance distributions are returned in least-squares fitting. In least-squares fitting, non-parametric distance distributions are preferred over parametric distance distributions, since they make fewer assumptions about the distribution and are more flexible. They introduce less bias. + To programmatically get information on a particular distance distribution :ref:`model functions ` and its parameters, call the function without input arguments :: -**Parametric distance distributions** have specific shapes that depend on a few parameters. DeerLab provides many parametric :ref:`distance distribution model functions `. All these functions start with the prefix ``dd_`` (``dd`` stands for "distance distibution"). They take a distance vector ``r`` and a parameter vector ``param`` as inputs and return the distance distribution as a vector ``P``. Here is an example: + info = dl.dd_gauss() # obtain information on model and parameters -.. code-block:: python - - r = np.linspace(1.5,6,201) # distance, in nanometers - P = dl.dd_gauss(r,[3 0.2]) # Gaussian distribution, center 3 nm, fwhm 0.5 nm - plt.plot(r,P) - -The first line generates a distance axis ``r`` as a row vector. The second line generates the distance distribution ``P`` as a vector defined over ``r``. The function ``dd_gauss`` is a distance distribution model function provided by DeerLab. It takes as inputs the distance axis and a two-element parameter array (center and full width at half maximum) and returns the calculated distance distribution consisting of one Gaussian, truncated to the range of ``r`` and normalized. - -To programmatically get information on a particular :ref:`distance distribution model functions ` and its parameters, call the function without input arguments - -.. code-block:: python - - info = dl.dd_gauss() # obtain information on model and parameters - -DeerLab provides a wide range of :ref:`parametric distribution models` that fall into several groups. There are models that are based on basis functions and linear combinations thereof. Gaussian and 3D-Rice functions are the most important ones. To use a single Gaussian distribution, use :ref:`dd_gauss`. To combine 2, 3, etc. Gaussians distributions, use :ref:`dd_gauss2`, :ref:`dd_gauss3`, etc. Similarly, for 3D-Rice distributions, use :ref:`dd_rice`, :ref:`dd_rice2`, etc. A second group of distance distribution models represent three-dimensional disordered segmented objects such as proteins and other polymers: :ref:`dd_wormchain`, :ref:`dd_wormgauss`, :ref:`dd_randcoil`. A third group provides models for distributions of labels in simple confined spaces such as spheres and spherical shells. Finally, DeerLab provides a series of distance distribution models that represent simple geometric shapes. These models are worthless for practical purposes, but are useful as toy distributions for methods development: :ref:`dd_circle`, :ref:`dd_triangle`, and :ref:`dd_uniform`. + which returns a dictionary ``info`` containing the the names of the model parameters, and other quantities such as their built-in lower and upper boundaries. + + DeerLab provides a wide range of :ref:`parametric distribution models` that fall into several groups. .. _bgmodels: +Dipolar background + In DeerLab, all inter-molecular contributions to the dipolar signal (i.e. the signal due to randomly distributed spins in the sample that are not part of the + spin-labeled protein or object) are referred to as the dipolar background. There is a wide collection of parametric models that can be used to model the background. + All these background :ref:`model functions ` start with the prefix ``bg_``, they take the time axis vector ``t`` (in microseconds), and a parameter vector ``param``. + as inputs. The output is a background vector ``B`` defined over ``t``. To get information on the model and its parameters, call the function without inputs: :: -Backgrounds --------------------------------------------- - -DeerLab includes a collection of parametric models that can be used to model the background or inter-molecular signal, i.e. the signal due to randomly distributed spins in the sample that are not part of the spin-labeled protein or object. All these :ref:`background model functions ` start with the prefix ``bg_`` and have the same calling syntax. The inputs a time axis vector ``t`` (in microseconds), a parameter vector ``param``, and a modulation amplitude ``lambda`` (between 0 and 1) . The length of ``param``, and the meaning of the elements, depends on the particular model. If ``lambda`` is not provided, it is set to one. The output is a background decay vector ``B``, defined over ``t``. - -Here is an example: - -.. code-block:: python + info = dl.bg_hom3d() # obtain information on model and parameters - t = np.linspace(-0.1,3,201) # time, in microseconds - lam = 0.4 # modulation depth - conc = 200 # spin concentration, in uM - B = dl.bg_hom3d(t,conc,lam) # homogeneous 3D background - plt.plot(t,B) + DeerLab's :ref:`background models` fall into two categories, physical and phenomenological: -The first line generate the desired time axis. The second line gives the modulation depth, and the third gives the spin concentration (in micromolar). Both are inputs to the background function ``bg_hom3d``, which calculates a decay due to a homogeneous three-dimensional distribution of spins and returns it in ``B``. + Physical background models + Describe particular distributions of spin labels in space and depend on physical parameters such as spin concentration, exclusion distances, and fractal dimensionality. + The most common background model is :ref:`bg_hom3d`, which describes the signal due to a homogeneous three-dimensional distribution of spins of a given concentration. + A background due to a homogeneous distribution of spins in fractal dimensions is available with :ref:`bg_homfractal`, and excluded-volume effects can be accounted for using + :ref:`bg_hom3dex` to model the background. -To get information on the model and its parameters, call the function without inputs: + In addition to ``t`` and the model parameters ``param`` physical background model functions take the dipolar pathway amplitude ``lambda`` as a third input, for example :: -.. code-block:: python + t = np.linspace(-0.1,4,200) # time, in microseconds + lam = 0.4 # modulation depth + conc = 70 # spin concentration, in uM + B = dl.bg_hom3d(t,conc,lam) # homogeneous 3D background + plt.plot(t,B) - info = dl.bg_hom3d() # obtain information on model and parameters + Phenomenological background models + Represent various mathematical functions that are intended to *mimic* the background decay, without reference to a particular spatial distribution of spins. The parameters + of these models do no have a direct physical meaning. Some examples include :ref:`bg_exp`, which models the background decay as a simple exponential function, or :ref:`bg_strexp` + which model the background decay as a stretched exponential function. + Phenomenological background model functions just take ``t`` and the model parameters ``param`` as input, for example :: -DeerLab's :ref:`background models` fall into two categories, physical and phenomenological. **Physical models** describe particular distributions of spin labels in space. These models depend on physical parameters such as spin concentration, exclusion distances, and dimensionality. The most common one is :ref:`bg_hom3d`, which describes the signal due to a homogeneous three-dimensional distribution of spins of a given concentration. A homogeneous distribution in a fractal dimensions is available with :ref:`bg_homfractal`, and excluded-volume effects can be modelled using :ref:`bg_hom3dex`. **Phenomenological models** represent various mathematical functions that are intended to mimick the background decay, without reference to a particular spatial distribution of spins. The parameters of these models do no have direct physical meaning. In general, it is preferable to use the physical instead of phenomenological models. + t = np.linspace(-0.1,4,200) # time, in microseconds + kappa = 0.35 # decay rate, in inverse microseconds + B = dl.bg_exp(t,kappa) # exponential background + plt.plot(t,B) + + In general, it is preferable to use the physical instead of phenomenological models. .. _exmodels: Experiments -------------------------------------------------------------- - -DeerLab supports a wide range of dipolar EPR experiments. Experiments differ in the number of dipolar modulation components and their refocusing times. For each type of supported dipolar EPR experiment, there is a dedicated :ref:`experiment model function` starting with ``ex_``. These functions take as inputs the time axis ``t`` and an array of parameters characterizing the experiment. As output, they return an array containing information about the dipolar pathways of the experiment model. - -For example, the model function representing the typical model for a 4-pulse DEER signal is ``ex_4pdeer``: - -.. code-block:: python - - t = np.linspace(0,3,151) - lam = 0.3; - pathways = dl.ex_4pdeer(t,lam) - -The returned output is - -.. code-block:: python - - pathways =[[0.7], [0.3, 0 ]] + DeerLab supports a wide range of dipolar EPR experiments. Different experiments differ in their modulated dipolar pathways. Each of these pathways leads to a dipolar modulation + contribution to the dipolar signal, with a certain amplitude and refocusing times. A dipolar signal can be defined as a combination of an unmodulated contribution of and a contribution + from all modulated pathways, which can de defined by their amplitude, refocusing time, and harmonic. For each type of supported dipolar EPR experiment, there is a dedicated experiment + :ref:` model function` starting with ``ex_``, which models the dipolar pathways for that specific experiment. These functions take an array of parameters characterizing + the experiment. As output, they return an array containing information about the dipolar pathways of the experiment model. -Each nested list holds information about one pathway. The first element is modulation amplitude, and the second element is the refocusing point. In the above example, the first list shows a pathway with amplitude 0.7 and no refocusing time, indicating that it represents the unmodulated contribution. The pathway of the second list shows amplitude of 0.3 and refocusing time 0, i.e. this is the primary dipolar pathway. + For example, the model function representing the typical model for a 4-pulse DEER signal is ``ex_4pdeer``: :: -Kernel matrices --------------------------------------------- + t = np.linspace(0,3,151) + lam = 0.3; + pathways = dl.ex_4pdeer(t,lam) -One of the core functions of DeerLab is ``dipolarkernel``. It provides the kernel that provides the connection between the distance distribution and the time-domain signal. + The returned output ``pathways`` is a list of pathways information :: -.. code-block:: python + pathways =[[0.7], [0.3, 0]] - t = np.linspace(0,3,301) # time axis, in us - r = np.linspace(2,7,301) # distance axis, in nm - K0 = dl.dipolarkernel(t,r) # dipolar kernel matrix + Each nested list holds information about one pathway. The first element is modulation amplitude, and the second element is the refocusing point. + In the above example, the first list shows a pathway with amplitude 0.7 and no refocusing time, indicating that it represents the unmodulated contribution. + The pathway of the second list shows amplitude of 0.3 and refocusing time 0, i.e. this is the primary dipolar pathway. -To obtain the time-domain signal due to a distribution ``P``, use -.. code-block:: python - - V = K0@P -The above is the most elementary kernel, giving a time-domain signal without any background decay, and with a single dipolar evolution function centered at time zero and with modulation depth of 1. +Dipolar Kernel + One of the core functions of DeerLab is ``dipolarkernel``. It constructs the kernel that provides the connection between the distance distribution and the time-domain dipolar signal + via the Fredholm integral equation -The kernel can also include the background and the modulation depth. Then, the multiplication of ``P`` by ``K`` will return the complete time-domain signal. Here is an example: + .. math:: V(t) = \int K(t,r)P(r) \mathrm{d}r -.. code-block:: python + The most simple dipolar kernel just requires the time-vector ``t`` and distance-vector ``r`` :: - lam = 0.4 - B = dl.bg_hom3d(t,200,lam) - K = dl.dipolarkernel(t,r,lam,B) - V = K@P + t = np.linspace(0,6,300) # time axis, in us + r = np.linspace(2,7,300) # distance axis, in nm + K0 = dl.dipolarkernel(t,r) # dipolar kernel matrix -The function ``dipolarkernel`` also has options to add an excitation bandwidth limitation, to select the internal calculation method, and more. + To calculate the dipolar signal corresponding to a distance distribution ``P`` according to the equation above, use :: + + V = K0@P -It is not necessary to precompute the background decay. Instead, provide ``dipolarkernel`` with a lambda-callable to a function that takes only time and modulation depth and encapsulates all other parameters. + The above ``K0`` is the most elementary kernel, giving a dipolar signal without any background decay, and with a single dipolar evolution function centered at time zero and with modulation depth of 1. -.. code-block:: python - - bg = lambda t,lam: dl.bg_hom3d(t,200,lam) # define function for background - K = dl.dipolarkernel(t,r,lam,bg) + The kernel can also account for the background and the dipolar pathways. Then, operation ``V=K@P`` will return the complete time-domain dipolar signal. Here is an example for a 4-pulse DEER signal :: -The use of function handles is central to DeerLab, especially when fitting experimental data. + lam = 0.4 + B = dl.bg_hom3d(t,200,lam) + K = dl.dipolarkernel(t,r,lam,B) + V = K@P + plt.plot(t,V) + When accounting for more than one dipolar pathway, the different refocusing times and modulation amplitudes must be provided to ``dipolarkernel``. Additionally, the background must be provided as a callable + function that takes only time and modulation amplitude and encapsulates all other parameters. For example, for a 5-pulse DEER signal :: -Time-domain signals --------------------------------------------- + Lam0 = 0.5 # amplitude of unmodulated component + lam1 = 0.4 # amplitude of primary pathway + lam2 = 0.1 # amplitude of secondary pathway + T02 = 3.1 # refocusing time of secondary pathway, in us + pathways = dl.ex_5pdeer([Lam0,lam1,lam2,T02]) # dipolar pathways of 5-pulse DEER experiment + Bfcn = lambda t,lam: dl.bg_hom3d(t,200,lam) # define function for background + K = dl.dipolarkernel(t,r,pathways,Bfcn) # 5-pulse DEER dipolar kernel + + The function ``dipolarkernel`` also has :ref:`options` to add an excitation bandwidth limitation, to select the internal calculation method, and more. -To generate complete time-domain signals from a distance distribution and a background decay, use the function ``dipolarkernel`` and apply it to the distance distribution. -.. code-block:: python +Dipolar signals + Dipolar signals are the results of the many different dipolar EPR spectroscopy experiments. They represent the data from which distance distributions can be infered. + DeerLab provides the tools for simulating dipolar signals originating from different experiments. Note that these simulations are not based on spin dynamics simulations, + but rather on theoretical analytical treatments of such problems. - K = dl.dipolarkernel(t,r,lam,B) # generate dipolar kernel - V = K@P # generate dipolar signal - plt.plot(t,V) + Simulations in DeerLab can be easily achieved, as mentioned above, via a Fredholm integral using the correct dipolar kernel. To generate complete time-domain signals from + a distance distribution and a background decay, use the function ``dipolarkernel`` and apply it to the distance distribution. :: -It is possible to add noise to simulated data by using the ``whitegaussnoise`` function: + K = dl.dipolarkernel(t,r,lam,B) # generate dipolar kernel + V = K@P # generate dipolar signal + plt.plot(t,V) -.. code-block:: python + It is possible to add noise to simulated data by using the ``whitegaussnoise`` function: :: - sigma = 0.05 # noise level - V = K@P + dl.whitegaussnoise(t,sigma) # add some noise + sigma = 0.05 # noise level + V = K@P + dl.whitegaussnoise(t,sigma) # add some noise -With this, uncorrelated Gaussian noise with standard deviation given as ``sigma`` is added to the noise-free signal. + With this, uncorrelated Gaussian noise with standard deviation given as ``sigma`` is added to the noise-free signal. -Adding a phase rotation is also possible, yielding a complex-valued signal with non-zero imaginary component. The phase shift on the noise has to be taken into account too: + Adding a phase rotation is also possible, yielding a complex-valued signal with non-zero imaginary component. The phase shift on the noise has to be taken into account too: :: - phase = np.pi/4 # phase shift, radians - V = K@P*exp(-1j*phase) # add a phase shift - rnoise = dl.whitegaussnoise(t,sigma) # real-component noise - inoise = dl.whitegaussnoise(t,sigma) # imaginary-component noise - V = V + rnoise + inoise # complex-valued noisy signal + phase = np.pi/4 # phase shift, radians + V = K@P*exp(-1j*phase) # add a phase shift + rnoise = dl.whitegaussnoise(t,sigma) # real-component noise + inoise = dl.whitegaussnoise(t,sigma) # imaginary-component noise + V = V + rnoise + inoise # complex-valued noisy signal diff --git a/docsrc/source/beginners_guide.rst b/docsrc/source/beginners_guide.rst new file mode 100644 index 000000000..6e99860b0 --- /dev/null +++ b/docsrc/source/beginners_guide.rst @@ -0,0 +1,317 @@ +.. _beginners_guide: + +Getting Started +============================================================ + +This is the introductory guide to DeerLab. If you have comments or suggestions, please don’t hesitate to reach out! + +-------- + +Installing DeerLab +------------------- +To install DeerLab, you need a valid Python installation on your computer. You can download it from the `official Python website `_ There are many useful and thorough tutorials out there to guide +you through the installation and setup (see `here `_ for example). Make sure you install one of the compatible Python versions listed :ref:`here `. + +If you have Python installed you can download and install DeerLab with: :: + + pip install deerlab + + +If you want more detailed instructions for installing DeerLab on your operating system, you can :ref:`find all details here `. + +-------- + +Importing DeerLab +------------------- + +Every time you want to use a package, library, or module in your code or script, you first need to make it accessible. +In order to start using all the functions available in DeerLab, you need to import it. This can be easily done with this import statement: :: + + import deerlab as dl + +We usually shorten DeerLab as ``dl`` in order to keep the code standardized so that everybody knows that these are DeerLab functions by +looking at your code, it also shortens the amount of text you need to type. + +Other packages need to be imported as well, typically used in scientific work are :: + + import numpy as np # NumPy: vectors, matrices, linear algebra + import matplotlib.pyplot as plt # MatPlotLib: plotting + +-------- + +Getting to know Numpy +---------------------- + +`NumPy `_ is the fundamental package for scientific computing in Python. It is a Python library +that provides a multidimensional array object, various derived objects (such as masked arrays and matrices), and an +assortment of routines for fast operations on arrays, including mathematical, logical, shape manipulation, sorting, selecting, I/O, +discrete Fourier transforms, basic linear algebra, basic statistical operations, +random simulation and much more. + +Most mathematical operations in DeerLab are based on Numpy, and all numerical outputs returned by DeerLab functions are Numpy data types. +It is recommendable, to invest a short amount of time to familiarize yourself +with some `basic Numpy concepts `_. + +On the other hand, if you have previously worked with the MATLAB, here is a good guide of +`Numpy for MATLAB users `_. + + +What’s the difference between a Python list and a NumPy array? +--------------------------------------------------------------- +In DeerLab, you will notice that many functions take lists, NumPy-arrays or both as inputs. While a Python list can contain different data +types within a single list, all of the elements in a NumPy array (a so called ndarray) should share the same data type. For example: :: + + a = [1,2,3] # is a list-type + b = np.array([1,2,3]) # is an ndarray-type + +and the elements on such variables can be accessed by their indices in the exact same way: :: + + print(a[0]) # print the first element of the list + print(b[2]) # print the third element of the ndarray + +Note that Python is a 0-indexed language, meaning that the first element of any list, array,... is indexed with 0. + +-------- + +Loading Spectrometer Files +-------------------------- + +Dipolar EPR spectroscopy experiments, saved at the spectrometers, come in a variety of spectrometer-specific formats. DeerLab provides a +function ``deerload`` that can read most spectrometer formats, can read both 1D and 2D datasets, both real- or complex-valued. If you are familiar +with the Easyspin package, ``deerload`` is equivalent to the load function ``eprload`` of that package. + +First, you need to locate the spectrometer files you want to load and where the script you are writing is. Let's assume that your script is called ``myscript.py`` +and that you experiment data is called ``DEERexperiment.DTA`` and you directories look like this: :: + + home + |-----experiments + | | + | |---DEERexperiment.DSC + | +---DEERexperiment.DTA + | + +-----scripts + | + +---myscript.py + +From the perspective of your script, you have two ways to reference the data files: using absolute or relative paths, in the example above: :: + + filepath = '/home/experiments/DEERexperiment.DTA' # absolute path + filepath = '../../experiments/DEERexperiment.DTA' # relative path + +Then you just need to call ``deerload`` and specify the path of the file. If you look into the :ref:`reference documentation ` +for ``deerload`` you will see that the function returns two output variables: the first one ``t`` is the time-axis of your experiment, and the second one +``V`` is the raw experimental data as exported by your spectrometer. :: + + t,V = dl.deerload(filepath) # load experimental data + +Both ``t`` and ``V`` will be returned as (at least) 1D-Numpy arrays with ``N`` elements. If you need or want to load more than one dataset, you +just need to specify the path of the other file and load them into different variables: :: + + filepath1 = '/home/experiments/DEER4p_experiment.DTA' # absolute path to 1st file + filepath2 = '/home/experiments/DEER5p_experiment.DTA' # absolute path to 2nd file + t1,V2 = dl.deerload(filepath1) # load 1st set of experimental data + t2,V2 = dl.deerload(filepath2) # load 2nd set of experimental data + +Note that ``deerload`` attempts to return the experiment time-axis ``t`` in the correct units (microseconds), but might not be able to do so for all file formats. + +--------------- + +Pre-Processing +--------------- + +Raw experimental dipolar EPR spectroscopy data comes in a crude state and must be undergo a series of pre-processing steps prior to any fitting. + +Phase correction + Experimental dipolar signals are usually complex, the first step if to perform a phase correction which will minimize the imaginary component and maximize the real component. + If the signal is not complex-valued this step can be omitted. The phase correction function ``correctphase`` takes the complex-valued dipolar signal, and returns the real-valued + dipolar signal, whose real-part has been optimized: :: + + V = dl.correctphase(V) # phase correction of experimental data + + It is important to note that all other DeerLab functions do not behave properly or might lead to errors if the dipolar signal is + complex-valued. + +Zero-time correction + Depending on the file format and the spectrometer, the values of the experiment time-axis might be defined differently. Frequently, in commercial spectrometers these are defined + as absolute timings, with the zero-time corresponding to the first element ``t[0]=0``. In dipolar EPR spectroscopy models, we define the zero-time as that time, where the dipolar + signal has its largest contribution or amplitude. + The function ``correctzerotime`` takes the time-axis ``t`` and dipolar signal ``V`` and optimizes the timing that corresponds to the signal's maximum (taking into account noise in + the data). The function returns the time-axis with the correct zero-time: :: + + t = dl.correctzerotime(V,t) # zero-time correction + + Note that the zero-time can technically be fitted if included as a model parameter. However, this requires much more expertise with DeerLab and its theoretical principles. + +In both cases, the corrections are based on optimization approaches. Should that fail for a specific case, the phase and zero-time needed for correction can also be manually specified. +See the documentation for :ref:`correctphase` and :ref:`correctzerotime`. + +Note that all analysis functions in DeeLab assume the dipolar signals and their corresponding time-axes to be properly pre-processed. + +--------------- + +Fitting Dipolar Signals +----------------------- + +DeerLab provides a wide range of functionality to analyze experimental dipolar EPR data using least-squares fitting. While there are multiple analysis functions of varying application, +generality and difficulty, the main fit function of DeerLab is ``fitsignal``. This function can fit non-parametric and parametric distance distributions, and all model parameters +(e.g. modulation depth, background decay rate, spin concentration, etc.) in a one-step analysis. It also provides uncertainty estimates for all fitted quantities (see later). + +Picking the right model +*********************** + +DeerLab provides a very flexible framework to model dipolar signals originating from many different dipolar EPR spectroscopy experiments. Choosing a model that properly describes your +is of paramount importance. In ``fitsignal`` the main structure of the model is already defined, and the decision is divided into four decisions that define the model: + * **Distance range**: Also called the distance-axis, is the range of distances where the distribution is defined. + * **Distribution model**: Describes the distance distributions in a parametric (e.g. a Gaussian distribution) or non-parametric way. + * **Background model**: Describes the dipolar background arising from the inter-molecular contributions during an experiment. + * **Experiment model**: Describes specific properties of a dipolar signal that are dependent on the experiment used to acquire the data. + +Now you must decide for a model. As described above, there are four separate choices to take + +(1) **Choosing a distance range** + + The distance range :math:`[r_\mathrm{min},r_\mathrm{max}]` is an important choice, as any distance distribution is truncated to this range, i.e. :math:`P(r)=0` for + :math:`rr_\mathrm{max}`. The lower limit of the distance range is determined by the bandwidth of the pulses, and also on the time increment. + Typically, 1.5 nm is a reasonable choice. The upper limit depends on the length of the experimental time trace and on the distances in your sample. The number of points + in ``r`` is usually set equal to the number of time points. Such a distance-axis is usually defined as ``r`` is most easily defined using the ``linspace`` function from NumPy: :: + + r = np.linspace(1.5,6,len(t)) # define distance range form 1.5nm to 6nm with the same amount of points as t + +(2) **Choosing a distribution model** + + Generally, a non-parametric distribution is preferred (specified using the string ``'P'`` in ``fitsignal``), i.e. a distribution where each element :math:`P_i` of the distribution is a parameter. + Non-parametric distributions are obtained via methods such as Tikhonov regularization. If there are reasons to believe that the distance distribution has a + specific shape (e.g. Gaussian, Rice, random-coil, etc.), use the associated parametric distance distribution model from the :ref:`list of available models`. + +(3) **Choosing a background model** + + Typically, a background arising from a homogenous 3D-distribution of spins is sufficient. The associated parametric model function is :ref:`bg_hom3d`. In some cases, depending on + the properties of your sample, other background models might be needed could be needed, such as background arising from distributions of spins in fractal dimensions or when + accounting for volume-exclusion effects. In such cases, use the associated parametric background models from the :ref:`list of available models`. + +(4) **Choosing an experiment model** + + This decision should be based on the experiment you used to acquire the data. In the case of 4-pulse DEER data, when analyzing a standard 4-pulse DEER signal without 2+1 component + at the end, use :ref:`ex_4pdeer`. If the 2+1 components should be fitted as well, use the :ref:`ex_ovl4pdeer` model. There are :experiment models for more complicated signals, such + as 5-pulse DEER or 7-pulse DEER. Use the associated parametric experiment models from the :ref:`list of available models`. + +When your model does not have one of the these components, i.e. no background, no foreground, etc. the corresponding submodels can be set to ``None`` to specify the choice. + +Here is a list of examples with different situations and what the proper choices of model are: + +=========================================================================== ==================== ================== ================== + Description Distribution model Background model Experiment model +=========================================================================== ==================== ================== ================== +4pDEER signal with homogenous 3D background and non-parametric distribution ``'P'`` ``bg_hom3d`` ``ex_4pdeer`` +4pDEER signal with homogenous 3D background and Gaussian distribution ``dd_gauss`` ``bg_hom3d`` ``ex_4pdeer`` +Dipolar evolution function with a random-coil distribution ``dd_randcoil`` ``None`` ``None`` +4pDEER signal with no background and non-parametric distribution ``'P'`` ``None`` ``ex_4pdeer`` +5pDEER signal with fractal background and non-parametric distribution ``'P'`` ``bg_homfractal`` ``ex_5pdeer`` +=========================================================================== ==================== ================== ================== + + +Starting the fit +***************** + +Once you have chosen your model, you need to specify it to ``fitsignal``. The function takes several inputs: the experimental dipolar signal ``V`` and its time=axis ``t``, followed by all four +model components described above, distance-axis ``r`` the distribution, background and experiment models. + +The models that have an associated parametric function, e.g. ``bg_hom3d``, must be passed directly as inputs to ``fitsignal``. In Python functions are treated as common numerical variables in that +they can be passed as inputs to other functions. + +For example, a 4pDEER signal with homogenous 3D background and Tikhonov regularization can be fitted via :: + + fit = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer) # 4pDEER fit + +For the other examples in the table above, the call to ``fitsignal`` would look like this + +=========================================================================== ================================================================ + Description Fit +=========================================================================== ================================================================ +4pDEER signal with homogenous 3D background and non-parametric distribution ``fit = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer)`` +4pDEER signal with homogenous 3D background and Gaussian distribution ``fit = dl.fitsignal(V,t,r,dl.gauss,dl.bg_hom3d,dl.ex_4pdeer)`` +Dipolar evolution function with a random-coil distribution ``fit = dl.fitsignal(V,t,r,dl.randcoil,None,None)`` +4pDEER signal with no background and non-parametric distribution ``fit = dl.fitsignal(V,t,r,'P',None,dl.ex_4pdeer)`` +5pDEER signal with fractal background and non-parametric distribution ``fit = dl.fitsignal(V,t,r,'P',dl.bg_homfractal,dl.ex_5pdeer)`` +=========================================================================== ================================================================ + +fitsignal uses a least-squares fitting algorithm to determine the optimal distance distribution, background parameters, and experiment parameters that fit the experiment data. To determine the non-parametric +distribution, it internally uses Tikhnonov regularization with a regularization parameter optimized using the Akaike Information Criterion (AIC). All settings related to the fit can be adjusted by using the +appropriate keywords, see the :ref:`reference documentation ` for details. For example, the regularization parameter used in the Tikhonov regularization could be manually adjusted by using the ``regparam`` +keyword: :: + + fit1 = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer, regparam='aic') # regularization with Akaike information criterion + fit2 = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer, regparam='gcv') # regularization with Generalized Cross-Validation + fit3 = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer, regparam=0.05) # regularization with fixed regularization parameter + +After the function has found a solution if will return a variable ``fit``. This fit is an object with different fields containing all quantities of interest with the fit results. +A detailed list of these quantities can be found again in the :ref:`reference` for ``fitsignal``. + + +Displaying the results +********************** + +For just a quick display of the results, you can use the ``plot()`` method of the ``fit`` object that will display a figure with you experimental data, the corresponding fit, and the fit of the distance distribution +including confidence bands. :: + + fit.plot() # display results + + +.. image:: ./images/beginners_guide1.png + :width: 450px + +It is important to note that these confidence bands are covariance-based and might represent an overestimation of the true uncertainty on the results (see :ref:`uncertainty` for further details). These bands indicate +regions in which the true values might be contained with a certain probability. It is important to always report fitted distance distributions with confidence bands. + +The ``fit`` output contains some interesting information as well, for example: + + * ``fit.V``, ``fit.B``, and ``fit.P`` contain the arrays of the fitted dipolar signal, background and distance distribution, respectively. + * ``fit.exparam``, ``fit.bgparam``, and ``fit.ddparam`` contain the arrays of fitted model parameters for each of the experiment, background and distribution models. + * ``fit.scale`` contains the fitted arbitrary scale of the dipolar signal. + +In addition to the distance distribution fit, it is important to check and report the fitted model parameters and their uncertainties. While this can be computed manually, a summary can be easily requested by enabling the +``verbose`` option of ``fitsignal``. By using :: + + fit = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer,verbose=True) # 4pDEER fit and report parameter fits + +after the function has fitted your data, it will print a summary the results, including goodness-of-fit estimators +and fitted parameters with uncertainties, for example + +.. code-block:: text + + ----------------------------------------------------------------------------------- + Goodness of fit + Vexp[0]: chi2 = 1.008066 RMSD = 0.019209 + ----------------------------------------------------------------------------------- + Fitted parameters and 95%-confidence intervals + Vfit[0]: + bgparam[0]: 292.02402 (230.33576, 353.71227) Concentration of pumped spins (μM) + exparam[0]: 0.5097663 (0.4809968, 0.5385358) Modulation depth () + ----------------------------------------------------------------------------------- + +where there are no distribution parameters ``ddparam`` due to the distribution model being non-parametric. + +------------ + +Summary +-------- + +Summarizing, this would be an example script to load experimental data, pre-process the signal, and fit a 4-pulse DEER model with a non-parametric distance distribution: :: + + import numpy as np + import deerlab as dl + + # Data import + filepath = '/home/experiments/DEERexperiment.DTA' # file path + t,V = dl.deerload(filepath) # load experimental data + + # Pre-processing + V = dl.correctphase(V) # phase correction of experimental data + t = dl.correctzerotime(V,t) # zero-time correction + + # Distance range + r = np.linspace(1.5,6,len(t)) # define distance range form 1.5nm to 6nm with the same amount of points as t + + # Fit + fit = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer,verbose=True) # 4pDEER fit + fit.plot() # display results diff --git a/docsrc/source/changelog.rst b/docsrc/source/changelog.rst index 6e161554f..7164012e4 100644 --- a/docsrc/source/changelog.rst +++ b/docsrc/source/changelog.rst @@ -1,6 +1,7 @@ +.. _changelog: ----------- -Changelog ----------- +-------------- +Release Notes +-------------- .. mdinclude:: ../../CHANGELOG.md diff --git a/docsrc/source/examples.rst b/docsrc/source/examples.rst index 996b9bd22..ad9966232 100644 --- a/docsrc/source/examples.rst +++ b/docsrc/source/examples.rst @@ -1 +1,3 @@ +.. _examples: + .. include:: ./auto_examples/index.rst diff --git a/docsrc/source/firstscript.rst b/docsrc/source/firstscript.rst index 67864be00..78c3b4cc7 100644 --- a/docsrc/source/firstscript.rst +++ b/docsrc/source/firstscript.rst @@ -1,28 +1,47 @@ -A first script +Quickstart tutorial ============================================================ -Here is a first script that shows how DeerLab works. It simulates a noisy DEER data trace and then fits it. +This tutorial is intended as a overview of the fundamentals of DeerLab. If you are not familiar with DeerLab or +dipolar EPR spectroscopy data analysis, then the :ref:`Beginner's Guide ` might give you a better +insight on the basics for using DeerLab. -We start by importing DeerLab - -.. code-block:: python +This tutorial will assume that DeerLab is already installed on your computer. DeerLab always needs to be imported prior +to calling any of its functions as well as any other packages :: import deerlab as dl # we always use 'dl' as the local name for DeerLab + import numpy as np # NumPy: vectors, matrices, linear algebra + import matplotlib.pyplot as plt # MatPlotLib: plotting -and other needed packages: +Loading experimental data +-------------------------- -.. code-block:: python - import numpy as np # NumPy: vectors, matrices, linear algebra - import matplotlib.pyplot as plt # MatPlotLib: plotting -Then, we generate a distance distribution consisting of a single Gaussian: + +Simulating dipolar signals +--------------------------- + +DeerLab is provides the tools for simulating dipolar signals originating from different experiments. Note that these simulations +are not based on spin dynamics simulations but rather on theoretical analytical treatments of such problems. + +Simulations in DeerLab can be easily achieved via the Fredholm integral which for a general dipolar signal reads + +.. math:: V(t) = \int K(t,r)P(r) \mathrm{d}r + +which in matrix form reads + +.. math:: \boldsymbol{V} = \boldsymbol{K}\boldsymbol{P} + +To simulate a dipolar signal you need first to define a distance distribution. This distribution can be the result of a fit, come +from molecular dynamics simulations, etc. or you can assume a certain shape for the distribution and use one of the parametric models +available in DeerLab. For example, to simulate a Gaussian distance distribution use the ``dd_gauss`` model function .. code-block:: python - N = 201 # number of points - r = np.linspace(1.5,7,N) # distance range, in nanometers - P = dl.dd_gauss(r,[3.5, 0.15]) # single-Gaussian distance distribution + r = np.linspace(1.5,7,200) # distance range, in nm + rmean = 3.5 # nm + sigma = 0.3 # nm + P = dl.dd_gauss(r,[rmean, sigma]) # single-Gaussian distance distribution Next, we calculate the background decay function due to a homogeneus 3D distribution of spins: diff --git a/docsrc/source/functions.rst b/docsrc/source/functions.rst index 2f1048352..586b6ec82 100644 --- a/docsrc/source/functions.rst +++ b/docsrc/source/functions.rst @@ -4,9 +4,7 @@ Functions --------------------- -This is the official documentation for the DeerLab toolbox functions. The following list contains the names of the different function and a brief description of their functionality. The parametric model functions are listed in separate sections. - - +The following list contains the names of the different function and a brief description of their functionality. The parametric model functions are listed in separate sections. Modeling ========================================= diff --git a/docsrc/source/images/beginners_guide1.png b/docsrc/source/images/beginners_guide1.png new file mode 100644 index 0000000000000000000000000000000000000000..63f4baee00c160028fb4638f1c137f8795e46ba3 GIT binary patch literal 68193 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≠i1KF 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 U6I1465by_` or at :ref:`reference ` for specific DeerLab functions. -
-
- +------- + +What's new? + Check the :ref:`changelog` for the changes introduced in the latest releases. + +------- + +Citing DeerLab + When you use DeerLab in your work, please cite the following publication: + + .. raw:: html + + -
-

DeerLab: a comprehensive software package for analyzing dipolar electron paramagnetic resonance spectroscopy data

- Luis Fábregas Ibáñez, Gunnar Jeschke, Stefan Stoll
- Magn. Reson., 1, 209–224, 2020
- doi.org/10.5194/mr-1-209-2020 + + +------- + +Contributing + .. raw:: html + +
+
+ +
+
+ DeerLab is hosted on Github : + +
-
-.. toctree:: - :hidden: - :caption: Installation - :maxdepth: 0 - ./installation +.. include:: changelog.rst + :start-after: Version .. toctree:: :hidden: :caption: User Guide :maxdepth: 0 - ./firstscript + ./installation ./basics - ./preprocess - ./fitting + ./beginners_guide ./uncertainty ./examples @@ -57,6 +86,7 @@ When you use DeerLab in your work, please cite the following publication: :caption: Others :maxdepth: 1 + ./license ./funding ./changelog diff --git a/docsrc/source/installation.rst b/docsrc/source/installation.rst index 96a3afcd9..340c07dcb 100644 --- a/docsrc/source/installation.rst +++ b/docsrc/source/installation.rst @@ -1,4 +1,6 @@ -Installation instructions +.. _installation: + +Installation ========================= Requirements @@ -11,11 +13,11 @@ DeerLab requires one of the following versions of the Python interpreter which can be downloaded from the `official Python distribution `_. -For Windows systems it is imporant to ensure that the **Install launcher for all users (recommended)** and the **Add Python 3.x to PATH** checkboxes at the bottom are checked. To test if python has been succesfully installed, open a terminal window and run the command:: +For Windows systems it is important to ensure that the **Install launcher for all users (recommended)** and the **Add Python 3.x to PATH** checkboxes at the bottom are checked. To test if python has been successfully installed, open a terminal window and run the command:: python -wihch should open the Python interface as well as display the installed Python version. To exit use the ``exit()`` command. +which should open the Python interface as well as display the installed Python version. To exit use the ``exit()`` command. Installing pre-built DeerLab ----------------------------- diff --git a/docsrc/source/license.rst b/docsrc/source/license.rst new file mode 100644 index 000000000..6ef53face --- /dev/null +++ b/docsrc/source/license.rst @@ -0,0 +1,35 @@ +License +------- + +.. raw:: html + +
+
+

MIT License

+
+
+ +
+
+
+ + +Copyright (c) 2019-2020 Luis Fabregas, Stefan Stoll, and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docsrc/source/models/bg_exp.rst b/docsrc/source/models/bg_exp.rst index 69eafd2e6..11e923a25 100644 --- a/docsrc/source/models/bg_exp.rst +++ b/docsrc/source/models/bg_exp.rst @@ -12,7 +12,7 @@ Model .. math:: - B(t) = \exp\left(-\lambda\kappa \vert t \vert\right) + B(t) = \exp\left(-\kappa \vert t \vert\right) ============== =============== ============= ============= ============= ================================ Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/models/bg_hom3dex.rst b/docsrc/source/models/bg_hom3dex.rst index 27b9614fb..222d9a65a 100644 --- a/docsrc/source/models/bg_hom3dex.rst +++ b/docsrc/source/models/bg_hom3dex.rst @@ -17,13 +17,11 @@ This implements a hard-shell excluded-volume model, with pumped spin concentrati The expression for this model is -.. math:: - B(t) = \mathrm{exp}\left(-\frac{8\pi^2}{9\sqrt{3}}\alpha(R) \lambda c D |t|\right)` +.. math:: B(t) = \mathrm{exp}\left(-\frac{8\pi^2}{9\sqrt{3}}\alpha(R) \lambda c D |t|\right)` where :math:`c` is the spin concentration (entered in spins/m\ :sup:`3` into this expression) and :math:`D` is the dipolar constant -.. math:: - D = \frac{\mu_0}{4\pi}\frac{(g_\mathrm{e}\mu_\mathrm{B})^2}{\hbar} +.. math:: D = \frac{\mu_0}{4\pi}\frac{(g_\mathrm{e}\mu_\mathrm{B})^2}{\hbar} The function :math:`\alpha(R)` of the exclusion distance :math:`R` captures the excluded-volume effect. It is a smooth function, but doesn't have an analytical representation. For details, see `Kattnig et al, J.Phys.Chem.B 2013, 117, 16542 `_. diff --git a/docsrc/source/models/bg_poly1.rst b/docsrc/source/models/bg_poly1.rst index 0a17af713..a6582b71c 100644 --- a/docsrc/source/models/bg_poly1.rst +++ b/docsrc/source/models/bg_poly1.rst @@ -10,7 +10,7 @@ Model ========================================= -:math:`B(t) = p_0 + p_1(\lambda t)` +:math:`B(t) = p_0 + p_1 t` ============== =============== ============= ============= ============= ==================================== Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/models/bg_poly2.rst b/docsrc/source/models/bg_poly2.rst index dc59b4ba4..31c912cfc 100644 --- a/docsrc/source/models/bg_poly2.rst +++ b/docsrc/source/models/bg_poly2.rst @@ -11,7 +11,7 @@ Model ========================================= -:math:`B(t) = p_0 + p_1(\lambda t) + p_2(\lambda t)^2` +:math:`B(t) = p_0 + p_1 t + p_2t^2` ============== =============== ============= ============= ============= =================================== Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/models/bg_poly3.rst b/docsrc/source/models/bg_poly3.rst index e2e3321d7..0e92cdb02 100644 --- a/docsrc/source/models/bg_poly3.rst +++ b/docsrc/source/models/bg_poly3.rst @@ -11,7 +11,7 @@ Model ========================================= -:math:`B(t) = p_0 + p_1(\lambda t) + p_2(\lambda t)^2 + p_3(\lambda t)^3` +:math:`B(t) = p_0 + p_1t + p_2t^2 + p_3t^3` ============== =============== ============= ============= ============= =================================== Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/models/bg_prodstrexp.rst b/docsrc/source/models/bg_prodstrexp.rst index 5e99a942e..4a2ff7164 100644 --- a/docsrc/source/models/bg_prodstrexp.rst +++ b/docsrc/source/models/bg_prodstrexp.rst @@ -11,7 +11,7 @@ Model ========================================= -:math:`B(t) = \exp\left(-\lambda\kappa_1 \vert t \vert^{d_1}\right) \exp\left(-\lambda\kappa_2 \vert t\vert^{d_2}\right)` +:math:`B(t) = \exp\left(-\kappa_1 \vert t \vert^{d_1}\right) \exp\left(-\kappa_2 \vert t\vert^{d_2}\right)` ============== ================= ============= ============= ============= ================================= Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/models/bg_strexp.rst b/docsrc/source/models/bg_strexp.rst index 78ba1768c..529e40c0e 100644 --- a/docsrc/source/models/bg_strexp.rst +++ b/docsrc/source/models/bg_strexp.rst @@ -13,7 +13,7 @@ Model .. math:: - B(t) = \exp\left(-\lambda \kappa \vert t\vert^{d}\right) + B(t) = \exp\left(-\kappa \vert t\vert^{d}\right) ============== ================= ============= ============= ============= ================================= Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/models/bg_sumstrexp.rst b/docsrc/source/models/bg_sumstrexp.rst index 959754153..88a5ce4d4 100644 --- a/docsrc/source/models/bg_sumstrexp.rst +++ b/docsrc/source/models/bg_sumstrexp.rst @@ -10,7 +10,7 @@ Model ========================================= -:math:`B(t) = A_1\exp \left(-\lambda\kappa_1 \vert t \vert^{d_1}\right) + (1-A_1)\exp\left(-\lambda\kappa_2 \vert t \vert^{d_2}\right)` +:math:`B(t) = A_1\exp \left(-\kappa_1 \vert t \vert^{d_1}\right) + (1-A_1)\exp\left(-\kappa_2 \vert t \vert^{d_2}\right)` ============== ================= ============= ============= ============= ======================================== Variable Symbol Start Value Lower bound Upper bound Description diff --git a/docsrc/source/uncertainty.rst b/docsrc/source/uncertainty.rst index 234b9eb89..9f7e63eae 100644 --- a/docsrc/source/uncertainty.rst +++ b/docsrc/source/uncertainty.rst @@ -1,109 +1,132 @@ Uncertainty ========================================= -After fitting experimental data with a distance distribution, a background, and a modulation depth, it is important to assess the uncertainty in the fitted parameters. +The presence of any level of noise in the data will result in uncertainty of any fitted quantities based on +that data. This means that despite the fit returning single values for these quantities, there is a distributions +of values for each quantity which can describe the data. -DeerLab provides uncertainty estimates for all parameters in the form of confidence intervals (CIs). They can be calculated in two ways: either using the covariance matrix, or using bootstrap. The first method is fast, but is not entirely accurate, whereas bootstrap is much slower, but more robust. All confidence intervals provided by DeerLab functions describe the range of values which might contain the ground truth with a ceratin probability. +These so-called uncertainty distributions are important to determine the accuracy of the results. DeerLab provides a +complete framework to calculate them for any kind of fitted quantity and to derive related quantities such as confidence +intervals. Confidence intervals (CI) provide a simple mean to report on the uncertainty, by defining a range of values within +which the true solution might reside with a given probability. -Covariance Uncertainty Quantification ------------------------------------------- - -Along with every fit, functions like ``fitsignal`` return confidence intervals for the fitted parameters and the fitted distributions. For example, +DeerLab provides two means to estimate the uncertainty of fitted quantities: **covariance-matrix uncertainty**; a fast method, but not entirely accurate, and **bootstrapped uncertainty**; a much slower method, but more robust and accurate. -.. code-block:: python +The ``UncertQuant`` Object +--------------------------- - fit = dl.fitsignal(Vexp,t,r) - Puq = fit.Puncert # uncertainty information about the distance distribution - Buq = fit.Buncert # Uncertainty information about the background - lamuq = fit.exparamUncert # Uncertainty information about the modulation depth - +The uncertainty estimation framework in DeerLab is contained into :ref:`UncertQuant` (Uncertainty Quantification) objects. +These objects are variables returned by any function which calculates some kind of uncertainty estimate, and contain different +fields with all quantities of interested related to uncertainty (see details :ref:`here `). Some of the most basic +attributes is ``UncertQuant.type``, which identifies whether the uncertainty was estimated by +covariance-based or bootstrap methods. -The variables ``Puq``, ``Buq`` and ``lamuq`` are uncertainty quantification objects :ref:`UncertQuant` which contain the full uncertainty information of the corresponding variables, calculated using the standard method based on the variance-covariance matrices. +One ``UncertQuant`` can contain the uncertainty of multiple parameters and information on their correlations. For example, if ``fitsignal`` is used to fit a +4-pulse DEER signal without background :: -The confidence intervals (at any confidence level) can be calculated by using the ``ci()`` method, e.g. for the 50% and 95% confidence intervals of the fitted distance distribution are calculated as: + fit = dl.fitsignal(Vexp,t,r,'P',None,ex_4pdeer) # Fit a 4-DEER form factor + Puq = fit.Puncert # Uncertainty quantification of fitted distance distribution + lamuq = fit.exparamUncert # Uncertainty quantification of fitted modulation depth -.. code-block:: python +it will return a fit with a non-parametric distribution of N-points ``fit.P``, and the fitted modulation depth as ``fit.exparam``, then the output ``fit.Puncert`` will be a ``UncertQuant`` object with the information on all +N-distribution elements and the output ``fit.exparamUncert`` will contain just the uncertainty of the modulation depth. - Pfit_ci50 = Puq.ci(50) # 50%-confidence intervals of Pfit - Pfit_ci95 = Puq.ci(95) # 95%-confidence intervals of Pfit +Confidence intervals + As mentioned above, confidence intervals are the most practical quantities to report uncertainty of fit results. The ``UncertQuant.ci()`` is a method + that takes the coverage or probability to be covered, and generates the confidence intervals. For the example above, if you want to generate the 95%-confidence + intervals you need to call :: -Uncertainty can also be propagated to dependent models. For example, assume that we have fitted a single Gaussian distance distribution with ``rmean`` and ``fwhm`` as parameters. Now, we can propagate the uncertainty in the fit of ``rmean`` and ``fwhm`` to the resulting Gaussian distance distribution. This can be done via the ``propagate()`` method, this will create the uncertainty quantification for the fitted distribution: + P_ci = Puq.ci(95) # 95%-confidence intervals of the distance distribution + lam_ci = lamuq.ci(95) # 95%-confidence intervals of the modulation depth parameter -.. code-block:: python + With this method you can calculate different confidence intervals for the same quantity, for example :: - # parfit = [rmean, fwhm] - ddmodel = lambda parfit: dl.dd_gauss(r,parfit) - - # Get the fitted model - Pfit = ddmodel(parfit) - - # Propagate the error in the parameters to the model - lb = zeros(numel(Pfit),1) # Non-negativity constraint of the distance distribution - ub = [] # No upper bounds - Puq = paruq.propagate(ddmodel,lb) + P_ci95 = Puq.ci(95) # 95%-confidence intervals of the distance distribution + P_ci75 = Puq.ci(75) # 75%-confidence intervals of the distance distribution + P_ci50 = Puq.ci(50) # 50%-confidence intervals of the distance distribution - # Get the 95%-confidence intervals of the fitted distribution - Pci95 = Puq.ci(95) + The confidence intervals are always returned as a ``Nx2``-array, where each one of the ``N`` parameters has two values, the lower and upper boundaries of the confidence interval. :: + P_ci[:,0] # lower bound of the 95%-CI of the distance distribution + P_ci[:,1] # upper bound of the 95%-CI of the distance distribution + lam_ci[0] # lower bound of the 95%-CI of the modulation depth + lam_ci[1] # upper bound of the 95%-CI of the modulation depth -Theoretical assumptions of covariance-based uncertainty analysis: - - The uncertainty in the fitted parameters is approximated by a Gaussian distribution. - - The mean of this Gaussian is assumed to be the fitted value and its width by the diagonal elements of the covariance matrix. - - All parameters are assumed to be unconstrained. - - -Bootstrap Uncertainty Quantification ------------------------------------------- - -A more thorough way of assessing parameter uncertainty is bootstrap. In this method, many additional synthetic datasets are generated from the given experimental data and the fitted model and are fitted individually. This yields an ensemble of parameter fits that is analyzed statistically to provide information about the scatter. +Uncertainty distributions + A more complete description of the uncertainty are the uncertainty distributions for the fit parameter. These can be requested from the + the ``UncertQuant.pardist`` method. Using ``UncertQuant.pardist(n)`` will return the parameter uncertainty probability density function + corresponding to the fit parameter with index ``n`` and its corresponding abscissa values. For example, :: -Here is an example for a parametric model: + P5_dist,P5_vals = Puq.pardit(5) # Uncertainty distribution of fit.P[5] values + lam_dist,lam_vals = lamuq.pardist(0) # Uncertainty distribution of modulation depth values -.. code-block:: python +Uncertainty Propagation + Sometimes you might fit some parameters and get their corresponding uncertainty analysis, but you might be interested in knowing what the uncertainty + is of a model that depends on those parameters. Analyzing the effect that the uncertainty of a set of parameters has on a dependent function is called + uncertainty or error propagation. + + The ``UncertQuant.propagate`` method provides a simple interface for propagation uncertainty to dependent models or functions. The method just takes as input the function or model to which you + want to propagate the uncertainty. Additionally if the model has some boundaries (e.g. a distance distribution with non-negative values) you can specify the lower and upper bounds as additional inputs. + + For example, if you fitted a Gaussian distribution with ``fitparamodel`` and obtained the uncertainty quantification for its parameters :: - def fitfcn(V): - Vmodel = lambda par: K@dl.dd_gauss(r,par) - fit = dl.fitparamodel(V,Vmodel,r,K) - return fit.param + model = lambda p: K@dl.dd_gauss(r,p) # Parametric model depending on p=[rmean,sigma] + fit = dl.fitparamodel(V,model) # Fit dipolar signal with parametric model + Pfit = dl.dd_gauss(r,fit.param) # Fitted distance distribution - bootuq = dl.bootan(fitfcn,Vexp,Vfit,samples=1000,verbose=True); + paramuq = fit.paramuq # Uncertainty quantification for fitted parameters -The output ``bootuq`` is again a :ref:`UncertQuant` object that can be used as described above to evaluate confidence intervals at different confidence levels, e.g the 50% and 95% confidence intervals: + you can propagate the uncertainty to the model ``dd_gauss`` as follows :: -.. code-block:: python + lb = np.zeros_like(Pfit) # Non-negativity constraint of the distance distribution + Puq = paruq.propagate(ddmodel,lb) # Uncertainty quantification of fitted distribution - parfit_ci50 = bootuq.ci(50) - parfit_ci95 = bootuq.ci(95) + Pci95 = Puq.ci(95) # 95%-confidence intervals of fitted distance distribution -The bootstrapped distributions for each parameter can be accessed by using the ``pardist()`` method, e.g.if the modulation depth is the second fit parameter: -.. code-block:: python - moddepth_dist = bootuq.pardist(2); +Covariance Uncertainty Quantification +------------------------------------------ +Covariance-baed uncertainty is the fastest method for uncertainty quantification available in DeerLab. Due to its readiness all fit functions +in DeerLab return covariance-based ``UncertQuant`` objects for all quantities fitted. -Here is an example for a model with a non-parametric distribution: +This method estimates the uncertainty based on the curvature of the optimization surface. During optimization, when a minimum is found, the curvature +of the parameter hypersurface at that point is measured. A very sharp minimum means that there is less uncertainty in the found result, whereas a shallow +minimum will indicate a larger uncertainty in the results. +This curvature is determined via the Jacobian of the optimization objective function and the corresponding covariance matrix. The method then assumes the uncertainty +distributions of all parameters to be normally distributed, to be centered at the fitted values, and their variance to be given by the diagonal elements of the covariance matrix. +In addition, the method does not take into consideration boundaries of the parameters, i.e. they are assumed to be unconstrained. However, in DeerLab confidence intervals and +uncertainty distributions are clipped at the boundaries as it is common practice. -.. code-block:: python +All these assumptions and approximation can lead to a less accurate estimate of the uncertainty. It is common for covariance-based confidence intervals to be overestimated and broader +than the bootstrapped equivalents. However, their cheap computation cost makes them ideal for immediate estimations of uncertainty. +Bootstrap Uncertainty Quantification +------------------------------------------ - def fitfcn(V): - fit = dl.fitsignal(V,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer) - return fit.P, fit.bgparam, fit.exparam +A more thorough way of assessing parameter uncertainty is bootstrap. In this method, many additional synthetic datasets (called bootstrap samples) are generated from the given experimental data +and fitted in the same way as the original data. This means that the method samples the uncertainty arising when the same analysis is repeated for multiple noise realizations. +When all bootstrap samples have been analyzed, for each fitted quantity a distribution of values are obtained, which are taken as the uncertainty distributions for that quantity. - bootuq = dl.bootan(fitfcn,Vexp,Vfit,samples=100,verbose=True) +Due to the need of repeating the fit for multiple bootstrap samples, this method can take long to estimate the uncertainty. However, since this method does not rely on +any assumptions, the bootstrap uncertainty estimation are considered some of the most accurate, provided that enough bootstrap samples are taken. While a reduced number of +samples (50-100) can be used when testing workflows or new scripts, for conclusive analysis the minimum standard is to use at least about 1000 bootstrap samples. -To plot the resulting 95% and 50% confidence interval for the non-parametric distance distribution, use +In DeerLab you can calculate bootstrap uncertainty estimates using the :ref:`bootan` function. The function takes the experimental data, the fit, and the analysis function. This analysis +function must be a function that takes the experimental data and returns the quantities whose uncertainties are to be calculated. For examples, to bootstrap the distance distribution and +parameters obtained from a 4-pulse DEER fit using ``fitsignal`` you could use the following :: -.. code-block:: python + fit = dl.fitsignal(Vexp,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer) + Vfit = fit.V # Fitted signal - Pci50 = bootuq.ci(50) - Pci95 = bootuq.ci(95) - - import matplotlib.pyplot as plt - plt.plot(r,Pfit,'k') - plt.fill_between(r,Pci50[:,0]; Pci50[:,1],color='r',alpha=0.5) - plt.fill_between(r,Pci95[:,0]; Pci95[:,1],color='r',alpha=0.2) + # Define the function to be bootstrapped + def fitfcn(Vexp): + fit = dl.fitsignal(Vexp,t,r,'P',dl.bg_hom3d,dl.ex_4pdeer) + return fit.P, fit.exparam, fit.bgparam # bootstrap the fitted distance distribution, modulation depth and spin concentration + + bootuq = dl.bootan(fitfcn,Vexp,Vfit,samples=1000,verbose=True) # Bootstrap uncertainty quantification -Assumptions: - - ``Vfit`` is a good fit of the experimental data ``Vexp``. +The output of ``bootuq`` is an ``UncertQuant`` object equivalent to the ones obtained for covariance-based uncertainty analysis. If the fit procedure is slow or +costly, it is very recommendable to use the ``cores`` option to assign multiple CPU cores to the bootstrapping, in order to run different bootstrap samples in parallel, speeding +up the uncertainty estimation. diff --git a/examples/README.rst b/examples/README.rst index fc7fd1c2a..7895cce4f 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -1,6 +1,4 @@ Examples ========================================= -Here is a collection of example scripts for the use of DeerLab. - -.. note:: Couldn't find what you were looking for? `Add a request `_ for a new example/tutorial and it will considered for the next release. +Here is a collection of example scripts for the use of DeerLab. \ No newline at end of file