1
0
forked from IPKM/nmreval

doc added; reading of isochronal bds

This commit is contained in:
dominik 2022-03-22 20:07:59 +01:00
parent a222072b28
commit 6cd630245c
89 changed files with 41178 additions and 973 deletions

9
doc/examples/README.rst Normal file
View File

@ -0,0 +1,9 @@
.. examples-index:
.. _gallery:
========
Examples
========
This page contains example plots. Click on any image to see the full image and source code.

View File

@ -0,0 +1,6 @@
.. _distribution_examples:
.. _distribution-examples-index:
Distribution of correlation times
=================================

View File

@ -0,0 +1,50 @@
"""
=========
Cole-Cole
=========
Example for Cole-Cole distributions
"""
import matplotlib.pyplot as plt
import numpy as np
from nmreval.distributions import ColeCole
x = np.logspace(-5, 5, num=101)
cc = ColeCole
alpha_CC = [0.3, 0.5, 0.7]
fig, axes = plt.subplots(2, 3, constrained_layout=True)
lines = []
for a in alpha_CC:
axes[0, 0].plot(np.log10(x), cc.correlation(x, 1, a))
axes[1, 0].plot(np.log10(x), np.log10(cc.specdens(x, 1, a)))
axes[0, 1].plot(np.log10(x), np.log10(cc.susceptibility(x, 1, a).real))
axes[1, 1].plot(np.log10(x), np.log10(cc.susceptibility(x, 1, a).imag))
l, = axes[0, 2].plot(np.log10(x), cc.distribution(x, 1, a),
label=rf'$\alpha={a}$')
lines.append(l)
fig_titles = ('Correlation function', 'Susceptibility (real)', 'Distribution',
'Spectral density', 'Susceptibility (imag)')
fig_xlabel = (r'$\log(t/\tau_\mathrm{HN})$', r'$\log(\omega\tau_\mathrm{HN})$',
r'$\log(\tau/\tau_\mathrm{HN})$', r'$\log(\omega\tau_\mathrm{HN})$',
r'$\log(\omega\tau_\mathrm{HN})$')
fig_ylabel = (r'$C(t)$', r"$\log(\chi'(\omega))$", r'$G(\ln\tau)$',
r'$\log(J(\omega))$', r"$\log(\chi''(\omega))$")
for title, xlabel, ylabel, ax in zip(fig_titles, fig_xlabel, fig_ylabel, axes.ravel()):
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left', bbox_to_anchor=(1.05, 0.50),
bbox_transform=axes[1, 1].transAxes)
fig.delaxes(axes[1, 2])
plt.show()

View File

@ -0,0 +1,50 @@
"""
=============
Cole-Davidson
=============
Example for Cole-Davidson distributions
"""
import matplotlib.pyplot as plt
import numpy as np
from nmreval.distributions import ColeDavidson
x = np.logspace(-5, 5, num=101)
cd = ColeDavidson
gamma_CD = [0.3, 0.5, 0.7]
fig, axes = plt.subplots(2, 3, constrained_layout=True)
lines = []
for g in gamma_CD:
axes[0, 0].plot(np.log10(x), cd.correlation(x, 1, g))
axes[1, 0].plot(np.log10(x), np.log10(cd.specdens(x, 1, g)))
axes[0, 1].plot(np.log10(x), np.log10(cd.susceptibility(x, 1, g).real))
axes[1, 1].plot(np.log10(x), np.log10(cd.susceptibility(x, 1, g).imag))
l, = axes[0, 2].plot(np.log10(x), cd.distribution(x, 1, g),
label=rf'$\gamma={g}$')
lines.append(l)
fig_titles = ('Correlation function', 'Susceptibility (real)', 'Distribution',
'Spectral density', 'Susceptibility (imag)')
fig_xlabel = (r'$\log(t/\tau_\mathrm{CD})$', r'$\log(\omega\tau_\mathrm{CD})$',
r'$\log(\tau/\tau_\mathrm{CD})$', r'$\log(\omega\tau_\mathrm{CD})$',
r'$\log(\omega\tau_\mathrm{CD})$')
fig_ylabel = (r'$C(t)$', r"$\log(\chi'(\omega))$", r'$G(\ln\tau)$',
r'$\log(J(\omega))$', r"$\log(\chi''(\omega))$")
for title, xlabel, ylabel, ax in zip(fig_titles, fig_xlabel, fig_ylabel, axes.ravel()):
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left', bbox_to_anchor=(1.05, 0.50),
bbox_transform=axes[1, 1].transAxes)
fig.delaxes(axes[1, 2])
plt.show()

View File

@ -0,0 +1,53 @@
"""
================
Havriliak-Negami
================
Example for Havriliak-Negami distributions
"""
from itertools import product
import matplotlib.pyplot as plt
import numpy as np
from nmreval.distributions import HavriliakNegami
x = np.logspace(-5, 5, num=101)
hn = HavriliakNegami
alpha_CC = [0.4, 0.8]
gamma_CD = [0.3, 0.7]
fig, axes = plt.subplots(2, 3, constrained_layout=True)
lines = []
for a, g in product(alpha_CC, gamma_CD):
axes[0, 0].plot(np.log10(x), hn.correlation(x, 1, a, g))
axes[1, 0].plot(np.log10(x), np.log10(hn.specdens(x, 1, a, g)))
axes[0, 1].plot(np.log10(x), np.log10(hn.susceptibility(x, 1, a, g).real))
axes[1, 1].plot(np.log10(x), np.log10(hn.susceptibility(x, 1, a, g).imag))
l, = axes[0, 2].plot(np.log10(x), hn.distribution(x, 1, a, g),
label=rf'$\alpha={a}, \gamma={g}$')
lines.append(l)
fig_titles = ('Correlation function', 'Susceptibility (real)', 'Distribution',
'Spectral density', 'Susceptibility (imag)')
fig_xlabel = (r'$\log(t/\tau_\mathrm{HN})$', r'$\log(\omega\tau_\mathrm{HN})$',
r'$\log(\tau/\tau_\mathrm{HN})$', r'$\log(\omega\tau_\mathrm{HN})$',
r'$\log(\omega\tau_\mathrm{HN})$')
fig_ylabel = (r'$C(t)$', r"$\log(\chi'(\omega))$", r'$G(\ln\tau)$',
r'$\log(J(\omega))$', r"$\log(\chi''(\omega))$")
for title, xlabel, ylabel, ax in zip(fig_titles, fig_xlabel, fig_ylabel, axes.ravel()):
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left', bbox_to_anchor=(1.05, 0.50),
bbox_transform=axes[1, 1].transAxes)
fig.delaxes(axes[1, 2])
plt.show()

View File

@ -0,0 +1,50 @@
"""
=========================
Kohlrausch-Williams-Watts
=========================
Example for KWW distributions
"""
import matplotlib.pyplot as plt
import numpy as np
from nmreval.distributions import KWW
x = np.logspace(-5, 5, num=101)
kww = KWW
beta_KWW = [0.3, 0.5, 0.7]
fig, axes = plt.subplots(2, 3, constrained_layout=True)
lines = []
for b in beta_KWW:
axes[0, 0].plot(np.log10(x), kww.correlation(x, 1, b))
axes[1, 0].plot(np.log10(x), np.log10(kww.specdens(x, 1, b)))
axes[0, 1].plot(np.log10(x), np.log10(kww.susceptibility(x, 1, b).real))
axes[1, 1].plot(np.log10(x), np.log10(kww.susceptibility(x, 1, b).imag))
l, = axes[0, 2].plot(np.log10(x), kww.distribution(x, 1, b),
label=rf'$\beta={b}$')
lines.append(l)
fig_titles = ('Correlation function', 'Susceptibility (real)', 'Distribution',
'Spectral density', 'Susceptibility (imag)')
fig_xlabel = (r'$\log(t/\tau_\mathrm{KWW})$', r'$\log(\omega\tau_\mathrm{KWW})$',
r'$\log(\tau/\tau_\mathrm{KWW})$', r'$\log(\omega\tau_\mathrm{KWW})$',
r'$\log(\omega\tau_\mathrm{KWW})$')
fig_ylabel = (r'$C(t)$', r"$\log(\chi'(\omega))$", r'$G(\ln\tau)$',
r'$\log(J(\omega))$', r"$\log(\chi''(\omega))$")
for title, xlabel, ylabel, ax in zip(fig_titles, fig_xlabel, fig_ylabel, axes.ravel()):
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left', bbox_to_anchor=(1.05, 0.50),
bbox_transform=axes[1, 1].transAxes)
fig.delaxes(axes[1, 2])
plt.show()

View File

@ -0,0 +1,50 @@
"""
============
Log-Gaussian
============
Example for Log-Gaussian distributions
"""
import matplotlib.pyplot as plt
import numpy as np
from nmreval.distributions import LogGaussian
x = np.logspace(-5, 5, num=101)
lg = LogGaussian
sigma_lg = [1, 3, 5]
fig, axes = plt.subplots(2, 3, constrained_layout=True)
lines = []
for s in sigma_lg:
axes[0, 0].plot(np.log10(x), lg.correlation(x, 1, s))
axes[1, 0].plot(np.log10(x), np.log10(lg.specdens(x, 1, s)))
axes[0, 1].plot(np.log10(x), np.log10(lg.susceptibility(x, 1, s).real))
axes[1, 1].plot(np.log10(x), np.log10(lg.susceptibility(x, 1, s).imag))
l, = axes[0, 2].plot(np.log10(x), lg.distribution(x, 1, s),
label=rf'$\sigma={s}$')
lines.append(l)
fig_titles = ('Correlation function', 'Susceptibility (real)', 'Distribution',
'Spectral density', 'Susceptibility (imag)')
fig_xlabel = (r'$\log(t/\tau_\mathrm{LG})$', r'$\log(\omega\tau_\mathrm{LG})$',
r'$\log(\tau/\tau_\mathrm{LG})$', r'$\log(\omega\tau_\mathrm{LG})$',
r'$\log(\omega\tau_\mathrm{LG})$')
fig_ylabel = (r'$C(t)$', r"$\log(\chi'(\omega))$", r'$G(\ln\tau)$',
r'$\log(J(\omega))$', r"$\log(\chi''(\omega))$")
for title, xlabel, ylabel, ax in zip(fig_titles, fig_xlabel, fig_ylabel, axes.ravel()):
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left', bbox_to_anchor=(1.05, 0.50),
bbox_transform=axes[1, 1].transAxes)
fig.delaxes(axes[1, 2])
plt.show()

View File

@ -0,0 +1,6 @@
.. _nmr_examples:
.. _nmr-examples-index:
NMR specifics
=============

View File

@ -0,0 +1,67 @@
"""
=======================
Spin-lattice relaxation
=======================
Example for
"""
import numpy as np
from matplotlib import pyplot as plt
from nmreval.distributions import ColeDavidson
from nmreval.nmr import Relaxation, RelaxationEvaluation
from nmreval.nmr.coupling import Quadrupolar
from nmreval.utils.constants import kB
# Define temperature range
inv_temp = np.linspace(3, 9, num=30)
temperature = 1000/inv_temp
# spectral density parameter
ea = 0.45
tau = 1e-21 * np.exp(ea / kB / temperature)
gamma_cd = 0.1
# interaction parameter
omega = 2*np.pi*46e6
delta = 120e3
eta = 0
r = Relaxation()
r.set_distribution(ColeDavidson) # the only parameter that has to be set beforehand
t1_values = r.t1(omega, tau, gamma_cd, mode='bpp',
prefactor=Quadrupolar.relax(delta, eta))
# add noise
rng = np.random.default_rng(123456789)
noisy = (rng.random(t1_values.size)-0.5) * 0.5 * t1_values + t1_values
# set parameter and data
r_eval = RelaxationEvaluation()
r_eval.set_distribution(ColeDavidson)
r_eval.set_coupling(Quadrupolar, (delta, eta))
r_eval.data(temperature, noisy)
r_eval.omega = omega
t1_min_data, _ = r_eval.calculate_t1_min() # second argument is None
t1_min_inter, line = r_eval.calculate_t1_min(interpolate=1, trange=(160, 195), use_log=True)
fig, ax = plt.subplots()
ax.semilogy(1000/t1_min_data[0], t1_min_data[1], 'rx', label='Data minimum')
ax.semilogy(1000/t1_min_inter[0], t1_min_inter[1], 'r+', label='Parabola')
ax.semilogy(1000/line[0], line[1])
found_gamma, found_height = r_eval.get_increase(t1_min_inter[1], idx=0, mode='distribution')
print(found_gamma)
plt.axhline(found_height)
plt.show()
#%%
# Now we found temperature and height of the minimum we can calculate the correlation time
plt.semilogy(1000/temperature, tau)
tau_from_t1, opts = r_eval.correlation_from_t1()
print(opts)
plt.semilogy(1000/tau_from_t1[:, 0], tau_from_t1[:, 1], 'o')
plt.show()

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 600 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

@ -0,0 +1,14 @@
:mod:`{{ module | escape }}`.{{ objname }}
{{ underline }}==================
.. currentmodule:: {{ module }}
.. autoclass:: {{ objname }}
:members:
:inherited-members:
.. include:: {{ fullname }}.examples
.. raw:: html
<div class="clearer"></div>

5
doc/source/api/data.rst Normal file
View File

@ -0,0 +1,5 @@
.. automodule:: nmreval.data
:no-members:
:no-inherited-members:
:no-special-members:

View File

@ -0,0 +1,4 @@
.. automodule:: nmreval.distributions
:no-members:
:no-inherited-members:
:no-special-members:

13
doc/source/api/index.rst Normal file
View File

@ -0,0 +1,13 @@
=========
Reference
=========
.. toctree::
:caption: Table of contents
:maxdepth: 1
:glob:
data
models
distributions
nmr

View File

@ -0,0 +1,4 @@
.. automodule:: nmreval.models
:no-members:
:no-inherited-members:
:no-special-members:

4
doc/source/api/nmr.rst Normal file
View File

@ -0,0 +1,4 @@
.. automodule:: nmreval.nmr
:no-members:
:no-inherited-members:
:no-special-members:

View File

@ -10,6 +10,7 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
import sphinx_bootstrap_theme
sys.path.append('/autohome/dominik/nmreval')
@ -43,8 +44,46 @@ extensions = [
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinx_gallery.gen_gallery',
]
# configuration for intersphinx
intersphinx_mapping = {
'numpy': ('https://numpy.org/doc/stable/', None),
'python': ('https://docs.python.org/3/', None),
'scipy': ('https://docs.scipy.org/doc/scipy/', None),
'PyQt': ('https://www.riverbankcomputing.com/static/Docs/PyQt5/', None),
'matplotlib': ('https://matplotlib.org/stable', None),
}
# autodoc options
autodoc_typehints = 'none'
autodoc_class_signature = 'separated'
autoclass_content = 'class'
autodoc_member_order = 'groupwise'
# autodoc_default_options = {'members': False, 'inherited-members': False}
# autosummay options
autosummary_generate = True
# spinx-gallery
sphinx_gallery_conf = {
'examples_dirs': '../examples', # path to your example scripts
'gallery_dirs': ['gallery'], # path to where to save gallery generated output
'backreferences_dir': os.path.join('api', 'generated'), # directory where function/class granular galleries are stored
'doc_module': 'nmreval', # Modules for which function/class level galleries are created.
'reference_url': {
'nmreval': None, # The module you locally document uses None
},
'download_all_examples': False,
'plot_gallery': True,
'inspect_global_variables': False,
'remove_config_comments': True,
'min_reported_time': 10000000000,
'show_memory': False,
'show_signature': False,
}
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
@ -115,30 +154,15 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
# html_theme = 'bootstrap'
html_theme = 'pydata_sphinx_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {
# # 'source_link_position': "footer",
# 'bootswatch_theme': "cosmo",
# 'navbar_title': "Welcome to hell",
# 'navbar_sidebarrel': True,
# 'nosidebar': False,
# 'body_max_width': '100%',
# 'navbar_links': [
# ('User guide', 'user_guide/index'),
# ('References', 'api/index'),
# ],
# }
html_theme_options = {
'collapse_navigation': False,
'show_prev_next': False,
'navbar_end': ['navbar-icon-links.html', 'search-field.html'],
'show_toc_level': 3
}
# Add any paths that contain custom themes here, relative to this directory.
@ -396,29 +420,3 @@ epub_exclude_files = ['search.html']
# If false, no index is generated.
#
# epub_use_index = True
# configuration for intersphinx
intersphinx_mapping = {
'Pillow': ('https://pillow.readthedocs.io/en/stable/', None),
'cycler': ('https://matplotlib.org/cycler/', None),
'dateutil': ('https://dateutil.readthedocs.io/en/stable/', None),
'ipykernel': ('https://ipykernel.readthedocs.io/en/latest/', None),
'numpy': ('https://numpy.org/doc/stable/', None),
'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None),
'pytest': ('https://pytest.org/en/stable/', None),
'python': ('https://docs.python.org/3/', None),
'scipy': ('https://docs.scipy.org/doc/scipy/', None),
'tornado': ('https://www.tornadoweb.org/en/stable/', None),
'xarray': ('https://xarray.pydata.org/en/stable/', None),
'PyQt': ('https://www.riverbankcomputing.com/static/Docs/PyQt5/', None),
}
# autodoc options
autodoc_typehints = 'none'
autodoc_class_signature = 'separated'
autoclass_content = 'class'
# autosummay options
autosummary_generate = True

View File

@ -0,0 +1,179 @@
:orphan:
.. _sphx_glr_gallery:
.. examples-index:
.. _gallery:
========
Examples
========
This page contains example plots. Click on any image to see the full image and source code.
.. raw:: html
<div class="sphx-glr-clear"></div>
.. _sphx_glr_gallery_distribution:
.. _distribution_examples:
.. _distribution-examples-index:
Distribution of correlation times
=================================
.. raw:: html
<div class="sphx-glr-thumbcontainer" tooltip="Example for KWW distributions">
.. only:: html
.. figure:: /gallery/distribution/images/thumb/sphx_glr_plot_KWW_thumb.png
:alt: Kohlrausch-Williams-Watts
:ref:`sphx_glr_gallery_distribution_plot_KWW.py`
.. raw:: html
</div>
.. toctree::
:hidden:
/gallery/distribution/plot_KWW
.. raw:: html
<div class="sphx-glr-thumbcontainer" tooltip="Example for Cole-Cole distributions">
.. only:: html
.. figure:: /gallery/distribution/images/thumb/sphx_glr_plot_ColeCole_thumb.png
:alt: Cole-Cole
:ref:`sphx_glr_gallery_distribution_plot_ColeCole.py`
.. raw:: html
</div>
.. toctree::
:hidden:
/gallery/distribution/plot_ColeCole
.. raw:: html
<div class="sphx-glr-thumbcontainer" tooltip="Example for Log-Gaussian distributions">
.. only:: html
.. figure:: /gallery/distribution/images/thumb/sphx_glr_plot_LogGaussian_thumb.png
:alt: Log-Gaussian
:ref:`sphx_glr_gallery_distribution_plot_LogGaussian.py`
.. raw:: html
</div>
.. toctree::
:hidden:
/gallery/distribution/plot_LogGaussian
.. raw:: html
<div class="sphx-glr-thumbcontainer" tooltip="Example for Cole-Davidson distributions">
.. only:: html
.. figure:: /gallery/distribution/images/thumb/sphx_glr_plot_ColeDavidson_thumb.png
:alt: Cole-Davidson
:ref:`sphx_glr_gallery_distribution_plot_ColeDavidson.py`
.. raw:: html
</div>
.. toctree::
:hidden:
/gallery/distribution/plot_ColeDavidson
.. raw:: html
<div class="sphx-glr-thumbcontainer" tooltip="Example for Havriliak-Negami distributions">
.. only:: html
.. figure:: /gallery/distribution/images/thumb/sphx_glr_plot_HavriliakNegami_thumb.png
:alt: Havriliak-Negami
:ref:`sphx_glr_gallery_distribution_plot_HavriliakNegami.py`
.. raw:: html
</div>
.. toctree::
:hidden:
/gallery/distribution/plot_HavriliakNegami
.. raw:: html
<div class="sphx-glr-clear"></div>
.. _sphx_glr_gallery_nmr:
.. _nmr_examples:
.. _nmr-examples-index:
NMR specifics
=============
.. raw:: html
<div class="sphx-glr-thumbcontainer" tooltip="Example for">
.. only:: html
.. figure:: /gallery/nmr/images/thumb/sphx_glr_plot_RelaxationEvaluation_thumb.png
:alt: Spin-lattice relaxation
:ref:`sphx_glr_gallery_nmr_plot_RelaxationEvaluation.py`
.. raw:: html
</div>
.. toctree::
:hidden:
/gallery/nmr/plot_RelaxationEvaluation
.. raw:: html
<div class="sphx-glr-clear"></div>

View File

@ -0,0 +1,3 @@
'/autohome/dominik/nmreval/doc/_build/html/index.html', (0, 6969)
'/autohome/dominik/nmreval/doc/_build/html/_static/documentation_options.js', (7168, 364)
'/autohome/dominik/nmreval/doc/_build/html/searchindex.js', (7680, 29280)

Binary file not shown.

View File

@ -0,0 +1,3 @@
'/autohome/dominik/nmreval/doc/_build/html/index.html', (0, 6969)
'/autohome/dominik/nmreval/doc/_build/html/_static/documentation_options.js', (7168, 364)
'/autohome/dominik/nmreval/doc/_build/html/searchindex.js', (7680, 29280)

View File

@ -3,17 +3,15 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
###################################
#####################
NMREval documentation
###################################
#####################
.. toctree::
:maxdepth: 1
user_guide/index
nmr/index
gallery/index
api/index

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -1,25 +0,0 @@
{{ fullname | escape | underline }}
.. currentmodule:: {{ module }}
{% if objtype in ['class'] %}
Inheritance diagram
.. inheritance-diagram:: {{ objname }}
:parts: 1
{{ objtype }}
.. auto{{ objtype }}:: {{ objname }}
:special-members: __call__
:members:
:undoc-members:
{% else %}
.. auto{{ objtype }}:: {{ objname }}
{% endif %}

View File

@ -1,23 +0,0 @@
**************
Data container
**************
.. automodule:: nmreval.data
:no-members:
:no-undoc-members:
.. contents:: Table of Contents
:depth: 3
:local:
:backlinks: entry
.. autosummary::
:toctree: generated/
:template: autosummary.rst
:nosignatures:
nmreval.data.Points
nmreval.data.Signal
nmreval.data.BDS

View File

@ -1,23 +0,0 @@
nmreval.data.BDS
================
.. currentmodule:: nmreval.data
Inheritance diagram
.. inheritance-diagram:: BDS
:parts: 1
class
.. autoclass:: BDS
:special-members: __call__
:members:
:undoc-members:

View File

@ -1,23 +0,0 @@
nmreval.data.Points
===================
.. currentmodule:: nmreval.data
Inheritance diagram
.. inheritance-diagram:: Points
:parts: 1
class
.. autoclass:: Points
:special-members: __call__
:members:
:undoc-members:

View File

@ -1,23 +0,0 @@
nmreval.data.Signal
===================
.. currentmodule:: nmreval.data
Inheritance diagram
.. inheritance-diagram:: Signal
:parts: 1
class
.. autoclass:: Signal
:special-members: __call__
:members:
:undoc-members:

View File

@ -1,12 +0,0 @@
==========
References
==========
.. toctree::
:caption: Table of contents
:maxdepth: 2
:glob:
data.rst
models/index.rst
distributions/index.rst

View File

@ -1,3 +1,20 @@
"""
====================================
Data container (:mod:`nmreval.data`)
====================================
.. autosummary::
:toctree: generated/
:nosignatures:
Points
Signal
FID
Spectrum
BDS
"""
from .points import Points
from .signals import Signal
from .bds import BDS

View File

@ -15,10 +15,11 @@ class BDS(Signal):
def __init__(self, x, y, **kwargs: Any):
super().__init__(x, y, **kwargs)
if np.all(self._x > 0) and not np.allclose(np.diff(self._x), self._x[1]-self._x[0]):
self.dx = self.x[1] / self.x[0]
else:
self.dx = self._x[1] - self._x[0]
if len(self._x) > 1:
if np.all(self._x > 0) and np.allclose(self._x[1:]/self._x[:-1], self._x[1]/self._x[0]):
self.dx = self.x[1] / self.x[0]
else:
self.dx = self._x[1] - self._x[0]
def __repr__(self) -> str:
return f"{self.meta['mode']}: {self.name}"
@ -32,10 +33,10 @@ class BDS(Signal):
window_length (int)
Returns:
Points
Points:
New Points instance with
References:
Reference:
Wübbenhorst, M.; van Turnhout, J.: Analysis of complex dielectric spectra.
I. One-dimensional derivative techniques and three-dimensional modelling.
J. Non-Cryst. Solid. 305 (2002) https://doi.org/10.1016/s0022-3093(02)01086-4

View File

@ -7,6 +7,9 @@ from .signals import Signal
class FID(Signal):
"""
Extensions if :class:`Signal` for NMR timesignals
"""
def __init__(self, x, y, **kwargs):
super().__init__(x, y, **kwargs)
@ -16,7 +19,16 @@ class FID(Signal):
def __repr__(self):
return f"FID: {self.name}"
def baseline(self, percentage=0.12):
def baseline(self, percentage: float = 0.12):
"""
Substract
Args:
percentage:
Returns:
"""
self._y -= self._y[int(-percentage * self.length):].mean()
return self
@ -51,7 +63,7 @@ class FID(Signal):
return self
def fourier(self):
def fourier(self) -> 'Spectrum':
ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx
freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx))
@ -63,9 +75,15 @@ class FID(Signal):
def fft_depake(self, scale: bool = False):
"""
Calculate deconvoluted Pake spectra
M.A. McCabe, S.R. Wassail:
Rapid deconvolution of NMR powder spectra by weighted fast Fourier transformation,
SSNMR (1997), Vol.10, Nr.1-2, pp. 53-61,
Args:
scale (bool):
Reference:
M.A. McCabe, S.R. Wassail:
Rapid deconvolution of NMR powder spectra by weighted fast Fourier transformation,
SSNMR (1997), Vol.10, Nr.1-2, pp. 53-61
"""
_y = self._y + 0
# Perform FFT depaking
@ -102,8 +120,9 @@ class FID(Signal):
self._y_err = np.std(self._y[-int(0.1*self.length):])/1.41
return self._y_err * np.ones(self.length)
def shift(self, points: Union[str, float], mode: str = 'pts'):
def shift(self, points: int, mode: str = 'pts') -> None:
"""
Shift y values to new x indexes
Args:
points : shift value,
@ -113,13 +132,17 @@ class FID(Signal):
"""
if mode == 'pts':
super().shift(points)
super().shift(int(points))
else:
pts = int(points//self.dx)
super().shift(pts)
class Spectrum(Signal):
"""
Extension of :class:`Signal` for NMR spectra
"""
def __init__(self, x, y, dx=None, **kwargs):
super().__init__(x, y, **kwargs)
if dx is None:
@ -139,7 +162,7 @@ class Spectrum(Signal):
return self
def fourier(self):
def fourier(self) -> FID:
ft = np.fft.ifft(np.fft.ifftshift(self._y * self.dx))
t = np.arange(len(ft))*self.dx
shifted = np.fft.ifftshift(self._x)[0]
@ -172,8 +195,8 @@ class Spectrum(Signal):
return self._y_err * np.ones(self.length)
def shift(self, points, mode='pts'):
def shift(self, points: int, mode: str = 'pts'):
if mode == 'pts':
super().shift(points)
super().shift(int(points))
else:
return self

View File

@ -412,11 +412,15 @@ class Points:
def diff(self, log: bool = False, limits: Optional[Tuple[float, float]] = None) -> PointLike:
"""
Calculate first derivate :math:`dy/dx` or :math:`dy/d(\ln x)`.
Args:
log:
limits:
log (bool) : Flag if derivative is with respect to ln(x) instead of x. Default is `False`.
limits (tuple, optional): Range `(xmin, xmax)` which is differentiated.
Returns:
Copy of set with new y values.
"""
@ -430,6 +434,8 @@ class Points:
else:
new_data.y = np.gradient(new_data.y, new_data.x)
new_data.remove(np.argwhere(np.isnan(new_data.y)))
return new_data
def magnitude(self) -> PointLike:
@ -530,6 +536,8 @@ class Points:
def shift(self, points: int) -> PointLike:
"""
Shift indexes of y values.
Move y values `points` indexes, remove invalid indexes and fill remaining with zeros
Negative `points` shift to the left, positive `points` shift to the right
@ -558,11 +566,21 @@ class Points:
return self
def sort(self, axis=0):
if axis == 0:
def sort(self, axis: str = 'x') -> PointLike:
"""
Sort data in increased order.
Args:
axis (str, {`x`, `y`}) : Axis that determines new order. Default is `x`
"""
if axis == 'x':
sort_order = np.argsort(self._x)
else:
elif axis == 'y':
sort_order = np.argsort(self._y)
else:
raise ValueError('Unknown sort axis, use `x` or `y`.')
self._x = self._x[sort_order]
self._y = self._y[sort_order]
self._y_err = self._y_err[sort_order]

View File

@ -6,7 +6,7 @@ from .points import Points
class Signal(Points):
"""
Extension of Points for complex y values.
Extension of :class:`Points` for complex y values.
"""
def __init__(self, x, y, **kwargs):
@ -18,7 +18,10 @@ class Signal(Points):
super().__init__(x, y, **kwargs)
self.dx = kwargs.get('dx', abs(self._x[1] - self._x[0]))
if len(self._x) > 1:
self.dx = kwargs.get('dx', abs(self._x[1] - self._x[0]))
else:
self.dx = 0
self.meta.update({'phase': [], 'shift': 0})
def __repr__(self):

View File

@ -1,3 +1,25 @@
"""
=================================================================
Distributions of correlation times (:mod:`nmreval.distributions`)
=================================================================
Each class provides a distribution of correlation times, correlation
function, spectral density, susceptibility, and calculation of different
types of correlation times.
.. autosummary::
:toctree: generated/
:template: autosummary/class_with_attributes.rst
:nosignatures:
Debye
ColeCole
ColeDavidson
HavriliakNegami
KWW
LogGaussian
"""
from .havriliaknegami import HavriliakNegami
from .colecole import ColeCole

View File

@ -1,8 +1,11 @@
import abc
from warnings import warn
from typing import Any, Union
import numpy as np
from nmreval.lib.utils import ArrayLike
class Distribution(abc.ABC):
name = 'Abstract distribution'
@ -15,7 +18,12 @@ class Distribution(abc.ABC):
@staticmethod
@abc.abstractmethod
def distribution(taus, tau0, *args):
def distribution(tau, tau0, *args):
pass
@staticmethod
@abc.abstractmethod
def correlation(t, tau, *args):
pass
@staticmethod
@ -24,13 +32,8 @@ class Distribution(abc.ABC):
pass
@classmethod
def specdens(cls, omega, tau, *args):
return -cls.susceptibility(omega, tau, *args).imag / omega
@staticmethod
@abc.abstractmethod
def correlation(t, tau0, *args):
pass
def specdens(cls, omega: ArrayLike, tau: ArrayLike, *args: Any) -> Union[np.ndarray, float]:
return cls.susceptibility(omega, tau, *args).imag / omega
@classmethod
def mean_value(cls, *args, mode='mean'):
@ -43,6 +46,27 @@ class Distribution(abc.ABC):
return args[0]
@staticmethod
def mean(*args):
return args[0]
@staticmethod
def logmean(*args):
"""
Return mean logarithmic tau
Args:
*args:
Returns:
"""
return np.log(args[0])
@staticmethod
def max(*args):
return args[0]
@classmethod
def convert(cls, *args, from_='raw', to_='mean'):
if from_ == to_:
@ -62,24 +86,3 @@ class Distribution(abc.ABC):
return raw
else:
return cls.mean_value(raw, *args[1:], mode=to_)
@staticmethod
def logmean(*args):
"""
Return mean logarithmic tau
Args:
*args:
Returns:
"""
return np.log(args[0])
@staticmethod
def mean(*args):
return args[0]
@staticmethod
def max(*args):
return args[0]

View File

@ -1,6 +1,8 @@
import numpy as np
from .base import Distribution
from ..lib.utils import ArrayLike
from ..lib.decorator import adjust_dims
from ..math.mittagleffler import mlf
@ -13,7 +15,7 @@ class ColeCole(Distribution):
bounds = [(0, 1)]
@staticmethod
def distribution(taus, tau0, alpha):
def distribution(tau, tau0, alpha):
"""
Distribution of correlation times
@ -21,32 +23,33 @@ class ColeCole(Distribution):
G(\ln\\tau) = \\frac{1}{2\pi} \\frac{\\sin(\\pi\\alpha)}{\cosh[\\alpha\ln(\\tau/\\tau_0)] + \cos(\\alpha \pi))}
Args:
taus:
tau:
tau0:
alpha:
"""
z = np.log(taus/tau0)
z = np.log(tau/tau0)
return np.sin(np.pi*alpha)/(np.cosh(z*alpha) + np.cos(alpha*np.pi))/(2*np.pi)
@staticmethod
def susceptibility(omega, tau, alpha):
"""
Susceptibility
@adjust_dims
def susceptibility(omega: ArrayLike, tau: ArrayLike, alpha: float) -> ArrayLike:
r"""
Complex susceptibility
.. math::
\chi(\omega, \\tau, \\alpha) = \\frac{1}{1-(i\omega\\tau_0)^\\alpha}
\chi(\omega, \tau, \alpha) = \frac{1}{[1-(i\omega\tau)^\alpha]}
Args:
omega:
tau:
alpha:
omega (array-like): Frequency axis in 1/s (not Hz).
tau (array-like): Correlation times :math:`\tau_\text{CC}` in s.
alpha (float): Shape parameter.
"""
_tau = np.atleast_1d(tau)
_omega = np.atleast_1d(omega)
val = 1/(1+(1j*_omega[:, None]*_tau[None, :])**alpha)
return np.conjugate(val).squeeze()
val = 1/(1+(1j*omega*tau)**alpha)
return np.conjugate(val)
@staticmethod
@adjust_dims
def specdens(omega, tau, alpha):
"""
Spectral density
@ -59,12 +62,11 @@ class ColeCole(Distribution):
tau:
alpha:
"""
_tau = np.atleast_1d(tau)
_omega = np.atleast_1d(omega)
omtau = (_omega*_tau)**alpha
omtau = (omega*tau)**alpha
return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega
@staticmethod
@adjust_dims
def correlation(t, tau0, alpha):
"""
Correlation function :math:`C(t)`
@ -80,26 +82,3 @@ class ColeCole(Distribution):
alpha:
"""
return mlf(-(t/tau0)**alpha, alpha)
if __name__ == '__main__':
import matplotlib.pyplot as plt
x = np.logspace(-3, 3, num=61)
fig, ax = plt.subplots(2, 2)
ax[0, 0].set_title('Distribution')
ax[0, 1].set_title('Correlation func.')
ax[1, 0].set_title('Spectral density')
ax[1, 1].set_title('Susceptibility')
for a in [0.4, 0.6, 0.8]:
ax[0, 0].loglog(x, ColeCole.distribution(x, 1, a))
ax[0, 1].semilogx(x, ColeCole.correlation(x, 1, a))
ax[1, 0].loglog(x, ColeCole.specdens(x, 1, a))
g = ax[1, 1].loglog(x, ColeCole.susceptibility(x, 1, a).imag)
ax[1, 1].loglog(x, ColeCole.susceptibility(x, 1, a).real, '--', color=g[0].get_color())
fig.tight_layout()
plt.show()

View File

@ -4,6 +4,8 @@ import numpy as np
from scipy.special import psi, gammaincc
from .base import Distribution
from ..lib.decorator import adjust_dims
from ..lib.utils import ArrayLike
from ..utils.constants import Eu
@ -16,22 +18,50 @@ class ColeDavidson(Distribution):
bounds = [(0, 1)]
@staticmethod
def susceptibility(omega, tau, gamma):
def distribution(tau, tau0, gamma):
"""
Susceptibility
Distribution of correlation times
.. math::
\chi(\omega, \\tau, \\gamma) = \\frac{1}{(1-i\omega\\tau_0)^\\gamma}
G(\ln\\tau) =
\\begin{cases}
\\frac{\sin(\pi\gamma)}{\pi} \\Big(\\frac{\\tau}{\\tau_0 - \\tau}\\Big)^\gamma & \\tau < \\tau_0 \\\\
0 & \\text{else}
\end{cases}
Args:
omega:
tau:
tau0:
gamma:
"""
ratio = tau0/tau
ret_val = np.zeros_like(ratio)
ret_val[ratio > 1] = np.sin(np.pi*gamma) / np.pi * (1 / (ratio[ratio>1] - 1))**gamma
return ret_val
@staticmethod
@adjust_dims
def susceptibility(omega: ArrayLike, tau: ArrayLike, gamma: float) -> ArrayLike:
r"""
Complex susceptibility
.. math::
\chi(\omega, \tau, \alpha, \gamma) = \frac{1}{[1-i\omega\tau]^\gamma}
Args:
omega (array-like): Frequency axis in 1/s (not Hz).
tau (array-like): Correlation times in s.
gamma (float): Shape parameter.
"""
return (1/(1+1j*omega*tau)**gamma).conjugate()
@staticmethod
def specdens(omega, tau,gamma):
@adjust_dims
def specdens(omega, tau, gamma):
"""
Spectral density
@ -43,15 +73,12 @@ class ColeDavidson(Distribution):
tau:
gamma:
"""
gam = gamma
_w = np.atleast_1d(omega)
_t = np.atleast_1d(tau)
omtau = _w[:, None] * _t[None, :]
omtau = omega*tau
ret_val = np.sin(gam*np.arctan2(omtau, 1)) / (1 + omtau**2)**(gam/2.) / _w[:, None]
ret_val[_w == 0, :] = gam*_t
ret_val = np.sin(gamma*np.arctan2(omtau, 1)) / (1 + omtau**2)**(gamma/2.) / omega
ret_val[np.argwhere(omega == 0)] = gamma*tau
return np.squeeze(ret_val)
return ret_val
@staticmethod
def mean(tau, gamma):
@ -113,33 +140,8 @@ class ColeDavidson(Distribution):
return tau/np.tan(np.pi/(2*gamma+2))
@staticmethod
def distribution(tau, tau0, gamma):
"""
Distribution of correlation times
.. math::
G(\ln\\tau) =
\\begin{cases}
\\frac{\sin(\pi\gamma)}{\pi} \\Big(\\frac{\\tau}{\\tau_0 - \\tau}\\Big)^\gamma & \\tau < \\tau_0 \\\\
0 & \\text{else}
\end{cases}
Args:
tau:
tau0:
gamma:
"""
if isinstance(tau, numbers.Number):
return np.sin(np.pi*gamma) / np.pi * (1 / (tau0/tau - 1))**gamma if tau < tau0 else 0
ret_val = np.zeros_like(tau)
ret_val[tau < tau0] = np.sin(np.pi*gamma) / np.pi * (1 / (tau0/tau[tau < tau0] - 1))**gamma
return ret_val
@staticmethod
def correlation(t, tau0, gamma):
@adjust_dims
def correlation(t, tau, gamma):
r"""
Correlation function
@ -153,14 +155,14 @@ class ColeDavidson(Distribution):
Args:
t:
tau0:
tau:
gamma:
References:
R. Hilfer: H-function representations for stretched exponential relaxation and non-Debye susceptibilities in glassy systems. Phys. Rev. E (2002) https://doi.org/10.1103/PhysRevE.65.061510
"""
return gammaincc(gamma, t / tau0)
return gammaincc(gamma, t / tau)
if __name__ == '__main__':

View File

@ -3,28 +3,44 @@ import numbers
import numpy as np
from .base import Distribution
from ..lib.decorator import adjust_dims
from ..lib.utils import ArrayLike
class Debye(Distribution):
name = 'Debye'
@staticmethod
def correlation(t, tau0, *args):
return np.exp(-t/tau0)
def distribution(tau: ArrayLike, tau0: float) -> ArrayLike:
if isinstance(tau, numbers.Number):
return 1 if tau == tau0 else 0
@staticmethod
def susceptibility(omega, tau0, *args):
return 1/(1 + 1j*omega*tau0)
@staticmethod
def specdens(omega, tau0, *args):
return tau0 / (1 + (omega*tau0)**2)
@staticmethod
def distribution(taus, tau0, *args):
if isinstance(taus, numbers.Number):
return 1 if taus == tau0 else 0
ret_val = np.zeros_like(taus)
ret_val[np.argmin(abs(taus - tau0))] = 1
ret_val = np.zeros_like(tau)
ret_val[tau == tau0] = 1.
return ret_val
@staticmethod
@adjust_dims
def correlation(t, tau, *args):
return np.exp(-t/tau)
@staticmethod
@adjust_dims
def susceptibility(omega: ArrayLike, tau: ArrayLike) -> ArrayLike:
r"""
Complex susceptibility
.. math::
\chi(\omega, \tau) = \frac{1}{1-i\omega\tau}
Args:
omega (array-like): Frequency axis in 1/s (not Hz).
tau (array-like): Correlation times in s.
"""
return 1/(1 - 1j*omega*tau)
@staticmethod
@adjust_dims
def specdens(omega: ArrayLike, tau: ArrayLike) -> ArrayLike:
return tau / (1 + (omega*tau)**2)

View File

@ -1,14 +1,18 @@
from typing import Union
import numpy as np
from scipy.special import psi
from .base import Distribution
from ..lib.utils import ArrayLike
from ..lib.decorator import adjust_dims
from ..math.mittagleffler import mlf
from ..utils import Eu
class HavriliakNegami(Distribution):
"""
Functions based on Cole-Davidson distribution
Functions based on Havriliak-Negami distribution
"""
name = 'Havriliak-Negami'
@ -16,51 +20,31 @@ class HavriliakNegami(Distribution):
bounds = [(0, 1), (0, 1)]
@staticmethod
def correlation(t, tau0, alpha, gamma):
def distribution(tau: ArrayLike, tau0: float, alpha: float, gamma: float) -> ArrayLike:
r"""
Correlation function
Distribution of correlation times :math:`G(\ln\tau)`.
.. math::
C(t, \tau_0, \alpha, \gamma) = 1 - \left(\frac{t}{\tau_0}\right)^{\alpha\gamma} \text{E}_{\alpha,\alpha\gamma+1}^\gamma\left[-\left(\frac{t}{\tau_0}\right)^\alpha\right]
with incomplete Gamma function
.. math::
\Gamma(\gamma, t/\tau_0) = \frac{1}{\Gamma(\gamma)}\int_{t/\tau_0}^\infty x^{\gamma-1}\text{e}^{-x}\,\mathrm{d}x
G(\ln\tau) = \frac{(\tau/\tau_0)^{\alpha\gamma}
\sin\left\lbrace\gamma\arctan\left[\frac{\sin\alpha\pi}{(\tau/\tau_0)^\alpha + \cos\alpha\pi}\right] \right\rbrace}{\pi\left[1+2(\tau/\tau_0)^\alpha\cos\alpha\pi + (\tau/\tau_0)^{2\gamma}\right]}
Args:
t:
tau0:
tau (array_like) :
tau0 (array-like) :
alpha:
gamma:
References:
R. Hilfer: H-function representations for stretched exponential relaxation and non-Debye susceptibilities in glassy systems. Phys. Rev. E (2002) https://doi.org/10.1103/PhysRevE.65.061510
Returns:
"""
return 1 - (t/tau0)**(alpha*gamma) * mlf(-(t/tau0)**alpha, alpha, alpha*gamma+1, gamma)
@staticmethod
def susceptibility(omega, tau, alpha, gamma):
return np.conjugate(1/(1 + (1j*omega[:, None]*tau[None, :])**alpha)**gamma).squeeze()
@staticmethod
def specdens(omega, tau, alpha, gamma):
omtau = (omega[:, None]*tau[None, :])**alpha
zaehler = np.sin(gamma * np.arctan2(omtau*np.sin(0.5*alpha*np.pi), 1 + omtau*np.cos(0.5*alpha*np.pi)))
nenner = (1 + 2*omtau * np.cos(0.5*alpha*np.pi) + omtau**2)**(0.5*gamma)
return ((1 / omega) * (zaehler/nenner)).squeeze()
@staticmethod
def distribution(tau, tau0, alpha, gamma):
if alpha == 1:
from .coledavidson import ColeDavidson
return ColeDavidson.distribution(tau, tau0, gamma)
elif gamma == 1:
from .colecole import ColeCole
return ColeCole.distribution(tau, tau0, alpha)
else:
_y = tau/tau0
om_y = (1 + 2*np.cos(np.pi*alpha)*(_y**alpha) + _y**(2*alpha))**(-gamma/2)
@ -69,11 +53,104 @@ class HavriliakNegami(Distribution):
return np.sin(gamma*theta_y) * om_y * (_y**(alpha*gamma)) / np.pi
@staticmethod
def max(tau, alpha, gamma):
return tau*(np.sin(0.5*np.pi*alpha*gamma/(gamma+1)) / np.sin(0.5*np.pi*alpha/(gamma+1)))**(1/alpha)
@adjust_dims
def correlation(t: ArrayLike, tau: ArrayLike, alpha: float, gamma: float) -> ArrayLike:
r"""
Correlation function
.. math::
C(t, \tau_\text{HN}, \alpha, \gamma) = 1 - \left(\frac{t}{\tau_\text{HN}}\right)^{\alpha\gamma}
\text{E}_{\alpha,\alpha\gamma+1}^\gamma\left[-\left(\frac{t}{\tau_\text{HN}}\right)^\alpha\right]
with three-parameter Mittag-Leffler function
.. math::
\text{E}_{a,b}^g (z) = \frac{1}{\Gamma(g)} \sum_{k=0}^\infty \frac{\Gamma(g+k) z^k}{k!\Gamma(ak+b)}
Args:
t (array-like): Time axis in s.
tau (array-like): Correlation times :math:`\tau_\text{HN}` in s.
alpha (float): Cole-Cole shape parameter.
gamma (float): Cole-Davidson shape parameter.
References:
R. Garrappa: Models of dielectric relaxation based on completely monotone functions.
Frac. Calc Appl. Anal. 19 (2016) https://arxiv.org/abs/1611.04028
"""
return 1 - (t/tau)**(alpha*gamma) * mlf(-(t/tau)**alpha, alpha, alpha*gamma+1, gamma)
@staticmethod
def logmean(tau, alpha, gamma):
@adjust_dims
def susceptibility(omega: ArrayLike, tau: ArrayLike, alpha: float, gamma: float) -> Union[np.ndarray, complex]:
r"""
Complex susceptibility
.. math::
\chi(\omega, \tau, \alpha, \gamma) = \frac{1}{[1-(i\omega\tau)^\alpha]^\gamma}
Args:
omega (array-like): Frequency axis in 1/s (not Hz).
tau (array-like): Correlation times in s.
alpha (float): Shape parameter.
gamma (float): Even more shape parameter.
"""
return np.conjugate(1/(1 + (1j*omega*tau)**alpha)**gamma)
@staticmethod
@adjust_dims
def specdens(omega: ArrayLike, tau: ArrayLike, alpha: float, gamma: float) -> Union[np.ndarray, float]:
r"""
Spectral density :math:`J(\omega)`
.. math::
J(\omega) = \frac{\sin\left\lbrace \gamma\arctan\left[ \frac{(\omega\tau)^\alpha\sin(\alpha\pi/2)}{1+(\omega\tau)^\alpha\cos(\alpha\pi/2)} \right] \right\rbrace}
{\omega \left[ 1+(\omega\tau)^\alpha \cos(\alpha\pi/2) + (\omega\tau)^{2\alpha} \right]^{\gamma/2}}
Args:
omega (array-like): Frequency axis in 1/s (not Hz).
tau (array-like): Correlation times in s.
alpha (float): Cole-Cole shape parameter.
gamma (float): Cole-Davidson shape parameter.
Returns:
"""
omtau = (omega*tau)**alpha
zaehler = np.sin(gamma * np.arctan2(omtau*np.sin(0.5*alpha*np.pi), 1 + omtau*np.cos(0.5*alpha*np.pi)))
nenner = (1 + 2*omtau * np.cos(0.5*alpha*np.pi) + omtau**2)**(0.5*gamma)
result = (1 / omega) * (zaehler/nenner)
return result
@staticmethod
def mean(tau: ArrayLike, alpha: float, gamma: float) -> Union[np.ndarray, float]:
r"""
.. math::
\langle \tau \rangle \approx \tau_\text{HN} \alpha \gamma
This is an approximation given in `[1]`_
Args:
tau (array-like): Function parameter in s.
alpha (float): Shape parameter.
gamma (float): Even more shape parameter.
Reference:
_`[1]` Bauer, Th., Köhler, M., Lunkenheimer, P., Angell, C.A.:
Relaxation dynamics and ionic conductivity in a fragile plastic crystal.
J. Chem. Phys. 133, 144509 (2010). https://doi.org/10.1063/1.3487521
"""
return tau * alpha*gamma
@staticmethod
def logmean(tau: ArrayLike, alpha: float, gamma: float) -> ArrayLike:
r"""
Calculate mean logarithm of tau
@ -82,25 +159,12 @@ class HavriliakNegami(Distribution):
Args:
tau:
alpha:
gamma:
Returns:
alpha (float):
gamma (float):
"""
return np.log(tau) + (psi(gamma)+Eu)/alpha
@staticmethod
def mean(tau, alpha, gamma):
# approximation according to Th. Bauer et al., J. Chem. Phys. 133, 144509 (2010).
return tau * alpha*gamma
if __name__ == '__main__':
import matplotlib.pyplot as plt
x = np.logspace(-7, 3)
y = HavriliakNegami.correlation(x, 1, 0.8, 0.26)
plt.semilogx(x, y)
plt.show()
def max(tau: ArrayLike, alpha: float, gamma: float) -> ArrayLike:
return tau*(np.sin(0.5*np.pi*alpha*gamma/(gamma+1)) / np.sin(0.5*np.pi*alpha/(gamma+1)))**(1/alpha)

View File

@ -2,6 +2,7 @@ import numpy as np
from scipy.interpolate import interp1d
from scipy.special import gamma
from nmreval.lib.decorator import adjust_dims
from .base import Distribution
from ..math.kww import kww_cos, kww_sin
from ..utils.constants import Eu
@ -13,52 +14,52 @@ class KWW(Distribution):
boounds = [(0, 1)]
@staticmethod
def distribution(taus, tau, *args):
b = args[0]
assert 0.1 <= b <= 0.9
def distribution(taus, tau, beta):
if not (0.1 <= beta <= 0.9):
raise ValueError('KWW distribution is only defined between 0.1 and 0.9')
b_supp = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
B_supp = [0.145, 0.197, 0.243, 0.285, 0.382, 0.306, 0.360, 0.435, 0.700]
C_supp = [0.89, 0.50, 0.35, 0.25, 0, 0.13, 0.22, 0.4015, 0.32]
B = interp1d(b_supp, B_supp)(b)
C = interp1d(b_supp, C_supp)(b)
B = interp1d(b_supp, B_supp)(beta)
C = interp1d(b_supp, C_supp)(beta)
tt = tau/taus
delta = b * abs(b-0.5) / (1-b)
if b > 0.5:
delta = beta * abs(beta-0.5) / (1-beta)
if beta > 0.5:
f = 1 + C * tt**delta
else:
f = 1 / (1 + C * tt**delta)
ret_val = tau * B * np.exp(-(1-b) * b**(b/(1-b)) / tt**(b/(1-b))) / tt**((1-0.5*b)/(1-b))
ret_val = tau * B * np.exp(-(1-beta) * beta**(beta/(1-beta)) / tt**(beta/(1-beta))) / tt**((1-0.5*beta)/(1-beta))
return ret_val * f / taus
@staticmethod
def correlation(t, tau, *args):
return np.exp(-(t/tau)**args[0])
@adjust_dims
def correlation(t, tau, beta):
return np.exp(-(t/tau)**beta)
@staticmethod
def susceptibility(omega, tau, *args):
return 1-omega*kww_sin(omega, tau, args[0]) + 1j*omega*kww_cos(omega, tau, args[0])
@adjust_dims
def susceptibility(omega, tau, beta):
return 1-omega*kww_sin(omega, tau, beta) + 1j*omega*kww_cos(omega, tau, beta)
@staticmethod
def specdens(omega, tau, *args):
return kww_cos(omega, tau, args[0])
@adjust_dims
def specdens(omega, tau, beta):
return kww_cos(omega, tau, beta)
@staticmethod
def mean(*args):
tau, beta = args
def mean(tau, beta):
return tau/beta * gamma(1 / beta)
@staticmethod
def logmean(*args):
tau, beta = args
def logmean(tau, beta):
return (1-1/beta) * Eu + np.log(tau)
@staticmethod
def max(*args):
tau, beta = args
def max(tau, beta):
return (1.7851 - 0.87052*beta - 0.028836*beta**2 + 0.11391*beta**3) * tau

View File

@ -2,6 +2,9 @@ from multiprocessing import Pool, cpu_count
from itertools import product
import numpy as np
from nmreval.lib.decorator import adjust_dims
try:
from scipy.integrate import simpson
except ImportError:
@ -25,8 +28,7 @@ class LogGaussian(Distribution):
return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma
@staticmethod
def correlation(t, tau0, *args):
sigma = args[0]
def correlation(t, tau0, sigma: float):
_t = np.atleast_1d(t)
_tau = np.atleast_1d(tau0)
@ -40,8 +42,7 @@ class LogGaussian(Distribution):
return ret_val.squeeze()
@staticmethod
def susceptibility(omega, tau0, *args):
sigma = args[0]
def susceptibility(omega, tau0, sigma: float):
_omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0)
@ -56,8 +57,7 @@ class LogGaussian(Distribution):
return ret_val.squeeze()
@staticmethod
def specdens(omega, tau0, *args):
sigma = args[0]
def specdens(omega, tau0, sigma):
_omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0)
@ -78,7 +78,7 @@ class LogGaussian(Distribution):
def _integrate_process_1(args):
omega_i, tau_j, sigma = args
area = quad(_integrand_freq_imag_high, 0, 50, args=(omega_i, tau_j, sigma))[0]
area, _ = quad(_integrand_freq_imag_high, 0, 50, args=(omega_i, tau_j, sigma))
area += quad(_integrand_freq_imag_low, -50, 0, args=(omega_i, tau_j, sigma))[0]
return area
@ -102,7 +102,9 @@ def _integrand_time(u, t, tau, sigma):
return LogGaussian.distribution(uu, tau, sigma) * np.exp(-t/uu)
# integrands
def _integrand_freq_imag_low(u, omega, tau, sigma):
# integrand
uu = np.exp(u)
return LogGaussian.distribution(uu, tau, sigma) * omega * uu / (1 + (omega*uu)**2)

View File

@ -2,14 +2,12 @@
# Form implementation generated from reading ui file 'resources/_ui/basewindow.ui'
#
# Created by: PyQt5 UI code generator 5.12.3
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_BaseWindow(object):
def setupUi(self, BaseWindow):
BaseWindow.setObjectName("BaseWindow")
@ -461,6 +459,9 @@ class Ui_BaseWindow(object):
self.toolBar.addAction(self.actionSave)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionMouse_behaviour)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionPrevious)
self.toolBar.addAction(self.actionNext_window)
self.toolbar_edit.addAction(self.action_calc)
self.toolbar_edit.addAction(self.action_mean_t1)
self.toolbar_edit.addAction(self.actionShift)
@ -598,6 +599,7 @@ class Ui_BaseWindow(object):
self.actionLife.setText(_translate("BaseWindow", "Life..."))
self.actionTetris.setText(_translate("BaseWindow", "Not Tetris"))
self.actionUpdate.setText(_translate("BaseWindow", "Look for updates"))
from ..data.datawidget.datawidget import DataWidget
from ..data.point_select import PointSelectWidget
from ..data.signaledit.editsignalwidget import EditSignalWidget

View File

@ -2,19 +2,19 @@
# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui'
#
# Created by: PyQt5 UI code generator 5.12.3
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 319)
Dialog.resize(544, 443)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout")
self.listWidget = QtWidgets.QListWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
@ -23,7 +23,66 @@ class Ui_Dialog(object):
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 1)
self.gridLayout.addWidget(self.listWidget, 1, 0, 2, 1)
self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
self.groupBox_2.setObjectName("groupBox_2")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_2)
self.verticalLayout_3.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_3.setSpacing(3)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.freq_button = QtWidgets.QRadioButton(self.groupBox_2)
self.freq_button.setChecked(True)
self.freq_button.setObjectName("freq_button")
self.buttonGroup = QtWidgets.QButtonGroup(Dialog)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.freq_button)
self.verticalLayout_3.addWidget(self.freq_button)
self.temp_button = QtWidgets.QRadioButton(self.groupBox_2)
self.temp_button.setObjectName("temp_button")
self.buttonGroup.addButton(self.temp_button)
self.verticalLayout_3.addWidget(self.temp_button)
self.gridLayout.addWidget(self.groupBox_2, 1, 1, 1, 1)
self.groupBox = QtWidgets.QGroupBox(Dialog)
self.groupBox.setObjectName("groupBox")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.eps_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.eps_checkBox.setChecked(True)
self.eps_checkBox.setObjectName("eps_checkBox")
self.verticalLayout_2.addWidget(self.eps_checkBox)
self.modul_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.modul_checkBox.setObjectName("modul_checkBox")
self.verticalLayout_2.addWidget(self.modul_checkBox)
self.cond_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.cond_checkBox.setObjectName("cond_checkBox")
self.verticalLayout_2.addWidget(self.cond_checkBox)
self.loss_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.loss_checkBox.setObjectName("loss_checkBox")
self.verticalLayout_2.addWidget(self.loss_checkBox)
self.line = QtWidgets.QFrame(self.groupBox)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.verticalLayout_2.addWidget(self.line)
self.cap_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.cap_checkBox.setObjectName("cap_checkBox")
self.verticalLayout_2.addWidget(self.cap_checkBox)
self.temp_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.temp_checkBox.setObjectName("temp_checkBox")
self.verticalLayout_2.addWidget(self.temp_checkBox)
self.time_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.time_checkBox.setObjectName("time_checkBox")
self.verticalLayout_2.addWidget(self.time_checkBox)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem)
self.gridLayout.addWidget(self.groupBox, 2, 1, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
self.label = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
@ -31,46 +90,31 @@ class Ui_Dialog(object):
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.label_2 = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.eps_checkBox = QtWidgets.QCheckBox(Dialog)
self.eps_checkBox.setChecked(True)
self.eps_checkBox.setObjectName("eps_checkBox")
self.verticalLayout.addWidget(self.eps_checkBox)
self.modul_checkBox = QtWidgets.QCheckBox(Dialog)
self.modul_checkBox.setObjectName("modul_checkBox")
self.verticalLayout.addWidget(self.modul_checkBox)
self.cond_checkBox = QtWidgets.QCheckBox(Dialog)
self.cond_checkBox.setObjectName("cond_checkBox")
self.verticalLayout.addWidget(self.cond_checkBox)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.gridLayout.addLayout(self.verticalLayout, 1, 1, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 2)
self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.freq_button, self.temp_button)
Dialog.setTabOrder(self.temp_button, self.eps_checkBox)
Dialog.setTabOrder(self.eps_checkBox, self.modul_checkBox)
Dialog.setTabOrder(self.modul_checkBox, self.cond_checkBox)
Dialog.setTabOrder(self.cond_checkBox, self.listWidget)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Read BDS data"))
self.label.setText(_translate("Dialog", "Found temperatures"))
self.label_2.setText(_translate("Dialog", "Read as:"))
self.groupBox_2.setTitle(_translate("Dialog", "X Axis"))
self.freq_button.setText(_translate("Dialog", "Frequency"))
self.temp_button.setText(_translate("Dialog", "Temperature"))
self.groupBox.setTitle(_translate("Dialog", "Y Axis"))
self.eps_checkBox.setText(_translate("Dialog", "Permittivity ε"))
self.modul_checkBox.setText(_translate("Dialog", "Modulus 1/ε"))
self.cond_checkBox.setText(_translate("Dialog", "Conductivity iεω"))
self.loss_checkBox.setText(_translate("Dialog", "Loss factor tan(δ)"))
self.cap_checkBox.setText(_translate("Dialog", "Capacity"))
self.temp_checkBox.setText(_translate("Dialog", "Meas. temperature"))
self.time_checkBox.setText(_translate("Dialog", "Meas. time"))
self.label.setText(_translate("Dialog", "Found entries"))

View File

@ -599,10 +599,13 @@ class SignalContainer(ExperimentContainer):
if isinstance(self._data, BDS):
self.mode = 'bds'
if sym_kwargs['symbol'] is None and line_kwargs['style'] is None:
sym_kwargs['symbol'] = next(PointContainer.symbols)
line_kwargs['style'] = LineStyle.No
if len(self._data) <= 91:
sym_kwargs['symbol'] = next(PointContainer.symbols)
line_kwargs['style'] = LineStyle.No
else:
line_kwargs['style'] = LineStyle.Solid
sym_kwargs['symbol'] = SymbolStyle.No
elif isinstance(self._data, Signal):
if line_kwargs['style'] is None and sym_kwargs['symbol'] is None:
@ -617,7 +620,7 @@ class SignalContainer(ExperimentContainer):
raise TypeError('Unknown class %s, should be FID, Spectrum, or BDS.' % type(self._data))
for mode in ['real', 'imag']:
if mode == 'imag':
if mode == 'imag' and line_kwargs['style'] != LineStyle.No:
line_kwargs['style'] = LineStyle.Dashed
self.setSymbol(mode=mode, **sym_kwargs)
self.setLine(mode=mode, **line_kwargs)

View File

@ -591,6 +591,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
if item_dic:
dic['items'].append(item_dic)
print(dic)
return dic
def get_state(self) -> dict:

View File

@ -1,7 +1,6 @@
from ...io.bds_reader import BDSReader
from ..Qt import QtCore, QtWidgets
from .._py.bdsdialog import Ui_Dialog
from nmreval.io.bds_reader import BDSReader
from nmreval.gui_qt.Qt import QtCore, QtWidgets
from nmreval.gui_qt._py.bdsdialog import Ui_Dialog
class QBDSReader(QtWidgets.QDialog, Ui_Dialog):
@ -11,22 +10,39 @@ class QBDSReader(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, fname: str = None, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.reader = None
self.fname = fname
if fname is not None:
if self.fname is not None:
self.reader = BDSReader(fname)
self.setup_gui()
def __call__(self, fname: str):
self.reader = BDSReader(fname)
self.fname = fname
self.setup_gui()
return self
def setup_gui(self):
self.listWidget.clear()
self.label.setText(f'Found entries for {self.reader.fname.name}')
for temp in self.reader.set_temp:
item = QtWidgets.QListWidgetItem(str(temp))
self.make_list(True)
@QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked')
def change_list(self, bttn: QtWidgets.QAbstractButton):
self.make_list(bttn == self.freq_button)
def make_list(self, use_freq: bool) -> None:
self.listWidget.clear()
if use_freq:
secondary = self.reader.set_temp
else:
secondary = self.reader.frequencies
for x2 in secondary:
item = QtWidgets.QListWidgetItem(f'{x2:.8g}')
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable)
item.setCheckState(QtCore.Qt.Checked)
self.listWidget.addItem(item)
@ -34,19 +50,26 @@ class QBDSReader(QtWidgets.QDialog, Ui_Dialog):
def accept(self):
data = []
temps = []
x2 = []
for i in range(self.listWidget.count()):
item = self.listWidget.item(i)
if item.checkState() == QtCore.Qt.Checked:
temps.append(i)
x2.append(i)
for (mode, cb) in [('epsilon', self.eps_checkBox),
('sigma', self.cond_checkBox),
('modulus', self.modul_checkBox)]:
xmode = 'freq' if self.freq_button.isChecked() else 'temp'
for (mode, cb) in [
('epsilon', self.eps_checkBox),
('sigma', self.cond_checkBox),
('modulus', self.modul_checkBox),
('capacity', self.cap_checkBox),
('time', self.time_checkBox),
('sample_temp', self.temp_checkBox),
('loss_factor', self.loss_checkBox)
]:
if cb.checkState() == QtCore.Qt.Checked:
values = self.reader.export(mode=mode)
for t in temps:
values = self.reader.export(ymode=mode, xmode=xmode)
for t in x2:
data.append(values[t])
self.data_read.emit(data)
@ -60,7 +83,8 @@ if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
reader = QBDSReader()
reader('/autohome/dominik/nmreval/testdata/ZWEITECHANCE_EGD4H2O.EPS')
# reader('/autohome/dominik/nmreval/testdata/ZWEITECHANCE_EGD4H2O.EPS')
# reader('/autohome/dominik/nmreval/testdata/MZ-ME/CHLOROFORM_2017-02-06.EPS')
reader('/autohome/dominik/nmreval/testdata/SBA_5P4NM_ISOCHRON_150-300K_18-03-22.EPS')
reader.show()
sys.exit(app.exec())

View File

@ -127,13 +127,13 @@ def pgitem_to_dic(item):
item_dic['symbolcolor'] = Colors.Black
else:
item_dic['symbol'] = SymbolStyle.from_str(opts['symbol'])
item_dic['symbolcolor'] = Colors.from_rgb(*opts['symbolBrush'].color().getRgbF()[:3])
item_dic['symbolcolor'] = Colors.from_rgb(*opts['symbolBrush'].color().getRgbF()[:3], normed=True)
pen = opts['pen']
if pen is not None:
item_dic['linestyle'] = LineStyle(pen.style())
item_dic['linecolor'] = Colors.from_rgb(*pen.color().getRgbF()[:3])
item_dic['linecolor'] = Colors.from_rgb(*pen.color().getRgbF()[:3], normed=True)
item_dic['linewidth'] = pen.widthF()
else:
item_dic['linestyle'] = LineStyle.No

View File

@ -186,10 +186,7 @@ class SelectionWidget(QtWidgets.QWidget):
@property
def value(self):
try:
return float(self.options[str(self.comboBox.currentText())])
except ValueError:
return str(self.options[str(self.comboBox.currentText())])
return {self.argname: self.options[self.comboBox.currentText()]}
def get_parameter(self):
return str(self.comboBox.currentText())

View File

@ -562,7 +562,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.management.skip_points(**dial.get_arguments())
@QtCore.pyqtSlot(str, name='on_action_coup_calc_triggered')
@QtCore.pyqtSlot(name='on_action_coup_calc_triggered')
def coupling_dialog(self):
dialog = QCoupCalcDialog(self)
dialog.show()

View File

@ -977,10 +977,10 @@ class UpperManagement(QtCore.QObject):
sd.convert(_x, *sd_param, from_=opts['tau_type'], to_='raw')
relax = Relaxation()
relax.distribution(sd, parameter=sd_param, keywords=opts['sd_param'][1])
relax.set_distribution(sd, parameter=sd_param, keywords=opts['sd_param'][1])
cp_param = [self.data[p].y.real if isinstance(p, str) else p for p in opts['cp_param'][0]]
relax.coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1])
relax.set_coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1])
if opts['out'] == 't1':
y = relax.t1(x2, _x)

View File

@ -1,4 +1,4 @@
from ...nmr.couplings import *
from ...nmr.coupling import *
from ...utils.text import convert
from ..Qt import QtWidgets, QtCore
@ -11,7 +11,7 @@ class QCoupCalcDialog(QtWidgets.QDialog, Ui_coupling_calc_dialog):
super().__init__(parent)
self.setupUi(self)
self.cp = [Quadrupolar, QuadrupolarQCC, Czjzek, HeteroDipolar, HomoDipolar]
self.cp = [Quadrupolar, Czjzek, HeteroDipolar, HomoDipolar]
for cp in self.cp:
self.comboBox.addItem(cp.name)
@ -62,7 +62,7 @@ class QCoupCalcDialog(QtWidgets.QDialog, Ui_coupling_calc_dialog):
for pp in self._coupling_parameter:
p.append(pp.value)
self.label.setText('Coupling constant: %.8g' % self.cp[self.comboBox.currentIndex()].func(*p))
self.label.setText('Coupling constant: %.8g' % self.cp[self.comboBox.currentIndex()].relax(*p))
if __name__ == '__main__':

View File

@ -1,6 +1,6 @@
from typing import List, Tuple
from ...nmr.couplings import *
from ...nmr.coupling import *
from ...distributions import ColeCole, ColeDavidson, HavriliakNegami
from ...utils import pi
from ...utils.text import convert
@ -20,7 +20,7 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
self.graphs = {}
self.specdens = [ColeCole, ColeDavidson, HavriliakNegami]
self.coupling = [Quadrupolar, QuadrupolarQCC, Czjzek, HomoDipolar]
self.coupling = [Quadrupolar, Czjzek, HomoDipolar]
self.tau_parameter = []
for line_edit in [self.ea_lineEdit, self.tau0_lineEdit, self.start_lineEdit, self.stop_lineEdit,

View File

@ -4,7 +4,7 @@ from pyqtgraph import mkBrush, mkPen
from ..lib.pg_objects import PlotItem
from ...data.points import Points
from ...nmr.relaxation import RelaxationEvaluation
from ...nmr.couplings import *
from ...nmr.coupling import *
from ...distributions import *
from ..Qt import QtCore, QtWidgets, QtGui
@ -37,7 +37,7 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
self.specdens_combobox.currentIndexChanged.connect(self.update_specdens)
self.cp_parameter = []
self.coupling = [Quadrupolar, QuadrupolarQCC, Czjzek, HomoDipolar, Constant]
self.coupling = [Quadrupolar, Czjzek, HomoDipolar, Constant]
for i in self.coupling:
self.coupling_combobox.addItem(i.name)
self.coupling_combobox.currentIndexChanged.connect(self.update_coupling)
@ -116,7 +116,7 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
self.verticalLayout_3.addWidget(_temp)
self.sd_parameter.append(_temp)
self.t1calculator.distribution(self.sdmodels[idx])
self.t1calculator.set_distribution(self.sdmodels[idx])
self.update_model()
@QtCore.pyqtSlot()
@ -159,6 +159,7 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
_temp.valueChanged.connect(self.update_coupling_parameter)
_temp.stateChanged.connect(self.update_coupling_parameter)
self.cp_parameter.append(_temp)
print(self.cp_parameter)
if self.coupling[idx].choice is not None:
for (name, kw_name, opts) in self.coupling[idx].choice:
@ -172,10 +173,14 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
@QtCore.pyqtSlot()
def update_coupling_parameter(self):
new_p = []
new_kw = {}
for pp in self.cp_parameter:
new_p.append(pp.value)
if isinstance(pp, FormWidget):
new_p.append(pp.value)
else:
new_kw.update(pp.value)
new_coupling = self.coupling[self.coupling_combobox.currentIndex()]
self.t1calculator.coupling(new_coupling)
self.t1calculator.set_coupling(new_coupling, parameter=new_p, keywords=new_kw)
self.update_model()
@ -253,30 +258,33 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
'Only one can be determined from a minimum.')
return
notfix = ()
notfix = None
var_idx = 0
if not all(sd_fix):
notfix = ('distribution', sd_fix.index(False))
notfix = 'distribution'
var_idx = sd_fix.index(False)
if not all(cp_fix):
notfix = ('coupling', cp_fix.index(False))
notfix = 'coupling'
var_idx = cp_fix.index(False)
if not np.isfinite(self.t1calculator.t1min[1]):
return
mini = np.nan
with busy_cursor():
calc_stretching, mini = self.t1calculator.increase(notfix, height=self.minimum[1],
omega=2*np.pi*self.frequency,
dist_parameter=sd_args, prefactor=cp_args,
coupling_kwargs=cp_kwargs)
calc_stretching, mini = self.t1calculator.get_increase(height=self.minimum[1],
idx=var_idx, mode=notfix,
omega=2*np.pi*self.frequency,
dist_parameter=sd_args, prefactor=cp_args,
coupling_kwargs=cp_kwargs)
self.label_t1min.setText(f'{mini:.4g} s')
if notfix:
forms = self.sd_parameter if notfix[0] == 'distribution' else self.cp_parameter
forms[notfix[1]].blockSignals(True)
forms[notfix[1]].value = f'{calc_stretching:.4g}'
forms[notfix[1]].blockSignals(False)
forms = self.sd_parameter if notfix == 'distribution' else self.cp_parameter
forms[var_idx].blockSignals(True)
forms[var_idx].value = f'{calc_stretching:.4g}'
forms[var_idx].blockSignals(False)
@QtCore.pyqtSlot(name='on_calc_pushButton_clicked')
def calculate_correlations(self):
@ -317,7 +325,7 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
cp_args.append(p.value)
except AttributeError:
# SelectionWidget has no isChecked()
cp_kwargs[p.argname] = p.value
cp_kwargs.update(p.value)
return cp_args, cp_kwargs, cp_fix

View File

@ -2,10 +2,11 @@ import re
import struct
import warnings
from pathlib import Path
from typing import List
import numpy as np
from ..data.signals import Signal
from ..data import BDS, Points
MAGIC_RE = re.compile(b'NOVOCONTROL DK-RESULTS')
@ -27,6 +28,9 @@ class BDSReader:
return self
def __getitem__(self, val):
return self.opts[val]
def _parse_file(self):
self.opts = {}
with self.fname.open(mode='rb') as f:
@ -43,7 +47,7 @@ class BDSReader:
f.seek(1, 1)
self.opts['electrode thickness'] = struct.unpack('f', f.read(4))[0]
self.opts['parameter'] += struct.unpack('3f', f.read(12))
self.opts['parameter'] += struct.unpack('3f', f.read(12)) # 1, 0.05, 0.06, 0.02
f.seek(2, 1)
self.opts['capacity'] = struct.unpack('f', f.read(4))[0]
@ -56,43 +60,51 @@ class BDSReader:
self.opts['C_0'] = (self.opts['area']-self.opts['spacer area']) / self.opts['thickness'] * 8.8541878128e-12
f.seek(158)
name_length = struct.unpack('b', f.read(1))[0]
name_length = struct.unpack('i', f.read(4))[0]
f.seek(164)
f.seek(2, 1)
b = f.read(name_length)
self.opts['name'] = str(struct.unpack(f'<{name_length}s', b)[0][:-1], encoding='utf-8')
_ = struct.unpack('h', f.read(2))
freq_values_length = struct.unpack('b', f.read(1))[0]
freq_values_length = struct.unpack('i', f.read(4))[0]
f.seek(7, 1)
f.seek(4, 1)
self.x = np.empty(freq_values_length)
for freqs in range(freq_values_length):
self.x[freqs] = struct.unpack('f', f.read(4))[0]
(self.x[freqs],) = struct.unpack('f', f.read(4))
_ = struct.unpack('h', f.read(2))
length_temps = struct.unpack('b', f.read(1))[0]
_ = struct.unpack('h', f.read(2)) # TODO if no temperature set this is not 400 and rest breaks
length_temps = struct.unpack('i', f.read(4))[0]
f.seek(7, 1)
f.seek(4, 1)
self.opts['temperatures'] = np.array(struct.unpack('f' * length_temps, f.read(4*length_temps)))
f.seek(110, 1)
date_length = (struct.unpack('b', f.read(1)))[0]
date_length = (struct.unpack('i', f.read(4)))[0]
f.seek(5, 1)
f.seek(2, 1)
self.opts['date'] = str(struct.unpack(f'<{date_length}s', f.read(date_length))[0][:-1],
encoding='utf-8')
f.seek(47, 1)
# there are 9 different values per frequency and temperature
_y = []
actual_temps_length = 0
read_length = 9 * freq_values_length
read_length = 9 * freq_values_length # per entry 9 * 4 byte
while True:
# catch error if measurement was aborted
try:
_y += struct.unpack('f' * read_length, f.read(4*read_length))
y_i = struct.unpack('f' * read_length, f.read(4*read_length))
if any([y_ii != 0.0 for y_ii in y_i[-3:]]):
# the last three should be zero
break
_y += y_i
actual_temps_length += 1
if actual_temps_length == length_temps:
break
except struct.error:
break
@ -100,79 +112,86 @@ class BDSReader:
warnings.warn('Number of set temperatures does not match number of data points')
_y = np.array(_y).reshape((actual_temps_length, freq_values_length, 9))
print(_y.shape)
print(f.tell())
# last 3 entries are zero, save only 6
# Z.imag*omega), Z.real, meas.time, meas. temp., ac voltage, dc voltage
self.y = np.transpose(_y[:, :, :6], (2, 0, 1))
def export(self, mode='epsilon') -> list:
if mode == 'epsilon':
eps = self.epsilon()
elif mode == 'sigma':
eps = self.sigma()
elif mode == 'modulus':
eps = self.modulus()
else:
raise ValueError(f'Unknown mode {mode}, not "epsilon", "sigma", or "modulus".')
ret_val = []
def export(self, ymode: str = 'epsilon', xmode: str = 'freq') -> List[BDS]:
y = getattr(self, ymode)()
useful_info = {
'voltage': self.opts['voltage'],
'diameter': self.opts['diameter'],
'area': self.opts['area'],
'thickness': self.opts['thickness'],
'mode': mode
'mode': ymode
}
for i, temps in enumerate(self.temperature):
ret_val.append(Signal(x=self.frequencies, y=eps[0][i, :] + 1j*eps[1][i, :],
name=f'{temps:.2f}', value=temps, **useful_info))
if xmode == 'freq':
fmt = '.2f'
x2 = self.temperature
x = self.frequencies
elif xmode == 'temp':
fmt = '.2f'
x2 = self.frequencies
x = self.temperature
y = y.T
else:
raise ValueError(f'Unknown mode {xmode!r}, use `freq` or `temp`.')
ret_val = []
for i, label in enumerate(x2):
y_i = y[i]
if np.allclose(y_i.imag, 0):
ret_val.append(Points(x=x, y=y_i,
name=f'{label:{fmt}}', value=label, **useful_info))
else:
ret_val.append(BDS(x=x, y=y_i,
name=f'{label:{fmt}}', value=label, **useful_info))
return ret_val
def __getitem__(self, val):
return self.opts[val]
def capacity(self):
# NOTE: nobody ever used edge correction, so we ignore this option entirely
return self.y[0] - self.opts['capacity'] - 1j / self.y[1] / (self.frequencies*2*np.pi)
def sample_temp(self):
return self.y[2]
def time(self):
return self.y[3]
def sigma(self):
# sigma^* = i * omega * eps_0 * (eps^* - 1)
conv = 0.01 * 8.8541878128e-12 * 2*np.pi*self.x # factor 0.01: sigma in S/cm
return conv * (self.epsilon() - 1)
def epsilon(self):
# eps^* = Cp^* d/A/eps_0
c = self.capacity()
return (c.real - 1j * c.imag) / self.opts['C_0']
def modulus(self):
# M^* = 1/eps^*
return np.conjugate(1/self.epsilon())
def loss_factor(self):
eps = self.epsilon()
return eps.imag / eps.real
@property
def set_temp(self):
return self.opts['temperatures'][:len(self.temperature)]
@property
def probe_temp(self):
return self.y[2]
@property
def temperature(self):
return self.probe_temp.mean(axis=1)
return self.sample_temp().mean(axis=1)
@property
def frequencies(self):
return self.x
def capacity(self):
# NOTE: nobody ever used edge correction, so we ignore this option entirely
return self.y[0] - self.opts['capacity'], -1./self.y[1] / (self.frequencies*2*np.pi)
def voltage(self):
return self.y[5]
def sigma(self):
# sigma^* = i * omega * eps_0 * (eps^* - 1)
eps_r, eps_i = self.epsilon()
conv = 0.01 * 8.8541878128e-12 * 2*np.pi*self.x # factor 0.01: sigma in S/cm
return conv * eps_i, conv * (eps_r-1)
def epsilon(self):
# eps^* = Cp^* d/A/eps_0
cr, ci = self.capacity()
return cr / self.opts['C_0'], -ci / self.opts['C_0']
def modulus(self):
# M^* = 1/eps^*
eps_r, eps_i = self.epsilon()
magn = eps_r ** 2 + eps_i ** 2
return eps_r / magn, eps_i / magn
def loss_factor(self):
eps_r, eps_i = self.epsilon()
return eps_i / eps_r, None

30
nmreval/lib/decorator.py Normal file
View File

@ -0,0 +1,30 @@
from functools import wraps
from typing import Callable
import numpy as np
def adjust_dims(func) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
"""
ALlow outer product for omega*tau and remove redundant dimension afterwards
"""
x1, x2, *rest = args
x1 = np.asanyarray(x1)
x2 = np.asanyarray(x2)
if len(x1.shape) > 1 or len(x2.shape) > 1:
raise ValueError('Only numbers or 1d-arrays are supported')
result = func(x1[..., None], x2[None, ...], *rest, **kwargs)
if result.ndim == 1:
return result[0]
else:
return result.squeeze()
return wrapper
#
# return func

View File

@ -3,7 +3,6 @@ from typing import TypeVar
from ..math.mittagleffler import mlf
ArrayLike = TypeVar('ArrayLike')

View File

@ -19,7 +19,8 @@ def _kww_low(w, beta, kappa):
ln_w = np.log(w[:, np.newaxis])
ln_a_k = scipy.special.gammaln((k+1) / beta) - scipy.special.gammaln(k+1) + k*ln_w
a_k = np.exp(ln_a_k)
with np.errstate(over='ignore'):
a_k = np.exp(ln_a_k)
y_n = (sign * a_k).cumsum(axis=1)
y_n = np.nan_to_num(y_n)
@ -45,7 +46,7 @@ def kwws_low(w, beta):
def _kww_high(w, beta, kappa):
if beta < 0.1 or beta > 2.0:
raise ValueError("invalid call to kww_hig: beta out of range")
raise ValueError("invalid call to kww_high: beta out of range")
ln_omega = np.log(w[:, np.newaxis])
k = np.atleast_2d(np.arange(kappa, __max_terms+kappa))
@ -89,7 +90,7 @@ def kwwc_high(w, beta):
def _kww_mid(w, beta, kappa):
if beta < 0.1 or beta > 2.0:
raise ValueError("invalid call to kww_mid: beta out of range")
elif any(w < 0):
elif np.any(w < 0):
raise ValueError("invalid call to kww_mid: w out of range")
if kappa and beta == 2:
@ -203,19 +204,19 @@ def _kwws_lim_hig(b):
def kww_cos(w, tau, beta):
r"""
Calculate \int_0^\infty dt cos(w*t) exp(-(t/tau)^beta)
:param w: array-like
:param tau: float
:param beta: float; 0.1 < beta <= 2
:return: array (w.shape)
"""
Args:
w:
tau:
beta:
Returns:
"""
# check input data
if beta < 0.1:
raise ValueError("kww: beta smaller than 0.1")
if beta > 2.0:
raise ValueError("kww: beta larger than 2.0")
if not (0.1 <= beta <= 2.):
raise ValueError('KWW beta is not inside range 0.1-2')
if beta == 1:
return tau/(1 + (w*tau)**2)
@ -246,11 +247,8 @@ def kww_cos(w, tau, beta):
# \int_0^\infty dt sin(w*t) exp(-(t/tau)^beta)
def kww_sin(w, tau, beta):
# check input data
if beta < 0.1:
raise ValueError("kww: beta smaller than 0.1")
if beta > 2.0:
raise ValueError("kww: beta larger than 2.0")
if not (0.1 <= beta <= 2.):
raise ValueError('KWW beta is not inside range 0.1-2')
if beta == 1:
return w*tau**2/(1+(w*tau)**2)

View File

@ -1,10 +1,13 @@
from numbers import Number
from typing import TypeVar, Union
import numpy as np
from math import sqrt, log, exp, ceil
from scipy.special import gamma
T = TypeVar('T')
__all__ = ['mlf']
# calculate Mittag-Leffler based on MATLAB code
@ -13,7 +16,7 @@ __all__ = ['mlf']
# SIAM Journal of Numerical Analysis, 2015, 53(3), 1350-1369
def mlf(z, a: float, b: float = 1, g: float = 1):
def mlf(z: T, a: float, b: float = 1, g: float = 1) -> T:
if b == 1 and gamma == 1:
if a == 0:
return 1 / (1 - z)
@ -27,15 +30,16 @@ def mlf(z, a: float, b: float = 1, g: float = 1):
if isinstance(z, Number):
return _mlf_single(z, a, b, g)
else:
ret_val = np.zeros_like(z)
for i, zz in enumerate(z):
ret_val = np.zeros(z.size)
for i, zz in enumerate(z.ravel()):
ret_val[i] = _mlf_single(zz, a, b, g)
return ret_val
return ret_val.reshape(z.shape)
def _mlf_single(z, a, b, g):
def _mlf_single(z: Union[int, float, complex], a: float, b: float, g: float):
if abs(z) < 1.0e-15:
ret_val = 1 / gamma(b)
else:

View File

@ -1,3 +1,26 @@
"""
=============
Fit functions
=============
.. currentmodule:: nmreval.models
.. autosummary::
:toctree: generated
:nosignatures:
:template: autosummary/class_with_attributes.rst
Constant
Linear
Parabola
PowerLaw
PowerLawCross
ExpFunc
MittagLeffler
Log
Sine
"""
from .basic import *
from .relaxation import *

View File

@ -1,42 +1,44 @@
"""
***************
Basic functions
***************
Simple basic functions
"""
import numpy as np
from ..lib.utils import ArrayLike
from ..math.mittagleffler import mlf
class Constant:
"""
Constant
.. math::
y = c\cdot x
Args:
x (array_like): Input values
c (float): constant
A boring constant line.
"""
type = 'Basic'
name = 'Constant'
equation = 'C'
params = ['C']
@staticmethod
def func(x, c: float):
def func(x: ArrayLike, c: float) -> ArrayLike:
"""
Constant
.. math::
y = c \cdot x
Args:
x (array-like): Input values
c (float): constant
"""
return c*np.ones(len(x))
class Linear:
"""
Straight line.
.. math::
y = m\cdot x + t
Args:
x (array_like): Input values
m (float): Slope
t (float): Intercept
Slightly more exciting line
"""
type = 'Basic'
name = 'Straight line'
@ -44,7 +46,19 @@ class Linear:
params = ['m', 't']
@staticmethod
def func(x, m: float, t: float):
def func(x: ArrayLike, m: float, t: float) -> ArrayLike:
"""
Straight line.
.. math::
y = m\cdot x + t
Args:
x (array_like): Input values
m (float): Slope
t (float): Intercept
"""
return m*x + t

View File

@ -1,5 +1,9 @@
import numpy as np
from numpy import trapz
try:
from scipy.integrate import simpson
except ImportError:
from scipy.integrate import simps as simpson
from numpy import pi
from ..math.orientations import zcw_spherical as crystallites
@ -29,21 +33,22 @@ class Pake:
_x -= 0.5*_x[-1]
if broad == 'l':
apd = 2 * sigma / (4 * _x**2 + sigma**2) / np.pi
apd = 2 * sigma / (4 * _x**2 + sigma**2) / pi
else:
apd = np.exp(-4 * np.log(2) * (_x/sigma)**2) * 2 * np.sqrt(np.log(2) / np.pi) / sigma
apd = np.exp(-4 * np.log(2) * (_x/sigma)**2) * 2 * np.sqrt(np.log(2) / pi) / sigma
ret_val = np.convolve(s, apd, mode='same')
else:
ret_val = s
omega_1 = np.pi/2/t_pulse
attn = omega_1 * np.sin(t_pulse*np.sqrt(omega_1**2+0.5*(2*np.pi*x)**2)) / np.sqrt(omega_1**2+(np.pi*x)**2)
omega_1 = pi/2/t_pulse
attn = omega_1 * np.sin(t_pulse*np.sqrt(omega_1**2+0.5*(2*pi*x)**2)) / \
np.sqrt(omega_1**2+(np.pi*x)**2)
ret_val *= attn
return c * ret_val / trapz(ret_val, x)
return c * ret_val / simpson(ret_val, x)
class CSA:
@ -69,14 +74,14 @@ class CSA:
_x = np.arange(len(x)) * (x[1] - x[0])
_x -= 0.5 * _x[-1]
if broad == 'l':
apd = 2 * sigma / (4*_x**2 + sigma**2) / np.pi
apd = 2 * sigma / (4*_x**2 + sigma**2) / pi
else:
apd = np.exp(-4 * np.log(2) * (_x / sigma) ** 2) * 2 * np.sqrt(np.log(2) / np.pi) / sigma
apd = np.exp(-4 * np.log(2) * (_x / sigma) ** 2) * 2 * np.sqrt(np.log(2) / pi) / sigma
ret_val = np.convolve(s, apd, mode='same')
else:
ret_val = s
return c * ret_val / trapz(ret_val, x)
return c * ret_val / simpson(ret_val, x)
class SecCentralLine:

View File

@ -0,0 +1,42 @@
"""
=========================================
NMR specific methods (:mod:`nmreval.nmr`)
=========================================
Each class provides a distribution of correlation times, correlation
function, spectral density, susceptibility, and calculation of different
types of correlation times.
.. currentmodule:: nmreval.nmr
Relaxation
**********
Calculate spin-lattice and spin-spin relaxation, and evaluate T1 minima for correlation times.
.. autosummary::
:toctree: generated/
:nosignatures:
Relaxation
RelaxationEvaluation
Coupling constants (:mod:`nmreval.nmr.coupling`)
************************************************
.. autosummary::
:toctree: generated/
:template: autosummary/class_with_attributes.rst
:nosignatures:
~coupling.Quadrupolar
~coupling.HeteroDipolar
~coupling.HomoDipolar
~coupling.CSA
~coupling.Constant
~coupling.Czjzek
"""
from .relaxation import Relaxation, RelaxationEvaluation

236
nmreval/nmr/coupling.py Normal file
View File

@ -0,0 +1,236 @@
"""
NMR coupling values
===================
This is a compilation of NMR prefactors for calculation of relaxation times.
A note of caution is
"""
import abc
from math import log
from collections import OrderedDict
from ..utils.constants import gamma_full, hbar_joule, pi, gamma, mu0
__all__ = ['Quadrupolar', 'Czjzek', 'HeteroDipolar',
'HomoDipolar', 'Constant', 'CSA']
class Coupling(metaclass=abc.ABCMeta):
name = 'coupling'
parameter = None
choice = None
unit = None
equation = ''
@staticmethod
@abc.abstractmethod
def relax(*args, **kwargs):
raise NotImplementedError
class Quadrupolar(Coupling):
""" Quadrupolar interaction
"""
name = 'Quadrupolar'
parameter = [r'\delta/C_{q}', r'\eta']
unit = ['Hz', '']
equation = r'3/50 \pi^{2} C_{q}^{2} (1+\eta^{2}/3) (2I+3)/(I^{2}(2I-1))'
choice = [
('Spin', 'spin', OrderedDict([('1', 1), ('3/2', 1.5), ('2', 2), ('5/2', 2.5)])),
('Type', 'is_delta', {'Anisotropy': True, 'QCC': False})
]
@staticmethod
def relax(value: float, eta: float, spin: float = 1, is_delta: bool = True) -> float:
r"""Calculates prefactor for quadrupolar relaxation
.. math::
\frac{3}{200} (2\pi C_q)^2 \left(1+\frac{\eta^{2}}{3}\right) \frac{2I+3}{I^{2}(2I-1)}
If input is given as anisotropy :math:`\delta` then :math:`C_q` is calculated via
.. math::
C_q = \delta \frac{4I(2I-1)}{3(2m-1)}
where `m` is 1 for integer spin and 3/2 else.
Args:
value (float): Quadrupolar coupling constant in Hz.
eta (float): Asymmetry parameter
spin (float): Spin `I`. Default is 1.
is_delta (bool): If True, uses value as anisotropy :math:`\delta` else as QCC. Default is True.
"""
m = 1 if (2*spin) % 2 == 0 else 1.5
if is_delta:
value *= 4*spin*(2*spin-1) / (3*(2*m-1))
return 0.06 * (2*spin+3) * (pi*value)**2 * (1 + eta**2 / 3) / (spin**2 * (2*spin-1))
@staticmethod
def convert(in_value: float, to: str = 'aniso', spin: float = 1) -> float:
r"""Convert between QCC and anisotropy.
Relation between quadrupole coupling constant :math:`C_q` and anisotropy :math:`\delta` is given by
.. math::
\delta = \frac{3(2m-1)}{4I(2I-1)} C_q
where `m` is 1 for integer spin and 3/2 else.
Args:
in_value (float): Input value
to (str, {`aniso`, `qcc`}): Direction of conversion. Default is `aniso`
spin (float): Spin number `I`. Default is 1.
Returns:
Converted value
"""
if to not in ['aniso', 'qcc']:
raise ValueError(f'Cannot convert to {to} use `aniso` or `qcc`')
m = 1 if (2 * spin) % 2 == 0 else 1.5
fac = 4 * spin * (2 * spin - 1) / (3 * (2 * m - 1))
if to == 'delta':
return in_value * fac
else:
return in_value / fac
class HomoDipolar(Coupling):
"""Homonuclear dipolar coupling"""
name = 'Homonuclear Dipolar'
parameter = ['r']
unit = ['m']
choice = [
(r'\gamma', 'nucleus', {k: k for k in gamma}),
('Spin', 'spin', OrderedDict([('1/2', 0.5), ('1', 1), ('3/2', 1.5), ('2', 2), ('5/2', 2.5), ('3', 3)])),
]
equation = r'2/5 [\mu_{0}/(4\pi) \hbar \gamma^{2}/r^{3}]^{2} I(I+1)'
@staticmethod
def relax(r: float, nucleus: str = '1H', spin: float = 0.5) -> float:
"""Calculate prefactor for homonuclear dipolar relaxation.
Args:
r (float): Distance in m.
nucleus (str): Name of isotope. Default is `1H`
spin (float): Spin `I` of isotope. Default is 1/2
"""
if nucleus not in gamma_full:
raise KeyError(f'Unknown nucleus {nucleus}')
try:
coupling = mu0 / (4*pi) * hbar_joule * gamma_full[nucleus]**2 / (r+1e-34)**3
except ZeroDivisionError:
return 1e318
coupling **= 2
coupling *= 2 * spin * (spin+1) / 5
return coupling
class HeteroDipolar(Coupling):
"""Heteronuclear dipolar coupling"""
name = 'Heteronuclear Dipolar'
parameter = ['r']
unit = ['m']
choice = [
(r'\gamma_{1}', 'nucleus1', {k: k for k in gamma}),
(r'\gamma_{2}', 'nucleus2', {k: k for k in gamma}),
('Spin', 'spin', dict([('1/2', 0.5), ('1', 1), ('3/2', 1.5), ('2', 2), ('5/2', 2.5), ('3', 3)]))
]
equation = r'2/15 [\mu_{0}/(4\pi) \hbar \gamma^{2}/r^{3}]^{2} I(I+1)'
@staticmethod
def relax(r: float, nucleus1: str = '1H', nucleus2: str = '19F', spin: float = 0.5) -> float:
r"""Calculate prefactor for feteronuclear dipolar relaxation.
.. math::
\frac{2}{15} \left(\frac{\mu_0}{4\pi} \hbar \frac{\gamma_1 \gamma_2}{r^3} \right)^2 S(S+1)
Args:
r (float): Distance in m.
nucleus1 (str): Name of observed isotope. Default is `1H`
nucleus2 (str): Name of coupled isotope. Default is `19F`
spin (float): Spin `S` of the coupled isotope. Default is 1/2
"""
if nucleus1 not in gamma_full:
raise KeyError(f'Unknown nucleus {nucleus1}')
if nucleus2 not in gamma_full:
raise KeyError(f'Unknown nucleus {nucleus2}')
try:
coupling = mu0 / (4*pi) * hbar_joule * gamma_full[nucleus1]*gamma_full[nucleus2] / r**3
except ZeroDivisionError:
return 1e318
coupling **= 2
coupling *= 2 * spin * (spin+1) / 15
return coupling
class Czjzek(Coupling):
name = 'Czjzek'
parameter = [r'\Sigma']
unit = ['Hz']
choice = [('Spin', 'spin', {'1': 1, '3/2': 1.5, '5/2': 2.5, '7/2': 3.5})]
equation = r'3/[20log(2)] \pi^{2} \Sigma^{2} (2I+3)/(I^{2}(2I-1))'
@staticmethod
def relax(fwhm, spin=1):
return 3*(2+spin+3) * pi**2 * fwhm**2 / 20 / log(2) / spin**2 / (2*spin-1)
class Constant(Coupling):
name = 'Constant'
parameter = ['C']
equation = 'C'
@staticmethod
def relax(c):
return c
class CSA(Coupling):
"""Chemical shift anisotropy"""
name = 'CSA'
equation = r'2/15 \Delta\sigma^{2} (1+\eta^{2}/2)'
parameter = [r'\Delta\sigma', '\eta']
unit = ['ppm', '']
@staticmethod
def relax(sigma: float, eta: float) -> float:
r"""Relaxation prefactor for chemical shift anisotropy.
.. math::
C = \frac{2}{15} \Delta\sigma^2 \left(1+\frac{\eta^2}{3}\right)
.. note::
The influence of the external magnetic field is missing here but
is multiplied in :func:`~nmreval.nmr.Relaxation.t1_csa`
Args:
sigma: Anisotropy (in ppm)
eta: Asymmetry parameter
Reference:
Spiess, H.W.: Rotation of Molecules and Nuclear Spin Relaxation.
In: NMR - Basic principles and progress, Vol. 15, (Springer, 1978)
https://doi.org/10.1007/978-3-642-66961-3_2
"""
return 2 * sigma**2 * (1+eta**2 / 3) / 15

View File

@ -1,102 +0,0 @@
from math import log
from collections import OrderedDict
from ..utils.constants import pi, gamma, mu0, hbar
__all__ = ['Quadrupolar', 'QuadrupolarQCC', 'Czjzek', 'HeteroDipolar', 'HomoDipolar', 'Constant']
class Coupling(object):
name = 'coupling'
parameter = None
choice = None
unit = None
equation = ''
class Quadrupolar(Coupling):
name = 'Quadrupolar'
parameter = [r'\delta', r'\eta']
unit = ['Hz', '']
equation = r'24 (2I-1)(2I+3) / (25(6m+3)^{2}) (1+\eta^2/3) \pi^{2} \delta^{2}'
choice = [('Spin', 'spin', OrderedDict([('1', 1), ('3/2', 1.5), ('2', 2),
('5/2', 2.5), ('3', 3), ('7/2', 3.5)]))]
@staticmethod
def func(delta, eta, spin=1):
m = 0 if (2*spin) % 2 == 0 else 0.5
return 24*(2*spin-1)*(2*spin+3) * pi**2 * delta**2 * (1+eta**2 / 3) / 25 / (6*m+3)**2
class QuadrupolarQCC(Coupling):
name = 'Quadrupolar (QCC)'
parameter = ['C_{Q}', r'\eta']
unit = ['Hz', '']
choice = [('Spin', 'spin', {'1': 1, '3/2': 1.5, '5/2': 2.5, '7/2': 3.5})]
equation = r'3/50 \pi^{2} C_{Q}^{2} (1+\eta^{2}/3) (2I+3)/(I^{2}(2I-1))'
@staticmethod
def func(cq, eta, spin=1):
return 0.06 * (2*spin+3)*pi**2 * cq**2 * (1+eta**2 / 3) / spin**2 / (2*spin-1)
class Czjzek(Coupling):
name = 'Czjzek'
parameter = [r'\Sigma']
unit = ['Hz']
choice = [('Spin', 'spin', {'1': 1, '3/2': 1.5, '5/2': 2.5, '7/2': 3.5})]
equation = r'3/[20log(2)] \pi^{2} \Sigma^{2} (2I+3)/(I^{2}(2I-1))'
@staticmethod
def func(fwhm, spin=1):
return 3*(2+spin+3) * pi**2 * fwhm**2 / 20 / log(2) / spin**2 / (2*spin-1)
class HeteroDipolar(Coupling):
name = 'Heteronuclear Dipolar'
parameter = ['r']
unit = ['m']
choice = [(r'\gamma_{1}', 'nucleus1', {k: k for k in gamma.keys()}),
(r'\gamma_{2}', 'nucleus2', {k: k for k in gamma.keys()})]
equation = r'1/10 * [\mu_{0}/(4\pi) * \hbar * \gamma_{1}\gamma_{2}/r^{3}]^{2}'
@staticmethod
def func(r, nucleus1='1H', nucleus2='19F'):
if nucleus1 not in gamma:
raise KeyError('Unknown nucleus {}'.format(nucleus1))
if nucleus2 not in gamma:
raise KeyError('Unknown nucleus {}'.format(nucleus2))
coupling = mu0 / (4*pi) * hbar*gamma[nucleus1]*gamma[nucleus2] / (r+1e-34)**3
coupling **= 2
return 0.1 * coupling
class HomoDipolar(Coupling):
name = 'Homonuclear Dipolar'
parameter = ['r']
unit = ['m']
choice = [(r'\gamma', 'nucleus', {k: k for k in gamma.keys()})]
equation = r'3/10 * [\mu_{0}/(4\pi) * \hbar * \gamma^{2}/r^{3}]^{2}'
@staticmethod
def func(r, nucleus='1H'):
if nucleus not in gamma:
raise KeyError('Unknown nucleus {}'.format(nucleus))
coupling = mu0 / (4*pi) * hbar * gamma[nucleus]**2 / (r+1e-34)**3
coupling **= 2
return 0.3 * coupling
class Constant(Coupling):
name = 'Constant'
parameter = ['C']
equation = 'C'
@staticmethod
def func(c):
return c

View File

@ -1,6 +1,12 @@
from numbers import Number
"""
Relaxation
==========
Classes to calculate spin-lattice and spin-spin relaxation, as well as to evaluate T1 data and calculate correlation times
"""
from pathlib import Path
from typing import Tuple
from typing import Any, Optional, Tuple, Type, Union
from warnings import warn
import numpy as np
@ -8,138 +14,417 @@ from scipy.interpolate import interp1d, Akima1DInterpolator
from scipy.optimize import minimize
from nmreval.lib.utils import ArrayLike
from .coupling import Coupling
from ..distributions.base import Distribution
from ..distributions.debye import Debye as Debye
from ..utils import gamma_full
class Relaxation:
def __init__(self, distribution=None):
self._distribution = distribution
self._dist_parameter = ()
"""
Class to calculate relaxation times.
"""
def __init__(self, distribution: Type[Distribution] = None):
self.distribution = distribution
self.dist_parameter = ()
self._dist_kw = {}
self._coupling = None
self._coup_parameter = ()
self._coup_kw = {}
self.coupling = None
self.coup_parameter = []
self.coup_kw = {}
self._prefactor = 1.
self.prefactor = 1.
def __repr__(self):
if self._distribution is not None:
return str(self._distribution.name)
if self.distribution is not None:
return str(self.distribution.name)
else:
return super().__repr__()
def coupling(self, coupling, parameter: list = None, keywords: dict = None):
self._coupling = coupling
def set_coupling(self, coupling: Union[float, Type[Coupling]],
parameter: Union[tuple, list] = None, keywords: dict = None):
if parameter is not None:
self._coup_parameter = parameter
self.coup_parameter = parameter
if keywords is not None:
self._coup_kw = keywords
self.coup_kw = keywords
if isinstance(self._coupling, Number):
self._prefactor = self._coupling
if isinstance(coupling, float):
self.prefactor = coupling
elif issubclass(coupling, Coupling):
self.coupling = coupling
if self.coup_kw is None:
raise ValueError('Coupling is missing parameter')
self.prefactor = self.coupling.relax(*self.coup_parameter, **self.coup_kw)
else:
try:
self._prefactor = self._coupling.func(*self._coup_parameter, **self._coup_kw)
except TypeError:
pass
raise ValueError(f'`coupling` is not number or of type `Coupling`, found {coupling!r}')
def distribution(self, dist, parameter=None, keywords=None):
self._distribution = dist
def set_distribution(self, dist: Type[Distribution], parameter: Union[tuple, list] = None, keywords: dict = None):
self.distribution = dist
if parameter is not None:
self._dist_parameter = parameter
self.dist_parameter = parameter
if keywords is not None:
self._dist_kw = keywords
def t1(self, omega, tau, *args, mode='bpp', **kwargs):
# defaults to BPP
if mode == 'bpp':
return self.t1_bpp(omega, tau, *args, **kwargs)
elif mode == 'dipolar':
return self.t1_dipolar(omega, tau, *args, **kwargs)
else:
raise AttributeError(f'Unknown mode {mode}. Use either "bpp" or "dipolar".')
def t1_dipolar(self, omega: ArrayLike, tau: ArrayLike, *args,
inverse: bool = True, prefactor: float = None, **kwargs) -> ArrayLike:
"""
def t1(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
mode: str = 'bpp', **kwargs) -> Union[np.ndarray, float]:
r"""
Convenience function
Args:
omega:
tau:
*args:
inverse:
prefactor:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args: Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
mode (str, {`bpp`, `dipolar`, `csa`}): Type of relaxation
- `bpp` : Homonuclear dipolar/quadrupolar interaction :func:`(see here) <nmreval.nmr.Relaxation.t1_bpp>`
- `dipolar` : Heteronuclear dipolar interaction :func:`(see here) <nmreval.nmr.Relaxation.t1_dipolar>`
- `csa` : Chemical shift interaction :func:`(see here) <nmreval.nmr.Relaxation.t1_csa>`
Default is `bpp`
**kwargs:
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
try:
omega_2 = kwargs['omega_coup']
except KeyError:
if kwargs.get('gamma_obs', False):
omega_2 = kwargs['gamma_coup'] / kwargs['gamma_obs'] * omega
else:
raise AttributeError('Unknown second frequency.')
if mode not in ['bpp', 'dipolar', 'csa']:
raise ValueError(f'Unknown mode {mode} not `bpp`, `dipolar`, `csa`.')
# defaults to BPP
if mode == 'bpp':
return self.t1_bpp(omega, tau, *specdens_args, **kwargs)
elif mode == 'dipolar':
return self.t1_dipolar(omega, tau, *specdens_args, **kwargs)
def t1_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, inverse: bool = True,
prefactor: float = None, omega_coup: ArrayLike = None,
gamma_coup: str = None, gamma_obs: str = None) -> Union[np.ndarray, float]:
r"""Calculate T1 under heteronuclear dipolar coupling.
.. math::
\frac{1}{T_1} = C [3J(\omega_I) + 6J(\omega_I + \omega_I) + J(\omega_I - \omega_S)]
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args: Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
omega_coup (array-like, optional): Frequency of coupled isotope must be of same shape as omega.
gamma_obs (str, optional): Name of observed isotope ('1H', '23Na', ...).
Values is used if `omega_coup` is None.
gamma_coup (str, optional): Name of coupled isotope ('1H', '23Na', ...).
Values is used if `omega_coup` is None.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
omega = np.asanyarray(omega)
if omega_coup is not None:
omega_coup = np.asanyarray(omega_coup)
if omega.shape != omega_coup.shape:
raise ValueError('omega and omega_coup have not same shape')
omega_2 = omega_coup
elif (gamma_coup is not None) and (gamma_obs is not None):
omega_2 = gamma_full[gamma_coup] / gamma_full[gamma_obs] * omega
else:
raise AttributeError('Unknown second frequency. Set `omega_coup`, or `gamma_coup` and `gamma_obs`')
if prefactor is None:
prefactor = self._prefactor
prefactor = self.prefactor
if len(args) == 0:
args = self._dist_parameter
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (3 * self._distribution.specdens(omega, tau, *args) +
self._distribution.specdens(np.abs(omega - omega_2), tau, *args) +
6 * self._distribution.specdens(omega + omega_2, tau, *args))
rate = prefactor * (self.distribution.specdens(omega - omega_2, tau, *specdens_args) +
3 * self.distribution.specdens(omega, tau, *specdens_args) +
6 * self.distribution.specdens(omega + omega_2, tau, *specdens_args)) / 2
if inverse:
return 1 / rate
else:
return rate
def t1_bpp(self, omega, tau, *args, inverse=True, prefactor=None, **kwargs):
def t1_bpp(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]:
r"""Calculate T1 under homonuclear dipolar coupling or quadrupolar coupling.
.. math::
\frac{1}{T_1} = C [J(\omega) + 4J(2\omega)]
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args: Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
if prefactor is None:
prefactor = self._prefactor
prefactor = self.prefactor
if len(args) == 0:
args = self._dist_parameter
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (self._distribution.specdens(omega, tau, *args) +
4*self._distribution.specdens(2*omega, tau, *args))
rate = prefactor * (self.distribution.specdens(omega, tau, *specdens_args) +
4 * self.distribution.specdens(2 * omega, tau, *specdens_args))
if inverse:
return 1. / rate
else:
return rate
def t1_rho(self, omega, tau, omega_rf, *args, inverse=True, prefactor=None, **kwargs):
def t1_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]:
r"""Calculate T1 under chemical shift anisotropy.
.. math::
\frac{1}{T_1} = C \omega^2 [J(\omega)]
This relation disregards antisymmetric CSA contributions
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args: Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
if prefactor is None:
prefactor = self._prefactor
prefactor = self.prefactor
if len(args) == 0:
args = self._dist_parameter
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (10 * self._distribution.specdens(omega, tau, *args) +
4 * self._distribution.specdens(2 * omega, tau, *args) +
6 * self._distribution.specdens(omega_rf, tau, *args))
rate = prefactor * (self.distribution.specdens(omega, tau, *specdens_args) +
4 * self.distribution.specdens(2 * omega, tau, *specdens_args))
if inverse:
return 1. / rate
else:
return rate
def t2(self, omega, tau, *args, inverse=True, prefactor=None, **kwargs):
def t1q(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]:
r"""Calculate T1q for homonuclear dipolar coupling or quadrupolar coupling (I=1).
.. math::
\frac{1}{T_{1q}} = 3 C J(\omega)
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args: Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
if prefactor is None:
prefactor = self._prefactor / 2.
prefactor = self.prefactor
if len(args) == 0:
args = self._dist_parameter
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (3 * self._distribution.specdens(0, tau, *args) +
5 * self._distribution.specdens(omega, tau, *args) +
2 * self._distribution.specdens(2 * omega, tau, *args))
rate = 3 * prefactor * self.distribution.specdens(omega, tau, *specdens_args)
if inverse:
return 1. / rate
else:
return rate
def t1_rho(self, omega: ArrayLike, tau: ArrayLike, omega_rf: float, *specdens_args: Any,
inverse: bool = True, prefactor: float = None):
r"""Calculate T1rho for homonuclear dipolar coupling or quadrupolar coupling.
.. math::
\frac{1}{T_{1\rho}} = \frac{C}{2} [5J(\omega) + 2J(2\omega) + 3J(2\omega_{RF})]
Args:
omega (array-like): Frequency in 1/s (not Hz).
tau (array-like): Correlation times in s.
omega_rf (float): Frequency of RF lock IN 1/s.
*specdens_args: Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
if prefactor is None:
prefactor = self.prefactor
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (5 * self.distribution.specdens(omega, tau, *specdens_args) +
2 * self.distribution.specdens(2 * omega, tau, *specdens_args) +
3 * self.distribution.specdens(2 * omega_rf, tau, *specdens_args)) / 2
if inverse:
return 1. / rate
else:
return rate
def t2_bpp(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]:
r"""Calculate T2 under homonuclear dipolar coupling or quadrupolar coupling.
.. math::
\frac{1}{T_2} = \frac{C}{2} [3J() + 5J(2\omega) + 3J(2\omega)]
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args (optional): Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
if prefactor is None:
prefactor = self.prefactor
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (3 * self.distribution.specdens(0, tau, *specdens_args) +
5 * self.distribution.specdens(omega, tau, *specdens_args) +
2 * self.distribution.specdens(2 * omega, tau, *specdens_args)) / 2
if inverse:
return 1. / rate
else:
return rate
def t2_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None, omega_coup: ArrayLike = None,
gamma_coup: str = None, gamma_obs: str = None) -> Union[np.ndarray, float]:
r"""Calculate T2 under heteronuclear dipolar coupling.
.. math::
\frac{1}{T_2} = \frac{C}{2} [4J(0) + J(\omega_I - \omega_I) + 3J(\omega_S) + 6J(\omega_I) + 6J(\omega_I + \omega_I)]
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args (optional): Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
omega_coup (array-like, optional): Frequency of coupled isotope must be of same shape as omega.
gamma_obs (str, optional): Name of observed isotope ('1H', '23Na', ...).
Values is used if `omega_coup` is None.
gamma_coup (str, optional): Name of coupled isotope ('1H', '23Na', ...).
Values is used if `omega_coup` is None.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
omega = np.asanyarray(omega)
if omega_coup is not None:
omega_coup = np.asanyarray(omega_coup)
if omega.shape != omega_coup.shape:
raise ValueError('omega and omega_coup have not same shape')
omega_2 = omega_coup
elif (gamma_coup is not None) and (gamma_obs is not None):
omega_2 = gamma_full[gamma_coup] / gamma_full[gamma_obs] * omega
else:
raise AttributeError('Unknown second frequency. Set `omega_coup`, or `gamma_coup` and `gamma_obs`')
if prefactor is None:
prefactor = self.prefactor
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (4 * self.distribution.specdens(0, tau, *specdens_args) +
self.distribution.specdens(omega - omega_2, tau, *specdens_args) +
3 * self.distribution.specdens(omega_2, tau, *specdens_args) +
6 * self.distribution.specdens(omega, tau, *specdens_args) +
6 * self.distribution.specdens(omega + omega_2, tau, *specdens_args)) / 2.
if inverse:
return 1. / rate
else:
return rate
def t2_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]:
r"""Calculate T1 under chemical shift anisotropy.
.. math::
\frac{1}{T_2} = \frac{C}{2} \omega^2 \bigl[J(0) + \frac{4}{3}J(\omega)\bigr]
Args:
omega (array-like): Frequency in 1/s (not Hz)
tau (array-like): Correlation times in s
*specdens_args (optional): Additional parameter for spectral density.
If given this will replace previously set arguments during calculation
inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True`
prefactor (float, optional): If given it is used as prefactor `C`
instead of previously set coupling prefactor.
Returns:
float or ndarray:
A k by n array where k is length of ``omega`` and n is length of ``tau``.
If both are of length 1 a number is returned instead of array.
"""
if prefactor is None:
prefactor = self.prefactor
if len(specdens_args) == 0:
specdens_args = self.dist_parameter
rate = prefactor * (self.distribution.specdens(0, tau, *specdens_args) +
4 / 3 * self.distribution.specdens(omega, tau, *specdens_args)) / 2.
if inverse:
return 1. / rate
else:
@ -158,60 +443,31 @@ class RelaxationEvaluation(Relaxation):
self.y = None
def data(self, temp, t1):
temp = np.asarray(temp)
t1 = np.asarray(t1)
temp = np.asanyarray(temp)
t1 = np.asanyarray(t1)
sortidx = temp.argsort()
self.x = temp[sortidx]
self.y = t1[sortidx]
self.calculate_t1_min()
def calculate_t1_min(self, interpolate: int = None, trange=None):
min_index = np.argmin(self.y)
t1_min = (self.x[min_index], self.y[min_index])
parabola = None
self._interpolate_range = (None, None)
def get_increase(self, height: float = None, idx: int = 0, mode: str = None, omega: float = None,
dist_parameter: Union[tuple, list] = None, prefactor: Union[tuple, list, float] = None,
coupling_kwargs: dict = None):
"""
Determine a single parameter from a T1 minimum
if interpolate is not None:
if interpolate not in [0, 1, 2, 3]:
raise ValueError(f'Unknown interpolation value {interpolate}')
Args:
height (float, optional): Height of T1 minimum
mode (str, {`distribution`, `prefactor`}, optional): If given,
idx (int): Default is 0.
omega (float, optional): Larmor frequency (in 1/s)
dist_parameter (tuple, optional):
prefactor (tuple, float, optional):
coupling_kwargs (dict, optional):
if interpolate != 0:
if trange is None:
left_b = max(min_index-2, 0)
right_b = min(len(self.x), min_index+3)
else:
left_b = np.argmin(np.abs(self.x-trange[0]))
right_b = np.argmin(np.abs(self.x-trange[1]))+1
Returns:
temp_range = self.x[left_b:right_b]
t1_range = self.y[left_b:right_b]
try:
x_inter = np.linspace(temp_range[0], temp_range[-1], num=51)
if interpolate == 1:
i_func = np.poly1d(np.polyfit(temp_range, t1_range, 2))
elif interpolate == 2:
i_func = interp1d(temp_range, t1_range, kind='cubic')
else:
i_func = Akima1DInterpolator(temp_range, t1_range)
y_inter = i_func(x_inter)
t1_min = (x_inter[np.argmin(y_inter)], y_inter[np.argmin(y_inter)])
self._interpolate = i_func
parabola = (x_inter, y_inter)
self._interpolate_range = (temp_range[0], temp_range[-1])
except IndexError:
pass
self.t1min = t1_min
return t1_min, parabola
def increase(self, variable: Tuple[str, int], height: float = None, omega: float = None,
dist_parameter: list = None, prefactor=None,
coupling_kwargs=None):
"""
stretching = mini = np.nan
if height is None:
@ -224,44 +480,62 @@ class RelaxationEvaluation(Relaxation):
omega = self.omega
if prefactor is None:
prefactor = self._prefactor
prefactor = self.prefactor
if dist_parameter is None:
dist_parameter = self._dist_parameter
dist_parameter = self.dist_parameter
tau_lims = np.log10(1/omega)-3, np.log10(1/omega)+3
if coupling_kwargs is None:
coupling_kwargs = self.coup_kw
if not variable:
if isinstance(prefactor, list):
tau_lims = np.log10(1 / omega) - 3, np.log10(1 / omega) + 3
if mode is None:
# nothing is variable -> just calculate minimum for given parameter
if isinstance(prefactor, (tuple, list)):
if coupling_kwargs is None:
coupling_kwargs = self._coup_kw
prefactor = self._coupling.func(*prefactor, **coupling_kwargs)
coupling_kwargs = self.coup_kw
prefactor = self.coupling.relax(*prefactor, **coupling_kwargs)
return stretching, np.min(self.t1(omega, np.logspace(*tau_lims, num=1001),
*dist_parameter, prefactor=prefactor, inverse=True))
if variable[0] == 'distribution':
if isinstance(self._distribution, Debye):
return 1.
if mode == 'distribution':
# width parameter of spectral density is variable
if isinstance(self.distribution, Debye):
# Debye is easy
return 1., np.min(self.t1(omega, np.logspace(*tau_lims, num=1001),
*dist_parameter, prefactor=prefactor, inverse=True))
if isinstance(prefactor, (list, tuple)):
# calculate prefactor from coupling
if self.coupling is None:
raise ValueError('Coupling must be set before evaluation of T1 min')
if isinstance(prefactor, list):
if coupling_kwargs is None:
coupling_kwargs = self._coup_kw
prefactor = self._coupling.func(*prefactor, **coupling_kwargs)
coupling_kwargs = self.coup_kw
prefactor = self.coupling.relax(*prefactor, **coupling_kwargs)
use_fmin = True
if self._distribution.name in ['KWW', 'Cole-Davidson', 'Cole-Cole', 'Log-Gaussian']:
if self.distribution.name in ['KWW', 'Cole-Davidson', 'Cole-Cole', 'Log-Gaussian']:
# using precalculated values is faster than actual calculation, especially KWW and LG
from importlib.resources import path
with path('resources.nmr', 'T1_min.npz') as fp:
if fp.exists():
t1min_table = np.load(str(fp))[self._distribution.name.replace('-', '_')]
t1min_table = np.load(str(fp))[self.distribution.name.replace('-', '_')]
debye_min = omega / 1.42517571908650 / prefactor # Zahl kommt von Wolfram alpha
ratio = height / debye_min
stretching = t1min_table[np.argmin(np.abs(t1min_table[:, 1]-ratio)), 0]
stretching = t1min_table[np.argmin(np.abs(t1min_table[:, 1] - ratio)), 0]
use_fmin = False
dist_parameter = [stretching]
if use_fmin:
# use for untabulated spectral densities or if something went wrong
#
def _f(_x, p, ii):
p[ii] = _x[0]
t1_calc = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *p,
@ -269,40 +543,100 @@ class RelaxationEvaluation(Relaxation):
return np.abs(t1_calc - height)
stretching = minimize(_f, np.array([dist_parameter[variable[1]]]),
args=(dist_parameter, variable[1]),
stretching = minimize(_f, np.array([dist_parameter[idx]]),
args=(dist_parameter, idx),
method='Nelder-Mead').x[0]
dist_parameter[idx] = stretching
elif mode == 'coupling':
# variable is somewhere in the prefcotor
elif variable[0] == 'coupling':
t1_no_coup = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001),
*dist_parameter, prefactor=1))
if isinstance(prefactor, list):
def _f(_x, p, ii):
p[ii] = _x
return np.abs(t1_no_coup/height - self._coupling.func(*p, **coupling_kwargs)[0])
if isinstance(prefactor, (tuple, list)):
prefactor = list(prefactor)
def _f(_x, p, ii):
p[ii] = _x[0]
return np.abs(t1_no_coup / height - self.coupling.relax(*p, **coupling_kwargs))
stretching = minimize(_f, np.array([prefactor[idx]]),
args=(prefactor, idx)).x[0]
stretching = minimize(_f, np.array([prefactor[variable[1]]]),
args=(prefactor, variable[1])).x[0]
else:
stretching = t1_no_coup / height
else:
raise ValueError('Use "distribution" or "coupling" to set parameter')
raise ValueError('Use `distribution` or `coupling` to set parameter')
if stretching:
self._prefactor = prefactor
self._dist_parameter = dist_parameter
if isinstance(prefactor, list):
self._coup_parameter = prefactor
mini = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *self._dist_parameter,
prefactor=self._prefactor))
self.prefactor = prefactor
self.dist_parameter = dist_parameter
if isinstance(prefactor, (tuple, list)):
self.coup_parameter = prefactor
self.prefactor = self.coupling.relax(*self.coup_parameter, **self.coup_kw)
else:
self.prefactor = prefactor
mini = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *self.dist_parameter,
prefactor=self.prefactor))
return stretching, mini
def correlation_from_t1(self, mode: str = 'raw', interpolate=False, omega=None,
dist_parameter: list = None, prefactor: float = None,
coupling_param: list = None, coupling_kwargs: dict = None):
def calculate_t1_min(self, interpolate: int = None, trange: Tuple[float, float] = None, use_log: bool = False) -> \
Tuple[Tuple[float, float], Optional[Tuple[np.ndarray, np.ndarray]]]:
min_index = np.argmin(self.y)
t1_min = (self.x[min_index], self.y[min_index])
parabola = None
self._interpolate_range = (None, None)
if interpolate is not None:
if interpolate not in [0, 1, 2, 3]:
raise ValueError(f'Unknown interpolation value {interpolate}')
if interpolate != 0:
if trange is None:
left_b = max(min_index - 2, 0)
right_b = min(len(self.x), min_index + 3)
else:
left_b = np.argmin(np.abs(self.x - trange[0]))
right_b = np.argmin(np.abs(self.x - trange[1])) + 1
temp_range = self.x[left_b:right_b]
t1_range = self.y[left_b:right_b]
if use_log:
t1_range = np.log10(t1_range)
try:
x_inter = np.linspace(temp_range[0], temp_range[-1], num=51)
if interpolate == 1:
i_func = np.poly1d(np.polyfit(temp_range, t1_range, 2))
elif interpolate == 2:
i_func = interp1d(temp_range, t1_range, kind='cubic')
else:
i_func = Akima1DInterpolator(temp_range, t1_range)
if use_log:
y_inter = 10**i_func(x_inter)
else:
y_inter = i_func(x_inter)
t1_min = (x_inter[np.argmin(y_inter)], y_inter[np.argmin(y_inter)])
self._interpolate = i_func
parabola = (x_inter, y_inter)
self._interpolate_range = (temp_range[0], temp_range[-1])
t1_min = x_inter[np.argmin(y_inter)], y_inter[np.argmin(y_inter)]
except IndexError:
pass
self.t1min = t1_min
return t1_min, parabola
def correlation_from_t1(self, mode: str = 'raw', interpolate: bool = False, omega: float = None,
dist_parameter: Union[float, list, tuple] = None, prefactor: Union[float, tuple, list] = None,
coupling_param: list = None, coupling_kwargs: dict = None) -> Tuple[np.ndarray, dict]:
if self.x is None:
raise ValueError('Temperature is not set')
@ -317,40 +651,40 @@ class RelaxationEvaluation(Relaxation):
if prefactor is None:
if coupling_kwargs is None:
coupling_kwargs = self._coup_kw
coupling_kwargs = self.coup_kw
if coupling_param is None:
prefactor = self._prefactor
prefactor = self.prefactor
else:
prefactor = self._coupling.func(*coupling_param, **coupling_kwargs)
prefactor = self.coupling.relax(*coupling_param, **coupling_kwargs)
if dist_parameter is None:
dist_parameter = self._dist_parameter
dist_parameter = self.dist_parameter
fast_idx = self.x > self.t1min[0]
slow_t1 = self.y[~fast_idx]
slow_temp = self.x[~fast_idx]
correlation_times = np.ones(self.x.shape)
correlation_times = np.ones_like(self.x)
offset = len(slow_t1)
base_taus = np.logspace(-10, -7, num=1001)
min_tau = base_taus[np.argmin(self.t1(omega, base_taus, *dist_parameter, prefactor=prefactor))]
taus = np.geomspace(min_tau, 100.*min_tau, num=501)
taus = np.geomspace(min_tau, 100. * min_tau, num=501)
current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
for i in range(1, len(slow_t1)+1):
for i in range(1, len(slow_t1) + 1):
t1_i = slow_t1[-i]
if interpolate and self._interpolate is not None:
if slow_temp[-i] >= self._interpolate_range[0]:
t1_i = self._interpolate(slow_temp[-i])
if min(current_t1) > t1_i:
if np.min(current_t1) > t1_i:
warn('Correlation time could not be calculated')
correlation_times[offset-i] = taus[0]
correlation_times[offset - i] = taus[0]
continue
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0]
@ -359,13 +693,13 @@ class RelaxationEvaluation(Relaxation):
current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0]
lamb = (t1_i - current_t1[cross_idx]) / (current_t1[cross_idx+1]-current_t1[cross_idx])
correlation_times[offset-i] = (taus[cross_idx+1] * lamb + (1 - lamb) * taus[cross_idx])[0]
lamb = (t1_i - current_t1[cross_idx]) / (current_t1[cross_idx + 1] - current_t1[cross_idx])
correlation_times[offset - i] = (taus[cross_idx + 1] * lamb + (1 - lamb) * taus[cross_idx])[0]
fast_t1 = self.y[fast_idx]
fast_temp = self.x[fast_idx]
taus = np.geomspace(0.01*min_tau, min_tau, num=501)
taus = np.geomspace(0.01 * min_tau, min_tau, num=501)
current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
for i in range(len(fast_t1)):
@ -376,8 +710,8 @@ class RelaxationEvaluation(Relaxation):
t1_i = self._interpolate(fast_temp[i])
if current_t1[-1] > t1_i:
correlation_times[offset+i] = taus[-1]
warn('Correlation time could not be calculated')
correlation_times[offset + i] = taus[-1]
warn(f'Correlation time for {correlation_times[offset + i]} could not be calculated')
continue
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0]
@ -386,19 +720,22 @@ class RelaxationEvaluation(Relaxation):
current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0]
lamb = (t1_i - current_t1[cross_idx]) / (current_t1[cross_idx+1]-current_t1[cross_idx])
correlation_times[offset+i] = (taus[cross_idx+1] * lamb + (1-lamb) * taus[cross_idx])[0]
lamb = (t1_i - current_t1[cross_idx]) / (current_t1[cross_idx + 1] - current_t1[cross_idx])
correlation_times[offset + i] = (taus[cross_idx + 1] * lamb + (1 - lamb) * taus[cross_idx])[0]
opts = {'distribution': (self._distribution.name, dist_parameter),
'coupling': (self._coupling.name, coupling_param, coupling_kwargs),
'frequency': omega/2/np.pi}
opts = {'distribution': (self.distribution.name, dist_parameter),
'frequency': omega / 2 / np.pi}
if self.coupling is not None:
opts['coupling'] = (self.coupling.name, self.prefactor, coupling_param, coupling_kwargs)
else:
opts['coupling'] = (self.prefactor,)
return np.c_[self.x, self._distribution.mean_value(correlation_times, *dist_parameter, mode=mode)], opts
return np.c_[self.x, self.distribution.mean_value(correlation_times, *dist_parameter, mode=mode)], opts
def _create_minimum_file(self, filename):
x = np.geomspace(0.1, 1, num=10001)
with Path(filename).open('w') as f:
f.write('#broadening\tT1_min/min_Debye\n')
f.write('# broadening\tT1_min/min_Debye\n')
for i, a in enumerate(np.geomspace(0.1, 10, num=10000)):
alpha = a
t1_i = self.t1_bpp(1, x, alpha)

View File

@ -11,7 +11,7 @@ __all__ = ['NA', 'kb_joule', 'h_joule', 'hbar_joule',
NA = 6.02214076e23
kb_joule = 1.380649e-23
e = 1.602176634e-19
h_joule = 6.62607015e-23
h_joule = 6.62606896e-34
mu0 = 1.256637062124e-6
epsilon0 = 8.8541878128e-12
@ -38,7 +38,7 @@ spintxt = """\
9Be 3/2 100 -3.759666 5.288
10B 3 19.9 2.8746786 8.459
11B 3/2 80.1 8.5847044 4.059
13C 1/2 1.07 6.728 284
13C 1/2 1.07 6.728284
14N 1 99.632 1.9337792 2.044
15N 1/2 0.368 -2.71261804
17O 5/2 0.038 -3.62808 -2.558

View File

@ -16,7 +16,7 @@ small_greek = [
r'\f{Symbol}s\f{} \f{Symbol}t\f{} \f{Symbol}u\f{} \f{Symbol}f\f{} \f{Symbol}c\f{} \f{Symbol}y\f{} '
r'\f{Symbol}w\f{}',
'alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu ' # plain
'xi omicron pi rho sigma sigma tau ypsilon phi chi psi omega'
'xi omicron pi rho sigma sigma tau ypsilon phi chi psi omega',
]
big_greek = [
r'\Alpha \Beta \Gamma \Delta \Epsilon \Zeta \Eta \Theta \Iota \Kappa \Lambda \Mu ' # tex
@ -28,7 +28,7 @@ big_greek = [
r'\f{Symbol}N\f{} \f{Symbol}X\f{} \f{Symbol}O\f{} \f{Symbol}P\f{} \f{Symbol}R\f{} \f{Symbol}S\f{} '
r'\f{Symbol}T\f{} \f{Symbol}Y\f{} \f{Symbol}F\f{} \f{Symbol}C\f{} \f{Symbol}U\f{} \f{Symbol}W\f{}',
'Alpha Beta Gamma Delta Epsilon Zeta Eta Theta Iota Kappa Lambda Mu ' # plain
'Nu Xi Omicron Pi Rho Sigma Tau Ypsilon Phi Chi Psi Omega'
'Nu Xi Omicron Pi Rho Sigma Tau Ypsilon Phi Chi Psi Omega',
]
special_chars = [
r'\infty \int \sum \langle \rangle \pm \perp \para \leftarrow \rightarrow \leftrightarrow \cdot \hbar',
@ -36,13 +36,13 @@ special_chars = [
r'\f{Symbol}¥\f{} \f{Symbol}ò\f{} \f{Symbol}å\f{} \f{Symbol}á\f{} \f{Symbol}ñ\f{} \f{Symbol}±\f{} '
r'\f{Symbol}^\f{} \f{Symbol}||\f{} \f{Symbol}¬\f{} \f{Symbol}®\f{} \f{Symbol}«\f{} \f{Symbol}×\f{Symbol} '
r'h\h{-0.6}\v{0.3}-\v{-0.3}\h{0.3}',
'infty int sum < > +- perp para <- -> <-> * hbar'
r'infty int sum < > \+- perp para <- -> <-> \* hbar',
]
funcs = [
r'\exp \log \ln \sin \cos \tan',
'exp log ln sin cos tan',
'exp log ln sin cos tan',
'exp log ln sin cos tan'
'exp log ln sin cos tan',
]
delims = [
[(r'_{', r'}'), (r'<sub>', r'</sub>'), (r'\\s', r'\\N'), (r'_{', r'}')],

8
requirements.txt Normal file
View File

@ -0,0 +1,8 @@
matplotlib
numpy
scipy
pyqtgraph
bsddb3
h5py
PyQt5

View File

@ -353,6 +353,9 @@
<addaction name="actionSave"/>
<addaction name="separator"/>
<addaction name="actionMouse_behaviour"/>
<addaction name="separator"/>
<addaction name="actionPrevious"/>
<addaction name="actionNext_window"/>
</widget>
<widget class="QToolBar" name="toolbar_edit">
<property name="sizePolicy">

View File

@ -6,15 +6,30 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>319</height>
<width>544</width>
<height>443</height>
</rect>
</property>
<property name="windowTitle">
<string>Read BDS data</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="0" rowspan="2">
<widget class="QListWidget" name="listWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
@ -24,74 +39,150 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Found temperatures</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Read as:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="eps_checkBox">
<property name="text">
<string>Permittivity ε</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="modul_checkBox">
<property name="text">
<string>Modulus 1/ε</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cond_checkBox">
<property name="text">
<string>Conductivity iεω</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>X Axis</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QRadioButton" name="freq_button">
<property name="text">
<string>Frequency</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="temp_button">
<property name="text">
<string>Temperature</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="2" column="1">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Y Axis</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QCheckBox" name="eps_checkBox">
<property name="text">
<string>Permittivity ε</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="modul_checkBox">
<property name="text">
<string>Modulus 1/ε</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cond_checkBox">
<property name="text">
<string>Conductivity iεω</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="loss_checkBox">
<property name="text">
<string>Loss factor tan(δ)</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cap_checkBox">
<property name="text">
<string>Capacity</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="temp_checkBox">
<property name="text">
<string>Meas. temperature</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="time_checkBox">
<property name="text">
<string>Meas. time</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -101,8 +192,29 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Found entries</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>freq_button</tabstop>
<tabstop>temp_button</tabstop>
<tabstop>eps_checkBox</tabstop>
<tabstop>modul_checkBox</tabstop>
<tabstop>cond_checkBox</tabstop>
<tabstop>listWidget</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
@ -112,8 +224,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
<x>254</x>
<y>411</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@ -128,8 +240,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
<x>322</x>
<y>411</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
@ -138,4 +250,7 @@
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

9001
resources/nmr/KWW_min.dat Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.