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

Corrects TCT constraint #521

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 4 additions & 14 deletions docs/source/config-wildcards.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ The REM, SAFER, RPS can be defined using either the reeds zone name 'p##"
the state code (eg, TX, CA, MT), pypsa-usa interconnect name (western, eastern, texas, usa),
or nerc region name.

```{warning}
TCT Targets can only be used with renewable generators and utility scale batteries in sector studies.
```

There are currently:

```{eval-rst}
Expand All @@ -84,14 +88,9 @@ There are currently:
:file: configtables/opts.csv
```


(sector)=
## The `{sector}` wildcard

```{warning}
Sector coupling studies are all under active development
```

The `{sector}` wildcard is used to specify what sectors to include. If `None`
is provided, an electrical only study is completed.

Expand All @@ -100,15 +99,6 @@ is provided, an electrical only study is completed.
| Electricity | E | Electrical sector. Will always be run. | Runs |
| Natural Gas | G | All sectors added | Development |

(scope)=
## The `{scope}` wildcard

```{warning}
Sector coupling studies are all under active development
```

Takes values `residential`, `urban`, `total`. Used in sector coupling studies to define
population breakdown.

(cutout_wc)=
## The `{cutout}` wildcard
Expand Down
135 changes: 72 additions & 63 deletions workflow/scripts/solve_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ def get_region_buses(n, region_list):
]


def filter_components(n, component_type, planning_horizon, carrier_list, region_buses, extendable):
def filter_components(
n: pypsa.Network,
component_type: str,
planning_horizon: str | int,
carrier_list: list[str],
region_buses: pd.Index,
extendable: bool,
):
"""
Filter components based on common criteria.

Expand Down Expand Up @@ -188,8 +195,6 @@ def add_technology_capacity_target_constraints(n, config):

Add minimum or maximum levels of generator nominal capacity per carrier for individual regions. Each constraint can be designated for a specified planning horizon in multi-period models. Opts and path for technology_capacity_targets.csv must be defined in config.yaml. Default file is available at config/policy_constraints/technology_capacity_targets.csv.

*** Review to subtract the non-extensible capacity from the rhs? ***

Parameters
----------
n : pypsa.Network
Expand All @@ -207,64 +212,68 @@ def add_technology_capacity_target_constraints(n, config):
if tct_data.empty:
return

for idx, target in tct_data.iterrows():
for _, target in tct_data.iterrows():
planning_horizon = target.planning_horizon
region_list = [region_.strip() for region_ in target.region.split(",")]
carrier_list = [carrier_.strip() for carrier_ in target.carrier.split(",")]
region_buses = get_region_buses(n, region_list)

lhs_gens = filter_components(
lhs_gens_ext = filter_components(
n=n,
component_type="Generator",
planning_horizon=planning_horizon,
carrier_list=carrier_list,
region_buses=region_buses.index,
extendable=True,
)
lhs_gens_existing = filter_components(
n=n,
component_type="Generator",
planning_horizon=planning_horizon,
carrier_list=carrier_list,
region_buses=region_buses.index,
extendable=False,
)

# rhs_g_non_extendable = filter_components(
# n=n,
# component_type="Generator",
# planning_horizon=planning_horizon,
# carrier_list=carrier_list,
# region_buses=region_buses.index,
# extendable=False,
# ).p_nom.sum()

lhs_storage = filter_components(
lhs_storage_ext = filter_components(
n=n,
component_type="StorageUnit",
planning_horizon=planning_horizon,
carrier_list=carrier_list,
region_buses=region_buses.index,
extendable=True,
)
lhs_storage_existing = filter_components(
n=n,
component_type="StorageUnit",
planning_horizon=planning_horizon,
carrier_list=carrier_list,
region_buses=region_buses.index,
extendable=False,
)

# rhs_s_non_extendable = filter_components(
# n=n,
# component_type="StorageUnit",
# planning_horizon=planning_horizon,
# carrier_list=carrier_list,
# region_buses=region_buses.index,
# extendable=False,
# ).p_nom.sum()

if region_buses.empty or (lhs_gens.empty and lhs_storage.empty):
if region_buses.empty or (lhs_gens_ext.empty and lhs_storage_ext.empty):
continue

if not lhs_gens.empty:
grouper_g = pd.concat([lhs_gens.bus.map(n.buses.country), lhs_gens.carrier], axis=1).rename_axis(
if not lhs_gens_ext.empty:
grouper_g = pd.concat(
[lhs_gens_ext.bus.map(n.buses.country), lhs_gens_ext.carrier],
axis=1,
).rename_axis(
"Generator-ext",
)
lhs_g = p_nom.loc[lhs_gens.index].groupby(grouper_g).sum().rename(bus="country")
lhs_g = p_nom.loc[lhs_gens_ext.index].groupby(grouper_g).sum().rename(bus="country")
else:
lhs_g = None

if not lhs_storage.empty:
grouper_s = pd.concat([lhs_storage.bus.map(n.buses.country), lhs_storage.carrier], axis=1).rename_axis(
if not lhs_storage_ext.empty:
grouper_s = pd.concat(
[lhs_storage_ext.bus.map(n.buses.country), lhs_storage_ext.carrier],
axis=1,
).rename_axis(
"StorageUnit-ext",
)
lhs_s = n.model["StorageUnit-p_nom"].loc[lhs_storage.index].groupby(grouper_s).sum()
lhs_s = n.model["StorageUnit-p_nom"].loc[lhs_storage_ext.index].groupby(grouper_s).sum()
else:
lhs_s = None

Expand All @@ -277,56 +286,58 @@ def add_technology_capacity_target_constraints(n, config):
else:
lhs = (lhs_g + lhs_s).sum()

lhs_extendable = lhs_gens.p_nom.sum() + lhs_storage.p_nom.sum()
# rhs_non_extendable = rhs_g_non_extendable + rhs_s_non_extendable
lhs_existing = lhs_gens_existing.p_nom.sum() + lhs_storage_existing.p_nom.sum()

if target["max"] == "existing":
target["max"] = lhs_extendable
target["max"] = round(lhs_existing, 2) + 0.01
else:
target["max"] = float(target["max"])

if target["min"] == "existing":
target["min"] = lhs_extendable
target["min"] = round(lhs_existing, 2) - 0.01
else:
target["min"] = float(target["min"])

if not np.isnan(target["min"]):

rhs = target["min"] - round(lhs_existing, 2)

n.model.add_constraints(
lhs >= (target["min"]),
name=f"GlobalConstraint-{target.name}_{target.planning_horizon}_min",
Copy link
Collaborator

Choose a reason for hiding this comment

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

The reason I have "GlobalConstraint" appended is to be able to get pypsa to export the shadow price of the constraint (PyPSA/PyPSA#850) . So it'd be good to add that back!

lhs >= rhs,
name=f"{target.name}_{target.planning_horizon}_min",
)

logger.info(
"Adding TCT Constraint:\n"
"Name: %s\n"
"Planning Horizon: %s\n"
"Region: %s\n"
"Carrier: %s\n"
"Min Value: %s",
target.name,
target.planning_horizon,
target.region,
target.carrier,
(target["min"]),
f"Name: {target.name}\n"
f"Planning Horizon: {target.planning_horizon}\n"
f"Region: {target.region}\n"
f"Carrier: {target.carrier}\n"
f"Min Value: {target['min']}\n",
f"Min Value Adj: {rhs}",
)

if not np.isnan(target["max"]):

assert (
target["max"] >= lhs_existing
), f"TCT constraint of {target['max']} MW for {target['carrier']} must be at least {lhs_existing}"

rhs = target["max"] - round(lhs_existing, 2)

n.model.add_constraints(
lhs <= (target["max"]),
name=f"GlobalConstraint-{target.name}_{target.planning_horizon}_max",
lhs <= rhs,
name=f"{target.name}_{target.planning_horizon}_max",
)

logger.info(
"Adding TCT Constraint:\n"
"Name: %s\n"
"Planning Horizon: %s\n"
"Region: %s\n"
"Carrier: %s\n"
"Max Value: %s",
target.name,
target.planning_horizon,
target.region,
target.carrier,
(target["max"]),
f"Name: {target.name}\n"
f"Planning Horizon: {target.planning_horizon}\n"
f"Region: {target.region}\n"
f"Carrier: {target.carrier}\n"
f"Max Value: {target['max']}\n",
f"Max Value Adj: {rhs}",
)


Expand Down Expand Up @@ -1546,17 +1557,15 @@ def solve_network(n, config, solving, opts="", **kwargs):
if "snakemake" not in globals():
from _helpers import mock_snakemake

print("")

snakemake = mock_snakemake(
"solve_network",
interconnect="western",
simpl="70",
clusters="4m",
ll="v1.0",
opts="3h-TCT",
opts="1h-TCT",
sector="E-G",
planning_horizons="2019",
planning_horizons="2030",
)
configure_logging(snakemake)
update_config_from_wildcards(snakemake.config, snakemake.wildcards)
Expand Down