Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure for improved modularity #60

Closed
wants to merge 12 commits into from
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/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep

64 changes: 64 additions & 0 deletions examples/scripts/parameter_estimation_from_Chen_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pybop
import pandas as pd
import numpy as np
from os import path

# Load dataset
data_path = path.join(pybop.script_path, "..", "examples/scripts/Chen_example.csv")
measurements = pd.read_csv(data_path, comment="#").to_numpy()
observations = [
pybop.Dataset("Time [s]", measurements[:, 0]),
pybop.Dataset("Current function [A]", measurements[:, 1]),
pybop.Dataset("Voltage [V]", measurements[:, 2]),
]

# Define model
# parameter_set = pybop.ParameterSet("pybamm", "Chen2020")
model = pybop.models.lithium_ion.SPM()

# Define fitting parameters
params = [
pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Gaussian(0.75, 0.05),
bounds=[0.65, 0.85],
),
pybop.Parameter(
"Positive electrode active material volume fraction",
prior=pybop.Gaussian(0.65, 0.05),
bounds=[0.55, 0.75],
),
]

# Define the cost to optimise
cost = pybop.RMSE()
signal = "Voltage [V]"

# Select optimiser
optimiser = pybop.NLoptOptimize(x0=params)

# Build the optimisation problem
parameterisation = pybop.Optimisation(
cost=cost,
dataset=observations,
signal=signal,
model=model,
optimiser=optimiser,
fit_parameters=params,
)

# Run the optimisation problem
x, output, final_cost, num_evals = parameterisation.run()

print("Estimated parameters:", x)
print("Final cost:", final_cost)


# get MAP estimate, starting at a random initial point in parameter space
# optimisation.map(x0=[p.sample() for p in params])

# or sample from posterior
# optimisation.sample(1000, n_chains=4, ....)

# or SOBER
# optimisation.sober()
90 changes: 90 additions & 0 deletions examples/scripts/parameter_estimation_from_synthetic_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import pybop
import pybamm
import pandas as pd
import numpy as np


def getdata(x0):
# Define the "ground truth" model with the default parameter set
model = pybamm.lithium_ion.SPM()
params = model.default_parameter_values

# Overwrite the uncertain parameters
params.update(
{
"Negative electrode active material volume fraction": x0[0],
"Positive electrode active material volume fraction": x0[1],
}
)

# Define the experimental protocol
experiment = pybamm.Experiment(
[
(
"Discharge at 2C for 5 minutes (1 second period)",
"Rest for 2 minutes (1 second period)",
"Charge at 1C for 5 minutes (1 second period)",
"Rest for 2 minutes (1 second period)",
),
]
* 2
)

# Run a forward simulation
sim = pybamm.Simulation(model, experiment=experiment, parameter_values=params)

# Return the simulation results
return sim.solve()


# Define the initial values of the uncertain parameters
x0 = np.array([0.55, 0.63])

# Generate observations
solution = getdata(x0)
observations = [
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),
]

# Define the model with the default parameter set
model = pybop.models.lithium_ion.SPM()
model.parameter_set = model.pybamm_model.default_parameter_values

# Initialise the fitting parameters
params = [
pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Gaussian(0.5, 0.05),
bounds=[0.35, 0.75],
),
pybop.Parameter(
"Positive electrode active material volume fraction",
prior=pybop.Gaussian(0.65, 0.05),
bounds=[0.45, 0.85],
),
]

# Define the cost to optimise
cost = pybop.RMSE()
signal = "Voltage [V]"

# Select optimiser
optimiser = pybop.NLoptOptimize(x0=params)

# Build the optimisation problem
parameterisation = pybop.Optimisation(
cost=cost,
dataset=observations,
signal=signal,
model=model,
optimiser=optimiser,
fit_parameters=params,
)

# Run the optimisation problem
x, output, final_cost, num_evals = parameterisation.run()

print("Estimated parameters:", x) # x = [0.54452026, 0.63064801]
print("Final cost:", final_cost)
50 changes: 0 additions & 50 deletions examples/scripts/rmse-estimisation.py

This file was deleted.

47 changes: 33 additions & 14 deletions pybop/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#
# Root of the pybop module.
# Root of the PyBOP module.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to sort out a standard for this, real low priority though.

# Provides access to all shared functionality (models, solvers, etc.).
#
# This file is adapted from Pints
# (see https://github.com/pints-team/pints)
#

import sys
import os
from os import path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe don't need to change, depends on whether we need any functionality from os in the future.


#
# Version info
Expand All @@ -21,31 +21,50 @@
# loss of information
FLOAT_FORMAT = "{: .17e}"
# Absolute path to the PyBOP repo
script_path = os.path.abspath(__file__)
script_path = path.dirname(__file__)

#
# Model Classes
# Cost function class
#
from .models import BaseModel, lithium_ion
from .costs.error_costs import RMSE

#
# Parameterisation class
# Dataset class
#
from .identification import Parameterisation, ParameterSet, Parameter, Observed
from .datasets.base_dataset import Dataset

#
# Priors class
# Model classes
#
from .priors import Gaussian, Uniform, Exponential
from .models.base_model import BaseModel
from .models import lithium_ion

#
# Optimisation class
# Main optimisation class
#
from .optimisation import BaseOptimisation
from .optimisation.NLoptOptimize import NLoptOptimize
from .optimisation.SciPyMinimize import SciPyMinimize
from .optimisation import Optimisation

#
# Remove any imported modules, so we don't expose them as part of pybop
# Optimiser class
#
from .optimisers.base_optimiser import BaseOptimiser
from .optimisers.nlopt_optimize import NLoptOptimize
from .optimisers.scipy_minimize import SciPyMinimize
from .optimisers.pints_optimiser import PintsOptimiser, PintsError, PintsBoundaries

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

#
# Plotting class
#
from .plotting.quick_plot import QuickPlot

#
# Remove any imported modules, so we don't expose them as part of PyBOP
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for the above.

#
del sys
File renamed without changes.
93 changes: 93 additions & 0 deletions pybop/costs/error_costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import pybop
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:
cost = np.sqrt(np.mean((prediction - target) ** 2))
except:
print("Error in RMSE calculation")

return cost


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:
cost = 0 # update with MLE residual
except:
print("Error in MLE calculation")

return cost


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:
cost = 0 # update with MLE residual
except:
print("Error in PEM calculation")

return cost


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:
cost = 0 # update with MLE residual
except:
print("Error in MAP calculation")

return cost

def sample(self, n_chains):
"""
Sample from the posterior distribution.
"""
pass
Empty file added pybop/datasets/__init__.py
Empty file.
Loading