From 8cb3e57123ce6a490c28d98fe51919cc523e8c24 Mon Sep 17 00:00:00 2001 From: nick-gorman Date: Fri, 31 Jan 2025 14:27:26 +1100 Subject: [PATCH] add flow path expansion --- dodo.py | 4 ++ .../ispypsa_inputs/ispypsa_config.yaml | 3 ++ src/ispypsa/config/validators.py | 1 + .../6.0/transmission_expansion_costs.csv | 14 ++++++ src/ispypsa/translator/lines.py | 27 ++++++++++- src/ispypsa/translator/mappings.py | 1 + tests/config/test_pydantic_model_config.py | 47 +++++++++++++++++++ 7 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/ispypsa/templater/manually_extracted_template_tables/6.0/transmission_expansion_costs.csv diff --git a/dodo.py b/dodo.py index d66f491..357b4ca 100644 --- a/dodo.py +++ b/dodo.py @@ -188,6 +188,9 @@ def create_pypsa_inputs_from_config_and_ispypsa_inputs( ) lines_interregional_or_subregional = translate_flow_paths_to_lines( ispypsa_inputs_location, + config.network.transmission_expansion, + config.wacc, + config.network.annuitisation_lifetime, ) lines_rez_to_region_or_subregion = ( translate_renewable_energy_zone_build_limits_to_flow_paths( @@ -309,6 +312,7 @@ def task_create_ispypsa_inputs(): "mapping_nem_region_to_single_sub_region.csv", ), Path(_ISPYPSA_INPUT_TABLES_DIRECTORY, "flow_paths.csv"), + Path(_ISPYPSA_INPUT_TABLES_DIRECTORY, "transmission_expansion_costs.csv"), Path(_ISPYPSA_INPUT_TABLES_DIRECTORY, "ecaa_generators.csv"), Path(_ISPYPSA_INPUT_TABLES_DIRECTORY, "coal_prices.csv"), Path(_ISPYPSA_INPUT_TABLES_DIRECTORY, "gas_prices.csv"), diff --git a/ispypsa_runs/development/ispypsa_inputs/ispypsa_config.yaml b/ispypsa_runs/development/ispypsa_inputs/ispypsa_config.yaml index bb5e669..45caec5 100644 --- a/ispypsa_runs/development/ispypsa_inputs/ispypsa_config.yaml +++ b/ispypsa_runs/development/ispypsa_inputs/ispypsa_config.yaml @@ -13,6 +13,9 @@ scenario: Step Change # costs, as a fraction, i.e. 0.07 is 7%. wacc: 0.07 network: + # Does the model consider the expansion of sub-region to sub-region transmission + # capacity + transmission_expansion: True # Does the model consider the expansion of renewable energy zone transmission # capacity rez_transmission_expansion: True diff --git a/src/ispypsa/config/validators.py b/src/ispypsa/config/validators.py index 93e1048..660d2c0 100644 --- a/src/ispypsa/config/validators.py +++ b/src/ispypsa/config/validators.py @@ -15,6 +15,7 @@ class NodesConfig(BaseModel): class NetworkConfig(BaseModel): nodes: NodesConfig annuitisation_lifetime: int + transmission_expansion: bool rez_transmission_expansion: bool rez_to_sub_region_transmission_default_limit: float diff --git a/src/ispypsa/templater/manually_extracted_template_tables/6.0/transmission_expansion_costs.csv b/src/ispypsa/templater/manually_extracted_template_tables/6.0/transmission_expansion_costs.csv new file mode 100644 index 0000000..55ddae1 --- /dev/null +++ b/src/ispypsa/templater/manually_extracted_template_tables/6.0/transmission_expansion_costs.csv @@ -0,0 +1,14 @@ +flow_path_name,indicative_transmission_expansion_cost_$/mw +CQ-NQ,1.126363636 +CQ-GG,0.838709677 +SQ-CQ,0.513333333 +NNSW-SQ,1.702027027 +Terranora,NA +CNSW-NNSW,0.497666667 +CNSW-SNW,0.284 +SNSW-CNSW,0.502333333 +VIC-SNSW,2.00554939 +Heywood,0.454333333 +SESA-CSA,0.602333333 +Murraylink,NA +Basslink,3.646666667 diff --git a/src/ispypsa/translator/lines.py b/src/ispypsa/translator/lines.py index df6097f..fc65e08 100644 --- a/src/ispypsa/translator/lines.py +++ b/src/ispypsa/translator/lines.py @@ -2,22 +2,45 @@ import pandas as pd +from ispypsa.translator.helpers import annuitised_investment_costs from ispypsa.translator.mappings import _LINE_ATTRIBUTES -def translate_flow_paths_to_lines(ispypsa_inputs_path: Path | str) -> pd.DataFrame: +def translate_flow_paths_to_lines( + ispypsa_inputs_path: Path | str, + expansion_on: bool, + wacc: float, + asset_lifetime: int, +) -> pd.DataFrame: """Process network line data into a format aligned with PyPSA inputs. Args: ispypsa_inputs_path: Path to directory containing modelling input template CSVs. + expansion_on: bool indicating if transmission line expansion is considered. + wacc: float, as fraction, indicating the weighted average coast of capital for + transmission line investment, for the purposes of annuitising capital + costs. + asset_lifetime: int specifying the nominal asset lifetime in years or the + purposes of annuitising capital costs. Returns: `pd.DataFrame`: PyPSA style generator attributes in tabular format. """ lines = pd.read_csv(ispypsa_inputs_path / Path("flow_paths.csv")) + costs = pd.read_csv(ispypsa_inputs_path / Path("transmission_expansion_costs.csv")) + lines = pd.merge(lines, costs, how="left", on="flow_path_name") + lines = lines.loc[:, _LINE_ATTRIBUTES.keys()] lines = lines.rename(columns=_LINE_ATTRIBUTES) lines = lines.set_index("name", drop=True) + + lines["capital_cost"] = lines["capital_cost"].apply( + lambda x: annuitised_investment_costs(x, wacc, asset_lifetime) + ) + + # not extendable by default lines["s_nom_extendable"] = False - lines["capital_cost"] = 0.0 + # If a non-nan capital_cost is given then set to extendable + lines.loc[~lines["capital_cost"].isna(), "s_nom_extendable"] = expansion_on + return lines diff --git a/src/ispypsa/translator/mappings.py b/src/ispypsa/translator/mappings.py index 93807f9..784a29f 100644 --- a/src/ispypsa/translator/mappings.py +++ b/src/ispypsa/translator/mappings.py @@ -11,6 +11,7 @@ "node_from": "bus0", "node_to": "bus1", "forward_direction_mw_summer_typical": "s_nom", + "indicative_transmission_expansion_cost_$/mw": "capital_cost", # TODO: implement reverse direction limit # "reverse_direction_mw_summer_typical": "" } diff --git a/tests/config/test_pydantic_model_config.py b/tests/config/test_pydantic_model_config.py index e771f0f..2db1b34 100644 --- a/tests/config/test_pydantic_model_config.py +++ b/tests/config/test_pydantic_model_config.py @@ -22,6 +22,7 @@ def test_valid_config( "scenario": scenario, "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -55,6 +56,7 @@ def test_invalid_scenario(): "scenario": "BAU", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -88,6 +90,7 @@ def test_invalid_node_granularity(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -121,6 +124,7 @@ def test_invalid_nodes_rezs(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -154,6 +158,7 @@ def test_not_a_directory_parsed_traces_path(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -187,6 +192,7 @@ def test_invalid_parsed_traces_path(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -220,6 +226,7 @@ def test_invalid_end_year(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -253,6 +260,7 @@ def test_invalid_representative_weeks(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -286,6 +294,7 @@ def test_invalid_wacc(): "scenario": "Step Change", "wacc": "7%", "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -319,6 +328,7 @@ def test_invalid_annuitisation_lifetime(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": "years", "nodes": { @@ -344,6 +354,40 @@ def test_invalid_annuitisation_lifetime(): ) +def test_invalid_transmission_expansion(): + with pytest.raises(ValidationError): + ModelConfig( + **{ + "ispypsa_run_name": "test", + "scenario": "Step Change", + "wacc": 0.07, + "network": { + "transmission_expansion": "help", + "rez_transmission_expansion": True, + "annuitisation_lifetime": 30, + "nodes": { + "regional_granularity": "sub_regions", + "rezs": "discrete_nodes", + }, + "rez_to_sub_region_transmission_default_limit": 1e6, + }, + "temporal": { + "operational_temporal_resolution_min": 30, + "path_to_parsed_traces": "tests/test_traces", + "year_type": "fy", + "start_year": 2025, + "end_year": 2026, + "reference_year_cycle": [2018], + "aggregation": { + "representative_weeks": [0], + }, + }, + "solver": "highs", + "iasr_workbook_version": "6.0", + } + ) + + def test_invalid_rez_transmission_expansion(): with pytest.raises(ValidationError): ModelConfig( @@ -352,6 +396,7 @@ def test_invalid_rez_transmission_expansion(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": "help", "annuitisation_lifetime": 30, "nodes": { @@ -385,6 +430,7 @@ def test_invalid_rez_to_sub_region_transmission_default_limit(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": { @@ -418,6 +464,7 @@ def test_invalid_iasr_workbook_version(): "scenario": "Step Change", "wacc": 0.07, "network": { + "transmission_expansion": True, "rez_transmission_expansion": True, "annuitisation_lifetime": 30, "nodes": {