From 39c79367b0650879722f2f2bb31854365a040355 Mon Sep 17 00:00:00 2001 From: Mathias Berg Rosendal <77012503+Mathias157@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:01:05 +0200 Subject: [PATCH] Improving symbol_to_df and added a fast .locate_results() function (#23) * Made symbol_to_df accept variables or equations too * Made function for locating results, if you just want an overview instead of the more heavy loading * Improving docs * Making the plot maps function work for any cases in the commodity input * Release 0.4.3 --- docs/conf.py | 2 +- docs/examples/execution.md | 2 +- docs/examples/postprocessing.md | 43 +++++++++++++++++++++++- docs/examples/preprocessing.md | 2 +- docs/get_started/installation.md | 2 +- environment.yaml | 2 +- pyproject.toml | 2 +- src/pybalmorel/classes.py | 43 ++++++++++++++++++------ src/pybalmorel/plotting/maps_balmorel.py | 1 + src/pybalmorel/utils.py | 25 +++++++++++++- tests/test_postprocessing.py | 4 +-- 11 files changed, 108 insertions(+), 20 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 509ccf8..e99835f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,7 @@ project = "pybalmorel" copyright = "2024, Mathias Berg Rosendal, Théodore Le Nalinec" author = "Mathias Berg Rosendal, Théodore Le Nalinec" -release = "0.4.2" +release = "0.4.3" exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".testenv", ".testenv/**"] diff --git a/docs/examples/execution.md b/docs/examples/execution.md index 2cf7df8..58da92c 100644 --- a/docs/examples/execution.md +++ b/docs/examples/execution.md @@ -1,6 +1,6 @@ # Execution -The 'Balmorel' class can also be used to run Balmorel scenarios. +The 'Balmorel' class can also be used to run Balmorel scenarios. Check out [this notebook](https://github.com/Mathias157/pybalmorel/blob/master/examples/Execution.ipynb) or the examples below. ```python from pybalmorel import Balmorel diff --git a/docs/examples/postprocessing.md b/docs/examples/postprocessing.md index 6b92bac..57ccfe4 100644 --- a/docs/examples/postprocessing.md +++ b/docs/examples/postprocessing.md @@ -1,3 +1,44 @@ # Post-Processing -There are several functions to plot energy balances, transmission capacity maps and a ipywidget to interactively plot bar charts, check out [this notebook](https://github.com/Mathias157/pybalmorel/blob/master/examples/PostProcessing.ipynb). \ No newline at end of file +There are several functions to plot energy balances, transmission capacity maps and a ipywidget to interactively plot bar charts, check out [this notebook](https://github.com/Mathias157/pybalmorel/blob/master/examples/PostProcessing.ipynb). + +The example below uses the Balmorel class to load results. + +## Interactively Plotting All Results +```python +from pybalmorel import Balmorel + +# Load Balmorel +model = Balmorel('path/to/Balmorel') + +# Collect results +model.collect_results() + +# Plot bar charts with an interactive GUI +model.results.interactive_bar_chart() +``` + +## Transmission Maps and Energy Balances + +Using the collected results below, the examples below illustrate how to plot figures of transmission capacities and save them. + +```python +# Plot electricity transmission capacities in the first scenario, year 2050 +model.results.plot_map(model.results.sc[0], 'electricity', 2050) + +# Plot total heat energy balance in the second scenario, year 2050 +model.results.plot_profile('heat', 2050, model.results.sc[1]) +``` + +## Getting Specific Results +The `model.results` is actually a `MainResults` class. The example below illustrates how to get specific results using that class. + +```python +from pybalmorel import MainResults + +mainresults_files = ['MainResults_SC1.gdx', 'MainResults_SC2.gdx'] +paths = ['path/to/folder/with/SC1', 'path/to/folder/with/SC2'] +results = MainResults(mainresults_files, paths=paths) +``` + +If the files are in the same folder, you just need to input a single path to the paths argument. The class will name the scenarios 'SC1' and 'SC2' by default and store them in a list in `results.sc`, but you can also provide your own names with the scenario_names argument. \ No newline at end of file diff --git a/docs/examples/preprocessing.md b/docs/examples/preprocessing.md index 3584f66..e3b5dd1 100644 --- a/docs/examples/preprocessing.md +++ b/docs/examples/preprocessing.md @@ -1,6 +1,6 @@ # Pre-Processing -The examples below show how to create single .inc-files, loading all .inc files using the Balmorel class, and defining geography using an interactive GUI. +The examples below show how to create single .inc-files, loading all .inc files using the Balmorel class, and defining geography using an interactive GUI. A [notebook](https://github.com/Mathias157/pybalmorel/blob/master/examples/PreProcessing.ipynb) can also be downloaded from the GitHub repository. ## Create an .inc File The 'IncFile' class is a handy class for creating .inc files that are the input for Balmorel. diff --git a/docs/get_started/installation.md b/docs/get_started/installation.md index 996fcd2..c8daeff 100644 --- a/docs/get_started/installation.md +++ b/docs/get_started/installation.md @@ -21,6 +21,6 @@ dependencies: - ipykernel>=6.29.5 - pip - pip: - - pybalmorel==0.4.2 + - pybalmorel==0.4.3 ``` diff --git a/environment.yaml b/environment.yaml index b5037d8..d5fbd6d 100644 --- a/environment.yaml +++ b/environment.yaml @@ -12,4 +12,4 @@ dependencies: - pytest=8.3.3 - pip - pip: - - pybalmorel==0.4.2 + - pybalmorel==0.4.3 diff --git a/pyproject.toml b/pyproject.toml index f542206..968ccbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pybalmorel" -version = "0.4.2" +version = "0.4.3" maintainers = [ { name="Mathias Berg Rosendal", email="mathiasros@gmail.com"}, { name="Théodore Le Nalinec"}, diff --git a/src/pybalmorel/classes.py b/src/pybalmorel/classes.py index 99afdf5..e71d2c0 100644 --- a/src/pybalmorel/classes.py +++ b/src/pybalmorel/classes.py @@ -307,23 +307,46 @@ def __init__(self, model_folder: str, gams_system_directory: str = None): else: print('Folder %s not added to scenario as the necessary %s/model/Balmorel.gms and/or %s/model/cplex.op4 did not exist'%tuple([SC]*3)) - def collect_results(self): - - files = [] - paths = [] - scenario_names = [] + def locate_results(self): + """ + Locates results, which is faster than collecting them if you just want an overview + """ + + self.files = [] + self.paths = [] + self.scenario_names = [] + self.scfolder_to_scname = {} + self.scname_to_scfolder = {} for SC in self.scenarios: path = os.path.join(self.path, '%s/model'%SC) mainresults_files = pd.Series(os.listdir(path)) mainresults_files = mainresults_files[(mainresults_files.str.find('MainResults') != -1) & (mainresults_files.str.find('.gdx') != -1)] - files += list(mainresults_files) - paths += [path]*len(mainresults_files) + self.files += list(mainresults_files) + self.paths += [path]*len(mainresults_files) if len(mainresults_files) == 1: - scenario_names += [SC] + self.scenario_names += [SC] + self.scfolder_to_scname[SC] = [SC] + self.scname_to_scfolder[SC] = SC else: - scenario_names += list(mainresults_files) + mainresults_files = ( + mainresults_files + .str.replace('MainResults_', '') + .str.replace('.gdx', '') + ) + self.scenario_names += list(mainresults_files) + self.scfolder_to_scname[SC] = list(mainresults_files) + + for scenario_name in mainresults_files: + self.scname_to_scfolder[scenario_name] = SC + + def collect_results(self): + """ + Collects results + """ + + self.locate_results() - self.results = MainResults(files=files, paths=paths, scenario_names=scenario_names, system_directory=self._gams_system_directory) + self.results = MainResults(files=self.files, paths=self.paths, scenario_names=self.scenario_names, system_directory=self._gams_system_directory) def run(self, scenario: str, cmd_line_options: dict = {}): diff --git a/src/pybalmorel/plotting/maps_balmorel.py b/src/pybalmorel/plotting/maps_balmorel.py index 71e5c2d..cd1aee8 100644 --- a/src/pybalmorel/plotting/maps_balmorel.py +++ b/src/pybalmorel/plotting/maps_balmorel.py @@ -159,6 +159,7 @@ def plot_map(path_to_result: str, font_hub = 12 #Font size of hub labels font_region = 10 #Font size of region labels line_decimals = 1 #Number of decimals shown for line capacities + COMMODITY = COMMODITY.capitalize() # For Elec if COMMODITY == 'Electricity': line_width_constant = .2 #Constant related to thickness of lines: the higher the number, the narrower the lines will be diff --git a/src/pybalmorel/utils.py b/src/pybalmorel/utils.py index bbf87b0..17b470e 100644 --- a/src/pybalmorel/utils.py +++ b/src/pybalmorel/utils.py @@ -36,6 +36,25 @@ def create_parameter_columns(df: pd.DataFrame, return df +def create_variable_columns(df: pd.DataFrame, + db: gams.GamsDatabase, + symbol: str, + mainresult_symbol_columns: list, + cols: Tuple[list, None]): + if cols == None: + try: + df.columns = mainresult_symbol_columns[symbol] + ['Unit', 'Value', 'Marginal', 'Lower', 'Upper', 'Scale'] + except (ValueError, KeyError): + try: + df.columns = mainresult_symbol_columns[symbol] + ['Value', 'Marginal', 'Lower', 'Upper', 'Scale'] + except KeyError: + # If no standard format exists, just use columns from GAMS + df.columns = db[symbol].domains_as_strings + ['Value', 'Marginal', 'Lower', 'Upper', 'Scale'] + else: + df.columns = cols + + return df + def create_set_columns(df: pd.DataFrame, db: gams.GamsDatabase, symbol: str, @@ -77,8 +96,12 @@ def symbol_to_df(db: gams.GamsDatabase, symbol: str, elif type(db[symbol]) == gams.GamsSet: df = pd.DataFrame([tuple(rec.keys) for rec in db[symbol] ]) df = create_set_columns(df, db, symbol, preformatted_columns[result_type.lower()], cols) + elif type(db[symbol]) == gams.GamsVariable or type(db[symbol]) == gams.GamsEquation: + df = dict( (tuple(rec.keys), rec.level) for rec in db[symbol] ) + df = pd.DataFrame(df, index=['Value', 'Marginal', 'Lower', 'Upper', 'Scale']).T.reset_index() # Convert to dataframe + df = create_variable_columns(df, db, symbol, preformatted_columns[result_type.lower()], cols) else: - print('Variables or equations not supported yet') + raise TypeError('%s is not supported by symbol_to_df'%(str(type(db[symbol])))) if print_explanatory_text: print(db[symbol].text) diff --git a/tests/test_postprocessing.py b/tests/test_postprocessing.py index d7d07f4..2c9fd72 100644 --- a/tests/test_postprocessing.py +++ b/tests/test_postprocessing.py @@ -54,8 +54,8 @@ def test_MainResults(): assert 'electricity_profile.png' in os.listdir('tests/output') and 'heat_profile.png' in os.listdir('tests/output') and 'hydrogen_profile.png' in os.listdir('tests/output') # Test map - fig, ax = res.plot_map('SC2', 'Electricity', 2050) + fig, ax = res.plot_map('SC2', 'elecTriciTY', 2050) fig.savefig('tests/output/electricity_map.png') - fig, ax = res.plot_map('SC2', 'Hydrogen', 2050) + fig, ax = res.plot_map('SC2', 'HYDROGEN', 2050) fig.savefig('tests/output/hydrogen_map.png') assert 'electricity_map.png' in os.listdir('tests/output') and 'hydrogen_map.png' in os.listdir('tests/output') \ No newline at end of file