Source code for RepTate.theories.TheoryBasic

# RepTate: Rheology of Entangled Polymers: Toolkit for the Analysis of Theory and Experiments
# --------------------------------------------------------------------------------------------------------
#
# Authors:
#     Jorge Ramirez, jorge.ramirez@upm.es
#     Victor Boudara, victor.boudara@gmail.com
#
# Useful links:
#     http://blogs.upm.es/compsoftmatter/software/reptate/
#     https://github.com/jorge-ramirez-upm/RepTate
#     http://reptate.readthedocs.io
#
# --------------------------------------------------------------------------------------------------------
#
# Copyright (2017-2023): Jorge Ramirez, Victor Boudara, Universidad Politécnica de Madrid, University of Leeds
#
# This file is part of RepTate.
#
# RepTate is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RepTate is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with RepTate.  If not, see <http://www.gnu.org/licenses/>.
#
# --------------------------------------------------------------------------------------------------------
"""Module TheoryBasic

Module that defines the basic theories that should be available for all Applications.

"""
from numpy import *
import numpy as np
import re
from RepTate.gui.QTheory import QTheory
from RepTate.core.Parameter import Parameter, ParameterType, OptType
from PySide6.QtWidgets import QToolBar, QSpinBox, QComboBox
from PySide6.QtCore import QSize

"""
             _                             _       _ 
 _ __   ___ | |_   _ _ __   ___  _ __ ___ (_) __ _| |
| '_ \ / _ \| | | | | '_ \ / _ \| '_ ` _ \| |/ _` | |
| |_) | (_) | | |_| | | | | (_) | | | | | | | (_| | |
| .__/ \___/|_|\__, |_| |_|\___/|_| |_| |_|_|\__,_|_|
|_|            |___/                                 
"""

[docs] class TheoryPolynomial(QTheory): """Fit a polynomial of degree :math:`n` to the data * **Function** .. math:: y(x) = \\sum_{i=0}^n A_i x^i * **Parameters** - :math:`n`: degree of the polynomial function. - :math:`A_i`: polynomial coefficeints. """ thname = "Polynomial" description = "Fit a polynomial of degree n" html_help_file = 'http://reptate.readthedocs.io/manual/All_Theories/basic_theories.html#polynomial' single_file = True def __init__(self, name="", parent_dataset=None, ax=None): """**Constructor** Keyword Arguments: - name {str} -- Name of the theory (default: {""}) - parent_dataset {DataSet} -- DataSet that contains this theory (default: {None}) - ax {Axes array} -- Matplotlib axes where the theory will plot the data (default: {None}) """ super().__init__(name, parent_dataset, ax) self.MAX_DEGREE = 10 self.function = self.polynomial self.parameters["n"] = Parameter( name="n", value=1, description="Degree of Polynomial", type=ParameterType.integer, opt_type=OptType.const, display_flag=False) for i in range(self.parameters["n"].value + 1): self.parameters["A%d" % i] = Parameter( "A%d" % i, 1.0, "Coefficient order %d" % i, ParameterType.real, opt_type=OptType.opt) self.Qprint("%s: A0 + A1*x + A2*x^2 + ..." % self.thname) # add widgets specific to the theory tb = QToolBar() tb.setIconSize(QSize(24, 24)) self.spinbox = QSpinBox() self.spinbox.setRange(1, self.MAX_DEGREE) # min and max number of modes self.spinbox.setPrefix("degree ") self.spinbox.setValue(self.parameters["n"].value) #initial value tb.addWidget(self.spinbox) self.thToolsLayout.insertWidget(0, tb) connection_id = self.spinbox.valueChanged.connect( self.handle_spinboxValueChanged)
[docs] def handle_spinboxValueChanged(self, value): """Handle a change of the parameter 'nmode'""" self.set_param_value("n", value)
[docs] def set_param_value(self, name, value): """Change a parameter value, in particular *n* """ if name == 'n': nold = self.parameters["n"].value Aold = np.zeros(nold + 1) for i in range(nold + 1): Aold[i] = self.parameters["A%d" % i].value del self.parameters["A%d" % i] nnew = value message, success = super().set_param_value("n", nnew) for i in range(nnew + 1): if i <= nold: Aval = Aold[i] else: Aval = 1.0 self.parameters["A%d" % i] = Parameter( "A%d" % i, Aval, "Coefficient degree %d" % i, ParameterType.real, opt_type=OptType.opt) else: message, success = super().set_param_value(name, value) if self.autocalculate: self.parent_dataset.handle_actionCalculate_Theory() self.update_parameter_table() return message, success
[docs] def polynomial(self, f=None): """Actual polynomial function. .. math:: y(x) = \\sum_{i=0}^n A_i x^i """ ft = f.data_table tt = self.tables[f.file_name_short] tt.num_columns = ft.num_columns tt.num_rows = ft.num_rows tt.data = np.zeros((tt.num_rows, tt.num_columns)) tt.data[:, 0] = ft.data[:, 0] for i in range(self.parameters["n"].value + 1): a = self.parameters["A%d" % i].value for j in range(1, tt.num_columns): tt.data[:, j] += a * tt.data[:, 0]**i
""" _ __ _____ _____ _ __ | | __ ___ __ | '_ \ / _ \ \ /\ / / _ \ '__| | |/ _` \ \ /\ / / | |_) | (_) \ V V / __/ | | | (_| |\ V V / | .__/ \___/ \_/\_/ \___|_| |_|\__,_| \_/\_/ |_| """
[docs] class TheoryPowerLaw(QTheory): """Fit a power law to the data * **Function** .. math:: y(x) = a x^b * **Parameters** - :math:`a`: prefactor. - :math:`b`: exponent. """ thname = "Power Law" description = "Fit Power Law" html_help_file = 'http://reptate.readthedocs.io/manual/All_Theories/basic_theories.html#power-law' single_file = True def __init__(self, name="", parent_dataset=None, ax=None): """**Constructor**""" super().__init__(name, parent_dataset, ax) self.function = self.powerlaw self.parameters["a"] = Parameter( "a", 1.0, "Prefactor", ParameterType.real, opt_type=OptType.opt) self.parameters["b"] = Parameter( "b", 1.0, "Exponent", ParameterType.real, opt_type=OptType.opt) self.Qprint("%s: a*x^b" % self.thname)
[docs] def powerlaw(self, f=None): """Actual function * **Function** .. math:: y(x) = a x^b """ ft = f.data_table tt = self.tables[f.file_name_short] tt.num_columns = ft.num_columns tt.num_rows = ft.num_rows tt.data = np.zeros((tt.num_rows, tt.num_columns)) tt.data[:, 0] = ft.data[:, 0] for j in range(1, tt.num_columns): tt.data[:, j] = self.parameters[ "a"].value * tt.data[:, 0]**self.parameters["b"].value
""" _ _ _ _____ ___ __ ___ _ __ ___ _ __ | |_(_) __ _| | / _ \ \/ / '_ \ / _ \| '_ \ / _ \ '_ \| __| |/ _` | | | __/> <| |_) | (_) | | | | __/ | | | |_| | (_| | | \___/_/\_\ .__/ \___/|_| |_|\___|_| |_|\__|_|\__,_|_| |_| """
[docs] class TheoryExponential(QTheory): """Fit a single exponential decay to the data * **Function** .. math:: y(x) = a \\exp(-x/T) * **Parameters** - :math:`a`: prefactor. - :math:`T`: exponential "time" constant. """ thname = "Exponential" description = "Fit Exponential" html_help_file = 'http://reptate.readthedocs.io/manual/All_Theories/basic_theories.html#exponential' single_file = True def __init__(self, name="", parent_dataset=None, ax=None): """**Constructor**""" super().__init__(name, parent_dataset, ax) self.function = self.exponential self.parameters["a"] = Parameter( "a", 1.0, "Prefactor", ParameterType.real, opt_type=OptType.opt) self.parameters["T"] = Parameter( "T", 1.0, "Exponential time constant", ParameterType.real, opt_type=OptType.opt) self.Qprint("%s: a*exp(-x/T)" % self.thname)
[docs] def exponential(self, f=None): """**Function** :math:`y(x) = a \\exp(-x/T)`""" ft = f.data_table tt = self.tables[f.file_name_short] tt.num_columns = ft.num_columns tt.num_rows = ft.num_rows tt.data = np.zeros((tt.num_rows, tt.num_columns)) tt.data[:, 0] = ft.data[:, 0] for j in range(1, tt.num_columns): tt.data[:, j] = self.parameters["a"].value * np.exp( -tt.data[:, 0] / self.parameters["T"].value)
""" ____ _ _ _ |___ \ _____ ___ __ ___ _ __ ___ _ __ | |_(_) __ _| |___ __) | / _ \ \/ / '_ \ / _ \| '_ \ / _ \ '_ \| __| |/ _` | / __| / __/ | __/> <| |_) | (_) | | | | __/ | | | |_| | (_| | \__ \ |_____| \___/_/\_\ .__/ \___/|_| |_|\___|_| |_|\__|_|\__,_|_|___/ |_| """
[docs] class TheoryTwoExponentials(QTheory): """Fit **two** single exponential decay to the data * **Function** .. math:: y(x) = a_1 \\exp(x/T_1) + a_2 \\exp(-x/T_2) * **Parameters** - :math:`a_1`, :math:`a_2`: prefactors. - :math:`T_1`, :math:`T_2`: exponential "time" constants. """ thname = "Two Exponentials" description = "Fit two exponentials" html_help_file = 'http://reptate.readthedocs.io/manual/All_Theories/basic_theories.html#double-exponential' single_file = True def __init__(self, name="", parent_dataset=None, ax=None): """**Constructor**""" super().__init__(name, parent_dataset, ax) self.function = self.two_exponentials self.parameters["a1"] = Parameter( "a1", 0.9, "Prefactor 1", ParameterType.real, opt_type=OptType.opt) self.parameters["T1"] = Parameter( "T1", 1.0, "Exponential time constant 1", ParameterType.real, opt_type=OptType.opt) self.parameters["a2"] = Parameter( "a2", 0.1, "Prefactor 2", ParameterType.real, opt_type=OptType.opt) self.parameters["T2"] = Parameter( "T2", 10.0, "Exponential time constant 2", ParameterType.real, opt_type=OptType.opt) self.Qprint("%s: a1*exp(-x/T1) + a2*exp(-x/T2)" % self.thname)
[docs] def two_exponentials(self, f=None): """Actual function * **Function** .. math:: y(x) = a_1 \\exp(x/T_1) + a_2 \\exp(-x/T_2) """ ft = f.data_table tt = self.tables[f.file_name_short] tt.num_columns = ft.num_columns tt.num_rows = ft.num_rows tt.data = np.zeros((tt.num_rows, tt.num_columns)) tt.data[:, 0] = ft.data[:, 0] a1 = self.parameters["a1"].value a2 = self.parameters["a2"].value T1 = self.parameters["T1"].value T2 = self.parameters["T2"].value for j in range(1, tt.num_columns): tt.data[:, j] = a1 * np.exp(-tt.data[:, 0] / T1) + a2 * np.exp( -tt.data[:, 0] / T2)
""" _ _ _ __ _| | __ _ ___| |__ _ __ __ _(_) ___ / _` | |/ _` |/ _ \ '_ \| '__/ _` | |/ __| | (_| | | (_| | __/ |_) | | | (_| | | (__ \__,_|_|\__, |\___|_.__/|_| \__,_|_|\___| |___/ _ _____ ___ __ _ __ ___ ___ ___(_) ___ _ __ / _ \ \/ / '_ \| '__/ _ \/ __/ __| |/ _ \| '_ \ | __/> <| |_) | | | __/\__ \__ \ | (_) | | | | \___/_/\_\ .__/|_| \___||___/___/_|\___/|_| |_| |_| """
[docs] class TheoryAlgebraicExpression(QTheory): """Fit a user algebraic expression with :math:`n` parameters. The expression can contain any of the following mathematical functions: sin, cos, tan, arccos, arcsin, arctan, arctan2, deg2rad, rad2deg, sinh, cosh, tanh, arcsinh, arccosh, arctanh, around, round, rint, floor, ceil,trunc, exp, log, log10, fabs, mod, e, pi, power, sqrt It is the responsability of the user to input functions that make mathematical sense. * **Function** .. math:: y(x) = f({A_i}, x, F_{params}) * **Parameters** - :math:`n`: number of parameters. - :math:`A_i`: coefficeints of the algebraic expression """ thname = "Algebraic Expression" description = "Fit an algebraic expression with n parameters" html_help_file = 'http://reptate.readthedocs.io/manual/All_Theories/basic_theories.html#algebraic-expression' single_file = False def __init__(self, name="", parent_dataset=None, ax=None): """**Constructor**""" super().__init__(name, parent_dataset, ax) self.MAX_DEGREE = 10 self.function = self.algebraicexpression self.parameters["n"] = Parameter( name="n", value=2, description="Number of Parameters", type=ParameterType.integer, opt_type=OptType.const, display_flag=False) self.parameters["expression"] = Parameter( name="expression", value="A0+A1*x", description="Algebraic Expression", type=ParameterType.string, opt_type=OptType.const, display_flag=False) for i in range(self.parameters["n"].value): self.parameters["A%d" % i] = Parameter( "A%d" % i, 1.0, "Parameter %d" % i, ParameterType.real, opt_type=OptType.opt) safe_list = ['sin', 'cos', 'tan', 'arccos', 'arcsin', 'arctan', 'arctan2', 'deg2rad', 'rad2deg', 'sinh', 'cosh', 'tanh', 'arcsinh', 'arccosh', 'arctanh', 'around', 'round_', 'rint', 'floor', 'ceil','trunc', 'exp', 'log', 'log10', 'fabs', 'mod', 'e', 'pi', 'power', 'sqrt'] self.safe_dict = {} for k in safe_list: self.safe_dict[k] = globals().get(k, None) # add widgets specific to the theory tb = QToolBar() tb.setIconSize(QSize(24, 24)) self.spinbox = QSpinBox() self.spinbox.setRange(1, self.MAX_DEGREE) # min and max number of modes self.spinbox.setToolTip("Number of parameters") self.spinbox.setValue(self.parameters["n"].value) #initial value tb.addWidget(self.spinbox) self.expressionCB = QComboBox() self.expressionCB.setToolTip("Algebraic expression") self.expressionCB.addItem("A0+A1*x") self.expressionCB.addItem("A0*sin(A1*x)") self.expressionCB.addItem("A0*sin(A1*x+A2)") self.expressionCB.setEditable(True) self.expressionCB.setMinimumWidth(self.parent_dataset.width()-75) tb.addWidget(self.expressionCB) self.thToolsLayout.insertWidget(0, tb) connection_id = self.spinbox.valueChanged.connect( self.handle_spinboxValueChanged) connection_id = self.expressionCB.currentIndexChanged.connect( self.handle_expressionChanged)
[docs] def handle_spinboxValueChanged(self, value): """Handle a change of the parameter 'n'""" self.set_param_value("n", value)
[docs] def handle_expressionChanged(self, item): """Handle a change in the algebraic expression""" self.set_param_value("expression", self.expressionCB.itemText(item))
[docs] def set_param_value(self, name, value): """Change a parameter value, in particular *n* """ if name == 'n': nold = self.parameters["n"].value Aold = np.zeros(nold) for i in range(nold): Aold[i] = self.parameters["A%d" % i].value del self.parameters["A%d" % i] nnew = value message, success = super().set_param_value("n", nnew) for i in range(nnew): if i < nold: Aval = Aold[i] else: Aval = 1.0 self.parameters["A%d" % i] = Parameter( "A%d" % i, Aval, "Parameter %d" % i, ParameterType.real, opt_type=OptType.opt) else: message, success = super().set_param_value(name, value) if self.autocalculate: self.parent_dataset.handle_actionCalculate_Theory() self.update_parameter_table() return message, success
[docs] def algebraicexpression(self, f=None): """Actual function. * **Function** .. math:: y(x) = f({A_i}, x) """ ft = f.data_table tt = self.tables[f.file_name_short] tt.num_columns = ft.num_columns tt.num_rows = ft.num_rows tt.data = np.zeros((tt.num_rows, tt.num_columns)) tt.data[:, 0] = ft.data[:, 0] expression = self.parameters["expression"].value params = set(re.findall( "A\d{1,2}", expression)) nparams=len(params) maxparamindex=-1; for p in params: paramindex=int(p.split('A')[1]) if paramindex>maxparamindex: maxparamindex = paramindex n = self.parameters["n"].value if (maxparamindex!=n-1) or (nparams!=n): self.logger.warning("Wrong expression or number of parameters. Review your theory") self.Qprint("<b><font color=red>Wrong expression or number of parameters</font></b>. Review your theory") else: # Find FILE PARAMETERS IN THE EXPRESSION fparams = re.findall("\[(.*?)\]",expression) for fp in fparams: if fp in f.file_parameters: self.safe_dict[fp]=float(f.file_parameters[fp]) else: self.logger.warning("File parameter not found. Review your theory") self.Qprint("<b><font color=red>File parameter not found</font></b>. Review your theory") self.safe_dict[fp]=0.0 expression = expression.replace("[","").replace("]","") self.safe_dict['x']=tt.data[:, 0] for i in range(n): self.safe_dict['A%d'%i]=self.parameters["A%d" % i].value try: y = eval(expression, {"__builtins__":None}, self.safe_dict) for j in range(1, tt.num_columns): tt.data[:, j] = y except NameError as e: self.Qprint("<b>Error in algebraic expression <b>") self.logger.exception("Error in Algebraic Expression") except TypeError as e: self.Qprint("<b>Error in algebraic expression <b>") self.logger.exception("Error in Algebraic Expression") except Exception as e: self.Qprint("<b>Error in algebraic expression <b>") self.logger.exception("Error in Algebraic Expression")
#print (e.__class__, ":", e)
[docs] def do_error(self, line): super().do_error(line) self.Qprint("%s: <b>%s</b>" % (self.thname, self.parameters["expression"].value))