def add_variable(self, name, time=None, fixed_value=None, index=None, **kwargs): ''' Create a new variable and add it to the object's variables and the model's variables. :param name: name of optimization variable. :param kind: type of variable, specified by string. {Continuous or Binary/Boolean} :param low: low limit of variable :param high: high limit of variable :param fixed_value: a fixed value for a variable (making it a parameter) :param time: a single time for a variable :param index: a :class:`pyomo.Set` over which a variable is created ''' def map_args(kind='Continuous', low=None, high=None): return dict(bounds=(low, high), domain=variable_kinds[kind]) orig_name = name if index is None: name = self._t_id(name, time) if fixed_value is None: var = pyomo.Var(name=name, **map_args(**kwargs)) self._parent_problem().add_component_to_problem(var) else: var = pyomo.Param(name=name, default=fixed_value) # add var self._parent_problem().add_component_to_problem(var) # and set value var = self.get_variable(orig_name, time) var[None] = fixed_value else: name = self._id(name) if fixed_value is None: var = pyomo.Var(index, name=name, **map_args(**kwargs)) self._parent_problem().add_component_to_problem(var) else: var = pyomo.Param(index, name=name, default=fixed_value) self._parent_problem().add_component_to_problem(var) var = self._parent_problem().get_component(name) for i in index: var[i] = fixed_value
def add_parameter(self, name, index=None, values=None, mutable=True, default=None, **kwargs): name = self._id(name) self._parent_problem().add_component_to_problem( pyomo.Param(index, name=name, default=default, mutable=mutable, **kwargs)) if values is not None: if pd.Series(values).count() != len(values): raise ValueError('a parameter value cannot be NaN') var = self._parent_problem().get_component(name) for i in index: var[i] = values[i]
from __future__ import division from coopr import pyomo # define an abstract model model = pyomo.AbstractModel() # add number of linear constraints as attribute model.m = pyomo.Param(within=pyomo.NonNegativeIntegers) model.n = pyomo.Param(within=pyomo.NonNegativeIntegers) # although not required, it is convenient to define index sets model.I = pyomo.RangeSet(1, model.m) model.J = pyomo.RangeSet(1, model.n) # now we define the coefficients (which are themselves defined over index sets!) model.a = pyomo.Param(model.I, model.J) model.b = pyomo.Param(model.I) model.c = pyomo.Param(model.J) # the next line declares a variable indexed by the set J model.x = pyomo.Var(model.J, domain=pyomo.NonNegativeReals) def objective(model): """Abstract representation of our model objective.""" obj = pyomo.summation(model.c, model.x) return obj # add the objective function to the model object as an attribute (OBJ can be arbitrary!) model.OBJ = pyomo.Objective(rule=objective) def constraints(model, i):
from __future__ import division from coopr import pyomo # define an abstract life-cycle savings model model = pyomo.AbstractModel() ##### Define model parameters ##### # time horizon model.T = pyomo.Param(doc="time horizon", within=pyomo.NonNegativeIntegers) model.periods = pyomo.RangeSet(0, model.T) # retirement age model.R = pyomo.Param(doc="retirement age", within=pyomo.NonNegativeIntegers) # net interest rate model.r = pyomo.Param(doc='interest rate', within=pyomo.NonNegativeReals) # wages model.w0 = pyomo.Param(doc='initial real wage', within=pyomo.NonNegativeReals) model.g = pyomo.Param(doc='growth rate of real wages', within=pyomo.NonNegativeReals) def wage_schedule(model, t): """Defines the path of wages. This should really go in the .dat file!""" # extract parameters w0 = model.w0 g = model.g if t < model.R:
from __future__ import division from coopr import pyomo # define an abstract life-cycle savings model model = pyomo.AbstractModel() ##### Define model parameters ##### # time horizon model.T = pyomo.Param(doc="time horizon", within=pyomo.NonNegativeIntegers) model.periods = pyomo.RangeSet(0, model.T) # retirement age model.R = pyomo.Param(doc="retirement age", within=pyomo.NonNegativeIntegers) # net interest rate model.r = pyomo.Param(doc='interest rate', within=pyomo.NonNegativeReals) # wages def wage_schedule(model, t): """Defines the path of wages. This should really go in the .dat file!""" if t < model.R: wage = t / model.R else: wage = 0.0 return wage model.w = pyomo.Param(model.periods, doc='real wages',
def create_model(data, timesteps): """ Create a VICUS model object from input data. Creates and returns a Pyomo ConcreteModel object, given a model input file (supported formats: Excel spreadsheet [planned: SQLite DB]) Args: data: input dict with fields for Commodities, Processes, Storage and timeseries for Demand and SupIm timesteps: numpy array of timestep labels, matching the ones used in the Demand and SupIm timeseries Returns: A coopr.pyomo ConcreteModel object, ready to be instantiated and solved. """ m = pyomo.ConcreteModel() m.name = 'VICUS' m.created = datetime.now().strftime('%Y%m%dT%H%M%S', ) # Preparations # ============ # Data import. Syntax to access a value within equation definitions looks # like this: # # m.process.loc[sit, pro, coin, cout][attribute] # get_inputs = itemgetter("commodity", "process", "storage", "demand", "supim") (m.commodity, m.process, m.storage, m.demand, m.supim) = get_inputs(data) # Sets # ==== # Syntax: m.{name} = Set({domain}, initialize={values}) # where name: set name # domain: set domain for tuple sets, a cartesian set product # values: set values, a list or array of element tuples m.t = pyomo.Set(ordered=True, initialize=timesteps) m.tm = pyomo.Set(within=m.t, initialize=timesteps[1:]) m.co = pyomo.Set(initialize=m.commodity.index.levels[0]) m.coin = pyomo.Set(within=m.co, initialize=m.process.index.levels[1]) m.cout = pyomo.Set(within=m.co, initialize=m.process.index.levels[2]) m.co_type = pyomo.Set(initialize=m.commodity.index.levels[1]) m.pro = pyomo.Set(initialize=m.process.index.levels[0]) m.sto = pyomo.Set(initialize=m.storage.index.levels[0]) m.cost_type = pyomo.Set(initialize=['Inv', 'Fix', 'Var', 'Fuel']) # sets of existing tuples: # co_tuples = [('Coal', 'Stock'), ('Wind', 'SupIm'), ('ElecAC', 'Demand')...] # pro_tuples = [('pp', 'Coal', 'ElecAC'), ('wt', 'Wind', 'ElecAC')...] # sto_tuples = [('bat', 'ElecDC'), ('pst', 'ElecAC')...] m.co_tuples = pyomo.Set(within=m.co * m.co_type, initialize=m.commodity.index) m.pro_tuples = pyomo.Set(within=m.pro * m.coin * m.cout, initialize=m.process.index) m.sto_tuples = pyomo.Set(within=m.sto * m.co, initialize=m.storage.index) # subsets of commodities by type # for shorter equations that apply to only one commodity type m.co_supim = pyomo.Set(within=m.co, initialize=(c[0] for c in m.co_tuples if c[1] == 'SupIm')) m.co_stock = pyomo.Set(within=m.co, initialize=(c[0] for c in m.co_tuples if c[1] == 'Stock')) m.co_demand = pyomo.Set(within=m.co, initialize=(c[0] for c in m.co_tuples if c[1] == 'Demand')) # Parameters # ========== # for model entities (commodity, process, storage) no Pyomo params # are needed, just use the DataFrames m.commodity, m.process and # m.storage directly. # Syntax: m.{name} = Param({domain}, initialize={values}) # where name: param name # domain: one or multiple model sets; empty for scalar parameters # values: dict of values, addressed by elements of domain sets m.weight = pyomo.Param(initialize=float(8760) / len(m.t)) # Variables # ========= # listed alphabetically # Syntax: m.{name} = Var({domain}, within={range}) # where name: variable name # domain: variable domain, consisting of one or multiple sets # range: variable values, like Binary, Integer, NonNegativeReals m.cap_pro = pyomo.Var(m.pro_tuples, within=pyomo.NonNegativeReals, doc='Total process capacity (kW)') m.cap_pro_new = pyomo.Var(m.pro_tuples, within=pyomo.NonNegativeReals, doc='New process capacity (kW)') m.cap_sto_c = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='Total storage size (kWh)') m.cap_sto_c_new = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='New storage capacity (kWh)') m.cap_sto_p = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='Total storage power (kW)') m.cap_sto_p_new = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='New storage power (kW)') m.co2_pro_out = pyomo.Var( m.tm, m.pro_tuples, within=pyomo.NonNegativeReals, doc='CO2 emissions from processes (kg) per timestep') m.costs = pyomo.Var(m.cost_type, within=pyomo.NonNegativeReals, doc='Costs by type (EUR/a)') m.e_co_stock = pyomo.Var( m.tm, m.co_stock, within=pyomo.NonNegativeReals, doc='Source power flow from stock commodities (kW) per timestep') m.e_pro_in = pyomo.Var(m.tm, m.pro_tuples, within=pyomo.NonNegativeReals, doc='Power flow into process (kW) per timestep') m.e_pro_out = pyomo.Var(m.tm, m.pro_tuples, within=pyomo.NonNegativeReals, doc='Power flow out of process (kW) per timestep') m.e_sto_in = pyomo.Var(m.tm, m.sto_tuples, within=pyomo.NonNegativeReals, doc='Power flow into storage (kW) per timestep') m.e_sto_out = pyomo.Var(m.tm, m.sto_tuples, within=pyomo.NonNegativeReals, doc='Power flow out of storage (kW) per timestep') m.e_sto_con = pyomo.Var(m.t, m.sto_tuples, within=pyomo.NonNegativeReals, doc='Energy content of storage (kWh) in timestep') # Equation definition # =================== # listed by topic. All equations except the Objective function are # of type Constraint, although there are two semantics for those, # indicated by the name prefix (def, res). # - def: definition, usually equations, defining variable values # - res: restriction, usually inequalities, limiting variable values # topics # - commodity # - process # - storage # - emissions # - costs # commodity def res_demand_rule(m, tm, co, co_type): if co not in m.co_demand: return pyomo.Constraint.Skip else: provided_energy = -commodity_balance(m, tm, co) return provided_energy >= \ m.demand.loc[tm][co] * \ m.commodity.loc[co, co_type]['peak'] def def_e_co_stock_rule(m, tm, co, co_type): if co not in m.co_stock: return pyomo.Constraint.Skip else: return m.e_co_stock[tm, co] == commodity_balance(m, tm, co) def res_stock_hour_rule(m, tm, co, co_type): if co not in m.co_stock: return pyomo.Constraint.Skip else: return m.e_co_stock[tm, co] <= \ m.commodity.loc[co, co_type]['maxperhour'] def res_stock_total_rule(m, co, co_type): if co not in m.co_stock: return pyomo.Constraint.Skip else: # calculate total consumption of commodity co total_consumption = 0 for tm in m.tm: total_consumption += m.e_co_stock[tm, co] * m.weight return total_consumption <= m.commodity.loc[co, co_type]['max'] # process def def_process_capacity_rule(m, tm, pro, coin, cout): return m.cap_pro[pro,coin,cout] == \ m.cap_pro_new[pro,coin,cout] + \ m.process.loc[pro,coin,cout]['inst-cap'] def def_process_output_rule(m, tm, pro, coin, cout): return m.e_pro_out[tm, pro, coin, cout] == \ m.e_pro_in[tm, pro, coin, cout] * \ m.process.loc[pro, coin, cout]['eff'] def def_intermittent_supply_rule(m, tm, pro, coin, cout): if coin in m.co_supim: return m.e_pro_in[tm, pro, coin, cout] == \ m.cap_pro[pro, coin, cout] * m.supim.loc[tm][coin] else: return pyomo.Constraint.Skip def def_co2_emissions_rule(m, tm, pro, coin, cout): return m.co2_pro_out[tm, pro, coin, cout] == \ m.e_pro_in[tm, pro, coin, cout] * \ m.process.loc[pro, coin, cout]['co2'] * \ m.weight def res_process_output_by_capacity_rule(m, tm, pro, coin, cout): return m.e_pro_out[tm, pro, coin, cout] <= m.cap_pro[pro, coin, cout] def res_process_capacity_rule(m, pro, coin, cout): return (m.process.loc[pro, coin, cout]['cap-lo'], m.cap_pro[pro, coin, cout], m.process.loc[pro, coin, cout]['cap-up']) # storage def def_storage_state_rule(m, t, sto, co): return m.e_sto_con[t, sto, co] == \ m.e_sto_con[t-1, sto, co] + \ m.e_sto_in[t, sto, co] * m.storage.loc[sto, co]['eff-in'] - \ m.e_sto_out[t, sto, co] / m.storage.loc[sto, co]['eff-out'] def def_storage_power_rule(m, sto, co): return m.cap_sto_p[sto, co] == \ m.cap_sto_p_new[sto, co] + \ m.storage.loc[sto, co]['inst-cap-p'] def def_storage_capacity_rule(m, sto, co): return m.cap_sto_c[sto, co] == \ m.cap_sto_c_new[sto, co] + \ m.storage.loc[sto, co]['inst-cap-p'] def res_storage_input_by_power_rule(m, t, sto, co): return m.e_sto_in[t, sto, co] <= m.cap_sto_p[sto, co] def res_storage_output_by_power_rule(m, t, sto, co): return m.e_sto_out[t, sto, co] <= m.cap_sto_p[sto, co] def res_storage_state_by_capacity_rule(m, t, sto, co): return m.e_sto_con[t, sto, co] <= m.cap_sto_c[sto, co] def res_storage_power_rule(m, sto, co): return (m.storage.loc[sto, co]['cap-lo-p'], m.cap_sto_p[sto, co], m.storage.loc[sto, co]['cap-up-p']) def res_storage_capacity_rule(m, sto, co): return (m.storage.loc[sto, co]['cap-lo-c'], m.cap_sto_c[sto, co], m.storage.loc[sto, co]['cap-up-c']) def res_initial_and_final_storage_state_rule(m, t, sto, co): if t == m.t[1]: # first timestep (Pyomo uses 1-based indexing) return m.e_sto_con[t, sto, co] == \ m.cap_sto_c[sto, co] * \ m.storage.loc[sto, co]['init'] elif t == m.t[-1]: # last timestep return m.e_sto_con[t, sto, co] >= \ m.cap_sto_c[sto, co] * \ m.storage.loc[sto, co]['init'] else: return pyomo.Constraint.Skip # emissions def res_co2_emission_rule(m): return pyomo.summation(m.co2_pro_out) <= \ m.commodity.loc['CO2','Env']['max'] # costs def def_costs_rule(m, cost_type): """ Calculate total costs by cost type. Sums up process activity and capacity expansions and sums them in the cost types that are specified in the set m.cost_type. To change or add cost types, add/change entries there and modify the if/elif cases in this function accordingly. Cost types are - Investment costs for process power, storage power and storage capacity, annualized. - Fixed costs for process & storage power, storage capacity. - Variable costs for process and storage activity. - Fuel costs for purchased stock commodities. """ if cost_type == 'Inv': return m.costs['Inv'] == \ sum(m.cap_pro_new[p] * m.process.loc[p]['inv-cost'] * m.process.loc[p]['annuity_factor'] for p in m.pro_tuples) + \ sum(m.cap_sto_p_new[s] * m.storage.loc[s]['inv-cost-p'] * m.storage.loc[s]['annuity_factor'] + m.cap_sto_c_new[s] * m.storage.loc[s]['inv-cost-c'] * m.storage.loc[s]['annuity_factor'] for s in m.sto_tuples) elif cost_type == 'Fix': return m.costs['Fix'] == \ sum(m.cap_pro[p] * m.process.loc[p]['fix-cost'] for p in m.pro_tuples) + \ sum(m.cap_sto_p[s] * m.storage.loc[s]['fix-cost-p'] + m.cap_sto_c[s] * m.storage.loc[s]['fix-cost-c'] for s in m.sto_tuples) elif cost_type == 'Var': return m.costs['Var'] == \ sum(m.e_pro_out[(tm,) + p] * m.process.loc[p]['var-cost'] * m.weight for tm in m.tm for p in m.pro_tuples) + \ sum(m.e_sto_con[(tm,) + s] * m.storage.loc[s]['var-cost-c'] * m.weight + (m.e_sto_in[(tm,) + s] + m.e_sto_out[(tm,) + s]) * m.storage.loc[s]['var-cost-p'] * m.weight for tm in m.tm for s in m.sto_tuples) elif cost_type == 'Fuel': return m.costs['Fuel'] == \ sum(m.e_co_stock[(tm,c[0])] * m.commodity.loc[c]['price'] * m.weight for tm in m.tm for c in m.co_tuples if c[0] in m.co_stock) else: raise NotImplementedError("Unknown cost type!") def obj_rule(m): """ Return sum of total costs over all cost types. Simply calculates the sum of m.costs over all m.cost_types. """ return pyomo.summation(m.costs) # Equation declaration # ==================== # declarations connect rule functions to the model, specifying # the model sets for which the constraints are enforced. # commodity m.res_demand = pyomo.Constraint(m.tm, m.co_tuples, doc='storage + process balance >= demand') m.def_e_co_stock = pyomo.Constraint( m.tm, m.co_tuples, doc='commodity source term = hourly commodity consumption') m.res_stock_hour = pyomo.Constraint( m.tm, m.co_tuples, doc='hourly commodity source term <= commodity.maxperhour') m.res_stock_total = pyomo.Constraint( m.co_tuples, doc='total commodity source term <= commodity.max') # process m.def_process_capacity = pyomo.Constraint( m.tm, m.pro_tuples, doc='total process capacity = inst-cap + new capacity') m.def_process_output = pyomo.Constraint( m.tm, m.pro_tuples, doc='process output = process input * efficiency') m.def_intermittent_supply = pyomo.Constraint( m.tm, m.pro_tuples, doc='process output = process capacity * supim timeseries') m.def_co2_emissions = pyomo.Constraint( m.tm, m.pro_tuples, doc='process co2 output = process input * process.co2 * weight') m.res_process_output_by_capacity = pyomo.Constraint( m.tm, m.pro_tuples, doc='process output <= process capacity') m.res_process_capacity = pyomo.Constraint( m.pro_tuples, doc='process.cap-lo <= process capacity <= process.cap-up') # storage m.def_storage_state = pyomo.Constraint( m.tm, m.sto_tuples, doc='storage[t] = storage[t-1] + input - output') m.def_storage_power = pyomo.Constraint( m.sto_tuples, doc='storage power = inst-cap + new power') m.def_storage_capacity = pyomo.Constraint( m.sto_tuples, doc='storage capacity = inst-cap + new capacity') m.res_storage_input_by_power = pyomo.Constraint( m.tm, m.sto_tuples, doc='storage input <= storage power') m.res_storage_output_by_power = pyomo.Constraint( m.tm, m.sto_tuples, doc='storage output <= storage power') m.res_storage_state_by_capacity = pyomo.Constraint( m.t, m.sto_tuples, doc='storage content <= storage capacity') m.res_storage_power = pyomo.Constraint( m.sto_tuples, doc='storage.cap-lo <= storage power <= storage.cap-up') m.res_storage_capacity = pyomo.Constraint( m.sto_tuples, doc='storage.cap-lo <= storage capacity <= storage.cap-up') m.res_initial_and_final_storage_state = pyomo.Constraint( m.t, m.sto_tuples, doc='storage content initial == and final >= storage.init * capacity') # emissions m.res_co2_emission = pyomo.Constraint( doc='total co2 emissions <= commodity.co2.max') # costs m.def_costs = pyomo.Constraint(m.cost_type, doc='main cost function by cost type') m.obj = pyomo.Objective(sense=pyomo.minimize, doc='cost = sum of all cost types') return m