246 lines
8.8 KiB
Python
246 lines
8.8 KiB
Python
import inspect
|
|
import re
|
|
from collections import namedtuple
|
|
|
|
import numpy as np
|
|
|
|
from nmreval import models
|
|
from nmreval.configs import config_paths
|
|
from nmreval.lib.importer import find_models, import_
|
|
from nmreval.utils import constants as constants
|
|
from nmreval.utils.text import convert
|
|
|
|
from ..Qt import QtWidgets, QtCore
|
|
from .._py.namespace_widget import Ui_Form
|
|
|
|
|
|
class Namespace:
|
|
def __init__(self, fitfuncs=False, const=False, basic=False):
|
|
self.namespace = {}
|
|
self.groupings = {}
|
|
self.top_levels = {}
|
|
|
|
if basic:
|
|
self.add_namespace(
|
|
{'x': (None, 'x values'),
|
|
'y': (None, 'x values'),
|
|
'y_err': (None, 'y error values'),
|
|
'fit': (None, 'dictionary of fit parameter', 'fit["PIKA"]'),
|
|
'np': (np, 'numpy module'),
|
|
},
|
|
parents=('Basic', 'General'),
|
|
)
|
|
|
|
self.add_namespace(
|
|
{'sin': (np.sin, 'Sine', 'sin(PIKA)'),
|
|
'cos': (np.cos, 'Cosine', 'cos(PIKA)'),
|
|
'tan': (np.tan, 'Tangens', 'tan(PIKA)'),
|
|
'ln': (np.log, 'Natural Logarithm', 'ln(PIKA)'),
|
|
'log': (np.log10, 'Logarithm (base 10)', 'log(PIKA)'),
|
|
'exp': (np.exp, 'Exponential', 'exp(PIKA)'),
|
|
'sqrt': (np.sqrt, 'Root', 'sqrt(PIKA)'),
|
|
'lin_range': (np.linspace, 'N evenly spaced over interval [start, stop]', 'lin_range(start, stop, N)'),
|
|
'log_range': (np.geomspace, 'N evenly spaced (log-scale) over interval [start, stop]', 'lin_range(start, stop, N)'),
|
|
},
|
|
parents=('Basic', 'Functions'))
|
|
|
|
self.add_namespace(
|
|
{'max': (np.max, 'Maximum value', 'max(PIKA)'),
|
|
'min': (np.min, 'Minimum value', 'min(PIKA)'),
|
|
'argmax': (np.argmax, 'Index of maximum value', 'argmax(PIKA)'),
|
|
'argmin': (np.argmax, 'Index of minimum value', 'argmin(PIKA)'),
|
|
},
|
|
parents=('Basic', 'Values')),
|
|
|
|
if const:
|
|
self.add_namespace(
|
|
{'e': (constants.e, 'e / As'),
|
|
'eps0': (constants.epsilon0, 'epsilon0 / As/Vm'),
|
|
'Eu': (constants.Eu,), 'h': (constants.h, 'h / eVs'),
|
|
'hbar': (constants.hbar, 'hbar / eVs'), 'kB': (constants.kB, 'kB / eV/K'),
|
|
'mu0': (constants.mu0, 'mu0 / Vs/Am'), 'NA': (constants.NA, 'NA / 1/mol'),
|
|
'pi': (constants.pi,), 'R': (constants.R, 'R / eV'),
|
|
},
|
|
parents=('Constants', 'Maybe useful'),
|
|
)
|
|
|
|
self.add_namespace(
|
|
{f'gamma["{k}"]': (v, k, f'gamma["{k}"]') for k, v in constants.gamma.items()},
|
|
parents=('Constants', 'Magnetogyric ratios (in 1/(sT))')
|
|
)
|
|
|
|
if fitfuncs:
|
|
self.make_dict_from_fitmodule(models)
|
|
try:
|
|
usermodels = import_(config_paths() / 'usermodels.py')
|
|
self.make_dict_from_fitmodule(usermodels, parent='User-defined')
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
def __str__(self):
|
|
ret = '\n'.join([f'{k}: {v}' for k, v in self.groupings.items()])
|
|
return ret
|
|
|
|
def make_dict_from_fitmodule(self, module, parent='Fit function'):
|
|
fitfunc_dict = {}
|
|
for funcs in find_models(module):
|
|
name = funcs.name
|
|
if funcs.type not in fitfunc_dict:
|
|
fitfunc_dict[funcs.type] = {}
|
|
|
|
func_list = fitfunc_dict[funcs.type]
|
|
|
|
func_args = 'x, ' + ', '.join(convert(p, old='latex', new='str', brackets=False) for p in funcs.params)
|
|
|
|
# keywords arguments need names in function
|
|
sig = inspect.signature(funcs.func)
|
|
for p in sig.parameters.values():
|
|
if p.default != p.empty:
|
|
func_args += ', ' + str(p)
|
|
|
|
func_list[f"{funcs.__name__}"] = (funcs.func, name, f"{funcs.__name__}({func_args})")
|
|
|
|
for k, v in fitfunc_dict.items():
|
|
self.add_namespace(v, parents=(parent, k))
|
|
|
|
def add_namespace(self, namespace, parents):
|
|
if parents[0] in self.top_levels:
|
|
if parents[1] not in self.top_levels[parents[0]]:
|
|
self.top_levels[parents[0]].append(parents[1])
|
|
else:
|
|
self.top_levels[parents[0]] = [parents[1]]
|
|
if (parents[0], parents[1]) not in self.groupings:
|
|
self.groupings[(parents[0], parents[1])] = list(namespace.keys())
|
|
else:
|
|
self.groupings[(parents[0], parents[1])].extend(list(namespace.keys()))
|
|
|
|
self.namespace.update(namespace)
|
|
|
|
def flatten(self):
|
|
ret_dic = {}
|
|
graph = namedtuple('graphs', ['s'])
|
|
sets = namedtuple('sets', ['x', 'y', 'y_err', 'value', 'fit'], defaults=(None,))
|
|
|
|
gamma = {}
|
|
|
|
gs = re.compile(r'g\[(\d+)].s\[(\d+)].(x|y(?:_err)*|value|fit)')
|
|
gamma_re = re.compile(r'gamma\["(\w+)"\]')
|
|
|
|
for k, v in self.namespace.items():
|
|
m = gamma_re.match(k)
|
|
if m:
|
|
gamma[m.group(1)] = v[0]
|
|
|
|
continue
|
|
|
|
m = gs.match(k)
|
|
if m:
|
|
if 'g' not in ret_dic:
|
|
ret_dic['g'] = {}
|
|
|
|
g_num, s_num, pos = m.groups()
|
|
if int(g_num) not in ret_dic['g']:
|
|
ret_dic['g'][int(g_num)] = graph({})
|
|
|
|
gg = ret_dic['g'][int(g_num)]
|
|
if int(s_num) not in gg.s:
|
|
gg.s[int(s_num)] = sets('', '', '', '', {})
|
|
|
|
ss = gg.s[int(s_num)]
|
|
if pos == 'fit':
|
|
if ss.fit is None:
|
|
gg.s[int(s_num)] = ss._replace(**{pos: {}})
|
|
ss = gg.s[int(s_num)]
|
|
ss.fit[k[m.end()+2:-2]] = v[0]
|
|
|
|
else:
|
|
ret_dic['g'][int(g_num)].s[int(s_num)] = ss._replace(**{pos: v[0]})
|
|
|
|
continue
|
|
|
|
ret_dic[k] = v[0]
|
|
|
|
ret_dic['gamma'] = gamma
|
|
|
|
return ret_dic
|
|
|
|
|
|
class QNamespaceWidget(QtWidgets.QWidget, Ui_Form):
|
|
selected = QtCore.pyqtSignal(str)
|
|
sendKey = QtCore.pyqtSignal(str)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent=parent)
|
|
|
|
self.setupUi(self)
|
|
self.namespace = None
|
|
|
|
def make_namespace(self):
|
|
self.set_namespace(Namespace(fitfuncs=True, const=True, basic=True))
|
|
|
|
@QtCore.pyqtSlot(int, name='on_groups_comboBox_currentIndexChanged')
|
|
def show_subgroup(self, idx: int):
|
|
name = self.groups_comboBox.itemText(idx)
|
|
|
|
self.subgroups_comboBox.blockSignals(True)
|
|
self.subgroups_comboBox.clear()
|
|
for item in self.namespace.top_levels[name]:
|
|
self.subgroups_comboBox.addItem(item)
|
|
self.subgroups_comboBox.blockSignals(False)
|
|
|
|
self.show_namespace(0)
|
|
|
|
@QtCore.pyqtSlot(int, name='on_subgroups_comboBox_currentIndexChanged')
|
|
def show_namespace(self, idx: int):
|
|
subspace = self.namespace.groupings[(self.groups_comboBox.currentText(), self.subgroups_comboBox.itemText(idx))]
|
|
|
|
self.namespace_table.clear()
|
|
self.namespace_table.setRowCount(0)
|
|
|
|
for entry in subspace:
|
|
key_item = QtWidgets.QTableWidgetItem(entry)
|
|
key_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
|
|
|
vals = self.namespace.namespace[entry]
|
|
|
|
if len(vals) < 3:
|
|
alias = entry
|
|
else:
|
|
alias = vals[2]
|
|
|
|
if len(vals) < 2:
|
|
display = entry
|
|
else:
|
|
display = vals[1]
|
|
|
|
value_item = QtWidgets.QTableWidgetItem(display)
|
|
value_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
|
|
|
key_item.setData(QtCore.Qt.UserRole, alias)
|
|
key_item.setData(QtCore.Qt.UserRole+1, entry)
|
|
value_item.setData(QtCore.Qt.UserRole, alias)
|
|
value_item.setData(QtCore.Qt.UserRole+1, entry)
|
|
|
|
row = self.namespace_table.rowCount()
|
|
self.namespace_table.setRowCount(row+1)
|
|
self.namespace_table.setItem(row, 0, key_item)
|
|
self.namespace_table.setItem(row, 1, value_item)
|
|
|
|
self.namespace_table.resizeColumnsToContents()
|
|
|
|
def set_namespace(self, namespace):
|
|
self.namespace = namespace
|
|
|
|
self.groups_comboBox.blockSignals(True)
|
|
self.groups_comboBox.clear()
|
|
for k in self.namespace.top_levels.keys():
|
|
self.groups_comboBox.addItem(k)
|
|
self.groups_comboBox.blockSignals(False)
|
|
|
|
self.show_subgroup(0)
|
|
|
|
@QtCore.pyqtSlot(QtWidgets.QTableWidgetItem, name='on_namespace_table_itemDoubleClicked')
|
|
def item_selected(self, item: QtWidgets.QTableWidgetItem):
|
|
self.selected.emit(item.data(QtCore.Qt.UserRole))
|
|
self.sendKey.emit(item.data(QtCore.Qt.UserRole+1))
|