Source code for mpet.props_am

"""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)