Source code for optimizeTSAmultiStage

from fine import utils
import fine as fn
import numpy as np


[docs] def optimizeTSAmultiStage( esM, declaresOptimizationProblem=True, relaxIsBuiltBinary=False, numberOfTypicalPeriods=30, numberOfTimeStepsPerPeriod=24, clusterMethod="hierarchical", logFileName="", threads=3, solver="gurobi", timeLimit=None, optimizationSpecs="", warmstart=False, ): """ Call the optimize function for a temporally aggregated MILP (so the model has to include hasIsBuiltBinaryVariables in all or some components). Fix the binary variables and run it again without temporal aggregation. Furthermore, a LP with relaxed binary variables can be solved to obtain both, an upper and lower bound for the fully resolved MILP. **Required arguments:** :param esM: energy system model to which the component should be added. Used for unit checks. :type esM: EnergySystemModel instance from the FINE package **Default arguments:** :param declaresOptimizationProblem: states if the optimization problem should be declared (True) or not (False). (a) If true, the declareOptimizationProblem function is called and a pyomo ConcreteModel instance is built. (b) If false a previously declared pyomo ConcreteModel instance is used. |br| * the default value is True :type declaresOptimizationProblem: boolean :param relaxIsBuiltBinary: states if the optimization problem should be solved as a relaxed LP to get the lower bound of the problem. |br| * the default value is False :type declaresOptimizationProblem: boolean :param numberOfTypicalPeriods: states the number of typical periods into which the time series data should be clustered. The number of time steps per period must be an integer multiple of the total number of considered time steps in the energy system. .. note:: Please refer to the tsam package documentation of the parameter noTypicalPeriods for more information. |br| * the default value is 30 :type numberOfTypicalPeriods: strictly positive integer :param numberOfTimeStepsPerPeriod: states the number of time steps per period |br| * the default value is 24 :type numberOfTimeStepsPerPeriod: strictly positive integer :param clusterMethod: states the method which is used in the tsam package for clustering the time series data. Options are for example 'averaging','k_means','exact k_medoid' or 'hierarchical'. .. note:: Please refer to the tsam package documentation of the parameter clusterMethod for more information. |br| * the default value is 'hierarchical' :type clusterMethod: string :param logFileName: logFileName is used for naming the log file of the optimization solver output if gurobi is used as the optimization solver. If the logFileName is given as an absolute path (e.g. logFileName = os.path.join(os.getcwd(), 'Results', 'logFileName.txt')) the log file will be stored in the specified directory. Otherwise, it will be stored by default in the directory where the executing python script is called. |br| * the default value is 'job' :type logFileName: string :param threads: number of computational threads used for solving the optimization (solver dependent input) if gurobi is used as the solver. A value of 0 results in using all available threads. If a value larger than the available number of threads are chosen, the value will reset to the maximum number of threads. |br| * the default value is 3 :type threads: positive integer :param solver: specifies which solver should solve the optimization problem (which of course has to be installed on the machine on which the model is run). |br| * the default value is 'gurobi' :type solver: string :param timeLimit: if not specified as None, indicates the maximum solve time of the optimization problem in seconds (solver dependent input). The use of this parameter is suggested when running models in runtime restricted environments (such as clusters with job submission systems). If the runtime limitation is triggered before an optimal solution is available, the best solution obtained up until then (if available) is processed. |br| * the default value is None :type timeLimit: strictly positive integer or None :param optimizationSpecs: specifies parameters for the optimization solver (see the respective solver documentation for more information). Example: 'LogToConsole=1 OptimalityTol=1e-6' |br| * the default value is an empty string ('') :type timeLimit: string :param warmstart: specifies if a warm start of the optimization should be considered (not always supported by the solvers). |br| * the default value is False :type warmstart: boolean """ lowerBound = None if relaxIsBuiltBinary: esM.optimize( declaresOptimizationProblem=True, timeSeriesAggregation=False, relaxIsBuiltBinary=True, logFileName="relaxedProblem", threads=threads, solver=solver, timeLimit=timeLimit, optimizationSpecs=optimizationSpecs, warmstart=warmstart, ) lowerBound = esM.objectiveValue esM.aggregateTemporally( numberOfTypicalPeriods=numberOfTypicalPeriods, numberOfTimeStepsPerPeriod=numberOfTimeStepsPerPeriod, segmentation=False, clusterMethod=clusterMethod, solver=solver, sortValues=True, rescaleClusterPeriods=True, representationMethod=None, ) esM.optimize( declaresOptimizationProblem=True, timeSeriesAggregation=True, relaxIsBuiltBinary=False, logFileName="firstStage", threads=threads, solver=solver, timeLimit=timeLimit, optimizationSpecs=optimizationSpecs, warmstart=warmstart, ) # Set the binary variables to the values resulting from the first optimization step fn.fixBinaryVariables(esM) esM.optimize( declaresOptimizationProblem=True, timeSeriesAggregation=False, relaxIsBuiltBinary=False, logFileName="secondStage", threads=threads, solver=solver, timeLimit=timeLimit, optimizationSpecs=optimizationSpecs, warmstart=False, ) upperBound = esM.objectiveValue if lowerBound is not None: delta = upperBound - lowerBound gap = delta / upperBound esM.lowerBound, esM.upperBound = lowerBound, upperBound esM.gap = gap print( "The real optimal value lies between " + str(round(lowerBound, 2)) + " and " + str(round(upperBound, 2)) + " with a gap of " + str(round(gap * 100, 2)) + "%." )
[docs] 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( name="isBuiltVariablesOptimum", ip=0 )["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