Skip to content

Commit

Permalink
Merge pull request #2112 from pybamm-team/model-parameter-combos
Browse files Browse the repository at this point in the history
add example for combinations of models and parameters
  • Loading branch information
valentinsulzer authored Aug 3, 2022
2 parents aa5f85d + 1ba86e7 commit 3c7ad7c
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Moved general code about submodels to `BaseModel` instead of `BaseBatteryModel`, making it easier to build custom models from submodels. ([#2169](https://github.com/pybamm-team/PyBaMM/pull/2169))
- Events can now be plotted as a regular variable (under the name "Event: event_name", e.g. "Event: Minimum voltage [V]") ([#2158](https://github.com/pybamm-team/PyBaMM/pull/2158))
- Added example showing how to print whether a model is compatible with a parameter set ([#2112](https://github.com/pybamm-team/PyBaMM/pull/2112))
- Added SEI growth on cracks ([#2104](https://github.com/pybamm-team/PyBaMM/pull/2104))
- Added Arrhenius temperature dependence of SEI growth ([#2104](https://github.com/pybamm-team/PyBaMM/pull/2104))
- The "Inner SEI reaction proportion" parameter actually gets used now ([#2104](https://github.com/pybamm-team/PyBaMM/pull/2104))
Expand Down
38 changes: 38 additions & 0 deletions examples/scripts/print_model_parameter_combinations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# Loop through all combinations of models and parameters and print whether that
# model can be parameterized by those parameters
#
import pybamm

all_options = pybamm.BatteryModelOptions({}).possible_options
all_parameter_sets = [
x
for x in dir(pybamm.parameter_sets)
if not x.startswith("__") and x not in ["Sulzer2019"]
]

for option_name, option_list in all_options.items():
for parameter_set in all_parameter_sets:
parameter_values = pybamm.ParameterValues(parameter_set)
for option_value in option_list[
1:
]: # skip the first one since that's the default
options = {option_name: option_value}
try:
model = pybamm.lithium_ion.SPM(options.copy())
except pybamm.OptionError as e:
print(f"Cannot create model with {options}. (OptionError: {str(e)})")
except pybamm.ModelError as e:
# todo: properly resolve the cases that raise these errors
print(f"Cannot create model with {options}. (ModelError: {str(e)})")
except AttributeError as e:
# todo: properly resolve the cases that raise these errors
print(f"Cannot create model with {options}. (AttributeError: {str(e)})")
else:
output = f"{options} with {parameter_set} parameters: "
try:
parameter_values.process_model(model)
output += "success"
except KeyError:
output += "failure"
print(output)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Negative electrode Bruggeman coefficient (electrode),0,default,
Negative electrode cation signed stoichiometry,-1,,
Negative electrode electrons in reaction,1,,
Negative electrode charge transfer coefficient,0.5,Ai 2020,
Negative electrode double-layer capacity [F.m-2],0,,
Negative electrode double-layer capacity [F.m-2],0.2,,guess
Negative electrode exchange-current density [A.m-2],[function]graphite_electrolyte_exchange_current_density_Dualfoil1998,,
,,,
# Density,,,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Positive electrode Bruggeman coefficient (electrode),0,Ai 2020,
Positive electrode cation signed stoichiometry,-1,,
Positive electrode electrons in reaction,1,,
Positive electrode charge transfer coefficient,0.5,Ai 2020,
Negative electrode double-layer capacity [F.m-2],0,,#??
Positive electrode double-layer capacity [F.m-2],0.2,,guess
Positive electrode exchange-current density [A.m-2],[function]lico2_electrolyte_exchange_current_density_Dualfoil1998,,
,,,
# Density,,,
Expand Down
7 changes: 7 additions & 0 deletions pybamm/models/full_battery_models/base_battery_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,13 @@ def options(self, extra_options):
raise pybamm.OptionError(
"x-average side reactions cannot be 'false' for SPM models"
)
if isinstance(self, pybamm.lithium_ion.SPM) and not isinstance(
self, pybamm.lithium_ion.MPM
):
if options["particle size"] == "distribution":
raise pybamm.OptionError(
"'particle size' should be 'single' for SPM and SPMe models"
)
if isinstance(self, pybamm.lead_acid.BaseModel):
if options["thermal"] != "isothermal" and options["dimensionality"] != 0:
raise pybamm.OptionError(
Expand Down
68 changes: 6 additions & 62 deletions pybamm/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import timeit
from collections import defaultdict
from platform import system
import difflib

import numpy as np
import pkg_resources
Expand All @@ -32,68 +33,9 @@ def root_dir():


class FuzzyDict(dict):
def levenshtein_ratio(self, s, t):
"""
Calculates levenshtein distance between two strings s and t.
Uses the formula from
https://www.datacamp.com/community/tutorials/fuzzy-string-python
"""
# Initialize matrix of zeros
rows = len(s) + 1
cols = len(t) + 1
distance = np.zeros((rows, cols), dtype=int)

# Populate matrix of zeros with the indices of each character of both strings
for i in range(1, rows):
for k in range(1, cols):
distance[i][0] = i
distance[0][k] = k

# Iterate over the matrix to compute the cost of deletions, insertions and/or
# substitutions
for col in range(1, cols):
for row in range(1, rows):
if s[row - 1] == t[col - 1]:
# If the characters are the same in the two strings in a given
# position [i,j] then the cost is 0
cost = 0
else:
# In order to align the results with those of the Python Levenshtein
# package, the cost of a substitution is 2.
cost = 2
distance[row][col] = min(
distance[row - 1][col] + 1, # Cost of deletions
distance[row][col - 1] + 1, # Cost of insertions
distance[row - 1][col - 1] + cost, # Cost of substitutions
)
# Computation of the Levenshtein Distance Ratio
ratio = ((len(s) + len(t)) - distance[row][col]) / (len(s) + len(t))
return ratio

def get_best_matches(self, key):
"""Get best matches from keys"""
key = key.lower()
best_three = []
lowest_score = 0
for k in self.keys():
score = self.levenshtein_ratio(k.lower(), key)
# Start filling out the list
if len(best_three) < 3:
best_three.append((k, score))
# Sort once the list has three elements, using scores
if len(best_three) == 3:
best_three.sort(key=lambda x: x[1], reverse=True)
lowest_score = best_three[-1][1]
# Once list is full, start checking new entries
else:
if score > lowest_score:
# Replace last element with new entry
best_three[-1] = (k, score)
# Sort and update lowest score
best_three.sort(key=lambda x: x[1], reverse=True)
lowest_score = best_three[-1][1]

return [x[0] for x in best_three]
return difflib.get_close_matches(key, list(self.keys()), n=3, cutoff=0.5)

def __getitem__(self, key):
try:
Expand All @@ -119,7 +61,8 @@ def search(self, key, print_values=False):
both the keys and values will be printed. Otherwise just the values will
be printed. If no results are found, the best matches are printed.
"""
key = key.lower()
key_in = key
key = key_in.lower()

# Sort the keys so results are stored in alphabetical order
keys = list(self.keys())
Expand All @@ -135,7 +78,8 @@ def search(self, key, print_values=False):
# If no results, return best matches
best_matches = self.get_best_matches(key)
print(
f"No results for search using '{key}'. Best matches are {best_matches}"
f"No results for search using '{key_in}'. "
f"Best matches are {best_matches}"
)
elif print_values:
# Else print results, including dict items
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def test_x_average_options(self):
with self.assertRaisesRegex(pybamm.OptionError, "cannot be 'false' for SPM"):
pybamm.lithium_ion.SPM(options)

def test_distribution_options(self):
with self.assertRaisesRegex(pybamm.OptionError, "particle size"):
pybamm.lithium_ion.SPM({"particle size": "distribution"})

def test_new_model(self):
model = pybamm.lithium_ion.SPM({"thermal": "x-full"})
new_model = model.new_copy()
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ def test_url_gets_to_stdout(self):

# Test bad var search (returns best matches)
with patch("sys.stdout", new=StringIO()) as fake_out:
model.variables.search("bad var")
model.variables.search("Electrolyte cot")
out = (
"No results for search using 'bad var'. "
"No results for search using 'Electrolyte cot'. "
"Best matches are ['Electrolyte concentration', "
"'Electrode potential']\n"
)
Expand Down

0 comments on commit 3c7ad7c

Please sign in to comment.