Skip to content

Commit e8f5ce7

Browse files
committed
Merge remote-tracking branch 'rael/wecc' into improve_load_aug
2 parents 2360c55 + dba8ced commit e8f5ce7

File tree

20 files changed

+1064
-336
lines changed

20 files changed

+1064
-336
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
####################
3+
Add generation plants groups
4+
5+
Date applied:
6+
Description:
7+
This script adds the option to specify generation plant groups.
8+
The generation groups are specified in the table generation_plant_group.
9+
Plants are assigned to a group by adding them to the many-to-many table generation_plant_group_member.
10+
Groups are assigned to a generation_plant_scenario_id by specifying them in generation_plant_scenario_group_member
11+
#################
12+
*/
13+
14+
CREATE TABLE switch.generation_plant_group
15+
(
16+
generation_plant_group_id serial NOT NULL,
17+
description text NOT NULL,
18+
name character varying(30) NOT NULL,
19+
PRIMARY KEY (generation_plant_group_id)
20+
);
21+
22+
COMMENT ON TABLE switch.generation_plant_group
23+
IS 'This table specifies all the generation plant groups. Every group has a set of generation plants (see generation_plant_group_member). Groups can be assigned to a generation_plant_scenario (see generation_plant_scenario_group_member).';
24+
25+
CREATE TABLE switch.generation_plant_group_member
26+
(
27+
generation_plant_group_id integer,
28+
generation_plant_id integer,
29+
PRIMARY KEY (generation_plant_group_id, generation_plant_id)
30+
);
31+
32+
ALTER TABLE switch.generation_plant_group_member
33+
ADD CONSTRAINT generation_plant_group_member_group_id_fkey
34+
FOREIGN KEY (generation_plant_group_id)
35+
REFERENCES switch.generation_plant_group (generation_plant_group_id);
36+
37+
ALTER TABLE switch.generation_plant_group_member
38+
ADD CONSTRAINT generation_plant_group_member_generation_plant_id_fkey
39+
FOREIGN KEY (generation_plant_id)
40+
REFERENCES switch.generation_plant (generation_plant_id);
41+
42+
COMMENT ON TABLE switch.generation_plant_group_member
43+
IS 'This table is a many-to-many table that specifies the generation plants that are associated with a generation group.';
44+
45+
CREATE TABLE switch.generation_plant_scenario_group_member
46+
(
47+
generation_plant_scenario_id integer,
48+
generation_plant_group_id integer,
49+
PRIMARY KEY (generation_plant_scenario_id, generation_plant_group_id)
50+
);
51+
52+
ALTER TABLE switch.generation_plant_scenario_group_member
53+
ADD CONSTRAINT generation_plant_scenario_group_member_scenario_id_fkey
54+
FOREIGN KEY (generation_plant_scenario_id)
55+
REFERENCES switch.generation_plant_scenario (generation_plant_scenario_id);
56+
57+
ALTER TABLE switch.generation_plant_scenario_group_member
58+
ADD CONSTRAINT generation_plant_scenario_group_member_group_id_fkey
59+
FOREIGN KEY (generation_plant_group_id)
60+
REFERENCES switch.generation_plant_group (generation_plant_group_id);
61+
62+
COMMENT ON TABLE switch.generation_plant_scenario_group_member
63+
IS 'This table is a many-to-many table that specifies which generation plant groups belong to which generation plant scenarios';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
####################
3+
Add column gen_store_energy_to_power_ratio
4+
5+
Date applied: 2021-06-18
6+
Description:
7+
This script adds a column to the generation_plant
8+
table called gen_storage_energy_to_power_ratio specifying
9+
the storage duration
10+
#################
11+
*/
12+
13+
ALTER TABLE switch.generation_plant ADD COLUMN gen_storage_energy_to_power_ratio real;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
####################
3+
Add transmission options
4+
5+
Date applied: 2021-06-23
6+
Description:
7+
Adds two rows to table transmission_base_capital_cost_scenario_id
8+
1. A scenario where transmission costs are zero.
9+
2. A scenario where transmission costs are infinity (building not allowed).
10+
#################
11+
*/
12+
13+
INSERT INTO switch.transmission_base_capital_cost (transmission_base_capital_cost_scenario_id,
14+
trans_capital_cost_per_mw_km, description)
15+
VALUES (3, 'Infinity', 'For scenarios where building transmission is forbidden.');
16+
17+
INSERT INTO switch.transmission_base_capital_cost (transmission_base_capital_cost_scenario_id,
18+
trans_capital_cost_per_mw_km, description)
19+
VALUES (4, 0, 'For scenarios where transmission is unlimited.');
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
####################
3+
Add transmission options
4+
5+
Date applied: 2021-06-29
6+
Description:
7+
Adds an extra scenario to the database for a 10x increase in transmission costs.
8+
#################
9+
*/
10+
11+
INSERT INTO switch.transmission_base_capital_cost (transmission_base_capital_cost_scenario_id,
12+
trans_capital_cost_per_mw_km, description)
13+
VALUES (5, 9600, '10x the costs of scenario #2. Approximates the no TX case.');

switch_model/__main__.py

Lines changed: 70 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,75 +4,81 @@
44
"""Script to handle switch <cmd> calls from the command line."""
55
from __future__ import print_function
66

7-
import sys, os
7+
import argparse
8+
import importlib
9+
import sys
810
import switch_model
11+
from switch_model.utilities import get_git_branch
12+
13+
14+
def version():
15+
print("Switch model version " + switch_model.__version__)
16+
branch = get_git_branch()
17+
if branch is not None:
18+
print(f"Switch Git branch: {branch}")
19+
return 0
20+
21+
22+
def help_text():
23+
print(
24+
f"Must specifiy one of the following commands: {list(cmds.keys())}.\nE.g. Run 'switch solve' or 'switch get_inputs'."
25+
)
26+
27+
28+
def get_module_runner(module):
29+
def runner():
30+
importlib.import_module(module).main()
31+
32+
return runner
33+
34+
35+
cmds = {
36+
"solve": get_module_runner("switch_model.solve"),
37+
"solve-scenarios": get_module_runner("switch_model.solve_scenarios"),
38+
"test": get_module_runner("switch_model.test"),
39+
"upgrade": get_module_runner("switch_model.upgrade"),
40+
"get_inputs": get_module_runner("switch_model.wecc.get_inputs"),
41+
"drop": get_module_runner("switch_model.tools.drop"),
42+
"new": get_module_runner("switch_model.tools.new"),
43+
"graph": get_module_runner("switch_model.tools.graph.cli_graph"),
44+
"compare": get_module_runner("switch_model.tools.graph.cli_compare"),
45+
"db": get_module_runner("switch_model.wecc.__main__"),
46+
"help": help_text,
47+
}
948

1049

1150
def main():
12-
# TODO make a proper command line tool with help information for each option
13-
cmds = [
14-
"solve",
15-
"solve-scenarios",
16-
"test",
17-
"upgrade",
18-
"get_inputs",
19-
"--version",
20-
"drop",
21-
"new",
22-
"graph",
23-
"compare",
24-
"sampling",
25-
]
26-
if len(sys.argv) >= 2 and sys.argv[1] in cmds:
27-
# If users run a script from the command line, the location of the script
28-
# gets added to the start of sys.path; if they call a module from the
29-
# command line then an empty entry gets added to the start of the path,
30-
# indicating the current working directory. This module is often called
31-
# from a command-line script, but we want the current working
32-
# directory in the path because users may try to load local modules via
33-
# the configuration files, so we make sure that's always in the path.
34-
sys.path[0] = ""
35-
36-
# adjust the argument list to make it look like someone ran "python -m <module>" directly
37-
cmd = sys.argv[1]
38-
sys.argv[0] += " " + cmd
51+
parser = argparse.ArgumentParser(add_help=False)
52+
parser.add_argument(
53+
"--version", default=False, action="store_true", help="Get version info"
54+
)
55+
parser.add_argument(
56+
"subcommand",
57+
choices=cmds.keys(),
58+
help="The possible switch subcommands",
59+
nargs="?",
60+
default="help",
61+
)
62+
63+
# If users run a script from the command line, the location of the script
64+
# gets added to the start of sys.path; if they call a module from the
65+
# command line then an empty entry gets added to the start of the path,
66+
# indicating the current working directory. This module is often called
67+
# from a command-line script, but we want the current working
68+
# directory in the path because users may try to load local modules via
69+
# the configuration files, so we make sure that's always in the path.
70+
sys.path[0] = ""
71+
72+
args, remaining_args = parser.parse_known_args()
73+
74+
if args.version:
75+
return version()
76+
77+
# adjust the argument list to make it look like someone ran "python -m <module>" directly
78+
if len(sys.argv) > 1:
79+
sys.argv[0] += " " + sys.argv[1]
3980
del sys.argv[1]
40-
if cmd == "--version":
41-
print("Switch model version " + switch_model.__version__)
42-
from switch_model.utilities import get_git_branch
43-
44-
branch = get_git_branch()
45-
if branch is not None:
46-
print(f"Switch Git branch: {branch}")
47-
return 0
48-
if cmd == "solve":
49-
from switch_model.solve import main
50-
elif cmd == "solve-scenarios":
51-
from switch_model.solve_scenarios import main
52-
elif cmd == "test":
53-
from switch_model.test import main
54-
elif cmd == "upgrade":
55-
from switch_model.upgrade import main
56-
elif cmd == "get_inputs":
57-
from switch_model.wecc.get_inputs import main
58-
elif cmd == "sampling":
59-
from switch_model.wecc.sampling import main
60-
elif cmd == "drop":
61-
from switch_model.tools.drop import main
62-
elif cmd == "new":
63-
from switch_model.tools.new import main
64-
elif cmd == "graph":
65-
from switch_model.tools.graph.cli_graph import main
66-
elif cmd == "compare":
67-
from switch_model.tools.graph.cli_compare import main
68-
main()
69-
else:
70-
print(
71-
"Usage: {} {{{}}} ...".format(
72-
os.path.basename(sys.argv[0]), ", ".join(cmds)
73-
)
74-
)
75-
print("Use one of these commands with --help for more information.")
81+
cmds[args.subcommand]()
7682

7783

7884
if __name__ == "__main__":

switch_model/energy_sources/fuel_costs/markets.py

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,14 @@ def define_components(mod):
209209
become non-linear.
210210
211211
"""
212+
# When this variable is True we only allow positive fuel costs
213+
# This simplifies the model since we can set some of our constraints
214+
# as greater than instead of equals.
215+
ONLY_POSITIVE_RFM_COSTS = False
212216

213-
mod.REGIONAL_FUEL_MARKETS = Set(dimen=1, input_file="regional_fuel_markets.csv")
214-
mod.rfm_fuel = Param(
215-
mod.REGIONAL_FUEL_MARKETS,
216-
within=mod.FUELS,
217-
input_file="regional_fuel_markets.csv",
218-
input_column="fuel",
219-
)
217+
mod.REGIONAL_FUEL_MARKETS = Set(dimen=1)
218+
mod.rfm_fuel = Param(mod.REGIONAL_FUEL_MARKETS, within=mod.FUELS)
220219
mod.ZONE_RFMS = Set(
221-
input_file="zone_to_regional_fuel_market.csv",
222220
dimen=2,
223221
validate=lambda m, z, rfm: (
224222
rfm in m.REGIONAL_FUEL_MARKETS and z in m.LOAD_ZONES
@@ -247,22 +245,14 @@ def zone_rfm_init(m, load_zone, fuel):
247245

248246
# RFM_SUPPLY_TIERS = [(regional_fuel_market, period, supply_tier_index)...]
249247
mod.RFM_SUPPLY_TIERS = Set(
250-
input_file="fuel_supply_curves.csv",
251248
dimen=3,
252249
validate=lambda m, r, p, st: (r in m.REGIONAL_FUEL_MARKETS and p in m.PERIODS),
253250
)
254251
mod.rfm_supply_tier_cost = Param(
255-
mod.RFM_SUPPLY_TIERS,
256-
input_file="fuel_supply_curves.csv",
257-
input_column="unit_cost",
258-
within=Reals,
252+
mod.RFM_SUPPLY_TIERS, within=PositiveReals if ONLY_POSITIVE_RFM_COSTS else Reals
259253
)
260254
mod.rfm_supply_tier_limit = Param(
261-
mod.RFM_SUPPLY_TIERS,
262-
input_file="fuel_supply_curves.csv",
263-
input_column="max_avail_at_cost",
264-
within=NonNegativeReals,
265-
default=float("inf"),
255+
mod.RFM_SUPPLY_TIERS, within=NonNegativeReals, default=float("inf")
266256
)
267257
mod.min_data_check(
268258
"RFM_SUPPLY_TIERS", "rfm_supply_tier_cost", "rfm_supply_tier_limit"
@@ -327,8 +317,6 @@ def zone_fuel_cost_adder_validate(model, val, z, fuel, p):
327317
mod.zone_fuel_cost_adder = Param(
328318
mod.ZONE_FUELS,
329319
mod.PERIODS,
330-
input_file="zone_fuel_cost_diff.csv",
331-
input_column="fuel_cost_adder",
332320
within=Reals,
333321
default=0,
334322
validate=zone_fuel_cost_adder_validate,
@@ -383,13 +371,20 @@ def GENS_FOR_RFM_PERIOD_rule(m, rfm, p):
383371
enforce_fuel_consumption_scaling_factor = 1e-2
384372

385373
def Enforce_Fuel_Consumption_rule(m, rfm, p):
386-
return m.FuelConsumptionInMarket[
387-
rfm, p
388-
] * enforce_fuel_consumption_scaling_factor == enforce_fuel_consumption_scaling_factor * sum(
374+
lhs = (
375+
m.FuelConsumptionInMarket[rfm, p] * enforce_fuel_consumption_scaling_factor
376+
)
377+
rhs = enforce_fuel_consumption_scaling_factor * sum(
389378
m.GenFuelUseRate[g, t, m.rfm_fuel[rfm]] * m.tp_weight_in_year[t]
390379
for g in m.GENS_FOR_RFM_PERIOD[rfm, p]
391380
for t in m.TPS_IN_PERIOD[p]
392381
)
382+
# If we have only positive costs, FuelConsumptionInMarket will automatically
383+
# try to be minimized in which case we can use a one-sided constraint
384+
if ONLY_POSITIVE_RFM_COSTS:
385+
return lhs >= rhs
386+
else:
387+
return lhs == rhs
393388

394389
mod.Enforce_Fuel_Consumption = Constraint(
395390
mod.REGIONAL_FUEL_MARKETS, mod.PERIODS, rule=Enforce_Fuel_Consumption_rule
@@ -452,6 +447,39 @@ def load_inputs(mod, switch_data, inputs_dir):
452447
load_zone, fuel, period, fuel_cost
453448
454449
"""
450+
# Include select in each load() function so that it will check out
451+
# column names, be indifferent to column order, and throw an error
452+
# message if some columns are not found.
453+
454+
switch_data.load_aug(
455+
filename=os.path.join(inputs_dir, "regional_fuel_markets.csv"),
456+
select=("regional_fuel_market", "fuel"),
457+
index=mod.REGIONAL_FUEL_MARKETS,
458+
param=(mod.rfm_fuel),
459+
)
460+
switch_data.load_aug(
461+
filename=os.path.join(inputs_dir, "fuel_supply_curves.csv"),
462+
select=(
463+
"regional_fuel_market",
464+
"period",
465+
"tier",
466+
"unit_cost",
467+
"max_avail_at_cost",
468+
),
469+
index=mod.RFM_SUPPLY_TIERS,
470+
param=(mod.rfm_supply_tier_cost, mod.rfm_supply_tier_limit),
471+
)
472+
switch_data.load_aug(
473+
filename=os.path.join(inputs_dir, "zone_to_regional_fuel_market.csv"),
474+
set=mod.ZONE_RFMS,
475+
)
476+
switch_data.load_aug(
477+
filename=os.path.join(inputs_dir, "zone_fuel_cost_diff.csv"),
478+
optional=True,
479+
select=("load_zone", "fuel", "period", "fuel_cost_adder"),
480+
param=(mod.zone_fuel_cost_adder),
481+
)
482+
455483
# Load a simple specifications of costs if the file exists. The
456484
# actual loading, error checking, and casting into a supply curve is
457485
# slightly complicated, so I moved that logic to a separate function.

0 commit comments

Comments
 (0)