complex fit and stuff

This commit is contained in:
dominik 2022-03-30 17:27:02 +02:00
parent 4ad82cf5b2
commit e19d32b736
8 changed files with 155 additions and 92 deletions

View File

@ -65,7 +65,7 @@ class MultiModel:
self.bounds = []
self._kwargs_right = {}
self._kwargs_left = {}
self._fun_kwargs = {}
self.fun_kwargs = {}
# mapping kwargs to kwargs of underlying functions
self._ext_int_kw = {}
@ -92,7 +92,7 @@ class MultiModel:
if isinstance(func, MultiModel):
strcnt = ''
kw_dict.update(func.fun_kwargs)
self._fun_kwargs.update({k: v for k, v in kw_dict.items()})
self.fun_kwargs.update({k: v for k, v in kw_dict.items()})
self._ext_int_kw.update({k: k for k in kw_dict.keys()})
else:
@ -102,7 +102,7 @@ class MultiModel:
for k, v in temp_dic.items():
key_ = f'{k}_{idx}'
kw_dict[key_] = v
self._fun_kwargs[key_] = v
self.fun_kwargs[key_] = v
self._ext_int_kw[key_] = k
strcnt = f'({idx})'
@ -116,13 +116,27 @@ class MultiModel:
self.bounds.extend([(None, None)]*len(func.params))
def _left_arguments(self, *args, **kwargs):
kw_left = {k_int: kwargs[k_ext] for k_ext, k_int in self._ext_int_kw.items() if k_ext in self._kwargs_left}
kw_left = {}
for k_ext, k_int in self._ext_int_kw.items():
if k_ext in self._kwargs_left:
if not k_ext.startswith('complex_mode'):
kw_left[k_int] = kwargs[k_ext]
else:
kw_left['complex_mode'] = kwargs['complex_mode']
pl = args[:self._param_left]
return pl, kw_left
def _right_arguments(self, *args, **kwargs):
kw_right = {k_int: kwargs[k_ext] for k_ext, k_int in self._ext_int_kw.items() if k_ext in self._kwargs_right}
kw_right = {}
for k_ext, k_int in self._ext_int_kw.items():
if k_ext in self._kwargs_right:
if not k_ext.startswith('complex_mode'):
kw_right[k_int] = kwargs[k_ext]
else:
kw_right['complex_mode'] = kwargs['complex_mode']
pr = args[self._param_left:self._param_len]
return pr, kw_right
@ -142,10 +156,6 @@ class MultiModel:
def right_func(self, x, *args, **kwargs):
return self._right.func(x, *args, **kwargs)
@property
def fun_kwargs(self):
return self._fun_kwargs
def subs(self, x, *args, **kwargs):
""" Iterator over all sub-functions (depth-first and left-to-right) """
pl, kw_left = self._left_arguments(*args, **kwargs)
@ -159,3 +169,18 @@ class MultiModel:
yield from self._right.subs(x, *pr, **kw_right)
else:
yield self._right.func(x, *pr, **kw_right)
def sub_name(self):
if isinstance(self._left, MultiModel):
yield from self._left.sub_name()
elif hasattr(self._left, 'name'):
yield self._left.name
else:
yield self.name + '(lhs)'
if isinstance(self._right, MultiModel):
yield from self._right.sub_name()
elif hasattr(self._right, 'name'):
yield self._right.name
else:
yield self.name + '(rhs)'

View File

@ -61,7 +61,7 @@ class FitRoutine(object):
self.result.pop(idx)
except ValueError:
raise IndexError('Data {} not found'.format(data))
raise IndexError(f'Data {data} not found')
def set_model(self, func, *args, idx=None, **kwargs):
if isinstance(func, Model):

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import inspect
from typing import Sized
@ -61,11 +63,11 @@ class Model(object):
self._int_func = model.func
if hasattr(model, 'subs'):
self._int_iter = model.subs
self._iter_name = model.sub_name
self.is_multi = True
else:
self._int_iter = model.func
try:
self.lb, self.ub = list(zip(*model.bounds))
except AttributeError:
@ -78,16 +80,6 @@ class Model(object):
self.fun_kwargs = {k: v.default for k, v in inspect.signature(model.func).parameters.items()
if v.default is not inspect.Parameter.empty}
def set_complex(self, state):
if state not in [None, 'complex', 'real', 'imag']:
raise ValueError('"complex" argument is not None, "complex", "real", "imag"')
self.is_complex = state
if state in ['real', 'imag']:
self._complex_part = state
else:
self._complex_part = False
def set_global_parameter(self, idx, p, var=None, lb=None, ub=None, default_bounds=False):
if idx is None:
self.parameter = Parameters()
@ -127,26 +119,19 @@ class Model(object):
f = self._int_func(x, *p, *self.fun_args, **kwargs)
if self._complex_part:
if self._complex_part == 'real':
return f.real
else:
return f.imag
return f
def sub(self, p, x, **kwargs):
if not kwargs:
kwargs = self.fun_kwargs
if not self.is_multi:
return [self.func(p, x, **kwargs)]
else:
if not kwargs:
kwargs = self.fun_kwargs
if self._complex_part:
if self._complex_part == 'real':
return [f.real for f in self._int_iter(x, *p, *self.fun_args, **kwargs)]
else:
return [f.imag for f in self._int_iter(x, *p, *self.fun_args, **kwargs)]
return list(self._int_iter(x, *p, *self.fun_args, **kwargs))
def sub_name(self):
if not self.is_multi:
return [self.name]
else:
return list(self._iter_name())

View File

@ -62,11 +62,11 @@ class FitResultCreator:
part_functions = []
if model.is_multi:
for sub_y in model.sub(p_final, _x, **fun_kwargs):
for sub_name, sub_y in zip(model.sub_name(), model.sub(p_final, _x, **fun_kwargs)):
if np.iscomplexobj(sub_y):
part_functions.append(Signal(_x, sub_y))
part_functions.append(Signal(_x, sub_y, name=sub_name))
else:
part_functions.append(Points(_x, sub_y))
part_functions.append(Points(_x, sub_y, name=sub_name))
_y = model.func(p_final, _x, **fun_kwargs)
resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig
@ -98,7 +98,7 @@ class FitResultCreator:
FitResult(_x, _y, x_orig, y_orig, parameters, fun_kwargs, resid,
nobs, nvar, model.name, stats,
idx=idx, corr=correlation, pcorr=partial_correlation,
islog=islog, iscomplex=model.is_complex),
islog=islog),
part_functions,
)
@ -139,7 +139,7 @@ class FitResultCreator:
class FitResult(Points):
def __init__(self, x, y, x_data, y_data, params, fun_kwargs, resid, nobs, nvar, name, stats,
idx=None, corr=None, pcorr=None, islog=False, iscomplex=None,
idx=None, corr=None, pcorr=None, islog=False,
**kwargs):
self.parameter, name = self._prepare_names(params, name)
@ -155,7 +155,7 @@ class FitResult(Points):
self.correlation = corr
self.partial_correlation = pcorr
self.islog = islog
self.iscomplex = iscomplex
self.iscomplex = np.iscomplexobj(self.y)
self.x_data = x_data
self.y_data = y_data
self._model_name = name

View File

@ -6,6 +6,7 @@ from operator import add
from string import ascii_letters
from typing import Dict, List, Tuple
import numpy as np
from pyqtgraph import mkPen
from .fit_forms import FitTableWidget
@ -284,10 +285,11 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
parameter['func'] = func
parameter['order'] = order
parameter['len'] = param_len
if self._complex[k] is None:
parameter['complex'] = self._complex[k]
else:
parameter['complex'] = ['complex', 'real', 'imag'][self._complex[k]]
parameter['complex'] = self._complex[k]
if self._complex[k] is not None:
for p_k, p_v in parameter['parameter'].items():
p_v[1].update({'complex_mode': self._complex[k]})
parameter['parameter'][p_k] = p_v[0], p_v[1]
func_dict[k] = parameter
@ -409,23 +411,29 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
color = model['color']
for p, kwargs in parameters.values():
y = f.func(x, *p, **kwargs)
if is_complex is None:
if is_complex is not None:
y = f.func(x, *p, complex_mode=is_complex, **kwargs)
if np.iscomplexobj(y):
self.preview_lines.append(PlotItem(x=x, y=y.real, pen=mkPen(width=3)))
self.preview_lines.append(PlotItem(x=x, y=y.imag, pen=mkPen(width=3)))
else:
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
else:
y = f.func(x, *p, **kwargs)
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
if is_complex in [0, 1]:
self.preview_lines.append(PlotItem(x=x, y=y.real, pen=mkPen(width=3)))
if is_complex in [0, 2]:
self.preview_lines.append(PlotItem(x=x, y=y.imag, pen=mkPen(width=3)))
if isinstance(f, MultiModel):
for i, s in enumerate(f.subs(x, *p, **kwargs)):
sub_kwargs = kwargs.copy()
if is_complex is not None:
sub_kwargs.update({'complex_mode': is_complex})
for i, s in enumerate(f.subs(x, *p, **sub_kwargs)):
pen_i = mkPen(QtGui.QColor.fromRgbF(*color[i]))
if is_complex is None:
self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i))
if is_complex in [0, 1]:
if np.iscomplexobj(s):
self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i))
if is_complex in [0, 2]:
self.preview_lines.append(PlotItem(x=x, y=s.imag, pen=pen_i))
else:
self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i))
return self.preview_lines

View File

@ -41,17 +41,31 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self._opts = [(False, False) for _ in range(len(self._results))]
self.residplot = self.graphicsView.addPlot(row=0, col=0)
self.resid_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color='r'), pen=None)
self.resid_graph = PlotItem(x=[], y=[],
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(174, 199, 232)),
pen=None)
self.resid_graph_imag = PlotItem(x=[], y=[],
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
pen=None)
self.residplot.addItem(self.resid_graph)
self.residplot.addItem(self.resid_graph_imag)
self.residplot.setLabel('left', 'Residual')
self.fitplot = self.graphicsView.addPlot(row=1, col=0)
self.data_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color='r'), pen=None)
self.data_graph = PlotItem(x=[], y=[],
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(174, 199, 232)),
pen=None)
self.data_graph_imag = PlotItem(x=[], y=[],
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
pen=None)
self.fitplot.addItem(self.data_graph)
self.fitplot.addItem(self.data_graph_imag)
self.fitplot.setLabel('left', 'Function')
self.fit_graph = PlotItem(x=[], y=[])
self.fit_graph_imag = PlotItem(x=[], y=[])
self.fitplot.addItem(self.fit_graph)
self.fitplot.addItem(self.fit_graph_imag)
self.cmap = RdBuCMap(vmin=-1, vmax=1)
@ -138,15 +152,20 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
res = self._results[idx]
iscomplex = res.iscomplex
self.resid_graph.setData(x=res.x_data, y=res.residual)
if iscomplex == 'complex':
self.data_graph.setData(x=r_[res.x_data, res.x_data],
y=r_[res.y_data.real, res.y_data.imag])
self.fit_graph.setData(x=r_[res.x, res.x],
y=r_[res.y.real, res.y.imag])
if iscomplex:
self.data_graph.setData(x=res.x_data, y=res.y_data.real)
self.data_graph_imag.setData(x=res.x_data, y=res.y_data.imag)
self.fit_graph.setData(x=res.x, y=res.y.real)
self.fit_graph_imag.setData(x=res.x, y=res.y.imag)
self.resid_graph.setData(x=res.x_data, y=res.residual.real)
self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag)
else:
self.resid_graph.setData(x=res.x_data, y=res.residual)
self.resid_graph_imag.setData(x=[], y=[])
self.data_graph.setData(x=res.x_data, y=res.y_data)
self.data_graph_imag.setData(x=[], y=[])
self.fit_graph.setData(x=res.x, y=res.y)
self.fit_graph_imag.setData(x=[], y=[])
self.fitplot.setLogMode(x=res.islog)
self.residplot.setLogMode(x=res.islog)
@ -243,7 +262,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.redoFit.emit(self._results)
elif button_type == self.buttonBox.Ok:
graph = None
graph = '-1'
if self.parameter_checkbox.isChecked():
if self.graph_checkBox.checkState() == QtCore.Qt.Checked:
graph = ''

View File

@ -390,16 +390,15 @@ class UpperManagement(QtCore.QObject):
models[model_id] = m
m_complex = model_p['complex']
m.set_complex(m_complex)
for set_id, set_params in model_p['parameter'].items():
data_i = self.data[set_id]
if we == 'Deltay':
we = data_i.y_err**2
if m_complex is None or m_complex == 'real':
if m_complex is None or m_complex == 1:
_y = data_i.y.real
elif m_complex == 'imag' and np.iscomplexobj(self.data[set_id].y):
elif m_complex == 2 and np.iscomplexobj(self.data[set_id].y):
_y = data_i.y.imag
else:
_y = data_i.y
@ -518,14 +517,15 @@ class UpperManagement(QtCore.QObject):
if k in parts and show_fit:
for subfunc, col in zip(parts[k], TUColorsC):
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
subfunc.value = data_k.value
subfunc.group = data_k.group
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
f_id_list.append(sub_f_id)
self.delete_sets(tobedeleted)
if accepted and param_graph is not None:
if accepted and (param_graph != '-1'):
self.make_fit_parameter(accepted, graph_id=param_graph)
self.newData.emit(f_id_list, gid)
@ -698,7 +698,7 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot()
def update_color(self):
UpperManagement._colors = cycle(Colors)
UpperManagement._colors = cycle(TUColors)
for i in self.active:
self.data[i].color = next(UpperManagement._colors)
@ -1103,4 +1103,3 @@ class FitWorker(QtCore.QObject):
res = [e.args]
success = False
self.finished.emit(res, success)

View File

@ -1,5 +1,3 @@
from typing import List, Optional, Tuple
import numpy as np
from ..distributions import Debye, ColeCole, ColeDavidson, KWW, HavriliakNegami
@ -16,12 +14,18 @@ class _AbstractBDS:
iscomplex = True
@classmethod
def func(cls, x, *args, **kwargs):
def func(cls, x, *args, complex_mode: int = 0, **kwargs):
# args[0] : Delta epsilon
# args[1:] : every other parameter
chi = args[0] * cls.susceptibility(2*np.pi*x, *args[1:])
return chi
chi = args[0] * cls.susceptibility(2*np.pi*x, *args[1:], **kwargs)
if complex_mode == 0:
return chi
elif complex_mode == 1:
return chi.real
elif complex_mode == 2:
return chi.imag
else:
raise ValueError(f'{complex_mode!r} is not 0, 1, 2')
class DebyeBDS(_AbstractBDS):
@ -70,9 +74,16 @@ class EpsInfty:
iscomplex = True
@staticmethod
def func(x, eps):
ret_val = np.zeros(x.shape, dtype=complex)
ret_val += eps
def func(x, eps, complex_mode: int = 0):
if complex_mode == 0:
ret_val = np.zeros(x.shape, dtype=complex)
ret_val += eps
elif complex_mode == 1:
ret_val = eps * np.ones(x.shape)
elif complex_mode == 2:
ret_val = np.zeros(x.shape)
else:
raise ValueError(f'{complex_mode!r} is not 0, 1, 2')
return ret_val
@ -86,8 +97,17 @@ class PowerLawBDS:
iscomplex = True
@staticmethod
def func(x, a, n):
return a / (1j*x)**n
def func(x, a, n, complex_mode: int = 0):
if complex_mode == 0:
ret_val = np.exp(1j*n*np.pi/2) * a / x**n
elif complex_mode == 1:
ret_val = np.cos(n*np.pi/2) * a / x**n
elif complex_mode == 2:
ret_val = np.sin(n*np.pi/2) * a / x**n
else:
raise ValueError(f'{complex_mode!r} is not 0, 1, 2')
return ret_val
class DCCondBDS:
@ -99,14 +119,21 @@ class DCCondBDS:
iscomplex = True
@staticmethod
def func(x, sigma):
ret_val = np.zeros(x.shape, dtype=complex)
ret_val += 1j * sigma / x / epsilon0
def func(x, sigma, complex_mode: int = 0):
if complex_mode == 0:
ret_val = np.zeros(x.shape, dtype=complex)
ret_val += 1j * sigma / x / epsilon0
elif complex_mode == 1:
ret_val = np.zeros(x.shape)
elif complex_mode == 2:
ret_val = sigma / x / epsilon0
else:
raise ValueError(f'{complex_mode!r} is not 0, 1, 2')
return ret_val
class HavriliakNegamiDerivative:
class DerivativeHavriliakNegami:
name = 'Derivative HN'
type = 'Dielectric Spectroscopy'
params = [r'\Delta\epsilon', r'\tau', r'\alpha', r'\gamma']
@ -123,7 +150,7 @@ class HavriliakNegamiDerivative:
return eps * np.pi * numer / denom / 2.
class ColeColeDerivative:
class DerivativeColeCole:
name = 'Derivative CC'
type = 'Dielectric Spectroscopy'
params = [r'\Delta\epsilon', r'\tau', r'\alpha']
@ -140,7 +167,7 @@ class ColeColeDerivative:
return eps * np.pi * numer / denom / 2.
class ColeDavidsonDerivative:
class DerivativeColeDavidson:
name = 'Derivative CD'
type = 'Dielectric Spectroscopy'
params = [r'\Delta\epsilon', r'\tau', r'\gamma']
@ -149,7 +176,7 @@ class ColeDavidsonDerivative:
@staticmethod
def func(x, eps, tau, g):
omtau = 2*np.pi*x * tau
numer = g * omtau * np.sin((1+g)*np.sin(omtau))
numer = g * omtau * np.sin((1+g)*np.arctan(omtau))
denom = (1 + omtau**2)**((1+g)/2.)
return eps * np.pi * numer / denom / 2.