def __init__(self, esM, name, commodity, reactances, losses=0, distances=None, hasCapacityVariable=True, capacityVariableDomain='continuous', capacityPerPlantUnit=1, hasIsBuiltBinaryVariable=False, bigM=None, operationRateMax=None, operationRateFix=None, tsaWeight=1, locationalEligibility=None, capacityMin=None, capacityMax=None, sharedPotentialID=None, capacityFix=None, isBuiltFix=None, investPerCapacity=0, investIfBuilt=0, opexPerOperation=0, opexPerCapacity=0, opexIfBuilt=0, QPcostScale=0, interestRate=0.08, economicLifetime=10, technicalLifetime=None): """ Constructor for creating an LinearOptimalPowerFlow class instance. The LinearOptimalPowerFlow component specific input arguments are described below. The Transmission component specific input arguments are described in the Transmission class and the general component input arguments are described in the Component class. **Required arguments:** :param reactances: reactances for DC power flow modeling (of AC lines). :type reactances: Pandas DataFrame. The row and column indices of the DataFrame have to equal the in the energy system model specified locations. """ Transmission.__init__( self, esM, name, commodity, losses, distances, hasCapacityVariable, capacityVariableDomain, capacityPerPlantUnit, hasIsBuiltBinaryVariable, bigM, operationRateMax, operationRateFix, tsaWeight, locationalEligibility, capacityMin, capacityMax, sharedPotentialID, capacityFix, isBuiltFix, investPerCapacity, investIfBuilt, opexPerOperation, opexPerCapacity, opexIfBuilt, QPcostScale, interestRate, economicLifetime, technicalLifetime) self.modelingClass = LOPFModel self.reactances2dim = reactances try: self.reactances = pd.Series(self._mapC).apply( lambda loc: self.reactances2dim[loc[0]][loc[1]]) except: self.reactances = utils.preprocess2dimData(self.reactances2dim)
def getStock(esM, mileStoneYear, nbOfRepresentedYears): ''' Function for determining the stock of all considered technologies for the next optimization period. If the technical lifetime is expired, the fixed capacities of the concerned components are set to 0. :param mileStoneYear: Last year of the optimization period :type mileStoneYear: int :param nbOfRepresentativeYears: Number of years within one optimization period. :type nbOfRepresentativeYears: int :return esM: EnergySystemModel instance including the installed capacities of the previous optimization runs. :rtype: EnergySystemModel instance Last edited: February 11, 2020 |br| @author: Theresa Gross, Felix Kullmann ''' for mdl in esM.componentModelingDict.keys(): compValues = esM.componentModelingDict[mdl].getOptimalValues( 'capacityVariablesOptimum')['values'] if compValues is not None: for comp in compValues.index.get_level_values(0).unique(): if 'stock' not in esM.componentModelingDict[ mdl].componentsDict[comp].name: stockName = comp + '_stock' + '_' + str(mileStoneYear) stockComp = copy.deepcopy( esM.componentModelingDict[mdl].componentsDict[comp]) stockComp.name = stockName stockComp.lifetime = esM.componentModelingDict[ mdl].componentsDict[ comp].technicalLifetime - nbOfRepresentedYears # If lifetime is shorter than number of represented years, skip component if any(getattr(stockComp, 'lifetime') <= 0): continue # If capacities are installed, set the values as capacityFix. if getattr(stockComp, 'capacityFix') is None: if isinstance(compValues.loc[comp], pd.DataFrame): stockComp.capacityFix = utils.preprocess2dimData( compValues.loc[comp].fillna(value=-1), discard=False) else: # NOTE: Values of capacityMin and capacityMax are not overwritten. # CapacityFix values set the capacity fix and fulfills the boundary constraints (capacityMin <= capacityFix <= capacityMax) stockComp.capacityFix = compValues.loc[comp] esM.add(stockComp) elif 'stock' in esM.componentModelingDict[mdl].componentsDict[ comp].name: esM.componentModelingDict[mdl].componentsDict[ comp].lifetime -= nbOfRepresentedYears # If lifetime is exceeded, remove component from the energySystemModel instance if any( getattr( esM.componentModelingDict[mdl]. componentsDict[comp], 'lifetime') <= 0): esM.removeComponent(comp) return esM
def fixBinaryVariables(esM): """ Search for the optimized binary variables and set them as fixed. :param esM: energy system model to which the component should be added. Used for unit checks. :type esM: EnergySystemModel instance from the FINE package """ for mdl in esM.componentModelingDict.keys(): compValues = esM.componentModelingDict[mdl].getOptimalValues( "isBuiltVariablesOptimum")["values"] if compValues is not None: for comp in compValues.index.get_level_values(0).unique(): values = utils.preprocess2dimData( compValues.loc[comp].fillna(value=-1).round( decimals=0).astype(np.int64), discard=False, ) esM.componentModelingDict[mdl].componentsDict[ comp].isBuiltFix = values
def __init__(self, esM, name, commodity, losses=0, distances=None, hasCapacityVariable=True, capacityVariableDomain='continuous', capacityPerPlantUnit=1, hasIsBuiltBinaryVariable=False, bigM=None, operationRateMax=None, operationRateFix=None, tsaWeight=1, locationalEligibility=None, capacityMin=None, capacityMax=None, sharedPotentialID=None, capacityFix=None, isBuiltFix=None, investPerCapacity=0, investIfBuilt=0, opexPerOperation=0, opexPerCapacity=0, opexIfBuilt=0, interestRate=0.08, economicLifetime=10): """ Constructor for creating an Transmission class instance. The Transmission component specific input arguments are described below. The general component input arguments are described in the Component class. **Required arguments:** :param commodity: to the component related commodity. :type commodity: string **Default arguments:** :param losses: relative losses per lengthUnit (lengthUnit as specified in the energy system model) in percentage of the commodity flow. This loss factor can capture simple linear losses trans_in_ij=(1-losses*distance)*trans_out_ij (with trans being the commodity flow at a certain point in time and i and j being locations in the energy system). The losses can either be given as a float or a Pandas DataFrame with location specific values. |br| * the default value is 0 :type losses: positive float (0 <= float <= 1) or Pandas DataFrame with positive values (0 <= float <= 1). The row and column indices of the DataFrame have to equal the in the energy system model specified locations. :param distances: distances between locations given in the lengthUnit (lengthUnit as specified in the energy system model). |br| * the default value is None :type distances: positive float (>= 0) or Pandas DataFrame with positive values (>= 0). The row and column indices of the DataFrame have to equal the in the energy system model specified locations. :param operationRateMax: if specified, indicates a maximum operation rate for all possible connections (both directions) of the transmission component at each time step by a positive float. If hasCapacityVariable is set to True, the values are given relative to the installed capacities (i.e. a value of 1 indicates a utilization of 100% of the capacity). If hasCapacityVariable is set to False, the values are given as absolute values in form of the commodityUnit, referring to the transmitted commodity (before considering losses) during one time step. |br| * the default value is None :type operationRateMax: None or Pandas DataFrame with positive (>= 0) entries. The row indices have to match the in the energy system model specified time steps. The column indices are combinations of locations (as defined in the energy system model), separated by a underscore (e.g. "location1_location2"). The first location indicates where the commodity is coming from. The second one location indicates where the commodity is going too. If a flow is specified from location i to location j, it also has to be specified from j to i. :param operationRateFix: if specified, indicates a fixed operation rate for all possible connections (both directions) of the transmission component at each time step by a positive float. If hasCapacityVariable is set to True, the values are given relative to the installed capacities (i.e. a value of 1 indicates a utilization of 100% of the capacity). If hasCapacityVariable is set to False, the values are given as absolute values in form of the commodityUnit, referring to the transmitted commodity (before considering losses) during one time step. |br| * the default value is None :type operationRateFix: None or Pandas DataFrame with positive (>= 0) entries. The row indices have to match the in the energy system model specified time steps. The column indices are combinations of locations (as defined in the energy system model), separated by a underscore (e.g. "location1_location2"). The first location indicates where the commodity is coming from. The second one location indicates where the commodity is going too. If a flow is specified from location i to location j, it also has to be specified from j to i. :param tsaWeight: weight with which the time series of the component should be considered when applying time series aggregation. |br| * the default value is 1 :type tsaWeight: positive (>= 0) float :param opexPerOperation: describes the cost for one unit of the operation. The cost which is directly proportional to the operation of the component is obtained by multiplying the opexPerOperation parameter with the annual sum of the operational time series of the components. The opexPerOperation can either be given as a float or a Pandas DataFrame with location specific values. The cost unit in which the parameter is given has to match the one specified in the energy system model (e.g. Euro, Dollar, 1e6 Euro). |br| * the default value is 0 :type opexPerOperation: positive (>=0) float or Pandas DataFrame with positive (>=0) values. The row and column indices of the DataFrame have to equal the in the energy system model specified locations. """ # TODO add unit checks # Preprocess two-dimensional data self.locationalEligibility = utils.preprocess2dimData(locationalEligibility) self.capacityMax = utils.preprocess2dimData(capacityMax) self.capacityFix = utils.preprocess2dimData(capacityFix) self.isBuiltFix = utils.preprocess2dimData(isBuiltFix) # Set locational eligibility operationTimeSeries = operationRateFix if operationRateFix is not None else operationRateMax self.locationalEligibility = \ utils.setLocationalEligibility(esM, self.locationalEligibility, self.capacityMax, self.capacityFix, self.isBuiltFix, hasCapacityVariable, operationTimeSeries, '2dim') self._mapC, self._mapL, self._mapI = {}, {}, {} for loc1 in esM.locations: for loc2 in esM.locations: if loc1 + '_' + loc2 in self.locationalEligibility.index: if self.locationalEligibility[loc1 + '_' + loc2] == 0: self.locationalEligibility[loc1 + '_' + loc2].drop(inplace=True) self._mapC.update({loc1 + '_' + loc2: (loc1, loc2)}) self._mapL.setdefault(loc1, {}).update({loc2: loc1 + '_' + loc2}) self._mapI.update({loc1 + '_' + loc2: loc2 + '_' + loc1}) self.capacityMin = utils.preprocess2dimData(capacityMin, self._mapC) self.investPerCapacity = utils.preprocess2dimData(investPerCapacity, self._mapC) self.investIfBuilt = utils.preprocess2dimData(investIfBuilt, self._mapC) self.opexPerCapacity = utils.preprocess2dimData(opexPerCapacity, self._mapC) self.opexIfBuilt = utils.preprocess2dimData(opexIfBuilt, self._mapC) self.interestRate = utils.preprocess2dimData(interestRate, self._mapC) self.economicLifetime = utils.preprocess2dimData(economicLifetime, self._mapC) Component. __init__(self, esM, name, dimension='2dim', hasCapacityVariable=hasCapacityVariable, capacityVariableDomain=capacityVariableDomain, capacityPerPlantUnit=capacityPerPlantUnit, hasIsBuiltBinaryVariable=hasIsBuiltBinaryVariable, bigM=bigM, locationalEligibility=self.locationalEligibility, capacityMin=self.capacityMin, capacityMax=self.capacityMax, sharedPotentialID=sharedPotentialID, capacityFix=self.capacityFix, isBuiltFix=self.isBuiltFix, investPerCapacity=self.investPerCapacity, investIfBuilt=self.investIfBuilt, opexPerCapacity=self.opexPerCapacity, opexIfBuilt=self.opexIfBuilt, interestRate=self.interestRate, economicLifetime=self.economicLifetime) # Set general component data utils.checkCommodities(esM, {commodity}) self.commodity, self.commodityUnit = commodity, esM.commodityUnitsDict[commodity] self.distances = utils.preprocess2dimData(distances, self._mapC) self.losses = utils.preprocess2dimData(losses, self._mapC) self.distances = utils.checkAndSetDistances(self.distances, self.locationalEligibility, esM) self.losses = utils.checkAndSetTransmissionLosses(self.losses, self.distances, self.locationalEligibility) self.modelingClass = TransmissionModel # Set additional economic data self.opexPerOperation = utils.checkAndSetCostParameter(esM, name, opexPerOperation, '2dim', self.locationalEligibility) # Set location-specific operation parameters if operationRateMax is not None and operationRateFix is not None: operationRateMax = None if esM.verbose < 2: warnings.warn('If operationRateFix is specified, the operationRateMax parameter is not required.\n' + 'The operationRateMax time series was set to None.') self.fullOperationRateMax = utils.checkAndSetTimeSeries(esM, operationRateMax, self.locationalEligibility) self.aggregatedOperationRateMax, self.operationRateMax = None, None self.fullOperationRateFix = utils.checkAndSetTimeSeries(esM, operationRateFix, self.locationalEligibility) self.aggregatedOperationRateFix, self.operationRateFix = None, None utils.isPositiveNumber(tsaWeight) self.tsaWeight = tsaWeight
def __init__( self, esM, name, commodity, losses=0, distances=None, hasCapacityVariable=True, capacityVariableDomain="continuous", capacityPerPlantUnit=1, hasIsBuiltBinaryVariable=False, bigM=None, operationRateMax=None, operationRateFix=None, tsaWeight=1, locationalEligibility=None, capacityMin=None, capacityMax=None, partLoadMin=None, sharedPotentialID=None, linkedQuantityID=None, capacityFix=None, isBuiltFix=None, investPerCapacity=0, investIfBuilt=0, opexPerOperation=0, opexPerCapacity=0, opexIfBuilt=0, QPcostScale=0, interestRate=0.08, economicLifetime=10, technicalLifetime=None, balanceLimitID=None, ): """ Constructor for creating an Transmission class instance. The Transmission component specific input arguments are described below. The general component input arguments are described in the Component class. **Required arguments:** :param commodity: to the component related commodity. :type commodity: string **Default arguments:** :param losses: relative losses per lengthUnit (lengthUnit as specified in the energy system model) in percentage of the commodity flow. This loss factor can capture simple linear losses .. math:: trans_{in, ij} = (1 - \\text{losses} \\cdot \\text{distances}) \\cdot trans_{out, ij} (with trans being the commodity flow at a certain point in time and i and j being locations in the energy system). The losses can either be given as a float or a Pandas DataFrame with location specific values. |br| * the default value is 0 :type losses: positive float (0 <= float <= 1) or Pandas DataFrame with positive values (0 <= float <= 1). The row and column indices of the DataFrame have to equal the in the energy system model specified locations. :param distances: distances between locations given in the lengthUnit (lengthUnit as specified in the energy system model). |br| * the default value is None :type distances: positive float (>= 0) or Pandas DataFrame with positive values (>= 0). The row and column indices of the DataFrame have to equal the in the energy system model specified locations. :param operationRateMax: if specified, indicates a maximum operation rate for all possible connections (both directions) of the transmission component at each time step by a positive float. If hasCapacityVariable is set to True, the values are given relative to the installed capacities (i.e. a value of 1 indicates a utilization of 100% of the capacity). If hasCapacityVariable is set to False, the values are given as absolute values in form of the commodityUnit, referring to the transmitted commodity (before considering losses) during one time step. |br| * the default value is None :type operationRateMax: None or Pandas DataFrame with positive (>= 0) entries. The row indices have to match the in the energy system model specified time steps. The column indices are combinations of locations (as defined in the energy system model), separated by a underscore (e.g. "location1_location2"). The first location indicates where the commodity is coming from. The second location indicates where the commodity is going too. If a flow is specified from location i to location j, it also has to be specified from j to i. :param operationRateFix: if specified, indicates a fixed operation rate for all possible connections (both directions) of the transmission component at each time step by a positive float. If hasCapacityVariable is set to True, the values are given relative to the installed capacities (i.e. a value of 1 indicates a utilization of 100% of the capacity). If hasCapacityVariable is set to False, the values are given as absolute values in form of the commodityUnit, referring to the transmitted commodity (before considering losses) during one time step. |br| * the default value is None :type operationRateFix: None or Pandas DataFrame with positive (>= 0) entries. The row indices have to match the in the energy system model specified time steps. The column indices are combinations of locations (as defined in the energy system model), separated by a underscore (e.g. "location1_location2"). The first location indicates where the commodity is coming from. The second one location indicates where the commodity is going too. If a flow is specified from location i to location j, it also has to be specified from j to i. :param tsaWeight: weight with which the time series of the component should be considered when applying time series aggregation. |br| * the default value is 1 :type tsaWeight: positive (>= 0) float :param opexPerOperation: describes the cost for one unit of the operation. The cost which is directly proportional to the operation of the component is obtained by multiplying the opexPerOperation parameter with the annual sum of the operational time series of the components. The opexPerOperation can either be given as a float or a Pandas DataFrame with location specific values. The cost unit in which the parameter is given has to match the one specified in the energy system model (e.g. Euro, Dollar, 1e6 Euro). The value has to match the unit costUnit/operationUnit (e.g. Euro/kWh, Dollar/kWh). |br| * the default value is 0 :type opexPerOperation: positive (>=0) float or Pandas DataFrame with positive (>=0) values. The row and column indices of the DataFrame have to equal the in the energy system model specified locations. :param balanceLimitID: ID for the respective balance limit (out of the balance limits introduced in the esM). Should be specified if the respective component of the TransmissionModel is supposed to be included in the balance analysis. If the commodity is transported out of the region, it is counted as a negative, if it is imported into the region it is considered positive. |br| * the default value is None :type balanceLimitID: string """ # TODO add unit checks # Preprocess two-dimensional data self.locationalEligibility = utils.preprocess2dimData( locationalEligibility) self.capacityMax = utils.preprocess2dimData( capacityMax, locationalEligibility=locationalEligibility) self.capacityFix = utils.preprocess2dimData( capacityFix, locationalEligibility=locationalEligibility) self.isBuiltFix = utils.preprocess2dimData( isBuiltFix, locationalEligibility=locationalEligibility) # Set locational eligibility operationTimeSeries = (operationRateFix if operationRateFix is not None else operationRateMax) self.locationalEligibility = utils.setLocationalEligibility( esM, self.locationalEligibility, self.capacityMax, self.capacityFix, self.isBuiltFix, hasCapacityVariable, operationTimeSeries, "2dim", ) self._mapC, self._mapL, self._mapI = {}, {}, {} for loc1 in esM.locations: for loc2 in esM.locations: if loc1 + "_" + loc2 in self.locationalEligibility.index: if self.locationalEligibility[loc1 + "_" + loc2] == 0: self.locationalEligibility.drop(labels=loc1 + "_" + loc2, inplace=True) self._mapC.update({loc1 + "_" + loc2: (loc1, loc2)}) self._mapL.setdefault(loc1, {}).update({loc2: loc1 + "_" + loc2}) self._mapI.update({loc1 + "_" + loc2: loc2 + "_" + loc1}) self.capacityMax = utils.preprocess2dimData( capacityMax, self._mapC, locationalEligibility=self.locationalEligibility) self.capacityFix = utils.preprocess2dimData( capacityFix, self._mapC, locationalEligibility=self.locationalEligibility) self.capacityMin = utils.preprocess2dimData( capacityMin, self._mapC, locationalEligibility=self.locationalEligibility) self.investPerCapacity = utils.preprocess2dimData( investPerCapacity, self._mapC) self.investIfBuilt = utils.preprocess2dimData(investIfBuilt, self._mapC) self.isBuiltFix = utils.preprocess2dimData( isBuiltFix, self._mapC, locationalEligibility=self.locationalEligibility) self.opexPerCapacity = utils.preprocess2dimData( opexPerCapacity, self._mapC) self.opexIfBuilt = utils.preprocess2dimData(opexIfBuilt, self._mapC) self.interestRate = utils.preprocess2dimData(interestRate, self._mapC) self.economicLifetime = utils.preprocess2dimData( economicLifetime, self._mapC) self.technicalLifetime = utils.preprocess2dimData( technicalLifetime, self._mapC) self.balanceLimitID = balanceLimitID Component.__init__( self, esM, name, dimension="2dim", hasCapacityVariable=hasCapacityVariable, capacityVariableDomain=capacityVariableDomain, capacityPerPlantUnit=capacityPerPlantUnit, hasIsBuiltBinaryVariable=hasIsBuiltBinaryVariable, bigM=bigM, locationalEligibility=self.locationalEligibility, capacityMin=self.capacityMin, capacityMax=self.capacityMax, partLoadMin=partLoadMin, sharedPotentialID=sharedPotentialID, linkedQuantityID=linkedQuantityID, capacityFix=self.capacityFix, isBuiltFix=self.isBuiltFix, investPerCapacity=self.investPerCapacity, investIfBuilt=self.investIfBuilt, opexPerCapacity=self.opexPerCapacity, opexIfBuilt=self.opexIfBuilt, interestRate=self.interestRate, QPcostScale=QPcostScale, economicLifetime=self.economicLifetime, technicalLifetime=self.technicalLifetime, ) # Set general component data utils.checkCommodities(esM, {commodity}) self.commodity, self.commodityUnit = ( commodity, esM.commodityUnitsDict[commodity], ) self.distances = utils.preprocess2dimData( distances, self._mapC, locationalEligibility=self.locationalEligibility) self.losses = utils.preprocess2dimData(losses, self._mapC) self.distances = utils.checkAndSetDistances(self.distances, self.locationalEligibility, esM) self.losses = utils.checkAndSetTransmissionLosses( self.losses, self.distances, self.locationalEligibility) self.modelingClass = TransmissionModel # Set distance related costs data self.processedInvestPerCapacity = self.investPerCapacity * self.distances * 0.5 self.processedInvestIfBuilt = self.investIfBuilt * self.distances * 0.5 self.processedOpexPerCapacity = self.opexPerCapacity * self.distances * 0.5 self.processedOpexIfBuilt = self.opexIfBuilt * self.distances * 0.5 # Set additional economic data self.opexPerOperation = utils.preprocess2dimData( opexPerOperation, self._mapC) self.opexPerOperation = utils.checkAndSetCostParameter( esM, name, self.opexPerOperation, "2dim", self.locationalEligibility) self.operationRateMax = operationRateMax self.operationRateFix = operationRateFix self.fullOperationRateMax = utils.checkAndSetTimeSeries( esM, name, operationRateMax, self.locationalEligibility, self.dimension) self.aggregatedOperationRateMax, self.processedOperationRateMax = None, None self.fullOperationRateFix = utils.checkAndSetTimeSeries( esM, name, operationRateFix, self.locationalEligibility, self.dimension) self.aggregatedOperationRateFix, self.processedOperationRateFix = None, None # Set location-specific operation parameters if (self.fullOperationRateMax is not None and self.fullOperationRateFix is not None): self.fullOperationRateMax = None if esM.verbose < 2: warnings.warn( "If operationRateFix is specified, the operationRateMax parameter is not required.\n" + "The operationRateMax time series was set to None.") if self.partLoadMin is not None: if self.fullOperationRateMax is not None: if (((self.fullOperationRateMax > 0) & (self.fullOperationRateMax < self.partLoadMin) ).any().any()): raise ValueError( '"fullOperationRateMax" needs to be higher than "partLoadMin" or 0 for component ' + name) if self.fullOperationRateFix is not None: if (((self.fullOperationRateFix > 0) & (self.fullOperationRateFix < self.partLoadMin) ).any().any()): raise ValueError( '"fullOperationRateFix" needs to be higher than "partLoadMin" or 0 for component ' + name) utils.isPositiveNumber(tsaWeight) self.tsaWeight = tsaWeight