Source code for aristopy.sourceSink

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ==============================================================================
#    S O U R C E   A N D   S I N K
# ==============================================================================
"""
* File name: sourceSink.py
* Last edited: 2020-06-14
* Created by: Stefan Bruche (TU Berlin)

Sources and sinks are responsible for the transportation of commodities
across the system boundary into and out of the EnergySystem instance.
The Sink class inherits from the Source class. Both have the same input
parameters with only one exception: The Sink has an "inlet" instead of
an "outlet" attribute.
"""
import pyomo.environ as pyo
from aristopy.component import Component
from aristopy import utils


[docs]class Source(Component): def __init__(self, ensys, name, outlet, basic_variable='outlet_variable', has_existence_binary_var=False, has_operation_binary_var=False, time_series_data=None, scalar_params=None, additional_vars=None, user_expressions=None, capacity=None, capacity_min=None, capacity_max=None, capex_per_capacity=0, capex_if_exist=0, opex_per_capacity=0, opex_if_exist=0, opex_operation=0, commodity_rate_min=None, commodity_rate_max=None, commodity_rate_fix=None, commodity_cost=0, commodity_revenues=0, **kwargs # only a backdoor for 'inlet' keyword of Sink class ): """ Initialize an instance of the Source class. .. note:: See the documentation of the :class:`Component <aristopy.component.Component>` class for a description of all keyword arguments and inherited methods. :param commodity_rate_min: Scalar value or time series that provides a minimal value (lower bound) for the basic variable (typically, Sink inlet commodity, or Source outlet commodity) for every time step. |br| *Default: None* :type commodity_rate_min: int, or float, or aristopy Series, or None :param commodity_rate_max: Scalar value or time series that provides a maximal value (upper bound) for the basic variable (typically, Sink inlet commodity, or Source outlet commodity) for every time step. |br| *Default: None* :type commodity_rate_max: int, or float, or aristopy Series, or None :param commodity_rate_fix: Scalar value or time series that provides a fixed value for the basic variable (typically, Sink inlet commodity, or Source outlet commodity) for every time step. |br| *Default: None* :type commodity_rate_fix: int, or float, or aristopy Series, or None :param commodity_cost: Incurred costs for the use / expenditure of the basic variable. Keyword argument takes scalar values or time series data (Note: scalar values provide the same functionality like keyword argument 'opex_operation'). |br| *Default: 0* :type commodity_cost: int, or float, or aristopy Series :param commodity_revenues: Accruing revenues associated with the allocation of the basic variable. Keyword argument takes scalar values or time series data. |br| *Default: 0* :type commodity_revenues: int, or float, or aristopy Series """ inlet = None # init (used for Source) => is overwritten for Sink class # Source: needs outlet Flow (not None!) and kwargs should not be used if self.__class__ == Source: if outlet is None: raise utils.io_error_message('Source', name, 'outlet') if len(kwargs.keys()) != 0: raise ValueError('Found unexpected keyword arguments for Source' ' component: %s' % list(kwargs.keys())) # Sinks: needs inlet Flow (not None!). __init__ doesn't allow kwargs if self.__class__ == Sink: inlet = kwargs['inlet'] if inlet is None: raise utils.io_error_message('Sink', name, 'inlet') Component.__init__(self, ensys=ensys, name=name, inlet=inlet, outlet=outlet, basic_variable=basic_variable, has_existence_binary_var=has_existence_binary_var, has_operation_binary_var=has_operation_binary_var, time_series_data=time_series_data, scalar_params=scalar_params, additional_vars=additional_vars, user_expressions=user_expressions, capacity=capacity, capacity_min=capacity_min, capacity_max=capacity_max, capex_per_capacity=capex_per_capacity, capex_if_exist=capex_if_exist, opex_per_capacity=opex_per_capacity, opex_if_exist=opex_if_exist, opex_operation=opex_operation ) # Check and set additional input arguments self.commodity_cost, self.commodity_cost_time_series = \ utils.check_and_set_cost_and_revenues(self, commodity_cost) self.commodity_revenues, self.commodity_revenues_time_series = \ utils.check_and_set_cost_and_revenues(self, commodity_revenues) # Check and set data for commodity rates (scalar or time series or None) self.op_rate_min, self.op_rate_max, self.op_rate_fix = \ utils.check_and_set_commodity_rates( self, commodity_rate_min, commodity_rate_max, commodity_rate_fix) # Last step: Add the component to the EnergySystem instance self.add_to_energy_system(ensys, name) def __repr__(self): return '<Source: "%s">' % self.name # ========================================================================== # C O N V E N T I O N A L C O N S T R A I N T D E C L A R A T I O N # ==========================================================================
[docs] def declare_component_constraints(self, ensys, model): """ Method to declare all component constraints. The following constraint methods are inherited from the Component class and are not documented in this sub-class: * :meth:`con_couple_bi_ex_and_cap <aristopy.component.Component.con_couple_bi_ex_and_cap>` * :meth:`con_cap_min <aristopy.component.Component.con_cap_min>` * :meth:`con_bi_var_ex_and_op_relation <aristopy.component.Component.con_bi_var_ex_and_op_relation>` * :meth:`con_couple_op_binary_and_basic_var <aristopy.component.Component.con_couple_op_binary_and_basic_var>` *Method is not intended for public access!* :param ensys: Instance of the EnergySystem class :param model: Pyomo ConcreteModel of the EnergySystem instance """ # Time-independent constraints : # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ self.con_couple_bi_ex_and_cap() self.con_cap_min() # Time-dependent constraints: # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ self.con_bi_var_ex_and_op_relation(model) self.con_operation_limit(model) self.con_couple_op_binary_and_basic_var(model) self.con_commodity_rate_min(model) self.con_commodity_rate_max(model) self.con_commodity_rate_fix(model)
# ************************************************************************** # Time-dependent constraints # **************************************************************************
[docs] def con_operation_limit(self, model): """ The basic variable of a component is limited by its nominal capacity. This usually means, the operation (main commodity) of a sink / source (MWh) is limited by its nominal power (MW) multiplied with the number of hours per time step. E.g.: |br| ``Q[p, t] <= CAP * dt`` *Method is not intended for public access!* """ # Only required if component has a capacity variable if self.has_capacity_var: cap = self.variables[utils.CAP]['pyomo'] basic_var = self.variables[self.basic_variable]['pyomo'] has_time_set = self.variables[self.basic_variable]['has_time_set'] dt = self.ensys.hours_per_time_step def con_operation_limit(m, p, t): if has_time_set: return basic_var[p, t] <= cap * dt else: # Exceptional case: Selection of a scalar basic variable return basic_var == cap setattr(self.block, 'con_operation_limit', pyo.Constraint( model.time_set, rule=con_operation_limit))
[docs] def con_commodity_rate_min(self, model): """ The basic variable of a component needs to have a minimal value of "commodity_rate_min" in every time step. (Without correction with "hours_per_time_step" because it should already be included in the time series). E.g.: |br| ``Q[p, t] >= op_rate_min[p, t]`` *Method is not intended for public access!* """ # Only required if component has a time series for "commodity_rate_min". if self.op_rate_min is not None: op_min = self.parameters[self.op_rate_min]['values'] basic_var = self.variables[self.basic_variable]['pyomo'] has_time_set = self.parameters[self.op_rate_min]['has_time_set'] def con_commodity_rate_min(m, p, t): if has_time_set: return basic_var[p, t] >= op_min[p, t] else: # if 'commodity_rate_min' is provided as scalar value return basic_var[p, t] >= op_min setattr(self.block, 'con_commodity_rate_min', pyo.Constraint( model.time_set, rule=con_commodity_rate_min))
[docs] def con_commodity_rate_max(self, model): """ The basic variable of a component can have a maximal value of "commodity_rate_max" in every time step. (Without correction with "hours_per_time_step" because it should already be included in the time series). E.g.: |br| ``Q[p, t] <= op_rate_max[p, t]`` *Method is not intended for public access!* """ # Only required if component has a time series for "commodity_rate_max". if self.op_rate_max is not None: op_max = self.parameters[self.op_rate_max]['values'] basic_var = self.variables[self.basic_variable]['pyomo'] has_time_set = self.parameters[self.op_rate_max]['has_time_set'] def con_commodity_rate_max(m, p, t): if has_time_set: return basic_var[p, t] <= op_max[p, t] else: # if 'commodity_rate_max' is provided as scalar value return basic_var[p, t] <= op_max setattr(self.block, 'con_commodity_rate_max', pyo.Constraint( model.time_set, rule=con_commodity_rate_max))
[docs] def con_commodity_rate_fix(self, model): """ The basic variable of a component needs to have a value of "commodity_rate_fix" in every time step. (Without correction with "hours_per_time_step" because it should already be included in the time series). E.g.: |br| ``Q[p, t] == op_rate_fix[p, t]`` *Method is not intended for public access!* """ # Only required if component has a time series for "commodity_rate_fix" if self.op_rate_fix is not None: op_fix = self.parameters[self.op_rate_fix]['values'] basic_var = self.variables[self.basic_variable]['pyomo'] has_time_set = self.parameters[self.op_rate_fix]['has_time_set'] def con_commodity_rate_fix(m, p, t): if has_time_set: return basic_var[p, t] == op_fix[p, t] else: # if 'commodity_rate_fix' is provided as scalar value return basic_var[p, t] == op_fix setattr(self.block, 'con_commodity_rate_fix', pyo.Constraint( model.time_set, rule=con_commodity_rate_fix))
# ========================================================================== # O B J E C T I V E F U N C T I O N C O N T R I B U T I O N # ==========================================================================
[docs] def get_objective_function_contribution(self, ensys, model): """ Calculate the objective function contributions of the component and add the values to the component dictionary "comp_obj_dict". *Method is not intended for public access!* :param ensys: Instance of the EnergySystem class :param model: Pyomo ConcreteModel of the EnergySystem instance """ # Call method in "Component" class and calculate CAPEX and OPEX super().get_objective_function_contribution(ensys, model) # Get the basic variable: basic_var = self.variables[self.basic_variable]['pyomo'] # COMMODITY COST AND REVENUES : # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Time-independent cost of a commodity (scalar cost value) if self.commodity_cost > 0: self.comp_obj_dict['commodity_cost'] = \ -1 * ensys.pvf * self.commodity_cost * sum( basic_var[p, t] * ensys.period_occurrences[p] for p, t in model.time_set) / ensys.number_of_years # Time-dependent cost of a commodity (time series cost values) if self.commodity_cost_time_series is not None: cost_ts = self.parameters[self.commodity_cost_time_series]['values'] self.comp_obj_dict['commodity_cost'] = -1 * ensys.pvf * sum( cost_ts[p, t] * basic_var[p, t] * ensys.period_occurrences[p] for p, t in model.time_set) / ensys.number_of_years # Time-independent revenues of a commodity (scalar revenue value) if self.commodity_revenues > 0: self.comp_obj_dict['commodity_revenues'] = \ ensys.pvf * self.commodity_revenues * sum( basic_var[p, t] * ensys.period_occurrences[p] for p, t in model.time_set) / ensys.number_of_years # Time-dependent revenues of a commodity (time series revenue values) if self.commodity_revenues_time_series is not None: rev_ts = self.parameters[ self.commodity_revenues_time_series]['values'] self.comp_obj_dict['commodity_revenues'] = ensys.pvf * sum( rev_ts[p, t] * basic_var[p, t] * ensys.period_occurrences[p] for p, t in model.time_set) / ensys.number_of_years return sum(self.comp_obj_dict.values())
# ========================================================================== # S E R I A L I Z E # ==========================================================================
[docs] def serialize(self): """ This method collects all relevant input data and optimization results from the Component instance, and returns them in an ordered dictionary. :return: OrderedDict """ comp_dict = super().serialize() comp_dict['commodity_rate_min'] = self.op_rate_min comp_dict['commodity_rate_max'] = self.op_rate_max comp_dict['commodity_rate_fix'] = self.op_rate_fix comp_dict['commodity_cost_time_series'] = \ self.commodity_cost_time_series comp_dict['commodity_revenues_time_series'] = \ self.commodity_revenues_time_series return comp_dict
[docs]class Sink(Source): def __init__(self, ensys, name, inlet, basic_variable='inlet_variable', has_existence_binary_var=False, has_operation_binary_var=False, time_series_data=None, scalar_params=None, additional_vars=None, user_expressions=None, capacity=None, capacity_min=None, capacity_max=None, capex_per_capacity=0, capex_if_exist=0, opex_per_capacity=0, opex_if_exist=0, opex_operation=0, commodity_rate_min=None, commodity_rate_max=None, commodity_rate_fix=None, commodity_cost=0, commodity_revenues=0 ): """ Initialize an instance of the Sink class. The Sink class inherits from the Source class. Both have the same input parameters with only one exception: The Sink has an "inlet" instead of an "outlet" attribute. .. note:: See the documentation of the :class:`Component <aristopy.component.Component>` class and the :class:`Source <aristopy.sourceSink.Source>` class for a description of all keyword arguments and inherited methods. """ Source.__init__(self, ensys=ensys, name=name, outlet=None, inlet=inlet, # introduced by **kwargs! basic_variable=basic_variable, has_existence_binary_var=has_existence_binary_var, has_operation_binary_var=has_operation_binary_var, time_series_data=time_series_data, scalar_params=scalar_params, additional_vars=additional_vars, user_expressions=user_expressions, capacity=capacity, capacity_min=capacity_min, capacity_max=capacity_max, capex_per_capacity=capex_per_capacity, capex_if_exist=capex_if_exist, opex_per_capacity=opex_per_capacity, opex_if_exist=opex_if_exist, opex_operation=opex_operation, commodity_rate_min=commodity_rate_min, commodity_rate_max=commodity_rate_max, commodity_rate_fix=commodity_rate_fix, commodity_cost=commodity_cost, commodity_revenues=commodity_revenues) def __repr__(self): return '<Sink: "%s">' % self.name