Skip to content

Commit

Permalink
Squashed merge of #54
Browse files Browse the repository at this point in the history
  • Loading branch information
BradyPlanden committed Oct 31, 2023
1 parent 0dfad2f commit 0f91bab
Show file tree
Hide file tree
Showing 18 changed files with 185 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,6 @@ $RECYCLE.BIN/
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/python,macos,windows,linux,c

# Visual Studio Code settings
.vscode/*
14 changes: 14 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cff-version: 1.2.0
title: 'Python Battery Optimisation and Parameterisation (PyBOP)'
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Brady
family-names: Planden
- given-names: Nicola
family-names: Courtier
- given-names: David
family-names: Howey
repository-code: 'https://www.github.com/pybop-team/pybop'
6 changes: 3 additions & 3 deletions examples/scripts/rmse_estimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
# Form observations
Measurements = pd.read_csv("examples/scripts/Chen_example.csv", comment="#").to_numpy()
observations = [
pybop.Observed("Time [s]", Measurements[:, 0]),
pybop.Observed("Current function [A]", Measurements[:, 1]),
pybop.Observed("Voltage [V]", Measurements[:, 2]),
pybop.Dataset("Time [s]", Measurements[:, 0]),
pybop.Dataset("Current function [A]", Measurements[:, 1]),
pybop.Dataset("Voltage [V]", Measurements[:, 2]),
]

# Define model
Expand Down
38 changes: 26 additions & 12 deletions pybop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,44 @@
script_path = os.path.dirname(__file__)

#
# Model Classes
# Cost function class
#
from .costs.error_costs import RMSE

#
# Dataset class
#
from .datasets.base_dataset import Dataset

#
# Model classes
#
from .models.base_model import BaseModel
from .models import lithium_ion
from .models.BaseModel import BaseModel

#
# Parameterisation class
# Main optimisation class
#
from .parameters.parameter_set import ParameterSet
from .parameters.parameter import Parameter
from .datasets.observations import Observed
from .optimisation import Optimisation

#
# Priors class
# Optimiser class
#
from .optimisers.base_optimiser import BaseOptimiser
from .optimisers.nlopt_optimize import NLoptOptimize
from .optimisers.scipy_minimize import SciPyMinimize

#
# Parameter classes
#
from .parameters.base_parameter import Parameter
from .parameters.base_parameter_set import ParameterSet
from .parameters.priors import Gaussian, Uniform, Exponential

#
# Optimisation class
# Plotting class
#
from .optimisation import Optimisation
from .optimisers import BaseOptimiser
from .optimisers.NLoptOptimize import NLoptOptimize
from .optimisers.SciPyMinimize import SciPyMinimize
from .plotting.quick_plot import QuickPlot

#
# Remove any imported modules, so we don't expose them as part of pybop
Expand Down
92 changes: 92 additions & 0 deletions pybop/costs/error_costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import numpy as np


class RMSE:
"""
Defines the root mean square error cost function.
"""

def __init__(self):
self.name = "RMSE"

def compute(self, prediction, target):
# Check compatibility
if len(prediction) != len(target):
print(
"Length of vectors:",
len(prediction),
len(target),
)
raise ValueError(
"Measurement and simulated data length mismatch, potentially due to reaching a voltage cut-off"
)

print("Last Values:", prediction[-1], target[-1])

# Compute the cost
try:
return np.sqrt(np.mean((prediction - target) ** 2))

except Exception as e:
print(f"Error in RMSE calculation: {e}")
return None


class MLE:
"""
Defines the cost function for maximum likelihood estimation.
"""

def __init__(self):
self.name = "MLE"

def compute(self, prediction, target):
# Compute the cost
try:
return 0 # update with MLE residual

except Exception as e:
print(f"Error in RMSE calculation: {e}")
return None


class PEM:
"""
Defines the cost function for prediction error minimisation.
"""

def __init__(self):
self.name = "PEM"

def compute(self, prediction, target):
# Compute the cost
try:
return 0 # update with MLE residual

except Exception as e:
print(f"Error in RMSE calculation: {e}")
return None


class MAP:
"""
Defines the cost function for maximum a posteriori estimation.
"""

def __init__(self):
self.name = "MAP"

def compute(self, prediction, target):
# Compute the cost
try:
return 0 # update with MLE residual

except Exception as e:
print(f"Error in RMSE calculation: {e}")
return None

def sample(self, n_chains):
"""
Sample from the posterior distribution.
"""
pass
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import pybamm


class Observed:
class Dataset:
"""
Class for experimental Observations.
Class for experimental observations.
"""

def __init__(self, name, data):
self.name = name
self.data = data

def __repr__(self):
return f"Observation: {self.name} \n Data: {self.data}"
return f"Dataset: {self.name} \n Data: {self.data}"

def Interpolant(self):
if self.variable == "time":
Expand Down
2 changes: 1 addition & 1 deletion pybop/models/BaseModel.py → pybop/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class BaseModel:
"""
Base class for PyBOP models
Base class for pybop models.
"""

def __init__(self, name="Base Model"):
Expand Down
1 change: 0 additions & 1 deletion pybop/models/lithium_ion/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#
# Import lithium ion based models
#

from .base_echem import SPM, SPMe
2 changes: 1 addition & 1 deletion pybop/models/lithium_ion/base_echem.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pybamm
from ..BaseModel import BaseModel
from ..base_model import BaseModel


class SPM(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ def __init__(self):

def optimise(self, cost_function, x0, bounds, method=None):
"""
Optimise method to be overloaded by child classes.
Optimisiation method to be overloaded by child classes.
"""
# Set up optimisation
self.cost_function = cost_function
self.x0 = x0
self.method = method
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import nlopt
from .BaseOptimiser import BaseOptimiser
from .base_optimiser import BaseOptimiser


class NLoptOptimize(BaseOptimiser):
"""
Wrapper class for the NLOpt optimisation class. Extends the BaseOptimiser class.
Wrapper class for the NLOpt optimiser class. Extends the BaseOptimiser class.
"""

def __init__(self, method=None, x0=None, xtol=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from scipy.optimize import minimize
from .BaseOptimiser import BaseOptimiser
from .base_optimiser import BaseOptimiser


class SciPyMinimize(BaseOptimiser):
"""
Wrapper class for the Scipy optimisation class. Extends the BaseOptimiser class.
Wrapper class for the Scipy optimiser class. Extends the BaseOptimiser class.
"""

def __init__(self, cost_function, x0, bounds=None, options=None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ class Parameter:
Class for creating parameters in pybop.
"""

def __init__(self, param, value=None, prior=None, bounds=None):
self.name = param
def __init__(self, name, value=None, prior=None, bounds=None):
self.name = name
self.prior = prior
self.value = value
self.bounds = bounds
Expand All @@ -15,5 +15,8 @@ def __init__(self, param, value=None, prior=None, bounds=None):
# bounds checks and set defaults
# implement methods to assign and retrieve parameters

def update(self, value):
self.value = value

def __repr__(self):
return f"Parameter: {self.name} \n Prior: {self.prior} \n Bounds: {self.bounds}"
return f"Parameter: {self.name} \n Prior: {self.prior} \n Bounds: {self.bounds} \n Value: {self.value}"
16 changes: 16 additions & 0 deletions pybop/parameters/base_parameter_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pybamm


class ParameterSet:
"""
Class for creating parameter sets in pybop.
"""

def __new__(cls, method, name):
if method.casefold() == "pybamm":
try:
return pybamm.ParameterValues(name).copy()
except:
raise ValueError("Parameter set not found")
else:
raise ValueError("Only PyBaMM parameter sets are currently implemented")
13 changes: 0 additions & 13 deletions pybop/parameters/parameter_set.py

This file was deleted.

2 changes: 1 addition & 1 deletion pybop/parameters/priors.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __repr__(self):

class Exponential:
"""
exponential prior class.
Exponential prior class.
"""

def __init__(self, scale):
Expand Down
19 changes: 12 additions & 7 deletions tests/unit/test_parameterisations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import pybamm
import pytest
import numpy as np
import unittest


class TestModelParameterisation:
class TestModelParameterisation(unittest.TestCase):
"""
A class to test the model parameterisation methods.
"""
Expand All @@ -20,9 +21,9 @@ def test_spm(self):
solution = self.getdata(model, x0)

observations = [
pybop.Observed("Time [s]", solution["Time [s]"].data),
pybop.Observed("Current function [A]", solution["Current [A]"].data),
pybop.Observed("Voltage [V]", solution["Terminal voltage [V]"].data),
pybop.Dataset("Time [s]", solution["Time [s]"].data),
pybop.Dataset("Current function [A]", solution["Current [A]"].data),
pybop.Dataset("Voltage [V]", solution["Terminal voltage [V]"].data),
]

# Fitting parameters
Expand Down Expand Up @@ -63,9 +64,9 @@ def test_spme(self):
solution = self.getdata(model, x0)

observations = [
pybop.Observed("Time [s]", solution["Time [s]"].data),
pybop.Observed("Current function [A]", solution["Current [A]"].data),
pybop.Observed("Voltage [V]", solution["Terminal voltage [V]"].data),
pybop.Dataset("Time [s]", solution["Time [s]"].data),
pybop.Dataset("Current function [A]", solution["Current [A]"].data),
pybop.Dataset("Voltage [V]", solution["Terminal voltage [V]"].data),
]

# Fitting parameters
Expand Down Expand Up @@ -149,3 +150,7 @@ def test_parameter_set(self):
np.testing.assert_allclose(
parameter_test["Negative electrode active material volume fraction"], 0.75
)


if __name__ == "__main__":
unittest.main()

0 comments on commit 0f91bab

Please sign in to comment.