"""This module handles properties associated with the active materials.
Only helper functions are defined here.
Diffusion functions are defined in mpet.electrode.diffusion
Chemical potential functions are defined in mpet.electrode.materials"""
import types
import numpy as np
import mpet.geometry as geo
from mpet.config import constants
from mpet.utils import import_function
[docs]
class muRfuncs():
""" This class defines functions which describe the chemical
potential of active materials.
Each function describes a particular material. The
chemical potential is directly related to the open circuit voltage
(OCV) for solid solution materials.
In each function, muR_ref is the offset (non-dimensional) chemical
potential by which the returned value will be shifted (for
numerical convenience).
Each material function returns:
muR -- chemical potential
actR -- activity (if applicable, else None)
"""
def __init__(self, config, trode, ind=None):
"""config is the full dictionary of
parameters for the electrode particles, as made for the
simulations. trode is the selected electrode.
ind is optinally the selected particle, provided as (vInd, pInd)
"""
self.config = config
self.trode = trode
self.ind = ind
self.T = config['T'] # nondimensional
# eokT and kToe are the reference values for scalings
self.eokT = constants.e / (constants.k * constants.T_ref)
self.kToe = 1. / self.eokT
# If the user provided a filename with muRfuncs, try to load
# the function from there, otherwise load it from this class
filename = self.get_trode_param("muRfunc_filename")
muRfunc_name = self.get_trode_param("muRfunc")
if filename is None:
# the function will be loaded from the materials folder
muRfunc = import_function(None, muRfunc_name,
mpet_module=f"mpet.electrode.materials.{muRfunc_name}")
else:
muRfunc = import_function(filename, muRfunc_name)
# We have to make sure the function knows what 'self' is with
# the types.MethodType function
self.muRfunc = types.MethodType(muRfunc, self)
[docs]
def get_trode_param(self, item):
"""
Shorthand to retrieve electrode-specific value
"""
value = self.config[self.trode, item]
# check if it is a particle-specific parameter
if self.ind is not None and item in self.config.params_per_particle:
value = value[self.ind]
return value
[docs]
def get_muR_from_OCV(self, OCV, muR_ref):
return -self.eokT*OCV + muR_ref
######
# Helper functions
######
[docs]
def ideal_sln(self, y):
""" Helper function: Should not be called directly from
simulation. Call a specific material instead. """
T = self.T
muR = T*np.log(y/(1-y))
return muR
[docs]
def reg_sln(self, y, Omga):
""" Helper function """
muR_IS = self.ideal_sln(y)
enthalpyTerm = Omga*(1-2*y)
muR = muR_IS + enthalpyTerm
return muR
[docs]
def graphite_2param_homog(self, y, Omga, Omgb, Omgc, EvdW):
""" Helper function """
y1, y2 = y
muR1 = self.reg_sln(y1, Omga)
muR2 = self.reg_sln(y2, Omga)
muR1 += Omgb*y2 + Omgc*y2*(1-y2)*(1-2*y1)
muR2 += Omgb*y1 + Omgc*y1*(1-y1)*(1-2*y2)
muR1 += EvdW * (30 * y1**2 * (1-y1)**2)
muR2 += EvdW * (30 * y2**2 * (1-y2)**2)
return (muR1, muR2)
[docs]
def graphite_1param_homog(self, y, Omga, Omgb):
""" Helper function """
width = 5e-2
tailScl = 5e-2
muLtail = -tailScl*1./(y**(0.85))
muRtail = tailScl*1./((1-y)**(0.85))
slpScl = 0.45
muLlin = slpScl*Omga*4*(0.26-y)*step_down(y, 0.5, width)
muRlin = (slpScl*Omga*4*(0.74-y) + Omgb)*step_up(y, 0.5, width)
muR = muLtail + muRtail + muLlin + muRlin
return muR
[docs]
def graphite_1param_homog_2(self, y, Omga, Omgb):
""" Helper function """
width = 5e-2
tailScl = 5e-2
slpScl = 0.45
muLtail = -tailScl*1./(y**(0.85))
muRtail = tailScl*1./((1-y)**(0.85))
muLlin = (slpScl*Omga*12*(0.40-y)
* step_down(y, 0.49, 0.9*width)*step_up(y, 0.35, width))
muRlin = (slpScl*Omga*4*(0.74-y) + Omgb)*step_up(y, 0.5, width)
muLMod = (0.
+ 40*(-np.exp(-y/0.015))
+ 0.75*(np.tanh((y-0.17)/0.02) - 1)
+ 1.0*(np.tanh((y-0.22)/0.040) - 1)
)*step_down(y, 0.35, width)
muR = muLMod + muLtail + muRtail + muLlin + muRlin
return muR
[docs]
def graphite_1param_homog_3(self, y, Omga, Omgb):
""" Helper function with low hysteresis and soft tail """
width = 5e-2
tailScl = 5e-2
muLtail = -tailScl*1./(y**(0.85))
muRtail = tailScl*1./((1-y)**(0.85))
muRtail = 1.0e1*step_up(y, 1.0, 0.045)
muLlin = (0.15*Omga*12*(0.40-y**0.98)
* step_down(y, 0.49, 0.9*width)*step_up(y, 0.35, width))
muRlin = (0.1*Omga*4*(0.74-y) + 0.90*Omgb)*step_up(y, 0.5, 0.4*width)
muLMod = (0.
+ 40*(-np.exp(-y/0.015))
+ 0.75*(np.tanh((y-0.17)/0.02) - 1)
+ 1.0*(np.tanh((y-0.22)/0.040) - 1)
)*step_down(y, 0.35, width)
muR = 0.18 + muLMod + muLtail + muRtail + muLlin + muRlin
return muR
[docs]
def non_homog_rect_fixed_csurf(self, y, ybar, B, kappa, ywet):
""" Helper function """
N = len(y)
ytmp = np.empty(N+2, dtype=object)
ytmp[1:-1] = y
ytmp[0] = ywet
ytmp[-1] = ywet
dxs = 1./N
curv = np.diff(ytmp, 2)/(dxs**2)
muR_nh = -kappa*curv + B*(y - ybar)
return muR_nh
[docs]
def non_homog_rect_variational(self, y, ybar, B, kappa):
""" Helper function """
# the taylor expansion at the edges is used
N_2 = len(y)
ytmp = np.empty(N_2+2, dtype=object)
dxs = 1./N_2
ytmp[1:-1] = y
ytmp[0] = y[0] + np.diff(y)[0]*dxs + 0.5*np.diff(y,2)[0]*dxs**2
ytmp[-1] = y[-1] + np.diff(y)[-1]*dxs + 0.5*np.diff(y,2)[-1]*dxs**2
curv = np.diff(ytmp, 2)/(dxs**2)
muR_nh = -kappa*curv + B*(y - ybar)
return muR_nh
[docs]
def non_homog_round_wetting(self, y, ybar, B, kappa, beta_s, shape, r_vec):
""" Helper function """
dr = r_vec[1] - r_vec[0]
Rs = 1.
curv = geo.calc_curv(y, dr, r_vec, Rs, beta_s, shape)
muR_nh = B*(y - ybar) - kappa*curv
return muR_nh
[docs]
def general_non_homog(self, y, ybar):
""" Helper function """
ptype = self.get_trode_param("type")
mod1var, mod2var = False, False
if isinstance(y, np.ndarray):
mod1var = True
N = len(y)
elif (isinstance(y, tuple) and len(y) == 2
and isinstance(y[0], np.ndarray)):
mod2var = True
N = len(y[0])
else:
raise Exception("Unknown input type")
if ("homog" not in ptype) and (N > 1):
shape = self.get_trode_param("shape")
if shape == "C3":
if mod1var:
kappa = self.get_trode_param("kappa")
B = self.get_trode_param("B")
if self.get_trode_param("type") in ["ACR"]:
cwet = self.get_trode_param("cwet")
muR_nh = self.non_homog_rect_fixed_csurf(
y, ybar, B, kappa, cwet)
elif self.get_trode_param("type") in ["ACR_Diff"]:
muR_nh = self.non_homog_rect_variational(
y, ybar, B, kappa)
elif mod2var:
kappa = self.get_trode_param("kappa")
B = self.get_trode_param("B")
cwet = self.get_trode_param("cwet")
muR_nh = self.non_homog_rect_fixed_csurf(
y, ybar, B, kappa, cwet)
elif shape in ["cylinder", "sphere"]:
kappa = self.get_trode_param("kappa")
B = self.get_trode_param("B")
beta_s = self.get_trode_param("beta_s")
r_vec = geo.get_unit_solid_discr(shape, N)[0]
if mod1var:
muR_nh = self.non_homog_round_wetting(
y, ybar, B, kappa, beta_s, shape, r_vec)
elif mod2var:
muR1_nh = self.non_homog_round_wetting(
y[0], ybar[0], B, kappa, beta_s, shape, r_vec)
muR2_nh = self.non_homog_round_wetting(
y[1], ybar[1], B, kappa, beta_s, shape, r_vec)
muR_nh = (muR1_nh, muR2_nh)
else: # homogeneous particle
if mod1var:
muR_nh = 0*y
elif mod2var:
muR_nh = (0*y[0], 0*y[1])
return muR_nh
[docs]
def step_down(x, xc, delta):
return 0.5*(-np.tanh((x - xc)/delta) + 1)
[docs]
def step_up(x, xc, delta):
return 0.5*(np.tanh((x - xc)/delta) + 1)