# 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 TheoryGiesekus
Module for the Giesekus model for the non-linear flow of entangled polymers.
"""
import numpy as np
from scipy.integrate import odeint
from RepTate.core.Parameter import Parameter, ParameterType, OptType
from RepTate.gui.QTheory import QTheory, EndComputationRequested
from PySide6.QtWidgets import QToolBar, QToolButton, QMenu, QSpinBox, QMessageBox
from PySide6.QtCore import QSize
from PySide6.QtGui import QIcon
from RepTate.gui.Theory_rc import *
from RepTate.applications.ApplicationLAOS import ApplicationLAOS
from RepTate.theories.theory_helpers import FlowMode, EditModesDialog
[docs]
class TheoryGiesekus(QTheory):
"""Multi-mode Giesekus Model (see Chapter 6 of :cite:`NLVE-Larson1988`):
.. math::
\\boldsymbol \\sigma &= \\sum_{i=1}^n G_i \\boldsymbol {A_i},\\\\
\\dfrac {\\mathrm D \\boldsymbol A_i} {\\mathrm D t} &= \\boldsymbol \\kappa \\cdot \\boldsymbol A_i
+ \\boldsymbol A_i\\cdot \\boldsymbol \\kappa ^T
- \dfrac {1} {\\tau_i} (\\boldsymbol A_i - \\boldsymbol I)
- \dfrac {\\alpha_i} {\\tau_i} (\\boldsymbol A_i - \\boldsymbol I)^2,
where for each mode :math:`i`:
- :math:`G_i`: weight of mode :math:`i`
- :math:`\\tau_i`: relaxation time of mode :math:`i`
- :math:`\\alpha_i`: constant of proportionality mode :math:`i`
* **Parameters**
- ``alpha_i`` :math:`\\equiv \\alpha_i`
"""
thname = "Giesekus"
description = "Giesekus constitutive equation"
citations = ["Giesekus H., Rheol. Acta 1966, 5, 29"]
doi = ["http://dx.doi.org/10.1007/BF01973575"]
html_help_file = "http://reptate.readthedocs.io/manual/Applications/NLVE/Theory/theory.html#multi-mode-giesekus-model"
single_file = False
def __init__(self, name="", parent_dataset=None, axarr=None):
"""**Constructor**"""
super().__init__(name, parent_dataset, axarr)
self.function = self.calculate_giesekus
self.has_modes = True
self.parameters["nmodes"] = Parameter(
name="nmodes",
value=2,
description="Number of modes",
type=ParameterType.integer,
opt_type=OptType.const,
display_flag=False,
)
self.parameters["nstretch"] = Parameter(
name="nstretch",
value=2,
description="Number of strecthing modes",
type=ParameterType.integer,
opt_type=OptType.const,
display_flag=False,
)
for i in range(self.parameters["nmodes"].value):
self.parameters["G%02d" % i] = Parameter(
name="G%02d" % i,
value=1000.0,
description="Modulus of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.nopt,
display_flag=False,
min_value=0,
)
self.parameters["tauD%02d" % i] = Parameter(
name="tauD%02d" % i,
value=10.0,
description="Terminal time of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.nopt,
display_flag=False,
min_value=0,
)
self.parameters["alpha%02d" % i] = Parameter(
name="alpha%02d" % i,
value=0.5,
description="Dimensionless mobility factor of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.opt,
min_value=0,
max_value=1,
)
self.MAX_MODES = 40
self.init_flow_mode()
# add widgets specific to the theory
tb = QToolBar()
tb.setIconSize(QSize(24, 24))
if not isinstance(parent_dataset.parent_application, ApplicationLAOS):
self.tbutflow = QToolButton()
self.tbutflow.setPopupMode(QToolButton.MenuButtonPopup)
menu = QMenu(self)
self.shear_flow_action = menu.addAction(
QIcon(":/Icon8/Images/new_icons/icon-shear.png"), "Shear Flow"
)
self.extensional_flow_action = menu.addAction(
QIcon(":/Icon8/Images/new_icons/icon-uext.png"), "Extensional Flow"
)
if self.flow_mode == FlowMode.shear:
self.tbutflow.setDefaultAction(self.shear_flow_action)
else:
self.tbutflow.setDefaultAction(self.extensional_flow_action)
self.tbutflow.setMenu(menu)
tb.addWidget(self.tbutflow)
connection_id = self.shear_flow_action.triggered.connect(
self.select_shear_flow
)
connection_id = self.extensional_flow_action.triggered.connect(
self.select_extensional_flow
)
self.read_gdot_action = tb.addAction(
QIcon(":/Icon8/Images/new_icons/icons8-file-gdot.png"),
"Read gdot from file",
)
self.read_gdot_action.setCheckable(True)
else:
self.function = self.calculate_giesekusLAOS
self.tbutmodes = QToolButton()
self.tbutmodes.setPopupMode(QToolButton.MenuButtonPopup)
menu = QMenu(self)
self.get_modes_action = menu.addAction(
QIcon(":/Icon8/Images/new_icons/icons8-broadcasting.png"), "Get Modes"
)
self.edit_modes_action = menu.addAction(
QIcon(":/Icon8/Images/new_icons/icons8-edit-file.png"), "Edit Modes"
)
self.plot_modes_action = menu.addAction(
QIcon(":/Icon8/Images/new_icons/icons8-scatter-plot.png"), "Plot Modes"
)
self.save_modes_action = menu.addAction(
QIcon(":/Icon8/Images/new_icons/icons8-save-Maxwell.png"), "Save Modes"
)
self.tbutmodes.setDefaultAction(self.get_modes_action)
self.tbutmodes.setMenu(menu)
tb.addWidget(self.tbutmodes)
# SpinBox "n-stretch modes"
self.spinbox = QSpinBox()
self.spinbox.setRange(
0, self.parameters["nmodes"].value
) # min and max number of modes
self.spinbox.setSuffix(" stretch")
self.spinbox.setToolTip("Number of stretching modes")
self.spinbox.setValue(self.parameters["nmodes"].value) # initial value
tb.addWidget(self.spinbox)
self.thToolsLayout.insertWidget(0, tb)
connection_id = self.get_modes_action.triggered.connect(self.get_modes_reptate)
connection_id = self.edit_modes_action.triggered.connect(self.edit_modes_window)
connection_id = self.plot_modes_action.triggered.connect(self.plot_modes_graph)
connection_id = self.save_modes_action.triggered.connect(self.save_modes)
connection_id = self.spinbox.valueChanged.connect(
self.handle_spinboxValueChanged
)
[docs]
def handle_spinboxValueChanged(self, value):
nmodes = self.parameters["nmodes"].value
self.set_param_value("nstretch", min(nmodes, value))
if self.autocalculate:
self.parent_dataset.handle_actionCalculate_Theory()
[docs]
def select_shear_flow(self):
self.flow_mode = FlowMode.shear
self.tbutflow.setDefaultAction(self.shear_flow_action)
[docs]
def select_extensional_flow(self):
self.flow_mode = FlowMode.uext
self.tbutflow.setDefaultAction(self.extensional_flow_action)
[docs]
def get_modes_reptate(self):
self.Qcopy_modes()
[docs]
def edit_modes_window(self):
times, G, success = self.get_modes()
if not success:
self.logger.warning("Could not get modes successfully")
return
d = EditModesDialog(self, times, G, self.MAX_MODES)
if d.exec_():
nmodes = d.table.rowCount()
self.set_param_value("nmodes", nmodes)
self.set_param_value("nstretch", nmodes)
success = True
for i in range(nmodes):
msg, success1 = self.set_param_value(
"tauD%02d" % i, d.table.item(i, 0).text()
)
msg, success2 = self.set_param_value(
"G%02d" % i, d.table.item(i, 1).text()
)
success *= success1 * success2
if not success:
QMessageBox.warning(
self,
"Error",
"Some parameter(s) could not be updated.\nPlease try again.",
)
else:
self.handle_actionCalculate_Theory()
[docs]
def plot_modes_graph(self):
pass
[docs]
def init_flow_mode(self):
"""Find if data files are shear or extension"""
try:
f = self.theory_files()[0]
if f.file_type.extension == "shear":
self.flow_mode = FlowMode.shear
else:
self.flow_mode = FlowMode.uext
except Exception as e:
print("in RP init:", e)
self.flow_mode = FlowMode.shear # default mode: shear
[docs]
def get_modes(self):
"""Get the values of Maxwell Modes from this theory"""
nmodes = self.parameters["nmodes"].value
tau = np.zeros(nmodes)
G = np.zeros(nmodes)
for i in range(nmodes):
tau[i] = self.parameters["tauD%02d" % i].value
G[i] = self.parameters["G%02d" % i].value
return tau, G, True
[docs]
def set_modes(self, tau, G):
"""Set the values of Maxwell Modes from another theory"""
nmodes = len(tau)
self.set_param_value("nmodes", nmodes)
self.set_param_value("nstretch", nmodes)
for i in range(nmodes):
self.set_param_value("tauD%02d" % i, tau[i])
self.set_param_value("G%02d" % i, G[i])
return True
[docs]
def n1_uext(self, p, times):
"""Upper Convected Maxwell model in uniaxial extension.
Returns N1 = (XX -YY) component of stress tensor"""
_, G, tauD, ed = p
w = tauD * ed
sxx = (1 - 2 * w * np.exp(-(1 - 2 * w) * times / tauD)) / (1 - 2 * w)
syy = (1 + w * np.exp(-(1 + w) * times / tauD)) / (1 + w)
return G * (sxx - syy)
[docs]
def sigma_xy_shear(self, p, times):
"""Upper Convected Maxwell model in shear.
Returns XY component of stress tensor"""
_, G, tauD, gd = p
return G * gd * tauD * (1 - np.exp(-times / tauD))
[docs]
def sigma_xy_shearLAOS(self, p, times):
"""Giesekus model in LAOS"""
_, G, tauD, g0, w = p
eta = G * tauD
return (
eta
* g0
* w
* (tauD * w * np.sin(w * times) - np.exp(-times / tauD) + np.cos(w * times))
/ (1 + w**2 * tauD**2)
)
[docs]
def sigmadot_shear(self, sigma, times, p):
"""Giesekus model in shear"""
if self.stop_theory_flag:
raise EndComputationRequested
alpha, _, tau, gdot = p
sxx, syy, sxy = sigma
# If the deformation rate is read from the file
if self.read_gdot_action.isChecked():
gdot = np.interp(times, self.t, self.gfile)
dsxx = (
2 * gdot * sxy
+ (alpha - 1) * (sxx - 1) / tau
- alpha / tau * (sxx * sxx + sxy * sxy - sxx)
)
dsyy = (alpha - 1) * (syy - 1) / tau - alpha / tau * (
sxy * sxy + syy * syy - syy
)
dsxy = (
gdot * syy
+ (alpha - 1) * sxy / tau
- alpha / tau * (sxx * sxy + sxy * syy - sxy)
)
return [dsxx, dsyy, dsxy]
[docs]
def sigmadot_uext(self, sigma, times, p):
"""Giesekus model in uniaxial extension"""
if self.stop_theory_flag:
raise EndComputationRequested
alpha, _, tau, edot = p
sxx, syy = sigma
# If the deformation rate is read from the file
if self.read_gdot_action.isChecked():
edot = np.interp(times, self.t, self.gfile)
dsxx = (
2 * edot * sxx
+ (alpha - 1) * (sxx - 1) / tau
- alpha / tau * (sxx * sxx - sxx)
)
dsyy = (
-edot * syy
+ (alpha - 1) * (syy - 1) / tau
- alpha / tau * (syy * syy - syy)
)
return [dsxx, dsyy]
# def sigmadot_uext(self, sigma, times, p):
# """Giesekus model in uniaxial extension"""
# alpha, _, tau, gdot = p
# sxx, syy = sigma
# dsxx = 2 * gdot * sxx - (sxx - 1) / tau - alpha / tau * sxx * (sxx - 1)
# dsyy = -gdot * syy - (syy - 1) / tau - alpha / tau * syy * (syy - 1)
# return [dsxx, dsyy]
[docs]
def sigmadot_shearLAOS(self, sigma, times, p):
"""Giesekus model in shear"""
if self.stop_theory_flag:
raise EndComputationRequested
alpha, _, tau, g0, w = p
sxx, syy, sxy = sigma
gdot = g0 * w * np.cos(w * times)
dsxx = (
2 * gdot * sxy
+ (alpha - 1) * (sxx - 1) / tau
- alpha / tau * (sxx * sxx + sxy * sxy - sxx)
)
dsyy = (alpha - 1) * (syy - 1) / tau - alpha / tau * (
sxy * sxy + syy * syy - syy
)
dsxy = (
gdot * syy
+ (alpha - 1) * sxy / tau
- alpha / tau * (sxx * sxy + sxy * syy - sxy)
)
return [dsxx, dsyy, dsxy]
[docs]
def calculate_giesekus(self, f=None):
"""Calculate Giesekus"""
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]
# flow geometry
if self.flow_mode == FlowMode.shear:
sigma0 = [1.0, 1.0, 0.0] # sxx, syy, sxy
pde_stretch = self.sigmadot_shear
elif self.flow_mode == FlowMode.uext:
sigma0 = [1.0, 1.0] # sxx, syy
pde_stretch = self.sigmadot_uext
else:
return
# ODE solver parameters
abserr = 1.0e-8
relerr = 1.0e-6
self.t = ft.data[:, 0]
self.t = np.concatenate([[0], self.t])
if f.file_type.extension == "shear":
self.gfile = ft.data[:, 3]
elif f.file_type.extension == "uext":
self.gfile = ft.data[:, 2]
self.gfile = np.concatenate([[self.gfile[0]], self.gfile])
# sigma0 = [1.0, 1.0, 0.0] # sxx, syy, sxy
flow_rate = float(f.file_parameters["gdot"])
nmodes = self.parameters["nmodes"].value
nstretch = self.parameters["nstretch"].value
for i in range(nmodes):
if self.stop_theory_flag:
break
G = self.parameters["G%02d" % i].value
tauD = self.parameters["tauD%02d" % i].value
alpha = self.parameters["alpha%02d" % i].value
p = [alpha, G, tauD, flow_rate]
if i < nstretch:
try:
sig = odeint(
pde_stretch, sigma0, self.t, args=(p,), atol=abserr, rtol=relerr
)
except EndComputationRequested:
break
if self.flow_mode == FlowMode.shear:
sxy = np.delete(sig[:, 2], [0])
tt.data[:, 1] += G * sxy
elif self.flow_mode == FlowMode.uext:
sxx = np.delete(sig[:, 0], [0])
syy = np.delete(sig[:, 1], [0])
tt.data[:, 1] += G * (sxx - syy)
else:
# use UCM for non stretching modes
# TODO: Need to check the following lines for time dependent flow rate
if self.flow_mode == FlowMode.shear:
tt.data[:, 1] += self.sigma_xy_shear(p, ft.data[:, 0])
elif self.flow_mode == FlowMode.uext:
tt.data[:, 1] += self.n1_uext(p, ft.data[:, 0])
[docs]
def calculate_giesekusLAOS(self, f=None):
"""Calculate Giesekus for LAOS"""
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]
sigma0 = [1.0, 1.0, 0.0] # sxx, syy, sxy
pde_stretchLAOS = self.sigmadot_shearLAOS
# ODE solver parameters
abserr = 1.0e-8
relerr = 1.0e-6
g0 = float(f.file_parameters["gamma"])
w = float(f.file_parameters["omega"])
nmodes = self.parameters["nmodes"].value
nstretch = self.parameters["nstretch"].value
t = ft.data[:, 0]
tt.data[:, 1] = g0 * np.sin(w * t)
t = np.concatenate([[0], t])
for i in range(nmodes):
if self.stop_theory_flag:
break
G = self.parameters["G%02d" % i].value
tauD = self.parameters["tauD%02d" % i].value
alpha = self.parameters["alpha%02d" % i].value
p = [alpha, G, tauD, g0, w]
if i < nstretch:
try:
sig = odeint(
pde_stretchLAOS, sigma0, t, args=(p,), atol=abserr, rtol=relerr
)
except EndComputationRequested:
break
sxy = np.delete(sig[:, 2], [0])
tt.data[:, 2] += G * sxy
else:
# use UCM for non stretching modes
tt.data[:, 1] += self.sigma_xy_shearLAOS(p, ft.data[:, 0])
[docs]
def set_param_value(self, name, value):
"""Set value of a theory parameter"""
if name == "nmodes":
oldn = self.parameters["nmodes"].value
self.spinbox.setMaximum(int(value))
message, success = super(TheoryGiesekus, self).set_param_value(name, value)
if not success:
return message, success
if name == "nmodes":
for i in range(self.parameters["nmodes"].value):
self.parameters["G%02d" % i] = Parameter(
name="G%02d" % i,
value=1000.0,
description="Modulus of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.nopt,
display_flag=False,
min_value=0,
)
self.parameters["tauD%02d" % i] = Parameter(
name="tauD%02d" % i,
value=10.0,
description="Terminal time of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.nopt,
display_flag=False,
min_value=0,
)
self.parameters["alpha%02d" % i] = Parameter(
name="alpha%02d" % i,
value=0.5,
description="Constant of proportionality of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.opt,
display_flag=True,
min_value=0,
max_value=1,
)
if oldn > self.parameters["nmodes"].value:
for i in range(self.parameters["nmodes"].value, oldn):
del self.parameters["G%02d" % i]
del self.parameters["tauD%02d" % i]
del self.parameters["alpha%02d" % i]
return "", True