diff --git a/VERSION b/VERSION index 795460fc..0f1acbd5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.1.0 +v1.1.2 diff --git a/deerlab/dd_models.py b/deerlab/dd_models.py index afcd381d..dc32cbaa 100644 --- a/deerlab/dd_models.py +++ b/deerlab/dd_models.py @@ -125,7 +125,7 @@ def _multirice3dfun(r,nu,sig): r = r.T s2 = sig**2 I_scaled = spc.ive(n/2-1, nu*r/s2) - P = nu**(n/2-1)/s2*r**(n/2)*np.exp(-(r**2+nu**2)/(2*s2)+nu*r/s2)*I_scaled + P = nu**(1-n/2)/s2*r**(n/2)*np.exp(-(r**2+nu**2)/(2*s2)+nu*r/s2)*I_scaled P[P<0] = 0 # Normalization diff --git a/deerlab/solvers.py b/deerlab/solvers.py index 8c1c6a29..1052d7b3 100644 --- a/deerlab/solvers.py +++ b/deerlab/solvers.py @@ -426,6 +426,11 @@ def snlls(y, Amodel, par0=None, lb=None, ub=None, lbl=None, ubl=None, nnlsSolver * ``0`` : Work silently (default). * ``1`` : Display progress including the non-linear least-squares' solver termination report. * ``2`` : Display progress including the non-linear least-squares' solver iteration details. + + .. caution:: + + The verbose output from the non-linear least-squares solver uses a different definition of the cost function than DeerLab. + DeerLab uses the sum of squares of the residuals divided by the number of data points, whereas the non-linear least-squares solver uses the sum of squares of the residuals divided by 2. Returns ------- @@ -594,7 +599,8 @@ def linear_problem(y,A,optimize_alpha,alpha): if optimize_alpha: - output = dl.selregparam((y-yfrozen)[mask], Ared[mask,:], linSolver, regparam, + linsolver_result = lambda AtA, Aty: parseResult(linSolver(AtA, Aty)) + output = dl.selregparam((y-yfrozen)[mask], Ared[mask,:], linsolver_result, regparam, weights=weights[mask], regop=L, candidates=regparamrange, noiselvl=noiselvl,searchrange=regparamrange,full_output=True) alpha = output[0] diff --git a/deerlab/utils.py b/deerlab/utils.py index 5079bd5b..3b7f69d1 100644 --- a/deerlab/utils.py +++ b/deerlab/utils.py @@ -833,7 +833,7 @@ def sophegrid(octants,maxphi,size): weights = np.zeros(nOrientations) sindth2 = np.sin(dtheta/2) - w1 = 0.5 + w1 = 1.0 # North pole (z orientation) phi[0] = 0 @@ -841,7 +841,7 @@ def sophegrid(octants,maxphi,size): weights[0] = maxphi*(1 - np.cos(dtheta/2)) # All but equatorial slice - Start = 2 + Start = 1 for iSlice in np.arange(2,size): nPhi = nOct*(iSlice-1) + 1 dPhi = maxphi/(nPhi - 1) @@ -854,13 +854,13 @@ def sophegrid(octants,maxphi,size): # Equatorial slice nPhi = nOct*(size - 1) + 1 dPhi = maxphi/(nPhi - 1) - idx = Start + (np.arange(0,nPhi) - 1) + idx = Start + np.arange(0,nPhi) phi[idx] = np.linspace(0,maxphi,nPhi) theta[idx] = np.pi/2 weights[idx] = sindth2*dPhi*np.concatenate([[w1], np.ones(nPhi-2), [0.5]]) # Border removal - rmv = np.cumsum(nOct*np.arange(1,size-1)+1)+1 + rmv = np.cumsum(nOct*np.arange(1,size)+1) phi = np.delete(phi,rmv) theta = np.delete(theta,rmv) weights = np.delete(weights,rmv) diff --git a/docsrc/source/changelog.rst b/docsrc/source/changelog.rst index 9da8425e..894c269f 100644 --- a/docsrc/source/changelog.rst +++ b/docsrc/source/changelog.rst @@ -24,6 +24,18 @@ Release Notes - |fix| : Something which was not working as expected or leading to errors has been fixed. - |api| : This will require changes in your scripts or code. +Release ``v1.1.2`` - November 2023 +------------------------------------------ +- |fix| : Fixes an issue with sophgrid (:pr:`463`). +- |fix| : Fixes an error in the normalisation of the rice models (:pr:`459`). +- |fix| : Removes the broken three spin example (:pr:`427`). +- |fix| : Fixes an error in the linear solver for linearly constrained not non-negative problems. + +Release ``v1.1.1`` - August 2023 +------------------------------------------ +- |fix| : Fixes an error in the `FitResult.evaluate` function. (:pr:`454`) + + Release ``v1.1.0`` - August 2023 ------------------------------------------ - |api| : The definition of the dipolar time axis for RIDME has changed to match the one for 4-pulse DEER (:pr:`436`). diff --git a/docsrc/source/installation.rst b/docsrc/source/installation.rst index dd974a46..95257bc3 100644 --- a/docsrc/source/installation.rst +++ b/docsrc/source/installation.rst @@ -45,7 +45,7 @@ DeerLab installs the following packages: * `joblib `_ - Library lightweight pipelining and parallelization. * `tqdm `_ - A lightweight package for smart progress meters. * `dill `_ - An extension of Python's pickle module for serializing and de-serializing python objects. -* `quadprog `_ - A quadratic programming solver +* `quadprog `_ - A quadratic programming solver (Only for Python versions < 3.11) Importing DeerLab ------------------ @@ -74,9 +74,6 @@ Any DeerLab version released after v0.10.0 can be installed via ``pip`` using th python -m pip install deerlab==x.y.z -DeerLab version prior to 0.10 are written in MATLAB and are still available from an `archived repository `_. -Download and installation instruction for the MATLAB environment are provided in the released documentation. MATLAB releases have been deprecated and no further support is provided. - Installing from source ***************************** @@ -130,10 +127,18 @@ On a **Windows** computer, if you are trying to run a DeerLab function, you migh ImportError: DLL load failed: The specified module could not be found. -This happens when the MKL libraries have not been properly linked in ``numpy``, ``scipy`` or ``cvxopt`` -installations (typically ``numpy``). This can happen due to firewall restrictions, -user rights, or internet connection issues during the DeerLab installation. To solve this, the -best solution is to manually install as follows. +This issue can occur when a specific python package is not installled properly. This typicaly happens when installing the MKL linked libaries but can +occur when installing packages normaly from PyPI. This can happen due to firewall restrictions, +user rights, or internet connection issues during the DeerLab installation. + +If your where installing packages from PyPI (i.e. via pip) then please uninstall the package and reinstall it. + +.. code-block:: text + + python -m pip uninstall + python -m pip install + +If you were trying to install the MKL linked libraries then please follow the instructions below. 1) Go to https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy @@ -167,7 +172,7 @@ During installation on certain systems (e.g. some computation clusters) using on the following error might be raised during the installation: .. code-block:: text - + Error while finding module specification for 'setup.py' (ModuleNotFoundError: __path__ attribute not found on 'setup' while trying to find 'setup.py') diff --git a/examples/advanced/ex_long_threespin_analysis.py b/examples/advanced/ex_long_threespin_analysis.py deleted file mode 100644 index cdbde826..00000000 --- a/examples/advanced/ex_long_threespin_analysis.py +++ /dev/null @@ -1,95 +0,0 @@ -#%% -""" -Analyzing 4-pulse DEER data acquired on three-spin systems -============================================================================ - -As in the publication referenced below, this example will take two 4-pulse DEER signals acquired -on the same protein sample conatining three nitroxide spins with different attenuation levels of the pump -pulse power. - -For the original model and more information on these systems please refer to: -L. Fábregas Ibáñez, M. H. Tessmer, G. Jeschke, and S. Stoll. -Dipolar pathways in multi-spin and multi-dimensional dipolar EPR spectroscopy -Phys. Chem. Chem. Phys., 24 2022, 22645-22660 -""" -#%% -import numpy as np -import deerlab as dl - -# Load experimental data -files = [f'../data/triradical_protein_deer_{dB}dB.DTA' for dB in [0,6,9]] - -# Experiment information -t0 = 0.280 # Start time, μs -tau1 = 0.40 # First interpulse delay, μs -tau2 = 9.00 # Second interpulse delay, μs - -# Construct 4-pulse DEER experiment model -my4pdeer = dl.ex_4pdeer(tau1,tau2,pathways=[1]) - -# Loop over the different datasets -Vmodels,Vexps,ts,Vexps_sub,ts_sub = [],[],[],[],[] -for n,file in enumerate(files): - # Load the dataset - t,Vexp, descriptor = dl.deerload(file,full_output=True) - t = t[:-80] - Vexp = Vexp[:-80] - # Adjust the Start time - t = t - t[0] + t0 - - # Pre-processing - Vexp = dl.correctphase(Vexp) - Vexp /= np.max(Vexp) - - # Store the pre-processed datasets in a list - Vexps.append(Vexp) - ts.append(t) - - # Subsampling - # (required for efficient analysis in densely sampled datasets) - sampling = np.arange(0,len(t),4) # Take every 4th point - t_sub = t[sampling] - Vexp_sub = Vexp[sampling] - - # Store the subsampled datasets in a list - Vexps_sub.append(Vexp_sub) - ts_sub.append(t_sub) - - # Construct the three-spin dipolar model - Vmodel = dl.dipolarmodel(t_sub,spins=3,experiment=my4pdeer, minamp=0.01) - - # Add dipolar model to list of models - Vmodels.append(Vmodel) - -# Construct a global dipolar model describing all datasets -Vglobal = dl.merge(*Vmodels) -Vglobal = dl.link(Vglobal, - rmean1=[f'rmean1_{n+1}' for n in range(len(Vmodels))], - rmean2=[f'rmean2_{n+1}' for n in range(len(Vmodels))], - rmean3=[f'rmean3_{n+1}' for n in range(len(Vmodels))], - chol11=[f'chol11_{n+1}' for n in range(len(Vmodels))], - chol22=[f'chol22_{n+1}' for n in range(len(Vmodels))], - chol33=[f'chol33_{n+1}' for n in range(len(Vmodels))], - chol21=[f'chol21_{n+1}' for n in range(len(Vmodels))], - chol31=[f'chol31_{n+1}' for n in range(len(Vmodels))], - chol32=[f'chol32_{n+1}' for n in range(len(Vmodels))], - conc=[f'conc_{n+1}' for n in range(len(Vmodels))], - reftime1=[f'reftime1_{n+1}' for n in range(len(Vmodels))], -) -# Freeze the Cholesky-factors accounting for the correlation coefficients -# to zero (not always applicable) -Vglobal.chol21.freeze(0) -Vglobal.chol31.freeze(0) -Vglobal.chol32.freeze(0) - -# Fit the model to the data -results = dl.fit(Vglobal, Vexps_sub, reg=False, ftol=1e-5) - -# %% -# Plot the fitted datasets -results.plot(axis=ts_sub,xlabel='time (μs)') - -# Print the fit summary -print(results) - -# %% diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 00000000..2df9a899 --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,16 @@ +import numpy as np +from deerlab import sophegrid + + +# ====================================================================== +# Test sophegrid function + +def test_sophegrid(): + # Test that the function returns the values and weights summing to 1 + # Comparison taken from EasySpin 6.0 + phi, theta, weights = sophegrid(4,np.pi*2,3) + + assert np.allclose(weights.sum(),1) + assert np.allclose(phi, np.array([0,0,1.57079632679490,3.14159265358979,4.71238898038469,0,0.785398163397448,1.57079632679490,2.35619449019235,3.14159265358979,3.92699081698724,4.71238898038469,5.49778714378214])) + assert np.allclose(theta, np.array([0,0.785398163397448,0.785398163397448,0.785398163397448,0.785398163397448,1.57079632679490,1.57079632679490,1.57079632679490,1.57079632679490,1.57079632679490,1.57079632679490,1.57079632679490,1.57079632679490])) + assert np.allclose(weights*4*np.pi, np.array([0.956558005801449,1.70021769237074,1.70021769237074,1.70021769237074,1.70021769237074,0.601117729884346,0.601117729884346,0.601117729884346,0.601117729884346,0.601117729884346,0.601117729884346,0.601117729884346,0.601117729884346])) \ No newline at end of file