Source code for omegalpes.general.optimisation.elements

#! usr/bin/env python3
#  -*- coding: utf-8 -*-

"""
This module includes the optimization elements (quantities, constraints and
objectives) formulated in LP or MILP :

 - Quantity : related to the decision variable or parameter
 - Constraint : related to the optimization problem constraints
 - Objective : related to the objective function

..

    Copyright 2018 G2Elab / MAGE

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""

import datetime
from pulp import LpContinuous

__docformat__ = "restructuredtext en"


[docs]class Quantity: """" **Description** Class that defines what is a quantity. A quantity can wether be a decision variable or a parameter, depending on the opt parameter **Attributes** - name (str) : the name of the quantity - description (str) : a description of the meaning of the quantity - vtype (PuLP) : the variable type, depending on PuLP : * LpBinary (binary variable) * LpInteger (integer variable) * LpContinuous (continuous variable) - vlen (int) : size of the variable - unit (str) : unit of the quantity - opt (binary) : * True: this is an optimization variable * False: this is a constant - a parameter - value (float, list, dict) : value (unused if opt=True) - ub, lb : upper and lower bounds - parent (Unit) : the quantity belongs to this unit .. note:: Make sure that all the modifications on Quantity are made before adding the unit to the Model """ def __init__(self, name="var0", opt=True, unit="s.u", vlen=None, value=None, description="", vtype=LpContinuous, lb=None, ub=None, parent=None): """ :param name: (str) name of the quantity :param opt: (binary) optimisation parameter :param unit: (str) unit of the quantity :param vlen: (int) size of the quantity value :param value: (float, list or dict) value of the quantity :param description: (str) description of the quantity :param vtype: (type) the variable type according to PuLP :param lb: (float or int) : lower bound of the value of the quantity :param ub: (float or int) : upper bound of the value of the quantity :param parent: (Unit) parent unit """ self.name = name if vlen is None: try: vlen = len(value) except TypeError: vlen = 1 if parent is None: print('Warning : Parent argument of {0} is None'.format(self.name)) self.vlen = vlen self.description = description self.unit = unit self.lb = lb self.ub = ub self.vtype = vtype self.value = {} if not isinstance(vlen, int): raise TypeError('vlen should be of type int !') if isinstance(opt, bool): # The optimisation parameter is a boolean if isinstance(value, (int, float)): # The value is an int or a float self.opt = opt if value: # If the value is not None, the opt parameter should be # False self.opt = False if vlen <= 1: self.value = value else: # vlen should be 1 for ints and floats values raise ValueError( 'value\'s size is given as vlen = {0}, but the value ' 'is actually a {1}'.format(vlen, type(value))) elif isinstance(value, list): # The value is a list if value: # If value is not None, the opt parameter is a list of # False self.opt = [] for i in range(vlen): self.opt.append(False) if len(value) == vlen: self.value = value else: # vlen should be the list length raise ValueError( 'value\'s size ({0}) should be vlen ({1})'.format( len(value), vlen)) elif isinstance(value, dict): # The value is a dict if any(isinstance(n, int or float) for n in (list( value.values()))): # At least one value of the dict is specified self.opt = {} # The opt parameter is a dict of False self.opt.update({i: False for i in list(value.keys())}) print('At least one value is specified in the dict ' 'value : the opt parameter is set as a dict of ' 'False with the value keys') else: self.opt = {} self.opt.update({i: opt for i in list(value.keys())}) if len(value) == vlen: self.value = value else: # vlen should be the dict value size raise ValueError( 'value\'s size ({0}) should be vlen ({1})'.format( len(value), vlen)) elif value is None: if not opt: # The quantity should have a specified value if # opt=False OR opt should be set to True if the Quantity # is a decision variable raise Exception('If the Quantity.opt == False, ' 'Quantity.value cannot be None. ' 'Value is required, or opt must be set ' 'to True !') elif vlen >= 2: # if opt=True, the value is given the default value 0, # or a list of 0 if vlen>=2 self.opt = {i: opt for i in range(vlen)} self.value = {i: 0 for i in range(vlen)} else: self.opt = opt self.value = 0 else: # Value should be either an int, a float, a list, a dict or # None raise TypeError('Unknown type for argument \'value\'') elif isinstance(opt, (list, dict)): raise Exception('Not implemented yet...') else: # opt should be a boolean, a list or a dict raise TypeError('Unknown type for argument \'opt\'') self.parent = parent def __str__(self): # String special method return str(self.value) def __repr__(self): # Repr special method if isinstance(self.opt, bool): exp = '<OMEGALPES.general.optimisation.elements.Quantity : (' \ 'name:\'{0}\', opt:{1}, vlen:{1})> \n'.format(self.name, self.opt, self.vlen) elif isinstance(self.opt, list): exp = '<OMEGALPES.general.optimisation.elements.Quantity : (' \ 'name:\'{0}\', opt[0]:{1}, vlen:{2})> \n'.format(self.name, self.opt[0], self.vlen) elif isinstance(self.opt, dict): exp = '<OMEGALPES.general.optimisation.elements.Quantity : (' \ 'name:\'{0}\', opt:{1}, vlen:{2})> \n'.format(self.name, list( self.opt.values()), self.vlen) return exp def __float__(self): # Special method float (only works for int and float values, # not list and dict ones) return float(self.value)
[docs]class Constraint: """ **Description** Class that defines a constraint object **Attributes** - name: name of the constraint - description: a description of the constraint - active: False = non-active constraint; True = active constraint - exp: (str) : expression of the constraint - parent: (unit) : this constraint belongs to this unit .. note:: Make sure that all the modifications on Constraints are made before adding the unit to the Model (OptimisationModel.addUnit()). """ def __init__(self, exp, name='CST0', description='', active=True, parent=None): """ :param exp: (str) expression of the constraint :param name: (str) name of the constraint :param description: (str) description of the constrain :param active: (bool) is the constraint active or not :param parent: (Unit) parent unit """ self.name = name self.description = description self.exp = exp self.active = active self.parent = parent
[docs]class DynamicConstraint(Constraint): """ ** Description ** Defining a constraint depending on the time. NB : Mandatory for PuLP """ def __init__(self, exp_t, t_range='for t in time.I', name='DCST0', description='dynamic constraint', active=True, parent=None): """ :param exp_t (str): constraint expression time-dependant :param t_range (str): time range defining when the constraint is set :param name (str): name of the constraint :param description (str): description of the constraint :param active (bool): is the constraint activated or deactivated :param parent: unit containing the constraint """ if t_range[0] == ' ': # If the t_range starts with an empty space, it is deleted t_range = t_range[1:] exp = exp_t + ' ' + t_range Constraint.__init__(self, exp, name, description, active, parent) self.exp_t = exp_t self.t_range = t_range
[docs]class ExternalConstraint(Constraint): """ ** Description ** Defining a special type of constraint : the external constraint - This constraint does not translate a physical constraint - This constraint defines an external constraint, which could be relaxed """ def __init__(self, exp, name='ExCST0', description='', active=True, parent=None): Constraint.__init__(self, exp, name, description, active, parent)
[docs] def deactivate_constraint(self): """ An external constraint can be deactivated : - To compare scenarios - To try a less constrained problem """ self.active = False
[docs]class ExtDynConstraint(DynamicConstraint, ExternalConstraint): """ ** Description ** Defining a constraint both external and dynamic (see: DynamicConstraint, ExternalConstraint) """ def __init__(self, exp_t, t_range='for t in time.I', name='EDCST0', active=True, description='Non-physical and dynamic constraint', parent=None): ExternalConstraint.__init__(self, exp='', name=name, description=description, active=active, parent=None) DynamicConstraint.__init__(self, exp_t=exp_t, t_range=t_range, name=name, description=description, active=active, parent=parent)
[docs]class HourlyDynamicConstraint(DynamicConstraint): """ ** Description ** Class that defines an dynamic contraint for a time range Ex : Constraint applying between 7am and 10pm ex_cst = HourlyDynamicConstraint(exp_t, time, init_h=7, final_h=22, name='ex_cst') **Attributes** - name (str) : name of the constraint - exp_t (str) : expression of the constraint - init_h (int) : hour of beginning of the constraint [0-23] - final_h (int) : hour of end of the constraint [1-24] - description (str) : description of the constraint - active (bool) : defines if the constraint is active or not - parent (Unit) : parent of the constraint """ def __init__(self, exp_t, time, init_h: int = 0, final_h: int = 24, name='HDCST0', description='hourly dynamic constraint', active=True, parent=None): if init_h > final_h: raise ValueError('final_h {0} should be greater than init_h ' '{1}'.format(final_h, init_h)) if final_h > 24: raise ValueError('final_h {} should be lower than ' '24'.format(final_h)) index_list = [] # Initializing the list of index # For all days in the time periods, select the hour range for day in time.get_days: starting_date = datetime.datetime(year=day.year, month=day.month, day=day.day, hour=init_h) if final_h == 24: end = day + datetime.timedelta(days=1) else: end = datetime.datetime(year=day.year, month=day.month, day=day.day, hour=final_h) try: index_date = time.get_index_for_date_range( starting_date=starting_date, end=end) index_list += index_date except ValueError: pass # Exception when no values for the last day for instance t_range = 'for t in {}'.format(index_list) DynamicConstraint.__init__(self, exp_t, t_range=t_range, name=name, description=description, active=active, parent=parent)
[docs]class Objective: """ **Description** Class that defines an optimisation objective **Attributes** - name (str) : - description (str) : - active (bool) : - exp (str) : - weight (float) : weighted factor of the objective - parent (unit) - unit (str) : unit of the cost expression .. note:: Make sure that all the modifications on Objectives are made before adding the unit to the OptimisationModel, otherwise, it won't be taken into account """ def __init__(self, exp, name='OBJ0', description='', active=True, weight=1, unit='s.u.', parent=None): self.name = name self.exp = exp self.description = description self.active = active self.weight = weight self.parent = parent self.unit = unit