diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d258bff..6f905bf2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## Features
+
+- [#316](https://github.com/pybop-team/PyBOP/pull/316) - Adds Adam with weight decay (AdamW) optimiser, adds depreciation warning for pints.Adam implementation.
- [#271](https://github.com/pybop-team/PyBOP/issues/271) - Aligns the output of the optimisers via a generalisation of Result class.
- [#315](https://github.com/pybop-team/PyBOP/pull/315) - Updates __init__ structure to remove circular import issues and minimises dependancy imports across codebase for faster PyBOP module import. Adds type-hints to BaseModel and refactors rebuild parameter variables.
- [#236](https://github.com/pybop-team/PyBOP/issues/236) - Restructures the optimiser classes, adds a new optimisation API through direct construction and keyword arguments, and fixes the setting of `max_iterations`, and `_minimising`. Introduces `pybop.BaseOptimiser`, `pybop.BasePintsOptimiser`, and `pybop.BaseSciPyOptimiser` classes.
diff --git a/README.md b/README.md
index 6ff0af9f..8fd09c0a 100644
--- a/README.md
+++ b/README.md
@@ -85,13 +85,13 @@ The table below lists the currently supported [models](https://github.com/pybop-
|-----------------------------------------------|-------------------------------------------------------------|------------------------------------------|
| Single Particle Model (SPM) | Covariance Matrix Adaptation Evolution Strategy (CMA-ES) | Sum of Squared Errors (SSE)
|
| Single Particle Model with Electrolyte (SPMe) | Particle Swarm Optimization (PSO) | Root Mean Squared Error (RMSE) |
-| Doyle-Fuller-Newman (DFN) | Adaptive Moment Estimation (Adam) | Maximum Likelihood Estimation (MLE) |
-| Many Particle Model (MPM) | Improved Resilient Backpropagation (iRProp-) | Maximum a Posteriori (MAP) |
-| Multi-Species Multi-Reactants (MSMR) | Exponential Natural Evolution Strategy (xNES) | Unscented Kalman Filter (UKF) |
-| Equivalent Circuit Models (ECM) | Separable Natural Evolution Strategy (sNES) | Gravimetric Energy Density |
-| | Gradient Descent | Volumetric Energy Density |
+| Doyle-Fuller-Newman (DFN) | Exponential Natural Evolution Strategy (xNES) | Gaussian Log Likelihood |
+| Many Particle Model (MPM) | Separable Natural Evolution Strategy (sNES) | Gaussian Log Likelihood w/ known variance |
+| Multi-Species Multi-Reactants (MSMR) | Adaptive Moment Estimation with Weight Decay (AdamW) | Maximum a Posteriori (MAP) |
+| Equivalent Circuit Models (ECM) | Improved Resilient Backpropagation (iRProp-) | Unscented Kalman Filter (UKF) |
+| | SciPy Minimize & Differential Evolution | Gravimetric Energy Density |
+| | Gradient Descent| Volumetric Energy Density |
| | Nelder-Mead | |
-| | SciPy Minimize & Differential Evolution | |
diff --git a/benchmarks/benchmark_parameterisation.py b/benchmarks/benchmark_parameterisation.py
index 8440ba5e..a64116a4 100644
--- a/benchmarks/benchmark_parameterisation.py
+++ b/benchmarks/benchmark_parameterisation.py
@@ -13,7 +13,7 @@ class BenchmarkParameterisation:
[
pybop.SciPyMinimize,
pybop.SciPyDifferentialEvolution,
- pybop.Adam,
+ pybop.AdamW,
pybop.CMAES,
pybop.GradientDescent,
pybop.IRPropMin,
diff --git a/benchmarks/benchmark_track_parameterisation.py b/benchmarks/benchmark_track_parameterisation.py
index fac2d54a..9180ffec 100644
--- a/benchmarks/benchmark_track_parameterisation.py
+++ b/benchmarks/benchmark_track_parameterisation.py
@@ -13,7 +13,7 @@ class BenchmarkTrackParameterisation:
[
pybop.SciPyMinimize,
pybop.SciPyDifferentialEvolution,
- pybop.Adam,
+ pybop.AdamW,
pybop.CMAES,
pybop.GradientDescent,
pybop.IRPropMin,
diff --git a/examples/notebooks/multi_optimiser_identification.ipynb b/examples/notebooks/multi_optimiser_identification.ipynb
index 8bb6f353..f85b2609 100644
--- a/examples/notebooks/multi_optimiser_identification.ipynb
+++ b/examples/notebooks/multi_optimiser_identification.ipynb
@@ -279,7 +279,7 @@
"source": [
"### Selecting the Optimisers\n",
"\n",
- "Now, we can select the optimisers to investigate. The first object is a list of non-gradient-based PINTS's optimisers. The next object comprises the gradient-based PINTS's optimisers (Adam, GradientDescent, IRPropMin). The final object forms the SciPy optimisers which can have gradient and non-gradient-based algorithms."
+ "Now, we can select the optimisers to investigate. The first object is a list of non-gradient-based PINTS's optimisers. The next object comprises the gradient-based PINTS's optimisers (AdamW, GradientDescent, IRPropMin). The final object forms the SciPy optimisers which can have gradient and non-gradient-based algorithms."
]
},
{
@@ -296,7 +296,7 @@
"outputs": [],
"source": [
"gradient_optimisers = [\n",
- " pybop.Adam,\n",
+ " pybop.AdamW,\n",
" pybop.GradientDescent,\n",
" pybop.IRPropMin,\n",
"]\n",
@@ -343,8 +343,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Running Adam\n",
- "NOTE: Boundaries ignored by Adam\n",
+ "Running AdamW\n",
+ "NOTE: Boundaries ignored by AdamW\n",
"Running GradientDescent\n",
"NOTE: Boundaries ignored by Gradient Descent\n",
"Running IRPropMin\n"
@@ -441,16 +441,16 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "| Optimiser: Adam | Results: [0.7940917 0.66244264] |\n",
- "| Optimiser: Gradient descent | Results: [0.54971602 0.92692275] |\n",
- "| Optimiser: iRprop- | Results: [0.72245096 0.67281911] |\n",
- "| Optimiser: Covariance Matrix Adaptation Evolution Strategy (CMA-ES) | Results: [0.72101083 0.67313135] |\n",
- "| Optimiser: Seperable Natural Evolution Strategy (SNES) | Results: [0.72082105 0.67317566] |\n",
- "| Optimiser: Particle Swarm Optimisation (PSO) | Results: [0.71816152 0.6738613 ] |\n",
- "| Optimiser: Exponential Natural Evolution Strategy (xNES) | Results: [0.72064411 0.67318804] |\n",
- "| Optimiser: Nelder-Mead | Results: [0.72117726 0.67313287] |\n",
- "| Optimiser: SciPyMinimize | Results: [0.62747904 0.7 ] |\n",
- "| Optimiser: SciPyDifferentialEvolution | Results: [0.72103631 0.67312847] |\n"
+ "| Optimiser: AdamW | Results: [0.80186169 0.66943058] |\n",
+ "| Optimiser: Gradient descent | Results: [0.44491146 1.59642543] |\n",
+ "| Optimiser: iRprop- | Results: [0.8 0.66516386] |\n",
+ "| Optimiser: Covariance Matrix Adaptation Evolution Strategy (CMA-ES) | Results: [0.7999994 0.66516056] |\n",
+ "| Optimiser: Seperable Natural Evolution Strategy (SNES) | Results: [0.79672265 0.66566242] |\n",
+ "| Optimiser: Particle Swarm Optimisation (PSO) | Results: [0.79978922 0.66557426] |\n",
+ "| Optimiser: Exponential Natural Evolution Strategy (xNES) | Results: [0.79992605 0.66513294] |\n",
+ "| Optimiser: Nelder-Mead | Results: [0.81389091 0.66318217] |\n",
+ "| Optimiser: SciPyMinimize | Results: [0.63594266 0.7 ] |\n",
+ "| Optimiser: SciPyDifferentialEvolution | Results: [0.79999973 0.6651644 ] |\n"
]
}
],
@@ -509,7 +509,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model Adam Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model AdamW Time / s Voltage / V "
]
},
"metadata": {},
@@ -518,7 +518,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 4.1 Reference Model Gradient descent Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model Gradient descent Time / s Voltage / V "
]
},
"metadata": {},
@@ -527,7 +527,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model iRprop- Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model iRprop- Time / s Voltage / V "
]
},
"metadata": {},
@@ -536,7 +536,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model Covariance Matrix Adaptation Evolution Strategy (CMA-ES) Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model Covariance Matrix Adaptation Evolution Strategy (CMA-ES) Time / s Voltage / V "
]
},
"metadata": {},
@@ -545,7 +545,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model Seperable Natural Evolution Strategy (SNES) Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model Seperable Natural Evolution Strategy (SNES) Time / s Voltage / V "
]
},
"metadata": {},
@@ -554,7 +554,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model Particle Swarm Optimisation (PSO) Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model Particle Swarm Optimisation (PSO) Time / s Voltage / V "
]
},
"metadata": {},
@@ -563,7 +563,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model Exponential Natural Evolution Strategy (xNES) Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model Exponential Natural Evolution Strategy (xNES) Time / s Voltage / V "
]
},
"metadata": {},
@@ -572,7 +572,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model Nelder-Mead Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model Nelder-Mead Time / s Voltage / V "
]
},
"metadata": {},
@@ -581,7 +581,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model SciPyMinimize Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model SciPyMinimize Time / s Voltage / V "
]
},
"metadata": {},
@@ -590,7 +590,7 @@
{
"data": {
"image/svg+xml": [
- "0 500 1000 1500 2000 3.5 3.6 3.7 3.8 3.9 4 Reference Model SciPyDifferentialEvolution Time / s Voltage / V "
+ "0 200 400 600 800 3.75 3.8 3.85 3.9 3.95 4 4.05 Reference Model SciPyDifferentialEvolution Time / s Voltage / V "
]
},
"metadata": {},
@@ -627,7 +627,7 @@
{
"data": {
"image/svg+xml": [
- "5 10 15 20 25 30 0 0.5 1 1.5 2 2.5 3 3.5 Adam Iteration Cost "
+ "5 10 15 20 25 30 0 1 2 3 4 5 AdamW Iteration Cost "
]
},
"metadata": {},
@@ -636,7 +636,7 @@
{
"data": {
"image/svg+xml": [
- "0 10 20 30 0.6 0.65 0.7 0.75 0.8 0.85 0 10 20 30 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 5 10 15 20 25 0.6 0.65 0.7 0.75 0.8 0.85 0 5 10 15 20 25 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -645,7 +645,7 @@
{
"data": {
"image/svg+xml": [
- "5 10 15 20 1 2 3 4 5 6 Gradient descent Iteration Cost "
+ "5 10 15 20 2 4 6 8 10 12 14 16 18 Gradient descent Iteration Cost "
]
},
"metadata": {},
@@ -654,7 +654,7 @@
{
"data": {
"image/svg+xml": [
- "0 5 10 15 20 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0 5 10 15 20 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 5 10 15 0 5 10 15 20 25 30 0 5 10 15 0.5 1 1.5 2 2.5 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -663,7 +663,7 @@
{
"data": {
"image/svg+xml": [
- "10 20 30 40 50 60 0 0.5 1 1.5 2 2.5 3 3.5 iRprop- Iteration Cost "
+ "10 20 30 40 0 1 2 3 4 5 iRprop- Iteration Cost "
]
},
"metadata": {},
@@ -672,7 +672,7 @@
{
"data": {
"image/svg+xml": [
- "0 20 40 0.65 0.7 0.75 0.8 0 20 40 0.5 0.55 0.6 0.65 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 10 20 30 40 0.6 0.65 0.7 0.75 0.8 0 10 20 30 40 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -681,7 +681,7 @@
{
"data": {
"image/svg+xml": [
- "10 20 30 40 0 0.5 1 1.5 2 2.5 3 Covariance Matrix Adaptation Evolution Strategy (CMA-ES) Iteration Cost "
+ "10 20 30 40 0 0.5 1 1.5 2 2.5 3 Covariance Matrix Adaptation Evolution Strategy (CMA-ES) Iteration Cost "
]
},
"metadata": {},
@@ -690,7 +690,7 @@
{
"data": {
"image/svg+xml": [
- "0 50 100 150 200 250 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0 50 100 150 200 250 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 50 100 150 200 250 0.55 0.6 0.65 0.7 0.75 0.8 0 50 100 150 200 250 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -699,7 +699,7 @@
{
"data": {
"image/svg+xml": [
- "10 20 30 40 50 60 0 0.5 1 1.5 2 Seperable Natural Evolution Strategy (SNES) Iteration Cost "
+ "10 20 30 40 50 60 0 0.5 1 1.5 2 2.5 3 3.5 Seperable Natural Evolution Strategy (SNES) Iteration Cost "
]
},
"metadata": {},
@@ -708,7 +708,7 @@
{
"data": {
"image/svg+xml": [
- "0 100 200 300 0.55 0.6 0.65 0.7 0.75 0 100 200 300 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 100 200 300 0.55 0.6 0.65 0.7 0.75 0.8 0 100 200 300 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -717,7 +717,7 @@
{
"data": {
"image/svg+xml": [
- "10 20 30 40 50 0 0.5 1 1.5 Particle Swarm Optimisation (PSO) Iteration Cost "
+ "10 20 30 40 0 0.5 1 1.5 2 2.5 Particle Swarm Optimisation (PSO) Iteration Cost "
]
},
"metadata": {},
@@ -726,7 +726,7 @@
{
"data": {
"image/svg+xml": [
- "0 50 100 150 200 250 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0 50 100 150 200 250 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 50 100 150 200 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0 50 100 150 200 0.4 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -735,7 +735,7 @@
{
"data": {
"image/svg+xml": [
- "10 20 30 40 0 0.5 1 1.5 2 Exponential Natural Evolution Strategy (xNES) Iteration Cost "
+ "10 20 30 40 50 60 0 0.5 1 1.5 2 2.5 Exponential Natural Evolution Strategy (xNES) Iteration Cost "
]
},
"metadata": {},
@@ -744,7 +744,7 @@
{
"data": {
"image/svg+xml": [
- "0 50 100 150 200 250 0.6 0.65 0.7 0.75 0 50 100 150 200 250 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 100 200 300 0.6 0.65 0.7 0.75 0.8 0 100 200 300 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -753,7 +753,7 @@
{
"data": {
"image/svg+xml": [
- "10 20 30 40 50 60 0 0.5 1 1.5 2 2.5 Nelder-Mead Iteration Cost "
+ "10 20 30 40 50 60 0 0.5 1 1.5 2 2.5 3 3.5 Nelder-Mead Iteration Cost "
]
},
"metadata": {},
@@ -762,7 +762,7 @@
{
"data": {
"image/svg+xml": [
- "0 20 40 60 0.62 0.64 0.66 0.68 0.7 0.72 0 20 40 60 0.5 0.55 0.6 0.65 0.7 0.75 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 20 40 60 0.6 0.65 0.7 0.75 0.8 0.85 0 20 40 60 0.45 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.85 0.9 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -771,7 +771,7 @@
{
"data": {
"image/svg+xml": [
- "5 10 15 20 25 30 0 0.5 1 1.5 2 2.5 3 3.5 SciPyMinimize Iteration Cost "
+ "5 10 15 20 25 0 1 2 3 4 5 SciPyMinimize Iteration Cost "
]
},
"metadata": {},
@@ -780,7 +780,7 @@
{
"data": {
"image/svg+xml": [
- "0 5 10 15 20 25 0.61 0.62 0.63 0.64 0.65 0.66 0.67 0.68 0.69 0 5 10 15 20 25 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 5 10 15 20 25 0.6 0.61 0.62 0.63 0.64 0.65 0.66 0.67 0 5 10 15 20 25 0.45 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -789,7 +789,7 @@
{
"data": {
"image/svg+xml": [
- "5 10 15 20 0.00295 0.003 0.00305 0.0031 0.00315 0.0032 0.00325 0.0033 SciPyDifferentialEvolution Iteration Cost "
+ "5 10 15 20 25 30 0.0036 0.0038 0.004 0.0042 0.0044 SciPyDifferentialEvolution Iteration Cost "
]
},
"metadata": {},
@@ -798,7 +798,7 @@
{
"data": {
"image/svg+xml": [
- "0 5 10 15 20 0.695 0.7 0.705 0.71 0.715 0.72 0 5 10 15 20 0.673 0.674 0.675 0.676 0.677 0.678 0.679 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0 5 10 15 20 25 0.755 0.76 0.765 0.77 0.775 0.78 0.785 0.79 0.795 0.8 0 5 10 15 20 25 0.665 0.666 0.667 0.668 0.669 0.67 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -835,7 +835,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Adam Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 AdamW Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -844,7 +844,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Gradient descent Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 Gradient descent Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -853,7 +853,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 iRprop- Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 iRprop- Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -862,7 +862,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Covariance Matrix Adaptation Evolution Strategy (CMA-ES) Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 Covariance Matrix Adaptation Evolution Strategy (CMA-ES) Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -871,7 +871,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Seperable Natural Evolution Strategy (SNES) Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 Seperable Natural Evolution Strategy (SNES) Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -880,7 +880,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Particle Swarm Optimisation (PSO) Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 Particle Swarm Optimisation (PSO) Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -889,7 +889,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Exponential Natural Evolution Strategy (xNES) Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 Exponential Natural Evolution Strategy (xNES) Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -898,7 +898,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Nelder-Mead Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 Nelder-Mead Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -907,7 +907,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 SciPyMinimize Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 SciPyMinimize Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
@@ -916,7 +916,7 @@
{
"data": {
"image/svg+xml": [
- "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 SciPyDifferentialEvolution Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.5 1 1.5 2 2.5 3 3.5 SciPyDifferentialEvolution Negative electrode active material volume fraction Positive electrode active material volume fraction "
]
},
"metadata": {},
diff --git a/examples/notebooks/spm_AdamW.ipynb b/examples/notebooks/spm_AdamW.ipynb
new file mode 100644
index 00000000..20b73330
--- /dev/null
+++ b/examples/notebooks/spm_AdamW.ipynb
@@ -0,0 +1,826 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "expmkveO04pw"
+ },
+ "source": [
+ "## Parameter Estimation with AdamW in PyBOP\n",
+ "\n",
+ "In this notebook, we demonstrate an example of parameter estimation for a single-particle model using the AdamW optimiser [1][2]. The AdamW optimiser is an algorithm for gradient-based optimisation, combining the advantages of the Adaptive Gradient Algorithm (AdaGrad) and Root Mean Square Propagation (RMSProp).\n",
+ "\n",
+ "[[1]: Adam: A Method for Stochastic Optimization](https://arxiv.org/abs/1412.6980) \n",
+ "\n",
+ "[[2]: Decoupled Weight Decay Regularization](https://doi.org/10.48550/arXiv.1711.05101)\n",
+ "\n",
+ "### Setting up the Environment\n",
+ "\n",
+ "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP from its development branch and upgrade some dependencies:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:40.337833Z",
+ "iopub.status.busy": "2024-04-04T13:51:40.337689Z",
+ "iopub.status.idle": "2024-04-04T13:51:41.935008Z",
+ "shell.execute_reply": "2024-04-04T13:51:41.934618Z"
+ },
+ "id": "X87NUGPW04py",
+ "outputId": "0d785b07-7cff-4aeb-e60a-4ff5a669afbf"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Requirement already satisfied: pip in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (24.0)\n",
+ "Requirement already satisfied: ipywidgets in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (8.1.2)\n",
+ "Requirement already satisfied: comm>=0.1.3 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipywidgets) (0.2.1)\n",
+ "Requirement already satisfied: ipython>=6.1.0 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipywidgets) (8.22.1)\n",
+ "Requirement already satisfied: traitlets>=4.3.1 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipywidgets) (5.14.1)\n",
+ "Requirement already satisfied: widgetsnbextension~=4.0.10 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipywidgets) (4.0.10)\n",
+ "Requirement already satisfied: jupyterlab-widgets~=3.0.10 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipywidgets) (3.0.10)\n",
+ "Requirement already satisfied: decorator in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (5.1.1)\n",
+ "Requirement already satisfied: jedi>=0.16 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (0.19.1)\n",
+ "Requirement already satisfied: matplotlib-inline in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.6)\n",
+ "Requirement already satisfied: prompt-toolkit<3.1.0,>=3.0.41 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (3.0.43)\n",
+ "Requirement already satisfied: pygments>=2.4.0 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (2.17.2)\n",
+ "Requirement already satisfied: stack-data in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (0.6.3)\n",
+ "Requirement already satisfied: pexpect>4.3 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from ipython>=6.1.0->ipywidgets) (4.9.0)\n",
+ "Requirement already satisfied: parso<0.9.0,>=0.8.3 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.3)\n",
+ "Requirement already satisfied: ptyprocess>=0.5 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n",
+ "Requirement already satisfied: wcwidth in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from prompt-toolkit<3.1.0,>=3.0.41->ipython>=6.1.0->ipywidgets) (0.2.13)\n",
+ "Requirement already satisfied: executing>=1.2.0 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (2.0.1)\n",
+ "Requirement already satisfied: asttokens>=2.1.0 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (2.4.1)\n",
+ "Requirement already satisfied: pure-eval in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (0.2.2)\n",
+ "Requirement already satisfied: six>=1.12.0 in /Users/engs2510/.pyenv/versions/3.11.7/envs/pybop/lib/python3.11/site-packages (from asttokens>=2.1.0->stack-data->ipython>=6.1.0->ipywidgets) (1.16.0)\n",
+ "Note: you may need to restart the kernel to use updated packages.\n",
+ "Note: you may need to restart the kernel to use updated packages.\n"
+ ]
+ }
+ ],
+ "source": [
+ "%pip install --upgrade pip ipywidgets\n",
+ "%pip install pybop -q"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "jAvD5fk104p0"
+ },
+ "source": [
+ "### Importing Libraries\n",
+ "\n",
+ "With the environment set up, we can now import PyBOP alongside other libraries we will need:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:41.936561Z",
+ "iopub.status.busy": "2024-04-04T13:51:41.936439Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.508083Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.507654Z"
+ },
+ "id": "SQdt4brD04p1"
+ },
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "import pybop"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "5XU-dMtU04p2"
+ },
+ "source": [
+ "### Generate Synthetic Data\n",
+ "\n",
+ "To demonstrate parameter estimation, we first need some data. We will generate synthetic data using the PyBOP forward model, which requires defining a parameter set and the model itself.\n",
+ "\n",
+ "#### Defining Parameters and Model\n",
+ "\n",
+ "We start by creating an example parameter set and then instantiate the single-particle model (SPM):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.509591Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.509437Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.534794Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.534452Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n",
+ "model = pybop.lithium_ion.SPM(parameter_set=parameter_set)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Simulating Forward Model\n",
+ "\n",
+ "We can then simulate the model using the `predict` method, with a default constant current to generate voltage data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.536154Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.536069Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.610305Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.609892Z"
+ },
+ "id": "sBasxv8U04p3"
+ },
+ "outputs": [],
+ "source": [
+ "t_eval = np.arange(0, 900, 2)\n",
+ "values = model.predict(t_eval=t_eval)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Adding Noise to Voltage Data\n",
+ "\n",
+ "To make the parameter estimation more realistic, we add Gaussian noise to the data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.611946Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.611728Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.621525Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.621156Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "sigma = 0.001\n",
+ "corrupt_values = values[\"Voltage [V]\"].data + np.random.normal(0, sigma, len(t_eval))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "X8-tubYY04p_"
+ },
+ "source": [
+ "## Identify the Parameters"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PQqhvSZN04p_"
+ },
+ "source": [
+ "We will now set up the parameter estimation process by defining the datasets for optimisation and selecting the model parameters we wish to estimate."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Creating Optimisation Dataset\n",
+ "\n",
+ "The dataset for optimisation is composed of time, current, and the noisy voltage data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.622671Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.622478Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.628864Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.628519Z"
+ },
+ "id": "zuvGHWID04p_"
+ },
+ "outputs": [],
+ "source": [
+ "dataset = pybop.Dataset(\n",
+ " {\n",
+ " \"Time [s]\": t_eval,\n",
+ " \"Current function [A]\": values[\"Current [A]\"].data,\n",
+ " \"Voltage [V]\": corrupt_values,\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ffS3CF_704qA"
+ },
+ "source": [
+ "### Defining Parameters to Estimate\n",
+ "\n",
+ "We select the parameters for estimation and set up their prior distributions and bounds:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.629987Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.629809Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.631895Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.631621Z"
+ },
+ "id": "WPCybXIJ04qA"
+ },
+ "outputs": [],
+ "source": [
+ "parameters = [\n",
+ " pybop.Parameter(\n",
+ " \"Negative electrode active material volume fraction\",\n",
+ " prior=pybop.Gaussian(0.6, 0.02),\n",
+ " bounds=[0.5, 0.8],\n",
+ " ),\n",
+ " pybop.Parameter(\n",
+ " \"Positive electrode active material volume fraction\",\n",
+ " prior=pybop.Gaussian(0.48, 0.02),\n",
+ " bounds=[0.4, 0.7],\n",
+ " ),\n",
+ "]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "n4OHa-aF04qA"
+ },
+ "source": [
+ "### Setting up the Optimisation Problem\n",
+ "\n",
+ "With the datasets and parameters defined, we can set up the optimisation problem, its cost function, and the optimiser."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.632931Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.632782Z",
+ "iopub.status.idle": "2024-04-04T13:51:42.705454Z",
+ "shell.execute_reply": "2024-04-04T13:51:42.705066Z"
+ },
+ "id": "etMzRtx404qA"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "NOTE: Boundaries ignored by AdamW\n"
+ ]
+ }
+ ],
+ "source": [
+ "problem = pybop.FittingProblem(model, parameters, dataset)\n",
+ "cost = pybop.SumSquaredError(problem)\n",
+ "optim = pybop.Optimisation(cost, optimiser=pybop.AdamW)\n",
+ "optim.set_max_unchanged_iterations(40)\n",
+ "optim.set_max_iterations(150)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "caprp-bV04qB"
+ },
+ "source": [
+ "### Running the Optimisation\n",
+ "\n",
+ "We proceed to run the AdamW optimisation algorithm to estimate the parameters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:42.706564Z",
+ "iopub.status.busy": "2024-04-04T13:51:42.706469Z",
+ "iopub.status.idle": "2024-04-04T13:51:50.537424Z",
+ "shell.execute_reply": "2024-04-04T13:51:50.537032Z"
+ },
+ "id": "-9OVt0EQ04qB"
+ },
+ "outputs": [],
+ "source": [
+ "x, final_cost = optim.run()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "-4pZsDmS04qC"
+ },
+ "source": [
+ "### Viewing the Estimated Parameters\n",
+ "\n",
+ "After the optimisation, we can examine the estimated parameter values:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:50.538815Z",
+ "iopub.status.busy": "2024-04-04T13:51:50.538619Z",
+ "iopub.status.idle": "2024-04-04T13:51:50.541683Z",
+ "shell.execute_reply": "2024-04-04T13:51:50.541465Z"
+ },
+ "id": "Hgz8SV4i04qC",
+ "outputId": "e1e42ae7-5075-4c47-dd68-1b22ecc170f6"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([0.76334915, 0.66225839])"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x # This will output the estimated parameters"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "KxKURtH704qC"
+ },
+ "source": [
+ "## Plotting and Visualisation\n",
+ "\n",
+ "PyBOP provides various plotting utilities to visualise the results of the optimisation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "-cWCOiqR04qC"
+ },
+ "source": [
+ "### Comparing System Response\n",
+ "\n",
+ "We can quickly plot the system's response using the estimated parameters compared to the target:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 467
+ },
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:50.542618Z",
+ "iopub.status.busy": "2024-04-04T13:51:50.542472Z",
+ "iopub.status.idle": "2024-04-04T13:51:50.986055Z",
+ "shell.execute_reply": "2024-04-04T13:51:50.985844Z"
+ },
+ "id": "tJUJ80Ve04qD",
+ "outputId": "855fbaa2-1e09-4935-eb1a-8caf7f99eb75"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "0 200 400 600 800 3.8 3.85 3.9 3.95 4 4.05 Reference Model Optimised Comparison Time / s Voltage / V "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "pybop.quick_plot(problem, parameter_values=x, title=\"Optimised Comparison\");"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Convergence and Parameter Trajectories\n",
+ "\n",
+ "To assess the optimisation process, we can plot the convergence of the cost function and the trajectories of the parameters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:50.987237Z",
+ "iopub.status.busy": "2024-04-04T13:51:50.986963Z",
+ "iopub.status.idle": "2024-04-04T13:51:52.766386Z",
+ "shell.execute_reply": "2024-04-04T13:51:52.766178Z"
+ },
+ "id": "N5XYkevi04qD"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "20 40 60 80 100 120 140 0 0.5 1 1.5 2 2.5 3 3.5 4 Convergence Iteration Cost "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ "0 50 100 0.6 0.65 0.7 0.75 0.8 0.85 0 50 100 0.5 0.55 0.6 0.65 0.7 Negative electrode active material volume fraction Positive electrode active material volume fraction Parameter Convergence Function Call Function Call Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "pybop.plot_convergence(optim)\n",
+ "pybop.plot_parameters(optim);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Cost Landscape\n",
+ "\n",
+ "Finally, we can visualise the cost landscape and the path taken by the optimiser:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2024-04-04T13:51:52.767346Z",
+ "iopub.status.busy": "2024-04-04T13:51:52.767261Z",
+ "iopub.status.idle": "2024-04-04T13:51:57.666000Z",
+ "shell.execute_reply": "2024-04-04T13:51:57.665745Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0 2 4 6 8 10 Cost Landscape Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ "0.6 0.65 0.7 0.75 0.8 0.85 0.9 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.4 0.8 1.2 1.6 2 2.4 Cost Landscape Negative electrode active material volume fraction Positive electrode active material volume fraction "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Plot the cost landscape\n",
+ "pybop.plot2d(cost, steps=15)\n",
+ "# Plot the cost landscape with optimisation path and updated bounds\n",
+ "bounds = np.array([[0.6, 0.9], [0.5, 0.8]])\n",
+ "pybop.plot2d(optim, bounds=bounds, steps=15);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Conclusion\n",
+ "\n",
+ "This notebook illustrates how to perform parameter estimation using AdamW in PyBOP, providing insights into the optimisation process through various visualisations."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.7"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "06f2374f91c8455bb63252092512f2ed": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "2.0.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "2.0.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border_bottom": null,
+ "border_left": null,
+ "border_right": null,
+ "border_top": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ },
+ "423bffea3a1c42b49a9ad71218e5811b": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "2.0.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "2.0.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border_bottom": null,
+ "border_left": null,
+ "border_right": null,
+ "border_top": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ },
+ "56ff19291e464d63b23e63b8e2ac9ea3": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "SliderStyleModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "2.0.0",
+ "_model_name": "SliderStyleModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "2.0.0",
+ "_view_name": "StyleView",
+ "description_width": "",
+ "handle_color": null
+ }
+ },
+ "646a8670cb204a31bb56bc2380898093": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "2.0.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "2.0.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border_bottom": null,
+ "border_left": null,
+ "border_right": null,
+ "border_top": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ },
+ "7d46516469314b88be3500e2afcafcf6": {
+ "model_module": "@jupyter-widgets/output",
+ "model_module_version": "1.0.0",
+ "model_name": "OutputModel",
+ "state": {
+ "_dom_classes": [],
+ "_model_module": "@jupyter-widgets/output",
+ "_model_module_version": "1.0.0",
+ "_model_name": "OutputModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/output",
+ "_view_module_version": "1.0.0",
+ "_view_name": "OutputView",
+ "layout": "IPY_MODEL_646a8670cb204a31bb56bc2380898093",
+ "msg_id": "",
+ "outputs": [],
+ "tabbable": null,
+ "tooltip": null
+ }
+ },
+ "8d003c14da5f4fa68284b28c15cee6e6": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "VBoxModel",
+ "state": {
+ "_dom_classes": [
+ "widget-interact"
+ ],
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "2.0.0",
+ "_model_name": "VBoxModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/controls",
+ "_view_module_version": "2.0.0",
+ "_view_name": "VBoxView",
+ "box_style": "",
+ "children": [
+ "IPY_MODEL_aef2fa7adcc14ad0854b73d5910ae3b4",
+ "IPY_MODEL_7d46516469314b88be3500e2afcafcf6"
+ ],
+ "layout": "IPY_MODEL_423bffea3a1c42b49a9ad71218e5811b",
+ "tabbable": null,
+ "tooltip": null
+ }
+ },
+ "aef2fa7adcc14ad0854b73d5910ae3b4": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatSliderModel",
+ "state": {
+ "_dom_classes": [],
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "2.0.0",
+ "_model_name": "FloatSliderModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/controls",
+ "_view_module_version": "2.0.0",
+ "_view_name": "FloatSliderView",
+ "behavior": "drag-tap",
+ "continuous_update": true,
+ "description": "t",
+ "description_allow_html": false,
+ "disabled": false,
+ "layout": "IPY_MODEL_06f2374f91c8455bb63252092512f2ed",
+ "max": 1.1333333333333333,
+ "min": 0,
+ "orientation": "horizontal",
+ "readout": true,
+ "readout_format": ".2f",
+ "step": 0.011333333333333332,
+ "style": "IPY_MODEL_56ff19291e464d63b23e63b8e2ac9ea3",
+ "tabbable": null,
+ "tooltip": null,
+ "value": 0
+ }
+ }
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/examples/scripts/spm_adam.py b/examples/scripts/spm_AdamW.py
similarity index 98%
rename from examples/scripts/spm_adam.py
rename to examples/scripts/spm_AdamW.py
index 82f884e2..10351512 100644
--- a/examples/scripts/spm_adam.py
+++ b/examples/scripts/spm_AdamW.py
@@ -54,7 +54,7 @@ def noise(sigma):
model, parameters, dataset, signal=signal, init_soc=init_soc
)
cost = pybop.RootMeanSquaredError(problem)
-optim = pybop.Adam(
+optim = pybop.AdamW(
cost,
verbose=True,
allow_infeasible_solutions=True,
diff --git a/pybop/__init__.py b/pybop/__init__.py
index 6d2c0dbf..61971e95 100644
--- a/pybop/__init__.py
+++ b/pybop/__init__.py
@@ -102,6 +102,8 @@
#
# Optimiser class
#
+
+from .optimisers._adamw import AdamWImpl
from .optimisers.base_optimiser import BaseOptimiser, Result
from .optimisers.base_pints_optimiser import BasePintsOptimiser
from .optimisers.scipy_optimisers import (
@@ -118,6 +120,7 @@
PSO,
SNES,
XNES,
+ AdamW,
)
from .optimisers.optimisation import Optimisation
diff --git a/pybop/optimisers/_adamw.py b/pybop/optimisers/_adamw.py
new file mode 100644
index 00000000..24e5ec98
--- /dev/null
+++ b/pybop/optimisers/_adamw.py
@@ -0,0 +1,235 @@
+#
+# Extends the Pints' Adam Class with a Weight Decay addition
+#
+
+import numpy as np
+from pints import Optimiser as PintsOptimiser
+
+
+class AdamWImpl(PintsOptimiser):
+ """
+ AdamW optimiser (adaptive moment estimation with weight decay), as described in [1]_.
+
+ This method is an extension of the Adam optimiser that introduces weight decay,
+ which helps to regularise the weights and prevent overfitting.
+
+ This class reimplements the Pints' Adam Optimiser, but with the weight decay
+ functionality mentioned above. Original creation and credit is attributed to Pints.
+
+ Pseudo-code is given below. Here the value of the j-th parameter at
+ iteration i is given as ``p_j[i]`` and the corresponding derivative is
+ denoted ``g_j[i]``::
+
+ m_j[i] = beta1 * m_j[i - 1] + (1 - beta1) * g_j[i]
+ v_j[i] = beta2 * v_j[i - 1] + (1 - beta2) * g_j[i]**2
+
+ m_j' = m_j[i] / (1 - beta1**(1 + i))
+ v_j' = v_j[i] / (1 - beta2**(1 + i))
+
+ p_j[i] = p_j[i - 1] - alpha * (m_j' / (sqrt(v_j') + eps) + lambda * p_j[i - 1])
+
+ The initial values of the moments are ``m_j[0] = v_j[0] = 0``, after which
+ they decay with rates ``beta1`` and ``beta2``. The default values for these are,
+ ``beta1 = 0.9`` and ``beta2 = 0.999``.
+
+ The terms ``m_j'`` and ``v_j'`` are "initialisation bias corrected"
+ versions of ``m_j`` and ``v_j`` (see section 2 of the paper).
+
+ The parameter ``alpha`` is a step size, which is set as ``min(sigma0)`` in
+ this implementation.
+
+ The parameter ``lambda`` is the weight decay rate, which is set to ``0.01``
+ by default in this implementation.
+
+ Finally, ``eps`` is a small constant used to avoid division by zero, set to
+ ``eps = `np.finfo(float).eps` in this implementation.
+
+ This is an unbounded method: Any ``boundaries`` will be ignored.
+
+ References
+ ----------
+ .. [1] Decoupled Weight Decay Regularization
+ Loshchilov and Hutter, 2019, arxiv (version v3)
+ https://doi.org/10.48550/arXiv.1711.05101
+ """
+
+ def __init__(self, x0, sigma0=0.015, boundaries=None):
+ if boundaries is not None:
+ print("NOTE: Boundaries ignored by AdamW")
+
+ self.boundaries = None
+ super().__init__(x0, sigma0, self.boundaries)
+
+ # Set optimiser state
+ self._running = False
+ self._ready_for_tell = False
+
+ # Best solution found
+ self._x_best = self._x0
+ self._f_best = np.inf
+
+ # Current point, score, and gradient
+ self._current = self._x0
+ self._current_f = np.inf
+ self._current_df = None
+
+ # Proposed next point (read-only, so can be passed to user)
+ self._proposed = self._x0
+ self._proposed.setflags(write=False)
+
+ # Moment vectors
+ self._m = np.zeros(self._x0.shape)
+ self._v = np.zeros(self._x0.shape)
+
+ # Exponential decay rates for the moment estimates
+ self._b1 = 0.9
+ self._b2 = 0.999
+
+ # Step size
+ self._alpha = np.min(self._sigma0)
+
+ # Weight decay rate
+ self.set_lambda()
+
+ # Small number added to avoid divide-by-zero
+ self._eps = np.finfo(float).eps
+
+ # Powers of decay rates
+ self._b1t = 1
+ self._b2t = 1
+
+ def ask(self):
+ """
+ Returns a list of next points in the parameter-space
+ to evaluate from the optimiser.
+ """
+
+ # Running, and ready for tell now
+ self._ready_for_tell = True
+ self._running = True
+
+ # Return proposed points (just the one)
+ return [self._proposed]
+
+ def f_best(self):
+ """
+ Returns the best score found so far.
+ """
+ return self._f_best
+
+ def f_guessed(self):
+ """
+ Returns the score of the last guessed point.
+ """
+ return self._current_f
+
+ def name(self):
+ """
+ Returns the name of the optimiser.
+ """
+ return "AdamW"
+
+ def needs_sensitivities(self):
+ """
+ Returns ``False`` if this optimiser does not require gradient,
+ and ``True`` otherwise.
+ """
+ return True
+
+ def n_hyper_parameters(self):
+ """
+ The number of hyper-parameters used by this optimiser.
+ """
+ return 5
+
+ def running(self):
+ """
+ Returns ``True`` if the optimisation is in progress.
+ """
+ return self._running
+
+ def tell(self, reply):
+ """
+ Receives a list of function values from the cost function from points
+ previously specified by `self.ask()`, and updates the optimiser state
+ accordingly.
+ """
+
+ # Check ask-tell pattern
+ if not self._ready_for_tell:
+ raise Exception("ask() not called before tell()")
+ self._ready_for_tell = False
+
+ # Unpack reply
+ fx, dfx = reply[0]
+
+ # Update current point
+ self._current = self._proposed
+ self._current_f = fx
+ self._current_df = dfx
+
+ # Update bx^t
+ self._b1t *= self._b1
+ self._b2t *= self._b2
+
+ # "Update biased first moment estimate"
+ self._m = self._b1 * self._m + (1 - self._b1) * dfx
+
+ # "Update biased second raw moment estimate"
+ self._v = self._b2 * self._v + (1 - self._b2) * dfx**2
+
+ # "Compute bias-corrected first moment estimate"
+ m = self._m / (1 - self._b1t)
+
+ # "Compute bias-corrected second raw moment estimate"
+ v = self._v / (1 - self._b2t)
+
+ # Take step with weight decay
+ self._proposed = self._current - self._alpha * (
+ m / (np.sqrt(v) + self._eps) + self._lambda * self._current
+ )
+
+ # Update x_best and f_best
+ if self._f_best > fx:
+ self._f_best = fx
+ self._x_best = self._current
+
+ def x_best(self):
+ """
+ Returns the best parameter values found so far.
+ """
+ return self._x_best
+
+ def x_guessed(self):
+ """
+ Returns the last guessed parameter values.
+ """
+ return self._current
+
+ def set_lambda(self, lambda_: float = 0.01) -> None:
+ """
+ Sets the lambda_ decay constant. This is the weight decay rate
+ that helps in finding the optimal solution.
+ """
+ if not isinstance(lambda_, (int, float)) or not 0 < lambda_ <= 1:
+ raise ValueError("lambda_ must be a numeric value between 0 and 1.")
+
+ self._lambda = float(lambda_)
+
+ def set_b1(self, b1: float) -> None:
+ """
+ Sets the b1 momentum decay constant.
+ """
+ if not isinstance(b1, (int, float)) or not 0 < b1 <= 1:
+ raise ValueError("b1 must be a numeric value between 0 and 1.")
+
+ self._b1 = float(b1)
+
+ def set_b2(self, b2: float) -> None:
+ """
+ Sets the b2 momentum decay constant.
+ """
+ if not isinstance(b2, (int, float)) or not 0 < b2 <= 1:
+ raise ValueError("b2 must be a numeric value between 0 and 1.")
+
+ self._b2 = float(b2)
diff --git a/pybop/optimisers/pints_optimisers.py b/pybop/optimisers/pints_optimisers.py
index d2e55371..4872973a 100644
--- a/pybop/optimisers/pints_optimisers.py
+++ b/pybop/optimisers/pints_optimisers.py
@@ -1,3 +1,5 @@
+from warnings import warn
+
from pints import CMAES as PintsCMAES
from pints import PSO as PintsPSO
from pints import SNES as PintsSNES
@@ -7,7 +9,7 @@
from pints import IRPropMin as PintsIRPropMin
from pints import NelderMead as PintsNelderMead
-from pybop import BasePintsOptimiser
+from pybop import AdamWImpl, BasePintsOptimiser
class GradientDescent(BasePintsOptimiser):
@@ -60,10 +62,43 @@ class Adam(BasePintsOptimiser):
pints.Adam : The PINTS implementation this class is based on.
"""
+ warn(
+ "Adam is deprecated and will be removed in a future release. Please use AdamW instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
def __init__(self, cost, **optimiser_kwargs):
super().__init__(cost, PintsAdam, **optimiser_kwargs)
+class AdamW(BasePintsOptimiser):
+ """
+ Implements the AdamW optimisation algorithm in PyBOP.
+
+ This class extends the AdamW optimiser, which is a variant of the Adam
+ optimiser that incorporates weight decay. AdamW is designed to be more
+ robust and stable for training deep neural networks, particularly when
+ using larger learning rates.
+
+ Parameters
+ ----------
+ **optimiser_kwargs : optional
+ Valid PyBOP option keys and their values, for example:
+ x0 : array_like
+ Initial position from which optimisation will start.
+ sigma0 : float
+ Initial step size.
+
+ See Also
+ --------
+ pybop.AdamWImpl : The PyBOP implementation this class is based on.
+ """
+
+ def __init__(self, cost, **optimiser_kwargs):
+ super().__init__(cost, AdamWImpl, **optimiser_kwargs)
+
+
class IRPropMin(BasePintsOptimiser):
"""
Implements the iRpropMin optimization algorithm.
diff --git a/tests/integration/test_spm_parameterisations.py b/tests/integration/test_spm_parameterisations.py
index 6e5108d7..9ae2b421 100644
--- a/tests/integration/test_spm_parameterisations.py
+++ b/tests/integration/test_spm_parameterisations.py
@@ -81,7 +81,7 @@ def spm_costs(self, model, parameters, cost_class, init_soc):
"optimiser",
[
pybop.SciPyDifferentialEvolution,
- pybop.Adam,
+ pybop.AdamW,
pybop.CMAES,
pybop.IRPropMin,
pybop.NelderMead,
diff --git a/tests/unit/test_optimisation.py b/tests/unit/test_optimisation.py
index 2eef89ad..97fe12fc 100644
--- a/tests/unit/test_optimisation.py
+++ b/tests/unit/test_optimisation.py
@@ -73,6 +73,7 @@ def two_param_cost(self, model, two_parameters, dataset):
(pybop.SciPyDifferentialEvolution, "SciPyDifferentialEvolution"),
(pybop.GradientDescent, "Gradient descent"),
(pybop.Adam, "Adam"),
+ (pybop.AdamW, "AdamW"),
(pybop.CMAES, "Covariance Matrix Adaptation Evolution Strategy (CMA-ES)"),
(pybop.SNES, "Seperable Natural Evolution Strategy (SNES)"),
(pybop.XNES, "Exponential Natural Evolution Strategy (xNES)"),
@@ -110,6 +111,7 @@ def test_optimiser_classes(self, two_param_cost, optimiser, expected_name):
pybop.SciPyDifferentialEvolution,
pybop.GradientDescent,
pybop.Adam,
+ pybop.AdamW,
pybop.SNES,
pybop.XNES,
pybop.PSO,
@@ -210,6 +212,39 @@ def test_optimiser_kwargs(self, cost, optimiser):
):
optimiser(cost=cost, bounds={"upper": [np.inf], "lower": [0.57]})
+ # Test AdamW hyperparameters
+ if optimiser in [pybop.AdamW]:
+ optim = optimiser(cost=cost, b1=0.9, b2=0.999, lambda_=0.1)
+ optim.pints_optimiser.set_b1(0.9)
+ optim.pints_optimiser.set_b2(0.9)
+ optim.pints_optimiser.set_lambda(0.1)
+
+ assert optim.pints_optimiser._b1 == 0.9
+ assert optim.pints_optimiser._b2 == 0.9
+ assert optim.pints_optimiser._lambda == 0.1
+
+ # Incorrect values
+ for i, match in (("Value", -1),):
+ with pytest.raises(
+ Exception, match="must be a numeric value between 0 and 1."
+ ):
+ optim.pints_optimiser.set_b1(i)
+ with pytest.raises(
+ Exception, match="must be a numeric value between 0 and 1."
+ ):
+ optim.pints_optimiser.set_b2(i)
+ with pytest.raises(
+ Exception, match="must be a numeric value between 0 and 1."
+ ):
+ optim.pints_optimiser.set_lambda(i)
+
+ # Check defaults
+ assert optim.pints_optimiser.n_hyper_parameters() == 5
+ assert not optim.pints_optimiser.running()
+ assert optim.pints_optimiser.x_guessed() == optim.pints_optimiser._x0
+ with pytest.raises(Exception):
+ optim.pints_optimiser.tell([0.1])
+
else:
# Check and update initial values
assert optim.x0 == cost.x0