Commit bf4f03b3 authored by EXT Jaume Fitó's avatar EXT Jaume Fitó
Browse files

Merge remote-tracking branch 'origin/master' into dev_omegalpes_jf

parents 13ebe397 c804d1b1
......@@ -5,6 +5,9 @@ OMEGAlpes Lib for linear energy systems modelling
**OMEGAlpes** aims to be an energy systems modelling tool for linear optimisation (LP, MILP).
A **web interface** is available at https://mhi-srv.g2elab.grenoble-inp.fr/OMEGAlpes-web-front-end/ to generate scripts
We are happy that you will use or develop the OMEGAlpes library.
It is an **Open Source** project located on GitLab at https://gricad-gitlab.univ-grenoble-alpes.fr/omegalpes/omegalpes
......@@ -28,6 +31,16 @@ Some examples and article case studies are avalaible as Notebooks at:
A scientific article presenting OMEGAlpes with a detailed example is available here:
https://hal.archives-ouvertes.fr/hal-02285954v1
**Notebooks :**
Mostly all examples and article case studies are associated to a Notebook.
They can be found in the folder notebooks at :
https://gricad-gitlab.univ-grenoble-alpes.fr/omegalpes/omegalpes_examples
**OMEGAlpes-web interface :**
An web interface is available at https://mhi-srv.g2elab.grenoble-inp.fr/OMEGAlpes-web-front-end/
This interface enable you to generate scripts more easily using the graphical representation detailed
in https://omegalpes.readthedocs.io/en/latest/OMEGAlpes_grah_representation.html
OMEGAlpes' Community
====================
......
......@@ -10,7 +10,8 @@ It aims to be an energy systems modelling tool for linear optimisation (LP, MILP
It is currently based on the LP modeler PuLP.
It is an Open Source project located on GitLab at `OMEGAlpes Gitlab`_
An associated web interface is avalable at `OMEGAlpes interface`_ to help
generate scripts
Contents
--------
......@@ -53,3 +54,4 @@ in the framework of the "Investissements d’avenir” program (ANR-15-IDEX-02)
.. _CDP Eco-SESA: https://ecosesa.univ-grenoble-alpes.fr/
.. _OMEGAlpes Gitlab: https://gricad-gitlab.univ-grenoble-alpes.fr/omegalpes/omegalpes
.. _OMEGAlpes Examples Documentation: https://omegalpes_examples.readthedocs.io/
.. _OMEGAlpes interface: https://mhi-srv.g2elab.grenoble-inp.fr/OMEGAlpes-web-front-end/
\ No newline at end of file
......@@ -8,16 +8,18 @@ Bug fixed
*general.optimisation.model*, as well as requirements.txt, setup.py and
associated docs.
New functionalities
-------------------
Main Folder
Energy
+++++++++++
Category
Units
********
- e_max and e_min parameters now add technical constraints (set_e_min and set_e_max) when set to a value.
- e_tot upper bound is now set as the p_max value times the number of hours over the studied time period. The lower bound is either set to 0, or minus the upper bound if the unit is a storage unit.
- the *add* methods put in place with parameters are now *_add* methods.
- set_e_max constraint changed to set_e_max_period (respectively e_min)
Deprecated
......
......@@ -62,19 +62,14 @@ class ConsumptionUnit(EnergyUnit):
"""
**Description**
Simple Consumption unit
Simple Consumption unit. The parameters and attributes are described
in EnergyUnit parent class. Here, consumption_cost is the cost
associated to the energy consumption of the unit (€/kWh).
**Attributes**
* p : instantaneous power demand (kW)
* p_max : maximal instantaneous power demand (kW)
* p_min : minimal instantaneous power demand (kW)
* energy_type : type of energy ('Electrical', 'Heat', ...)
* consumption_cost : cost associated to the energy consumption
"""
def __init__(self, time, name, p=None, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, co2_out=None, starting_cost=None,
def __init__(self, time, name, p=None, p_min=1e-5, p_max=1e+5, e_min=None,
e_max=None, co2_out=None, starting_cost=None,
consumption_cost=None, min_time_on=None, min_time_off=None,
max_ramp_up=None, max_ramp_down=None, availability_hours=None,
energy_type=None, verbose=True):
......@@ -90,26 +85,32 @@ class ConsumptionUnit(EnergyUnit):
verbose=verbose)
# OBJECTIVES#
def minimize_consumption(self, weight=1):
def minimize_consumption(self, weight=1, pareto=False):
"""
:param weight: Weight coefficient for the objective
:param pareto: if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
"""
self.minimize_energy(weight=weight)
self.minimize_energy(weight=weight, pareto=pareto)
self.min_energy.name = 'min_consumption'
def maximize_consumption(self, weight=1):
def maximize_consumption(self, weight=1, pareto=False):
"""
:param weight: Weight coefficient for the objective
:param pareto: if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
"""
self.minimize_energy(weight=-1 * weight)
self.minimize_energy(weight=-1 * weight, pareto=pareto)
self.min_energy.name = 'max_consumption'
def minimize_consumption_cost(self, weight=1):
def minimize_consumption_cost(self, weight=1, pareto=False):
"""
:param weight: Weight coefficient for the objective
:param pareto: if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
"""
self.minimize_operating_cost(weight)
self.minimize_operating_cost(weight, pareto=pareto)
self.min_operating_cost.name = 'min_consumption_cost'
......@@ -154,8 +155,8 @@ class VariableConsumptionUnit(VariableEnergyUnit, ConsumptionUnit):
"""
def __init__(self, time, name, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, co2_out=None, starting_cost=None,
def __init__(self, time, name, p_min=1e-5, p_max=1e+5, e_min=None,
e_max=None, co2_out=None, starting_cost=None,
operating_cost=None, min_time_on=None, min_time_off=None,
max_ramp_up=None, max_ramp_down=None, energy_type=None,
verbose=True, no_warn=True):
......@@ -186,8 +187,9 @@ class SeveralConsumptionUnit(VariableConsumptionUnit, SeveralEnergyUnit):
"""
def __init__(self, time, name, fixed_cons, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, nb_unit_min=0, nb_unit_max=None, co2_out=None,
def __init__(self, time, name, fixed_cons, p_min=1e-5, p_max=1e+5,
e_min=None,
e_max=None, nb_unit_min=0, nb_unit_max=None, co2_out=None,
starting_cost=None, operating_cost=None, max_ramp_up=None,
max_ramp_down=None, energy_type=None,
verbose=True, no_warn=True):
......@@ -225,8 +227,9 @@ class SeveralImaginaryConsumptionUnit(VariableConsumptionUnit,
"""
def __init__(self, time, name, fixed_cons, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, nb_unit_min=0, nb_unit_max=None, co2_out=None,
def __init__(self, time, name, fixed_cons, p_min=1e-5, p_max=1e+5,
e_min=None,
e_max=None, nb_unit_min=0, nb_unit_max=None, co2_out=None,
starting_cost=None, operating_cost=None, max_ramp_up=None,
max_ramp_down=None, energy_type=None,
verbose=True, no_warn=True):
......
This diff is collapsed.
......@@ -69,21 +69,13 @@ class ProductionUnit(EnergyUnit):
"""
**Description**
Simple Production unit
Simple Production unit. The parameters and attributes are described
in EnergyUnit parent class.
**Attributes**
* co2_out: outside co2 emissions
* starting_cost: the starting cost
* operating_cost: the operating cost
* min_time_on : the minimal operating time
* min_time_off : the minimal non-operating time
* max_ramp_up : the maximal increasing ramp
* max_ramp_down ; the maximal decreasing ramp
"""
def __init__(self, time, name, p=None, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, co2_out=None, starting_cost=None,
def __init__(self, time, name, p=None, p_min=1e-5, p_max=1e+5, e_min=None,
e_max=None, co2_out=None, starting_cost=None,
operating_cost=None, min_time_on=None, min_time_off=None,
max_ramp_up=None, max_ramp_down=None, availability_hours=None,
energy_type=None, verbose=True, no_warn=True):
......@@ -99,20 +91,24 @@ class ProductionUnit(EnergyUnit):
energy_type=energy_type,
verbose=verbose, no_warn=no_warn)
def minimize_production(self, weight=1):
def minimize_production(self, weight=1, pareto=False):
"""
:param weight: Weight coefficient for the objective
:param pareto: if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
"""
self.minimize_energy(weight=weight)
self.minimize_energy(weight=weight, pareto=pareto)
self.min_energy.name = 'min_production'
def maximize_production(self, weight=1):
def maximize_production(self, weight=1, pareto=False):
"""
:param weight: Weight coefficient for the objective
:param pareto: if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
"""
self.minimize_energy(weight=-1 * weight)
self.minimize_energy(weight=-1 * weight, pareto=pareto)
self.min_energy.name = 'max_production'
......@@ -157,8 +153,8 @@ class VariableProductionUnit(VariableEnergyUnit, ProductionUnit):
"""
def __init__(self, time, name, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, co2_out=None, starting_cost=None,
def __init__(self, time, name, p_min=1e-5, p_max=1e+5, e_min=None,
e_max=None, co2_out=None, starting_cost=None,
operating_cost=None, min_time_on=None, min_time_off=None,
max_ramp_up=None, max_ramp_down=None, energy_type=None,
verbose=True, no_warn=True):
......@@ -191,8 +187,9 @@ class SeveralProductionUnit(VariableProductionUnit, SeveralEnergyUnit):
"""
def __init__(self, time, name, fixed_prod, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, nb_unit_min=0, nb_unit_max=None, co2_out=None,
def __init__(self, time, name, fixed_prod, p_min=1e-5, p_max=1e+5,
e_min=None,
e_max=None, nb_unit_min=0, nb_unit_max=None, co2_out=None,
starting_cost=None, operating_cost=None, max_ramp_up=None,
max_ramp_down=None, energy_type=None,
verbose=True, no_warn=True):
......@@ -229,8 +226,9 @@ class SeveralImaginaryProductionUnit(VariableProductionUnit, SeveralEnergyUnit):
"""
def __init__(self, time, name, fixed_prod, p_min=1e-5, p_max=1e+5, e_min=0,
e_max=1e6, nb_unit_min=0, nb_unit_max=None, co2_out=None,
def __init__(self, time, name, fixed_prod, p_min=1e-5, p_max=1e+5,
e_min=None,
e_max=None, nb_unit_min=0, nb_unit_max=None, co2_out=None,
starting_cost=None, operating_cost=None, max_ramp_up=None,
max_ramp_down=None, energy_type=None,
verbose=True, no_warn=True):
......
......@@ -93,7 +93,7 @@ class StorageUnit(VariableEnergyUnit):
pd_min=1e-5, pd_max=1e+5, capacity=None, e_0=None,
e_f=None, soc_min=0, soc_max=1, eff_c=1, eff_d=1,
self_disch=0, self_disch_t=0, ef_is_e0=False, cycles=None,
energy_type=None, e_min=-1e+6, e_max=1e+6):
energy_type=None, e_min=None, e_max=None):
"""
:param time: TimeUnit describing the studied time period
:param name: name of the storage unit
......@@ -353,10 +353,12 @@ class StorageUnit(VariableEnergyUnit):
'hours between cycling (e[t] = e[t+cycles/dt]')
# OBJECTIVES
def minimize_capacity(self, weight=1):
def minimize_capacity(self, weight=1, pareto=False):
"""
:param weight: Weight coefficient for the objective
:param pareto: if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
"""
def_capacity = DefinitionDynamicConstraint(
exp_t='{0}_e[t] <= {0}_capacity'
......@@ -367,6 +369,7 @@ class StorageUnit(VariableEnergyUnit):
min_capacity = Objective(name='min_capacity',
exp='{0}_capacity'.format(self.name),
weight=weight,
pareto=pareto,
parent=self)
setattr(self, 'def_capacity', def_capacity)
......@@ -395,8 +398,8 @@ class ThermoclineStorage(StorageUnit):
def __init__(self, time, name, pc_min=1e-5, pc_max=1e+5,
pd_min=1e-5, pd_max=1e+5,
capacity=None, e_0=None, e_f=None, soc_min=0,
soc_max=1, eff_c=1, eff_d=1, self_disch=0, e_min=-1e6,
e_max=1e6, Tcycl=120, ef_is_e0=False):
soc_max=1, eff_c=1, eff_d=1, self_disch=0, e_min=None,
e_max=None, Tcycl=120, ef_is_e0=False):
"""
:param time: TimeUnit describing the studied time period
:param name: name of the storage unit
......
......@@ -85,6 +85,7 @@ class OptObject:
self._technical_constraints_list = [] # only for technical constraints
self._actor_constraints_list = [] # only for actor's constraints
self._objectives_list = []
self._pareto_objectives_list = []
if verbose:
print(("Creating the {0}.".format(name)))
......
......@@ -820,12 +820,19 @@ class Objective:
**Attributes**
- name (str) :
- exp (str) :
- description (str) :
- active (bool) :
- exp (str) :
- weight (float) : weighted factor of the objective
- parent (unit)
- unit (str) : unit of the cost expression
- pareto (str) : if True, OMEGAlpes calculates a pareto front based on
this objective (two objectives needed)
**Methods**
- _add_objectives_list()
- _add_pareto_objectives_list()
.. note::
Make sure that all the modifications on Objectives are made before
......@@ -835,7 +842,7 @@ class Objective:
"""
def __init__(self, exp, name='OBJ0', description='', active=True, weight=1,
unit='s.u.', parent=None):
unit='s.u.', pareto=False, parent=None):
self.name = name
self.exp = exp
self.description = description
......@@ -843,9 +850,17 @@ class Objective:
self.weight = weight
self.parent = parent
self.unit = unit
self._add_objective_list()
self.pareto = pareto
if pareto:
self._add_pareto_objectives_list()
else:
self._add_objectives_list()
def _add_objective_list(self):
def _add_objectives_list(self):
"""
Add the objectives in the _objectives_list of its parent (normally an
energy_unit or an actor)
"""
if self.parent:
if self not in self.parent._objectives_list:
self.parent._objectives_list.append(self)
......@@ -853,3 +868,12 @@ class Objective:
Warning("the objective {} should have a parent otherwise "
"the objective won't be added to the parent "
"_objectives_list".format(self.name))
def _add_pareto_objectives_list(self):
"""
Add the objectives for the pareto front in the
_pareto_objectives_list of its parent (normally an
energy_unit or an actor)
"""
if self not in self.parent._pareto_objectives_list:
self.parent._pareto_objectives_list.append(self)
......@@ -24,7 +24,10 @@ LP or MILP based on the package PuLP (LpProblem)**
import os
import warnings
import glob
import collections
import time as pytime
import numpy as np
import copy
from pulp import LpProblem, LpStatus, LpVariable, lpSum
from pulp.apis import LpSolver
......@@ -50,16 +53,19 @@ class OptimisationModel(LpProblem):
def __init__(self, time, name='optimisation_model'):
"""
:param name: Name of your optimisation model (str)
:param time:
:param name:
"""
LpProblem.__init__(self, name)
self.verbose = 1
self.noOverlap = False
self.time = time
self.quantities = collections.OrderedDict()
self._model_units_list = []
self._model_quantities_list = []
self._model_constraints_list = []
self._model_objectives_list = []
self._model_pareto_objectives_list = []
def add_nodes(self, *nodes):
"""
......@@ -229,6 +235,10 @@ class OptimisationModel(LpProblem):
q_lb=lb, q_ub=ub, q_opt=opt,
parent=quantity.parent)
# Add the whole variable as an attribute of Variable
# added in the optimization model
self.quantities[new_name] = quantity
def _add_quantity(self, q_name, q_val, q_type, q_lb, q_ub, q_opt,
parent=None):
"""
......@@ -242,16 +252,16 @@ class OptimisationModel(LpProblem):
lb_cst_exp = q_name + '[t] >= ' + str(q_lb) + '[t]'
setattr(parent, 'set_lb', DefinitionDynamicConstraint(
exp_t=lb_cst_exp,
name='set_lb',
parent=parent))
name='set_lb',
parent=parent))
q_lb = min(q_lb)
if isinstance(q_ub, list):
ub_cst_exp = q_name + '[t] <= ' + str(q_ub) + '[t]'
setattr(parent, 'set_ub', DefinitionDynamicConstraint(
exp_t=ub_cst_exp,
name='set_ub',
parent=parent))
name='set_ub',
parent=parent))
q_ub = max(q_ub)
# If the values are stored in a dictionary
......@@ -350,9 +360,58 @@ class OptimisationModel(LpProblem):
if self.verbose:
print('Adding objective : {0}'.format(obj_name))
objective += eval(obj_weight + ' * (' + obj_exp + ')')
if obj.pareto:
self._model_pareto_objectives_list.append(obj)
else:
objective += eval(obj_weight + ' * (' + obj_exp + ')')
self += objective, "obj_tot"
def _pareto_and_solve(self, solver=None, pareto_step=0.1):
"""
Solves the optimization model to produce a pareto front.
"""
pareto_models = []
time = self.time
i = 0
if len(self._model_pareto_objectives_list) == 2:
for alpha in np.arange(0.0, 1.1, pareto_step):
i = i + 1
print("\n - - - - RUN OPTIMIZATION {0} - - - - ".format(i))
pareto_model = None
pareto_model = copy.deepcopy(self)
pareto_model.objective += \
eval(str(alpha) + ' * (' +
pareto_model._model_pareto_objectives_list[
0].exp + ')')
pareto_model.objective += \
eval(str(1 - alpha) + ' * (' +
pareto_model._model_pareto_objectives_list[
1].exp + ')')
print('{0} * {1} + {2} * {3}'.format(
alpha, pareto_model._model_pareto_objectives_list[0].exp,
1 - alpha,
pareto_model._model_pareto_objectives_list[1].exp))
if solver is None:
pareto_model.solve(solver=solver, use_mps=False)
else:
pareto_model.solve(solver=solver)
if LpStatus[pareto_model.status] != 'Optimal':
print('Your problem should have solution before launching'
'the method pareto_solve()')
else:
print("\n- - UPDATE RESULTS IN PARETO MODEL {}"
" - - ".format(i))
pareto_model.update_units()
pareto_models.append(copy.deepcopy(pareto_model))
self.pareto_models = pareto_models
else:
print('Your problem should have only 2 pareto objectives and '
'not {}'.format(len(self._model_pareto_objectives_list)))
def update_units(self):
"""
Updates all units values with optimization results
......@@ -390,48 +449,59 @@ class OptimisationModel(LpProblem):
pass
def solve_and_update(self, solver: LpSolver = None,
find_infeasible_cst_set=False) -> None:
find_infeasible_cst_set=False, pareto_step=0.1) -> \
None:
"""
Solves the optimization model and updates all variables values.
:param solver: Optimization solver
:type solver: LpSolver
:param pareto_step: if there are pareto objectives, you can change
the step to calculate the pareto front
"""
print("\n - - - - - RUN OPTIMIZATION - - - - - ")
if solver is None:
start_time = pytime.time()
self.solve(solver=solver, use_mps=False)
print('Resolution duration =', pytime.time() - start_time,
'seconds.')
else:
self.solve(solver=solver)
if not self._model_pareto_objectives_list:
print("\n - - - - - RUN OPTIMIZATION - - - - - ")
if solver is None:
start_time = pytime.time()
self.solve(solver=solver, use_mps=False)
print('Resolution duration =', pytime.time() - start_time,
'seconds.')
else:
self.solve(solver=solver)
if LpStatus[self.status] == 'Optimal':
print("\n - - - - - UPDATE RESULTS - - - - - ")
self.update_units()
if LpStatus[self.status] == 'Optimal':
print("\n - - - - - UPDATE RESULTS - - - - - ")
self.update_units()
else:
# warnings.warn("Your optimization failed with status : {
# }.".format(
# LpStatus[self.status]))
print("\n\n/!\ Your optimization FAILED with status : {} "
"/!\.".format(
LpStatus[self.status]))
if LpStatus[self.status] == 'Infeasible':
print(
'\nIf you want to catch the source of infeasibility:\n'
'* Please download LPFICS and use the method '
'find_infeasible_constraint_set(your_model)\n'
'You can also use according to your needs:\n'
'- '
'find_definition_and_actor_infeasible_constraints_set('
'your_model)\n'
'- find_definition_and_technical_'
'infeasible_constraints_set(your_model)')
print(
'* If you are a Gurobi user, you can also refer to '
'the '
'method "compute_gurobi_IIS()" in '
'general\optimation\model.')
if find_infeasible_cst_set:
find_infeasible_cst_set
if LpStatus[self.status] == 'Not Solved':
print('You can maybe try with another solver')
else:
# warnings.warn("Your optimization failed with status : {
# }.".format(
# LpStatus[self.status]))
print("\n\n/!\ Your optimization FAILED with status : {} "
"/!\.".format(
LpStatus[self.status]))
if LpStatus[self.status] == 'Infeasible':
print('\nIf you want to catch the source of infeasibility:\n'
'* Please download LPFICS and use the method '
'find_infeasible_constraint_set(your_model)\n'
'You can also use according to your needs:\n'
'- find_definition_and_actor_infeasible_constraints_set('
'your_model)\n'
'- find_definition_and_technical_'
'infeasible_constraints_set(your_model)')
print('* If you are a Gurobi user, you can also refer to the '
'method "compute_gurobi_IIS()" in '
'general\optimation\model.')
if find_infeasible_cst_set:
find_infeasible_cst_set
if LpStatus[self.status] == 'Not Solved':
print('You can maybe try with another solver')
self._pareto_and_solve(solver=solver,
pareto_step=pareto_step)
def get_model_constraints_list(self):
""" Gets constraints of the model """
......
......@@ -4,12 +4,14 @@
"""
**This module includes the following display utils:**
- plot_node_energetic_flows() : enables one to plot the energy flows through
an EnergyNode
- plot_node_energetic_flows() : enables one to plot the energy flows
through an EnergyNode
- plot_energy_mix() : enables one to plot the energy flows connected to a
node
- plot_quantity() : enables one to plot easily a Quantitiy
- plot_quantity_bar() : enables one to plot easily a Quantitiy as a bar
- plot_pareto2D() : enables one to plot a pareto front based on two
quantities
- plot_quantity() : enables one to plot easily a Quantity
- plot_quantity_bar() : enables one to plot easily a Quantity as a bar
- sum_quantities_in_quantity() : enables one to to plot several quantities
in one once the optimisation is done
......@@ -57,7 +59,8 @@ def plot_node_energetic_flows(node):
matplotlib.style.use('seaborn')