Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased](https://github.com/NatLabRockies/batmods-lite)

### New Features
- Add slow NMC811 and GraphiteSiOx with the ability to pass OCV data as an input ([#27](https://github.com/NatLabRockies/batmods-lite/pull/27))
- Remove `linspace` option for `tspan`, add auto timesteps as an option ([#26](https://github.com/NatLabRockies/batmods-lite/pull/26))
- Adding NMC811 and GraphiteSiOx material for BatFIT calibrations ([#22](https://github.com/NatLabRockies/batmods-lite/pull/22))
- Check bounds of intercalation fraction before calculating exchange current density ([#19](https://github.com/NatLabRockies/batmods-lite/pull/19))
Expand Down
4 changes: 2 additions & 2 deletions docs/source/examples/P2D_tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"\n",
"Building an `Experiment` is similar to programming a cycler. First, create an instance, then add steps to it. Steps require a mode, value, time span, and optional limits or solver arguments.\n",
"\n",
"The `tspan` argument can be input as a float, a tuple, or an array. The behavior for how the solution is saved depends on which input type is provided. If you provide a float, it represents `tmax` for that step and tells the solver to select which time steps to save while integrating. A tuple is interpretted as `(tmax, dt)` and will save the solution in time increments specified by `dt`. For full control over where to save the solution, you can also provide any monotonically increasing numpy array. However, the array must start with zero and be more than two values in length. Arrays like `np.array([0, tmax])` will have the same behavior as simply providing `tmax`, i.e., the solver will control where to save the solution."
"The `tspan` argument can be input as a float, a tuple, or an array. The behavior for how the solution is saved depends on which input type is provided. If you provide a float, it represents `tmax` for that step and tells the solver to select which time steps to save while integrating. A tuple is interpreted as `(tmax, dt)` and will save the solution in time increments specified by `dt`. For full control over where to save the solution, you can also provide any monotonically increasing numpy array. However, the array must start with zero and be more than two values in length. Arrays like `np.array([0, tmax])` will have the same behavior as simply providing `tmax`, i.e., the solver will control where to save the solution."
]
},
{
Expand Down Expand Up @@ -231,7 +231,7 @@
"source": [
"From the output above you can see that the first step was successful, how long it took to solve, and some additional details. Generally, running step-by-step can be valuable when you need to pause between steps to perform some analysis, inform later steps, or to help refine solver options for a given step.\n",
"\n",
"There are a couple important thing to understands about how a `Simulation` runs experiments. When you first initialize a `Simulation` instance, the internal state is set to an equalibrium condition based on the initial intercalation fractions you defined in your `.yaml` file. When you run using `run_step`, the internal state of the `Simulation` is saved at the end of each step. This allows the next step to pick up from where the last step left off. If at any time you want to reset the state of your simulation back to the original initial equilibrium condition, you can use the `pre` method, which will re-run the preprocessing steps to re-initialize the instance. Since we will demonstrate the `run` method below using the same `sim` instance created above, we will use this approach to reset the simulation."
"There are a couple important thing to understands about how a `Simulation` runs experiments. When you first initialize a `Simulation` instance, the internal state is set to an equilibrium condition based on the initial intercalation fractions you defined in your `.yaml` file. When you run using `run_step`, the internal state of the `Simulation` is saved at the end of each step. This allows the next step to pick up from where the last step left off. If at any time you want to reset the state of your simulation back to the original initial equilibrium condition, you can use the `pre` method, which will re-run the preprocessing steps to re-initialize the instance. Since we will demonstrate the `run` method below using the same `sim` instance created above, we will use this approach to reset the simulation."
]
},
{
Expand Down
4 changes: 2 additions & 2 deletions docs/source/examples/SPM_tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"\n",
"Building an `Experiment` is similar to programming a cycler. First, create an instance, then add steps to it. Steps require a mode, value, time span, and optional limits or solver arguments.\n",
"\n",
"The `tspan` argument can be input as a float, a tuple, or an array. The behavior for how the solution is saved depends on which input type is provided. If you provide a float, it represents `tmax` for that step and tells the solver to select which time steps to save while integrating. A tuple is interpretted as `(tmax, dt)` and will save the solution in time increments specified by `dt`. For full control over where to save the solution, you can also provide any monotonically increasing numpy array. However, the array must start with zero and be more than two values in length. Arrays like `np.array([0, tmax])` will have the same behavior as simply providing `tmax`, i.e., the solver will control where to save the solution."
"The `tspan` argument can be input as a float, a tuple, or an array. The behavior for how the solution is saved depends on which input type is provided. If you provide a float, it represents `tmax` for that step and tells the solver to select which time steps to save while integrating. A tuple is interpreted as `(tmax, dt)` and will save the solution in time increments specified by `dt`. For full control over where to save the solution, you can also provide any monotonically increasing numpy array. However, the array must start with zero and be more than two values in length. Arrays like `np.array([0, tmax])` will have the same behavior as simply providing `tmax`, i.e., the solver will control where to save the solution."
]
},
{
Expand Down Expand Up @@ -231,7 +231,7 @@
"source": [
"From the output above you can see that the first step was successful, how long it took to solve, and some additional details. Generally, running step-by-step can be valuable when you need to pause between steps to perform some analysis, inform later steps, or to help refine solver options for a given step.\n",
"\n",
"There are a couple important thing to understands about how a `Simulation` runs experiments. When you first initialize a `Simulation` instance, the internal state is set to an equalibrium condition based on the initial intercalation fractions you defined in your `.yaml` file. When you run using `run_step`, the internal state of the `Simulation` is saved at the end of each step. This allows the next step to pick up from where the last step left off. If at any time you want to reset the state of your simulation back to the original initial equilibrium condition, you can use the `pre` method, which will re-run the preprocessing steps to re-initialize the instance. Since we will demonstrate the `run` method below using the same `sim` instance created above, we will use this approach to reset the simulation."
"There are a couple important thing to understands about how a `Simulation` runs experiments. When you first initialize a `Simulation` instance, the internal state is set to an equilibrium condition based on the initial intercalation fractions you defined in your `.yaml` file. When you run using `run_step`, the internal state of the `Simulation` is saved at the end of each step. This allows the next step to pick up from where the last step left off. If at any time you want to reset the state of your simulation back to the original initial equilibrium condition, you can use the `pre` method, which will re-run the preprocessing steps to re-initialize the instance. Since we will demonstrate the `run` method below using the same `sim` instance created above, we will use this approach to reset the simulation."
]
},
{
Expand Down
16 changes: 12 additions & 4 deletions src/bmlite/P2D/domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ def __init__(self, name: str, **kwargs) -> None:
Keyword arguments to set the electrode attributes. The required
keys and descriptions are given below:

========== ========================================================
========== =======================================================
Key Description [units] (type)
========== ========================================================
========== =======================================================
Nx number of `x` discretizations [-] (*int*)
Nr number of `r` discretizations [-] (*int*)
thick electrode thickness [m] (*float*)
Expand All @@ -207,8 +207,9 @@ def __init__(self, name: str, **kwargs) -> None:
i0_deg `i0` degradation factor [-] (*float*)
Ds_deg `Ds` degradation factor [-] (*float*)
material class name from `bmlite.materials` [-] (*str*)
csvfile path to CSV file with OCV data (optional) (*str*)
submodels `submodels` classes to include (*dict[dict]*)
========== ========================================================
========== =======================================================

"""
from . import submodels
Expand All @@ -234,6 +235,7 @@ def __init__(self, name: str, **kwargs) -> None:
self.i0_deg = kwargs.get('i0_deg')
self.Ds_deg = kwargs.get('Ds_deg')
self.material = kwargs.get('material')
self.csvfile = kwargs.get('csvfile', None)

self.update()

Expand All @@ -258,6 +260,7 @@ def update(self) -> None:
`A_s = 3 * eps_AM / R_s`

"""
import inspect
from .. import materials

self.eps_void = 1. - self.eps_s - self.eps_el
Expand All @@ -274,7 +277,12 @@ def update(self) -> None:
raise ValueError('eps_s <= eps_CBD.')

Material = getattr(materials, self.material)
self._material = Material(self.alpha_a, self.alpha_c, self.Li_max)

if 'csvfile' in inspect.signature(Material).parameters:
self._material = Material(self.alpha_a, self.alpha_c, self.Li_max,
csvfile=self.csvfile)
else:
self._material = Material(self.alpha_a, self.alpha_c, self.Li_max)

def get_Ds(self, x: float | np.ndarray, T: float,
fluxdir: float | np.ndarray) -> float | np.ndarray:
Expand Down
8 changes: 4 additions & 4 deletions src/bmlite/P2D/templates/graphiteSiOx_nmc811.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ electrolyte:
anode:
Nx: 32 # Number of x discretizations [-]
Nr: 30 # Number of r discretizations [-]
thick: 85.2e-6 # Thickness [m]
thick: 87.0e-6 # Thickness [m]
R_s: 5.86e-6 # Secondary particle radius [m]
eps_s: 0.75 # Solid-phase volume frac. [-]
eps_el: 0.25 # Electrolyte volume frac. [-]
Expand All @@ -27,7 +27,7 @@ anode:
x_0: 0.07 # Initial intercalation frac. [-]
i0_deg: 1.0 # Degradation factor for i0 [-]
Ds_deg: 1.0 # Degradation factor for Ds [-]
material: GraphiteSiOx # Anode active material class [-]
material: GraphiteSiOxSlow # Anode active material class [-]

separator:
Nx: 32 # Number of x discretizations [-]
Expand All @@ -38,7 +38,7 @@ separator:
cathode:
Nx: 32 # Number of x discretizations [-]
Nr: 30 # Number of r discretizations [-]
thick: 75.6e-6 # Thickness [m]
thick: 69.0e-6 # Thickness [m]
R_s: 5.22e-6 # Secondary particle radius [m]
eps_s: 0.665 # Solid-phase volume frac. [-]
eps_el: 0.335 # Electrolyte volume frac. [-]
Expand All @@ -51,4 +51,4 @@ cathode:
x_0: 0.89 # Initial intercalation frac. [-]
i0_deg: 1.0 # Degradation factor for i0 [-]
Ds_deg: 1.0 # Degradation factor for Ds [-]
material: NMC811 # Cathode active material class [-]
material: NMC811Slow # Cathode active material class [-]
10 changes: 9 additions & 1 deletion src/bmlite/SPM/domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def __init__(self, name: str, **kwargs):
i0_deg `i0` degradation factor [-] (*float*)
Ds_deg `Ds` degradation factor [-] (*float*)
material class name from `bmlite.materials` [-] (*str*)
csvfile path to CSV file with OCV data (optional) (*str*)
submodels `submodels` classes to include (*dict[dict]*)
========== ======================================================

Expand All @@ -154,6 +155,7 @@ def __init__(self, name: str, **kwargs):
self.i0_deg = kwargs.get('i0_deg')
self.Ds_deg = kwargs.get('Ds_deg')
self.material = kwargs.get('material')
self.csvfile = kwargs.get('csvfile', None)

self.update()

Expand All @@ -176,6 +178,7 @@ def update(self) -> None:
`A_s = 3 * eps_AM / R_s`

"""
import inspect
from .. import materials

self.eps_void = 1. - self.eps_s - self.eps_el
Expand All @@ -191,7 +194,12 @@ def update(self) -> None:
raise ValueError('eps_s <= eps_CBD')

Material = getattr(materials, self.material)
self._material = Material(self.alpha_a, self.alpha_c, self.Li_max)

if 'csvfile' in inspect.signature(Material).parameters:
self._material = Material(self.alpha_a, self.alpha_c, self.Li_max,
csvfile=self.csvfile)
else:
self._material = Material(self.alpha_a, self.alpha_c, self.Li_max)

def get_Ds(self, x: float | np.ndarray, T: float,
fluxdir: float) -> float | np.ndarray:
Expand Down
8 changes: 4 additions & 4 deletions src/bmlite/SPM/templates/graphiteSiOx_nmc811.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ electrolyte:

anode:
Nr: 30 # Number of r discretizations [-]
thick: 85.2e-6 # Thickness [m]
thick: 87.0e-6 # Thickness [m]
R_s: 5.86e-6 # Secondary particle radius [m]
eps_s: 0.75 # Solid-phase volume frac. [-]
eps_el: 0.25 # Electrolyte volume frac. [-]
Expand All @@ -19,11 +19,11 @@ anode:
x_0: 0.07 # Initial intercalation frac. [-]
i0_deg: 1.0 # Degradation factor for i0 [-]
Ds_deg: 1.0 # Degradation factor for Ds [-]
material: GraphiteSiOx # Anode active material class [-]
material: GraphiteSiOxSlow # Anode active material class [-]

cathode:
Nr: 30 # Number of r discretizations [-]
thick: 75.6e-6 # Thickness [m]
thick: 69.0e-6 # Thickness [m]
R_s: 5.22e-6 # Secondary particle radius [m]
eps_s: 0.665 # Solid-phase volume frac. [-]
eps_el: 0.335 # Electrolyte volume frac. [-]
Expand All @@ -34,4 +34,4 @@ cathode:
x_0: 0.89 # Initial intercalation frac. [-]
i0_deg: 1.0 # Degradation factor for i0 [-]
Ds_deg: 1.0 # Degradation factor for Ds [-]
material: NMC811 # Cathode active material class [-]
material: NMC811Slow # Cathode active material class [-]
6 changes: 4 additions & 2 deletions src/bmlite/materials/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@
from ._gen2_electrolyte import Gen2Electrolyte

from ._graphite import GraphiteFast, GraphiteSlow, GraphiteSlowExtrap
from ._graphite_SiOx import GraphiteSiOx
from ._graphite_SiOx import GraphiteSiOx, GraphiteSiOxSlow

from ._lfp import LFPInterp
from ._nmc_532 import NMC532Fast, NMC532Slow, NMC532SlowExtrap
from ._nmc_811 import NMC811
from ._nmc_811 import NMC811, NMC811Slow

__all__ = [
'Gen2Electrolyte',
'GraphiteFast',
'GraphiteSlow',
'GraphiteSlowExtrap',
'GraphiteSiOx',
'GraphiteSiOxSlow',
'LFPInterp',
'NMC532Fast',
'NMC532Slow',
'NMC532SlowExtrap',
'NMC811',
'NMC811Slow',
]
8 changes: 4 additions & 4 deletions src/bmlite/materials/_graphite.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,10 @@ class GraphiteSlow(GraphiteFast):

def __init__(self, alpha_a: float, alpha_c: float, Li_max: float) -> None:
"""
Computationally fast Graphite kinetic and transport properties.
Computationally slow Graphite kinetic and transport properties.

Differs from `GraphiteSlow` because the equilibrium potential is
not piecewise here, making it less accurate, but faster to evaluate.
Differs from `GraphiteFast` because the equilibrium potential is
piecewise here, making it more accurate, but slower to evaluate.

Parameters
----------
Expand Down Expand Up @@ -281,7 +281,7 @@ class GraphiteSlowExtrap(GraphiteFast):

def __init__(self, alpha_a: float, alpha_c: float, Li_max: float) -> None:
"""
Computationally fast Graphite kinetic and transport properties.
Computationally slow Graphite kinetic and transport properties.

Differs from `GraphiteSlow` because the piecewise equilibrium
potential is extrapolated to be valid on the full [0, 1] range.
Expand Down
Loading
Loading