Skip to content

Commit 04433c3

Browse files
committed
Merge remote-tracking branch 'rael/wecc' into make-mps
2 parents effc5b0 + dba8ced commit 04433c3

20 files changed

+945
-246
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: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ 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

213217
mod.REGIONAL_FUEL_MARKETS = Set(dimen=1)
214218
mod.rfm_fuel = Param(mod.REGIONAL_FUEL_MARKETS, within=mod.FUELS)
@@ -244,7 +248,9 @@ def zone_rfm_init(m, load_zone, fuel):
244248
dimen=3,
245249
validate=lambda m, r, p, st: (r in m.REGIONAL_FUEL_MARKETS and p in m.PERIODS),
246250
)
247-
mod.rfm_supply_tier_cost = Param(mod.RFM_SUPPLY_TIERS, within=Reals)
251+
mod.rfm_supply_tier_cost = Param(
252+
mod.RFM_SUPPLY_TIERS, within=PositiveReals if ONLY_POSITIVE_RFM_COSTS else Reals
253+
)
248254
mod.rfm_supply_tier_limit = Param(
249255
mod.RFM_SUPPLY_TIERS, within=NonNegativeReals, default=float("inf")
250256
)
@@ -365,13 +371,20 @@ def GENS_FOR_RFM_PERIOD_rule(m, rfm, p):
365371
enforce_fuel_consumption_scaling_factor = 1e-2
366372

367373
def Enforce_Fuel_Consumption_rule(m, rfm, p):
368-
return m.FuelConsumptionInMarket[
369-
rfm, p
370-
] * 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(
371378
m.GenFuelUseRate[g, t, m.rfm_fuel[rfm]] * m.tp_weight_in_year[t]
372379
for g in m.GENS_FOR_RFM_PERIOD[rfm, p]
373380
for t in m.TPS_IN_PERIOD[p]
374381
)
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
375388

376389
mod.Enforce_Fuel_Consumption = Constraint(
377390
mod.REGIONAL_FUEL_MARKETS, mod.PERIODS, rule=Enforce_Fuel_Consumption_rule

switch_model/generators/core/dispatch.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,57 @@ def init(m, gen, period):
252252
mod.GEN_TPS, rule=lambda m, g, t: m.GenCapacity[g, m.tp_period[t]]
253253
)
254254
mod.DispatchGen = Var(mod.GEN_TPS, within=NonNegativeReals)
255-
mod.DispatchGenByFuel = Var(mod.GEN_TP_FUELS, within=NonNegativeReals)
255+
256+
##########################################
257+
# Define DispatchGenByFuel
258+
#
259+
# Previously DispatchGenByFuel was simply a Variable for all the projects and a constraint ensured
260+
# that the sum of DispatchGenByFuel across all fuels was equal the total dispatch for that project.
261+
# However this approach creates extra variables in our model for projects that have only one fuel.
262+
# Although these extra variables likely get removed during Gurobi pre-solve, we've nonetheless
263+
# simplified the model here to reduce time in presolve and ensure the model is always
264+
# simplified regardless of the solving method.
265+
#
266+
# To do this we redefine DispatchGenByFuel to be an
267+
# expression that is equal to DispatchGenByFuelVar when we have multiple fuels but
268+
# equal to DispatchGen when we have only one fuel.
269+
270+
# Define a set that is used to define DispatchGenByFuelVar
271+
mod.GEN_TP_FUELS_FOR_MULTIFUELS = Set(
272+
dimen=3,
273+
initialize=mod.GEN_TP_FUELS,
274+
filter=lambda m, g, t, f: g in m.MULTIFUEL_GENS,
275+
doc="Same as GEN_TP_FUELS but only includes multi-fuel projects",
276+
)
277+
# DispatchGenByFuelVar is a variable that exists only for multi-fuel projects.
278+
mod.DispatchGenByFuelVar = Var(
279+
mod.GEN_TP_FUELS_FOR_MULTIFUELS, within=NonNegativeReals
280+
)
281+
# DispatchGenByFuel_Constraint ensures that the sum of all the fuels is DispatchGen
256282
mod.DispatchGenByFuel_Constraint = Constraint(
257283
mod.FUEL_BASED_GEN_TPS,
258-
rule=lambda m, g, t: sum(
259-
m.DispatchGenByFuel[g, t, f] for f in m.FUELS_FOR_GEN[g]
260-
)
261-
== m.DispatchGen[g, t],
284+
rule=lambda m, g, t: (
285+
Constraint.Skip
286+
if g not in m.MULTIFUEL_GENS
287+
else sum(
288+
m.DispatchGenByFuelVar[g, t, f] for f in m.FUELS_FOR_MULTIFUEL_GEN[g]
289+
)
290+
== m.DispatchGen[g, t]
291+
),
262292
)
263293

294+
# Define DispatchGenByFuel to equal the matching variable if we have many fuels but to equal
295+
# the total dispatch if we have only one fuel.
296+
mod.DispatchGenByFuel = Expression(
297+
mod.GEN_TP_FUELS,
298+
rule=lambda m, g, t, f: m.DispatchGenByFuelVar[g, t, f]
299+
if g in m.MULTIFUEL_GENS
300+
else m.DispatchGen[g, t],
301+
)
302+
303+
# End Defining DispatchGenByFuel
304+
##########################################
305+
264306
# Only used to improve the performance of calculating ZoneTotalCentralDispatch and ZoneTotalDistributedDispatch
265307
mod.GENS_FOR_ZONE_TPS = Set(
266308
mod.LOAD_ZONES,

0 commit comments

Comments
 (0)