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

Allow linked design parameters #533

Merged
merged 21 commits into from
Oct 17, 2024
Merged

Conversation

NicolaCourtier
Copy link
Member

@NicolaCourtier NicolaCourtier commented Oct 10, 2024

Description

Enables relations to be enforced between the optimisation parameters and other model parameters for the purpose of design optimisation. Adds an example showing how to link the porosity to the active material volume fraction.

Issue reference

Fixes #532

Review

Before you mark your PR as ready for review, please ensure that you've considered the following:

  • Updated the CHANGELOG.md in reverse chronological order (newest at the top) with a concise description of the changes, including the PR number.
  • Noted any breaking changes, including details on how it might impact existing functionality.

Type of change

  • New Feature: A non-breaking change that adds new functionality.
  • Optimization: A code change that improves performance.
  • Examples: A change to existing or additional examples.
  • Bug Fix: A non-breaking change that addresses an issue.
  • Documentation: Updates to documentation or new documentation for new features.
  • Refactoring: Non-functional changes that improve the codebase.
  • Style: Non-functional changes related to code style (formatting, naming, etc).
  • Testing: Additional tests to improve coverage or confirm functionality.
  • Other: (Insert description of change)

Key checklist:

  • No style issues: $ pre-commit run (or $ nox -s pre-commit) (see CONTRIBUTING.md for how to set this up to run automatically when committing locally, in just two lines of code)
  • All unit tests pass: $ nox -s tests
  • The documentation builds: $ nox -s doctest

You can run integration tests, unit tests, and doctests together at once, using $ nox -s quick.

Further checks:

  • Code is well-commented, especially in complex or unclear areas.
  • Added tests that prove my fix is effective or that my feature works.
  • Checked that coverage remains or improves, and added tests if necessary to maintain or increase coverage.

Thank you for contributing to our project! Your efforts help us to deliver great software.

Copy link

codecov bot commented Oct 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.08%. Comparing base (c8b00e6) to head (bdd8a12).
Report is 2 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop     #533   +/-   ##
========================================
  Coverage    99.08%   99.08%           
========================================
  Files           52       52           
  Lines         3605     3622   +17     
========================================
+ Hits          3572     3589   +17     
  Misses          33       33           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@NicolaCourtier NicolaCourtier marked this pull request as ready for review October 10, 2024 19:20
@NicolaCourtier
Copy link
Member Author

This is not the neatest implementation and only works for design problems, but it's functionality that I would like to use!

Copy link
Contributor

@martinjrobins martinjrobins left a comment

Choose a reason for hiding this comment

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

I'm not sure this is such a good idea. I'm not a fan of monkeypatching functions on a method (it can lead to some very confusing debugging later down the line), and its probably going to be confusing for users if they have to replace a function with signature def update_linked_parameters(self, parameter_set: None): with one that has a different signature def update_linked_parameters(parameter_set: Inputs):.

In the example given, you are effectivly removing the "Positive electrode porosity" parameter from the model, but not really because this parameter still exists in the underlying pybamm model. If the model is used in a design problem it won't have this parameter because your custom function is overwriting it every time, but if it is used in a fitting problem it will have this parameter. Worse yet, if the user accidiently tries to add "Positive electrode porosity" to the list of fitted parameters in a design problem, pybop will happily accept this, even through the model doesn't have this parameter in it anymore. I think this will lead to all sorts of confusions

@NicolaCourtier
Copy link
Member Author

These are all good points... Let's find another way to implement this. I would like to be able to run this example and also change/add parameter relations to test different design assumptions.

@martinjrobins
Copy link
Contributor

I would do something like this to alter the underlying pybamm model:

import pybop
import pybamm

# The aim of this script is to show how to systematically update
# design parameters which depend on the optimisation parameters.

# Define parameter set and model
parameter_set = pybop.ParameterSet.pybamm("Chen2020", formation_concentrations=True)
model = pybop.lithium_ion.SPMe(parameter_set=parameter_set)

parameter_to_replace = pybamm.Parameter("Positive electrode porosity")
replacement_symbol = 1 - pybamm.Parameter("Positive electrode active material volume fraction")

def replace_parameter(symbol):
    if symbol == parameter_to_replace:
        return replacement_symbol
    else:
        return symbol.create_copy(symbol.children)

def replace_dict(dictionary):
    return {key: replace_parameter(value) for key, value in dictionary.items()}

def replace_list(lst):
    return [replace_parameter(item) for item in lst]


new_model = model.pybamm_model.new_copy()
new_model.rhs = replace_dict(new_model.rhs)
new_model.algebraic = replace_dict(new_model.algebraic)
new_model.initial_conditions = replace_dict(new_model.initial_conditions)
new_model.boundary_conditions = replace_dict(new_model.boundary_conditions)
new_model.variables = replace_dict(new_model.variables)
new_model.events = replace_list(new_model.events)

model.pybamm_model = new_model


# Fitting parameters
parameters = pybop.Parameters(
    pybop.Parameter(
        "Positive electrode thickness [m]",
        prior=pybop.Gaussian(7.56e-05, 0.1e-05),
        bounds=[65e-06, 10e-05],
    ),
    pybop.Parameter(
        "Positive electrode active material volume fraction",
        prior=pybop.Gaussian(0.6, 0.15),
        bounds=[0.1, 0.9],
    ),
)

# Define test protocol
experiment = pybop.Experiment(
    [
        "Discharge at 1C until 2.5 V (5 seconds period)",
        "Hold at 2.5 V for 30 minutes or until 10 mA (5 seconds period)",
    ],
)
signal = ["Voltage [V]", "Current [A]"]

# Generate problem
problem = pybop.DesignProblem(
    model,
    parameters,
    experiment,
    signal=signal,
    initial_state={"Initial SoC": 1.0},
    update_capacity=True,
)

# Define the cost
cost = pybop.GravimetricEnergyDensity(problem)

# Run optimisation
optim = pybop.XNES(
    cost, verbose=True, allow_infeasible_solutions=False, max_iterations=10
)
x, final_cost = optim.run()
print("Estimated parameters:", x)
print(f"Initial gravimetric energy density: {cost(optim.x0):.2f} Wh.kg-1")
print(f"Optimised gravimetric energy density: {cost(x):.2f} Wh.kg-1")

# Plot the timeseries output
pybop.quick_plot(problem, problem_inputs=x, title="Optimised Comparison")

# Plot the cost landscape with optimisation path
pybop.plot2d(optim, steps=5)

@martinjrobins
Copy link
Contributor

You could hide all the messing around with the pybamm model in a replace function on the pybop model

@NicolaCourtier
Copy link
Member Author

This is very nice, thanks @martinjrobins!

Copy link
Contributor

@martinjrobins martinjrobins left a comment

Choose a reason for hiding this comment

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

looks great @NicolaCourtier. I've made a few suggestions to improve the test on evaluate_symbol

tests/unit/test_parameter_sets.py Outdated Show resolved Hide resolved
tests/unit/test_parameter_sets.py Outdated Show resolved Hide resolved
Copy link
Contributor

@martinjrobins martinjrobins left a comment

Choose a reason for hiding this comment

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

looks good to me!

Copy link
Member

@BradyPlanden BradyPlanden left a comment

Choose a reason for hiding this comment

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

Looks good, nice one!

@NicolaCourtier NicolaCourtier merged commit 01fda0c into develop Oct 17, 2024
31 checks passed
@NicolaCourtier NicolaCourtier deleted the 532-linked-parameters branch October 17, 2024 09:25
BradyPlanden pushed a commit that referenced this pull request Nov 25, 2024
* Remove unused arguments

* Create linked_parameters.py

* Update CHANGELOG.md

* swap to use expression in parameter set

* Update README.md

* Update optim result

* Update CHANGELOG.md

* Update linked_parameters.py

* Test evaluate_symbol

* Enforce volume fraction check

* Update tests/unit/test_parameter_sets.py

Co-authored-by: Martin Robinson <[email protected]>

* Update test_parameter_sets.py

---------

Co-authored-by: martinjrobins <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow linked parameter updates for design optimisation
3 participants