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

Rework math handling #639

Merged
merged 25 commits into from
Sep 30, 2024
Merged

Rework math handling #639

merged 25 commits into from
Sep 30, 2024

Conversation

irm-codebase
Copy link
Contributor

@irm-codebase irm-codebase commented Jul 16, 2024

Partially fixes #608 #619

Summary of changes in this pull request

This PR aims to improve the way in which math is handled across a model init->build->solve->postprocess pipeline.

  • MathDocumentation is now in postprocess. Model no longer includes it (instead, it's an input to it).
  • model._model_def_path -> model._def_path: the processing of this was made clearer to remove unnecessary code during __init__. As a result, load.py is now scenarios.py, which is what it was really doing all along.
  • ModelMath introduced to handle math better. This preprocess class contains the following:
    • math file additions (either user math or pre-defined math).
    • math file history checks, in order of addition
    • forbidding adding the same file twice (easy to remove, if this would cause trouble)
    • math validation against the schema and against external math dictionaries (before adding to the model).
    • methods to easily save / read math from netCDF attributes.
  • _model_data.attrs["applied_additional_math"] was eliminated in favor of math.history, which is also saved/read from netCDFs.
  • model._model_data.attrs no longer contains "math".
  • base.yaml -> plan.yaml. This is to avoid extra logic when checking math modes)... plus its clearer, imo.

Additional discussion

Stuff that I could add to this PR very easily, with permission.

Removal of double math objects

Featured in the PR!

Removing model.math is very possible now, because all the logic needed to process files is contained in ModelMath. Users can still reach it if we declare a @property that returns model.backend.math.data, or a warning if the backend has not been built / initialized.

I have not done this because it needs discussing (e.g., should we partially initialise the backend during init?). This resulted in some additional attributes to the ModelBackendGenerator, which can be easily removed.

Enabling no math ('clean') models

Featured in the PR!

This PR also allows us to fulfill #606 very easily. I believe that changing config.init.add_math::default::[] to config.init.added_math::default::['plan'] is the most transparent way and the easiest to maintain.

However, this might lead to some users accidentally omitting plan math if they add their own math... but this could hint at a documentation problem on our side.

Another option is to add config.init.default_math::default:true, but it's not my favorite because it seems intransparent...

Reviewer checklist

  • Test(s) added to cover contribution
  • Documentation updated
  • Changelog updated
  • Coverage maintained or improved

@irm-codebase irm-codebase marked this pull request as draft July 16, 2024 10:16
@irm-codebase irm-codebase changed the title Rework math Rework math handling Jul 16, 2024
@irm-codebase
Copy link
Contributor Author

irm-codebase commented Jul 18, 2024

Tried to improve model_math.py by redefining it as a @dataclass. Unfortunately, adding AttrDict as a parameter to a dataclass results in funky behavior. dataclasses.asdict fails, for example.

Ultimately I decided it was not worth the hassle, but this leads to unnecessary code (__eq__, __repr__, etc). Maybe we could improve AttrDict in the future to enable this usage.

@irm-codebase
Copy link
Contributor Author

irm-codebase commented Jul 18, 2024

@brynpickering requesting a preeliminary review of this, with some open questions:

@irm-codebase irm-codebase self-assigned this Jul 18, 2024
@irm-codebase irm-codebase marked this pull request as ready for review July 18, 2024 20:27
@irm-codebase irm-codebase marked this pull request as draft July 18, 2024 20:27
@irm-codebase irm-codebase changed the base branch from rework-model-data-handling to main July 18, 2024 20:28
@irm-codebase irm-codebase marked this pull request as ready for review July 18, 2024 20:28
Copy link
Contributor Author

@irm-codebase irm-codebase left a comment

Choose a reason for hiding this comment

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

Few comments to help with the review. Let me know what you think!

docs/hooks/generate_math_docs.py Outdated Show resolved Hide resolved
src/calliope/backend/__init__.py Show resolved Hide resolved
src/calliope/backend/backend_model.py Show resolved Hide resolved
src/calliope/backend/backend_model.py Outdated Show resolved Hide resolved
src/calliope/backend/latex_backend_model.py Show resolved Hide resolved
src/calliope/preprocess/model_math.py Show resolved Hide resolved
src/calliope/preprocess/scenarios.py Show resolved Hide resolved
tests/conftest.py Outdated Show resolved Hide resolved
tests/test_postprocess_math_documentation.py Outdated Show resolved Hide resolved
tests/test_preprocess_model_math.py Show resolved Hide resolved
Copy link
Member

@brynpickering brynpickering left a comment

Choose a reason for hiding this comment

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

Nice cleanup. Just a few comments:

  1. math documentation building isn't really a postprocessing step, it's a parallel process to building/solving the optimisation problem. Not sure where it should go, tbh.
  2. This is getting us one step closer to removing math from calliope.Model entirely, and I think I'm in favour of that. It just requires moving the validating_math_strings method to somewhere more suitable.
  3. I like being able to add a math dictionary and not just a reference to a math YAML. It's effectively equivalent to adding scenarios vs override_dict at calliope.Model instatiation. Ideally we'd add this to calliope.Model.build and allow that dict to completely replace the math dict or to be added as another override. If the approach matches scenarios/override_dict then add_math list would be applied first, followed by the math_dict. It just then needs a flag for ignoring math/base.yaml(/math/plan.yaml).
  4. Shall we use CalliopeMath as the class name? Many libraries seem to do this to avoid name clashes when using their lib as a dependency.

docs/hooks/generate_math_docs.py Outdated Show resolved Hide resolved
src/calliope/backend/__init__.py Outdated Show resolved Hide resolved
src/calliope/backend/backend_model.py Outdated Show resolved Hide resolved
src/calliope/backend/backend_model.py Outdated Show resolved Hide resolved
src/calliope/backend/backend_model.py Outdated Show resolved Hide resolved
Comment on lines +86 to +87
model_def_with_overrides["nodes"] = model_def_with_overrides["locations"]
del model_def_with_overrides["locations"]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
model_def_with_overrides["nodes"] = model_def_with_overrides["locations"]
del model_def_with_overrides["locations"]
model_def_with_overrides["nodes"] = model_def_with_overrides.pop("locations")

def _combine_overrides(overrides: AttrDict, scenario_overrides: list):
combined_override_dict = AttrDict()
for override in scenario_overrides:
try:
Copy link
Member

Choose a reason for hiding this comment

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

I tend to find try/except to be less readable. if override not in overrides is much more explicit

tests/conftest.py Outdated Show resolved Hide resolved
tests/test_preprocess_model_math.py Outdated Show resolved Hide resolved
@@ -561,8 +526,8 @@ def validate_math_strings(self, math_dict: dict) -> None:
"""
Copy link
Member

Choose a reason for hiding this comment

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

I guess it should move to the parsing module? It just needs a list of possible parameters passed to it so that it can use that in the parsing.

Copy link

codecov bot commented Jul 19, 2024

Codecov Report

Attention: Patch coverage is 94.46809% with 13 lines in your changes missing coverage. Please review.

Project coverage is 95.95%. Comparing base (99e5211) to head (d923a4a).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/calliope/preprocess/scenarios.py 86.66% 4 Missing ⚠️
src/calliope/postprocess/math_documentation.py 90.32% 1 Missing and 2 partials ⚠️
src/calliope/util/tools.py 66.66% 1 Missing and 1 partial ⚠️
src/calliope/backend/gurobi_backend_model.py 80.00% 0 Missing and 1 partial ⚠️
src/calliope/backend/latex_backend_model.py 80.00% 0 Missing and 1 partial ⚠️
src/calliope/backend/pyomo_backend_model.py 83.33% 0 Missing and 1 partial ⚠️
src/calliope/model.py 96.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #639      +/-   ##
==========================================
- Coverage   95.98%   95.95%   -0.04%     
==========================================
  Files          26       29       +3     
  Lines        3985     4025      +40     
  Branches      838      775      -63     
==========================================
+ Hits         3825     3862      +37     
- Misses         70       72       +2     
- Partials       90       91       +1     
Files with missing lines Coverage Δ
src/calliope/attrdict.py 96.48% <100.00%> (ø)
src/calliope/backend/__init__.py 100.00% <100.00%> (ø)
src/calliope/backend/backend_model.py 97.98% <100.00%> (+0.01%) ⬆️
src/calliope/backend/expression_parser.py 93.75% <100.00%> (+0.01%) ⬆️
src/calliope/backend/parsing.py 96.99% <ø> (ø)
src/calliope/io.py 96.80% <100.00%> (-0.04%) ⬇️
src/calliope/preprocess/__init__.py 100.00% <100.00%> (ø)
src/calliope/preprocess/model_math.py 100.00% <100.00%> (ø)
src/calliope/preprocess/time.py 94.05% <100.00%> (ø)
src/calliope/backend/gurobi_backend_model.py 95.66% <80.00%> (ø)
... and 6 more

@brynpickering
Copy link
Member

@irm-codebase I've made a bunch of changes, partly following offline discussions between us.

  1. I moved math to only be introduced at the calliope.Model.build step. I think this generally works well.
  2. I updated calliope.Model.math to calliope.Model.applied_math so it's clear that the math has already been applied (since the optimisation problem must have been built!)
  3. I've added math dict application as a build arg and enabled ignoring the mode math with a config.build option (default=false) ignore_mode_math. This could also be flipped to something like include_mode_math (default=true).
  4. I've moved math dict parsing validation to the calliope.Model.build step. It duplicated dict parsing that is done when adding each optimisation component, but does it much quicker (so you can catch math errors quickly for a large model). I've also added a config.build option to activate it (default=true): pre_validate_math_strings.

@irm-codebase
Copy link
Contributor Author

@brynpickering fantastic! It's a lot of changes, so I'll review them once I'm back this Monday.

Copy link
Contributor Author

@irm-codebase irm-codebase left a comment

Choose a reason for hiding this comment

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

Went through the changes. I'm quite happy with them!

I've added some suggestions to avoid bugs catched by mypy, and to improve logic in some parts.

src/calliope/backend/backend_model.py Show resolved Hide resolved
src/calliope/backend/backend_model.py Show resolved Hide resolved
src/calliope/backend/backend_model.py Show resolved Hide resolved
src/calliope/backend/backend_model.py Show resolved Hide resolved
src/calliope/backend/latex_backend_model.py Show resolved Hide resolved
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@brynpickering do you still consider this not a postprocessing feature after going through the changes?
should we change something here?

src/calliope/model.py Show resolved Hide resolved
src/calliope/preprocess/model_math.py Outdated Show resolved Hide resolved
src/calliope/preprocess/model_math.py Show resolved Hide resolved
src/calliope/preprocess/model_math.py Show resolved Hide resolved
@irm-codebase
Copy link
Contributor Author

@brynpickering what's next for this feature?
Will we wait until the approach to parameters is set, or should we merge this beforehand?

@brynpickering
Copy link
Member

@irm-codebase I'm unsure as to the best approach to this now, especially if we move math back to the model instatiation step when introducing parameters. Still, there's lots of useful updates here, so perhaps you could clean up the conflicts and assuming @sjpfenninger is also happy with it we can merge and deal with where math is defined and the issue of parameters at a later stage.

Could I ask that you also resolve all the comments that are now outdated? There's lots on here that I have the impression are still open issues but may have been dealt with by your updates.

@irm-codebase
Copy link
Contributor Author

@brynpickering I think that's the best approach.
I'll clean this up and we can go from there.

@irm-codebase
Copy link
Contributor Author

@brynpickering I've updated everything, and tests pass.
However, there seems to be a nasty pre-commit issue that prevents fixes to go through on my side...
For some reason it's re-arranging the imports every time...

ruff check --fix will sort them one way....
and pre-commit will cancel it, leading to a failure.

Seems related to breaking changes in 0.6 (https://astral.sh/blog/ruff-v0.6.0#migrating-to-v06)

Have you faced this issue before?

@brynpickering
Copy link
Member

Yes, I'm having the same issues. It is just a mismatch in the version of ruff you're using on autosave vs the one pre-commit has updated to. Best is to use fromEnvironment import strategy and then make sure that the version in your dev conda env is the same as in pre-commit.

@irm-codebase
Copy link
Contributor Author

That's odd... that's the configuration I am using. I even tried updating the pre-commit hook and ruff to 0.6.8 (the newest), and it still fails. Let me know if you find an approach that works.

@brynpickering
Copy link
Member

Weird, works fine for me having updated ruff to v0.6 in my calliope dev env

Copy link
Member

@brynpickering brynpickering left a comment

Choose a reason for hiding this comment

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

Let's get this merged in and hope we can clean up around the edges as necessary!

@brynpickering brynpickering merged commit 0fc4f5e into main Sep 30, 2024
12 of 13 checks passed
@brynpickering brynpickering deleted the rework-math branch September 30, 2024 20:53
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.

Potential desync in model configuration at the model.py level
2 participants