Skip to content

Commit 6432404

Browse files
committed
Merge pull request #81 from staadecker/wind_to_solar_ratio
Wind to solar ratio
2 parents e99b869 + c92d93c commit 6432404

File tree

4 files changed

+137
-14
lines changed

4 files changed

+137
-14
lines changed

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,python,venv,data,visualstudiocode,pycharm,jupyternotebooks,dotenv,virtualenv
33

44
### Data ###
5-
*.csv
6-
# Don't ignore data in switch_model.csv
7-
!switch_model/**/*.csv
85
*.dat
96
*.efx
107
*.gbr
@@ -18,8 +15,11 @@
1815
*.vcf
1916
*.xml
2017
*.pickle
18+
examples/**/temp/**
19+
examples/**/outputs/*.csv
20+
examples/**/outputs/*.txt
21+
examples/**/graphs/*.png
2122
examples/**/info.txt
22-
examples/**/*.png
2323

2424
### dotenv ###
2525
.env
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
####################
3+
Add wind_to_solar_ratio column
4+
5+
Date applied: 2021-07-07
6+
Description:
7+
Adds a column called wind_to_solar_ratio to the database which is used by
8+
switch_model.policies.wind_to_solar_ratio
9+
#################
10+
*/
11+
12+
ALTER TABLE switch.scenario ADD COLUMN wind_to_solar_ratio real;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
This module gives us the possibility to enforce a wind to solar capacity ratio.
3+
4+
It takes in wind_to_solar_ratio.csv that has the following format
5+
6+
PERIOD,wind_to_solar_ratio,wind_to_solar_ratio_const_gt
7+
2020,.,.
8+
2030,0.5,0
9+
2040,1,0
10+
2050,1.5,1
11+
12+
Here when wind_to_solar_ratio is specified (i.e. not '.') a constraint is activated that enforces that
13+
14+
Online wind capacity >=/<= Online solar capacity * wind_to_solar_ratio
15+
16+
for the entire period.
17+
18+
When wind_to_solar_ratio_const_gt is true (1) the constraint is a >= constraint.
19+
When wind_to_solar_ratio_const_gt is False (0) the constraint is a <= constraint.
20+
"""
21+
import os
22+
23+
import pandas as pd
24+
from pyomo.environ import *
25+
26+
from switch_model.reporting import write_table
27+
28+
_WIND_ENERGY_TYPE = "Wind"
29+
_SOLAR_ENERGY_TYPE = "Solar"
30+
31+
32+
def define_components(mod):
33+
mod.WindCapacity = Expression(
34+
mod.PERIODS,
35+
rule=lambda m, p: sum(
36+
m.GenCapacity[g, p]
37+
for g in m.VARIABLE_GENS
38+
if m.gen_energy_source[g] == _WIND_ENERGY_TYPE
39+
),
40+
)
41+
42+
mod.SolarCapacity = Expression(
43+
mod.PERIODS,
44+
rule=lambda m, p: sum(
45+
m.GenCapacity[g, p]
46+
for g in m.VARIABLE_GENS
47+
if m.gen_energy_source[g] == _SOLAR_ENERGY_TYPE
48+
),
49+
)
50+
51+
mod.wind_to_solar_ratio = Param(
52+
mod.PERIODS,
53+
default=0, # 0 means the constraint is inactive
54+
within=NonNegativeReals,
55+
)
56+
57+
mod.wind_to_solar_ratio_const_gt = Param(mod.PERIODS, default=True, within=Boolean)
58+
59+
# We use a scaling factor to improve the numerical properties
60+
# of the model.
61+
# Learn more by reading the documentation on Numerical Issues.
62+
# 1e-3 was picked since this value is normally on the order of GW instead of MW
63+
scaling_factor = 1e-3
64+
65+
def wind_to_solar_ratio_const_rule(m, p):
66+
if m.wind_to_solar_ratio[p] == 0: # 0 means Constraint is inactive
67+
return Constraint.Skip
68+
69+
lhs = m.WindCapacity[p] * scaling_factor
70+
rhs = m.SolarCapacity[p] * m.wind_to_solar_ratio[p] * scaling_factor
71+
if m.wind_to_solar_ratio_const_gt[p]:
72+
return lhs >= rhs
73+
else:
74+
return lhs <= rhs
75+
76+
mod.wind_to_solar_ratio_const = Constraint(
77+
mod.PERIODS, rule=wind_to_solar_ratio_const_rule
78+
)
79+
80+
81+
def load_inputs(mod, switch_data, inputs_dir):
82+
switch_data.load_aug(
83+
filename=os.path.join(inputs_dir, "wind_to_solar_ratio.csv"),
84+
auto_select=True,
85+
param=(mod.wind_to_solar_ratio, mod.wind_to_solar_ratio_const_gt),
86+
optional=True, # We want to allow including this module even if the file isn't there
87+
)
88+
89+
90+
def post_solve(m, outdir):
91+
df = pd.DataFrame(
92+
{
93+
"WindCapacity (GW)": value(m.WindCapacity[p]) / 1000,
94+
"SolarCapacity (GW)": value(m.SolarCapacity[p]) / 1000,
95+
"ComputedRatio": value(m.WindCapacity[p] / m.SolarCapacity[p])
96+
if value(m.SolarCapacity[p]) != 0
97+
else ".",
98+
"ExpectedRatio": value(m.wind_to_solar_ratio[p])
99+
if m.wind_to_solar_ratio[p] != 0
100+
else ".",
101+
}
102+
for p in m.PERIODS
103+
)
104+
write_table(
105+
m,
106+
output_file=os.path.join(outdir, "wind_to_solar_ratio.csv"),
107+
df=df,
108+
index=False,
109+
)

switch_model/utilities/__init__.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -637,14 +637,21 @@ def load_aug(
637637
switch_data.load(**kwds)
638638
return
639639

640+
# Use our custom DataManager to allow 'inf' in csvs.
641+
if extension == ".csv":
642+
kwds["using"] = "switch_csv"
643+
640644
# copy the optional_params to avoid side-effects when the list is altered below
641645
optional_params = list(optional_params)
642646
# Parse header and first row
643647
with open(path) as infile:
644648
headers_line = infile.readline()
645649
second_line = infile.readline()
646-
file_is_empty = headers_line == ""
647-
file_has_no_data_rows = second_line == ""
650+
651+
# Skip if the file is empty.
652+
if optional and headers_line == "":
653+
return
654+
648655
suffix = path.split(".")[-1]
649656
if suffix in {"tab", "tsv"}:
650657
separator = "\t"
@@ -654,9 +661,7 @@ def load_aug(
654661
raise InputError(f"Unrecognized file type for input file {path}")
655662
# TODO: parse this more formally, e.g. using csv module
656663
headers = headers_line.strip().split(separator)
657-
# Skip if the file is empty.
658-
if optional and file_is_empty:
659-
return
664+
660665
# Try to get a list of parameters. If param was given as a
661666
# singleton or a tuple, make it into a list that can be edited.
662667
params = []
@@ -741,14 +746,11 @@ def load_aug(
741746
del kwds["select"][i]
742747
del kwds["param"][p_i]
743748

744-
if optional and file_has_no_data_rows:
745-
# Skip the file. Note that we are only doing this after having
749+
if optional and second_line == "":
750+
# Skip the file if it has no data. Note that we are only doing this after having
746751
# validated the file's column headings.
747752
return
748753

749-
# Use our custom DataManager to allow 'inf' in csvs.
750-
if kwds["filename"][-4:] == ".csv":
751-
kwds["using"] = "switch_csv"
752754
# All done with cleaning optional bits. Pass the updated arguments
753755
# into the DataPortal.load() function.
754756
switch_data.load(**kwds)

0 commit comments

Comments
 (0)