#
# External circuit with an arbitrary function
#
import pybamm
from .base_external_circuit import BaseModel
[docs]
class FunctionControl(BaseModel):
"""
External circuit with an arbitrary function, implemented as a control on the current
either via an algebraic equation, or a differential equation.
Parameters
----------
param : parameter class
The parameters to use for this submodel
external_circuit_function : callable
The function that controls the current
options : dict
Dictionary of options to use for the submodel
control : str, optional
The type of control to use. Must be one of 'algebraic' (default)
or 'differential'.
"""
def __init__(self, param, external_circuit_function, options, control="algebraic"):
super().__init__(param, options)
self.external_circuit_function = external_circuit_function
self.control = control
[docs]
def get_fundamental_variables(self):
# Current is a variable
i_var = pybamm.Variable("Current variable [A]", scale=self.param.Q)
if self.control in ["algebraic", "differential"]:
I = i_var
elif self.control == "differential with max":
i_input = pybamm.FunctionParameter(
"CCCV current function [A]", {"Time [s]": pybamm.t}
)
I = pybamm.maximum(i_var, i_input)
# Update derived variables
i_cell = I / self.param.A_cc
variables = {
"Current variable [A]": i_var,
"Total current density [A.m-2]": i_cell,
"Current [A]": I,
"C-rate": I / self.param.Q,
}
if self.options.get("voltage as a state") == "true":
V = pybamm.Variable("Voltage [V]")
variables.update({"Voltage [V]": V})
return variables
[docs]
def set_initial_conditions(self, variables):
# Initial condition as a guess for consistent initial conditions
i_cell = variables["Current variable [A]"]
self.initial_conditions[i_cell] = self.param.Q
if self.options.get("voltage as a state") == "true":
V = variables["Voltage [V]"]
self.initial_conditions[V] = self.param.ocv_init
[docs]
def set_rhs(self, variables):
# External circuit submodels are always equations on the current
# The external circuit function should provide an update law for the current
# based on current/voltage/power/etc.
if "differential" in self.control:
i_cell = variables["Current variable [A]"]
self.rhs[i_cell] = self.external_circuit_function(variables)
[docs]
def set_algebraic(self, variables):
# External circuit submodels are always equations on the current
# The external circuit function should fix either the current, or the voltage,
# or a combination (e.g. I*V for power control)
if self.control == "algebraic":
i_cell = variables["Current variable [A]"]
self.algebraic[i_cell] = self.external_circuit_function(variables)
if self.options.get("voltage as a state") == "true":
V = variables["Voltage [V]"]
V_expression = variables["Voltage expression [V]"]
self.algebraic[V] = V - V_expression
[docs]
class VoltageFunctionControl(FunctionControl):
"""
External circuit with voltage control, implemented as an extra algebraic equation.
"""
def __init__(self, param, options):
super().__init__(param, self.constant_voltage, options, control="algebraic")
def constant_voltage(self, variables):
V = variables["Voltage [V]"]
return V - pybamm.FunctionParameter(
"Voltage function [V]", {"Time [s]": pybamm.t}
)
[docs]
class PowerFunctionControl(FunctionControl):
"""External circuit with power control."""
def __init__(self, param, options, control="algebraic"):
super().__init__(param, self.constant_power, options, control=control)
def constant_power(self, variables):
I = variables["Current [A]"]
V = variables["Voltage [V]"]
P = V * I
P_applied = pybamm.FunctionParameter(
"Power function [W]", {"Time [s]": pybamm.t}
)
if self.control == "algebraic":
return P - P_applied
else:
# Multiply by the time scale so that the overshoot only lasts a few seconds
K_P = 0.01
return -K_P * (P - P_applied)
[docs]
class ResistanceFunctionControl(FunctionControl):
"""External circuit with resistance control."""
def __init__(self, param, options, control="algebraic"):
super().__init__(param, self.constant_resistance, options, control=control)
def constant_resistance(self, variables):
I = variables["Current [A]"]
V = variables["Voltage [V]"]
R_applied = pybamm.FunctionParameter(
"Resistance function [Ohm]", {"Time [s]": pybamm.t}
)
if self.control == "algebraic":
return V - R_applied * I
else:
# Multiply by the time scale so that the overshoot only lasts a few seconds
K_R = 0.01
return -K_R * (V - R_applied * I)
class ExperimentFunctionControl(FunctionControl):
"""External circuit with one conditional control residual shared across steps."""
def __init__(self, param, options, step_index_input_name, step_control_builders):
self.step_index_input_name = step_index_input_name
self.step_control_builders = step_control_builders
super().__init__(
param,
self.conditional_control_residual,
options,
control="algebraic",
)
def conditional_control_residual(self, variables):
selector = pybamm.InputParameter(self.step_index_input_name)
return pybamm.Conditional(
selector,
*[builder(variables) for builder in self.step_control_builders],
)
[docs]
class CCCVFunctionControl(FunctionControl):
"""
External circuit with constant-current constant-voltage control, as implemented in
:footcite:t:`Mohtat2021`.
.. footbibliography::
"""
def __init__(self, param, options):
super().__init__(param, self.cccv, options, control="differential with max")
pybamm.citations.register("Mohtat2021")
def cccv(self, variables):
# Multiply by the time scale so that the votage overshoot only lasts a few
# seconds
K_aw = 1 # anti-windup
Q = self.param.Q
I_var = variables["Current variable [A]"]
I = variables["Current [A]"]
K_V = 1
V = variables["Voltage [V]"]
V_CCCV = pybamm.Parameter("Voltage function [V]")
return -K_aw / Q * (I_var - I) + K_V * (V - V_CCCV)