emhass 0.9.0__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emhass/command_line.py +392 -286
 - emhass/forecast.py +21 -21
 - emhass/machine_learning_regressor.py +14 -53
 - emhass/optimization.py +261 -82
 - emhass/retrieve_hass.py +76 -101
 - emhass/utils.py +70 -128
 - emhass/web_server.py +32 -7
 - {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/METADATA +125 -19
 - {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/RECORD +13 -13
 - {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/LICENSE +0 -0
 - {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/WHEEL +0 -0
 - {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/entry_points.txt +0 -0
 - {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/top_level.txt +0 -0
 
    
        emhass/optimization.py
    CHANGED
    
    | 
         @@ -3,6 +3,9 @@ 
     | 
|
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            import logging
         
     | 
| 
       5 
5 
     | 
    
         
             
            import copy
         
     | 
| 
      
 6 
     | 
    
         
            +
            import pathlib
         
     | 
| 
      
 7 
     | 
    
         
            +
            import bz2
         
     | 
| 
      
 8 
     | 
    
         
            +
            import pickle as cPickle
         
     | 
| 
       6 
9 
     | 
    
         
             
            from typing import Optional, Tuple
         
     | 
| 
       7 
10 
     | 
    
         
             
            import pandas as pd
         
     | 
| 
       8 
11 
     | 
    
         
             
            import numpy as np
         
     | 
| 
         @@ -89,7 +92,7 @@ class Optimization: 
     | 
|
| 
       89 
92 
     | 
    
         
             
                    if self.lp_solver == 'COIN_CMD' and self.lp_solver_path == 'empty': #if COIN_CMD but lp_solver_path is empty
         
     | 
| 
       90 
93 
     | 
    
         
             
                        self.logger.warning("lp_solver=COIN_CMD but lp_solver_path=empty, attempting to use lp_solver_path=/usr/bin/cbc")
         
     | 
| 
       91 
94 
     | 
    
         
             
                        self.lp_solver_path = '/usr/bin/cbc'  
         
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
       93 
96 
     | 
    
         
             
                def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: np.array, 
         
     | 
| 
       94 
97 
     | 
    
         
             
                                         unit_load_cost: np.array, unit_prod_price: np.array,
         
     | 
| 
       95 
98 
     | 
    
         
             
                                         soc_init: Optional[float] = None, soc_final: Optional[float] = None,
         
     | 
| 
         @@ -153,14 +156,14 @@ class Optimization: 
     | 
|
| 
       153 
156 
     | 
    
         
             
                    if def_end_timestep is None:
         
     | 
| 
       154 
157 
     | 
    
         
             
                        def_end_timestep = self.optim_conf['def_end_timestep']
         
     | 
| 
       155 
158 
     | 
    
         
             
                    type_self_conso = 'bigm' # maxmin
         
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
       157 
160 
     | 
    
         
             
                    #### The LP problem using Pulp ####
         
     | 
| 
       158 
161 
     | 
    
         
             
                    opt_model = plp.LpProblem("LP_Model", plp.LpMaximize)
         
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
       160 
163 
     | 
    
         
             
                    n = len(data_opt.index)
         
     | 
| 
       161 
164 
     | 
    
         
             
                    set_I = range(n)
         
     | 
| 
       162 
165 
     | 
    
         
             
                    M = 10e10
         
     | 
| 
       163 
     | 
    
         
            -
             
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
       164 
167 
     | 
    
         
             
                    ## Add decision variables
         
     | 
| 
       165 
168 
     | 
    
         
             
                    P_grid_neg  = {(i):plp.LpVariable(cat='Continuous',
         
     | 
| 
       166 
169 
     | 
    
         
             
                                                      lowBound=-self.plant_conf['P_to_grid_max'], upBound=0,
         
     | 
| 
         @@ -171,12 +174,16 @@ class Optimization: 
     | 
|
| 
       171 
174 
     | 
    
         
             
                    P_deferrable = []
         
     | 
| 
       172 
175 
     | 
    
         
             
                    P_def_bin1 = []
         
     | 
| 
       173 
176 
     | 
    
         
             
                    for k in range(self.optim_conf['num_def_loads']):
         
     | 
| 
      
 177 
     | 
    
         
            +
                        if type(self.optim_conf['P_deferrable_nom'][k]) == list:
         
     | 
| 
      
 178 
     | 
    
         
            +
                            upBound = np.max(self.optim_conf['P_deferrable_nom'][k])
         
     | 
| 
      
 179 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 180 
     | 
    
         
            +
                            upBound = self.optim_conf['P_deferrable_nom'][k]
         
     | 
| 
       174 
181 
     | 
    
         
             
                        if self.optim_conf['treat_def_as_semi_cont'][k]:
         
     | 
| 
       175 
182 
     | 
    
         
             
                            P_deferrable.append({(i):plp.LpVariable(cat='Continuous',
         
     | 
| 
       176 
183 
     | 
    
         
             
                                                                    name="P_deferrable{}_{}".format(k, i)) for i in set_I})
         
     | 
| 
       177 
184 
     | 
    
         
             
                        else:
         
     | 
| 
       178 
185 
     | 
    
         
             
                            P_deferrable.append({(i):plp.LpVariable(cat='Continuous',
         
     | 
| 
       179 
     | 
    
         
            -
                                                                    lowBound=0, upBound= 
     | 
| 
      
 186 
     | 
    
         
            +
                                                                    lowBound=0, upBound=upBound,
         
     | 
| 
       180 
187 
     | 
    
         
             
                                                                    name="P_deferrable{}_{}".format(k, i)) for i in set_I})
         
     | 
| 
       181 
188 
     | 
    
         
             
                        P_def_bin1.append({(i):plp.LpVariable(cat='Binary',
         
     | 
| 
       182 
189 
     | 
    
         
             
                                                              name="P_def{}_bin1_{}".format(k, i)) for i in set_I})
         
     | 
| 
         @@ -201,11 +208,16 @@ class Optimization: 
     | 
|
| 
       201 
208 
     | 
    
         
             
                    else:
         
     | 
| 
       202 
209 
     | 
    
         
             
                        P_sto_pos  = {(i):i*0 for i in set_I}
         
     | 
| 
       203 
210 
     | 
    
         
             
                        P_sto_neg  = {(i):i*0 for i in set_I}
         
     | 
| 
       204 
     | 
    
         
            -
             
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
       205 
212 
     | 
    
         
             
                    if self.costfun == 'self-consumption':
         
     | 
| 
       206 
213 
     | 
    
         
             
                        SC  = {(i):plp.LpVariable(cat='Continuous',
         
     | 
| 
       207 
214 
     | 
    
         
             
                                                  name="SC_{}".format(i)) for i in set_I}
         
     | 
| 
       208 
     | 
    
         
            -
             
     | 
| 
      
 215 
     | 
    
         
            +
                    if self.plant_conf['inverter_is_hybrid']:
         
     | 
| 
      
 216 
     | 
    
         
            +
                        P_hybrid_inverter = {(i):plp.LpVariable(cat='Continuous',
         
     | 
| 
      
 217 
     | 
    
         
            +
                                                                name="P_hybrid_inverter{}".format(i)) for i in set_I}
         
     | 
| 
      
 218 
     | 
    
         
            +
                    P_PV_curtailment = {(i):plp.LpVariable(cat='Continuous', lowBound=0,
         
     | 
| 
      
 219 
     | 
    
         
            +
                                                           name="P_PV_curtailment{}".format(i)) for i in set_I}
         
     | 
| 
      
 220 
     | 
    
         
            +
                    
         
     | 
| 
       209 
221 
     | 
    
         
             
                    ## Define objective
         
     | 
| 
       210 
222 
     | 
    
         
             
                    P_def_sum= []
         
     | 
| 
       211 
223 
     | 
    
         
             
                    for i in set_I:
         
     | 
| 
         @@ -238,17 +250,116 @@ class Optimization: 
     | 
|
| 
       238 
250 
     | 
    
         
             
                        objective = objective + plp.lpSum(-0.001*self.timeStep*(
         
     | 
| 
       239 
251 
     | 
    
         
             
                            self.optim_conf['weight_battery_discharge']*P_sto_pos[i] + \
         
     | 
| 
       240 
252 
     | 
    
         
             
                                self.optim_conf['weight_battery_charge']*P_sto_neg[i]) for i in set_I)
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                    # Add term penalizing each startup where configured
         
     | 
| 
      
 255 
     | 
    
         
            +
                    if ("def_start_penalty" in self.optim_conf and self.optim_conf["def_start_penalty"]):
         
     | 
| 
      
 256 
     | 
    
         
            +
                        for k in range(self.optim_conf["num_def_loads"]):
         
     | 
| 
      
 257 
     | 
    
         
            +
                            if (len(self.optim_conf["def_start_penalty"]) > k and self.optim_conf["def_start_penalty"][k]):
         
     | 
| 
      
 258 
     | 
    
         
            +
                                objective = objective + plp.lpSum(
         
     | 
| 
      
 259 
     | 
    
         
            +
                                    -0.001 * self.timeStep * self.optim_conf["def_start_penalty"][k] * P_def_start[k][i] *\
         
     | 
| 
      
 260 
     | 
    
         
            +
                                        unit_load_cost[i] * self.optim_conf['P_deferrable_nom'][k]
         
     | 
| 
      
 261 
     | 
    
         
            +
                                    for i in set_I)
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
       241 
263 
     | 
    
         
             
                    opt_model.setObjective(objective)
         
     | 
| 
       242 
     | 
    
         
            -
             
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
       243 
265 
     | 
    
         
             
                    ## Setting constraints
         
     | 
| 
       244 
266 
     | 
    
         
             
                    # The main constraint: power balance
         
     | 
| 
       245 
     | 
    
         
            -
                     
     | 
| 
       246 
     | 
    
         
            -
                         
     | 
| 
       247 
     | 
    
         
            -
                             
     | 
| 
       248 
     | 
    
         
            -
             
     | 
| 
       249 
     | 
    
         
            -
             
     | 
| 
       250 
     | 
    
         
            -
             
     | 
| 
      
 267 
     | 
    
         
            +
                    if self.plant_conf['inverter_is_hybrid']:
         
     | 
| 
      
 268 
     | 
    
         
            +
                        constraints = {"constraint_main1_{}".format(i) :
         
     | 
| 
      
 269 
     | 
    
         
            +
                            plp.LpConstraint(
         
     | 
| 
      
 270 
     | 
    
         
            +
                                e = P_hybrid_inverter[i] - P_def_sum[i] - P_load[i] + P_grid_neg[i] + P_grid_pos[i] ,
         
     | 
| 
      
 271 
     | 
    
         
            +
                                sense = plp.LpConstraintEQ,
         
     | 
| 
      
 272 
     | 
    
         
            +
                                rhs = 0)
         
     | 
| 
      
 273 
     | 
    
         
            +
                            for i in set_I}
         
     | 
| 
      
 274 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 275 
     | 
    
         
            +
                        constraints = {"constraint_main1_{}".format(i) :
         
     | 
| 
      
 276 
     | 
    
         
            +
                            plp.LpConstraint(
         
     | 
| 
      
 277 
     | 
    
         
            +
                                e = P_PV[i] - P_PV_curtailment[i] - P_def_sum[i] - P_load[i] + P_grid_neg[i] + P_grid_pos[i] + P_sto_pos[i] + P_sto_neg[i],
         
     | 
| 
      
 278 
     | 
    
         
            +
                                sense = plp.LpConstraintEQ,
         
     | 
| 
      
 279 
     | 
    
         
            +
                                rhs = 0)
         
     | 
| 
      
 280 
     | 
    
         
            +
                            for i in set_I}
         
     | 
| 
      
 281 
     | 
    
         
            +
                    
         
     | 
| 
      
 282 
     | 
    
         
            +
                    # Constraint for hybrid inverter and curtailment cases
         
     | 
| 
      
 283 
     | 
    
         
            +
                    if type(self.plant_conf['module_model']) == list:
         
     | 
| 
      
 284 
     | 
    
         
            +
                        P_nom_inverter = 0.0
         
     | 
| 
      
 285 
     | 
    
         
            +
                        for i in range(len(self.plant_conf['inverter_model'])):
         
     | 
| 
      
 286 
     | 
    
         
            +
                            if type(self.plant_conf['inverter_model'][i]) == str:
         
     | 
| 
      
 287 
     | 
    
         
            +
                                cec_inverters = bz2.BZ2File(pathlib.Path(__file__).parent / 'data/cec_inverters.pbz2', "rb")
         
     | 
| 
      
 288 
     | 
    
         
            +
                                cec_inverters = cPickle.load(cec_inverters)
         
     | 
| 
      
 289 
     | 
    
         
            +
                                inverter = cec_inverters[self.plant_conf['inverter_model'][i]]
         
     | 
| 
      
 290 
     | 
    
         
            +
                                P_nom_inverter += inverter.Paco
         
     | 
| 
      
 291 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 292 
     | 
    
         
            +
                                P_nom_inverter += self.plant_conf['inverter_model'][i]
         
     | 
| 
      
 293 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 294 
     | 
    
         
            +
                        if type(self.plant_conf['inverter_model'][i]) == str:
         
     | 
| 
      
 295 
     | 
    
         
            +
                            cec_inverters = bz2.BZ2File(pathlib.Path(__file__).parent / 'data/cec_inverters.pbz2', "rb")
         
     | 
| 
      
 296 
     | 
    
         
            +
                            cec_inverters = cPickle.load(cec_inverters)
         
     | 
| 
      
 297 
     | 
    
         
            +
                            inverter = cec_inverters[self.plant_conf['inverter_model']]
         
     | 
| 
      
 298 
     | 
    
         
            +
                            P_nom_inverter = inverter.Paco
         
     | 
| 
      
 299 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 300 
     | 
    
         
            +
                            P_nom_inverter = self.plant_conf['inverter_model']
         
     | 
| 
      
 301 
     | 
    
         
            +
                    if self.plant_conf['inverter_is_hybrid']:
         
     | 
| 
      
 302 
     | 
    
         
            +
                        constraints.update({"constraint_hybrid_inverter1_{}".format(i) :
         
     | 
| 
      
 303 
     | 
    
         
            +
                            plp.LpConstraint(
         
     | 
| 
      
 304 
     | 
    
         
            +
                                e = P_PV[i] - P_PV_curtailment[i] + P_sto_pos[i] + P_sto_neg[i] - P_nom_inverter,
         
     | 
| 
      
 305 
     | 
    
         
            +
                                sense = plp.LpConstraintLE,
         
     | 
| 
      
 306 
     | 
    
         
            +
                                rhs = 0)
         
     | 
| 
      
 307 
     | 
    
         
            +
                            for i in set_I})
         
     | 
| 
      
 308 
     | 
    
         
            +
                        constraints.update({"constraint_hybrid_inverter2_{}".format(i) :
         
     | 
| 
      
 309 
     | 
    
         
            +
                            plp.LpConstraint(
         
     | 
| 
      
 310 
     | 
    
         
            +
                                e = P_PV[i] - P_PV_curtailment[i] + P_sto_pos[i] + P_sto_neg[i] - P_hybrid_inverter[i],
         
     | 
| 
      
 311 
     | 
    
         
            +
                                sense = plp.LpConstraintEQ,
         
     | 
| 
      
 312 
     | 
    
         
            +
                                rhs = 0)
         
     | 
| 
      
 313 
     | 
    
         
            +
                            for i in set_I})
         
     | 
| 
      
 314 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 315 
     | 
    
         
            +
                        constraints.update({"constraint_curtailment_{}".format(i) :
         
     | 
| 
      
 316 
     | 
    
         
            +
                            plp.LpConstraint(
         
     | 
| 
      
 317 
     | 
    
         
            +
                                e = P_PV[i] - P_PV_curtailment[i] - P_nom_inverter,
         
     | 
| 
      
 318 
     | 
    
         
            +
                                sense = plp.LpConstraintLE,
         
     | 
| 
      
 319 
     | 
    
         
            +
                                rhs = 0)
         
     | 
| 
      
 320 
     | 
    
         
            +
                            for i in set_I})
         
     | 
| 
       251 
321 
     | 
    
         | 
| 
      
 322 
     | 
    
         
            +
                    # Constraint for sequence of deferrable
         
     | 
| 
      
 323 
     | 
    
         
            +
                    # WARNING: This is experimental, formulation seems correct but feasibility problems.
         
     | 
| 
      
 324 
     | 
    
         
            +
                    # Probably uncomptabile with other constraints
         
     | 
| 
      
 325 
     | 
    
         
            +
                    for k in range(self.optim_conf['num_def_loads']):
         
     | 
| 
      
 326 
     | 
    
         
            +
                        if type(self.optim_conf['P_deferrable_nom'][k]) == list:
         
     | 
| 
      
 327 
     | 
    
         
            +
                            power_sequence = self.optim_conf['P_deferrable_nom'][k]
         
     | 
| 
      
 328 
     | 
    
         
            +
                            sequence_length = len(power_sequence)
         
     | 
| 
      
 329 
     | 
    
         
            +
                            def create_matrix(input_list, n):
         
     | 
| 
      
 330 
     | 
    
         
            +
                                matrix = []
         
     | 
| 
      
 331 
     | 
    
         
            +
                                for i in range(n + 1):
         
     | 
| 
      
 332 
     | 
    
         
            +
                                    row = [0] * i + input_list + [0] * (n - i)
         
     | 
| 
      
 333 
     | 
    
         
            +
                                    matrix.append(row[:n*2])
         
     | 
| 
      
 334 
     | 
    
         
            +
                                return matrix
         
     | 
| 
      
 335 
     | 
    
         
            +
                            matrix = create_matrix(power_sequence, n-sequence_length)
         
     | 
| 
      
 336 
     | 
    
         
            +
                            y = plp.LpVariable.dicts(f"y{k}", (i for i in range(len(matrix))), cat='Binary')
         
     | 
| 
      
 337 
     | 
    
         
            +
                            constraints.update({f"single_value_constraint_{k}" :
         
     | 
| 
      
 338 
     | 
    
         
            +
                                plp.LpConstraint(
         
     | 
| 
      
 339 
     | 
    
         
            +
                                    e = plp.lpSum(y[i] for i in range(len(matrix))) - 1,
         
     | 
| 
      
 340 
     | 
    
         
            +
                                    sense = plp.LpConstraintEQ,
         
     | 
| 
      
 341 
     | 
    
         
            +
                                    rhs = 0)
         
     | 
| 
      
 342 
     | 
    
         
            +
                                })
         
     | 
| 
      
 343 
     | 
    
         
            +
                            constraints.update({f"pdef{k}_sumconstraint_{i}" :
         
     | 
| 
      
 344 
     | 
    
         
            +
                                plp.LpConstraint(
         
     | 
| 
      
 345 
     | 
    
         
            +
                                    e = plp.lpSum(P_deferrable[k][i] for i in set_I) - np.sum(power_sequence),
         
     | 
| 
      
 346 
     | 
    
         
            +
                                    sense = plp.LpConstraintEQ,
         
     | 
| 
      
 347 
     | 
    
         
            +
                                    rhs = 0)
         
     | 
| 
      
 348 
     | 
    
         
            +
                                })
         
     | 
| 
      
 349 
     | 
    
         
            +
                            constraints.update({f"pdef{k}_positive_constraint_{i}" :
         
     | 
| 
      
 350 
     | 
    
         
            +
                                plp.LpConstraint(
         
     | 
| 
      
 351 
     | 
    
         
            +
                                    e = P_deferrable[k][i],
         
     | 
| 
      
 352 
     | 
    
         
            +
                                    sense = plp.LpConstraintGE,
         
     | 
| 
      
 353 
     | 
    
         
            +
                                    rhs = 0)
         
     | 
| 
      
 354 
     | 
    
         
            +
                                for i in set_I})
         
     | 
| 
      
 355 
     | 
    
         
            +
                            for num, mat in enumerate(matrix):
         
     | 
| 
      
 356 
     | 
    
         
            +
                                constraints.update({f"pdef{k}_value_constraint_{num}_{i}" :
         
     | 
| 
      
 357 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 358 
     | 
    
         
            +
                                        e = P_deferrable[k][i] - mat[i]*y[num],
         
     | 
| 
      
 359 
     | 
    
         
            +
                                        sense = plp.LpConstraintEQ,
         
     | 
| 
      
 360 
     | 
    
         
            +
                                        rhs = 0)
         
     | 
| 
      
 361 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 362 
     | 
    
         
            +
             
     | 
| 
       252 
363 
     | 
    
         
             
                    # Two special constraints just for a self-consumption cost function
         
     | 
| 
       253 
364 
     | 
    
         
             
                    if self.costfun == 'self-consumption':
         
     | 
| 
       254 
365 
     | 
    
         
             
                        if type_self_conso == 'maxmin': # maxmin linear problem
         
     | 
| 
         @@ -264,7 +375,7 @@ class Optimization: 
     | 
|
| 
       264 
375 
     | 
    
         
             
                                    sense = plp.LpConstraintLE,
         
     | 
| 
       265 
376 
     | 
    
         
             
                                    rhs = 0)
         
     | 
| 
       266 
377 
     | 
    
         
             
                                for i in set_I})
         
     | 
| 
       267 
     | 
    
         
            -
             
     | 
| 
      
 378 
     | 
    
         
            +
             
     | 
| 
       268 
379 
     | 
    
         
             
                    # Avoid injecting and consuming from grid at the same time
         
     | 
| 
       269 
380 
     | 
    
         
             
                    constraints.update({"constraint_pgridpos_{}".format(i) : 
         
     | 
| 
       270 
381 
     | 
    
         
             
                        plp.LpConstraint(
         
     | 
| 
         @@ -278,79 +389,145 @@ class Optimization: 
     | 
|
| 
       278 
389 
     | 
    
         
             
                            sense = plp.LpConstraintLE,
         
     | 
| 
       279 
390 
     | 
    
         
             
                            rhs = 0)
         
     | 
| 
       280 
391 
     | 
    
         
             
                        for i in set_I})
         
     | 
| 
       281 
     | 
    
         
            -
             
     | 
| 
      
 392 
     | 
    
         
            +
             
     | 
| 
       282 
393 
     | 
    
         
             
                    # Treat deferrable loads constraints
         
     | 
| 
       283 
394 
     | 
    
         
             
                    for k in range(self.optim_conf['num_def_loads']):
         
     | 
| 
       284 
     | 
    
         
            -
                         
     | 
| 
       285 
     | 
    
         
            -
             
     | 
| 
       286 
     | 
    
         
            -
             
     | 
| 
       287 
     | 
    
         
            -
             
     | 
| 
       288 
     | 
    
         
            -
             
     | 
| 
       289 
     | 
    
         
            -
                                rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
         
     | 
| 
       290 
     | 
    
         
            -
                            })
         
     | 
| 
       291 
     | 
    
         
            -
                        # Ensure deferrable loads consume energy between def_start_timestep & def_end_timestep
         
     | 
| 
       292 
     | 
    
         
            -
                        self.logger.debug("Deferrable load {}: Proposed optimization window: {} --> {}".format(k, def_start_timestep[k], def_end_timestep[k]))
         
     | 
| 
       293 
     | 
    
         
            -
                        def_start, def_end, warning = Optimization.validate_def_timewindow(def_start_timestep[k], def_end_timestep[k], ceil(def_total_hours[k]/self.timeStep), n)
         
     | 
| 
       294 
     | 
    
         
            -
                        if warning is not None: 
         
     | 
| 
       295 
     | 
    
         
            -
                            self.logger.warning("Deferrable load {} : {}".format(k, warning))
         
     | 
| 
       296 
     | 
    
         
            -
                        self.logger.debug("Deferrable load {}: Validated optimization window: {} --> {}".format(k, def_start, def_end))
         
     | 
| 
       297 
     | 
    
         
            -
                        if def_start > 0:                    
         
     | 
| 
       298 
     | 
    
         
            -
                            constraints.update({"constraint_defload{}_start_timestep".format(k) :
         
     | 
| 
      
 395 
     | 
    
         
            +
                        if type(self.optim_conf['P_deferrable_nom'][k]) == list:
         
     | 
| 
      
 396 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 397 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 398 
     | 
    
         
            +
                            # Total time of deferrable load
         
     | 
| 
      
 399 
     | 
    
         
            +
                            constraints.update({"constraint_defload{}_energy".format(k) :
         
     | 
| 
       299 
400 
     | 
    
         
             
                                plp.LpConstraint(
         
     | 
| 
       300 
     | 
    
         
            -
                                    e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in  
     | 
| 
      
 401 
     | 
    
         
            +
                                    e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in set_I),
         
     | 
| 
       301 
402 
     | 
    
         
             
                                    sense = plp.LpConstraintEQ,
         
     | 
| 
       302 
     | 
    
         
            -
                                    rhs =  
     | 
| 
      
 403 
     | 
    
         
            +
                                    rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
         
     | 
| 
       303 
404 
     | 
    
         
             
                                })
         
     | 
| 
       304 
     | 
    
         
            -
             
     | 
| 
       305 
     | 
    
         
            -
                             
     | 
| 
      
 405 
     | 
    
         
            +
                            # Ensure deferrable loads consume energy between def_start_timestep & def_end_timestep
         
     | 
| 
      
 406 
     | 
    
         
            +
                            self.logger.debug("Deferrable load {}: Proposed optimization window: {} --> {}".format(
         
     | 
| 
      
 407 
     | 
    
         
            +
                                k, def_start_timestep[k], def_end_timestep[k]))
         
     | 
| 
      
 408 
     | 
    
         
            +
                            def_start, def_end, warning = Optimization.validate_def_timewindow(
         
     | 
| 
      
 409 
     | 
    
         
            +
                                def_start_timestep[k], def_end_timestep[k], ceil(def_total_hours[k]/self.timeStep), n)
         
     | 
| 
      
 410 
     | 
    
         
            +
                            if warning is not None: 
         
     | 
| 
      
 411 
     | 
    
         
            +
                                self.logger.warning("Deferrable load {} : {}".format(k, warning))
         
     | 
| 
      
 412 
     | 
    
         
            +
                            self.logger.debug("Deferrable load {}: Validated optimization window: {} --> {}".format(
         
     | 
| 
      
 413 
     | 
    
         
            +
                                k, def_start, def_end))
         
     | 
| 
      
 414 
     | 
    
         
            +
                            if def_start > 0:                    
         
     | 
| 
      
 415 
     | 
    
         
            +
                                constraints.update({"constraint_defload{}_start_timestep".format(k) :
         
     | 
| 
      
 416 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 417 
     | 
    
         
            +
                                        e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(0, def_start)),
         
     | 
| 
      
 418 
     | 
    
         
            +
                                        sense = plp.LpConstraintEQ,
         
     | 
| 
      
 419 
     | 
    
         
            +
                                        rhs = 0)
         
     | 
| 
      
 420 
     | 
    
         
            +
                                    })
         
     | 
| 
      
 421 
     | 
    
         
            +
                            if def_end > 0:                    
         
     | 
| 
      
 422 
     | 
    
         
            +
                                constraints.update({"constraint_defload{}_end_timestep".format(k) :
         
     | 
| 
      
 423 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 424 
     | 
    
         
            +
                                        e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(def_end, n)),
         
     | 
| 
      
 425 
     | 
    
         
            +
                                        sense = plp.LpConstraintEQ,
         
     | 
| 
      
 426 
     | 
    
         
            +
                                        rhs = 0)
         
     | 
| 
      
 427 
     | 
    
         
            +
                                    })
         
     | 
| 
      
 428 
     | 
    
         
            +
                            # Treat deferrable load as a semi-continuous variable
         
     | 
| 
      
 429 
     | 
    
         
            +
                            if self.optim_conf['treat_def_as_semi_cont'][k]:
         
     | 
| 
      
 430 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) : 
         
     | 
| 
      
 431 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 432 
     | 
    
         
            +
                                        e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
         
     | 
| 
      
 433 
     | 
    
         
            +
                                        sense=plp.LpConstraintGE,
         
     | 
| 
      
 434 
     | 
    
         
            +
                                        rhs=0)
         
     | 
| 
      
 435 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 436 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_semicont2_{}".format(k, i) :
         
     | 
| 
      
 437 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 438 
     | 
    
         
            +
                                        e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
         
     | 
| 
      
 439 
     | 
    
         
            +
                                        sense=plp.LpConstraintLE,
         
     | 
| 
      
 440 
     | 
    
         
            +
                                        rhs=0)
         
     | 
| 
      
 441 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 442 
     | 
    
         
            +
                            # Treat the number of starts for a deferrable load
         
     | 
| 
      
 443 
     | 
    
         
            +
                            if self.optim_conf['set_def_constant'][k]:
         
     | 
| 
      
 444 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_start1_{}".format(k, i) : 
         
     | 
| 
      
 445 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 446 
     | 
    
         
            +
                                        e=P_deferrable[k][i] - P_def_bin2[k][i]*M,
         
     | 
| 
      
 447 
     | 
    
         
            +
                                        sense=plp.LpConstraintLE,
         
     | 
| 
      
 448 
     | 
    
         
            +
                                        rhs=0)
         
     | 
| 
      
 449 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 450 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_start2_{}".format(k, i): 
         
     | 
| 
      
 451 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 452 
     | 
    
         
            +
                                        e=P_def_start[k][i] - P_def_bin2[k][i] + (P_def_bin2[k][i-1] if i-1 >= 0 else 0),
         
     | 
| 
      
 453 
     | 
    
         
            +
                                        sense=plp.LpConstraintGE,
         
     | 
| 
      
 454 
     | 
    
         
            +
                                        rhs=0)
         
     | 
| 
      
 455 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 456 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_start3".format(k) :
         
     | 
| 
       306 
457 
     | 
    
         
             
                                plp.LpConstraint(
         
     | 
| 
       307 
     | 
    
         
            -
                                    e = plp.lpSum( 
     | 
| 
      
 458 
     | 
    
         
            +
                                    e = plp.lpSum(P_def_start[k][i] for i in set_I),
         
     | 
| 
       308 
459 
     | 
    
         
             
                                    sense = plp.LpConstraintEQ,
         
     | 
| 
       309 
     | 
    
         
            -
                                    rhs =  
     | 
| 
      
 460 
     | 
    
         
            +
                                    rhs = 1)
         
     | 
| 
       310 
461 
     | 
    
         
             
                                })
         
     | 
| 
       311 
     | 
    
         
            -
             
     | 
| 
       312 
     | 
    
         
            -
             
     | 
| 
       313 
     | 
    
         
            -
             
     | 
| 
       314 
     | 
    
         
            -
             
     | 
| 
       315 
     | 
    
         
            -
             
     | 
| 
       316 
     | 
    
         
            -
             
     | 
| 
       317 
     | 
    
         
            -
             
     | 
| 
       318 
     | 
    
         
            -
                                     
     | 
| 
       319 
     | 
    
         
            -
                                 
     | 
| 
       320 
     | 
    
         
            -
             
     | 
| 
      
 462 
     | 
    
         
            +
                            # Treat deferrable load as a semi-continuous variable
         
     | 
| 
      
 463 
     | 
    
         
            +
                            if self.optim_conf['treat_def_as_semi_cont'][k]:
         
     | 
| 
      
 464 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) : 
         
     | 
| 
      
 465 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 466 
     | 
    
         
            +
                                        e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
         
     | 
| 
      
 467 
     | 
    
         
            +
                                        sense=plp.LpConstraintGE,
         
     | 
| 
      
 468 
     | 
    
         
            +
                                        rhs=0)
         
     | 
| 
      
 469 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 470 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_semicont2_{}".format(k, i) :
         
     | 
| 
      
 471 
     | 
    
         
            +
                                    plp.LpConstraint(
         
     | 
| 
      
 472 
     | 
    
         
            +
                                        e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
         
     | 
| 
      
 473 
     | 
    
         
            +
                                        sense=plp.LpConstraintLE,
         
     | 
| 
      
 474 
     | 
    
         
            +
                                        rhs=0)
         
     | 
| 
      
 475 
     | 
    
         
            +
                                    for i in set_I})
         
     | 
| 
      
 476 
     | 
    
         
            +
                            # Treat the number of starts for a deferrable load
         
     | 
| 
      
 477 
     | 
    
         
            +
                            current_state = 0
         
     | 
| 
      
 478 
     | 
    
         
            +
                            if ("def_current_state" in self.optim_conf and len(self.optim_conf["def_current_state"]) > k):
         
     | 
| 
      
 479 
     | 
    
         
            +
                                current_state = 1 if self.optim_conf["def_current_state"][k] else 0
         
     | 
| 
      
 480 
     | 
    
         
            +
                            # P_deferrable < P_def_bin2 * 1 million
         
     | 
| 
      
 481 
     | 
    
         
            +
                            # P_deferrable must be zero if P_def_bin2 is zero
         
     | 
| 
      
 482 
     | 
    
         
            +
                            constraints.update({"constraint_pdef{}_start1_{}".format(k, i): 
         
     | 
| 
       321 
483 
     | 
    
         
             
                                plp.LpConstraint(
         
     | 
| 
       322 
     | 
    
         
            -
                                    e=P_deferrable[k][i] -  
     | 
| 
      
 484 
     | 
    
         
            +
                                    e=P_deferrable[k][i] - P_def_bin2[k][i] * M,
         
     | 
| 
       323 
485 
     | 
    
         
             
                                    sense=plp.LpConstraintLE,
         
     | 
| 
       324 
486 
     | 
    
         
             
                                    rhs=0)
         
     | 
| 
       325 
487 
     | 
    
         
             
                                for i in set_I})
         
     | 
| 
       326 
     | 
    
         
            -
             
     | 
| 
       327 
     | 
    
         
            -
             
     | 
| 
       328 
     | 
    
         
            -
                            
         
     | 
| 
       329 
     | 
    
         
            -
                            constraints.update({"constraint_pdef{}_start1_{}".format(k, i) : 
         
     | 
| 
      
 488 
     | 
    
         
            +
                            # P_deferrable - P_def_bin2 <= 0
         
     | 
| 
      
 489 
     | 
    
         
            +
                            # P_def_bin2 must be zero if P_deferrable is zero
         
     | 
| 
      
 490 
     | 
    
         
            +
                            constraints.update({"constraint_pdef{}_start1a_{}".format(k, i): 
         
     | 
| 
       330 
491 
     | 
    
         
             
                                plp.LpConstraint(
         
     | 
| 
       331 
     | 
    
         
            -
                                    e= 
     | 
| 
      
 492 
     | 
    
         
            +
                                    e=P_def_bin2[k][i] - P_deferrable[k][i],
         
     | 
| 
       332 
493 
     | 
    
         
             
                                    sense=plp.LpConstraintLE,
         
     | 
| 
       333 
494 
     | 
    
         
             
                                    rhs=0)
         
     | 
| 
       334 
495 
     | 
    
         
             
                                for i in set_I})
         
     | 
| 
      
 496 
     | 
    
         
            +
                            # P_def_start + P_def_bin2[i-1] >= P_def_bin2[i]
         
     | 
| 
      
 497 
     | 
    
         
            +
                            # If load is on this cycle (P_def_bin2[i] is 1) then P_def_start must be 1 OR P_def_bin2[i-1] must be 1
         
     | 
| 
      
 498 
     | 
    
         
            +
                            # For first timestep, use current state if provided by caller.
         
     | 
| 
       335 
499 
     | 
    
         
             
                            constraints.update({"constraint_pdef{}_start2_{}".format(k, i): 
         
     | 
| 
       336 
500 
     | 
    
         
             
                                plp.LpConstraint(
         
     | 
| 
       337 
     | 
    
         
            -
                                    e=P_def_start[k][i] 
     | 
| 
      
 501 
     | 
    
         
            +
                                    e=P_def_start[k][i]
         
     | 
| 
      
 502 
     | 
    
         
            +
                                    - P_def_bin2[k][i]
         
     | 
| 
      
 503 
     | 
    
         
            +
                                    + (P_def_bin2[k][i - 1] if i - 1 >= 0 else current_state),
         
     | 
| 
       338 
504 
     | 
    
         
             
                                    sense=plp.LpConstraintGE,
         
     | 
| 
       339 
505 
     | 
    
         
             
                                    rhs=0)
         
     | 
| 
       340 
506 
     | 
    
         
             
                                for i in set_I})
         
     | 
| 
       341 
     | 
    
         
            -
                             
     | 
| 
       342 
     | 
    
         
            -
                             
     | 
| 
       343 
     | 
    
         
            -
             
     | 
| 
       344 
     | 
    
         
            -
                                 
     | 
| 
       345 
     | 
    
         
            -
             
     | 
| 
       346 
     | 
    
         
            -
             
     | 
| 
       347 
     | 
    
         
            -
             
     | 
| 
       348 
     | 
    
         
            -
             
     | 
| 
       349 
     | 
    
         
            -
             
     | 
| 
       350 
     | 
    
         
            -
                                 
     | 
| 
       351 
     | 
    
         
            -
                                 
     | 
| 
       352 
     | 
    
         
            -
             
     | 
| 
       353 
     | 
    
         
            -
             
     | 
| 
      
 507 
     | 
    
         
            +
                            # P_def_bin2[i-1] + P_def_start <= 1
         
     | 
| 
      
 508 
     | 
    
         
            +
                            # If load started this cycle (P_def_start[i] is 1) then P_def_bin2[i-1] must be 0
         
     | 
| 
      
 509 
     | 
    
         
            +
                            constraints.update({"constraint_pdef{}_start3_{}".format(k, i):
         
     | 
| 
      
 510 
     | 
    
         
            +
                                plp.LpConstraint(
         
     | 
| 
      
 511 
     | 
    
         
            +
                                    e=(P_def_bin2[k][i-1] if i-1 >= 0 else 0) + P_def_start[k][i],
         
     | 
| 
      
 512 
     | 
    
         
            +
                                    sense=plp.LpConstraintLE,
         
     | 
| 
      
 513 
     | 
    
         
            +
                                    rhs=1)
         
     | 
| 
      
 514 
     | 
    
         
            +
                                for i in set_I})
         
     | 
| 
      
 515 
     | 
    
         
            +
                            if self.optim_conf['set_def_constant'][k]:
         
     | 
| 
      
 516 
     | 
    
         
            +
                                # P_def_start[i] must be 1 for exactly 1 value of i
         
     | 
| 
      
 517 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_start4".format(k) :
         
     | 
| 
      
 518 
     | 
    
         
            +
                                plp.LpConstraint(
         
     | 
| 
      
 519 
     | 
    
         
            +
                                    e = plp.lpSum(P_def_start[k][i] for i in set_I),
         
     | 
| 
      
 520 
     | 
    
         
            +
                                    sense = plp.LpConstraintEQ,
         
     | 
| 
      
 521 
     | 
    
         
            +
                                    rhs = 1)
         
     | 
| 
      
 522 
     | 
    
         
            +
                                })
         
     | 
| 
      
 523 
     | 
    
         
            +
                                # P_def_bin2 must be 1 for exactly the correct number of timesteps.
         
     | 
| 
      
 524 
     | 
    
         
            +
                                constraints.update({"constraint_pdef{}_start5".format(k) :
         
     | 
| 
      
 525 
     | 
    
         
            +
                                plp.LpConstraint(
         
     | 
| 
      
 526 
     | 
    
         
            +
                                    e = plp.lpSum(P_def_bin2[k][i] for i in set_I),
         
     | 
| 
      
 527 
     | 
    
         
            +
                                    sense = plp.LpConstraintEQ,
         
     | 
| 
      
 528 
     | 
    
         
            +
                                    rhs = def_total_hours[k]/self.timeStep)
         
     | 
| 
      
 529 
     | 
    
         
            +
                                })
         
     | 
| 
      
 530 
     | 
    
         
            +
             
     | 
| 
       354 
531 
     | 
    
         
             
                    # The battery constraints
         
     | 
| 
       355 
532 
     | 
    
         
             
                    if self.optim_conf['set_use_battery']:
         
     | 
| 
       356 
533 
     | 
    
         
             
                        # Optional constraints to avoid charging the battery from the grid
         
     | 
| 
         @@ -423,7 +600,7 @@ class Optimization: 
     | 
|
| 
       423 
600 
     | 
    
         
             
                                rhs=(soc_init - soc_final)*self.plant_conf['Enom']/self.timeStep)
         
     | 
| 
       424 
601 
     | 
    
         
             
                            })
         
     | 
| 
       425 
602 
     | 
    
         
             
                    opt_model.constraints = constraints
         
     | 
| 
       426 
     | 
    
         
            -
             
     | 
| 
      
 603 
     | 
    
         
            +
             
     | 
| 
       427 
604 
     | 
    
         
             
                    ## Finally, we call the solver to solve our optimization model:
         
     | 
| 
       428 
605 
     | 
    
         
             
                    # solving with default solver CBC
         
     | 
| 
       429 
606 
     | 
    
         
             
                    if self.lp_solver == 'PULP_CBC_CMD':
         
     | 
| 
         @@ -435,7 +612,7 @@ class Optimization: 
     | 
|
| 
       435 
612 
     | 
    
         
             
                    else:
         
     | 
| 
       436 
613 
     | 
    
         
             
                        self.logger.warning("Solver %s unknown, using default", self.lp_solver)
         
     | 
| 
       437 
614 
     | 
    
         
             
                        opt_model.solve()
         
     | 
| 
       438 
     | 
    
         
            -
             
     | 
| 
      
 615 
     | 
    
         
            +
             
     | 
| 
       439 
616 
     | 
    
         
             
                    # The status of the solution is printed to the screen
         
     | 
| 
       440 
617 
     | 
    
         
             
                    self.optim_status = plp.LpStatus[opt_model.status]
         
     | 
| 
       441 
618 
     | 
    
         
             
                    self.logger.info("Status: " + self.optim_status)
         
     | 
| 
         @@ -444,7 +621,7 @@ class Optimization: 
     | 
|
| 
       444 
621 
     | 
    
         
             
                        return
         
     | 
| 
       445 
622 
     | 
    
         
             
                    else:
         
     | 
| 
       446 
623 
     | 
    
         
             
                        self.logger.info("Total value of the Cost function = %.02f", plp.value(opt_model.objective))
         
     | 
| 
       447 
     | 
    
         
            -
             
     | 
| 
      
 624 
     | 
    
         
            +
             
     | 
| 
       448 
625 
     | 
    
         
             
                    # Build results Dataframe
         
     | 
| 
       449 
626 
     | 
    
         
             
                    opt_tp = pd.DataFrame()
         
     | 
| 
       450 
627 
     | 
    
         
             
                    opt_tp["P_PV"] = [P_PV[i] for i in set_I]
         
     | 
| 
         @@ -465,8 +642,11 @@ class Optimization: 
     | 
|
| 
       465 
642 
     | 
    
         
             
                            SOC_opt.append(SOCinit - SOC_opt_delta[i])
         
     | 
| 
       466 
643 
     | 
    
         
             
                            SOCinit = SOC_opt[i]
         
     | 
| 
       467 
644 
     | 
    
         
             
                        opt_tp["SOC_opt"] = SOC_opt
         
     | 
| 
      
 645 
     | 
    
         
            +
                    if self.plant_conf['inverter_is_hybrid']:
         
     | 
| 
      
 646 
     | 
    
         
            +
                        opt_tp["P_hybrid_inverter"] = [P_hybrid_inverter[i].varValue for i in set_I]
         
     | 
| 
      
 647 
     | 
    
         
            +
                    opt_tp["P_PV_curtailment"] = [P_PV_curtailment[i].varValue for i in set_I]
         
     | 
| 
       468 
648 
     | 
    
         
             
                    opt_tp.index = data_opt.index
         
     | 
| 
       469 
     | 
    
         
            -
             
     | 
| 
      
 649 
     | 
    
         
            +
             
     | 
| 
       470 
650 
     | 
    
         
             
                    # Lets compute the optimal cost function
         
     | 
| 
       471 
651 
     | 
    
         
             
                    P_def_sum_tp = []
         
     | 
| 
       472 
652 
     | 
    
         
             
                    for i in set_I:
         
     | 
| 
         @@ -479,7 +659,7 @@ class Optimization: 
     | 
|
| 
       479 
659 
     | 
    
         
             
                    else:
         
     | 
| 
       480 
660 
     | 
    
         
             
                        opt_tp["cost_profit"] = [-0.001*self.timeStep*(unit_load_cost[i]*P_grid_pos[i].varValue + \
         
     | 
| 
       481 
661 
     | 
    
         
             
                            unit_prod_price[i]*P_grid_neg[i].varValue) for i in set_I]
         
     | 
| 
       482 
     | 
    
         
            -
             
     | 
| 
      
 662 
     | 
    
         
            +
             
     | 
| 
       483 
663 
     | 
    
         
             
                    if self.costfun == 'profit':
         
     | 
| 
       484 
664 
     | 
    
         
             
                        if self.optim_conf['set_total_pv_sell']:
         
     | 
| 
       485 
665 
     | 
    
         
             
                            opt_tp["cost_fun_profit"] = [-0.001*self.timeStep*(unit_load_cost[i]*(P_load[i] + P_def_sum_tp[i]) + \
         
     | 
| 
         @@ -500,17 +680,16 @@ class Optimization: 
     | 
|
| 
       500 
680 
     | 
    
         
             
                                unit_prod_price[i]*P_grid_neg[i].varValue) for i in set_I]
         
     | 
| 
       501 
681 
     | 
    
         
             
                    else:
         
     | 
| 
       502 
682 
     | 
    
         
             
                        self.logger.error("The cost function specified type is not valid")
         
     | 
| 
       503 
     | 
    
         
            -
             
     | 
| 
      
 683 
     | 
    
         
            +
             
     | 
| 
       504 
684 
     | 
    
         
             
                    # Add the optimization status
         
     | 
| 
       505 
685 
     | 
    
         
             
                    opt_tp["optim_status"] = self.optim_status
         
     | 
| 
       506 
     | 
    
         
            -
             
     | 
| 
      
 686 
     | 
    
         
            +
             
     | 
| 
       507 
687 
     | 
    
         
             
                    # Debug variables
         
     | 
| 
       508 
688 
     | 
    
         
             
                    if debug:
         
     | 
| 
       509 
     | 
    
         
            -
                         
     | 
| 
       510 
     | 
    
         
            -
             
     | 
| 
       511 
     | 
    
         
            -
             
     | 
| 
       512 
     | 
    
         
            -
             
     | 
| 
       513 
     | 
    
         
            -
                    
         
     | 
| 
      
 689 
     | 
    
         
            +
                        for k in range(self.optim_conf["num_def_loads"]):
         
     | 
| 
      
 690 
     | 
    
         
            +
                            opt_tp[f"P_def_start_{k}"] = [P_def_start[k][i].varValue for i in set_I]
         
     | 
| 
      
 691 
     | 
    
         
            +
                            opt_tp[f"P_def_bin2_{k}"] = [P_def_bin2[k][i].varValue for i in set_I]
         
     | 
| 
      
 692 
     | 
    
         
            +
             
     | 
| 
       514 
693 
     | 
    
         
             
                    return opt_tp
         
     | 
| 
       515 
694 
     | 
    
         | 
| 
       516 
695 
     | 
    
         
             
                def perform_perfect_forecast_optim(self, df_input_data: pd.DataFrame, days_list: pd.date_range) -> pd.DataFrame:
         
     | 
| 
         @@ -548,9 +727,9 @@ class Optimization: 
     | 
|
| 
       548 
727 
     | 
    
         
             
                            self.opt_res = opt_tp
         
     | 
| 
       549 
728 
     | 
    
         
             
                        else:
         
     | 
| 
       550 
729 
     | 
    
         
             
                            self.opt_res = pd.concat([self.opt_res, opt_tp], axis=0)
         
     | 
| 
       551 
     | 
    
         
            -
             
     | 
| 
      
 730 
     | 
    
         
            +
             
     | 
| 
       552 
731 
     | 
    
         
             
                    return self.opt_res
         
     | 
| 
       553 
     | 
    
         
            -
             
     | 
| 
      
 732 
     | 
    
         
            +
             
     | 
| 
       554 
733 
     | 
    
         
             
                def perform_dayahead_forecast_optim(self, df_input_data: pd.DataFrame, 
         
     | 
| 
       555 
734 
     | 
    
         
             
                                                    P_PV: pd.Series, P_load: pd.Series) -> pd.DataFrame:
         
     | 
| 
       556 
735 
     | 
    
         
             
                    r"""
         
     | 
| 
         @@ -577,7 +756,7 @@ class Optimization: 
     | 
|
| 
       577 
756 
     | 
    
         
             
                                                             P_load.values.ravel(), 
         
     | 
| 
       578 
757 
     | 
    
         
             
                                                             unit_load_cost, unit_prod_price)
         
     | 
| 
       579 
758 
     | 
    
         
             
                    return self.opt_res
         
     | 
| 
       580 
     | 
    
         
            -
             
     | 
| 
      
 759 
     | 
    
         
            +
             
     | 
| 
       581 
760 
     | 
    
         
             
                def perform_naive_mpc_optim(self, df_input_data: pd.DataFrame, P_PV: pd.Series, P_load: pd.Series,
         
     | 
| 
       582 
761 
     | 
    
         
             
                                            prediction_horizon: int, soc_init: Optional[float] = None, soc_final: Optional[float] = None,
         
     | 
| 
       583 
762 
     | 
    
         
             
                                            def_total_hours: Optional[list] = None,
         
     |