From 279b9aa277b4390c37cef427affc3acf0ae09a21 Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 13:58:39 +1100 Subject: [PATCH 01/17] move save_results to results.io and add load_results --- dodo.py | 2 +- src/ispypsa/model/__init__.py | 2 -- src/ispypsa/model/save_results.py | 7 ------- src/ispypsa/results/__init__.py | 3 +++ src/ispypsa/results/io.py | 12 ++++++++++++ 5 files changed, 16 insertions(+), 10 deletions(-) delete mode 100644 src/ispypsa/model/save_results.py create mode 100644 src/ispypsa/results/__init__.py create mode 100644 src/ispypsa/results/io.py diff --git a/dodo.py b/dodo.py index 540c1bfd..3d031a05 100644 --- a/dodo.py +++ b/dodo.py @@ -15,8 +15,8 @@ add_lines_to_network, initialise_network, run, - save_results, ) +from ispypsa.results import save_results from ispypsa.templater.dynamic_generator_properties import ( template_generator_dynamic_properties, ) diff --git a/src/ispypsa/model/__init__.py b/src/ispypsa/model/__init__.py index ece4d007..075c794a 100644 --- a/src/ispypsa/model/__init__.py +++ b/src/ispypsa/model/__init__.py @@ -4,7 +4,6 @@ from ispypsa.model.initialise import initialise_network from ispypsa.model.lines import add_lines_to_network from ispypsa.model.run import run -from ispypsa.model.save_results import save_results __all__ = [ "add_buses_to_network", @@ -13,5 +12,4 @@ "add_carriers_to_network", "add_lines_to_network", "run", - "save_results", ] diff --git a/src/ispypsa/model/save_results.py b/src/ispypsa/model/save_results.py deleted file mode 100644 index 0a62bf06..00000000 --- a/src/ispypsa/model/save_results.py +++ /dev/null @@ -1,7 +0,0 @@ -from pathlib import Path - -import pypsa - - -def save_results(network: pypsa.Network, pypsa_outputs_location: Path) -> None: - network.export_to_hdf5(Path(pypsa_outputs_location, "network.hdf5")) diff --git a/src/ispypsa/results/__init__.py b/src/ispypsa/results/__init__.py new file mode 100644 index 00000000..70113a59 --- /dev/null +++ b/src/ispypsa/results/__init__.py @@ -0,0 +1,3 @@ +from ispypsa.results.io import load_results, save_results + +__all__ = ["load_results", "save_results"] diff --git a/src/ispypsa/results/io.py b/src/ispypsa/results/io.py new file mode 100644 index 00000000..ba3242bd --- /dev/null +++ b/src/ispypsa/results/io.py @@ -0,0 +1,12 @@ +from pathlib import Path +import pypsa + + +def save_results(network: pypsa.Network, pypsa_outputs_location: Path) -> None: + network.export_to_hdf5(Path(pypsa_outputs_location, "network.hdf5")) + + +def load_results(pypsa_outputs_location: str | Path) -> pypsa.Network: + network = pypsa.Network() + network.import_from_hdf5(Path(pypsa_outputs_location, "network.hdf5")) + return network From 8b24b944307ce9064680f42030ada3f9c6f8eebb Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 14:43:40 +1100 Subject: [PATCH 02/17] add latitude and longitude to buses in pypsa network --- src/ispypsa/model/buses.py | 12 +++++++----- src/ispypsa/translator/mappings.py | 6 +++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ispypsa/model/buses.py b/src/ispypsa/model/buses.py index da2efa1f..e2e9166d 100644 --- a/src/ispypsa/model/buses.py +++ b/src/ispypsa/model/buses.py @@ -5,7 +5,7 @@ def _add_bus_to_network( - bus_name: str, network: pypsa.Network, path_to_demand_traces: Path + bus_definition: dict, network: pypsa.Network, path_to_demand_traces: Path ) -> None: """ Adds a Bus to the network and if a demand trace for the Bus exists, also adds the @@ -19,8 +19,9 @@ def _add_bus_to_network( Returns: None """ - network.add(class_name="Bus", name=bus_name) - + bus_definition["class_name"] = "Bus" + network.add(**bus_definition) + bus_name = bus_definition["name"] demand_trace_path = path_to_demand_traces / Path(f"{bus_name}.parquet") if demand_trace_path.exists(): demand = pd.read_parquet(demand_trace_path) @@ -48,6 +49,7 @@ def add_buses_to_network(network: pypsa.Network, path_pypsa_inputs: Path) -> Non """ buses = pd.read_csv(path_pypsa_inputs / Path("buses.csv")) path_to_demand_traces = path_pypsa_inputs / Path("demand_traces") - buses["name"].apply( - lambda x: _add_bus_to_network(x, network, path_to_demand_traces) + buses.apply( + lambda row: _add_bus_to_network(row.to_dict(), network, path_to_demand_traces), + axis=1, ) diff --git a/src/ispypsa/translator/mappings.py b/src/ispypsa/translator/mappings.py index eb0cadaf..98531de8 100644 --- a/src/ispypsa/translator/mappings.py +++ b/src/ispypsa/translator/mappings.py @@ -4,7 +4,11 @@ "fuel_type": "carrier", } -_BUS_ATTRIBUTES = {"node_id": "name"} +_BUS_ATTRIBUTES = { + "node_id": "name", + "substation_longitude": "x", + "substation_latitude": "y", +} _LINE_ATTRIBUTES = { "flow_path_name": "name", From 08a99ed394c2f364b4cf8419ab2682ed35bcca7d Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 16:01:13 +1100 Subject: [PATCH 03/17] add cartopy for plotting --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 06a29bbb..b0c8d87b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "isp-trace-parser>=1.0.0", "pyarrow>=18.0.0", "tables>=3.10.1", + "cartopy>=0.24.1", ] readme = "README.md" requires-python = ">= 3.10" From b24e034e51b646c63bb927e7aabd635e23fc4b3d Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 17:19:02 +1100 Subject: [PATCH 04/17] first pass map of total generation by carrier plot --- src/ispypsa/results/plotting.py | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/ispypsa/results/plotting.py diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py new file mode 100644 index 00000000..dc7919ce --- /dev/null +++ b/src/ispypsa/results/plotting.py @@ -0,0 +1,107 @@ +import matplotlib +import matplotlib.axes +import matplotlib.figure +import matplotlib.pyplot as plt +import pandas as pd +import pypsa +from matplotlib.patches import Patch +import cartopy.crs as ccrs + +from ispypsa.config.validators import ModelConfig + +_CARRIER_COLOUR_MAPPING = { + # corresponds to distillate in OpenElectricity + "Liquid Fuel": "#E46E56", + "Black Coal": "#251C00", + "Brown Coal": "#675B42", + "Gas": "#E78114", + "Water": "#ACE9FE", + "Solar": "#FECE00", + "Wind": "#2A7E3F", + # corresponds to gas_hydrogen in OpenElectricity + "Hyblend": "#C75338", +} +"""Colour mapping based on mapping from OpenElectricity""" + + +def plot_map_of_energy_generation_by_carrier( + network: pypsa.Network, + config: ModelConfig, + figure_size_in_inches: tuple[float, float], + bus_size_scaling_factor: float | None = None, + flow_colormap: str = "inferno", + flow_arrow_size_scaling_factor: float | None = None, + min_max_latitudes: tuple[float, float] = (-44.0, -15.0), + min_max_longitudes: tuple[float, float] = (137.5, 156.0), + subplot_kwargs=dict(projection=ccrs.PlateCarree()), + pypsa_plot_kwargs={}, +) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: + gen = _sum_generation_by_bus_and_carrier(network) + # size of ~1.0 appears to work well from trial-and-error + if bus_size_scaling_factor is None: + bus_size_scaling_factor = min(1.0 / gen.groupby("bus").sum()) + if flow_arrow_size_scaling_factor is None: + flow_arrow_size_scaling_factor = min(1.0 / network.lines_t.p0.mean().abs()) + title = _create_plot_title(config) + + fig, ax = plt.subplots( + 1, + 1, + figsize=figure_size_in_inches, + subplot_kw=subplot_kwargs, + ) + collection = network.plot( + geomap=True, + color_geomap=True, + ax=ax, + bus_colors=_CARRIER_COLOUR_MAPPING, + bus_sizes=gen * bus_size_scaling_factor, + flow="mean", + line_colors=network.lines_t.p0.mean().abs(), + line_cmap=flow_colormap, + line_widths=flow_arrow_size_scaling_factor, + boundaries=[ + min_max_longitudes[0], + min_max_longitudes[1], + min_max_latitudes[0], + min_max_latitudes[1], + ], + title=title, + **pypsa_plot_kwargs, + ) + plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") + _add_fuel_type_legend(ax, _CARRIER_COLOUR_MAPPING) + return fig, ax + + +def _sum_generation_by_bus_and_carrier(network: pypsa.Network) -> pd.Series: + generators_with_total_generation = network.generators.assign( + total_generation=network.generators_t.p.sum() + ) + return generators_with_total_generation.groupby( + ["bus", "carrier"] + ).total_generation.sum() + + +def _create_plot_title(config: ModelConfig) -> str: + run_name = config.ispypsa_run_name + (start, end) = (config.traces.start_year, config.traces.end_year) + year_type = config.traces.year_type + if year_type == "fy": + year_range = f"FY{str(start)[-2:]}-{str(end)[-2:]}" + else: + year_range = f"{start}-{end}" + title = ( + f"ISPyPSA run '{run_name}'\nTotal energy generation by fuel type, {year_range}" + ) + return title + + +def _add_fuel_type_legend( + ax: matplotlib.axes.Axes, carrier_colour_mapping: dict[str, str] +) -> None: + legend_patches = [ + Patch(color=color, label=carrier) + for carrier, color in carrier_colour_mapping.items() + ] + ax.legend(handles=legend_patches, title="Fuel Type", loc="upper right") From d65b1f8a4111727aed412421f03205048db677f1 Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 17:21:24 +1100 Subject: [PATCH 05/17] fix flow arrow sizing --- src/ispypsa/results/plotting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index dc7919ce..b747a387 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -40,8 +40,9 @@ def plot_map_of_energy_generation_by_carrier( # size of ~1.0 appears to work well from trial-and-error if bus_size_scaling_factor is None: bus_size_scaling_factor = min(1.0 / gen.groupby("bus").sum()) + # size of ~300.0 appears to work well from trial-and-error if flow_arrow_size_scaling_factor is None: - flow_arrow_size_scaling_factor = min(1.0 / network.lines_t.p0.mean().abs()) + flow_arrow_size_scaling_factor = min(300.0 / network.lines_t.p0.mean().abs()) title = _create_plot_title(config) fig, ax = plt.subplots( From ee3bf399e42a0d6f751c6dad0df75ab29fe2d801 Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 17:34:31 +1100 Subject: [PATCH 06/17] add plotting to dodo --- dodo.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/dodo.py b/dodo.py index 3d031a05..1037c0c9 100644 --- a/dodo.py +++ b/dodo.py @@ -16,7 +16,7 @@ initialise_network, run, ) -from ispypsa.results import save_results +from ispypsa.results import load_results, save_results from ispypsa.templater.dynamic_generator_properties import ( template_generator_dynamic_properties, ) @@ -41,6 +41,7 @@ _translate_generator_timeseries, ) from ispypsa.translator.lines import translate_flow_paths_to_lines +from ispypsa.results.plotting import plot_map_of_energy_generation_by_carrier root_folder = Path("ispypsa_runs") @@ -57,6 +58,7 @@ _PYPSA_FRIENDLY_DIRECTORY = Path(run_folder, "pypsa_friendly") _PARSED_TRACE_DIRECTORY = Path(config.traces.path_to_parsed_traces) _PYPSA_OUTPUTS_DIRECTORY = Path(run_folder, "outputs") +_RESULTS_DIRECTORY = Path(run_folder, "results") configure_logging() @@ -203,6 +205,19 @@ def create_and_run_pypsa_model( save_results(network, pypsa_outputs_location) +def create_results( + config: ModelConfig, pypsa_outputs_location: Path, results_location: Path +) -> None: + create_or_clean_task_output_folder(results_location) + network = load_results(pypsa_outputs_location) + fig, ax = plot_map_of_energy_generation_by_carrier( + network, config, figure_size_in_inches=(7, 10) + ) + fig.savefig( + Path(results_location, "map_of_total_generation_by_carrier.png"), dpi=600 + ) + + def task_cache_required_tables(): return { "actions": [(build_parsed_workbook_cache, [_PARSED_WORKBOOK_CACHE])], @@ -288,3 +303,18 @@ def task_create_and_run_pypsa_model(): ], "targets": [Path(_PYPSA_OUTPUTS_DIRECTORY, "network.hdf5")], } + + +def task_produce_results(): + return { + "actions": [ + ( + create_results, + [config, _PYPSA_OUTPUTS_DIRECTORY, _RESULTS_DIRECTORY], + ) + ], + "file_dep": [ + Path(_PYPSA_OUTPUTS_DIRECTORY, "network.hdf5"), + ], + "targets": [Path(_RESULTS_DIRECTORY, "map_of_total_generation_by_carrier.png")], + } From ef752de1af245a358fd48a24998f166e1e64e53f Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 17:34:39 +1100 Subject: [PATCH 07/17] ignore results folders --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dc6a0dbb..f0a2745f 100644 --- a/.gitignore +++ b/.gitignore @@ -170,6 +170,7 @@ scratch.py ispypsa_runs/**/*.csv ispypsa_runs/**/*.parquet ispypsa_runs/**/*.hdf5 +ispypsa_runs/**/results/** # ignore doit database .doit* From 9bc40bfbed393b2c81f7e9045043b5fc1144aa02 Mon Sep 17 00:00:00 2001 From: prakaa Date: Wed, 27 Nov 2024 17:38:19 +1100 Subject: [PATCH 08/17] handle single year title --- src/ispypsa/results/plotting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index b747a387..40aad994 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -88,8 +88,10 @@ def _create_plot_title(config: ModelConfig) -> str: run_name = config.ispypsa_run_name (start, end) = (config.traces.start_year, config.traces.end_year) year_type = config.traces.year_type - if year_type == "fy": + if year_type == "fy" and start != end: year_range = f"FY{str(start)[-2:]}-{str(end)[-2:]}" + elif start == end: + year_range = f"{start}" else: year_range = f"{start}-{end}" title = ( From 8fa5aa1a039e9eddbf1127bd4b61b85306b3b9db Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 11:26:37 +1100 Subject: [PATCH 09/17] consolidate plot kwargs so that user-defined plots kwargs override ispypsa defaults --- src/ispypsa/results/plotting.py | 70 +++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index 40aad994..f5f84a10 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -27,49 +27,59 @@ def plot_map_of_energy_generation_by_carrier( network: pypsa.Network, config: ModelConfig, - figure_size_in_inches: tuple[float, float], + figure_size_inches: tuple[float, float], bus_size_scaling_factor: float | None = None, flow_colormap: str = "inferno", flow_arrow_size_scaling_factor: float | None = None, min_max_latitudes: tuple[float, float] = (-44.0, -15.0), min_max_longitudes: tuple[float, float] = (137.5, 156.0), - subplot_kwargs=dict(projection=ccrs.PlateCarree()), - pypsa_plot_kwargs={}, + pypsa_plot_kwargs: dict = dict(), + figure_kwargs: dict = dict(), + subplot_kwargs: dict = dict(), ) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: - gen = _sum_generation_by_bus_and_carrier(network) + total_gen = _sum_generation_by_bus_and_carrier(network) # size of ~1.0 appears to work well from trial-and-error if bus_size_scaling_factor is None: - bus_size_scaling_factor = min(1.0 / gen.groupby("bus").sum()) + bus_size_scaling_factor = min(1.0 / total_gen.groupby("bus").sum()) # size of ~300.0 appears to work well from trial-and-error if flow_arrow_size_scaling_factor is None: flow_arrow_size_scaling_factor = min(300.0 / network.lines_t.p0.mean().abs()) title = _create_plot_title(config) - fig, ax = plt.subplots( 1, 1, - figsize=figure_size_in_inches, - subplot_kw=subplot_kwargs, + subplot_kw=_consolidate_plot_kwargs( + dict(projection=ccrs.PlateCarree()), subplot_kwargs + ), + **_consolidate_plot_kwargs( + dict( + figsize=figure_size_inches, + ), + figure_kwargs, + ), ) - collection = network.plot( - geomap=True, - color_geomap=True, - ax=ax, - bus_colors=_CARRIER_COLOUR_MAPPING, - bus_sizes=gen * bus_size_scaling_factor, - flow="mean", - line_colors=network.lines_t.p0.mean().abs(), - line_cmap=flow_colormap, - line_widths=flow_arrow_size_scaling_factor, - boundaries=[ - min_max_longitudes[0], - min_max_longitudes[1], - min_max_latitudes[0], - min_max_latitudes[1], - ], - title=title, - **pypsa_plot_kwargs, + pyspsa_plot_kwgs = _consolidate_plot_kwargs( + dict( + geomap=True, + color_geomap=True, + ax=ax, + bus_colors=_CARRIER_COLOUR_MAPPING, + bus_sizes=total_gen * bus_size_scaling_factor, + flow="mean", + line_colors=network.lines_t.p0.mean().abs(), + line_cmap=flow_colormap, + line_widths=flow_arrow_size_scaling_factor, + boundaries=[ + min_max_longitudes[0], + min_max_longitudes[1], + min_max_latitudes[0], + min_max_latitudes[1], + ], + title=title, + ), + pypsa_plot_kwargs, ) + collection = network.plot(**pyspsa_plot_kwgs) plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") _add_fuel_type_legend(ax, _CARRIER_COLOUR_MAPPING) return fig, ax @@ -84,6 +94,14 @@ def _sum_generation_by_bus_and_carrier(network: pypsa.Network) -> pd.Series: ).total_generation.sum() +def _consolidate_plot_kwargs( + predefined_kwargs: dict, user_specified_kwargs: dict +) -> dict: + kwargs = predefined_kwargs + kwargs.update(user_specified_kwargs) + return kwargs + + def _create_plot_title(config: ModelConfig) -> str: run_name = config.ispypsa_run_name (start, end) = (config.traces.start_year, config.traces.end_year) From 9547402629090ebde683d69491558afb1b50db86 Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 11:42:34 +1100 Subject: [PATCH 10/17] change legend to fig legend and add kwargs handling --- src/ispypsa/results/plotting.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index f5f84a10..bf781bdb 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -81,7 +81,7 @@ def plot_map_of_energy_generation_by_carrier( ) collection = network.plot(**pyspsa_plot_kwgs) plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") - _add_fuel_type_legend(ax, _CARRIER_COLOUR_MAPPING) + _add_fuel_type_legend(fig, _CARRIER_COLOUR_MAPPING) return fig, ax @@ -119,10 +119,21 @@ def _create_plot_title(config: ModelConfig) -> str: def _add_fuel_type_legend( - ax: matplotlib.axes.Axes, carrier_colour_mapping: dict[str, str] + fig: matplotlib.figure.Figure, + carrier_colour_mapping: dict[str, str], + legend_kwargs=dict(), ) -> None: legend_patches = [ Patch(color=color, label=carrier) for carrier, color in carrier_colour_mapping.items() ] - ax.legend(handles=legend_patches, title="Fuel Type", loc="upper right") + legend_kwgs = dict( + title="Fuel Type", + loc="lower center", + fontsize=8, + title_fontsize=10, + ncol=4, + frameon=False, + ) + legend_kwgs.update(legend_kwargs) + fig.legend(handles=legend_patches, **legend_kwgs) From 8edd5b7e2a0ddfe2f83dafae83b1dc2f0cf948ca Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 12:22:45 +1100 Subject: [PATCH 11/17] move generic functions to plot_helpers --- src/ispypsa/results/plot_helpers.py | 67 +++++++++++++++++++++ src/ispypsa/results/plotting.py | 90 ++++++++--------------------- 2 files changed, 92 insertions(+), 65 deletions(-) create mode 100644 src/ispypsa/results/plot_helpers.py diff --git a/src/ispypsa/results/plot_helpers.py b/src/ispypsa/results/plot_helpers.py new file mode 100644 index 00000000..49aa0c4c --- /dev/null +++ b/src/ispypsa/results/plot_helpers.py @@ -0,0 +1,67 @@ +import matplotlib.figure +from matplotlib.patches import Patch + +from ispypsa.config.validators import ModelConfig + +_DEFAULT_CARRIER_COLOUR_MAPPING = { + # corresponds to distillate in OpenElectricity + "Liquid Fuel": "#E46E56", + "Black Coal": "#251C00", + "Brown Coal": "#675B42", + "Gas": "#E78114", + "Water": "#ACE9FE", + "Solar": "#FECE00", + "Wind": "#2A7E3F", + # corresponds to gas_hydrogen in OpenElectricity + "Hyblend": "#C75338", +} +"""Colour mapping based on mapping from OpenElectricity""" + +_DEFAULT_GEOMAP_COLOURS = dict(ocean="#dbdbdd", land="#fdfdfe") + +_DEFAULT_FACECOLOR = "#faf9f6" + + +def _determine_title_year_range(config: ModelConfig) -> str: + (start, end) = (config.traces.start_year, config.traces.end_year) + year_type = config.traces.year_type + if year_type == "fy" and start != end: + year_range = f"FY{str(start)[-2:]}-{str(end)[-2:]}" + elif start == end: + year_range = f"{start}" + else: + year_range = f"{start}-{end}" + return year_range + + +def _add_figure_fuel_type_legend( + fig: matplotlib.figure.Figure, + carrier_colour_mapping: dict[str, str], + legend_kwargs=dict(), +) -> None: + legend_patches = [ + Patch(color=color, label=carrier) + for carrier, color in carrier_colour_mapping.items() + ] + fig.legend( + handles=legend_patches, + **_consolidate_plot_kwargs( + dict( + title="Fuel Type", + loc="lower center", + fontsize=8, + title_fontsize=10, + ncol=4, + frameon=False, + ), + legend_kwargs, + ), + ) + + +def _consolidate_plot_kwargs( + predefined_kwargs: dict, user_specified_kwargs: dict +) -> dict: + kwargs = predefined_kwargs + kwargs.update(user_specified_kwargs) + return kwargs diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index bf781bdb..48a118a8 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -1,27 +1,20 @@ +import cartopy.crs as ccrs import matplotlib import matplotlib.axes import matplotlib.figure import matplotlib.pyplot as plt import pandas as pd import pypsa -from matplotlib.patches import Patch -import cartopy.crs as ccrs from ispypsa.config.validators import ModelConfig - -_CARRIER_COLOUR_MAPPING = { - # corresponds to distillate in OpenElectricity - "Liquid Fuel": "#E46E56", - "Black Coal": "#251C00", - "Brown Coal": "#675B42", - "Gas": "#E78114", - "Water": "#ACE9FE", - "Solar": "#FECE00", - "Wind": "#2A7E3F", - # corresponds to gas_hydrogen in OpenElectricity - "Hyblend": "#C75338", -} -"""Colour mapping based on mapping from OpenElectricity""" +from ispypsa.results.plot_helpers import ( + _DEFAULT_CARRIER_COLOUR_MAPPING, + _DEFAULT_FACECOLOR, + _DEFAULT_GEOMAP_COLOURS, + _add_figure_fuel_type_legend, + _consolidate_plot_kwargs, + _determine_title_year_range, +) def plot_map_of_energy_generation_by_carrier( @@ -37,14 +30,15 @@ def plot_map_of_energy_generation_by_carrier( figure_kwargs: dict = dict(), subplot_kwargs: dict = dict(), ) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: + # Bus pie size determined by total generation at the bus total_gen = _sum_generation_by_bus_and_carrier(network) - # size of ~1.0 appears to work well from trial-and-error + ## size of ~1.0 appears to work well from trial-and-error if bus_size_scaling_factor is None: bus_size_scaling_factor = min(1.0 / total_gen.groupby("bus").sum()) - # size of ~300.0 appears to work well from trial-and-error + ## size of ~300.0 appears to work well from trial-and-error if flow_arrow_size_scaling_factor is None: flow_arrow_size_scaling_factor = min(300.0 / network.lines_t.p0.mean().abs()) - title = _create_plot_title(config) + (main_title, sub_title) = (_create_main_title(config), _create_sub_title(config)) fig, ax = plt.subplots( 1, 1, @@ -52,18 +46,16 @@ def plot_map_of_energy_generation_by_carrier( dict(projection=ccrs.PlateCarree()), subplot_kwargs ), **_consolidate_plot_kwargs( - dict( - figsize=figure_size_inches, - ), + dict(figsize=figure_size_inches, facecolor=_DEFAULT_FACECOLOR), figure_kwargs, ), ) pyspsa_plot_kwgs = _consolidate_plot_kwargs( dict( geomap=True, - color_geomap=True, + color_geomap=_DEFAULT_GEOMAP_COLOURS, ax=ax, - bus_colors=_CARRIER_COLOUR_MAPPING, + bus_colors=_DEFAULT_CARRIER_COLOUR_MAPPING, bus_sizes=total_gen * bus_size_scaling_factor, flow="mean", line_colors=network.lines_t.p0.mean().abs(), @@ -75,13 +67,14 @@ def plot_map_of_energy_generation_by_carrier( min_max_latitudes[0], min_max_latitudes[1], ], - title=title, + title=sub_title, ), pypsa_plot_kwargs, ) collection = network.plot(**pyspsa_plot_kwgs) plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") - _add_fuel_type_legend(fig, _CARRIER_COLOUR_MAPPING) + _add_figure_fuel_type_legend(fig, _DEFAULT_CARRIER_COLOUR_MAPPING) + fig.suptitle(main_title, fontsize=18, x=0.6) return fig, ax @@ -94,46 +87,13 @@ def _sum_generation_by_bus_and_carrier(network: pypsa.Network) -> pd.Series: ).total_generation.sum() -def _consolidate_plot_kwargs( - predefined_kwargs: dict, user_specified_kwargs: dict -) -> dict: - kwargs = predefined_kwargs - kwargs.update(user_specified_kwargs) - return kwargs +def _create_main_title(config: ModelConfig) -> str: + year_range = _determine_title_year_range(config) + title = f"Total energy generation by fuel type, {year_range}" + return title -def _create_plot_title(config: ModelConfig) -> str: +def _create_sub_title(config: ModelConfig) -> str: run_name = config.ispypsa_run_name - (start, end) = (config.traces.start_year, config.traces.end_year) - year_type = config.traces.year_type - if year_type == "fy" and start != end: - year_range = f"FY{str(start)[-2:]}-{str(end)[-2:]}" - elif start == end: - year_range = f"{start}" - else: - year_range = f"{start}-{end}" - title = ( - f"ISPyPSA run '{run_name}'\nTotal energy generation by fuel type, {year_range}" - ) + title = f"ISPyPSA run: '{run_name}'" return title - - -def _add_fuel_type_legend( - fig: matplotlib.figure.Figure, - carrier_colour_mapping: dict[str, str], - legend_kwargs=dict(), -) -> None: - legend_patches = [ - Patch(color=color, label=carrier) - for carrier, color in carrier_colour_mapping.items() - ] - legend_kwgs = dict( - title="Fuel Type", - loc="lower center", - fontsize=8, - title_fontsize=10, - ncol=4, - frameon=False, - ) - legend_kwgs.update(legend_kwargs) - fig.legend(handles=legend_patches, **legend_kwgs) From 2510b8636d0abe18a742be51db715bfe5d20180d Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 16:41:32 +1100 Subject: [PATCH 12/17] add bus name labels and generation reference legend --- src/ispypsa/results/plotting.py | 77 ++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index 48a118a8..1d07e75f 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -30,11 +30,11 @@ def plot_map_of_energy_generation_by_carrier( figure_kwargs: dict = dict(), subplot_kwargs: dict = dict(), ) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: - # Bus pie size determined by total generation at the bus - total_gen = _sum_generation_by_bus_and_carrier(network) + total_gen_by_bus_and_carrier = _sum_generation_by_bus_and_carrier(network) + max_bus_gen = max(total_gen_by_bus_and_carrier.groupby("bus").sum()) ## size of ~1.0 appears to work well from trial-and-error if bus_size_scaling_factor is None: - bus_size_scaling_factor = min(1.0 / total_gen.groupby("bus").sum()) + bus_size_scaling_factor = 1.0 / max_bus_gen ## size of ~300.0 appears to work well from trial-and-error if flow_arrow_size_scaling_factor is None: flow_arrow_size_scaling_factor = min(300.0 / network.lines_t.p0.mean().abs()) @@ -56,7 +56,7 @@ def plot_map_of_energy_generation_by_carrier( color_geomap=_DEFAULT_GEOMAP_COLOURS, ax=ax, bus_colors=_DEFAULT_CARRIER_COLOUR_MAPPING, - bus_sizes=total_gen * bus_size_scaling_factor, + bus_sizes=total_gen_by_bus_and_carrier * bus_size_scaling_factor, flow="mean", line_colors=network.lines_t.p0.mean().abs(), line_cmap=flow_colormap, @@ -72,9 +72,11 @@ def plot_map_of_energy_generation_by_carrier( pypsa_plot_kwargs, ) collection = network.plot(**pyspsa_plot_kwgs) + _add_bus_name_labels(ax, network, bus_size_scaling_factor) plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") _add_figure_fuel_type_legend(fig, _DEFAULT_CARRIER_COLOUR_MAPPING) - fig.suptitle(main_title, fontsize=18, x=0.6) + _add_generation_circle_reference_legend(ax, max_bus_gen) + fig.suptitle(main_title, fontsize=16, x=0.6) return fig, ax @@ -89,7 +91,7 @@ def _sum_generation_by_bus_and_carrier(network: pypsa.Network) -> pd.Series: def _create_main_title(config: ModelConfig) -> str: year_range = _determine_title_year_range(config) - title = f"Total energy generation by fuel type, {year_range}" + title = f"Total energy generation by fuel type\nand mean line flows, {year_range}" return title @@ -97,3 +99,66 @@ def _create_sub_title(config: ModelConfig) -> str: run_name = config.ispypsa_run_name title = f"ISPyPSA run: '{run_name}'" return title + + +def _add_generation_circle_reference_legend( + ax: matplotlib.axes.Axes, + max_bus_gen: float, + patch_kwargs=dict(), + legend_kwargs=dict(), +) -> None: + pypsa.plot.add_legend_circles( + ax, + [1 / max_bus_gen * factor for factor in (1e6, 1e7, 1e8)], + ["1 TWh", "10 TWh", "100 TWh"], + patch_kw=_consolidate_plot_kwargs( + dict(edgecolor="black", facecolor=("black", 0.0)), patch_kwargs + ), + legend_kw=_consolidate_plot_kwargs( + dict(labelspacing=2, frameon=False), legend_kwargs + ), + ) + + +def _add_bus_name_labels( + ax: matplotlib.axes.Axes, + network: pypsa.Network, + bus_size_scaling_factor: float, + x_offset: float = 0.15, + y_offset: float = -0.2, + label_kwargs: dict = dict(), +) -> None: + plotted_buses = network.buses[network.buses.x != 0.0] + label_offsets = _calculate_label_offsets( + network, bus_size_scaling_factor, x_offset, y_offset + ) + plotted_buses = plotted_buses.merge( + label_offsets, how="left", left_index=True, right_index=True + ) + bus_name_xy = plotted_buses[["x", "y", "x_offset", "y_offset"]].T.to_dict() + for bus, attrs in bus_name_xy.items(): + ax.text( + attrs["x"] + attrs["x_offset"], + attrs["y"] + attrs["y_offset"], + bus, + **_consolidate_plot_kwargs(dict(horizontalalignment="left"), label_kwargs), + ) + + +def _calculate_label_offsets( + network: pypsa.Network, + bus_size_scaling_factor: float, + x_offset: float, + y_offset: float, +) -> pd.DataFrame: + total_gen_by_bus = _sum_generation_by_bus_and_carrier(network).groupby("bus").sum() + rough_bus_radii = (total_gen_by_bus * bus_size_scaling_factor).pow(0.5) + offsets = pd.DataFrame(index=rough_bus_radii.index) + if x_offset > 0: + # offset from right end of pie + offsets["x_offset"] = rough_bus_radii + x_offset + else: + # offset from left end of pie + offsets["x_offset"] = -1 * rough_bus_radii - x_offset + offsets["y_offset"] = y_offset + return offsets From 3415936541d9c5e48bfe822a42a5f2cb360f7ceb Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 16:43:09 +1100 Subject: [PATCH 13/17] generation circle legend title --- src/ispypsa/results/plotting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index 1d07e75f..bb5b0fa9 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -115,7 +115,8 @@ def _add_generation_circle_reference_legend( dict(edgecolor="black", facecolor=("black", 0.0)), patch_kwargs ), legend_kw=_consolidate_plot_kwargs( - dict(labelspacing=2, frameon=False), legend_kwargs + dict(labelspacing=1.5, frameon=False, title="Total generation"), + legend_kwargs, ), ) From 5508fe0d3e12818ea7019efefac527af661b5fd3 Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 16:52:28 +1100 Subject: [PATCH 14/17] change defaults so that saved file looks ok --- dodo.py | 2 +- src/ispypsa/results/plotting.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dodo.py b/dodo.py index 1037c0c9..02672c0a 100644 --- a/dodo.py +++ b/dodo.py @@ -211,7 +211,7 @@ def create_results( create_or_clean_task_output_folder(results_location) network = load_results(pypsa_outputs_location) fig, ax = plot_map_of_energy_generation_by_carrier( - network, config, figure_size_in_inches=(7, 10) + network, config, figure_size_inches=(6.5, 8), figure_kwargs=dict(dpi=600) ) fig.savefig( Path(results_location, "map_of_total_generation_by_carrier.png"), dpi=600 diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index bb5b0fa9..4b9e6f02 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -76,7 +76,7 @@ def plot_map_of_energy_generation_by_carrier( plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") _add_figure_fuel_type_legend(fig, _DEFAULT_CARRIER_COLOUR_MAPPING) _add_generation_circle_reference_legend(ax, max_bus_gen) - fig.suptitle(main_title, fontsize=16, x=0.6) + fig.suptitle(main_title, fontsize=16) return fig, ax @@ -115,7 +115,7 @@ def _add_generation_circle_reference_legend( dict(edgecolor="black", facecolor=("black", 0.0)), patch_kwargs ), legend_kw=_consolidate_plot_kwargs( - dict(labelspacing=1.5, frameon=False, title="Total generation"), + dict(labelspacing=1.75, frameon=False, title="Total generation"), legend_kwargs, ), ) From 41c33a8b9720fc2a30ad0beb1e946ec1d7d4123f Mon Sep 17 00:00:00 2001 From: prakaa Date: Thu, 28 Nov 2024 17:49:38 +1100 Subject: [PATCH 15/17] add docstrings --- src/ispypsa/results/plot_helpers.py | 21 ++++- src/ispypsa/results/plotting.py | 129 +++++++++++++++++++++++++--- 2 files changed, 139 insertions(+), 11 deletions(-) diff --git a/src/ispypsa/results/plot_helpers.py b/src/ispypsa/results/plot_helpers.py index 49aa0c4c..c9653316 100644 --- a/src/ispypsa/results/plot_helpers.py +++ b/src/ispypsa/results/plot_helpers.py @@ -15,14 +15,20 @@ # corresponds to gas_hydrogen in OpenElectricity "Hyblend": "#C75338", } -"""Colour mapping based on mapping from OpenElectricity""" +"""Colour mapping for carriers/fuel types. Same colour scheme as OpenElectricity""" _DEFAULT_GEOMAP_COLOURS = dict(ocean="#dbdbdd", land="#fdfdfe") +"""Colour mapping for ocean and land in the map plot""" _DEFAULT_FACECOLOR = "#faf9f6" +"""Facecolour to use in Figures (from OpenElectricity)""" def _determine_title_year_range(config: ModelConfig) -> str: + """ + Determines the year range string for use in plot titles based on + ISPyPSA configuration options. + """ (start, end) = (config.traces.start_year, config.traces.end_year) year_type = config.traces.year_type if year_type == "fy" and start != end: @@ -39,6 +45,16 @@ def _add_figure_fuel_type_legend( carrier_colour_mapping: dict[str, str], legend_kwargs=dict(), ) -> None: + """Adds a legend that maps fuel types to their patch colours to a + `matplotlib.figure.Figure`. + + Args: + fig: `matplotlib.figure.Figure` + carrier_colour_mapping: Dictionary that maps each carrier to a colour + legend_kwargs (optional): Keyword arguments for + `matplotlib.figure.Figure.legend()`. Anything specified in this dict will + overwrite ISPyPSA defaults. Defaults to dict(). + """ legend_patches = [ Patch(color=color, label=carrier) for carrier, color in carrier_colour_mapping.items() @@ -62,6 +78,9 @@ def _add_figure_fuel_type_legend( def _consolidate_plot_kwargs( predefined_kwargs: dict, user_specified_kwargs: dict ) -> dict: + """Adds to or replaces ISPyPSA's keyword arguments for plot functions using those + provided by the user in the function call. + """ kwargs = predefined_kwargs kwargs.update(user_specified_kwargs) return kwargs diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py index 4b9e6f02..293afde5 100644 --- a/src/ispypsa/results/plotting.py +++ b/src/ispypsa/results/plotting.py @@ -22,20 +22,71 @@ def plot_map_of_energy_generation_by_carrier( config: ModelConfig, figure_size_inches: tuple[float, float], bus_size_scaling_factor: float | None = None, - flow_colormap: str = "inferno", flow_arrow_size_scaling_factor: float | None = None, + flow_colormap: str = "inferno", min_max_latitudes: tuple[float, float] = (-44.0, -15.0), min_max_longitudes: tuple[float, float] = (137.5, 156.0), + bus_labels_x_offset: float = 0.15, + bus_labels_y_offset: float = -0.2, pypsa_plot_kwargs: dict = dict(), figure_kwargs: dict = dict(), subplot_kwargs: dict = dict(), + fuel_type_legend_kwargs: dict = dict(), + generation_reference_legend_kwargs: dict = dict(), + bus_label_kwargs: dict = dict(), ) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: + """Creates a geomap plot that shows the total energy generated at each bus in the + modelled period, the percentage of that energy produced generators of a particular + fuel type, and the mean flows across lines in the same period. + + Args: + network: The `pypsa.Network` with the model solved. + config: The ISPyPSA model configuration used to generate the model. + figure_size_inches: Figure (width, height) in inches (input to `matplotlib`). + bus_size_scaling_factor (optional): The factor used to scale total generation at + each bus to obtain bus circle/pie sizes. If set to `None`, this factor will + be automatically determined based on a sensible preset. Defaults to None. + flow_arrow_size_scaling_factor (optional): The factor used to mean flow on each + line to obtain arrow sizes. If set to `None`, this factor will be + automatically determined based on a sensible preset. Defaults to None. + flow_colormap (optional): The `matplotlib` colormap to use for the flow colormap. + Defaults to "inferno". + min_max_latitudes (optional): Geomap latitude limits. Defaults to (-44.0, -15.0). + min_max_longitudes (optional): Geomap longitude limits. Defaults to (137.5, 156.0). + bus_labels_x_offset (optional): X-axis offset from the edge of the bus circle/pie. + Defaults to 0.15. + bus_labels_y_offset (optional): Y-axis offset from the centre of the bus + circle/pie. Defaults to -0.2. + pypsa_plot_kwargs (optional): Keyword arguments for `pyspa.Network.plot()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + figure_kwargs (optional): Keyword arguments for `matplotlib.figure.Figure()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + subplot_kwargs (optional): Keyword arguments for subplots created by + `matplotlib.pyplot.subplots()`. Anything specified in this dict will + overwrite ISPyPSA defaults. Defaults to dict(). + fuel_type_legend_kwargs (optional): Keyword arguments for + `matplotlib.figure.Figure.legend()`. Anything specified in this dict will + overwrite ISPyPSA defaults. Defaults to dict(). + generation_reference_legend_kwargs (optional): Keyword arguments for + `matplotlib.ax.legend()`. Anything specified in this dict will overwrite + ISPyPSA defaults. Defaults to dict(). + bus_label_kwargs (optional): Keyword arguments for `ax.text`. + Anything specified in this dict will overwrite ISPyPSA defaults. + Defaults to dict(). + + Returns: + tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: Figure and axis with + geomap plot. + """ + # use total generation to define bus sizes total_gen_by_bus_and_carrier = _sum_generation_by_bus_and_carrier(network) max_bus_gen = max(total_gen_by_bus_and_carrier.groupby("bus").sum()) - ## size of ~1.0 appears to work well from trial-and-error + # size of ~1.0 appears to work well from trial-and-error if bus_size_scaling_factor is None: bus_size_scaling_factor = 1.0 / max_bus_gen - ## size of ~300.0 appears to work well from trial-and-error + # size of ~300.0 appears to work well from trial-and-error if flow_arrow_size_scaling_factor is None: flow_arrow_size_scaling_factor = min(300.0 / network.lines_t.p0.mean().abs()) (main_title, sub_title) = (_create_main_title(config), _create_sub_title(config)) @@ -72,10 +123,21 @@ def plot_map_of_energy_generation_by_carrier( pypsa_plot_kwargs, ) collection = network.plot(**pyspsa_plot_kwgs) - _add_bus_name_labels(ax, network, bus_size_scaling_factor) + _add_bus_name_labels( + ax, + network, + bus_size_scaling_factor, + x_offset=bus_labels_x_offset, + y_offset=bus_labels_y_offset, + label_kwargs=bus_label_kwargs, + ) plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") - _add_figure_fuel_type_legend(fig, _DEFAULT_CARRIER_COLOUR_MAPPING) - _add_generation_circle_reference_legend(ax, max_bus_gen) + _add_figure_fuel_type_legend( + fig, _DEFAULT_CARRIER_COLOUR_MAPPING, legend_kwargs=fuel_type_legend_kwargs + ) + _add_generation_circle_reference_legend( + ax, max_bus_gen, legend_kwargs=generation_reference_legend_kwargs + ) fig.suptitle(main_title, fontsize=16) return fig, ax @@ -107,6 +169,19 @@ def _add_generation_circle_reference_legend( patch_kwargs=dict(), legend_kwargs=dict(), ) -> None: + """Adds a legend with circles with reference sizes that correspond to total + generation of 1 TWh, 10 TWh and 100 TWh. + + Args: + ax: `matplotlib.axes.Axes` with geomap. + max_bus_gen: The maximum total generation at any one bus. + patch_kwargs (optional). Keyword arguments for `matplotlib.patches.Circle()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + legend_kwargs (optional): Keyword arguments for `matplotlib.ax.legend()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + """ pypsa.plot.add_legend_circles( ax, [1 / max_bus_gen * factor for factor in (1e6, 1e7, 1e8)], @@ -125,12 +200,23 @@ def _add_bus_name_labels( ax: matplotlib.axes.Axes, network: pypsa.Network, bus_size_scaling_factor: float, - x_offset: float = 0.15, - y_offset: float = -0.2, + x_offset: float, + y_offset: float, label_kwargs: dict = dict(), ) -> None: + """Adds bus name labels for plotted buses to the map. + + Args: + ax: `matplotlib.axes.Axes` with geomap. + network: The `pypsa.Network` with the model solved. + bus_size_scaling_factor: The factor used to scale the size of the buses. + x_offset: X-axis offset from the edge of the bus circle/pie. + y_offset: Y-axis offset from the centre of the bus circle/pie. + label_kwargs (optional): Keyword arguments for `ax.text`. Anything specified in + this dict will overwrite ISPyPSA defaults. Defaults to dict(). + """ plotted_buses = network.buses[network.buses.x != 0.0] - label_offsets = _calculate_label_offsets( + label_offsets = _calculate_bus_label_offsets( network, bus_size_scaling_factor, x_offset, y_offset ) plotted_buses = plotted_buses.merge( @@ -146,12 +232,35 @@ def _add_bus_name_labels( ) -def _calculate_label_offsets( +def _calculate_bus_label_offsets( network: pypsa.Network, bus_size_scaling_factor: float, x_offset: float, y_offset: float, ) -> pd.DataFrame: + """Calculates bus label offsets accounting for the size of the bus generation pie + chart + + X-axis text label offsets are applied on top of a base offset that roughly + corresponds to the radius of the bus circle/pie. Without the base offset, the label + would be obscured where the bus has a large circle/pie. + + Note 1: By default, the bottom left hand corner of the bus label text box is used + as the anchor point. + + Note 2: PyPSA appears to calculate the radius of a bus circle/pie by taking the + square root of `bus_size` (or area). `bus_size` is proportional to total generation + at that bus in this chart. + + Args: + network: The `pypsa.Network` with the model solved. + bus_size_scaling_factor: The factor used to scale the size of the buses. + x_offset: X-axis offset from the edge of the bus circle/pie. + y_offset: Y-axis offset from the centre of the bus circle/pie. + + Returns: + pd.DataFrame: DataFrame with x-axis and y-axis offsets for each bus. + """ total_gen_by_bus = _sum_generation_by_bus_and_carrier(network).groupby("bus").sum() rough_bus_radii = (total_gen_by_bus * bus_size_scaling_factor).pow(0.5) offsets = pd.DataFrame(index=rough_bus_radii.index) From 2b2439e6ac0cc99000b228a44dfdb70d2a399338 Mon Sep 17 00:00:00 2001 From: prakaa Date: Mon, 16 Dec 2024 12:11:20 +1100 Subject: [PATCH 16/17] update uv lock after rebasing on main --- uv.lock | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/uv.lock b/uv.lock index 892ae159..3ad07144 100644 --- a/uv.lock +++ b/uv.lock @@ -176,6 +176,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/f0/e1640ccd8468c61693092f38f835ef35a68a1ea72c3388683148b3800aa6/Bottleneck-1.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:26b5f0531f7044befaad95c20365dd666372e66bdacbfaf009ff65d60285534d", size = 111774 }, ] +[[package]] +name = "cartopy" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyproj" }, + { name = "pyshp" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/75/94aff4fef338887641aa780d13795609861e6e9f9593bd66d4917ab7954b/cartopy-0.24.1.tar.gz", hash = "sha256:01c910d5634c69a7efdec46e0a17d473d2328767f001d4dc0b5c4b48e585c8bd", size = 10741277 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/41/9dd14e3ee3f7a0546768c11a8f4a37b1c09fc4868b992f431431d526502b/Cartopy-0.24.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce0c83314570c61a695a1f7c3a4a22dc75f79d28f4c68b88a8aeaf13d6a2343c", size = 10982199 }, + { url = "https://files.pythonhosted.org/packages/72/57/8b4a3856aaf4c600504566d7d956928b79d8b17e8d3a1c70060e5f90124f/Cartopy-0.24.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:511f992340baea2c171cb17b3ef595537e5355640f3baa7ac895de25df016a70", size = 10971756 }, + { url = "https://files.pythonhosted.org/packages/ea/50/e5170302a62259f34289ff7f4944a32ac04a49b38713d001873732742726/Cartopy-0.24.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54f4d23961e0f9436baaf4747928361ccdcc893fa9b7bad9f615551bc8aa3fe8", size = 11658621 }, + { url = "https://files.pythonhosted.org/packages/0b/78/7d77c72c85371f5d87088887ec05fd3701dfdcd640845f85378341a355de/Cartopy-0.24.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0b55526b605a9dee4fa3d7e5659b6d7d9d30e609bc5e62487bc4f7d8e90873b", size = 10960201 }, + { url = "https://files.pythonhosted.org/packages/c4/f0/eaa16216c8b91cfd433b60e79080ffaf9e470bb5c939662835faa40fd347/Cartopy-0.24.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a14638b63d7df2858f73e9f8f4f4826d7c9cf13781aa6824fa0134fbaebbd98", size = 10982474 }, + { url = "https://files.pythonhosted.org/packages/63/99/681a7ae5e572343e15ce8697dd4b41f49d45fe89f5e5d8b122bff0f8165c/Cartopy-0.24.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d74b4a3eae9e570f474276fb61847112cdccdead396ec2ddad226dad9eaf4564", size = 10971918 }, + { url = "https://files.pythonhosted.org/packages/04/87/8dc9249e67c635a5c08ae81d4243a1ad69a1b91b703d3ab9be7fa78efc2b/Cartopy-0.24.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa38fb216cfd16cc266fd6f86b60ebdf0056839b490f38d2d89229b03abc877", size = 11718335 }, + { url = "https://files.pythonhosted.org/packages/4f/ce/ba4baced164ecd78b4109cd611d7b64d256f012784e944c1b0f6f5dff5c1/Cartopy-0.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:f440ddb61171319adf34ecec4d91202864cd514a7bc8a252e0ff7788a6604031", size = 10960539 }, + { url = "https://files.pythonhosted.org/packages/6e/76/774a4f808c6a4fc19b87c2cc38dd8731d413aad606689451c017ff93ad12/Cartopy-0.24.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a984e33977daed8f760c09c331c8368a6af060db1190af89d74a027c272e39c3", size = 10983939 }, + { url = "https://files.pythonhosted.org/packages/2f/48/8517d5d1cc56ce5c4abda1de6454593474a23412115a543f7981aa7e4377/Cartopy-0.24.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71d8a6d061d0764aba3baf357a68f3d73796a8a46d34b8c9fb241171b273c69e", size = 10972374 }, + { url = "https://files.pythonhosted.org/packages/c8/84/cb1577d5ac2f0deb002001c6e25b291735151c8c3033c97f212dc482ef72/Cartopy-0.24.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f354a1d902a8d6ee33b099acc86ac2e1af528bbc0ea718b834111c97e604981", size = 11715215 }, + { url = "https://files.pythonhosted.org/packages/11/95/40c7abae8789aae22ad2a5da3974d3270dc3526b46cee253f680f72ee6cc/Cartopy-0.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:b1bb2d02b31884ee1d4f14e5b436bbf95745eac39c6fc0d6c67c83bb907b55b3", size = 10959875 }, + { url = "https://files.pythonhosted.org/packages/e6/e8/38e00eb35743f22d4ee9bcb131ab273fb47ec39cc03ce5144686a3142756/Cartopy-0.24.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d279968b845f72e3423e454b2b0b985fb2389e6ccd18fb73324abeca4e43f516", size = 10982533 }, + { url = "https://files.pythonhosted.org/packages/66/60/cc852a0835a053db18085dec1d1dd6f9cedc764d1524bfe30fd8be5ee3a7/Cartopy-0.24.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f0963b80a048252815c56fbd21bc4e5d163618a9eaa36c8898ce2c60b6c03979", size = 10971183 }, + { url = "https://files.pythonhosted.org/packages/a3/5b/476c8f3a277d7c78e5a5318bd32f234b994bfdc5d7731ae84218f2fa8a8f/Cartopy-0.24.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfdde0a6e0e56c5fc46f4e7d332237eb31bbd9908417f0f190fda5d322754184", size = 11709060 }, + { url = "https://files.pythonhosted.org/packages/93/31/50bf07ba820c5aa5d4e674d72cdb5da90bbd012ba1b9c6c95c3f96afe233/Cartopy-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:b17cf23dd74d0a922c2a5682dacef3c0bf89fa8c0bd0eae96b87fb684f966b15", size = 10959830 }, +] + [[package]] name = "certifi" version = "2024.8.30" @@ -993,6 +1025,7 @@ name = "ispypsa" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "cartopy" }, { name = "doit" }, { name = "isp-trace-parser" }, { name = "isp-workbook-parser" }, @@ -1030,6 +1063,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "cartopy", specifier = ">=0.24.1" }, { name = "doit", specifier = ">=0.36.0" }, { name = "isp-trace-parser", specifier = ">=1.0.0" }, { name = "isp-workbook-parser", specifier = ">=2.0.3" }, @@ -2262,6 +2296,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/8d/7e86347eda3dca8eaf6da846ba093340398dbd89c6a2ca5f331a82a3408b/pypsa-0.31.2-py3-none-any.whl", hash = "sha256:2d1befbaaf295856dfe299378f2b9ffda0e0948812823ebb2845595062491ab1", size = 144609 }, ] +[[package]] +name = "pyshp" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9f/0dd21250c60375a532c35e89fad8d5e8a3f1a2e3f7c389ccc5a60b05263e/pyshp-2.3.1.tar.gz", hash = "sha256:4caec82fd8dd096feba8217858068bacb2a3b5950f43c048c6dc32a3489d5af1", size = 1731544 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/2f/68116db5b36b895c0450e3072b8cb6c2fac0359279b182ea97014d3c8ac0/pyshp-2.3.1-py2.py3-none-any.whl", hash = "sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49", size = 46537 }, +] + [[package]] name = "pytest" version = "8.3.3" From c162b49c564a63f634265189fd20a59c17028d17 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 01:12:16 +0000 Subject: [PATCH 17/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dodo.py | 2 +- src/ispypsa/results/io.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dodo.py b/dodo.py index 02672c0a..42d2dd94 100644 --- a/dodo.py +++ b/dodo.py @@ -17,6 +17,7 @@ run, ) from ispypsa.results import load_results, save_results +from ispypsa.results.plotting import plot_map_of_energy_generation_by_carrier from ispypsa.templater.dynamic_generator_properties import ( template_generator_dynamic_properties, ) @@ -41,7 +42,6 @@ _translate_generator_timeseries, ) from ispypsa.translator.lines import translate_flow_paths_to_lines -from ispypsa.results.plotting import plot_map_of_energy_generation_by_carrier root_folder = Path("ispypsa_runs") diff --git a/src/ispypsa/results/io.py b/src/ispypsa/results/io.py index ba3242bd..32dd6af1 100644 --- a/src/ispypsa/results/io.py +++ b/src/ispypsa/results/io.py @@ -1,4 +1,5 @@ from pathlib import Path + import pypsa