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 # 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. # documentation root, use os.path.abspath to make it absolute, like shown here.
# #
import os
import sys import sys
import sphinx_bootstrap_theme import sphinx_bootstrap_theme
sys.path.append('/autohome/dominik/nmreval') sys.path.append('/autohome/dominik/nmreval')
@ -43,8 +44,46 @@ extensions = [
'sphinx.ext.napoleon', 'sphinx.ext.napoleon',
'sphinx.ext.viewcode', 'sphinx.ext.viewcode',
'sphinx.ext.intersphinx', '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. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # 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 # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
# html_theme = 'bootstrap'
html_theme = 'pydata_sphinx_theme' html_theme = 'pydata_sphinx_theme'
# Theme options are theme-specific and customize the look and feel of a 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 # further. For a list of options available for each theme, see the
# documentation. # 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 = { html_theme_options = {
'collapse_navigation': False, 'collapse_navigation': False,
'show_prev_next': False, 'show_prev_next': False,
'navbar_end': ['navbar-icon-links.html', 'search-field.html'], '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. # 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. # If false, no index is generated.
# #
# epub_use_index = True # 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 You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive. contain the root `toctree` directive.
################################### #####################
NMREval documentation NMREval documentation
################################### #####################
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
user_guide/index user_guide/index
gallery/index
nmr/index
api/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 .points import Points
from .signals import Signal from .signals import Signal
from .bds import BDS from .bds import BDS

View File

@ -15,10 +15,11 @@ class BDS(Signal):
def __init__(self, x, y, **kwargs: Any): def __init__(self, x, y, **kwargs: Any):
super().__init__(x, y, **kwargs) 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]): if len(self._x) > 1:
self.dx = self.x[1] / self.x[0] if np.all(self._x > 0) and np.allclose(self._x[1:]/self._x[:-1], self._x[1]/self._x[0]):
else: self.dx = 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: def __repr__(self) -> str:
return f"{self.meta['mode']}: {self.name}" return f"{self.meta['mode']}: {self.name}"
@ -32,10 +33,10 @@ class BDS(Signal):
window_length (int) window_length (int)
Returns: Returns:
Points Points:
New Points instance with New Points instance with
References: Reference:
Wübbenhorst, M.; van Turnhout, J.: Analysis of complex dielectric spectra. Wübbenhorst, M.; van Turnhout, J.: Analysis of complex dielectric spectra.
I. One-dimensional derivative techniques and three-dimensional modelling. 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 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): class FID(Signal):
"""
Extensions if :class:`Signal` for NMR timesignals
"""
def __init__(self, x, y, **kwargs): def __init__(self, x, y, **kwargs):
super().__init__(x, y, **kwargs) super().__init__(x, y, **kwargs)
@ -16,7 +19,16 @@ class FID(Signal):
def __repr__(self): def __repr__(self):
return f"FID: {self.name}" 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() self._y -= self._y[int(-percentage * self.length):].mean()
return self return self
@ -51,7 +63,7 @@ class FID(Signal):
return self return self
def fourier(self): def fourier(self) -> 'Spectrum':
ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx
freq = np.fft.fftshift(np.fft.fftfreq(self.length, 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): def fft_depake(self, scale: bool = False):
""" """
Calculate deconvoluted Pake spectra Calculate deconvoluted Pake spectra
M.A. McCabe, S.R. Wassail:
Rapid deconvolution of NMR powder spectra by weighted fast Fourier transformation, Args:
SSNMR (1997), Vol.10, Nr.1-2, pp. 53-61, 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 _y = self._y + 0
# Perform FFT depaking # Perform FFT depaking
@ -102,8 +120,9 @@ class FID(Signal):
self._y_err = np.std(self._y[-int(0.1*self.length):])/1.41 self._y_err = np.std(self._y[-int(0.1*self.length):])/1.41
return self._y_err * np.ones(self.length) 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: Args:
points : shift value, points : shift value,
@ -113,13 +132,17 @@ class FID(Signal):
""" """
if mode == 'pts': if mode == 'pts':
super().shift(points) super().shift(int(points))
else: else:
pts = int(points//self.dx) pts = int(points//self.dx)
super().shift(pts) super().shift(pts)
class Spectrum(Signal): class Spectrum(Signal):
"""
Extension of :class:`Signal` for NMR spectra
"""
def __init__(self, x, y, dx=None, **kwargs): def __init__(self, x, y, dx=None, **kwargs):
super().__init__(x, y, **kwargs) super().__init__(x, y, **kwargs)
if dx is None: if dx is None:
@ -139,7 +162,7 @@ class Spectrum(Signal):
return self return self
def fourier(self): def fourier(self) -> FID:
ft = np.fft.ifft(np.fft.ifftshift(self._y * self.dx)) ft = np.fft.ifft(np.fft.ifftshift(self._y * self.dx))
t = np.arange(len(ft))*self.dx t = np.arange(len(ft))*self.dx
shifted = np.fft.ifftshift(self._x)[0] shifted = np.fft.ifftshift(self._x)[0]
@ -172,8 +195,8 @@ class Spectrum(Signal):
return self._y_err * np.ones(self.length) 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': if mode == 'pts':
super().shift(points) super().shift(int(points))
else: else:
return self return self

View File

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

View File

@ -6,7 +6,7 @@ from .points import Points
class Signal(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): def __init__(self, x, y, **kwargs):
@ -18,7 +18,10 @@ class Signal(Points):
super().__init__(x, y, **kwargs) 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}) self.meta.update({'phase': [], 'shift': 0})
def __repr__(self): 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 .havriliaknegami import HavriliakNegami
from .colecole import ColeCole from .colecole import ColeCole

View File

@ -1,8 +1,11 @@
import abc import abc
from warnings import warn from typing import Any, Union
import numpy as np import numpy as np
from nmreval.lib.utils import ArrayLike
class Distribution(abc.ABC): class Distribution(abc.ABC):
name = 'Abstract distribution' name = 'Abstract distribution'
@ -15,7 +18,12 @@ class Distribution(abc.ABC):
@staticmethod @staticmethod
@abc.abstractmethod @abc.abstractmethod
def distribution(taus, tau0, *args): def distribution(tau, tau0, *args):
pass
@staticmethod
@abc.abstractmethod
def correlation(t, tau, *args):
pass pass
@staticmethod @staticmethod
@ -24,13 +32,8 @@ class Distribution(abc.ABC):
pass pass
@classmethod @classmethod
def specdens(cls, omega, tau, *args): def specdens(cls, omega: ArrayLike, tau: ArrayLike, *args: Any) -> Union[np.ndarray, float]:
return -cls.susceptibility(omega, tau, *args).imag / omega return cls.susceptibility(omega, tau, *args).imag / omega
@staticmethod
@abc.abstractmethod
def correlation(t, tau0, *args):
pass
@classmethod @classmethod
def mean_value(cls, *args, mode='mean'): def mean_value(cls, *args, mode='mean'):
@ -43,6 +46,27 @@ class Distribution(abc.ABC):
return args[0] 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 @classmethod
def convert(cls, *args, from_='raw', to_='mean'): def convert(cls, *args, from_='raw', to_='mean'):
if from_ == to_: if from_ == to_:
@ -62,24 +86,3 @@ class Distribution(abc.ABC):
return raw return raw
else: else:
return cls.mean_value(raw, *args[1:], mode=to_) 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 import numpy as np
from .base import Distribution from .base import Distribution
from ..lib.utils import ArrayLike
from ..lib.decorator import adjust_dims
from ..math.mittagleffler import mlf from ..math.mittagleffler import mlf
@ -13,7 +15,7 @@ class ColeCole(Distribution):
bounds = [(0, 1)] bounds = [(0, 1)]
@staticmethod @staticmethod
def distribution(taus, tau0, alpha): def distribution(tau, tau0, alpha):
""" """
Distribution of correlation times 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))} G(\ln\\tau) = \\frac{1}{2\pi} \\frac{\\sin(\\pi\\alpha)}{\cosh[\\alpha\ln(\\tau/\\tau_0)] + \cos(\\alpha \pi))}
Args: Args:
taus: tau:
tau0: tau0:
alpha: 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) return np.sin(np.pi*alpha)/(np.cosh(z*alpha) + np.cos(alpha*np.pi))/(2*np.pi)
@staticmethod @staticmethod
def susceptibility(omega, tau, alpha): @adjust_dims
""" def susceptibility(omega: ArrayLike, tau: ArrayLike, alpha: float) -> ArrayLike:
Susceptibility r"""
Complex susceptibility
.. math:: .. 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: Args:
omega: omega (array-like): Frequency axis in 1/s (not Hz).
tau: tau (array-like): Correlation times :math:`\tau_\text{CC}` in s.
alpha: alpha (float): Shape parameter.
""" """
_tau = np.atleast_1d(tau) val = 1/(1+(1j*omega*tau)**alpha)
_omega = np.atleast_1d(omega) return np.conjugate(val)
val = 1/(1+(1j*_omega[:, None]*_tau[None, :])**alpha)
return np.conjugate(val).squeeze()
@staticmethod @staticmethod
@adjust_dims
def specdens(omega, tau, alpha): def specdens(omega, tau, alpha):
""" """
Spectral density Spectral density
@ -59,12 +62,11 @@ class ColeCole(Distribution):
tau: tau:
alpha: alpha:
""" """
_tau = np.atleast_1d(tau) omtau = (omega*tau)**alpha
_omega = np.atleast_1d(omega)
omtau = (_omega*_tau)**alpha
return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega
@staticmethod @staticmethod
@adjust_dims
def correlation(t, tau0, alpha): def correlation(t, tau0, alpha):
""" """
Correlation function :math:`C(t)` Correlation function :math:`C(t)`
@ -80,26 +82,3 @@ class ColeCole(Distribution):
alpha: alpha:
""" """
return mlf(-(t/tau0)**alpha, 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 scipy.special import psi, gammaincc
from .base import Distribution from .base import Distribution
from ..lib.decorator import adjust_dims
from ..lib.utils import ArrayLike
from ..utils.constants import Eu from ..utils.constants import Eu
@ -16,22 +18,50 @@ class ColeDavidson(Distribution):
bounds = [(0, 1)] bounds = [(0, 1)]
@staticmethod @staticmethod
def susceptibility(omega, tau, gamma): def distribution(tau, tau0, gamma):
""" """
Susceptibility Distribution of correlation times
.. math:: .. 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: Args:
omega:
tau: tau:
tau0:
gamma: 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() return (1/(1+1j*omega*tau)**gamma).conjugate()
@staticmethod @staticmethod
def specdens(omega, tau,gamma): @adjust_dims
def specdens(omega, tau, gamma):
""" """
Spectral density Spectral density
@ -43,15 +73,12 @@ class ColeDavidson(Distribution):
tau: tau:
gamma: gamma:
""" """
gam = gamma omtau = omega*tau
_w = np.atleast_1d(omega)
_t = np.atleast_1d(tau)
omtau = _w[:, None] * _t[None, :]
ret_val = np.sin(gam*np.arctan2(omtau, 1)) / (1 + omtau**2)**(gam/2.) / _w[:, None] ret_val = np.sin(gamma*np.arctan2(omtau, 1)) / (1 + omtau**2)**(gamma/2.) / omega
ret_val[_w == 0, :] = gam*_t ret_val[np.argwhere(omega == 0)] = gamma*tau
return np.squeeze(ret_val) return ret_val
@staticmethod @staticmethod
def mean(tau, gamma): def mean(tau, gamma):
@ -113,33 +140,8 @@ class ColeDavidson(Distribution):
return tau/np.tan(np.pi/(2*gamma+2)) return tau/np.tan(np.pi/(2*gamma+2))
@staticmethod @staticmethod
def distribution(tau, tau0, gamma): @adjust_dims
""" def correlation(t, tau, 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):
r""" r"""
Correlation function Correlation function
@ -153,14 +155,14 @@ class ColeDavidson(Distribution):
Args: Args:
t: t:
tau0: tau:
gamma: gamma:
References: 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 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__': if __name__ == '__main__':

View File

@ -3,28 +3,44 @@ import numbers
import numpy as np import numpy as np
from .base import Distribution from .base import Distribution
from ..lib.decorator import adjust_dims
from ..lib.utils import ArrayLike
class Debye(Distribution): class Debye(Distribution):
name = 'Debye' name = 'Debye'
@staticmethod @staticmethod
def correlation(t, tau0, *args): def distribution(tau: ArrayLike, tau0: float) -> ArrayLike:
return np.exp(-t/tau0) if isinstance(tau, numbers.Number):
return 1 if tau == tau0 else 0
@staticmethod ret_val = np.zeros_like(tau)
def susceptibility(omega, tau0, *args): ret_val[tau == tau0] = 1.
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
return ret_val 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 import numpy as np
from scipy.special import psi from scipy.special import psi
from .base import Distribution from .base import Distribution
from ..lib.utils import ArrayLike
from ..lib.decorator import adjust_dims
from ..math.mittagleffler import mlf from ..math.mittagleffler import mlf
from ..utils import Eu from ..utils import Eu
class HavriliakNegami(Distribution): class HavriliakNegami(Distribution):
""" """
Functions based on Cole-Davidson distribution Functions based on Havriliak-Negami distribution
""" """
name = 'Havriliak-Negami' name = 'Havriliak-Negami'
@ -16,51 +20,31 @@ class HavriliakNegami(Distribution):
bounds = [(0, 1), (0, 1)] bounds = [(0, 1), (0, 1)]
@staticmethod @staticmethod
def correlation(t, tau0, alpha, gamma): def distribution(tau: ArrayLike, tau0: float, alpha: float, gamma: float) -> ArrayLike:
r""" r"""
Correlation function Distribution of correlation times :math:`G(\ln\tau)`.
.. math:: .. 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] 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]}
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
Args: Args:
t: tau (array_like) :
tau0: tau0 (array-like) :
alpha: alpha:
gamma: gamma:
References: Returns:
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 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: if alpha == 1:
from .coledavidson import ColeDavidson from .coledavidson import ColeDavidson
return ColeDavidson.distribution(tau, tau0, gamma) return ColeDavidson.distribution(tau, tau0, gamma)
elif gamma == 1: elif gamma == 1:
from .colecole import ColeCole from .colecole import ColeCole
return ColeCole.distribution(tau, tau0, alpha) return ColeCole.distribution(tau, tau0, alpha)
else: else:
_y = tau/tau0 _y = tau/tau0
om_y = (1 + 2*np.cos(np.pi*alpha)*(_y**alpha) + _y**(2*alpha))**(-gamma/2) 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 return np.sin(gamma*theta_y) * om_y * (_y**(alpha*gamma)) / np.pi
@staticmethod @staticmethod
def max(tau, alpha, gamma): @adjust_dims
return tau*(np.sin(0.5*np.pi*alpha*gamma/(gamma+1)) / np.sin(0.5*np.pi*alpha/(gamma+1)))**(1/alpha) 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 @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""" r"""
Calculate mean logarithm of tau Calculate mean logarithm of tau
@ -82,25 +159,12 @@ class HavriliakNegami(Distribution):
Args: Args:
tau: tau:
alpha: alpha (float):
gamma: gamma (float):
Returns:
""" """
return np.log(tau) + (psi(gamma)+Eu)/alpha return np.log(tau) + (psi(gamma)+Eu)/alpha
@staticmethod @staticmethod
def mean(tau, alpha, gamma): def max(tau: ArrayLike, alpha: float, gamma: float) -> ArrayLike:
# approximation according to Th. Bauer et al., J. Chem. Phys. 133, 144509 (2010). return tau*(np.sin(0.5*np.pi*alpha*gamma/(gamma+1)) / np.sin(0.5*np.pi*alpha/(gamma+1)))**(1/alpha)
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()

View File

@ -2,6 +2,7 @@ import numpy as np
from scipy.interpolate import interp1d from scipy.interpolate import interp1d
from scipy.special import gamma from scipy.special import gamma
from nmreval.lib.decorator import adjust_dims
from .base import Distribution from .base import Distribution
from ..math.kww import kww_cos, kww_sin from ..math.kww import kww_cos, kww_sin
from ..utils.constants import Eu from ..utils.constants import Eu
@ -13,52 +14,52 @@ class KWW(Distribution):
boounds = [(0, 1)] boounds = [(0, 1)]
@staticmethod @staticmethod
def distribution(taus, tau, *args): def distribution(taus, tau, beta):
b = args[0] if not (0.1 <= beta <= 0.9):
assert 0.1 <= b <= 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.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] 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] 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) B = interp1d(b_supp, B_supp)(beta)
C = interp1d(b_supp, C_supp)(b) C = interp1d(b_supp, C_supp)(beta)
tt = tau/taus tt = tau/taus
delta = b * abs(b-0.5) / (1-b) delta = beta * abs(beta-0.5) / (1-beta)
if b > 0.5: if beta > 0.5:
f = 1 + C * tt**delta f = 1 + C * tt**delta
else: else:
f = 1 / (1 + C * tt**delta) 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 return ret_val * f / taus
@staticmethod @staticmethod
def correlation(t, tau, *args): @adjust_dims
return np.exp(-(t/tau)**args[0]) def correlation(t, tau, beta):
return np.exp(-(t/tau)**beta)
@staticmethod @staticmethod
def susceptibility(omega, tau, *args): @adjust_dims
return 1-omega*kww_sin(omega, tau, args[0]) + 1j*omega*kww_cos(omega, tau, args[0]) def susceptibility(omega, tau, beta):
return 1-omega*kww_sin(omega, tau, beta) + 1j*omega*kww_cos(omega, tau, beta)
@staticmethod @staticmethod
def specdens(omega, tau, *args): @adjust_dims
return kww_cos(omega, tau, args[0]) def specdens(omega, tau, beta):
return kww_cos(omega, tau, beta)
@staticmethod @staticmethod
def mean(*args): def mean(tau, beta):
tau, beta = args
return tau/beta * gamma(1 / beta) return tau/beta * gamma(1 / beta)
@staticmethod @staticmethod
def logmean(*args): def logmean(tau, beta):
tau, beta = args
return (1-1/beta) * Eu + np.log(tau) return (1-1/beta) * Eu + np.log(tau)
@staticmethod @staticmethod
def max(*args): def max(tau, beta):
tau, beta = args
return (1.7851 - 0.87052*beta - 0.028836*beta**2 + 0.11391*beta**3) * tau 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 from itertools import product
import numpy as np import numpy as np
from nmreval.lib.decorator import adjust_dims
try: try:
from scipy.integrate import simpson from scipy.integrate import simpson
except ImportError: 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 return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma
@staticmethod @staticmethod
def correlation(t, tau0, *args): def correlation(t, tau0, sigma: float):
sigma = args[0]
_t = np.atleast_1d(t) _t = np.atleast_1d(t)
_tau = np.atleast_1d(tau0) _tau = np.atleast_1d(tau0)
@ -40,8 +42,7 @@ class LogGaussian(Distribution):
return ret_val.squeeze() return ret_val.squeeze()
@staticmethod @staticmethod
def susceptibility(omega, tau0, *args): def susceptibility(omega, tau0, sigma: float):
sigma = args[0]
_omega = np.atleast_1d(omega) _omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0) _tau = np.atleast_1d(tau0)
@ -56,8 +57,7 @@ class LogGaussian(Distribution):
return ret_val.squeeze() return ret_val.squeeze()
@staticmethod @staticmethod
def specdens(omega, tau0, *args): def specdens(omega, tau0, sigma):
sigma = args[0]
_omega = np.atleast_1d(omega) _omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0) _tau = np.atleast_1d(tau0)
@ -78,7 +78,7 @@ class LogGaussian(Distribution):
def _integrate_process_1(args): def _integrate_process_1(args):
omega_i, tau_j, sigma = 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] area += quad(_integrand_freq_imag_low, -50, 0, args=(omega_i, tau_j, sigma))[0]
return area return area
@ -102,7 +102,9 @@ def _integrand_time(u, t, tau, sigma):
return LogGaussian.distribution(uu, tau, sigma) * np.exp(-t/uu) return LogGaussian.distribution(uu, tau, sigma) * np.exp(-t/uu)
# integrands
def _integrand_freq_imag_low(u, omega, tau, sigma): def _integrand_freq_imag_low(u, omega, tau, sigma):
# integrand
uu = np.exp(u) uu = np.exp(u)
return LogGaussian.distribution(uu, tau, sigma) * omega * uu / (1 + (omega*uu)**2) 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' # 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! # WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_BaseWindow(object): class Ui_BaseWindow(object):
def setupUi(self, BaseWindow): def setupUi(self, BaseWindow):
BaseWindow.setObjectName("BaseWindow") BaseWindow.setObjectName("BaseWindow")
@ -461,6 +459,9 @@ class Ui_BaseWindow(object):
self.toolBar.addAction(self.actionSave) self.toolBar.addAction(self.actionSave)
self.toolBar.addSeparator() self.toolBar.addSeparator()
self.toolBar.addAction(self.actionMouse_behaviour) 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_calc)
self.toolbar_edit.addAction(self.action_mean_t1) self.toolbar_edit.addAction(self.action_mean_t1)
self.toolbar_edit.addAction(self.actionShift) self.toolbar_edit.addAction(self.actionShift)
@ -598,6 +599,7 @@ class Ui_BaseWindow(object):
self.actionLife.setText(_translate("BaseWindow", "Life...")) self.actionLife.setText(_translate("BaseWindow", "Life..."))
self.actionTetris.setText(_translate("BaseWindow", "Not Tetris")) self.actionTetris.setText(_translate("BaseWindow", "Not Tetris"))
self.actionUpdate.setText(_translate("BaseWindow", "Look for updates")) self.actionUpdate.setText(_translate("BaseWindow", "Look for updates"))
from ..data.datawidget.datawidget import DataWidget from ..data.datawidget.datawidget import DataWidget
from ..data.point_select import PointSelectWidget from ..data.point_select import PointSelectWidget
from ..data.signaledit.editsignalwidget import EditSignalWidget from ..data.signaledit.editsignalwidget import EditSignalWidget

View File

@ -2,19 +2,19 @@
# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui' # 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! # WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object): class Ui_Dialog(object):
def setupUi(self, Dialog): def setupUi(self, Dialog):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
Dialog.resize(400, 319) Dialog.resize(544, 443)
self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.listWidget = QtWidgets.QListWidget(Dialog) self.listWidget = QtWidgets.QListWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
@ -23,7 +23,66 @@ class Ui_Dialog(object):
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setObjectName("listWidget") 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) self.label = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
@ -31,46 +90,31 @@ class Ui_Dialog(object):
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy) self.label.setSizePolicy(sizePolicy)
self.label.setObjectName("label") self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
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.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject) self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog) 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): def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Read BDS data")) Dialog.setWindowTitle(_translate("Dialog", "Read BDS data"))
self.label.setText(_translate("Dialog", "Found temperatures")) self.groupBox_2.setTitle(_translate("Dialog", "X Axis"))
self.label_2.setText(_translate("Dialog", "Read as:")) 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.eps_checkBox.setText(_translate("Dialog", "Permittivity ε"))
self.modul_checkBox.setText(_translate("Dialog", "Modulus 1/ε")) self.modul_checkBox.setText(_translate("Dialog", "Modulus 1/ε"))
self.cond_checkBox.setText(_translate("Dialog", "Conductivity iεω")) 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): if isinstance(self._data, BDS):
self.mode = 'bds' self.mode = 'bds'
if sym_kwargs['symbol'] is None and line_kwargs['style'] is None: if sym_kwargs['symbol'] is None and line_kwargs['style'] is None:
sym_kwargs['symbol'] = next(PointContainer.symbols) if len(self._data) <= 91:
line_kwargs['style'] = LineStyle.No 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): elif isinstance(self._data, Signal):
if line_kwargs['style'] is None and sym_kwargs['symbol'] is None: 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)) raise TypeError('Unknown class %s, should be FID, Spectrum, or BDS.' % type(self._data))
for mode in ['real', 'imag']: for mode in ['real', 'imag']:
if mode == 'imag': if mode == 'imag' and line_kwargs['style'] != LineStyle.No:
line_kwargs['style'] = LineStyle.Dashed line_kwargs['style'] = LineStyle.Dashed
self.setSymbol(mode=mode, **sym_kwargs) self.setSymbol(mode=mode, **sym_kwargs)
self.setLine(mode=mode, **line_kwargs) self.setLine(mode=mode, **line_kwargs)

View File

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

View File

@ -1,7 +1,6 @@
from ...io.bds_reader import BDSReader from nmreval.io.bds_reader import BDSReader
from nmreval.gui_qt.Qt import QtCore, QtWidgets
from ..Qt import QtCore, QtWidgets from nmreval.gui_qt._py.bdsdialog import Ui_Dialog
from .._py.bdsdialog import Ui_Dialog
class QBDSReader(QtWidgets.QDialog, 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): def __init__(self, fname: str = None, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
self.setupUi(self) 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.reader = BDSReader(fname)
self.setup_gui() self.setup_gui()
def __call__(self, fname: str): def __call__(self, fname: str):
self.reader = BDSReader(fname) self.reader = BDSReader(fname)
self.fname = fname
self.setup_gui() self.setup_gui()
return self return self
def setup_gui(self): def setup_gui(self):
self.listWidget.clear() self.listWidget.clear()
self.label.setText(f'Found entries for {self.reader.fname.name}')
for temp in self.reader.set_temp: self.make_list(True)
item = QtWidgets.QListWidgetItem(str(temp))
@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.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable)
item.setCheckState(QtCore.Qt.Checked) item.setCheckState(QtCore.Qt.Checked)
self.listWidget.addItem(item) self.listWidget.addItem(item)
@ -34,19 +50,26 @@ class QBDSReader(QtWidgets.QDialog, Ui_Dialog):
def accept(self): def accept(self):
data = [] data = []
temps = [] x2 = []
for i in range(self.listWidget.count()): for i in range(self.listWidget.count()):
item = self.listWidget.item(i) item = self.listWidget.item(i)
if item.checkState() == QtCore.Qt.Checked: if item.checkState() == QtCore.Qt.Checked:
temps.append(i) x2.append(i)
for (mode, cb) in [('epsilon', self.eps_checkBox), xmode = 'freq' if self.freq_button.isChecked() else 'temp'
('sigma', self.cond_checkBox),
('modulus', self.modul_checkBox)]:
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: if cb.checkState() == QtCore.Qt.Checked:
values = self.reader.export(mode=mode) values = self.reader.export(ymode=mode, xmode=xmode)
for t in temps: for t in x2:
data.append(values[t]) data.append(values[t])
self.data_read.emit(data) self.data_read.emit(data)
@ -60,7 +83,8 @@ if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
reader = QBDSReader() 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() reader.show()
sys.exit(app.exec()) sys.exit(app.exec())

View File

@ -127,13 +127,13 @@ def pgitem_to_dic(item):
item_dic['symbolcolor'] = Colors.Black item_dic['symbolcolor'] = Colors.Black
else: else:
item_dic['symbol'] = SymbolStyle.from_str(opts['symbol']) 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'] pen = opts['pen']
if pen is not None: if pen is not None:
item_dic['linestyle'] = LineStyle(pen.style()) 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() item_dic['linewidth'] = pen.widthF()
else: else:
item_dic['linestyle'] = LineStyle.No item_dic['linestyle'] = LineStyle.No

View File

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

View File

@ -562,7 +562,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.management.skip_points(**dial.get_arguments()) 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): def coupling_dialog(self):
dialog = QCoupCalcDialog(self) dialog = QCoupCalcDialog(self)
dialog.show() dialog.show()

View File

@ -977,10 +977,10 @@ class UpperManagement(QtCore.QObject):
sd.convert(_x, *sd_param, from_=opts['tau_type'], to_='raw') sd.convert(_x, *sd_param, from_=opts['tau_type'], to_='raw')
relax = Relaxation() 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]] 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': if opts['out'] == 't1':
y = relax.t1(x2, _x) 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 ...utils.text import convert
from ..Qt import QtWidgets, QtCore from ..Qt import QtWidgets, QtCore
@ -11,7 +11,7 @@ class QCoupCalcDialog(QtWidgets.QDialog, Ui_coupling_calc_dialog):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.cp = [Quadrupolar, QuadrupolarQCC, Czjzek, HeteroDipolar, HomoDipolar] self.cp = [Quadrupolar, Czjzek, HeteroDipolar, HomoDipolar]
for cp in self.cp: for cp in self.cp:
self.comboBox.addItem(cp.name) self.comboBox.addItem(cp.name)
@ -62,7 +62,7 @@ class QCoupCalcDialog(QtWidgets.QDialog, Ui_coupling_calc_dialog):
for pp in self._coupling_parameter: for pp in self._coupling_parameter:
p.append(pp.value) 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__': if __name__ == '__main__':

View File

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

View File

@ -2,10 +2,11 @@ import re
import struct import struct
import warnings import warnings
from pathlib import Path from pathlib import Path
from typing import List
import numpy as np import numpy as np
from ..data.signals import Signal from ..data import BDS, Points
MAGIC_RE = re.compile(b'NOVOCONTROL DK-RESULTS') MAGIC_RE = re.compile(b'NOVOCONTROL DK-RESULTS')
@ -27,6 +28,9 @@ class BDSReader:
return self return self
def __getitem__(self, val):
return self.opts[val]
def _parse_file(self): def _parse_file(self):
self.opts = {} self.opts = {}
with self.fname.open(mode='rb') as f: with self.fname.open(mode='rb') as f:
@ -43,7 +47,7 @@ class BDSReader:
f.seek(1, 1) f.seek(1, 1)
self.opts['electrode thickness'] = struct.unpack('f', f.read(4))[0] 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) f.seek(2, 1)
self.opts['capacity'] = struct.unpack('f', f.read(4))[0] 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 self.opts['C_0'] = (self.opts['area']-self.opts['spacer area']) / self.opts['thickness'] * 8.8541878128e-12
f.seek(158) 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) b = f.read(name_length)
self.opts['name'] = str(struct.unpack(f'<{name_length}s', b)[0][:-1], encoding='utf-8') self.opts['name'] = str(struct.unpack(f'<{name_length}s', b)[0][:-1], encoding='utf-8')
_ = struct.unpack('h', f.read(2)) _ = 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) self.x = np.empty(freq_values_length)
for freqs in range(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)) _ = struct.unpack('h', f.read(2)) # TODO if no temperature set this is not 400 and rest breaks
length_temps = struct.unpack('b', f.read(1))[0] 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))) self.opts['temperatures'] = np.array(struct.unpack('f' * length_temps, f.read(4*length_temps)))
f.seek(110, 1) 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], self.opts['date'] = str(struct.unpack(f'<{date_length}s', f.read(date_length))[0][:-1],
encoding='utf-8') encoding='utf-8')
f.seek(47, 1) f.seek(47, 1)
# there are 9 different values per frequency and temperature
_y = [] _y = []
actual_temps_length = 0 actual_temps_length = 0
read_length = 9 * freq_values_length read_length = 9 * freq_values_length # per entry 9 * 4 byte
while True: while True:
# catch error if measurement was aborted
try: 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 actual_temps_length += 1
if actual_temps_length == length_temps:
break
except struct.error: except struct.error:
break break
@ -100,79 +112,86 @@ class BDSReader:
warnings.warn('Number of set temperatures does not match number of data points') 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)) _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 # 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)) self.y = np.transpose(_y[:, :, :6], (2, 0, 1))
def export(self, mode='epsilon') -> list: def export(self, ymode: str = 'epsilon', xmode: str = 'freq') -> List[BDS]:
if mode == 'epsilon': y = getattr(self, ymode)()
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 = []
useful_info = { useful_info = {
'voltage': self.opts['voltage'], 'voltage': self.opts['voltage'],
'diameter': self.opts['diameter'], 'diameter': self.opts['diameter'],
'area': self.opts['area'], 'area': self.opts['area'],
'thickness': self.opts['thickness'], 'thickness': self.opts['thickness'],
'mode': mode 'mode': ymode
} }
for i, temps in enumerate(self.temperature): if xmode == 'freq':
ret_val.append(Signal(x=self.frequencies, y=eps[0][i, :] + 1j*eps[1][i, :], fmt = '.2f'
name=f'{temps:.2f}', value=temps, **useful_info)) 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 return ret_val
def __getitem__(self, val): def capacity(self):
return self.opts[val] # 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 @property
def set_temp(self): def set_temp(self):
return self.opts['temperatures'][:len(self.temperature)] return self.opts['temperatures'][:len(self.temperature)]
@property
def probe_temp(self):
return self.y[2]
@property @property
def temperature(self): def temperature(self):
return self.probe_temp.mean(axis=1) return self.sample_temp().mean(axis=1)
@property @property
def frequencies(self): def frequencies(self):
return self.x 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 from ..math.mittagleffler import mlf
ArrayLike = TypeVar('ArrayLike') ArrayLike = TypeVar('ArrayLike')

View File

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

View File

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

View File

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

View File

@ -1,5 +1,9 @@
import numpy as np 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 from ..math.orientations import zcw_spherical as crystallites
@ -29,21 +33,22 @@ class Pake:
_x -= 0.5*_x[-1] _x -= 0.5*_x[-1]
if broad == 'l': if broad == 'l':
apd = 2 * sigma / (4 * _x**2 + sigma**2) / np.pi apd = 2 * sigma / (4 * _x**2 + sigma**2) / pi
else: 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') ret_val = np.convolve(s, apd, mode='same')
else: else:
ret_val = s ret_val = s
omega_1 = np.pi/2/t_pulse omega_1 = 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) 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 ret_val *= attn
return c * ret_val / trapz(ret_val, x) return c * ret_val / simpson(ret_val, x)
class CSA: class CSA:
@ -69,14 +74,14 @@ class CSA:
_x = np.arange(len(x)) * (x[1] - x[0]) _x = np.arange(len(x)) * (x[1] - x[0])
_x -= 0.5 * _x[-1] _x -= 0.5 * _x[-1]
if broad == 'l': if broad == 'l':
apd = 2 * sigma / (4*_x**2 + sigma**2) / np.pi apd = 2 * sigma / (4*_x**2 + sigma**2) / pi
else: 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') ret_val = np.convolve(s, apd, mode='same')
else: else:
ret_val = s ret_val = s
return c * ret_val / trapz(ret_val, x) return c * ret_val / simpson(ret_val, x)
class SecCentralLine: 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 pathlib import Path
from typing import Tuple from typing import Any, Optional, Tuple, Type, Union
from warnings import warn from warnings import warn
import numpy as np import numpy as np
@ -8,138 +14,417 @@ from scipy.interpolate import interp1d, Akima1DInterpolator
from scipy.optimize import minimize from scipy.optimize import minimize
from nmreval.lib.utils import ArrayLike from nmreval.lib.utils import ArrayLike
from .coupling import Coupling
from ..distributions.base import Distribution
from ..distributions.debye import Debye as Debye from ..distributions.debye import Debye as Debye
from ..utils import gamma_full
class Relaxation: class Relaxation:
def __init__(self, distribution=None): """
self._distribution = distribution Class to calculate relaxation times.
self._dist_parameter = () """
def __init__(self, distribution: Type[Distribution] = None):
self.distribution = distribution
self.dist_parameter = ()
self._dist_kw = {} self._dist_kw = {}
self._coupling = None self.coupling = None
self._coup_parameter = () self.coup_parameter = []
self._coup_kw = {} self.coup_kw = {}
self._prefactor = 1. self.prefactor = 1.
def __repr__(self): def __repr__(self):
if self._distribution is not None: if self.distribution is not None:
return str(self._distribution.name) return str(self.distribution.name)
else: else:
return super().__repr__() return super().__repr__()
def coupling(self, coupling, parameter: list = None, keywords: dict = None): def set_coupling(self, coupling: Union[float, Type[Coupling]],
self._coupling = coupling parameter: Union[tuple, list] = None, keywords: dict = None):
if parameter is not None: if parameter is not None:
self._coup_parameter = parameter self.coup_parameter = parameter
if keywords is not None: if keywords is not None:
self._coup_kw = keywords self.coup_kw = keywords
if isinstance(self._coupling, Number): if isinstance(coupling, float):
self._prefactor = self._coupling 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: else:
try: raise ValueError(f'`coupling` is not number or of type `Coupling`, found {coupling!r}')
self._prefactor = self._coupling.func(*self._coup_parameter, **self._coup_kw)
except TypeError:
pass
def distribution(self, dist, parameter=None, keywords=None): def set_distribution(self, dist: Type[Distribution], parameter: Union[tuple, list] = None, keywords: dict = None):
self._distribution = dist self.distribution = dist
if parameter is not None: if parameter is not None:
self._dist_parameter = parameter self.dist_parameter = parameter
if keywords is not None: if keywords is not None:
self._dist_kw = keywords self._dist_kw = keywords
def t1(self, omega, tau, *args, mode='bpp', **kwargs): def t1(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
# defaults to BPP mode: str = 'bpp', **kwargs) -> Union[np.ndarray, float]:
if mode == 'bpp': r"""
return self.t1_bpp(omega, tau, *args, **kwargs) Convenience function
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:
"""
Args: Args:
omega: omega (array-like): Frequency in 1/s (not Hz)
tau: tau (array-like): Correlation times in s
*args: *specdens_args: Additional parameter for spectral density.
inverse: If given this will replace previously set arguments during calculation
prefactor: 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: **kwargs:
Returns: 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: if mode not in ['bpp', 'dipolar', 'csa']:
omega_2 = kwargs['omega_coup'] raise ValueError(f'Unknown mode {mode} not `bpp`, `dipolar`, `csa`.')
except KeyError:
if kwargs.get('gamma_obs', False): # defaults to BPP
omega_2 = kwargs['gamma_coup'] / kwargs['gamma_obs'] * omega if mode == 'bpp':
else: return self.t1_bpp(omega, tau, *specdens_args, **kwargs)
raise AttributeError('Unknown second frequency.') 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: if prefactor is None:
prefactor = self._prefactor prefactor = self.prefactor
if len(args) == 0: if len(specdens_args) == 0:
args = self._dist_parameter specdens_args = self.dist_parameter
rate = prefactor * (3 * self._distribution.specdens(omega, tau, *args) + rate = prefactor * (self.distribution.specdens(omega - omega_2, tau, *specdens_args) +
self._distribution.specdens(np.abs(omega - omega_2), tau, *args) + 3 * self.distribution.specdens(omega, tau, *specdens_args) +
6 * self._distribution.specdens(omega + omega_2, tau, *args)) 6 * self.distribution.specdens(omega + omega_2, tau, *specdens_args)) / 2
if inverse: if inverse:
return 1 / rate return 1 / rate
else: else:
return rate 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: if prefactor is None:
prefactor = self._prefactor prefactor = self.prefactor
if len(args) == 0: if len(specdens_args) == 0:
args = self._dist_parameter specdens_args = self.dist_parameter
rate = prefactor * (self._distribution.specdens(omega, tau, *args) + rate = prefactor * (self.distribution.specdens(omega, tau, *specdens_args) +
4*self._distribution.specdens(2*omega, tau, *args)) 4 * self.distribution.specdens(2 * omega, tau, *specdens_args))
if inverse: if inverse:
return 1. / rate return 1. / rate
else: else:
return rate 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: if prefactor is None:
prefactor = self._prefactor prefactor = self.prefactor
if len(args) == 0: if len(specdens_args) == 0:
args = self._dist_parameter specdens_args = self.dist_parameter
rate = prefactor * (10 * self._distribution.specdens(omega, tau, *args) + rate = prefactor * (self.distribution.specdens(omega, tau, *specdens_args) +
4 * self._distribution.specdens(2 * omega, tau, *args) + 4 * self.distribution.specdens(2 * omega, tau, *specdens_args))
6 * self._distribution.specdens(omega_rf, tau, *args))
if inverse: if inverse:
return 1. / rate return 1. / rate
else: else:
return rate 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: if prefactor is None:
prefactor = self._prefactor / 2. prefactor = self.prefactor
if len(args) == 0: if len(specdens_args) == 0:
args = self._dist_parameter specdens_args = self.dist_parameter
rate = prefactor * (3 * self._distribution.specdens(0, tau, *args) + rate = 3 * prefactor * self.distribution.specdens(omega, tau, *specdens_args)
5 * self._distribution.specdens(omega, tau, *args) + if inverse:
2 * self._distribution.specdens(2 * omega, tau, *args)) 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: if inverse:
return 1. / rate return 1. / rate
else: else:
@ -158,60 +443,31 @@ class RelaxationEvaluation(Relaxation):
self.y = None self.y = None
def data(self, temp, t1): def data(self, temp, t1):
temp = np.asarray(temp) temp = np.asanyarray(temp)
t1 = np.asarray(t1) t1 = np.asanyarray(t1)
sortidx = temp.argsort() sortidx = temp.argsort()
self.x = temp[sortidx] self.x = temp[sortidx]
self.y = t1[sortidx] self.y = t1[sortidx]
self.calculate_t1_min() self.calculate_t1_min()
def calculate_t1_min(self, interpolate: int = None, trange=None): def get_increase(self, height: float = None, idx: int = 0, mode: str = None, omega: float = None,
min_index = np.argmin(self.y) dist_parameter: Union[tuple, list] = None, prefactor: Union[tuple, list, float] = None,
t1_min = (self.x[min_index], self.y[min_index]) coupling_kwargs: dict = None):
parabola = None """
self._interpolate_range = (None, None) Determine a single parameter from a T1 minimum
if interpolate is not None: Args:
if interpolate not in [0, 1, 2, 3]: height (float, optional): Height of T1 minimum
raise ValueError(f'Unknown interpolation value {interpolate}') 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: Returns:
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]
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 stretching = mini = np.nan
if height is None: if height is None:
@ -224,44 +480,62 @@ class RelaxationEvaluation(Relaxation):
omega = self.omega omega = self.omega
if prefactor is None: if prefactor is None:
prefactor = self._prefactor prefactor = self.prefactor
if dist_parameter is None: 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: tau_lims = np.log10(1 / omega) - 3, np.log10(1 / omega) + 3
if isinstance(prefactor, list):
if mode is None:
# nothing is variable -> just calculate minimum for given parameter
if isinstance(prefactor, (tuple, list)):
if coupling_kwargs is None: if coupling_kwargs is None:
coupling_kwargs = self._coup_kw coupling_kwargs = self.coup_kw
prefactor = self._coupling.func(*prefactor, **coupling_kwargs) prefactor = self.coupling.relax(*prefactor, **coupling_kwargs)
return stretching, np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), return stretching, np.min(self.t1(omega, np.logspace(*tau_lims, num=1001),
*dist_parameter, prefactor=prefactor, inverse=True)) *dist_parameter, prefactor=prefactor, inverse=True))
if variable[0] == 'distribution': if mode == 'distribution':
if isinstance(self._distribution, Debye): # width parameter of spectral density is variable
return 1.
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: if coupling_kwargs is None:
coupling_kwargs = self._coup_kw coupling_kwargs = self.coup_kw
prefactor = self._coupling.func(*prefactor, **coupling_kwargs)
prefactor = self.coupling.relax(*prefactor, **coupling_kwargs)
use_fmin = True 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 from importlib.resources import path
with path('resources.nmr', 'T1_min.npz') as fp: with path('resources.nmr', 'T1_min.npz') as fp:
if fp.exists(): 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 debye_min = omega / 1.42517571908650 / prefactor # Zahl kommt von Wolfram alpha
ratio = height / debye_min 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 use_fmin = False
dist_parameter = [stretching]
if use_fmin: if use_fmin:
# use for untabulated spectral densities or if something went wrong
#
def _f(_x, p, ii): def _f(_x, p, ii):
p[ii] = _x[0] p[ii] = _x[0]
t1_calc = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *p, 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) return np.abs(t1_calc - height)
stretching = minimize(_f, np.array([dist_parameter[variable[1]]]), stretching = minimize(_f, np.array([dist_parameter[idx]]),
args=(dist_parameter, variable[1]), args=(dist_parameter, idx),
method='Nelder-Mead').x[0] 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), t1_no_coup = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001),
*dist_parameter, prefactor=1)) *dist_parameter, prefactor=1))
if isinstance(prefactor, list): if isinstance(prefactor, (tuple, list)):
def _f(_x, p, ii): prefactor = list(prefactor)
p[ii] = _x
return np.abs(t1_no_coup/height - self._coupling.func(*p, **coupling_kwargs)[0]) 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: else:
stretching = t1_no_coup / height stretching = t1_no_coup / height
else: else:
raise ValueError('Use "distribution" or "coupling" to set parameter') raise ValueError('Use `distribution` or `coupling` to set parameter')
if stretching: if stretching:
self._prefactor = prefactor self.prefactor = prefactor
self._dist_parameter = dist_parameter self.dist_parameter = dist_parameter
if isinstance(prefactor, list): if isinstance(prefactor, (tuple, list)):
self._coup_parameter = prefactor self.coup_parameter = prefactor
mini = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *self._dist_parameter, self.prefactor = self.coupling.relax(*self.coup_parameter, **self.coup_kw)
prefactor=self._prefactor)) 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 return stretching, mini
def correlation_from_t1(self, mode: str = 'raw', interpolate=False, omega=None, def calculate_t1_min(self, interpolate: int = None, trange: Tuple[float, float] = None, use_log: bool = False) -> \
dist_parameter: list = None, prefactor: float = None, Tuple[Tuple[float, float], Optional[Tuple[np.ndarray, np.ndarray]]]:
coupling_param: list = None, coupling_kwargs: dict = None): 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: if self.x is None:
raise ValueError('Temperature is not set') raise ValueError('Temperature is not set')
@ -317,40 +651,40 @@ class RelaxationEvaluation(Relaxation):
if prefactor is None: if prefactor is None:
if coupling_kwargs is None: if coupling_kwargs is None:
coupling_kwargs = self._coup_kw coupling_kwargs = self.coup_kw
if coupling_param is None: if coupling_param is None:
prefactor = self._prefactor prefactor = self.prefactor
else: else:
prefactor = self._coupling.func(*coupling_param, **coupling_kwargs) prefactor = self.coupling.relax(*coupling_param, **coupling_kwargs)
if dist_parameter is None: if dist_parameter is None:
dist_parameter = self._dist_parameter dist_parameter = self.dist_parameter
fast_idx = self.x > self.t1min[0] fast_idx = self.x > self.t1min[0]
slow_t1 = self.y[~fast_idx] slow_t1 = self.y[~fast_idx]
slow_temp = self.x[~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) offset = len(slow_t1)
base_taus = np.logspace(-10, -7, num=1001) base_taus = np.logspace(-10, -7, num=1001)
min_tau = base_taus[np.argmin(self.t1(omega, base_taus, *dist_parameter, prefactor=prefactor))] 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) 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] t1_i = slow_t1[-i]
if interpolate and self._interpolate is not None: if interpolate and self._interpolate is not None:
if slow_temp[-i] >= self._interpolate_range[0]: if slow_temp[-i] >= self._interpolate_range[0]:
t1_i = self._interpolate(slow_temp[-i]) 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') warn('Correlation time could not be calculated')
correlation_times[offset-i] = taus[0] correlation_times[offset - i] = taus[0]
continue continue
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0] 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) current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0] 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]) 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] correlation_times[offset - i] = (taus[cross_idx + 1] * lamb + (1 - lamb) * taus[cross_idx])[0]
fast_t1 = self.y[fast_idx] fast_t1 = self.y[fast_idx]
fast_temp = self.x[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) current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
for i in range(len(fast_t1)): for i in range(len(fast_t1)):
@ -376,8 +710,8 @@ class RelaxationEvaluation(Relaxation):
t1_i = self._interpolate(fast_temp[i]) t1_i = self._interpolate(fast_temp[i])
if current_t1[-1] > t1_i: if current_t1[-1] > t1_i:
correlation_times[offset+i] = taus[-1] correlation_times[offset + i] = taus[-1]
warn('Correlation time could not be calculated') warn(f'Correlation time for {correlation_times[offset + i]} could not be calculated')
continue continue
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0] 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) current_t1 = self.t1(omega, taus, *dist_parameter, prefactor=prefactor)
cross_idx = np.where(np.diff(np.sign(current_t1 - t1_i)))[0] 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]) 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] correlation_times[offset + i] = (taus[cross_idx + 1] * lamb + (1 - lamb) * taus[cross_idx])[0]
opts = {'distribution': (self._distribution.name, dist_parameter), opts = {'distribution': (self.distribution.name, dist_parameter),
'coupling': (self._coupling.name, coupling_param, coupling_kwargs), 'frequency': omega / 2 / np.pi}
'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): def _create_minimum_file(self, filename):
x = np.geomspace(0.1, 1, num=10001) x = np.geomspace(0.1, 1, num=10001)
with Path(filename).open('w') as f: 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)): for i, a in enumerate(np.geomspace(0.1, 10, num=10000)):
alpha = a alpha = a
t1_i = self.t1_bpp(1, x, alpha) 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 NA = 6.02214076e23
kb_joule = 1.380649e-23 kb_joule = 1.380649e-23
e = 1.602176634e-19 e = 1.602176634e-19
h_joule = 6.62607015e-23 h_joule = 6.62606896e-34
mu0 = 1.256637062124e-6 mu0 = 1.256637062124e-6
epsilon0 = 8.8541878128e-12 epsilon0 = 8.8541878128e-12
@ -38,7 +38,7 @@ spintxt = """\
9Be 3/2 100 -3.759666 5.288 9Be 3/2 100 -3.759666 5.288
10B 3 19.9 2.8746786 8.459 10B 3 19.9 2.8746786 8.459
11B 3/2 80.1 8.5847044 4.059 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 14N 1 99.632 1.9337792 2.044
15N 1/2 0.368 -2.71261804 15N 1/2 0.368 -2.71261804
17O 5/2 0.038 -3.62808 -2.558 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}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{}', r'\f{Symbol}w\f{}',
'alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu ' # plain '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 = [ big_greek = [
r'\Alpha \Beta \Gamma \Delta \Epsilon \Zeta \Eta \Theta \Iota \Kappa \Lambda \Mu ' # tex 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}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{}', 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 '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 = [ special_chars = [
r'\infty \int \sum \langle \rangle \pm \perp \para \leftarrow \rightarrow \leftrightarrow \cdot \hbar', 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{} '
r'\f{Symbol}^\f{} \f{Symbol}||\f{} \f{Symbol}¬\f{} \f{Symbol}®\f{} \f{Symbol}«\f{} \f{Symbol}×\f{Symbol} ' 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}', 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 = [ funcs = [
r'\exp \log \ln \sin \cos \tan', 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',
'exp log ln sin cos tan' 'exp log ln sin cos tan',
] ]
delims = [ delims = [
[(r'_{', r'}'), (r'<sub>', r'</sub>'), (r'\\s', r'\\N'), (r'_{', r'}')], [(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="actionSave"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionMouse_behaviour"/> <addaction name="actionMouse_behaviour"/>
<addaction name="separator"/>
<addaction name="actionPrevious"/>
<addaction name="actionNext_window"/>
</widget> </widget>
<widget class="QToolBar" name="toolbar_edit"> <widget class="QToolBar" name="toolbar_edit">
<property name="sizePolicy"> <property name="sizePolicy">

View File

@ -6,15 +6,30 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>544</width>
<height>319</height> <height>443</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Read BDS data</string> <string>Read BDS data</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <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"> <widget class="QListWidget" name="listWidget">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding"> <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
@ -24,74 +39,150 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout"> <widget class="QGroupBox" name="groupBox_2">
<item> <property name="title">
<widget class="QCheckBox" name="eps_checkBox"> <string>X Axis</string>
<property name="text"> </property>
<string>Permittivity ε</string> <layout class="QVBoxLayout" name="verticalLayout_3">
</property> <property name="spacing">
<property name="checked"> <number>3</number>
<bool>true</bool> </property>
</property> <property name="leftMargin">
</widget> <number>3</number>
</item> </property>
<item> <property name="topMargin">
<widget class="QCheckBox" name="modul_checkBox"> <number>3</number>
<property name="text"> </property>
<string>Modulus 1/ε</string> <property name="rightMargin">
</property> <number>3</number>
</widget> </property>
</item> <property name="bottomMargin">
<item> <number>3</number>
<widget class="QCheckBox" name="cond_checkBox"> </property>
<property name="text"> <item>
<string>Conductivity iεω</string> <widget class="QRadioButton" name="freq_button">
</property> <property name="text">
</widget> <string>Frequency</string>
</item> </property>
<item> <property name="checked">
<spacer name="verticalSpacer"> <bool>true</bool>
<property name="orientation"> </property>
<enum>Qt::Vertical</enum> <attribute name="buttonGroup">
</property> <string notr="true">buttonGroup</string>
<property name="sizeHint" stdset="0"> </attribute>
<size> </widget>
<width>20</width> </item>
<height>40</height> <item>
</size> <widget class="QRadioButton" name="temp_button">
</property> <property name="text">
</spacer> <string>Temperature</string>
</item> </property>
</layout> <attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item> </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"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -101,8 +192,29 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </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/> <resources/>
<connections> <connections>
<connection> <connection>
@ -112,8 +224,8 @@
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>248</x> <x>254</x>
<y>254</y> <y>411</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
@ -128,8 +240,8 @@
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>316</x> <x>322</x>
<y>260</y> <y>411</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>
@ -138,4 +250,7 @@
</hints> </hints>
</connection> </connection>
</connections> </connections>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui> </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.