#
# Copyright (c) 2023 CIDETEC Energy Storage.
#
# This file is part of cideMOD.
#
# cideMOD is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import dolfinx as dfx
from petsc4py.PETSc import ScalarType
from collections import namedtuple
from cideMOD.helpers.logging import VerbosityLevel, _print
from cideMOD.numerics.fem_handler import BlockFunction, _evaluate_parameter, _max, _min
from cideMOD.numerics.time_scheme import TimeScheme
from cideMOD.cell.parser import CellParser
from cideMOD.cell.components import BatteryCell, BaseCellComponent
from cideMOD.cell.variables import ProblemVariables
from cideMOD.cell.dimensional_analysis import DimensionalAnalysis
from cideMOD.models.PXD.base_model import BasePXDModelPreprocessing
[docs]
class ElectrochemicalModelPreprocessing(BasePXDModelPreprocessing):
"""
Base mixin class that contains the mandatory methods to be overrided
related to the preprocessing of the model inputs.
"""
# ******************************************************************************************* #
# *** Problem *** #
# ******************************************************************************************* #
[docs]
def set_state_variables(self, state_vars: list, mesher, V, V_vec, problem) -> None:
"""
This method sets the state variables of the electrochemical
model.
Parameters
----------
state_vars : List(Tuple(str, numpy.ndarray, dolfinx.fem.FunctionSpace))
List of tuples, each one containing the name, the
subdomain and the function space of the state variable.
mesher : BaseMesher
Object that contains the mesh information.
V : dolfinx.fem.FunctionSpace
Common FunctionSpace to be used for each model.
V_vec : dolfinx.fem.VectorFunctionSpace
Common VectorFunctionSpace to be used for each model.
Examples
--------
>>> res = mesher.get_restrictions()
>>> state_vars.append(('new_var', res.electrolyte, V.clone()))
"""
res = mesher.get_restrictions()
state_vars.append(('c_e', V.clone(), res.electrolyte))
state_vars.append(('phi_e', V.clone(), res.electrolyte))
state_vars.append(('phi_s', V.clone(), res.electrodes))
if problem.cell_parser.has_collectors:
state_vars.append(('phi_s_cc', V.clone(), res.current_collectors))
state_vars.append(('lm_phi_s', V.clone(), res.electrode_cc_facets))
state_vars.append(('lm_app', V.clone(), res.positive_tab))
state_vars.extend([(f'j_Li_a{i}', V.clone(), res.anode)
for i in range(problem.cell_parser.anode.n_mat)])
state_vars.extend([(f'j_Li_c{i}', V.clone(), res.cathode)
for i in range(problem.cell_parser.cathode.n_mat)])
state_var_names = [var_name for var_name, _, _ in state_vars]
self._state_vars = state_var_names[state_var_names.index('c_e'):] # To be used later
[docs]
def set_problem_variables(self, var: ProblemVariables, DT: TimeScheme, problem) -> None:
"""
This method sets the problem variables of the electrochemical
model.
Parameters
----------
var: ProblemVariables
Object that store the preprocessed problem variables.
DT: TimeScheme
Object that provide the temporal derivatives with the
specified scheme.
problem: Problem
Object that handles the battery cell simulation.
Notes
-----
This method is called within :class:`ProblemVariables` right
after setting up the state variables and before the
:class:`BatteryCell` is created. In this class is meant to
create the control variables and those ones that will help
cell parameters preprocessing.
"""
# Temperature
if 'temp' not in problem._f_1.var_names:
var.temp = problem.T_ini
# Number of active materials
var.n_mat_a = problem.cell_parser.anode.n_mat
var.n_mat_c = problem.cell_parser.cathode.n_mat
# Control variables
var.i_app = dfx.fem.Constant(problem.mesher.mesh, ScalarType(0))
var.switch = dfx.fem.Constant(problem.mesher.mesh, ScalarType(0))
var.v_app = dfx.fem.Constant(problem.mesher.mesh, ScalarType(0))
[docs]
def set_dependent_variables(self, var: ProblemVariables,
cell: BatteryCell, DT: TimeScheme, problem):
"""
This method sets the dependent variables of the electrochemical
model.
Parameters
----------
var: ProblemVariables
Object that store the preprocessed problem variables.
cell: BatteryCell
Object where cell parameters are preprocessed and stored.
DT: TimeScheme
Object that provide the temporal derivatives with the
specified scheme.
problem: Problem
Object that handles the battery cell simulation.
"""
a, s, c, e = cell.anode, cell.separator, cell.cathode, cell.electrolyte
if cell.has_collectors:
ncc, pcc = cell.negativeCC, cell.positiveCC
# Overpotential
var.overpotential_a = []
for i, am in enumerate(a.active_materials):
x_am = var.x_a_surf[i]
ocv_a = am.U(x_am, var.i_app)
if am.delta_S_was_provided:
ocv_a += am.delta_S(x_am, var.i_app) * (var.temp - am.U_T_ref)
var.overpotential_a.append(var.phi_s - var.phi_e - ocv_a)
var.overpotential_c = []
for i, am in enumerate(c.active_materials):
x_am = var.x_c_surf[i]
ocv_c = am.U(x_am, -var.i_app)
if am.delta_S_was_provided:
ocv_c += am.delta_S(x_am, -var.i_app) * (var.temp - am.U_T_ref)
var.overpotential_c.append(var.phi_s - var.phi_e - ocv_c)
# build j_Li
var._J_Li = namedtuple('J_Li', ['total', 'int', 'LLI', 'C_dl', 'total_0'])
for electrode in [a, c]:
j_Li = var._J_Li._make([list() for _ in range(len(var._J_Li._fields))])
setattr(var, f'j_Li_{electrode.label}', j_Li)
for idx, am in enumerate(electrode.active_materials):
# Intercalation/Deintercalation
j_Li.int.append(var.f_1(f'j_Li_{electrode.label}{idx}'))
# Double layer capacitance
j_Li.C_dl.append(
electrode.C_dl * DT.dt(var.f_0.phi_s - var.f_0.phi_e, var.phi_s - var.phi_e)
if electrode.C_dl else 0)
# Lost of Lithium Inventory
j_Li.LLI.append(0)
# Total Li flux
j_Li.total_0.append(j_Li.int[idx] + j_Li.LLI[idx]) # To be used inside wf 0
j_Li.total.append(j_Li.int[idx] + j_Li.LLI[idx] + j_Li.C_dl[idx])
# build j_Li term = sum(j_Li * a_s)
for electrode, j_Li in zip([a, c], [var.j_Li_a, var.j_Li_c]):
j_Li_term = []
for ff, field in enumerate(j_Li._fields):
j_Li_term.append(0)
for i, am in enumerate(electrode.active_materials):
j_Li_term[ff] += j_Li._asdict()[field][i] * am.a_s
setattr(var, f'j_Li_{electrode.label}_term', var._J_Li._make(j_Li_term))
# Ionic_current
var.ionic_current_a = (
- a.kappa * a.grad(var.phi_e)
+ 2 * a.kappa * cell.R * var.temp / cell.F * (1 - e.t_p)
* e.activity * a.grad(var.c_e) / var.c_e
)
var.ionic_current_s = (
- s.kappa * s.grad(var.phi_e)
+ 2 * s.kappa * cell.R * var.temp / cell.F * (1 - e.t_p)
* e.activity * s.grad(var.c_e) / var.c_e
)
var.ionic_current_c = (
- c.kappa * c.grad(var.phi_e)
+ 2 * c.kappa * cell.R * var.temp / cell.F * (1 - e.t_p)
* e.activity * c.grad(var.c_e) / var.c_e
)
# Electronic_current
var.electric_current_a = - a.sigma * a.grad(var.phi_s)
var.electric_current_c = - c.sigma * c.grad(var.phi_s)
if cell.has_collectors:
var.electric_current_ncc = - ncc.sigma * ncc.grad(var.phi_s_cc)
var.electric_current_pcc = - pcc.sigma * pcc.grad(var.phi_s_cc)
# Li_ion_flux
var.li_ion_flux_diffusion_a = - a.D_e * a.grad(var.c_e)
var.li_ion_flux_diffusion_c = - c.D_e * c.grad(var.c_e)
var.li_ion_flux_diffusion_s = - s.D_e * s.grad(var.c_e)
var.li_ion_flux_migration_a = e.t_p / cell.F * var.ionic_current_a
var.li_ion_flux_migration_c = e.t_p / cell.F * var.ionic_current_c
var.li_ion_flux_migration_s = e.t_p / cell.F * var.ionic_current_s
var.li_ion_flux_a = var.li_ion_flux_diffusion_a + var.li_ion_flux_migration_a
var.li_ion_flux_c = var.li_ion_flux_diffusion_c + var.li_ion_flux_migration_c
var.li_ion_flux_s = var.li_ion_flux_diffusion_s + var.li_ion_flux_migration_s
[docs]
def initial_guess(self, f: BlockFunction, var: ProblemVariables,
cell: BatteryCell, problem) -> None:
"""
This method initializes the state variables based on the initial
conditions and assuming that the simulation begins after a
stationary state.
Parameters
----------
f: BlockFunction
Block function that contain the state variables to be
initialized.
var: ProblemVariables
Object that store the preprocessed problem variables.
cell: BatteryCell
Object where cell parameters are preprocessed and stored.
problem: Problem
Object that handles the battery cell simulation.
"""
P1_map = problem.P1_map
# c_e initial
c_e_ini = cell.electrolyte.c_e_ini
P1_map.interpolate(
{'anode': c_e_ini, 'separator': c_e_ini, 'cathode': c_e_ini}, f.c_e)
# phi_s initial
# - First OCV of each material is calculated according with their initial concentrations
U_a_ini = [am.ref_U([_evaluate_parameter(am.c_s_ini / am.c_s_max)])
for am in cell.anode.active_materials]
U_c_ini = [am.ref_U([_evaluate_parameter(am.c_s_ini / am.c_s_max)])
for am in cell.cathode.active_materials]
# - Then the largest or lowest is selected to avoid overcharge/underdischarge
if round(problem.SoC_ini) == 1:
phi_s_a = max(U_a_ini)[0] if U_a_ini else 0
phi_s_c = min(U_c_ini)[0] if U_c_ini else 0
else:
phi_s_a = min(U_a_ini)[0] if U_a_ini else 0
phi_s_c = max(U_c_ini)[0] if U_c_ini else 0
# - Finally the values are incorporated in the Function
# NOTE: phi_s_a = phi_s_ncc = 0 (already initialized to 0)
P1_map.interpolate({'cathode': phi_s_c - phi_s_a}, f.phi_s)
if cell.has_collectors:
P1_map.interpolate({'positiveCC': phi_s_c - phi_s_a}, f.phi_s_cc)
# NOTE: phi_s, phi_e and j_Li will be adjusted when solving the stationary problem
# Update control variables to stationary problem
var.switch.value = 0
var.i_app.value = 0
var.v_app.value = 0 # self.get_voltage()
[docs]
def setup(self, problem):
"""
This method setup the electrochemical model.
Parameters
----------
problem: Problem
Object that handles the battery cell simulation.
"""
self.Q_out = 0
[docs]
def update_control_variables(self, var: ProblemVariables, problem, i_app=30.0, v_app=None):
"""
This method updates the control variables of the electrochemical
model. Either CC and CV are supported. Varying current and
voltage is supported via an expression dependant of the time.
Parameters
----------
var: ProblemVariables
Object that store the preprocessed problem variables.
problem: Problem
Object that handles the battery cell simulation.
i_app : Union[float,str], optional
The applied current in Amperes. If CV use None.
Default to 30.
v_app : Union[float,str], optional
The applied voltage in Volts. If CC use None.
Default to None.
"""
# TODO: Improve the efficiency of this, just peform the checks once for each
# Problem.solve call
if i_app is not None:
if not isinstance(i_app, (str, int, float)):
raise TypeError("'i_app' must be one of (str, int, float, None)")
elif isinstance(i_app, str):
raise NotImplementedError("'i_app' cannot be an expression yet")
# i_app = 0 # Check how to proceed if the I varies...
else:
i_app = i_app
if v_app is not None:
if not isinstance(v_app, (str, int, float)):
raise TypeError("'v_app' must be one of (str, int, float, None)")
elif isinstance(v_app, str):
raise NotImplementedError("'v_app' cannot be an expression yet")
# v_app = 0
# self.v_0 = problem.get_voltage()
else:
v_app = v_app
self._running_mode(var, i_app, v_app)
def _running_mode(self, var, i_app, v_app):
if None not in (i_app, v_app):
raise ValueError("Can only input either an applied current 'i_app' or an applied "
+ "voltage 'v_app', but not both")
elif i_app is None and v_app is None:
raise ValueError("Need to input either an applied current 'i_app' or an applied "
+ "voltage 'v_app'.")
elif i_app is None:
self._set_voltage(var, v_app)
else:
self._set_current(var, i_app)
def _set_voltage(self, var, v_app):
"""
Set voltage in voltios (V)
Parameters
----------
v_app : float, optional
Voltage in voltios (V)
"""
var.switch.value = 1
var.v_app.value = v_app
def _set_current(self, var, i_app):
"""
Set current in Amperes (A)
Parameters
----------
i : float
Current in Amperes (A)
capacity: float
Cell capacity
"""
var.switch.value = 0
var.i_app.value = i_app
# ******************************************************************************************* #
# *** DimensionalAnalysis *** #
# ******************************************************************************************* #
# ******************************************************************************************* #
# *** BatteryCell *** #
# ******************************************************************************************* #
def _set_component_parameters(self, component: BaseCellComponent, problem) -> None:
"""
This method preprocesses the geometric parameters of the cell
component.
Parameters
----------
component: BaseCellComponent
Object where component parameters are preprocessed and
stored.
problem: Problem
Object that handles the battery cell simulation.
"""
mesher = problem.mesher
component.L = component.parser.thickness.get_value(problem)
component.H = component.parser.height.get_value(problem)
component.W = component.parser.width.get_value(problem)
component.area = component.parser.area.get_value(problem)
component.grad = mesher.get_component_gradient(component.tag, component.L,
component.H, component.W,
problem.model_options.dimensionless)
# d = problem.mesher.get_measures()
# for name, measure in d._asdict().items():
# if len(name) > 2 and component._label_ in name[2:]:
# setattr(component, f'd{name}', measure)
def _set_porous_component_parameters(self, component: BaseCellComponent, problem) -> None:
"""
This method preprocesses the common parameters of the porous
components.
Parameters
----------
component: BaseCellComponent
Object where porous component parameters are preprocessed
and stored.
problem: Problem
Object that handles the battery cell simulation.
"""
parser = component.parser
cell = component.cell
electrolyte = cell.electrolyte
T = problem._vars.temp
vars_dic = {'t_p': electrolyte.t_p, 'T_0': problem.T_ini, 'temp': T}
# NOTE: 'temp' is added just in case the temperature is not a state variable.
# Preprocess common porous component parameters
self._set_component_parameters(component, problem)
component.eps_e = parser.porosity.get_value(problem)
component.bruggeman = parser.bruggeman.get_value(problem)
component.tortuosity_e = parser.tortuosity_e.get_value(problem)
component.tortuosity_s = parser.tortuosity_s.get_value(problem)
component.D_e = parser.D_e.get_value(
problem, eps=component.eps_e, brug=component.bruggeman,
tau=component.tortuosity_e, R=cell.R, **vars_dic)
component.kappa = parser.kappa.get_value(
problem, eps=component.eps_e, brug=component.bruggeman,
tau=component.tortuosity_e, R=cell.R, **vars_dic)
parser.kappa_D.set_value((- 2 * cell.R / cell.F) * (1 - electrolyte.t_p)
* T * component.kappa * electrolyte.activity)
component.kappa_D = parser.kappa_D.get_value()
[docs]
def set_cell_parameters(self, cell: BatteryCell, problem) -> None:
"""
This method preprocesses the cell parameters of the
electrochemical model.
Parameters
----------
cell: BatteryCell
Object where cell parameters are preprocessed and stored.
problem: Problem
Object that handles the battery cell simulation.
"""
# NOTE: A flag that was created when setting up CellParser components is added here.
cell.has_collectors = cell.parser.has_collectors
# Constants
cell.R = cell.parser.R.get_value(problem)
cell.F = cell.parser.F.get_value(problem)
cell.C_dl_cc = cell.parser.doubleLayerCapacitance_cc.get_value(problem)
[docs]
def set_electrode_parameters(self, electrode: BaseCellComponent, problem) -> None:
"""
This method preprocesses the electrode parameters of the
electrochemical model.
Parameters
----------
electrode: BaseCellComponent
Object where electrode parameters are preprocessed and
stored.
problem: Problem
Object that handles the battery cell simulation.
"""
parser = electrode.parser
cell = electrode.cell
electrolyte = cell.electrolyte
T = problem._vars.temp
vars_dic = {'t_p': electrolyte.t_p, 'T_0': problem.T_ini, 'temp': T}
# NOTE: 'temp' is added just in case the temperature is not a state variable.
# Preprocess electrode parameters
self._set_porous_component_parameters(electrode, problem)
electrode.type = parser.type.get_value(problem)
electrode.rho = parser.density.get_value(problem)
electrode.C_dl = parser.double_layer_capacitance.get_value(problem)
eps_s = sum([am.volume_fraction.get_value(problem) for am in parser.active_materials])
electrode.sigma = parser.electronic_conductivity.get_value(
problem, eps=eps_s, brug=1.5, tau=electrode.tortuosity_s,
R=cell.R, **vars_dic)
[docs]
def set_active_material_parameters(self, am: BaseCellComponent, problem) -> None:
"""
This method preprocesses the active material parameters of the
electrochemical model.
Parameters
----------
am: BaseCellComponent
Object where active material parameters are preprocessed and
stored.
problem: Problem
Object that handles the battery cell simulation.
"""
T = problem._vars.temp
SoC_ini = problem.SoC_ini
bruggeman = 1.5
c_e_ini = am.cell.electrolyte.c_e_ini
am.index = am.parser.index
am.R_s = am.parser.particle_radius.get_value(problem)
am.eps_s = am.parser.volume_fraction.get_value(problem)
am.porosity = am.parser.porosity.get_value(problem)
vars_dic = {'y': problem._vars(f'x_{am.electrode.label}_surf')[am.index], 'temp': T}
am.k_0 = am.parser.kinetic_constant.get_value(problem, R=am.cell.R, **vars_dic)
am.alpha = am.parser.alpha.get_value(problem)
am.c_s_max = am.parser.maximum_concentration.get_value(problem)
am.stoichiometry0 = am.parser.stoichiometry0.get_value(problem)
am.stoichiometry1 = am.parser.stoichiometry1.get_value(problem)
am.c_s_ini = am.c_s_max * (SoC_ini * (am.stoichiometry1 - am.stoichiometry0)
+ am.stoichiometry0)
am.parser.a_s.set_value(3. * am.eps_s / am.R_s)
if not am.parser.tortuosity_s.was_provided:
am.parser.tortuosity_s.set_value(am.eps_s ** (1 - bruggeman))
am.a_s = am.parser.a_s.get_value()
am.tortuosity_s = am.parser.tortuosity_s.get_value()
am.U = am.parser.ocp.get_value(problem, temp=T)
am.U_T_ref = am.parser.ocp.T_ref # NOTE: It could be None
am.ref_U = am.parser.ocp.get_reference_value(
temp=problem.T_ini, c_e=c_e_ini)
am.delta_S = am.parser.entropy_coefficient.get_value(problem, temp=T)
am.delta_S_was_provided = am.parser.entropy_coefficient.was_provided
am.ref_delta_S = am.parser.entropy_coefficient.get_reference_value(
temp=problem.T_ini, c_e=c_e_ini)
[docs]
def set_separator_parameters(self, separator, problem) -> None:
"""
This method preprocesses the separator parameters of the
electrochemical model.
Parameters
----------
separator: BaseCellComponent
Object where separator parameters are preprocessed and
stored.
problem: Problem
Object that handles the battery cell simulation.
"""
self._set_porous_component_parameters(separator, problem)
separator.type = separator.parser.type.get_value()
separator.rho = separator.parser.density.get_value(problem)
[docs]
def set_current_collector_parameters(self, cc, problem) -> None:
"""
This method preprocesses the current collector parameters of the
electrochemical model.
Parameters
----------
cc: BaseCellComponent
Object where current collector parameters are preprocessed
and stored.
problem: Problem
Object that handles the battery cell simulation.
"""
self._set_component_parameters(cc, problem)
cc.type = cc.parser.type.get_value()
cc.rho = cc.parser.density.get_value(problem)
cc.sigma = cc.parser.electronic_conductivity.get_value(problem)
[docs]
def set_electrolyte_parameters(self, electrolyte, problem) -> None:
"""
This method preprocesses the electrolyte parameters of the
electrochemical model.
Parameters
----------
electrolyte: BaseCellComponent
Object where electrolyte parameters are preprocessed and
stored.
problem: Problem
Object that handles the battery cell simulation.
"""
electrolyte.type = electrolyte.parser.type.get_value()
electrolyte.c_e_ini = electrolyte.parser.initial_concentration.get_value(problem)
electrolyte.t_p = electrolyte.parser.transference_number.get_value(problem)
# Build nonlinear properties
# NOTE: 'temp' is added just in case the temperature is not a state variable.
vars_dic = {'t_p': electrolyte.t_p, 'T_0': problem.T_ini, 'temp': problem._vars.temp}
electrolyte.D_e = electrolyte.parser.diffusion_constant.get_value(
problem, R=electrolyte.cell.R, **vars_dic)
electrolyte.kappa = electrolyte.parser.ionic_conductivity.get_value(
problem, R=electrolyte.cell.R, **vars_dic)
electrolyte.activity = electrolyte.parser.activity_dependence.get_value(
problem, **vars_dic)
[docs]
def compute_cell_properties(self, cell: BatteryCell):
"""
This method computes the general cell properties of the
electrochemical model.
Parameters
----------
cell: BatteryCell
Object where cell parameters are preprocessed and stored.
Notes
-----
This method is called once the cell parameters has been
preprocessed.
"""
cell.anode.capacity = self._get_electrode_capacity(cell.anode)
cell.cathode.capacity = self._get_electrode_capacity(cell.cathode)
cell.capacity = _min(cell.anode.capacity or 9e99, cell.cathode.capacity or 9e99)
cell.area = _min(cell.anode.area or 9e99, cell.cathode.area or 9e99)
components = [v for k, v in cell._components_.items() if k != 'electrolyte']
if any([element.H for element in components]):
cell.H = _max([element.H for element in components if element.H])
else:
cell.H = None
if any([element.W for element in components]):
cell.W = _max([element.W for element in components if element.W])
else:
cell.W = None
[docs]
def update_reference_values(self, updated_values: dict,
cell_parser: CellParser, problem=None) -> None:
"""
This method updates the reference cell cell properties of the
electrochemical model.
Parameters
----------
updated_values: Dict[str, float]
Dictionary containing the cell parameters that have already
been updated.
cell_parser: CellParser
Parser of the cell dictionary.
problem: Problem, optional
Object that handles the battery cell simulation.
Notes
-----
This method is called each time a set of dynamic parameters have
been updated. If problem is not given, then it is assumed that
it have not been already defined.
"""
# <*> Update reference cell properties
# NOTE: Update cell properties, no matter which dynamic parameters have been updated.
# In case this update operations are really slow, then perform some checks before.
# NOTE: Here we know this parameters do not need further preprocessing like arrhenius or
# bruggeman and thats why we use the values provided by the user directly.
if problem is None or not problem._ready:
self.compute_reference_cell_properties(cell_parser)
return
cell = problem.cell
DA = problem._DA
cell_parser.anode.ref_capacity = _evaluate_parameter(cell.anode.capacity)
cell_parser.cathode.ref_capacity = _evaluate_parameter(cell.cathode.capacity)
cell_parser.ref_capacity = min(cell_parser.anode.ref_capacity or 9e99,
cell_parser.cathode.ref_capacity or 9e99)
if cell_parser.verbose >= VerbosityLevel.BASIC_PROBLEM_INFO:
_print(f"Negative electrode capacity: {cell_parser.anode.ref_capacity:.6f}",
comm=cell_parser._comm)
_print(f"Positive electrode capacity: {cell_parser.cathode.ref_capacity :.6f}",
comm=cell_parser._comm)
_print(f"Cell capacity: {cell_parser.ref_capacity:.6f}", comm=cell_parser._comm)
# NOTE: If problem is not given, then it is assumed that it have not been already defined.
cell_parser.ref_area = _evaluate_parameter(cell.area)
cell_parser.ref_height = _evaluate_parameter(cell.H) if cell.H is not None else None
cell_parser.ref_width = _evaluate_parameter(cell.W) if cell.W is not None else None
# <*> Update dimensionless numbers reference values
# NOTE: In order to do this, take into account that there could be parameters that need
# further preproccessing. In addition, this reference parameters are included in the
# equations, so if they can vary, then they should have been defined as
# dolfinx.fem.Constant, despite the fact they are reference values.
# NOTE: What you have to do here is to recompute the reference dependent parameters that
# are not associated to a CellParameter (as these will be updated each time
# CellParameter.get_reference_value is called).
if DA.dimensionless:
raise NotImplementedError(
"Update dimensionless numbers reference values not implemented yet")
[docs]
def reset(self, problem, new_parameters=None, deep_reset=False) -> None:
"""
This method resets the problem variables related with the
electrochemical model in order to be ready for running another
simulation with the same initial conditions, and maybe using
different parameters.
Parameters
----------
problem: Problem
Object that handles the battery cell simulation.
new_parameters: Dict[str, float], optional
Dictionary containing the cell parameters that have already
been updated.
deep_reset: bool
Whether or not a deep reset will be performed. It means
that the Problem setup stage will be run again as the mesh
has been changed. Default to False.
"""
# Reset control variables if they have been already defined
if problem._ready and not deep_reset:
var = problem._vars
var.i_app.value = 0
var.v_app.value = 0
var.switch.value = 0
# Reset internal variables
if deep_reset:
self._T_ext = None
self._T_ini = None
else:
self.Q_out = 0.
def _get_electrode_capacity(self, electrode):
cap = 0
for am in electrode.active_materials:
cap += (am.eps_s * am.porosity * am.c_s_max
* abs(am.stoichiometry1 - am.stoichiometry0) / 3600)
for inc in am.inclusions:
cap += (am.eps_s * inc.eps_s * inc.c_s_max * inc.porosity
* abs(inc.stoichiometry1 - inc.stoichiometry0) / 3600)
return cap * electrode.area * electrode.L * electrode.cell.F