Skip to content

Commit

Permalink
Merge branch 'dev' into 37-simplified-equivalent-circuit-battery-model
Browse files Browse the repository at this point in the history
  • Loading branch information
teubert committed Nov 13, 2024
2 parents 36f331a + 74d74ce commit 6ef9eab
Show file tree
Hide file tree
Showing 4 changed files with 1,260 additions and 11 deletions.
301 changes: 300 additions & 1 deletion examples/04_New Models.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,305 @@
"To conclude, we have shown how to serialize models in ProgPy using both `pickle` and `JSON` methods. Understanding how to serialize and deserialize models can be a powerful tool for prognostics developers. It enables the saving of models to a disk and the re-loading of these models back into memory at a later time. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example - Simplified Battery Model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is an example of a somewhat more complicated model, in this case a battery. We will be implementing the simplified battery model introduced by Gina Sierra, et. al. (https://www.sciencedirect.com/science/article/pii/S0951832018301406)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we import the PrognosticsModel class. This is the parent class for all ProgPy Models"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from progpy import PrognosticsModel"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## State Transition\n",
"The first step to creating a physics-based model is implementing state transition. From the paper we see one state (SOC) and one state transition equation:\n",
"\n",
"$SOC(k+1) = SOC(k) - P(k)*\\Delta t * E_{crit}^{-1} + w_2(k)$\n",
"\n",
"where $k$ is discrete time. The $w$ term is process noise. This can be omitted, since it's handled by ProgPy. \n",
"\n",
"In this equation we see one input ($P$, power). Note that the previous battery model uses current, where this uses power.\n",
"\n",
"Armed with this information we can start defining our model. First, we start by declaring our inputs and states:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(PrognosticsModel):\n",
" inputs = ['P']\n",
" states = ['SOC']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we define parameters. In this case the parameters are the initial SOC state (1) and the E_crit (Internal Total Energy). We get the value for $E_{crit}$ from the paper.\n",
"\n",
"**Note: wont actually subclass in practice, but it's used to break apart model definition into chunks**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" default_parameters = {\n",
" 'E_crit': 202426.858, # Internal Total Energy\n",
" 'x0': {\n",
" 'SOC': 1, # State of Charge\n",
" }\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We know that SOC will always be between 0 and 1, so we can specify that explicitly."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" state_limits = {\n",
" 'SOC': (0.0, 1.0),\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we define the state transition equation. There are two methods for doing this: *dx* (for continuous) and *next_state* (for discrete). Today we're using the $dx$ function. This was selected because the model is continuous."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" def dx(self, x, u):\n",
" return self.StateContainer({'SOC': -u['P']/self['E_crit']})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Outputs\n",
"\n",
"Now that state transition is defined, the next step is to define the outputs of the function. From the paper we have the following output equations:\n",
"\n",
"$v(k) = v_{oc}(k) - i(k) * R_{int} + \\eta (k)$\n",
"\n",
"where\n",
"\n",
"$v_{oc}(k) = v_L - \\lambda ^ {\\gamma * SOC(k)} - \\mu * e ^ {-\\beta * \\sqrt{SOC(k)}}$\n",
"\n",
"and\n",
"\n",
"$i(k) = \\frac{v_{oc}(k) - \\sqrt{v_{oc}(k)^2 - 4 * R_{int} * P(k)}}{2 * R_{int}(k)}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There is one output here (v, voltage), the same one input (P, Power), and a few lumped parameters: $v_L$, $\\lambda$, $\\gamma$, $\\mu$, $\\beta$, and $R_{int}$. The default parameters are found in the paper.\n",
"\n",
"Note that $\\eta$ is the measurement noise, which progpy handles, so that's omitted from the equation below.\n",
"\n",
"Note 2: There is a typo in the paper where the sign of the second term in the $v_{oc}$ term. It should be negative (like above), but is reported as positive in the paper.\n",
"\n",
"We can update the definition of the model to include this output and parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" outputs = ['v']\n",
"\n",
" default_parameters = {\n",
" 'E_crit': 202426.858,\n",
" 'v_L': 11.148,\n",
" 'lambda': 0.046,\n",
" 'gamma': 3.355,\n",
" 'mu': 2.759,\n",
" 'beta': 8.482,\n",
" 'R_int': 0.027,\n",
"\n",
" 'x0': {\n",
" 'SOC': 1,\n",
" }\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the input ($P(k)$) is also used in the output equation, that means it's part of the state of the system. So we will update the states to include this.\n",
"\n",
"**NOTE: WE CHANGE TO next_state.** Above, we define state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" states = ['SOC', 'P']\n",
"\n",
" def next_state(self, x, u, dt):\n",
" x['SOC'] = x['SOC'] - u['P'] * dt / self['E_crit']\n",
" x['P'] = u['P']\n",
"\n",
" return x"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Adding a default P state as well:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" default_parameters = {\n",
" 'E_crit': 202426.858,\n",
" 'v_L': 11.148,\n",
" 'lambda': 0.046,\n",
" 'gamma': 3.355,\n",
" 'mu': 2.759,\n",
" 'beta': 8.482,\n",
" 'R_int': 0.027,\n",
"\n",
" 'x0': {\n",
" 'SOC': 1,\n",
" 'P': 0.01 # Added P\n",
" }\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we're ready to define the output equations (defined above)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" def output(self, x):\n",
" v_oc = self['v_L'] - self['lambda']**(self['gamma']*x['SOC']) - self['mu'] * math.exp(-self['beta']* math.sqrt(x['SOC']))\n",
" i = (v_oc - math.sqrt(v_oc**2 - 4 * self['R_int'] * x['P']))/(2 * self['R_int'])\n",
" v = v_oc - i * self['R_int']\n",
" return self.OutputContainer({\n",
" 'v': v})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Events\n",
"Finally we can define events. This is an easy case because our event state (SOC) is part of the model state. So we will simply define a single event (EOD: End of Discharge), where SOC is progress towards that event."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" events = ['EOD']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then for our event state, we simply extract the relevant state"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n",
" def event_state(self, x):\n",
" return {'EOD': x['SOC']}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The threshold of the event is defined as the state where the event state (EOD) is 0.\n",
"\n",
"That's it. We've now defined a complete model. Now it's ready to be used for state estimation or prognostics, like any model distributed with ProgPy\n",
"\n",
"Note that this model can be extended by changing the parameters ecrit and r to steady states. This will help the model account for the effects of aging, since they will be estimated with each state estimation step."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1647,7 +1946,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
"version": "3.13.0"
},
"orig_nbformat": 4,
"vscode": {
Expand Down
Loading

0 comments on commit 6ef9eab

Please sign in to comment.