save fit parameter and agr; more doc

This commit is contained in:
dominik 2022-03-24 17:35:10 +01:00
parent ef81030213
commit 73e4a2b4d9
19 changed files with 209 additions and 284 deletions

View File

@ -5,8 +5,8 @@
# from the environment for the first two. # from the environment for the first two.
SPHINXOPTS ?= SPHINXOPTS ?=
SPHINXBUILD ?= /autohome/dominik/miniconda3/bin/sphinx-build SPHINXBUILD ?= /autohome/dominik/miniconda3/bin/sphinx-build
SOURCEDIR = /autohome/dominik/nmreval/docs/source SOURCEDIR = /autohome/dominik/nmreval/doc/source
BUILDDIR = /autohome/dominik/nmreval/docs/build BUILDDIR = /autohome/dominik/nmreval/doc/_build
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
help: help:

View File

@ -1,12 +1,19 @@
""" """
======================= ==========
Spin-lattice relaxation T1 minimum
======================= ==========
Example for ``RelaxationEvaluation`` is used to get width parameter from a T1 minimum.
As a subclass of ``Relaxation`` it can also be used to calculate Relaxation times.
The basic steps are:
* Determine a T1 minimum with `nmreval.nmr.RelaxationEvaluation.calculate_t1_min`
* Calculate width parameter of a spectral density/coupling constants/... with
``RelaxationEvaluation.get_increase``
* Calculate correlation times from these values with ``RelaxationEvaluation.correlation_from_t1``
""" """
import numpy as np import numpy as np
from matplotlib import pyplot as plt import matplotlib.pyplot as plt
from nmreval.distributions import ColeDavidson from nmreval.distributions import ColeDavidson
from nmreval.nmr import Relaxation, RelaxationEvaluation from nmreval.nmr import Relaxation, RelaxationEvaluation
@ -20,7 +27,7 @@ temperature = 1000/inv_temp
# spectral density parameter # spectral density parameter
ea = 0.45 ea = 0.45
tau = 1e-21 * np.exp(ea / kB / temperature) tau = 1e-21 * np.exp(ea / kB / temperature)
gamma_cd = 0.1 gamma_cd = 0.4
# interaction parameter # interaction parameter
omega = 2*np.pi*46e6 omega = 2*np.pi*46e6
@ -28,40 +35,57 @@ delta = 120e3
eta = 0 eta = 0
r = Relaxation() r = Relaxation()
r.set_distribution(ColeDavidson) # the only parameter that has to be set beforehand r.set_distribution(ColeDavidson) # the only parameter that set beforehand
t1_values = r.t1(omega, tau, gamma_cd, mode='bpp', t1_values = r.t1(omega, tau, gamma_cd, mode='bpp',
prefactor=Quadrupolar.relax(delta, eta)) prefactor=Quadrupolar.relax(delta, eta))
# add noise # add noise
rng = np.random.default_rng(123456789) rng = np.random.default_rng()
noisy = (rng.random(t1_values.size)-0.5) * 0.5 * t1_values + t1_values noisy = (rng.random(t1_values.size)-0.5) * 0.5 * t1_values + t1_values
# set parameter and data ax_t1 = plt.figure().add_subplot()
ax_t1.semilogy(inv_temp, t1_values, label='Calculated T1')
ax_t1.semilogy(inv_temp, noisy, 'o', label='Noise')
ax_t1.legend()
plt.show()
# Actual evaluation starts here
# setting necessary parameter
r_eval = RelaxationEvaluation() r_eval = RelaxationEvaluation()
r_eval.set_distribution(ColeDavidson) r_eval.set_distribution(ColeDavidson)
r_eval.set_coupling(Quadrupolar, (delta, eta)) r_eval.set_coupling(Quadrupolar, (delta, eta))
r_eval.data(temperature, noisy) r_eval.set_data(temperature, noisy)
r_eval.omega = omega r_eval.omega = omega
# Find a T1 minumum
t1_min_data, _ = r_eval.calculate_t1_min() # second argument is None 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) t1_min_inter, line = r_eval.calculate_t1_min(interpolate=1, trange=(160, 195), use_log=True)
fig, ax = plt.subplots() ax_min = plt.figure().add_subplot()
ax.semilogy(1000/t1_min_data[0], t1_min_data[1], 'rx', label='Data minimum') ax_min.semilogy(inv_temp, noisy, 'o', label='Data')
ax.semilogy(1000/t1_min_inter[0], t1_min_inter[1], 'r+', label='Parabola') ax_min.semilogy(1000/line[0], line[1], '--')
ax.semilogy(1000/line[0], line[1]) ax_min.semilogy(1000/t1_min_data[0], t1_min_data[1], 'C2X',label='Data minimum')
ax_min.semilogy(1000/t1_min_inter[0], t1_min_inter[1], 'C3P',label='Parabola')
ax_min.set_xlim(4.5, 7)
ax_min.set_ylim(1e-3, 1e-1)
ax_min.legend()
# Vary the first (and for Cole-Davidson, only) parameter of the spectral density
found_gamma, found_height = r_eval.get_increase(t1_min_inter[1], idx=0, mode='distribution') found_gamma, found_height = r_eval.get_increase(t1_min_inter[1], idx=0, mode='distribution')
print(found_gamma) print(f'Minimum at {found_height} for {found_gamma}; input is {gamma_cd}')
plt.axhline(found_height)
plt.show() plt.show()
#%% ##################################################################################
# Now we found temperature and height of the minimum we can calculate the correlation time # Calculation of correlation times uses previously parameter for spectral density
# and prefactor
plt.semilogy(1000/temperature, tau) tau_from_t1, opts = r_eval.correlation_from_t1(mode='mean')
tau_from_t1, opts = r_eval.correlation_from_t1() print(f'Used options: {opts}')
print(opts)
plt.semilogy(1000/tau_from_t1[:, 0], tau_from_t1[:, 1], 'o') ax_tau = plt.figure().add_subplot()
ax_tau.semilogy(inv_temp, tau*gamma_cd, label='Original input')
ax_tau.semilogy(1000/tau_from_t1[:, 0], tau_from_t1[:, 1], 'o', label='Calculated')
ax_tau.legend()
plt.show() plt.show()

View File

@ -1,179 +0,0 @@
: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

@ -1,3 +0,0 @@
'/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

@ -1,3 +0,0 @@
'/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

@ -485,7 +485,7 @@ class PointContainer(ExperimentContainer):
} }
if sym_kwargs['symbol'] is None and line_kwargs['style'] is None: if sym_kwargs['symbol'] is None and line_kwargs['style'] is None:
if len(self._data) > 1000: if len(self._data) > 500:
line_kwargs['style'] = LineStyle.Solid line_kwargs['style'] = LineStyle.Solid
sym_kwargs['symbol'] = SymbolStyle.No sym_kwargs['symbol'] = SymbolStyle.No
else: else:

View File

@ -138,8 +138,6 @@ class ConversionDialog(QtWidgets.QDialog, Ui_Dialog):
src_sets.append((set_id_real, set_id_imag, graph_id, type_idx)) src_sets.append((set_id_real, set_id_imag, graph_id, type_idx))
print(src_sets)
self.convertSets.emit(src_sets) self.convertSets.emit(src_sets)
return src_sets return src_sets

View File

@ -124,7 +124,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
self.data_parameter[idx].blockSignals(False) self.data_parameter[idx].blockSignals(False)
@QtCore.pyqtSlot(str, object) @QtCore.pyqtSlot(str, object)
def change_global_choice(self, argname, value): def change_global_choice(self, _, value):
idx = self.global_parameter.index(self.sender()) idx = self.global_parameter.index(self.sender())
self.glob_values[idx] = value self.glob_values[idx] = value
if self.data_values[self.comboBox.currentData()][idx] is None: if self.data_values[self.comboBox.currentData()][idx] is None:
@ -242,6 +242,8 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
else: else:
if p_i is None: if p_i is None:
kw_p.update(g.value) kw_p.update(g.value)
elif isinstance(p_i, dict):
kw_p.update(p_i)
else: else:
kw_p[g.argname] = p_i kw_p[g.argname] = p_i

View File

@ -64,7 +64,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
# reconnect "Export..." in context menu to our function # reconnect "Export..." in context menu to our function
self.scene.contextMenu[0].disconnect() self.scene.contextMenu[0].disconnect()
self.scene.contextMenu[0].triggered.connect(self.export) self.scene.contextMenu[0].triggered.connect(self.export_dialog)
def _init_gui(self): def _init_gui(self):
self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter))) self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter)))
@ -515,7 +515,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
(item in self.graphic.items() or other_item in self.graphic.items()): (item in self.graphic.items() or other_item in self.graphic.items()):
self.legend.addItem(item, convert(item.opts.get('name', ''), old='tex', new='html')) self.legend.addItem(item, convert(item.opts.get('name', ''), old='tex', new='html'))
def export(self): def export_dialog(self):
filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)' filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)'
for imgformat in QtGui.QImageWriter.supportedImageFormats(): for imgformat in QtGui.QImageWriter.supportedImageFormats():
str_format = imgformat.data().decode('utf-8') str_format = imgformat.data().decode('utf-8')
@ -524,6 +524,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
outfile, _ = QtWidgets.QFileDialog.getSaveFileName(self, caption='Export graphic', filter=filters, outfile, _ = QtWidgets.QFileDialog.getSaveFileName(self, caption='Export graphic', filter=filters,
options=QtWidgets.QFileDialog.DontConfirmOverwrite) options=QtWidgets.QFileDialog.DontConfirmOverwrite)
if outfile: if outfile:
self.export(outfile)
def export(self, outfile: str):
_, suffix = os.path.splitext(outfile) _, suffix = os.path.splitext(outfile)
if suffix == '': if suffix == '':
QtWidgets.QMessageBox.warning(self, 'No file extension', QtWidgets.QMessageBox.warning(self, 'No file extension',
@ -566,7 +569,6 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
from ..io.exporters import PDFPrintExporter from ..io.exporters import PDFPrintExporter
PDFPrintExporter(self.graphic).export(outfile) PDFPrintExporter(self.graphic).export(outfile)
elif suffix == '.svg': elif suffix == '.svg':
from pyqtgraph.exporters import SVGExporter from pyqtgraph.exporters import SVGExporter
SVGExporter(self.scene).export(outfile) SVGExporter(self.scene).export(outfile)
@ -591,8 +593,6 @@ 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,3 +1,7 @@
from __future__ import annotations
import pathlib
from ..Qt import QtWidgets, QtCore from ..Qt import QtWidgets, QtCore
@ -74,15 +78,45 @@ class SaveDirectoryDialog(_FileDialog):
self.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False) self.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)
self.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) self.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
lay = self.layout()
self.label = QtWidgets.QLabel(self) self.label = QtWidgets.QLabel(self)
self.label.setTextFormat(QtCore.Qt.RichText) self.label.setTextFormat(QtCore.Qt.RichText)
self.label.setText('Use <b>&lt;label&gt;</b> as placeholder in filename. (e.g. <i>t1_&lt;label&gt;.dat</i>)') self.label.setText('Use <b>&lt;label&gt;</b> as placeholder in filename. (e.g. <i>t1_&lt;label&gt;.dat</i>)')
self.layout().addWidget(self.label, self.layout().rowCount(), 0, 1, self.layout().columnCount()) lay.addWidget(self.label, lay.rowCount(), 0, 1, lay.columnCount())
line = QtWidgets.QFrame(self)
line.setFrameShape(line.HLine)
line.setFrameShadow(line.Sunken)
lay.addWidget(line, lay.rowCount(), 0, 1, lay.columnCount())
h_layout = QtWidgets.QHBoxLayout()
h_layout.setContentsMargins(0, 0, 0, 0)
h_layout.setSpacing(3)
self.checkBox = QtWidgets.QCheckBox(self) self.checkBox = QtWidgets.QCheckBox(self)
self.checkBox.setChecked(True) self.checkBox.setChecked(True)
self.checkBox.setText('Replace spaces with underscore') self.checkBox.setText('Replace spaces with _')
self.layout().addWidget(self.checkBox, self.layout().rowCount(), 0, 1, self.layout().columnCount()) h_layout.addWidget(self.checkBox)
self.agr_cb = QtWidgets.QCheckBox(self)
self.agr_cb.setChecked(True)
self.agr_cb.setText('Save graph as Grace file')
h_layout.addWidget(self.agr_cb)
self.fit_cb = QtWidgets.QCheckBox(self)
self.fit_cb.setChecked(True)
self.fit_cb.setText('Save fit parameter')
h_layout.addWidget(self.fit_cb)
lay.addLayout(h_layout, lay.rowCount(), 0, 1, lay.columnCount())
self.setWindowTitle('Save') self.setWindowTitle('Save')
self.setNameFilters(['All files (*.*)', 'Session file (*.nmr)', 'Text file (*.dat)', 'HDF file (*.h5)', 'Grace files (*.agr)']) self.setNameFilters(['All files (*.*)', 'Session file (*.nmr)', 'Text file (*.dat)',
'HDF file (*.h5)', 'Grace files (*.agr)'])
def save_file(self) -> pathlib.Path | None:
outfile = self.selectedFiles()
if outfile:
return pathlib.Path(outfile[0])
return

View File

@ -193,6 +193,8 @@ class SelectionWidget(QtWidgets.QWidget):
@value.setter @value.setter
def value(self, val): def value(self, val):
if isinstance(val, dict):
val = list(val.values())[0]
key = [k for k, v in self.options.items() if v == val][0] key = [k for k, v in self.options.items() if v == val][0]
self.comboBox.setCurrentIndex(self.comboBox.findText(key)) self.comboBox.setCurrentIndex(self.comboBox.findText(key))

View File

@ -341,7 +341,7 @@ class PlotItem(PlotDataItem):
if opts['symbol'] is None: if opts['symbol'] is None:
item_dic['symbol'] = SymbolStyle.No item_dic['symbol'] = SymbolStyle.No
item_dic['symbolcolor'] = Colors.Black item_dic['symbolcolor'] = None
else: else:
item_dic['symbol'] = SymbolStyle.from_str(opts['symbol']) item_dic['symbol'] = SymbolStyle.from_str(opts['symbol'])
item_dic['symbolcolor'] = opts['symbolcolor'] item_dic['symbolcolor'] = opts['symbolcolor']
@ -354,9 +354,16 @@ class PlotItem(PlotDataItem):
item_dic['linewidth'] = pen.widthF() item_dic['linewidth'] = pen.widthF()
else: else:
item_dic['linestyle'] = LineStyle.No item_dic['linestyle'] = LineStyle.No
item_dic['linecolor'] = item_dic['symbolcolor'] item_dic['linecolor'] = None
item_dic['linewidth'] = 0.0 item_dic['linewidth'] = 0.0
if item_dic['linecolor'] is None and item_dic['symbolcolor'] is None:
item_dic['symbolcolor'] = Colors.Black.rgb()
elif item_dic['linecolor'] is None:
item_dic['linecolor'] = item_dic['symbolcolor']
elif item_dic['symbolcolor'] is None:
item_dic['symbolcolor'] = item_dic['linecolor']
return item_dic return item_dic

View File

@ -1,4 +1,5 @@
import pathlib import pathlib
import re
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Tuple
@ -249,11 +250,20 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
mode = save_dialog.exec() mode = save_dialog.exec()
if mode == QtWidgets.QDialog.Accepted: if mode == QtWidgets.QDialog.Accepted:
path = save_dialog.selectedFiles() savefile = save_dialog.save_file()
selected_filter = save_dialog.selectedNameFilter() selected_filter = save_dialog.selectedNameFilter()
if path: if savefile is not None:
self.management.save(path[0], selected_filter) use_underscore = save_dialog.checkBox.isChecked()
self.management.save(savefile, selected_filter, strip_spaces=use_underscore)
param_outfile = re.sub('[_\s-]?<label>[_\s-]?', '', savefile.stem)
if save_dialog.agr_cb.isChecked():
self.current_graph_widget.export(savefile.with_name(param_outfile + '.agr'))
if save_dialog.fit_cb.isChecked():
self.management.save_fit_parameter(savefile.with_name(param_outfile + '.dat'))
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
@QtCore.pyqtSlot(list) @QtCore.pyqtSlot(list)
@ -266,7 +276,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(name='on_actionExportGraphic_triggered') @QtCore.pyqtSlot(name='on_actionExportGraphic_triggered')
def export_graphic(self): def export_graphic(self):
self.current_graph_widget.export() self.current_graph_widget.export_dialog()
@QtCore.pyqtSlot(name='on_actionNew_window_triggered') @QtCore.pyqtSlot(name='on_actionNew_window_triggered')
def new_graph(self): def new_graph(self):
@ -304,7 +314,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def remove_graph(self, gid: str): def remove_graph(self, gid: str):
print(gid, self.current_graph_widget)
self.datawidget.remove_item(gid) self.datawidget.remove_item(gid)
w = None w = None
for w in self.area.subWindowList(): for w in self.area.subWindowList():
@ -655,7 +664,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def delete_data(self, sid): def delete_data(self, sid):
print('remove', sid)
if self.valuewidget.shown_set == sid: if self.valuewidget.shown_set == sid:
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
@ -765,7 +773,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
def _select_fitwidget(self, onoff: bool, block_window: bool): def _select_fitwidget(self, onoff: bool, block_window: bool):
if self.current_graph_widget is not None: if self.current_graph_widget is not None:
print('select', self.current_graph_widget.id) pass
if onoff: if onoff:
if self.management.active_sets: if self.management.active_sets:
self.fit_dialog.connected_figure = self.management.current_graph self.fit_dialog.connected_figure = self.management.current_graph

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import pathlib import pathlib
import re import re
import uuid import uuid
@ -260,7 +262,6 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def delete_sets(self, rm_sets: list = None): def delete_sets(self, rm_sets: list = None):
rm_graphs = [] rm_graphs = []
print(rm_sets)
if rm_sets is None: if rm_sets is None:
rm_sets = self.graphs[self.current_graph].sets + [self.current_graph] rm_sets = self.graphs[self.current_graph].sets + [self.current_graph]
@ -489,9 +490,6 @@ class UpperManagement(QtCore.QObject):
f_id = self.add(fit, color=color, src=k) f_id = self.add(fit, color=color, src=k)
if subplots:
print('subplots')
f_id_list.append(f_id) f_id_list.append(f_id)
data_k.set_fits(f_id) data_k.set_fits(f_id)
gid = data_k.graph gid = data_k.graph
@ -516,7 +514,7 @@ class UpperManagement(QtCore.QObject):
self.newData.emit(p_id_list, graph_id) self.newData.emit(p_id_list, graph_id)
def save_fit_parameter(self, fname: str, fit_sets: List[str] = None): def save_fit_parameter(self, fname: str | pathlib.Path, fit_sets: List[str] = None):
if fit_sets is None: if fit_sets is None:
fit_sets = [s for (s, _) in self.active_sets] fit_sets = [s for (s, _) in self.active_sets]
@ -1009,7 +1007,7 @@ class UpperManagement(QtCore.QObject):
def append(self, idx: str): def append(self, idx: str):
self.data[idx].add([0.0, 0.0, 0.0]) self.data[idx].add([0.0, 0.0, 0.0])
def save(self, outpath: str, extension: str, strip_spaces=False): def save(self, outpath: str | pathlib.Path, extension: str, strip_spaces=False):
path = pathlib.Path(outpath) path = pathlib.Path(outpath)
suffix = path.suffix suffix = path.suffix
@ -1030,7 +1028,7 @@ class UpperManagement(QtCore.QObject):
real_outnames = [] real_outnames = []
for set_id, set_name in self.active_sets: for set_id, set_name in self.active_sets:
full_name = path.stem full_name = path.stem
if '<label>' in outpath: if '<label>' in full_name:
full_name = full_name.replace('<label>', convert(set_name, old='tex', new='str')) full_name = full_name.replace('<label>', convert(set_name, old='tex', new='str'))
data_i = self.data[set_id] data_i = self.data[set_id]

View File

@ -87,7 +87,7 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
self.lineEdit_3.setText('{:.2f}'.format(x[right_b])) self.lineEdit_3.setText('{:.2f}'.format(x[right_b]))
self.lineEdit_3.blockSignals(False) self.lineEdit_3.blockSignals(False)
self.t1calculator.data(x, y) self.t1calculator.set_data(x, y)
self.determine_minimum(self.interpol_combobox.currentIndex()) self.determine_minimum(self.interpol_combobox.currentIndex())
self.name = name self.name = name
@ -159,7 +159,6 @@ 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:

View File

@ -112,8 +112,7 @@ 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 # 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))

View File

@ -450,7 +450,6 @@ class GraceGraph(GraceProperties):
self.idx = idx self.idx = idx
def set_limits(self, x=None, y=None): def set_limits(self, x=None, y=None):
print(x, y)
for i, line in enumerate(self): for i, line in enumerate(self):
m = self._RE_ENTRY.match(line) m = self._RE_ENTRY.match(line)
if m and m.group('key') == 'world': if m and m.group('key') == 'world':
@ -702,10 +701,3 @@ def _convert_to_str(value):
return ', '.join(map(str, value)) return ', '.join(map(str, value))
else: else:
return str(value) return str(value)
if __name__ == '__main__':
agr = GraceEditor('/autohome/dominik/nmreval/testdata/02_relax_2.agr')
import pprint
pprint.pprint(agr.graphs)
agr.graphs[0].set_property(title='"asdasdasd"')

View File

@ -4,9 +4,10 @@ Relaxation
Classes to calculate spin-lattice and spin-spin relaxation, as well as to evaluate T1 data and calculate correlation times Classes to calculate spin-lattice and spin-spin relaxation, as well as to evaluate T1 data and calculate correlation times
""" """
from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import Any, Optional, Tuple, Type, Union from typing import Any, Tuple, Type
from warnings import warn from warnings import warn
import numpy as np import numpy as np
@ -42,8 +43,8 @@ class Relaxation:
else: else:
return super().__repr__() return super().__repr__()
def set_coupling(self, coupling: Union[float, Type[Coupling]], def set_coupling(self, coupling: float | Type[Coupling],
parameter: Union[tuple, list] = None, keywords: dict = None): parameter: tuple | list = None, keywords: dict = None):
if parameter is not None: if parameter is not None:
self.coup_parameter = parameter self.coup_parameter = parameter
@ -61,7 +62,7 @@ class Relaxation:
else: else:
raise ValueError(f'`coupling` is not number or of type `Coupling`, found {coupling!r}') raise ValueError(f'`coupling` is not number or of type `Coupling`, found {coupling!r}')
def set_distribution(self, dist: Type[Distribution], parameter: Union[tuple, list] = None, keywords: dict = None): def set_distribution(self, dist: Type[Distribution], parameter: tuple | list = None, keywords: dict = None):
self.distribution = dist self.distribution = dist
if parameter is not None: if parameter is not None:
@ -71,7 +72,7 @@ class Relaxation:
self._dist_kw = keywords self._dist_kw = keywords
def t1(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t1(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
mode: str = 'bpp', **kwargs) -> Union[np.ndarray, float]: mode: str = 'bpp', **kwargs) -> np.ndarray | float:
r""" r"""
Convenience function Convenience function
@ -109,7 +110,7 @@ class Relaxation:
def t1_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, inverse: bool = True, def t1_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, inverse: bool = True,
prefactor: float = None, omega_coup: ArrayLike = None, prefactor: float = None, omega_coup: ArrayLike = None,
gamma_coup: str = None, gamma_obs: str = None) -> Union[np.ndarray, float]: gamma_coup: str = None, gamma_obs: str = None) -> np.ndarray | float:
r"""Calculate T1 under heteronuclear dipolar coupling. r"""Calculate T1 under heteronuclear dipolar coupling.
.. math:: .. math::
@ -162,7 +163,7 @@ class Relaxation:
return rate return rate
def t1_bpp(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t1_bpp(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]: inverse: bool = True, prefactor: float = None) -> np.ndarray | float:
r"""Calculate T1 under homonuclear dipolar coupling or quadrupolar coupling. r"""Calculate T1 under homonuclear dipolar coupling or quadrupolar coupling.
.. math:: .. math::
@ -197,7 +198,7 @@ class Relaxation:
return rate return rate
def t1_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t1_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]: inverse: bool = True, prefactor: float = None) -> np.ndarray | float:
r"""Calculate T1 under chemical shift anisotropy. r"""Calculate T1 under chemical shift anisotropy.
.. math:: .. math::
@ -234,7 +235,7 @@ class Relaxation:
return rate return rate
def t1q(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t1q(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]: inverse: bool = True, prefactor: float = None) -> np.ndarray | float:
r"""Calculate T1q for homonuclear dipolar coupling or quadrupolar coupling (I=1). r"""Calculate T1q for homonuclear dipolar coupling or quadrupolar coupling (I=1).
.. math:: .. math::
@ -305,7 +306,7 @@ class Relaxation:
return rate return rate
def t2(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t2(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
mode: str = 'bpp', **kwargs) -> Union[np.ndarray, float]: mode: str = 'bpp', **kwargs) -> np.ndarray | float:
r""" r"""
Convenience function Convenience function
@ -342,7 +343,7 @@ class Relaxation:
return self.t2_csa(omega, tau, *specdens_args, **kwargs) return self.t2_csa(omega, tau, *specdens_args, **kwargs)
def t2_bpp(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t2_bpp(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]: inverse: bool = True, prefactor: float = None) -> np.ndarray | float:
r"""Calculate T2 under homonuclear dipolar coupling or quadrupolar coupling. r"""Calculate T2 under homonuclear dipolar coupling or quadrupolar coupling.
.. math:: .. math::
@ -379,7 +380,7 @@ class Relaxation:
def t2_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t2_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None, omega_coup: ArrayLike = None, inverse: bool = True, prefactor: float = None, omega_coup: ArrayLike = None,
gamma_coup: str = None, gamma_obs: str = None) -> Union[np.ndarray, float]: gamma_coup: str = None, gamma_obs: str = None) -> np.ndarray | float:
r"""Calculate T2 under heteronuclear dipolar coupling. r"""Calculate T2 under heteronuclear dipolar coupling.
.. math:: .. math::
@ -435,7 +436,7 @@ class Relaxation:
return rate return rate
def t2_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, def t2_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any,
inverse: bool = True, prefactor: float = None) -> Union[np.ndarray, float]: inverse: bool = True, prefactor: float = None) -> np.ndarray | float:
r"""Calculate T1 under chemical shift anisotropy. r"""Calculate T1 under chemical shift anisotropy.
.. math:: .. math::
@ -471,7 +472,7 @@ class Relaxation:
class RelaxationEvaluation(Relaxation): class RelaxationEvaluation(Relaxation):
def __init__(self, distribution=None): def __init__(self, distribution: Type[Distribution] = None):
super().__init__(distribution=distribution) super().__init__(distribution=distribution)
self.t1min = (np.nan, np.nan) self.t1min = (np.nan, np.nan)
self._interpolate = None self._interpolate = None
@ -481,7 +482,15 @@ class RelaxationEvaluation(Relaxation):
self.x = None self.x = None
self.y = None self.y = None
def data(self, temp, t1): def set_data(self, temp: ArrayLike, t1: ArrayLike):
"""
Set data for evaluation.
Args:
temp (array-like): Temperature values
t1 (array-like): T1 values
"""
temp = np.asanyarray(temp) temp = np.asanyarray(temp)
t1 = np.asanyarray(t1) t1 = np.asanyarray(t1)
sortidx = temp.argsort() sortidx = temp.argsort()
@ -490,10 +499,11 @@ class RelaxationEvaluation(Relaxation):
self.calculate_t1_min() self.calculate_t1_min()
def get_increase(self, height: float = None, idx: int = 0, mode: str = None, omega: float = None, def get_increase(self, height: float = None, idx: int = 0, mode: str = None, omega: float = None,
dist_parameter: Union[tuple, list] = None, prefactor: Union[tuple, list, float] = None, dist_parameter: tuple | list = None, prefactor: tuple | list | float = None,
coupling_kwargs: dict = None): coupling_kwargs: dict = None):
""" """
Determine a single parameter from a T1 minimum Determine a single parameter from a T1 minimum.
It replaces the previously set value.
Args: Args:
height (float, optional): Height of T1 minimum height (float, optional): Height of T1 minimum
@ -501,14 +511,16 @@ class RelaxationEvaluation(Relaxation):
idx (int): Default is 0. idx (int): Default is 0.
omega (float, optional): Larmor frequency (in 1/s) omega (float, optional): Larmor frequency (in 1/s)
dist_parameter (tuple, optional): dist_parameter (tuple, optional):
prefactor (tuple, float, optional): prefactor (tuple, float, optional): Prefactor for
coupling_kwargs (dict, optional): coupling_kwargs (dict, optional): Keyword arguments for coupling, replacing old values
Returns: Returns:
A tuple of the value of varied parameter, or nan if nothing was varied
and the minimum height calculated for given parameters.
""" """
stretching = mini = np.nan stretching = minimon = np.nan
if height is None: if height is None:
height = self.t1min[1] height = self.t1min[1]
@ -605,6 +617,7 @@ class RelaxationEvaluation(Relaxation):
else: else:
stretching = t1_no_coup / height stretching = t1_no_coup / height
prefactor = stretching
else: else:
raise ValueError('Use `distribution` or `coupling` to set parameter') raise ValueError('Use `distribution` or `coupling` to set parameter')
@ -617,13 +630,30 @@ class RelaxationEvaluation(Relaxation):
self.prefactor = self.coupling.relax(*self.coup_parameter, **self.coup_kw) self.prefactor = self.coupling.relax(*self.coup_parameter, **self.coup_kw)
else: else:
self.prefactor = prefactor self.prefactor = prefactor
mini = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *self.dist_parameter, minimon = np.min(self.t1(omega, np.logspace(*tau_lims, num=1001), *self.dist_parameter,
prefactor=self.prefactor)) prefactor=self.prefactor))
return stretching, mini return stretching, minimon
def calculate_t1_min(self, interpolate: int = None, trange: Tuple[float, float] = None, use_log: bool = False) -> \ def calculate_t1_min(self, interpolate: int = None, trange: Tuple[float, float] = None, use_log: bool = False) -> \
Tuple[Tuple[float, float], Optional[Tuple[np.ndarray, np.ndarray]]]: Tuple[Tuple[float, float], Tuple[np.ndarray, np.ndarray] | None]:
"""
Determine a minimum position for given T1 data
Args:
interpolate (int, optional):
* 0 or None: No interpolation, minimum is data minimum
* 1: Interpolation with a parabola
* 2: Interpolation with a cubic spline
* 3: Interpolation with Akima spline (less wiggly than cubic)
trange (tuple): Range (left border, range border) of interpolation in K.
Interpolation without a given range uses two points left and right of minimum value.
use_log (bool): Default is `True`.
Returns:
The minimum position (`T_min`, `T1_min`)
"""
min_index = np.argmin(self.y) min_index = np.argmin(self.y)
t1_min = (self.x[min_index], self.y[min_index]) t1_min = (self.x[min_index], self.y[min_index])
parabola = None parabola = None
@ -674,9 +704,25 @@ class RelaxationEvaluation(Relaxation):
return t1_min, parabola return t1_min, parabola
def correlation_from_t1(self, mode: str = 'raw', interpolate: bool = False, omega: float = None, 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, dist_parameter: list | tuple = None, prefactor: float = None,
coupling_param: list = None, coupling_kwargs: dict = None) -> Tuple[np.ndarray, dict]: coupling_param: list = None, coupling_kwargs: dict = None) -> Tuple[np.ndarray, dict]:
"""
Calculate correlation times from set T1 data.
Optional arguments overwrite previousliy set parameter.
Args:
mode (str, {`raw`, `mean`, `logmean`, `max`}): Type of correlation time. Default is `raw`.
interpolate (bool): If ``True`` and T1 minimum was determined by nterpolation,
T1 on interpolated line instead of measured value is used. Default is `False`.
omega (float, optional): Larmor frequency (in 1/s)
dist_parameter (list, optional): List of parameter of spectral density
prefactor (float, optional): Prefactor of T1 calculation, will
coupling_param (list, optional): Parameter for coupling constant, ignored if `prefactor`is given.
coupling_kwargs (dict, optional): Keyword arguments for coupling constant, ignored if `prefactor`is given.
Returns:
"""
if self.x is None: if self.x is None:
raise ValueError('Temperature is not set') raise ValueError('Temperature is not set')
@ -694,6 +740,7 @@ class RelaxationEvaluation(Relaxation):
if coupling_param is None: if coupling_param is None:
prefactor = self.prefactor prefactor = self.prefactor
coupling_param = self.coup_parameter
else: else:
prefactor = self.coupling.relax(*coupling_param, **coupling_kwargs) prefactor = self.coupling.relax(*coupling_param, **coupling_kwargs)
@ -711,7 +758,7 @@ class RelaxationEvaluation(Relaxation):
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=1001)
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):
@ -722,7 +769,7 @@ class RelaxationEvaluation(Relaxation):
t1_i = self._interpolate(slow_temp[-i]) t1_i = self._interpolate(slow_temp[-i])
if np.min(current_t1) > t1_i: if np.min(current_t1) > t1_i:
warn('Correlation time could not be calculated') warn(f'Value {t1_i} below set minimum, wonky correlation time')
correlation_times[offset - i] = taus[0] correlation_times[offset - i] = taus[0]
continue continue
@ -738,7 +785,7 @@ class RelaxationEvaluation(Relaxation):
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=1001)
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)):
@ -750,7 +797,7 @@ class RelaxationEvaluation(Relaxation):
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(f'Correlation time for {correlation_times[offset + i]} could not be calculated') warn(f'Value {t1_i} below set minimum, wonky correlation time')
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]
@ -763,11 +810,10 @@ class RelaxationEvaluation(Relaxation):
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),
'frequency': omega / 2 / np.pi} 'frequency': omega / 2 / np.pi,
'prefactor': self.prefactor}
if self.coupling is not None: if self.coupling is not None:
opts['coupling'] = (self.coupling.name, self.prefactor, coupling_param, coupling_kwargs) opts['coupling'] = (self.coupling.name, 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