"""
Routines in this submodule contains all post-processing functions for the SPM
package.
"""
from typing import TypeVar
import numpy as np
import matplotlib.pyplot as plt
from ._solutions import BaseSolution
Solution = TypeVar('Solution', bound='BaseSolution')
[docs]
def post(soln: Solution) -> dict:
"""
Run post processing to determine secondary variables.
Parameters
----------
soln : Solution
A single particle model solution object.
Returns
-------
postvars : dict
Post processed variables, as described below.
========= ========================================================
Key Value [units] (*type*)
========= ========================================================
sdot_an anode Faradaic current at t [kmol/m2/s] (*1D array*)
sdot_ca cathode Faradaic current at t [kmol/m2/s] (*1D array*)
========= ========================================================
See Also
--------
~bmlite.SPM.solutions.StepSolution
~bmlite.SPM.solutions.CycleSolution
"""
from .dae import residuals
# Pull sim and exp from sol
sim = soln._sim
step = {
'mode': 'post',
'units': 'post',
'value': 'post',
}
# Extract desired variables for each time
sdot_an = np.zeros_like(soln.t)
sdot_ca = np.zeros_like(soln.t)
# Turn on output from residuals
for i, t in enumerate(soln.t):
sv, svdot = soln.y[i, :], soln.yp[i, :]
output = residuals(t, sv, svdot, np.zeros_like(sv), (sim, step))
sdot_an[i], sdot_ca[i] = output
# Store outputs
postvars = {
'sdot_an': sdot_an,
'sdot_ca': sdot_ca,
}
return postvars
def _solid_phase_Li(soln: Solution) -> np.array:
"""
Calculate the solid-phase lithium vs. time.
Parameters
----------
soln : Solution
A single particle model solution object.
Returns
-------
Li_ed_0 : float
Solid-phase lithium [kmol/m2] based on `an.x_0` and `ca.x_0`.
Li_ed_t : 1D array
Solution's solid-phase lithium [kmol/m2] vs. time [s].
See Also
--------
~bmlite.SPM.solutions.StepSolution
~bmlite.SPM.solutions.CycleSolution
"""
from ..mathutils import int_r
an, ca = soln._sim.an, soln._sim.ca
# Initial total solid-phase lithium [kmol/m2]
Li_ed_0 = an.x_0*an.Li_max*an.eps_AM*an.thick \
+ ca.x_0*ca.Li_max*ca.eps_AM*ca.thick
# Anode/cathode lithium [kmol/m2] vs. time [s]
V_an = 4.*np.pi*an.R_s**3 / 3.
V_ca = 4.*np.pi*ca.R_s**3 / 3.
Li_an = np.zeros_like(soln.t)
Li_ca = np.zeros_like(soln.t)
for i in range(soln.t.size):
Li_an[i] = an.thick*an.eps_AM / V_an \
* int_r(an.rm, an.rp, soln.vars['an']['cs'][i, :])
Li_ca[i] = ca.thick*ca.eps_AM / V_ca \
* int_r(ca.rm, ca.rp, soln.vars['ca']['cs'][i, :])
# Total solid-phase lithium [kmol/m2] vs. time [s]
Li_ed_t = Li_an + Li_ca
return Li_ed_0, Li_ed_t
[docs]
def potentials(soln: Solution) -> None:
"""
Plots anode, electrolyte, and cathode potentials vs. time.
Parameters
----------
soln : Solution
A single particle model solution object.
See Also
--------
~bmlite.SPM.solutions.StepSolution
~bmlite.SPM.solutions.CycleSolution
"""
from .._utils import ExitHandler
from ..plotutils import format_ticks
fig, ax = plt.subplots(nrows=1, ncols=3, figsize=[12, 3],
layout='constrained')
ax[0].set_ylabel(r'$\phi_{\rm an}$ [V]')
ax[1].set_ylabel(r'$\phi_{\rm ca}$ [V]')
ax[2].set_ylabel(r'$\phi_{\rm el}$ [V]')
ax[0].plot(soln.vars['time_s'], soln.vars['an']['phis'], '-C3')
ax[1].plot(soln.vars['time_s'], soln.vars['ca']['phis'], '-C2')
ax[2].plot(soln.vars['time_s'], soln.vars['el']['phie'], '-C0')
for i in range(3):
ax[i].set_xlabel(r'$t$ [s]')
format_ticks(ax[i])
fig.get_layout_engine().set(wspace=0.1)
if not plt.isinteractive():
ExitHandler.register_atexit(plt.show)
[docs]
def intercalation(soln: Solution) -> None:
"""
Plots anode and cathode particle intercalation profiles vs. time.
Parameters
----------
soln : Solution
A single particle model solution object.
See Also
--------
~bmlite.SPM.solutions.StepSolution
~bmlite.SPM.solutions.CycleSolution
"""
import matplotlib.colors as clrs
from .._utils import ExitHandler
from ..plotutils import format_ticks
# Pull sim and exp from sol
sim = soln._sim
# Break inputs into separate objects
an, ca = sim.an, sim.ca
# Pull time indices and setup colorbar
t_inds = np.ceil(np.linspace(0, soln.t.size - 1, 11)).astype(int)
cmap = plt.get_cmap('jet', len(t_inds))
norm = clrs.Normalize(vmin=soln.t.min(), vmax=soln.t.max())
sm = plt.cm.ScalarMappable(cmap='jet', norm=norm)
# Solid-phase Li intercalation fracs -- anode and cathode
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=[8, 3],
layout='constrained')
ax[0].set_ylabel(r'$X_{\rm Li}$ [$-$]')
ax[1].set_yticklabels([])
ax[0].text(0.1, 0.1, 'Anode particle', transform=ax[0].transAxes)
ax[1].text(0.1, 0.1, 'Cathode particle', transform=ax[1].transAxes)
for i, it in enumerate(t_inds):
Li_an = soln.vars['an']['xs'][it, :]
ax[0].plot(an.r*1e6, Li_an, color=cmap(i))
Li_ca = soln.vars['ca']['xs'][it, :]
ax[1].plot(ca.r*1e6, Li_ca, color=cmap(i))
cb = plt.colorbar(sm, ax=ax[1], ticks=soln.t[t_inds])
cb.set_label(r'$t$ [s]')
ax[0].set_xlim([0., an.R_s*1e6])
ax[1].set_xlim([0., ca.R_s*1e6])
for i in range(2):
ax[i].set_xlabel(r'$r$ [$\mu$m]')
ax[i].set_ylim([0., 1.05])
format_ticks(ax[i])
if not plt.isinteractive():
ExitHandler.register_atexit(plt.show)
[docs]
def pixels(soln: Solution) -> None:
"""
Makes pixel plots for most 2D (space/time) variables.
Parameters
----------
soln : SPM Solution object
A single particle model solution object.
See Also
--------
~bmlite.SPM.solutions.StepSolution
~bmlite.SPM.solutions.CycleSolution
"""
from ..plotutils import pixel
from .._utils import ExitHandler
# Get needed domains
an, ca = soln._sim.an, soln._sim.ca
# Make figure
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=[5.5, 3.25],
layout='constrained')
# Li concentrations in anode [kmol/m3]
xlims = [an.rm[0]*1e6, an.rp[-1]*1e6]
ylims = [soln.t.min(), soln.t.max()]
z = soln.vars['an']['cs']
pixel(ax[0], xlims, ylims, z, r'[kmol/m$^3$]')
ax[0].set_ylabel(r'$t$ [s]')
ax[0].set_xlabel(r'$r$ [$\mu$m]')
ax[0].set_title(r'$C_{\rm s, an}$')
# Li concentrations in cathode [kmol/m3]
xlims = [ca.rm[0]*1e6, ca.rp[-1]*1e6]
ylims = [soln.t.min(), soln.t.max()]
z = soln.vars['ca']['cs']
pixel(ax[1], xlims, ylims, z, r'[kmol/m$^3$]')
ax[1].set_yticks([])
ax[1].set_xlabel(r'$r$ [$\mu$m]')
ax[1].set_title(r'$C_{\rm s, ca}$')
# Adjust spacing
fig.get_layout_engine().set(hspace=0.1, wspace=0.1)
if not plt.isinteractive():
ExitHandler.register_atexit(plt.show)