# 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 TheoryPomPom
Module for the Pom-Pom model for the non-linear flow of entangled polymers.
"""
import os
import numpy as np
from math import exp # faster than np for scalar
from scipy.integrate import odeint
import RepTate
from RepTate.core.Parameter import Parameter, ParameterType, OptType
from RepTate.gui.QTheory import QTheory, EndComputationRequested, MinimizationMethod
from PySide6.QtWidgets import QToolBar, QToolButton, QMenu, QMessageBox, QFileDialog
from PySide6.QtCore import QSize
from PySide6.QtGui import QIcon
from RepTate.gui.Theory_rc import *
from RepTate.applications.ApplicationLAOS import ApplicationLAOS
import RepTate
import time
from RepTate.theories.theory_helpers import FlowMode, EditModesDialog
[docs]
class TheoryPomPom(QTheory):
"""Multi-mode PomPom Model based on :cite:`NLVE-Blackwell2000`:
.. math::
\\boldsymbol \\sigma &= 3 \\sum_{i=1}^n G_i \\lambda_i^2(t) \\boldsymbol S_i (t),\\\\
\\boldsymbol S_i &= \\dfrac{\\boldsymbol A_i } {\\mathrm{Tr} \\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_{\\mathrm b, i}} (\\boldsymbol A_i - \\boldsymbol I), \\\\
\\dfrac {\\mathrm D \\lambda_i} {\\mathrm D t} &=
\\lambda_i (\\boldsymbol \\kappa : \\boldsymbol S_i)
- \\dfrac {1} {\\tau_{\\mathrm s, i}} (\\lambda_i - 1)
\\exp\\left( \\nu^* (\\lambda_i - 1) \\right),
where, for each mode :math:`i`:
- :math:`G_i`: weight of mode :math:`i`
- :math:`\\tau_{\\mathrm b, i}`: backbone orientation relaxation time of mode :math:`i`
- :math:`\\tau_{\\mathrm s, i}`: backbone stretch relaxation time of mode :math:`i`
- :math:`\\nu_i^* = \\dfrac{2}{q_i - 1}`
- :math:`q_i`: the number of dangling arms of each mode
- **Parameters**
- ``q_i`` :math:`\\equiv q_i`: the number of dangling arms of each mode
- ``ratio_i`` :math:`\\equiv \\dfrac{\\tau_{\\mathrm b, i}}{\\tau_{\\mathrm s, i}}`:
the ratio of orientation to stretch relaxation times of each mode
"""
thname = "Pom-Pom"
description = "Pom-Pom constitutive equation"
citations = ["McLeish T.C.B. and Larson R.G., J. Rheol. 1998, 42, 81-110"]
doi = ["http://dx.doi.org/10.1122/1.550933"]
html_help_file = "http://reptate.readthedocs.io/manual/Applications/NLVE/Theory/theory.html#multi-mode-pom-pom-model"
single_file = False
def __init__(self, name="", parent_dataset=None, axarr=None):
"""**Constructor**"""
super().__init__(name, parent_dataset, axarr)
self.function = self.calculate_PomPom
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.mintype = MinimizationMethod.bruteforce
# self.BruteNs = 5
self.mintype = MinimizationMethod.diffevol
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["tauB%02d" % i] = Parameter(
name="tauB%02d" % i,
value=10.0,
description="Backbone relaxation time of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.nopt,
display_flag=False,
min_value=0,
)
self.parameters["q%02d" % i] = Parameter(
name="q%02d" % i,
value=1,
description="Number of dangling arms of mode %02d" % i,
type=ParameterType.integer,
opt_type=OptType.opt,
min_value=1,
max_value=10,
)
self.parameters["ratio%02d" % i] = Parameter(
name="ratio%02d" % i,
value=2,
description="Ratio of orientation to stretch relaxation times of mode %02d"
% i,
type=ParameterType.real,
opt_type=OptType.const,
min_value=1,
max_value=5,
)
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_PomPomLAOS
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)
# Save to flowsolve button
self.flowsolve_btn = tb.addAction(
QIcon(":/Icon8/Images/new_icons/icons8-save-flowsolve.png"),
"Save Parameters To FlowSolve",
)
self.flowsolve_btn.setCheckable(False)
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.flowsolve_btn.triggered.connect(self.handle_flowsolve_btn)
[docs]
def handle_flowsolve_btn(self):
"""Save theory parameters in FlowSolve format"""
# Get filename of RepTate project to open
fpath, _ = QFileDialog.getSaveFileName(
self,
"Save Parameters to FowSolve",
os.path.join(RepTate.root_dir, "data"),
"FlowSolve (*.fsrep)",
)
if fpath == "":
return
with open(fpath, "w") as f:
verdata = RepTate._version.get_versions()
version = verdata["version"].split("+")[0]
date = verdata["date"].split("T")[0]
build = verdata["version"]
header = "#flowGen input\n"
header += "# Generated with RepTate %s %s (build %s)\n" % (
version,
date,
build,
)
header += "# At %s on %s\n" % (
time.strftime("%X"),
time.strftime("%a %b %d, %Y"),
)
f.write(header)
f.write("\n#param global\n")
f.write("constit multip\n")
# f.write('# or multip (for pompom) or polydisperse (for polydisperse Rolie-Poly)\n')
f.write("\n#param constitutive\n")
n = self.parameters["nmodes"].value
td = np.zeros(n)
for i in range(n):
td[i] = self.parameters["tauB%02d" % i].value
# sort taud ascending order
args = np.argsort(td)
modulus = "modulus"
taub = "taub"
ratio = "ratio"
qarms = "qarms"
for i, arg in enumerate(args):
modulus += " %.6g" % self.parameters["G%02d" % arg].value
taub += " %.6g" % self.parameters["tauB%02d" % arg].value
ratio += " %.6g" % self.parameters["ratio%02d" % arg].value
qarms += " %.6g" % self.parameters["q%02d" % arg].value
f.write("%s\n%s\n%s\n%s\n" % (modulus, taub, ratio, qarms))
f.write("nustar 2\n")
f.write("\n#end")
QMessageBox.information(
self, "Success", 'Wrote FlowSolve parameters in "%s"' % fpath
)
[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)
success = True
for i in range(nmodes):
msg, success1 = self.set_param_value(
"tauB%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["tauB%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)
for i in range(nmodes):
self.set_param_value("tauB%02d" % i, tau[i])
self.set_param_value("G%02d" % i, G[i])
return True
[docs]
def sigmadot_shear(self, l, t, p):
"""PomPom model in shear"""
if self.stop_theory_flag:
raise EndComputationRequested
q, tauB, tauS, gdot = p
# If the deformation rate is read from the file
# if self.read_gdot_action.isChecked():
# gdot = np.interp(t, self.times, self.gfile)
if (l >= q) or (q == 1):
l = q
dydx = 0
elif l < 1:
l = 1
dydx = 0
else:
nustar = 2.0 / (q - 1)
Axy = gdot * tauB * (1 - exp(-t / tauB))
Axx = (
2 * gdot * gdot * tauB * tauB * (1 - exp(-t / tauB))
+ 1
- 2 * gdot * gdot * tauB * t * exp(-t / tauB)
)
Trace = Axx + 2
# For very fast modes, avoid integrating
aux = tauS / exp(nustar * (l - 1))
if aux * gdot < 1e-3:
dydx = 0
else:
dydx = l * gdot * Axy / Trace - (l - 1) / tauS * exp(nustar * (l - 1))
return dydx
[docs]
def sigmadot_uext(self, l, t, p):
"""PomPom model in uniaxial extension"""
if self.stop_theory_flag:
raise EndComputationRequested
q, tauB, tauS, edot = p
# If the deformation rate is read from the file
# if self.read_gdot_action.isChecked():
# edot = np.interp(t, self.times, self.gfile)
if (l >= q) or (q == 1):
l = q
dydx = 0
else:
nustar = 2.0 / (q - 1.0)
Axx = (1 - 2 * edot * tauB * exp((2 * edot * tauB - 1) * t / tauB)) / (
1 - 2 * edot * tauB
)
Ayy = (1 + edot * tauB * exp(-(1 + edot * tauB) * t / tauB)) / (
1 + edot * tauB
)
Trace = Axx + 2 * Ayy
# For very fast modes, avoid integrating
aux = tauS / exp(nustar * (l - 1))
if Axx > 1e240: # To avoid floating point overflow
firstterm = l * edot
else:
firstterm = l * edot * (Axx - Ayy) / Trace
if aux * edot < 1e-3:
dydx = 0
else:
dydx = firstterm - (l - 1) / tauS * exp(nustar * (l - 1))
return dydx
[docs]
def sigmadot_shearLAOS(self, l, t, p):
"""PomPom model in shear LAOS"""
if self.stop_theory_flag:
raise EndComputationRequested
q, tauB, tauS, g0, w = p
gdot = g0 * w * np.cos(w * t)
if (l >= q) or (q == 1):
l = q
dydx = 0
elif l < 1:
l = 1
dydx = 0
else:
nustar = 2.0 / (q - 1)
Axy = (
tauB
* g0
* w
* (tauB * w * np.sin(w * t) - np.exp(-t / tauB) + np.cos(w * t))
/ (1 + w**2 * tauB**2)
)
Axx = 1 - tauB * g0**2 * w * (
2 * np.cos(2 * w * t) * tauB**3 * w**3
+ 2 * np.exp(-t / tauB) * tauB**3 * w**3
+ 8 * np.exp(-t / tauB) * tauB**2 * w**2 * np.sin(w * t)
- 4 * tauB**3 * w**3
- 3 * np.sin(2 * w * t) * tauB**2 * w**2
- np.cos(2 * w * t) * tauB * w
+ 2 * tauB * np.exp(-t / tauB) * w
+ 2 * np.exp(-t / tauB) * np.sin(w * t)
- tauB * w
) / (4 * tauB**4 * w**4 + 5 * tauB**2 * w**2 + 1)
Trace = Axx + 2
# For very fast modes, avoid integrating
aux = tauS / exp(nustar * (l - 1))
if aux * gdot < 1e-3:
dydx = 0
else:
dydx = l * gdot * Axy / Trace - (l - 1) / tauS * exp(nustar * (l - 1))
return dydx
[docs]
def calculate_PomPom(self, f=None):
"""Calculate the theory"""
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:
pde_stretch = self.sigmadot_shear
elif self.flow_mode == FlowMode.uext:
pde_stretch = self.sigmadot_uext
else:
return
# ODE solver parameters
abserr = 1.0e-8
relerr = 1.0e-6
self.times = ft.data[:, 0]
self.times = np.concatenate([[0], self.times])
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])
# create parameters list
flow_rate = float(f.file_parameters["gdot"])
nmodes = self.parameters["nmodes"].value
for i in range(nmodes):
if self.stop_theory_flag:
break
G = self.parameters["G%02d" % i].value
q = np.round(self.parameters["q%02d" % i].value)
tauB = self.parameters["tauB%02d" % i].value
tauS = tauB / self.parameters["ratio%02d" % i].value
p = [q, tauB, tauS, flow_rate]
# solve ODEs
stretch_ini = 1
try:
l = odeint(
pde_stretch,
stretch_ini,
self.times,
args=(p,),
atol=abserr,
rtol=relerr,
)
except EndComputationRequested:
break
# write results in table
l = np.delete(l, [0]) # delete the t=0 value
t = np.delete(self.times, [0]) # delete the t=0 value
# TODO: Need to check the following lines for time dependent gdot
if self.flow_mode == FlowMode.shear:
Axy_arr = flow_rate * tauB * (1 - np.exp(-t / tauB))
Axx_arr = (
2 * flow_rate * flow_rate * tauB * tauB * (1 - np.exp(-t / tauB))
+ 1
- 2 * flow_rate * flow_rate * tauB * t * np.exp(-t / tauB)
)
tt.data[:, 1] += 3 * G * l * l * Axy_arr / (Axx_arr + 2.0)
elif self.flow_mode == FlowMode.uext:
Axx_arr = (
1
- 2
* flow_rate
* tauB
* np.exp((2 * flow_rate * tauB - 1) * t / tauB)
) / (1 - 2 * flow_rate * tauB)
Ayy_arr = (
1 + flow_rate * tauB * np.exp(-(1 + flow_rate * tauB) * t / tauB)
) / (1 + flow_rate * tauB)
k = np.ones(len(t))
k[Axx_arr < 1e240] = (
Axx_arr[Axx_arr < 1e240] - Ayy_arr[Axx_arr < 1e240]
) / (
Axx_arr[Axx_arr < 1e240] + 2 * Ayy_arr[Axx_arr < 1e240]
) # k=1 if Axx > 1e240
tt.data[:, 1] += 3 * G * l * l * k
[docs]
def calculate_PomPomLAOS(self, f=None):
"""Calculate the theory in 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]
pde_stretchLAOS = self.sigmadot_shearLAOS
# ODE solver parameters
abserr = 1.0e-8
relerr = 1.0e-6
# create parameters list
g0 = float(f.file_parameters["gamma"])
w = float(f.file_parameters["omega"])
nmodes = self.parameters["nmodes"].value
times = ft.data[:, 0]
tt.data[:, 1] = g0 * np.sin(w * times)
times = np.concatenate([[0], times])
for i in range(nmodes):
if self.stop_theory_flag:
break
G = self.parameters["G%02d" % i].value
q = self.parameters["q%02d" % i].value
tauB = self.parameters["tauB%02d" % i].value
tauS = tauB / self.parameters["ratio%02d" % i].value
p = [q, tauB, tauS, g0, w]
# solve ODEs
stretch_ini = 1
try:
l = odeint(
pde_stretchLAOS,
stretch_ini,
times,
args=(p,),
atol=abserr,
rtol=relerr,
)
except EndComputationRequested:
break
# write results in table
l = np.delete(l, [0]) # delete the t=0 value
t = np.delete(times, [0]) # delete the t=0 value
Axy_arr = (
tauB
* g0
* w
* (tauB * w * np.sin(w * t) - np.exp(-t / tauB) + np.cos(w * t))
/ (1 + w**2 * tauB**2)
)
Axx_arr = 1 - tauB * g0**2 * w * (
2 * np.cos(2 * w * t) * tauB**3 * w**3
+ 2 * np.exp(-t / tauB) * tauB**3 * w**3
+ 8 * np.exp(-t / tauB) * tauB**2 * w**2 * np.sin(w * t)
- 4 * tauB**3 * w**3
- 3 * np.sin(2 * w * t) * tauB**2 * w**2
- np.cos(2 * w * t) * tauB * w
+ 2 * tauB * np.exp(-t / tauB) * w
+ 2 * np.exp(-t / tauB) * np.sin(w * t)
- tauB * w
) / (4 * tauB**4 * w**4 + 5 * tauB**2 * w**2 + 1)
tt.data[:, 2] += 3 * G * l * l * Axy_arr / (Axx_arr + 2.0)
[docs]
def set_param_value(self, name, value):
"""Set the value of theory parameters"""
if name == "nmodes":
oldn = self.parameters["nmodes"].value
message, success = super(TheoryPomPom, 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["tauB%02d" % i] = Parameter(
name="tauB%02d" % i,
value=10.0,
description="Backbone relaxation time of mode %02d" % i,
type=ParameterType.real,
opt_type=OptType.nopt,
display_flag=False,
min_value=0,
)
self.parameters["q%02d" % i] = Parameter(
name="q%02d" % i,
value=1,
description="Number of dangling arms of mode %02d" % i,
type=ParameterType.integer,
opt_type=OptType.opt,
min_value=1,
max_value=10,
)
self.parameters["ratio%02d" % i] = Parameter(
name="ratio%02d" % i,
value=2,
description="Ratio of orientation to stretch relaxation times of mode %02d"
% i,
type=ParameterType.real,
opt_type=OptType.const,
min_value=1,
max_value=5,
)
if oldn > self.parameters["nmodes"].value:
for i in range(self.parameters["nmodes"].value, oldn):
del self.parameters["G%02d" % i]
del self.parameters["tauB%02d" % i]
del self.parameters["ratio%02d" % i]
del self.parameters["q%02d" % i]
return "", True