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

Issue 1500 - Print parameter info by submodel #3628

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
917a8d1
Add "by_submodel" feature and modify docstring
cringeyburger Dec 17, 2023
58586de
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Dec 17, 2023
d1513b8
Added line in CHANGELOG.md
cringeyburger Dec 17, 2023
699c329
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Dec 21, 2023
5d8c1fc
Implemented error handling if `get_parameter_info` method is used dir…
cringeyburger Dec 21, 2023
e4c05df
Implement changes to fix problem: Keep a track of what variables were…
cringeyburger Dec 21, 2023
27e7554
Implement changes to fix problem: Keep a track of what variables were…
cringeyburger Dec 21, 2023
bba8061
Created object `self.variables_by_submodel` which contains submodel's…
cringeyburger Dec 24, 2023
4ae39a5
Removed redundant comments.
cringeyburger Dec 24, 2023
087ba1b
Merge branch 'develop' into issue-1500-print-parameter-info-by-submodel
cringeyburger Dec 29, 2023
6c6b996
style: pre-commit fixes
pre-commit-ci[bot] Dec 29, 2023
5c22f90
Implemented "NotImplementedError" when user tries to use "get_paramet…
cringeyburger Dec 29, 2023
f5d5bf7
Implemented "NotImplementedError" when user tries to use "get_paramet…
cringeyburger Dec 29, 2023
c643b53
Updated Docstring of "get_parameter_info"
cringeyburger Dec 31, 2023
a792f49
Added Test Case for "NotImplementedError" when "get_parameter_info" o…
cringeyburger Jan 1, 2024
f0b6401
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Jan 3, 2024
2a156f7
Renamed variables, updated test, updated docstring
cringeyburger Jan 3, 2024
c23a55d
Added "_find_symbols_by_submodel" method and modified "get_parameter_…
cringeyburger Jan 4, 2024
d77b7cc
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Jan 13, 2024
30f66d5
Optimised "print_parameter_info" by simplification, improved readabil…
cringeyburger Jan 13, 2024
78c3785
Removed "calculate_max_lengths" and "format_table_row" from being nes…
cringeyburger Jan 15, 2024
b636650
Tests for "get_parameter_info" for both "by_submodel=False" and "by_s…
cringeyburger Jan 15, 2024
42a7fae
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Jan 21, 2024
c7ef815
Removed duplicate test in "test_base_submodel" for when "get_paramete…
cringeyburger Jan 21, 2024
7d3fb83
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Feb 1, 2024
a600580
added `test_get_parameter_info_submodel` test using custom model
cringeyburger Feb 1, 2024
f3ad7c4
added `test_print_parameter_info` and `test_print_parameter_info_subm…
cringeyburger Feb 3, 2024
29358f4
Merge branch 'develop' into issue-1500-print-parameter-info-by-submodel
cringeyburger Feb 3, 2024
77a126d
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Feb 11, 2024
be88384
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Feb 17, 2024
f1851e9
Modified `test_get_parameter_info_submodel` to include edge cases
cringeyburger Feb 17, 2024
77bbb0b
Merge branch 'develop' into issue-1500-print-parameter-info-by-submodel
rtimms Feb 20, 2024
70aef8f
Merge branch 'pybamm-team:develop' into issue-1500-print-parameter-in…
cringeyburger Feb 25, 2024
ae0737b
Merge branch 'develop' into issue-1500-print-parameter-info-by-submodel
cringeyburger Feb 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Serialisation added so models can be written to/read from JSON ([#3397](https://github.com/pybamm-team/PyBaMM/pull/3397))
- Added a `get_parameter_info` method for models and modified "print_parameter_info" functionality to extract all parameters and their type in a tabular and readable format ([#3584](https://github.com/pybamm-team/PyBaMM/pull/3584))
- Mechanical parameters are now a function of stoichiometry and temperature ([#3576](https://github.com/pybamm-team/PyBaMM/pull/3576))
- Added `by_submodel` feature in `print_parameter_info` method to allow users to print parameters and types of submodels in a tabular and readable format ([#3628](https://github.com/pybamm-team/PyBaMM/pull/3628))


## Bug fixes
Expand Down
82 changes: 48 additions & 34 deletions pybamm/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,37 +447,52 @@ def get_parameter_info(self):

return parameter_info

def print_parameter_info(self):
"""Print parameter information in a formatted table from a dictionary of parameters"""
info = self.get_parameter_info()
max_param_name_length = 0
max_param_type_length = 0

for param, param_type in info.values():
param_name_length = len(getattr(param, 'name', str(param)))
param_type_length = len(param_type)
max_param_name_length = max(max_param_name_length, param_name_length)
max_param_type_length = max(max_param_type_length, param_type_length)

header_format = f"| {{:<{max_param_name_length}}} | {{:<{max_param_type_length}}} |"
row_format = f"| {{:<{max_param_name_length}}} | {{:<{max_param_type_length}}} |"

table = [header_format.format("Parameter", "Type of parameter"),
header_format.format("=" * max_param_name_length, "=" * max_param_type_length)]

for param, param_type in info.values():
param_name = getattr(param, 'name', str(param))
param_name_lines = [param_name[i:i + max_param_name_length] for i in range(0, len(param_name), max_param_name_length)]
param_type_lines = [param_type[i:i + max_param_type_length] for i in range(0, len(param_type), max_param_type_length)]
max_lines = max(len(param_name_lines), len(param_type_lines))

for i in range(max_lines):
param_line = param_name_lines[i] if i < len(param_name_lines) else ""
type_line = param_type_lines[i] if i < len(param_type_lines) else ""
table.append(row_format.format(param_line, type_line))

for line in table:
print(line)
def print_parameter_info(self, by_submodel=False):
"""
Print parameter information in a formatted table from a dictionary of parameters

Parameters
----------
by_submodel : bool, optional
Whether to print the parameter info submodel wise or not (default False)
"""
if by_submodel:
for submodel_name, submodel in self.submodels.items():
submodel_info = submodel.get_parameter_info()
if not submodel_info:
print(f"'{submodel_name}' submodel parameters: \nNo parameters\n")
else:
print(f"'{submodel_name}' submodel parameters:")
submodel.print_parameter_info()
else:
info = self.get_parameter_info()
max_param_name_length = 0
max_param_type_length = 0

for param, param_type in info.values():
param_name_length = len(getattr(param, 'name', str(param)))
param_type_length = len(param_type)
max_param_name_length = max(max_param_name_length, param_name_length)
max_param_type_length = max(max_param_type_length, param_type_length)

header_format = f"| {{:<{max_param_name_length}}} | {{:<{max_param_type_length}}} |"
row_format = f"| {{:<{max_param_name_length}}} | {{:<{max_param_type_length}}} |"

table = [header_format.format("Parameter", "Type of parameter"),
header_format.format("=" * max_param_name_length, "=" * max_param_type_length)]

for param, param_type in info.values():
param_name = getattr(param, 'name', str(param))
param_name_lines = [param_name[i:i + max_param_name_length] for i in range(0, len(param_name), max_param_name_length)]
param_type_lines = [param_type[i:i + max_param_type_length] for i in range(0, len(param_type), max_param_type_length)]
max_lines = max(len(param_name_lines), len(param_type_lines))

for i in range(max_lines):
param_line = param_name_lines[i] if i < len(param_name_lines) else ""
type_line = param_type_lines[i] if i < len(param_type_lines) else ""
table.append(row_format.format(param_line, type_line))

print("\n".join(table) + "\n")

def _find_symbols(self, typ):
"""Find all the instances of `typ` in the model"""
Expand Down Expand Up @@ -565,9 +580,8 @@ def build_coupled_variables(self):
)
)
try:
self.variables.update(
submodel.get_coupled_variables(self.variables)
)
submodel_coupled_vars = submodel.get_coupled_variables(self.variables)
self.variables[submodel_name] = submodel_coupled_vars
Copy link
Contributor

@rtimms rtimms Dec 22, 2023

Choose a reason for hiding this comment

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

variables is already a dict with keys corresponding to variable names and values corresponding to their symbols. you'll need to store the coupled variables that each submodel defines in a new object.

i don't know if it makes sense to do this when building the model, or if it should be done separately when getting the parameter info. does it make a difference if we store e.g. self.variables and self.variables_by_submodel where the latter is a dict of dicts (e.g. {"submodel 1": {"var1": x, ...}, ...}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I know, there isn't much benefit to make a separate dict. other from keeping info clean and sorted + avoid searching a large dictionary.

Copy link
Member

Choose a reason for hiding this comment

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

i don't know if it makes sense to do this when building the model, or if it should be done separately when getting the parameter info

I think the latter is better. Accessing elements from a dictionary is usually very fast – even if a dict of dicts is used to print out the information, then it can be "normalised" with the name of a submodel as the key (i.e., print the values of the sub-dictionary, similar to how pandas can normalise JSON) to account for by_submodel=True.

If by_submodel is False, it can pretty-print the whole dict in a table like it currently does. This method might be better, but would also be harder.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay.. so what should be done now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I add an object in build_coupled_variables, e.g. self.variables_by_submodel which has a structure:

{
    'submodel1': {'coupled_var1': value1, 'coupled_var2': value2, ...},
    'submodel2': {'coupled_var3': value3, 'coupled_var4': value4, ...},
    ...
}

The code for this would be the same as before:

                    try:
                        model_var_copy = self.variables.copy()
                        submodel_coupled_result = submodel.get_coupled_variables(self.variables)
                        self.variables_by_submodel[submodel_name] = {
                            key: submodel_coupled_result[key]
                            for key in submodel_coupled_result
                            if key not in model_var_copy
                        }
                        self.variables.update(
                            submodel.get_coupled_variables(self.variables)
                        )
                        submodels.remove(submodel_name)

This stores both self.variables and self.variables_by_submodel.

Next, We can modify get_parameter_info to include by_submodel=False too.
In this way, if we set print_paramter_info(by_submodel=False), then get_parameter_info will print the usual way.
If we set print_paramter_info(by_submodel=True), then we change the parameter_info returned by get_parameter_info by submodel wise.

NOTE: I have been coding something for this. I was successful in getting the coupled variables. Now I am experimenting with parameter processing and trying to make the links between get_parameter_info and print_parameter_info better. Also trying to make the code efficient and cleaner to look at... Will commit soon

submodels.remove(submodel_name)
except KeyError as key:
if len(submodels) == 1 or count == 100:
Expand Down
16 changes: 16 additions & 0 deletions pybamm/models/submodels/base_submodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ def domain(self, domain):
def domain_Domain(self):
return self._domain, self._Domain

def get_parameter_info(self):
"""
Extracts the parameter information and returns it as a dictionary.
To get a list of all parameter-like objects without extra information,
use :py:attr:`model.parameters`.

Returns
-------
NotImplementedError:
This method is not available for direct use on submodels. It is recommended to use on the full model.
cringeyburger marked this conversation as resolved.
Show resolved Hide resolved
"""
raise NotImplementedError(
"Cannot use get_parameter_info directly on a submodel. "
"Please use it on the full model."
)

def get_fundamental_variables(self):
"""
A public method that creates and returns the variables in a submodel which can
Expand Down