Skip to content

Commit 03619ac

Browse files
committed
initial commit
0 parents  commit 03619ac

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed

urps.py

+390
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
from coopr.opt.base import SolverFactory
2+
from coopr.pyomo import *
3+
from datetime import datetime
4+
from numpy import arange
5+
import os
6+
import pandas as pd
7+
import pdb
8+
9+
10+
def create_model(filename, timesteps):
11+
12+
m = ConcreteModel()
13+
m.name = 'URPS'
14+
m.settings = {
15+
'dateformat': '%Y%m%dT%H%M%S',
16+
'basename': os.path.basename(os.path.splitext(filename)[0]),
17+
'timesteps': timesteps,
18+
}
19+
m.created = datetime.now().strftime(m.settings['dateformat'])
20+
21+
# Preparations
22+
# ============
23+
# Excel import
24+
# use Pandas DataFrames instead of Pyomo parameters for entity
25+
# attributes. Syntax to access a value:
26+
#
27+
# m.process.loc[pro, coin, cout][attribute]
28+
#
29+
xls = pd.ExcelFile(filename)
30+
m.site = xls.parse('Site', index_col=[0])
31+
m.commodity = xls.parse('Commodity', index_col=[0,1,2])
32+
m.process = xls.parse('Process', index_col=[0,1,2])
33+
m.transmission = xls.parse('Transmission', index_col=[0,1,2])
34+
m.storage = xls.parse('Storage', index_col=[0,1])
35+
m.demand = xls.parse('Demand', index_col=[0])
36+
m.supim = xls.parse('SupIm', index_col=[0])
37+
38+
# derive annuity factor for process and storage
39+
m.process['annuity_factor'] = annuity_factor(m.process['depreciation'], m.process['wacc'])
40+
m.transmission['annuity_factor'] = annuity_factor(m.transmission['depreciation'], m.transmission['wacc'])
41+
m.storage['annuity_factor'] = annuity_factor(m.storage['depreciation'], m.storage['wacc'])
42+
43+
# Sets
44+
# ====
45+
# Syntax: m.{name} = Set({domain}, initialize={values})
46+
# where name: set name
47+
# domain: set domain for tuple sets, a cartesian set product
48+
# values: set values, a list or array of element tuples
49+
m.t = Set(initialize=m.settings['timesteps'])
50+
m.tm = Set(within=m.t, initialize=m.settings['timesteps'][1:])
51+
m.sit = Set(initialize=m.site.index.levels[0])
52+
m.com = Set(initialize=m.commodity.index.levels[1])
53+
m.com_type = Set(initialize=m.commodity.index.levels[2])
54+
m.pro = Set(initialize=m.process.index.levels[0])
55+
m.sto = Set(initialize=m.storage.index.levels[0])
56+
m.cost_type = Set(initialize=['Inv', 'Fix', 'Var', 'Fuel'])
57+
58+
# sets of existing tuples:
59+
# co_tuples = [('Coal', 'Stock'), ('Wind', 'SupIm'), ('ElecAC', 'Demand')...]
60+
# pro_tuples = [('pp', 'Coal', 'ElecAC'), ('wt', 'Wind', 'ElecAC')...]
61+
# sto_tuples = [('bat', 'ElecDC'), ('pst', 'ElecAC')...]
62+
m.co_tuples = Set(within=m.sit*m.com*m.com_type, initialize=m.commodity.index)
63+
m.pro_tuples = Set(within=m.sit*m.pro*m.com*m.com, initialize=m.process.index)
64+
m.tra_tuples = Set(within=m.sit*m.sit*m.com, initialize=m.transport.index)
65+
m.sto_tuples = Set(within=m.sit*m.sto*m.com, initialize=m.storage.index)
66+
67+
# subsets of commodities by type
68+
# for shorter equations that apply to only one commodity type
69+
m.com_supim = Set(within=m.com, initialize=(c[0] for c in m.com_tuples if c[1] == 'SupIm'))
70+
m.com_stock = Set(within=m.com, initialize=(c[0] for c in m.com_tuples if c[1] == 'Stock'))
71+
m.com_demand = Set(within=m.com, initialize=(c[0] for c in m.com_tuples if c[1] == 'Demand'))
72+
73+
# Parameters
74+
# ==========
75+
# for model entities (commodity, process, storage) no Pyomo parames
76+
# are needed, just use the DataFrames m.commodity, m.process and
77+
# m.storage directly.
78+
# Syntax: m.{name} = Param({domain}, initialize={values})
79+
# where name: param name
80+
# domain: one or multiple model sets; empty for scalar parameters
81+
# values: dict of parameter values, addressed by elements of domain sets
82+
m.weight = Param(initialize=float(8760) / len(m.t))
83+
84+
# Variables
85+
# =========
86+
# listed alphabetically
87+
# Syntax: m.{name} = Var({domain}, within={range})
88+
# where name: variable name
89+
# domain: variable domain, consisting of one or multiple sets
90+
# range: variable values, like Binary, Integer, NonNegativeReals
91+
m.cap_pro = Var(m.pro_tuples, within=NonNegativeReals)
92+
m.cap_pro_new = Var(m.pro_tuples, within=NonNegativeReals)
93+
m.cap_sto_c = Var(m.sto_tuples, within=NonNegativeReals)
94+
m.cap_sto_c_new = Var(m.sto_tuples, within=NonNegativeReals)
95+
m.cap_sto_p = Var(m.sto_tuples, within=NonNegativeReals)
96+
m.cap_sto_p_new = Var(m.sto_tuples, within=NonNegativeReals)
97+
m.co2_pro_out = Var(m.tm, m.pro_tuples, within=NonNegativeReals)
98+
m.costs = Var(m.cost_type, within=NonNegativeReals)
99+
m.e_pro_in = Var(m.tm, m.pro_tuples, within=NonNegativeReals)
100+
m.e_pro_out = Var(m.tm, m.pro_tuples, within=NonNegativeReals)
101+
m.e_sto_in = Var(m.tm, m.sto_tuples, within=NonNegativeReals)
102+
m.e_sto_out = Var(m.tm, m.sto_tuples, within=NonNegativeReals)
103+
m.e_sto_con = Var(m.t, m.sto_tuples, within=NonNegativeReals)
104+
105+
# Equation definition
106+
# ===================
107+
# listed by topic. All equations except the Objective function are
108+
# of type Constraint, although there are two semantics for those,
109+
# indicated by the name prefix (def, res).
110+
# - def: definition, usually equations, defining variable values
111+
# - res: restriction, usually inequalities, limiting variable values
112+
# topics
113+
# - commodity
114+
# - process
115+
# - storage
116+
# - emissions
117+
# - costs
118+
119+
# commodity
120+
def res_demand_rule(m, tm, com, com_type):
121+
if co not in m.com_demand:
122+
return Constraint.Skip
123+
else:
124+
provided_energy = - commodity_balance(m, tm, com)
125+
return provided_energy >= \
126+
m.demand.loc[tm][com] * \
127+
m.commodity.loc[com, com_type]['peak']
128+
129+
def res_stock_hour_rule(m, tm, com, com_type):
130+
if com not in m.com_stock:
131+
return Constraint.Skip
132+
else:
133+
# calculate total consumption of commodity co in timestep tm
134+
total_consumption = commodity_balance(m, tm, com)
135+
return total_consumption <= m.commodity.loc[com, com_type]['maxperhour']
136+
137+
138+
def res_stock_total_rule(m, com, com_type):
139+
if com not in m.com_stock:
140+
return Constraint.Skip
141+
else:
142+
# calculate total consumption of commodity co
143+
total_consumption = 0
144+
for tm in m.tm:
145+
total_consumption += commodity_balance(m, tm, com)
146+
147+
return total_consumption <= m.commodity.loc[com, com_type]['max']
148+
149+
150+
# process
151+
def def_process_capacity_rule(m, tm, pro, coin, cout):
152+
return m.cap_pro[pro,coin,cout] == \
153+
m.cap_pro_new[pro,coin,cout] + \
154+
m.process.loc[pro,coin,cout]['inst-cap']
155+
156+
def def_process_output_rule(m, tm, pro, coin, cout):
157+
return m.e_pro_out[tm, pro, coin, cout] == \
158+
m.e_pro_in[tm, pro, coin, cout] * \
159+
m.process.loc[pro, coin, cout]['eff']
160+
161+
def def_intermittent_supply_rule(m, tm, pro, coin, cout):
162+
if coin in m.com_supim:
163+
return m.e_pro_in[tm, pro, coin, cout] == \
164+
m.cap_pro[pro, coin, cout] * m.supim.loc[tm][coin]
165+
else:
166+
return Constraint.Skip
167+
168+
def def_co2_emissions_rule(m, tm, pro, coin, cout):
169+
return m.co2_pro_out[tm, pro, coin, cout] == \
170+
m.e_pro_in[tm, pro, coin, cout] * \
171+
m.process.loc[pro, coin, cout]['co2'] * \
172+
m.weight
173+
174+
def res_process_output_by_capacity_rule(m, tm, pro, coin, cout):
175+
return m.e_pro_out[tm, pro, coin, cout] <= m.cap_pro[pro, coin, cout]
176+
177+
def res_process_capacity_rule(m, pro, coin, cout):
178+
return (m.process.loc[pro, coin, cout]['cap-lo'],
179+
m.cap_pro[pro, coin, cout],
180+
m.process.loc[pro, coin, cout]['cap-up'])
181+
182+
# storage
183+
def def_storage_state_rule(m, t, sto, com):
184+
return m.e_sto_con[t, sto, com] == \
185+
m.e_sto_con[t-1, sto, com] + \
186+
m.e_sto_in[t, sto, com] * m.storage.loc[sto, com]['eff-in'] - \
187+
m.e_sto_out[t, sto, com] / m.storage.loc[sto, com]['eff-out']
188+
189+
def def_storage_power_rule(m, sto, com):
190+
return m.cap_sto_p[sto, com] == \
191+
m.cap_sto_p_new[sto, com] + \
192+
m.storage.loc[sto, com]['inst-cap-p']
193+
194+
def def_storage_capacity_rule(m, sto, com):
195+
return m.cap_sto_c[sto, com] == \
196+
m.cap_sto_c_new[sto, com] + \
197+
m.storage.loc[sto, com]['inst-cap-p']
198+
199+
def res_storage_input_by_power_rule(m, t, sto, com):
200+
return m.e_sto_in[t, sto, com] <= m.cap_sto_p[sto, com]
201+
202+
def res_storage_output_by_power_rule(m, t, sto, co):
203+
return m.e_sto_out[t, sto, co] <= m.cap_sto_p[sto, co]
204+
205+
def res_storage_state_by_capacity_rule(m, t, sto, com):
206+
return m.e_sto_con[t, sto, com] <= m.cap_sto_c[sto, com]
207+
208+
def res_storage_power_rule(m, sto, com):
209+
return (m.storage.loc[sto, com]['cap-lo-p'],
210+
m.cap_sto_p[sto, com],
211+
m.storage.loc[sto, com]['cap-up-p'])
212+
213+
def res_storage_capacity_rule(m, sto, com):
214+
return (m.storage.loc[sto, com]['cap-lo-c'],
215+
m.cap_sto_c[sto, com],
216+
m.storage.loc[sto, com]['cap-up-c'])
217+
218+
def res_initial_and_final_storage_state_rule(m, t, sto, com):
219+
if t == m.t[1]: # first timestep (Pyomo uses 1-based indexing)
220+
return m.e_sto_con[t, sto, com] == \
221+
m.cap_sto_c[sto, com] * \
222+
m.storage.loc[sto, com]['init']
223+
elif t == m.t[-1]: # last timestep
224+
return m.e_sto_con[t, sto, com] >= \
225+
m.cap_sto_c[sto, com] * \
226+
m.storage.loc[sto, com]['init']
227+
else:
228+
return Constraint.Skip
229+
230+
# emissions
231+
def res_co2_emission_rule(m):
232+
return summation(m.co2_pro_out) <= m.commodity.loc['CO2','Env']['max']
233+
234+
# costs
235+
def def_costs_rule(m, cost_type):
236+
""" calculate total costs by cost type
237+
238+
This functions sums up process activity and capacity expansions
239+
and sums them in the cost types that are specified in the set
240+
m.cost_type. To change or add cost types, add/change entries
241+
there and modify the if/elif cases in this function accordingly.
242+
243+
Cost types are
244+
245+
- Investment costs for process power, storage power and
246+
storage capacity. They are multiplied by the annuity
247+
factors, which in turn are derived from the attributes
248+
'depreciation' and 'wacc'.
249+
250+
- Fixed costs for process power, storage power and storage
251+
capacity.
252+
"""
253+
if cost_type == 'Inv':
254+
return m.costs['Inv'] == \
255+
sum(m.cap_pro_new[p] *
256+
m.process.loc[p]['inv-cost'] *
257+
m.process.loc[p]['annuity_factor']
258+
for p in m.pro_tuples) + \
259+
sum(m.cap_sto_p_new[s] *
260+
m.storage.loc[s]['inv-cost-p'] *
261+
m.storage.loc[s]['annuity_factor'] +
262+
m.cap_sto_c_new[s] *
263+
m.storage.loc[s]['inv-cost-c'] *
264+
m.storage.loc[s]['annuity_factor']
265+
for s in m.sto_tuples)
266+
267+
elif cost_type == 'Fix':
268+
return m.costs['Fix'] == \
269+
sum(m.cap_pro[p] * m.process.loc[p]['fix-cost']
270+
for p in m.pro_tuples) + \
271+
sum(m.cap_sto_p[s] * m.storage.loc[s]['fix-cost-p'] +
272+
m.cap_sto_c[s] * m.storage.loc[s]['fix-cost-c']
273+
for s in m.sto_tuples)
274+
275+
elif cost_type == 'Var':
276+
return m.costs['Var'] == \
277+
sum(m.e_pro_out[(tm,) + p] *
278+
m.process.loc[p]['var-cost'] *
279+
m.weight
280+
for tm in m.tm for p in m.pro_tuples) + \
281+
sum(m.e_sto_con[(tm,) + s] *
282+
m.storage.loc[s]['var-cost-c'] * m.weight +
283+
(m.e_sto_in[(tm,) + s] + m.e_sto_out[(tm,) + s]) *
284+
m.storage.loc[s]['var-cost-p'] * m.weight
285+
for tm in m.tm for s in m.sto_tuples)
286+
287+
elif cost_type == 'Fuel':
288+
return m.costs['Fuel'] == \
289+
sum(m.e_pro_in[p] * m.commodity[c]['price'] * m.weight
290+
for p in m.pro_tuples
291+
for c in m.com_tuples
292+
if c[1] == p[1])
293+
294+
else:
295+
raise NotImplementedError("Unknown cost type!")
296+
297+
def obj_rule(m):
298+
return summation(m.costs)
299+
300+
301+
# Equation declaration
302+
# ====================
303+
# declarations connect rule functions to the model, specifying
304+
# the model sets for which the constraints are enforced
305+
306+
# commodity
307+
m.res_demand = Constraint(m.tm, m.com_tuples)
308+
m.res_stock_hour = Constraint(m.tm, m.com_tuples)
309+
m.res_stock_total = Constraint(m.com_tuples)
310+
311+
# process
312+
m.def_process_capacity = Constraint(m.tm, m.pro_tuples)
313+
m.def_process_output = Constraint(m.tm, m.pro_tuples)
314+
m.def_intermittent_supply = Constraint(m.tm, m.pro_tuples)
315+
m.def_co2_emissions = Constraint(m.tm, m.pro_tuples)
316+
m.res_process_output_by_capacity = Constraint(m.tm, m.pro_tuples)
317+
m.res_process_capacity = Constraint(m.pro_tuples)
318+
319+
# storage
320+
m.def_storage_state = Constraint(m.tm, m.sto_tuples)
321+
m.def_storage_power = Constraint(m.sto_tuples)
322+
m.def_storage_capacity = Constraint(m.sto_tuples)
323+
m.res_storage_input_by_power = Constraint(m.tm, m.sto_tuples)
324+
m.res_storage_output_by_power = Constraint(m.tm, m.sto_tuples)
325+
m.res_storage_state_by_capacity = Constraint(m.t, m.sto_tuples)
326+
m.res_storage_power = Constraint(m.sto_tuples)
327+
m.res_storage_capacity = Constraint(m.sto_tuples)
328+
m.res_initial_and_final_storage_state = Constraint(m.t, m.sto_tuples)
329+
330+
# emissions
331+
m.res_co2_emission = Constraint()
332+
333+
# costs
334+
m.def_costs = Constraint(m.cost_type)
335+
m.obj = Objective(sense=minimize)
336+
337+
return m
338+
339+
340+
341+
def annuity_factor(n, i):
342+
""" return annuity factor
343+
344+
Evaluates the annuity factor formula for depreciation duration
345+
and interest rate. Works also well for equally sized numpy arrays
346+
of values for n and i.
347+
Args:
348+
n: depreciation period (years)
349+
i: interest rate (percent, e.g. 0.06 means 6 %)
350+
351+
Returns:
352+
Value of the expression
353+
(1+i)**n * i / ((1+i)**n - 1)
354+
355+
"""
356+
return (1+i)**n * i / ((1+i)**n - 1)
357+
358+
359+
def commodity_balance(m, tm, com):
360+
""" calculate commodity balance at given timestep.
361+
362+
For a given commodity co and timestep tm, calculate the balance of
363+
consumed (to process/storage, counts positive) and provided (from
364+
process/storage, counts negative) energy. Used as helper function
365+
in create_model for constraints on demand and stock commodities.
366+
367+
Args:
368+
m: the model object
369+
tm: the timestep
370+
co: the commodity
371+
372+
Returns
373+
balance: net value of consumed (+) or provided (-) energy
374+
375+
"""
376+
balance = 0
377+
for p in m.pro_tuples:
378+
if p[1] == com:
379+
# usage as input for process increases balance
380+
balance += m.e_pro_in[(tm,)+p]
381+
if p[2] == com:
382+
# output from processes decreases balance
383+
balance -= m.e_pro_out[(tm,)+p]
384+
for s in m.sto_tuples:
385+
# usage as input for storage increases consumption
386+
# output from storage decreases consumption
387+
if s[1] == com:
388+
balance += m.e_sto_in[(tm,)+s]
389+
balance -= m.e_sto_out[(tm,)+s]
390+
return balance

0 commit comments

Comments
 (0)