Skip to content
Extraits de code Groupes Projets
Valider 8dc98325 rédigé par Aloïs Tavier's avatar Aloïs Tavier
Parcourir les fichiers

r

parent 01e39278
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
Impossible d'afficher diff de source : il est trop volumineux. Options pour résoudre ce problème : voir le blob.
%% Cell type:markdown id:tight-speech tags:
 
## LINMA1702 - Projet
# Utilisation optimale d'une pompe à chaleur domestique
 
### Notebook pour le rapport final - version 2.1
### <font color="red">Numéro du groupe : 36</font>
### <font color="red">Membres du groupe : Yanis Lahrach, Gaspar Robert, Aloïs Tavier, Alexis Thieltgen </font>
 
## Description générale
 
Une pompe à chaleur permet de chauffer un bâtiment en consommant moins d'énergie qu'au chauffage électrique classique, grâce à un coefficient de performance (COP) supérieur à un. Elle peut également fonctionner de façon réversible, c'est-à-dire qu'elle permet de refroidir en été.
 
Dans ce projet, on va utiliser une pompe à chaleur pour maintenir le température intérieur d'un bâtiment dans une plage confortable, tout en minimisant le coût de l'électricité consommée.
 
### Hypothèses et données
- On considère une année entière, qu'on discrétise par intervalles de temps d'une durée de 15 minutes
- Le bâtiment est situé à Montréal, et on dispose de la température extérieure durant chaque intervalle de temps
- On suppose que la température du bâtiment est homogène, et on s'intéressera uniquement à la valeur qu'elle prend toutes les 15 minutes (on ne s'intéresse donc pas à la dynamique de la température au cours d'un intervalle de temps)
- Durant chaque intervalle de temps la température intérieure évolue en fonction la température externe : la différence de température entre le début et la fin d'un intervalle de temps est proportionnel à la différence entre la température externe et la température interne (le coefficient de proportionnalité dépendant de l'isolation du bâtiment)
- Pendant chaque intervalle de temps on peut choisir d'activer la pompe à chaleur. Plus précisément, on peut décider de la puissance qu'on va utiliser pour la pompe à chaleur, jusqu'à une certaine puissance maximale. Celle-ci va alors prélever de la chaleur extérieure et la transférer à l'intérieur du bâtiment (ou l'inverse si on décide de fonctionne en mode refroidissement, nommé "reverse"). La quantité de chaleur transférée est proportionnelle à la puissance électrique consommée, mais aussi au coefficient de performance (COP).
- La variation de la température du bâtiment causée par l'activation de la pompe à chaleur est proportionnelle à la chaleur/énergie transférée
- Le coefficient de performance de la pompe à chaleur est supposé dépendre uniquement de la température extérieure et du mode de fonctionnement, normal ou reverse.
- Le coût unitaire de l'électricité consommée dépend de l'heure où elle est prélevée (tarif bi-horaire)
 
### Remarque à propos de la modélisation
En général, quand on modélise un problème, on décide d'effectuer certaines hypothèses et/ou approximations. Il y a certainement plusieurs façons tout à fait valides de modéliser le problème, donc pas pas forcément une unique bonne réponse. Vous pouvez interpréter l'énoncé de la façon qui vous convient le mieux du moment qu'elle reste raisonnable.
(par exemple : l'énoncé suggère de ne pas analyser/de prendre en compte ce qui se passe à l'intérieur d'un intervalle de temps, ce qui est un choix ; aussi : le fonctionnement simultané en mode chauffage et reverse pourrait être a priori permis ou interdit, mais cela change-t-il vraiment les choses ?)
 
%% Cell type:markdown id:loved-savings tags:
 
## Tâches
 
**Tâche 1** : on souhaite dans un premier temps que la température du bâtiment reste comprise dans une certaine plage admissible de températures, et on cherche à **minimiser le coût total de l'électricité consommée par la pompe à chaleur**. Formulez ce problème comme un problème d'optimisation linéaire, puis résolvez le.
 
Pour des raisons de temps de calcul, votre modèle considérera uniquement une période de 7 jours consécutifs. Il fera l'hypothèse que la température initiale au début de la période est égale à la valeur centrale de la plage admissible, et fera en sorte que la température finale à la fin de la période revienne à la même valeur. Votre code prendra donc en entrée un paramètre indiquant le numéro de l'intervalle de temps qui début la période, qui s'étendra sur $7 \times 24 \times 4 = 672$ intervalles de temps.
 
<div class="alert alert-block alert-warning"><b>A mentionner</b> :<br>
- coût minimal + graphique de l'évolution des températures + graphique représentant l'utilisation de la pompe à chaleur (en distinguant le fonctionnement normal du fonctionnement _reverse_) + temps de calcul + bref commentaire (maximum 4 lignes)<br>
- pour deux périodes distinctes (placer les résultats côté à côté) : à gauche une période pré-déterminée (cf. fichier de données), et à droite une seconde période que vous choisirez en fonction de son intérêt
</div>
 
---
 
%% Cell type:markdown id:892c7f7b tags:
 
La pompe à chaleur constitue une solution performante et économe en énergie pour le chauffage d'un bâtiment. Son COP supérieur à un la distingue avantageusement des systèmes de chauffage classiques. Par ailleurs, elle peut être utilisée en mode réversible pour rafraîchir le bâtiment en période estivale. Dans le cadre de ce projet, nous avons recours à une pompe à chaleur pour maintenir la température intérieure du bâtiment dans une plage de confort tout en réduisant la consommation électrique. Pour cela, nous considérerons une année complète que nous discréditons en intervalles de temps de 15 minutes. Le bâtiment est situé à Montréal, et nous disposons de données de température extérieure pour chaque intervalle de temps.
 
%% Cell type:markdown id:79b302b5 tags:
 
### Importation des modules
 
%% Cell type:code id:08fb3b94 tags:
 
``` python
import numpy as np # Module de manipulation de listes
import cvxpy as cp # Solver d'optimisation convexe
import matplotlib.pyplot as plt # Module de création de graphes
```
 
%% Cell type:markdown id:71cacb8c tags:
 
### Déclaration des variables
 
%% Cell type:markdown id:ff08fda0 tags:
 
##### Intervalles
 
%% Cell type:code id:30d3cb31 tags:
 
``` python
heure_initiale = 22.5 # Compris dans l'intervalle [0,24[ [h]
intervalle_initial = 13050
n = 672 # Nombre de périodes/intervalles
data = np.load("Temperatures-Montreal.npy")
T_ext = data[intervalle_initial:intervalle_initial+n]
```
 
%% Cell type:markdown id:72f2ef61 tags:
 
##### Coefficient de performance
 
%% Cell type:markdown id:17a74ae8 tags:
 
Nous posons l’hypothèse que le coefficient de performance de la pompe à chaleur dépend uniquement de la température extérieure et du mode de fonctionnement, normal ou reverse. En mode normal, le COP est fonction de la température extérieure. Tandis qu’en reverse, c’est une constante égale à 3,2.
 
%% Cell type:code id:6f3f138a tags:
 
``` python
COP_normal = lambda f: 3+10*abs(np.tanh(f/100))*np.tanh(f/100)
COP_reverse = 3.2
```
 
%% Cell type:markdown id:2173f762 tags:
 
##### Température [°C]
 
%% Cell type:markdown id:edd45834 tags:
 
Nous supposons que la température à l'intérieur du bâtiment est uniforme, et nous nous concentrons uniquement sur sa valeur toutes les 15 minutes. Au cours de chaque intervalle de temps, la température intérieure évolue en fonction de la température extérieure : la variation de température entre le début et la fin de l'intervalle est proportionnelle à la différence de température entre l'intérieur et l'extérieur (le coefficient de proportionnalité dépendant de l'isolation du bâtiment). Pour $n$ intervalles, il y a $n+1$ températures à prendre en compte. Les températures initiale ($T_0$) et finale ($T_n$) sont toutes deux fixées à 20°C, tandis que la plage de températures de confort est connue et comprise entre 19°C et 21°C.
 
%% Cell type:code id:55614aa7 tags:
 
``` python
T_initial = 20 # [°C]
T_final = 20 # [°C]
T_min = 19 # [°C]
T_max = 21 # [°C]
eta = 0.99 # Isolation du bâtiment [/]
T_i = cp.Variable(n+1)
deltaT_i = cp.Variable(n)
```
 
%% Cell type:markdown id:d3dbd195 tags:
 
##### Coût de l'énergie [$]
 
%% Cell type:markdown id:5f418ea2 tags:
 
Le coût unitaire de l'électricité consommée dépend de l'heure où elle est prélevée selon tarif bi-horaire. Ce prix est plus élevé durant les heures pleines de 7h à 22h.
 
%% Cell type:code id:2aa90a1b tags:
 
``` python
cout_heures_creuses = 0.00018 # [$/(Wh*h)]
cout_heures_pleines = 0.00026 # [$/(Wh*h)]
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
```
 
%% Cell type:markdown id:7188a70b tags:
 
##### Puissance [W]
 
%% Cell type:markdown id:1ff5ea64 tags:
 
Pendant chaque intervalle de temps on peut choisir d'activer la pompe à chaleur en décidant de la puissance qu'on lui fournit jusqu'à une valeur
maximale. La pompe préleve alors de la chaleur extérieure et la transférer à l'intérieur du bâtiment (ou
l'inverse si on décide de fonctionner en mode reverse). La quantité de chaleur
transférée est proportionnelle à la puissance électrique consommée et du coefficient de
performance (COP) mais est inversément proportionnelle à la capacité calorifique du bâtiment et à son volume.
On permet le mode normal et le mode reverse simultané. Cela ne posera pas de problème étant donné que la minimisation du coût fournira naturellement une solution dans laquelle maximum un des deux modes est utilisé. En effet, une utilisation simultanée des deux modes est contre-productive car la pompe consommerait alors de l'énergie pour fournir deux variations de température opposées.
 
%% Cell type:code id:fa2e9678 tags:
 
``` python
p_max = 1000 # [W]
Cx = 1000/(0.4*360) # Capacité calorifique du bâtiment [Wh/(°C*m^3)]
V = 360 # Volume du bâtiment [m^3]
p_n_i = cp.Variable(n) # Normal
p_r_i = cp.Variable(n) # Reverse
```
 
%% Cell type:markdown id:57be2a54 tags:
 
<div class="alert alert-block alert-info"><b>Question 1.1</b> :<br>
Donnez votre formulation linéaire, en commentant brièvement (en particulier si vous utilisez une technique de modélisation/reformulation).
</div>
 
%% Cell type:markdown id:6d63023d tags:
 
Dans un premier temps, on impose que la température du bâtiment est comprise dans la plage de confort à tout instant, et on cherche alors à minimiser le coût total de l'électricité consommée par la pompe à chaleur. On peut modéliser le problème de minimisation du coût comme suit :
 
On peut modéliser le problème de minimisation du coût comme suit :
$$ \min_{p_{n_i}, p_{r_i}, T_i, \Delta T_i} \sum_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i}) \quad \text{tel que}\\ $$
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_0=T_{initial}\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_n=T_{final}\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_{min} \le T_i \le T_{max} \qquad ,\forall i \in [0,n]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{n_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{r_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_{i+1}=T_i+\Delta T_i \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad p_{n_i}+p_{r_i} \le p_{max} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \Delta T_i = -(1-\eta)(T_i-T_{ext_i}) + \frac{0,25p_{n_i}COP_n(T_{ext_i})}{C_xV} - \frac{0,25p_{r_i}COP_r}{C_xV} \qquad ,\forall i \in [0,n-1]\\ $
 
Cette formulation à l'avantage de pouvoir se passer de variables binaires qui imposeraient que la pompe est soit en mode normal, soit en mode reverse. Comme dit précédemment, le problème de l'utilisation simultanée de la pompe dans ses deux modes ne se produit même pas car le solver privilégie évidemment une utilisation unidirectionnelle de la pompe à chaleur (voir Question 1.4).
 
%% Cell type:markdown id:6e4c0afb tags:
 
<div class="alert alert-block alert-info"><b>Question 1.2</b> :<br>
Résolvez votre modèle sur les deux intervalles de temps, affichez vos résultats sous forme graphique et commentez.
</div>
 
%% Cell type:markdown id:2c46fc8e tags:
 
### Intervalle 13050
Cet intervalle nous est imposé.
 
%% Cell type:markdown id:4ff6e898 tags:
 
##### Résolution du problème
 
%% Cell type:code id:922de5b4 tags:
 
``` python
objectif = cp.Minimize(c.T@(p_n_i+p_r_i))
contraintes = [T_i[0] == T_initial, T_i[n] == T_final, T_min <= T_i, T_i <= T_max, T_i[1:n+1] == T_i[0:n]+deltaT_i, (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx)]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"})
```
 
%% Output
 
8.182333695034458
 
%% Cell type:markdown id:88f0fe15 tags:
 
##### Affichage de la solution
 
%% Cell type:code id:86342dc9 tags:
 
``` python
fig1, axs1 = plt.subplots(2, 2, figsize=(14, 4))
 
fig1.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs1[0,0].plot(np.arange(n), T_ext, color='red',alpha=0.4)
axs1[0,0].set_title("Température extérieure à Montréal (°C)")
axs1[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,0].grid('on', alpha=0.3)
 
axs1[0,1].plot(np.arange(n+1), T_i.value, color='red',alpha=0.4)
axs1[0,1].set_title("Température à l'intérieur du bâtiment (°C)")
axs1[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,1].grid('on', alpha=0.3)
 
axs1[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan',alpha=0.4)
axs1[1,0].bar(np.arange(n), p_n_i.value, color='turquoise',alpha=0.4)
axs1[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs1[1,0].set_ylim(-30,1030)
axs1[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,0].grid('on', alpha=0.3)
 
axs1[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs1[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs1[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs1[1,1].set_ylim(-30,1030)
axs1[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,1].grid('on', alpha=0.3)
 
title1 = fig1.suptitle("Graphes de la solution optimale (Coût optimal={})".format(objectif.value),y=1.05)
title1.set_fontsize(15)
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:2464e1b7 tags:
 
### Intervalle 22504
 
%% Cell type:markdown id:ee2956e1 tags:
 
Nous choisissons cet intervalle car le premier contient principalement des températures inférieures à 20°C. L'intervalle choisi contient des températures plus estivales dépassant parfois les 25°C. Nous allons pouvoir observer les différences de comportement de la pompe pour les deux intervalles.
 
%% Cell type:markdown id:ff637cd4 tags:
 
##### Résolution du problème
 
%% Cell type:code id:3c2a6c3e tags:
 
``` python
# Modification de l'intervalle.
intervalle_initial = 22504
heure_initiale = 0
T_ext = data[intervalle_initial:intervalle_initial+n]
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
objectif = cp.Minimize(c.T@(p_n_i+p_r_i))
contraintes = [T_i[0] == T_initial, T_i[n] == T_final, T_min <= T_i, T_i <= T_max, T_i[1:n+1] == T_i[0:n]+deltaT_i, (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx)]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"}, verbose=False)
```
 
%% Output
 
1.9898040404466006
 
%% Cell type:markdown id:4d2a8259 tags:
 
##### Affichage de la solution
 
%% Cell type:code id:cefbc06c tags:
 
``` python
fig1, axs1 = plt.subplots(2, 2, figsize=(14, 4))
 
fig1.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs1[0,0].plot(np.arange(n), T_ext, color='red',alpha=0.4)
axs1[0,0].set_title("Température extérieure à Montréal (°C)")
axs1[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,0].grid('on', alpha=0.3)
 
axs1[0,1].plot(np.arange(n+1), T_i.value, color='red',alpha=0.4)
axs1[0,1].set_title("Température à l'intérieur du bâtiment (°C)")
axs1[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,1].grid('on', alpha=0.3)
 
axs1[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan',alpha=0.4)
axs1[1,0].bar(np.arange(n), p_n_i.value, color='turquoise',alpha=0.4)
axs1[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs1[1,0].set_ylim(-30,1030)
axs1[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,0].grid('on', alpha=0.3)
 
axs1[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs1[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs1[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs1[1,1].set_ylim(-30,1030)
axs1[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,1].grid('on', alpha=0.3)
 
title1 = fig1.suptitle("Graphes de la solution optimale (Coût optimal={})".format(objectif.value),y=1.05)
title1.set_fontsize(15)
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:a9f2839f tags:
 
### Commentaires
 
%% Cell type:markdown id:2c111601 tags:
 
L'objectif était de minimiser le coût total de l'électricité consommée par la pompe à chaleur tout en maintenant le bâtiment dans une certaine plage de températures. Le coût minimum observé est de 8.182\\$ pour la semaine commençant au 13050ème intervalle (milieu de la 19ème semaine de l'année) et 1.990\\$ pour celle commençant au 22504ème intervalle (début de la 37ème semaine). Notre code s'exécute en respectivement 0.0419 et 0.0387 secondes pour les deux semaines testées.
 
Comme attendu, le coût est plus important quand les températures sont plus basses et, de plus, nous remarquons dans l'analyse des graphiques que la puissance totale de la pompe réagit directement avec les variations de l'extérieur. On observe sept pics de puissance dûs aux passages au tarif des heures creuses. L'utilisation de la pompe est privilégiée durant ces heures-ci. De ces pics, les plus gros surviennent durant les périodes de températures extérieures les plus élevées. A l'inverse, l'unique utilisation de la pompe en mode normal survient quand la température extérieure est la plus basse. On voit aussi que les deux modes de la pompe ne sont jamais activés simultanément.
 
%% Cell type:markdown id:dcd4fd17 tags:
 
<div class="alert alert-block alert-info"><b>Question 1.3</b> :<br>
A partir de certaines informations fournies par le solver (et donc sans effectuer de nouveau calcul) et de la théorie vue au cours, prédisez l'effet sur le coût optimal d'une diminution de la tempéature minimale admissible Tmin. Faites de même pour une augmentation de la température maximale admissible Tmax.
Votre prédiction consiste en un formule pour le coût optimal en fonction des deux variations de température Tmin et Tmax. Commentez cette prédiction (en particulier : est-elle valide pour n'importe quelle variation des températures ?).
</div>
 
%% Cell type:markdown id:b4bf2cbb tags:
 
D'une part, en relaxant les contraintes sur la plage de température admissible, on s'attend à observer une diminution de la fonction coût. D'autre part, sur base de la théorie de l'analyse post-optimale, si on modifie le vecteur des ressources de $b$ à $b+\Delta b$, le sommet dual $y^{*}$ reste inchangé et tant que le condition d'admissibilité, $B^{-1}(b+\Delta b)\ge 0$, demeure sastifaite, le coût optimal augmente de $y^{*T} \Delta b$. Dans notre situation, soit $b=(T_{min},T_{max})$ les contraintes sur la plage de température, soit $y^{*}$ le vecteur des valeurs du sommet dual issue de ces contraintes et soit $\Delta b=(-\delta_{min},-\delta_{max})$ la variation sur la plage de température (on définit ici $\delta \ge 0$). En effet :
 
Pour la diminution de $T_{min}$ : $T_i\ge T_{min}-\delta_{min}$
 
Pour l'augmentation de $T_{max}$ : $T_i\le T_{max}+\delta_{max}$ soit $-T_i\ge-T_{max}-\delta_{max}$
 
La fonction coût diminue alors selon :
$\Delta z = y^{*T}.(-\delta_{min};-\delta_{max})$
 
Le problème se présentant ici sous forme géométrique, $y^{*T}\ge0$. On assiste bien, conformément à nos attentes, à une diminution du coût.
 
Le solveur nous fournit $y^{*T}$ via les commandes "probleme.constraints[2].dual_value" et "probleme.constraints[3].dual_value". Par contre, ne possédant pas d'informations supplémentaires sur $B^{-1}$, on peut seulement supposer que ce résulat fournit une valeur exacte pour des petites modifications $\Delta b$ telle que la condition d'admissibilté est satisfaite. Pour des modifications plus importantes, ces résultats présentent tout de même une approximation de la fonction coût, quoique inférieure à la réalité.
 
%% Cell type:markdown id:dd5e6cd6 tags:
 
<div class="alert alert-block alert-info"><b>Question 1.4</b> :<br>
Démontrez que, dans toute solution optimale de ce modèle, l'activation simultanée du chauffage et du mode reverse durant la même période de temps est impossible.
</div>
 
%% Cell type:markdown id:c7f40ff2 tags:
 
Repartons de la formulation de la différence de température pour un intervalle donné :
 
$\Delta T = -(1-\eta)(T-T_{ext}) + \frac{0,25p_{n}COP_n(T_{ext})}{C_xV} - \frac{0,25p_{r}COP_r}{C_xV} = -(1-\eta)(T-T_{ext}) + \frac{0,25}{C_xV}(p_{n}COP_n - p_{r}COP_r) \quad \quad (*)$
 
Cette expression avec $p_{n_i} \ne 0$ et $p_{r_i} \ne 0$ est celle qui s'applique lorsque la pompe est utilisée simultanément en mode normal et en mode reverse.
<br/>
<br/>
<br/>
A présent, interrogeons-nous sur ce qu'il se passe lorsque l'on veut atteindre ce même différentiel de température avec uniquement le mode normal, càd avec uniquement une puissance $p'_{n}$. On doit avoir :
 
$\Delta T = -(1-\eta)(T-T_{ext}) + \frac{0,25p_{n}COP_n(T_{ext})}{C_xV} - \frac{0,25p_{r}COP_r}{C_xV} = -(1-\eta)(T-T_{ext}) + \frac{0,25p'_{n}COP_n(T_{ext})}{C_xV}$, avec $p'_{n} = p_{n} - p_{r}\frac{COP_r}{COP_n}$
 
Remarquons d'ailleurs que $p'_{n} \gt 0 \implies p_{n}COP_n \gt p_{r}COP_r$, ce qui implique bien par la formule (*) ci-dessus que l'on veut faire monter la température.
<br/>
<br/>
<br/>
De même, on peut regarder ce qu'il se passe lorsque l'on veut atteindre ce même différentiel de température avec uniquement le mode reverse, càd avec uniquement une puissance $p'_{r}$. On doit avoir :
 
$\Delta T = -(1-\eta)(T-T_{ext}) + \frac{0,25p_{n}COP_n(T_{ext})}{C_xV} - \frac{0,25p_{r}COP_r}{C_xV} = -(1-\eta)(T-T_{ext}) - \frac{0,25p'_{r}COP_n(T_{ext})}{C_xV}$, avec $p'_{r} = p_{r} - p_{n}\frac{COP_n}{COP_r}$
 
Il s'agit bien d'une baisse de la température car on a cette fois $p'_{r} \gt 0 \implies p_{r}COP_r \gt p_{n}COP_n$. $\quad p'_{r}$ et $p'_{n}$ ne peuvent ainsi pas être strictement positifs en même temps.
<br/>
<br/>
<br/>
Qu'en est-il du coût, durant un intervalle, avec respectivement : les 2 modes activés, seulement le mode normal, seulement le mode reverse ?
- 2 modes : $C_{tot} = c(p_n+p_r) = cp_n + cp_r$
- 1 mode (normal) : $C_{tot} = cp'_n = c(p_{n} - p_{r}\frac{COP_r}{COP_n}) = cp_n - cp_r\frac{COP_r}{COP_n}$
- 1 mode (reverse) : $C_{tot} = cp'_r = c(p_{r} - p_{n}\frac{COP_n}{COP_r}) = cp_r - cp_n\frac{COP_n}{COP_r}$
 
 
On voit donc, comme toutes les variables sont positives, que le coût avec un seul mode est systématiquement plus bas que celui pour deux modes. Comme le solveur tente de minimiser le coût sur chaque intervalle, le chauffage ne sera jamais allumé simultanément avec ses deux modes.
 
%% Cell type:markdown id:af08cae2 tags:
 
<div class="alert alert-block alert-info"><b>Question 1.5</b> :<br>
Modifiez votre modèle de façon à tenir compte des deux nouvelles contraintes suivantes :<br>
- si la pompe à chaleur est utilisée (dans un mode ou dans l'autre), elle l'est au moins à 25% de sa puissance maximale. Il n'est donc plus possible d'utiliser la pompe à chaleur à très faible puissance.
<br>
- si on décide d'allumer (ou d'éteindre) la pompe à chaleur, elle reste allumée (ou éteinte) sur une période de x heures consécutives. Ces périodes sont fixes : par exemple, si x=4h, il s'agit de [0h-4h], [4h-8h], [8h-12h], [12h-16h], etc. pour chaque journée.<br>
Le nouveau modèle sera toujours obligatoirement linéaire, mais pourra faire appel à des variables discrètes.
Donnez votre formulation, et commentez brièvement.
</div>
 
%% Cell type:markdown id:a85f083d tags:
 
Nous reprenons une modélisation très proche de celle d'auparavant tout en ajoutant et modifiant certaines contraintes.
 
On peut modéliser le problème de minimisation du coût comme suit :
$$ \min_{p_{n_i}, p_{r_i}, T_i, \Delta T_i, x_i} \sum_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i}) \quad \text{tel que}\\ $$
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_0=T_{initial}\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_n=T_{final}\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_{min} \le T_i \le T_{max} \qquad ,\forall i \in [0,n]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_{i+1}=T_i+\Delta T_i \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{n_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad\qquad 0 \le p_{n_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{r_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad p_{n_i}+p_{r_i} \le x_{i//4h} \cdot p_{max} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0.25\cdot x_{i//4h} \cdot p_{max} \le p_{n_i}+p_{r_i}\qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \Delta T_i = -(1-\eta)(T_i-T_{ext_i}) + \frac{0,25p_{n_i}COP_n(T_{ext_i})}{C_xV} - \frac{0,25p_{r_i}COP_r}{C_xV} \qquad ,\forall i \in [0,n-1]\\ $
$\textbf{Remarque :}$ "//" représente la division entière.<p>
Avec cette formulation mixte (i.e. combinant des variables discrètes binaires et des variables continues), nous utilisons des variables binaires ($ x_{i} $) où chaque variable définit pour $h$ heures ($4h$ intervalles) si la pompe est allumée ($ x_{i} = 1 $) où non ($ x_{i} = 0 $). La variables $x_i$ est donc commune pour $4h$ intervalles. Lorsqu'elle vaut 0, la variable $x_i$ force les puissances $p_{n_i}$ et $p_{r_i}$ à 0, tandis que lorsqu'elle vaut 1, la contrainte sur les puissances se résume à $0.25 p_{max} \le p_{n_i}+p_{r_i} \le p_{max}$. Au vu de la consigne, il n'y a pas lieu d'interdire l'utilisation simultanée des deux modes de la pompe.</p>
Avec cette formulation mixte (i.e. combinant des variables discrètes binaires et des variables continues), nous utilisons des variables binaires ($ x_{i} $) où chaque variable définit pour $h$ heures ($4h$ intervalles) si la pompe est allumée ($ x_{i} = 1 $) où non ($ x_{i} = 0 $). La variables $x_i$ est donc commune pour $4h$ intervalles. Lorsqu'elle vaut 0, la variable $x_i$ force les puissances $p_{n_i}$ et $p_{r_i}$ à 0, tandis que lorsqu'elle vaut 1, la contrainte sur les puissances se résume à $0.25 p_{max} \le p_{n_i}+p_{r_i} \le p_{max}$. Au vu de la consigne, il n'y a pas lieu d'interdire l'utilisation simultanée des deux modes de la pompe, elle utilisera donc toujours au moins 25% de sa puissance mais ces 25% de puissance pourront être réparti entre les deux modes si besoin.</p>
 
%% Cell type:markdown id:7aa5fe9a tags:
 
<div class="alert alert-block alert-info"><b>Question 1.6</b> :<br>
Résolvez ce nouveau modèle, affichez les résultats et commentez (en particulier le temps de calcul). Choissisez d'abord une valeur x=4h, puis x=2h.
</div>
 
%% Cell type:code id:9fbb0da2 tags:
 
``` python
Duree = 4 # [h]
x_i = cp.Variable(n//(int(4*Duree)), boolean=True) #Allumage
 
# Modification de l'intervalle.
intervalle_initial = 13050
heure_initiale = 22.5
T_ext = data[intervalle_initial:intervalle_initial+n]
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
# Formulation et résolution du problème
objectif = cp.Minimize(c.T@(p_n_i+p_r_i))
contraintes = [T_i[0] == T_initial, T_i[n] == T_final, T_min <= T_i, T_i <= T_max, T_i[1:n+1] == T_i[0:n]+deltaT_i, p_n_i>=0, p_r_i>=0,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx)]
 
for i in range(n):
contraintes.append((p_n_i[i]+p_r_i[i])<=p_max*x_i[i//(int(4*Duree))])
contraintes.append((p_n_i[i]+p_r_i[i])>=0.25*p_max*x_i[i//(int(4*Duree))])
 
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"}, verbose=False)
```
 
%% Output
 
8.68944516654463
 
%% Cell type:code id:0f482be2 tags:
 
``` python
fig,axs = plt.subplots(2, 2, figsize=(14,8))
 
#axs[0, 0].plot(np.arange(n), T_ext)
#axs[0, 0].set_title("Température extérieure à Montréal")
fig.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs[0,0].plot(np.arange(n+1), T_i.value, color='red',alpha=0.4)
axs[0,0].set_title("Température à l'intérieur du bâtiment (°C)")
axs[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs[0,0].grid('on', alpha=0.3)
 
axs[0,1].plot(np.arange(n), p_n_i.value+p_r_i.value,color='darkcyan',alpha=0.4)
axs[0,1].bar(np.arange(n), p_n_i.value+p_r_i.value,color='turquoise',alpha=0.4)
axs[0,1].set_title("Puissance totale de la pompe (W)")
axs[0,1].set_ylim(-30,1030)
axs[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs[0,1].grid('on', alpha=0.3)
 
axs[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan',alpha=0.4)
axs[1,0].bar(np.arange(n), p_n_i.value, color='turquoise',alpha=0.4)
axs[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs[1,0].set_ylim(-30,1030)
axs[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs[1,0].grid('on', alpha=0.3)
 
axs[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs[1,1].set_ylim(-30,1030)
axs[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs[1,1].grid('on', alpha=0.3)
 
fig.suptitle("Graphes de la solution optimale pour x=2h (Coût optimal={})".format(objectif.value))
 
plt.show()
```
 
%% Output
 
 
%% Cell type:code id:4189d852 tags:
 
``` python
Duree = 2 # [h]
x_i = cp.Variable(n//(int(4*Duree)), boolean=True) #Allumage
 
# Formulation et résolution du problème
objectif = cp.Minimize(c.T@(p_n_i+p_r_i))
contraintes = [T_i[0] == T_initial, T_i[n] == T_final, T_min <= T_i, T_i <= T_max, T_i[1:n+1] == T_i[0:n]+deltaT_i, p_n_i>=0, p_r_i>=0,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx)]
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
for i in range(n):
contraintes.append((p_n_i[i]+p_r_i[i])<=p_max*x_i[i//(int(4*Duree))])
contraintes.append((p_n_i[i]+p_r_i[i])>=0.25*p_max*x_i[i//(int(4*Duree))])
 
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"}, verbose=False)
```
 
%% Output
 
8.394198342520985
 
%% Cell type:code id:1d05bc1e tags:
 
``` python
fig,axs = plt.subplots(2, 2, figsize=(14,8))
 
#axs[0, 0].plot(np.arange(n), T_ext)
#axs[0, 0].set_title("Température extérieure à Montréal")
fig.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs[0,0].plot(np.arange(n+1), T_i.value, color='red',alpha=0.4)
axs[0,0].set_title("Température à l'intérieur du bâtiment (°C)")
axs[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs[0,0].grid('on', alpha=0.3)
 
axs[0,1].plot(np.arange(n), p_n_i.value+p_r_i.value,color='darkcyan',alpha=0.4)
axs[0,1].bar(np.arange(n), p_n_i.value+p_r_i.value,color='turquoise',alpha=0.4)
axs[0,1].set_title("Puissance totale de la pompe (W)")
axs[0,1].set_ylim(-30,1030)
axs[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs[0,1].grid('on', alpha=0.3)
 
axs[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan',alpha=0.4)
axs[1,0].bar(np.arange(n), p_n_i.value, color='turquoise',alpha=0.4)
axs[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs[1,0].set_ylim(-30,1030)
axs[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs[1,0].grid('on', alpha=0.3)
 
axs[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs[1,1].set_ylim(-30,1030)
axs[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs[1,1].grid('on', alpha=0.3)
 
fig.suptitle("Graphes de la solution optimale pour x=2h (Coût optimal={})".format(objectif.value))
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:348d4df8 tags:
 
Rappelons-nous de la solution optimale du problème d'origine qui était de 8.182\\$. En imposant comme contrainte que la pompe doit rester allumée ou éteinte quatre heures consécutives nous observons un coût de 8.689\\$ (ce qui représente une hausse de 6.2%) et en imposant cette fois ci qu'elle doit rester allumée ou éteinte à chaque fois durant deux heures consécutives nous observons un prix de 8.394 $ (ce qui représente ne hausse de 2.6%). Ces résultats correspondent aux attentes, la contrainte x=2 est plus restrictive que le problème que le problème de départ et celle de x=4 l'est plus encore. Il est donc normal de rencontrer une augmentation du prix comme il sera plus compliqué de répondre aux contraintes.
Rappelons-nous de la solution optimale du problème d'origine qui était de 8.182\\$. En imposant comme contrainte que la pompe doit rester allumée ou éteinte quatre heures consécutives nous observons un coût de 8.689\\$ (ce qui représente une hausse de 6.2% du coût) et en imposant cette fois ci qu'elle doit rester allumée ou éteinte à chaque fois durant deux heures consécutives nous observons un prix de 8.394 $ (ce qui représente ne hausse de 2.6% du coût). Ces résultats correspondent aux attentes, la contrainte x=2 est plus restrictive que le problème que le problème de départ et celle de x=4 l'est plus encore. Il est donc normal de rencontrer une augmentation du prix comme il sera plus compliqué de répondre aux contraintes.
 
Pour le temps d'exécution, la solution optimale est obtenue en 0.091 seconde, 2.7 secondes, 7.8 secondes respectivement pour le problème initial, le problème avec x=4h et le problème avec x=2h. Les deux derniers problèmes ajoutent un grand nombre de contraintes (2*672) et de variables, ce qui augmente les calculs nécessaires pour atteindre l'optimum. Pour les variables nous avons respectivement 42 et 84 variables de plus pour le problème avec x=4 et le problème avec x=2. Le second cas comprend significativement plus de variables ce qui explique également le temps supplémentaire pris.
 
%% Cell type:markdown id:8fcc662f tags:
 
<div class="alert alert-block alert-info"><b>Question 1.7</b> :<br>
Décrivez comment on pourrait apporter les modifications suivantes au nouveau modèle de la Question 1.5, sans les implémenter : <br>
(a) tenir compte d'un coût fixe supplémentaire à payer pour chaque intervalle de temps où la pompe à chaleur est utilisée<br>
(b) empêcher le nombre total d'allumages de la pompe à chaleur à ne pas dépasser une certaine valeur maximale (un allumage = passage de l'état 'éteint' lors d'un invervalle de temps à l'état 'allumé' lors de l'intervalle de temps suivant)<br>
(c) dans ce nouveau modèle il n'est plus nécessairement impossible d'observer dans une solution optimale l'activation simultanée du chauffage et du mode reverse au cours du même intervalle de temps : expliquez pourquoi, et proposez une contrainte permettant d'éliminer cette possibilité d'activation simultanée.
</div>
 
%% Cell type:markdown id:86e20172 tags:
 
### (a) Coût fixe supplémentaire
 
%% Cell type:markdown id:bf51cba6 tags:
 
On va utiliser le "big M". Il est nécessaire d'ajouter la contrainte suivante :
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad p_{n_i}+p_{r_i} \le My_i\\ $
et de modifier la fonction objectif de la sorte :
$$ \min_{p_{n_i}, p_{r_i}, T_i, \Delta T_i, y_i} \sum_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i}) + y_if $$
où $y_i$ est une variable binaire valant 0 ou 1, $M$ est une constante relativement élevée devant $p_{n_i}$ et $p_{r_i}$ et où $f$ représente le coût fixe supplémentaire.$\\$
Ainsi, lorsque $p_{n_i}$ et $p_{r_i}$ sont nuls, $y_i$ l'est également pour minimiser la fonction objectif et cela n'engendre ainsi aucun coût supplémentaire. A l'inverse, si $p_{n_i}$ ou $p_{r_i}$ prennent des valeurs strictement positives, c'est-à-dire lorsque la pompe est utilisée, $y_i$ est forcée de prendre la valeur 1 pour ne pas violer cette nouvelle contrainte, entraînant de la sorte un coût fixe supplémentaire $f$.
 
%% Cell type:markdown id:0048810c tags:
 
### (b) Nombre d'allumages limité
 
%% Cell type:markdown id:99ecd372 tags:
 
(b) Reprenons à présent les variables d'activation $x_i$ de la question 1.5. Un passage d'un état "éteint" à l'état "allumé" s'exprime lorsque $x_i$ - $x_{i-1}$ = 1. A l'inverse, un passage d'un état "allumé" à l'état "éteint" s'exprime lorsque $x_i$ - $x_{i-1}$ = -1. Nous pouvons exprimer cette condition sous cette forme : $$\sum_{i=1}^{n/4h} \left| x_i - x_{i-1} \right| \le 2A $$ où $n$ est le nombre total d'intervalles, $h$ est la durée (en heure) definie à la question 1.5, et $A$ représente le nombre total d'allumages autorisé. Nous avons ici une somme de valeurs absolues afin d'éviter l'annulation de proche en proche d'un allumage suivi d'un éteignage. Nous avons donc que cette somme doit être inférieure ou égale à $2A$ car un allumage est toujours suivi d'un éteignage (sauf éventuellement pour le dernier). On peut remplacer la valeur absolue en exprimant cette condition sous forme linéaire. On introduit pour cela une nouvelle variable $a_i$ ainsi que deux nouvelles contraintes : $$\sum_{i=1}^{n/4h} a_i \le 2A $$
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad -a_i \le x_i - x_{i-1} \le a_i$
 
%% Cell type:markdown id:2bb743d7 tags:
 
### (c) Activation simultanée des modes normal et reverse
 
%% Cell type:markdown id:11c4dbac tags:
 
$\color{red}COMPLETER$
 
%% Cell type:markdown id:7a3e7b09 tags:
 
**Tâche 2** : on souhaite réduire le coût d'utilisation de la pompe à chaleur, et on va fixer le budget maximal à une certaine proportion du coût minimal identifié lors de la première tâche. Pour diminuer les coût, on va permettre aux températures de sortir de la plage admissible définie plus haut (on abandonne aussi la contrainte sur la température finale, qui devient libre). On va cependant alors comptabiliser la quantité d'_inconfort_ éventuellement subi durant chaque intervalle de temps, qui sera proportionnel au dépassement de la température maximale admissible, ou au dépassement par le bas de la température minimale admissible. On cherche alors à **minimiser l'inconfort total** (somme des inconforts sur toute la période considérée) **tout en respectant la contrainte de budget**. Formulez ce problème comme un problème d'optimisation linéaire, puis résolvez le.
 
 
<div class="alert alert-block alert-warning"><b>A mentionner</b> :<br>
- inconfort minimal + même graphiques que pour tâche 1 + temps de calcul + bref commentaire (maximum 4 lignes)<br>
- à nouveau pour les deux périodes mentionnées lors de la tâche 1
</div>
 
---
 
%% Cell type:markdown id:ad42aa0d tags:
 
<div class="alert alert-block alert-info"><b>Question 2.1</b> :<br>
Donnez votre formulation linéaire, en commentant brièvement (en particulier si vous utilisez une technique de modélisation/reformulation).
</div>
 
%% Cell type:markdown id:8f10fc9d tags:
 
### Nouvelles variables
 
%% Cell type:markdown id:99dd3eee tags:
 
On cherche à minimiser l'inconfort total tout en respectant la contrainte de budget. Par rapport à la tâche 1, il y a donc un nouveau paramètre relatif au budget maximal alloué.
 
%% Cell type:markdown id:6fa7c356 tags:
 
##### Budget [$]
 
%% Cell type:code id:ac4d6894 tags:
 
``` python
budget = 3 # [$]
```
 
%% Cell type:markdown id:1a3e181b tags:
 
##### Inconfort [/]
 
%% Cell type:markdown id:7768e2fc tags:
 
A chaque intervalle, on attribue une pénalité d'inconfort. Celle-ci sera représentative de l'écart entre la température à cet intervalle et la plage de températures "confortables". La pénalité par °C en-dessous de $T_{min}$ sera de 3 et la pénalité par °C au-dessus de $T_{max}$ sera de 1.
 
%% Cell type:code id:096c6386 tags:
 
``` python
I_i = cp.Variable(n)
```
 
%% Cell type:markdown id:0d664883 tags:
 
### Technique de l'épigraphe
 
%% Cell type:markdown id:509aabc1 tags:
 
En représentant le graphe de l'inconfort en fonction de la température à un intervalle donné, on se rend compte que la fonction, formée de trois droites, est convexe. Au lieu de s'embarquer dans des modélisations utilisant plusieurs variables binaires, il est opportun d'utiliser une méthode vue au cours : la technique de l'épigraphe. Une des droites est l'axe des abscisses, représentant un inconfort nul lorsqu'on se trouve dans la plage de températures confortables. Les deux autres droites (obliques) sont les suivantes :$\\$
$ f_{froid}(T)=-3T+3T_{min} $ $\\$
$ f_{chaud}(T)=T-T_{max} $
 
%% Cell type:code id:3b228554 tags:
 
``` python
plt.title("Inconfort en fonction de la température de la pièce (pour $T_{min}=19°C$ et $T_{max}=21°C$)")
plt.plot([15,16,17,18,19,20,21,22,23,24,25],[12,9,6,3,0,0,0,1,2,3,4],"-r")
plt.plot([19,20,21,22],[0,-3,-6,-9],":r")
plt.plot([15,16,17,18,19,20,21],[-6,-5,-4,-3,-2,-1,0],":r")
plt.grid()
plt.text(T_min-0.25,-0.75,"$T_{min}$")
plt.text(T_max-0.25,-0.75,"$T_{max}$")
plt.xlabel("$T_i$ [°C]",size=16)
plt.ylabel("$I_i$ [/]",size=16)
plt.show()
```
 
%% Output
 
 
%% Cell type:code id:b331c627 tags:
 
``` python
f_froid = lambda T: -3*T+3*T_min
f_chaud = lambda T: T-T_max
```
 
%% Cell type:markdown id:151041f9 tags:
 
### Formulation et résolution du problème
 
%% Cell type:markdown id:82c78b09 tags:
 
On peut modéliser le problème de minimisation de l'inconfort comme suit :
$$ \min_{I_i, p_{n_i}, p_{r_i}, T_i, \Delta T_i} \sum_{i=0}^{n-1}I_i \quad \text{tel que}\\ $$
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \sum\limits_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i})\le Budget\\$
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_0=T_{initial}\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_{i+1}=T_i+\Delta T_i \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{n_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{r_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad p_{n_i}+p_{r_i} \le p_{max} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \Delta T_i = -(1-\eta)(T_i-T_{ext_i}) + \frac{0,25p_{n_i}COP_n(T_{ext_i})}{C_xV} + \frac{0,25p_{r_i}COP_r}{C_xV} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad I_i \ge f_{froid}(T_i) \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad I_i \ge f_{chaud}(T_i) \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad I_i \ge 0 \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_0=T_{initial}\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad T_{i+1}=T_i+\Delta T_i \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{n_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad 0 \le p_{r_i} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad p_{n_i}+p_{r_i} \le p_{max} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \Delta T_i = -(1-\eta)(T_i-T_{ext_i}) + \frac{0,25p_{n_i}COP_n(T_{ext_i})}{C_xV} + \frac{0,25p_{r_i}COP_r}{C_xV} \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad I_i \ge f_{froid}(T_i) \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad I_i \ge f_{chaud}(T_i) \qquad ,\forall i \in [0,n-1]\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad I_i \ge 0 \qquad ,\forall i \in [0,n-1]\\ $
 
 
Par rapport à la tâche 1, on a donc supprimé la contrainte sur la plage de température, mais avons ajouté une contrainte portant sur le budget alloué et trois autres contraintes pour appliquer la technique de l'épigraphe susmentionnée.
 
%% Cell type:markdown id:185b221d tags:
 
<div class="alert alert-block alert-info"><b>Question 2.2</b> :<br>
Résolvez votre modèle sur les deux intervalles de temps, affichez vos résultats sous forme graphique et commentez.modélisation/reformulation)
</div>
 
%% Cell type:markdown id:01eb34c3 tags:
 
### Intervalle 13050
 
%% Cell type:markdown id:e0cc21fb tags:
 
##### Résolution du problème
 
%% Cell type:code id:f2d615c4 tags:
 
``` python
# On revient à l'intervalle 13050...
intervalle_initial = 13050
heure_initiale = 22.5
T_ext = data[intervalle_initial:intervalle_initial+n]
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
objectif = cp.Minimize(cp.sum(I_i))
contraintes = [c.T@(p_n_i+p_r_i) <= budget, T_i[0] == T_initial, T_i[1:n+1] == T_i[0:n]+deltaT_i, (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx),
I_i >= f_froid(T_i[0:n]), I_i >= f_chaud(T_i[0:n]), I_i >= 0]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"}, verbose=False)
```
 
%% Output
 
6681.201308241339
 
%% Cell type:markdown id:2d28c4ed tags:
 
##### Affichage de la solution
 
%% Cell type:code id:e6d9554a tags:
 
``` python
fig1, axs1 = plt.subplots(2, 2, figsize=(14, 4))
 
fig1.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs1[0,0].plot(np.arange(n+1), T_i.value, color='red', alpha=0.4)
axs1[0,0].set_title("Température à l'intérieur du bâtiment (°C)")
axs1[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,0].grid('on', alpha=0.3)
 
axs1[0,1].plot(np.arange(n), I_i.value, color='darkcyan',alpha=0.4)
axs1[0,1].bar(np.arange(n), I_i.value, color='turquoise',alpha=0.4)
axs1[0,1].set_title("Inconfort")
axs1[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,1].grid('on', alpha=0.3)
 
axs1[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan', alpha=0.4)
axs1[1,0].bar(np.arange(n), p_n_i.value, color='turquoise', alpha=0.4)
axs1[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs1[1,0].set_ylim(-30,1030)
axs1[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,0].grid('on', alpha=0.3)
 
axs1[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs1[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs1[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs1[1,1].set_ylim(-30,1030)
axs1[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,1].grid('on', alpha=0.3)
 
title1 = fig1.suptitle("Graphes de la solution optimale (Inconfort minimal={} et budget utilisé={})".format(objectif.value, c.T @ (p_n_i.value + p_r_i.value)),y=1.05)
title1.set_fontsize(15)
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:bb0516f4 tags:
 
### Intervalle 22504
 
%% Cell type:markdown id:91c6e0e0 tags:
 
##### Résolution du problème
 
%% Cell type:code id:852abdb9 tags:
 
``` python
# ...et on retourne à nouveau à l'intervalle 22054
intervalle_initial = 22504
heure_initiale = 0
T_ext = data[intervalle_initial:intervalle_initial+n]
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
objectif = cp.Minimize(cp.sum(I_i))
contraintes = [c.T@(p_n_i+p_r_i) <= budget, T_i[0] == T_initial, T_i[1:n+1] == T_i[0:n]+deltaT_i, 0 <= (p_n_i+p_r_i), (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx),
I_i >= f_froid(T_i[0:n]), I_i >= f_chaud(T_i[0:n]), I_i >= 0]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"}, verbose=False)
```
 
%% Output
 
0.0
 
%% Cell type:markdown id:36627caa tags:
 
##### Affichage de la solution
 
%% Cell type:code id:27b25fce tags:
 
``` python
fig1, axs1 = plt.subplots(2, 2, figsize=(14, 4))
 
fig1.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs1[0,0].plot(np.arange(n+1), T_i.value, color='red', linewidth=1.2, alpha=0.4)
axs1[0,0].set_title("Température à l'intérieur du bâtiment (°C)")
axs1[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,0].grid('on', alpha=0.3)
 
axs1[0,1].plot(np.arange(n), I_i.value, color='darkcyan',alpha=0.4)
axs1[0,1].bar(np.arange(n), I_i.value, color='turquoise',alpha=0.4)
axs1[0,1].set_title("Inconfort")
axs1[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,1].grid('on', alpha=0.3)
 
axs1[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan', alpha=0.4)
axs1[1,0].bar(np.arange(n), p_n_i.value, color='turquoise', alpha=0.4)
axs1[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs1[1,0].set_ylim(-30,1030)
axs1[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,0].grid('on', alpha=0.3)
 
axs1[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs1[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs1[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs1[1,1].set_ylim(-30,1030)
axs1[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,1].grid('on', alpha=0.3)
 
title1 = fig1.suptitle("Graphes de la solution optimale (Inconfort minimal={} et budget utilisé={})".format(objectif.value, c.T @ (p_n_i.value + p_r_i.value)),y=1.05)
title1.set_fontsize(15)
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:e4aee6ab tags:
 
### Commentaires
 
%% Cell type:markdown id:cef532c6 tags:
 
L'objectif était de minimiser l'inconfort occasionné par la température en fonction d'un budget donné, fixé ici à 3\\$. La pénalité minimale liée à l'inconfort est de 6681.20 pour l'intervalle de départ 13050 et de 0.0 pour le 22504ème intervalle avec un budget utilisé de 2.262\\$. Notre code s'exécute en respectivement 0.04708 et 0.03928 secondes pour les deux semaines testées.
 
Lorsque les températures extérieures sont proches des températures ne causant aucun inconfort, il est moins coûteux de rester dans la plage confortable. 3\\$ suffisent en commençant à l'intervalle 22504 (l'inconfort est donc nul), mais pas pour 13050. Les pics d'inconfort surviennent évidemment durant les pics de températures trop basses.
 
%% Cell type:markdown id:db6baecb tags:
 
On remplace à présent la notion d'inconfort décrite ci-dessus par une pénalisation quadratique : à présent l'inconfort est proportionnel au *carré* du dépassement de la température maximale admissible, ou au *carré* du dépassement par le bas de la température minimale admissible (les coefficients de proportionnalité restent identiques).
 
%% Cell type:markdown id:cf7d1ad5 tags:
 
<div class="alert alert-block alert-info"><b>Question 2.3</b> :<br>
Modélisez ce nouveau problème de façon linéaire, en utilisant une approximation. Cette approximation pourra par exemple être basée sur des tangentes (choisissez un nombre pas trop élevé, par exemple 5). Expliquez votre technique de modélisation. Résolvez ce modèle approché, affichez les solutions et commentez (en particulier l'effet sur la solution par rapport au modèle d'inconfort initial).
</div>
 
%% Cell type:markdown id:3d678078 tags:
 
### Technique de l'épigraphe avec approximations linéaires
 
%% Cell type:markdown id:06fe7ddc tags:
 
##### Mise en équations
 
%% Cell type:markdown id:06ad1cc3 tags:
 
Etant donné la nouvelle fonction non-linéaire de l'inconfort en fonction de la température à un intervalle donné, $\\$
$I(T) = 3(T-T_{min})^2 \qquad T \lt T_{min}$
 
$I(T) = 0 \qquad \qquad T_{min} \lt T \lt T_{max}$
 
$I(T) = (T-T_{max})^2 \qquad T \gt T_{max}$
$\\$ nous avons décidé de rendre cette fonction linéaire en l'approximant par six tangentes, en plus de l'axe des abscisses. Celle-ci étant convexe, l'épigraphe ainsi formé l'est également. Nous allons dès lors pouvoir à nouveau effectuer la technique de l'épigraphe. Voici l'équation des tangentes (approximation de Taylor) pour $T_{min}$=19°C et $T_{max}$=21°C de la courbe quadratique aux abscisses 13, 17, 18, 22, 23 et 27 :
 
$f_1(T) = 108 - 36(T - 13)$
 
$f_2(T) = 12 - 12(T - 17)$
 
$f_3(T) = 3 - 6(T - 18)$
 
$f_4(T) = 1 + 2(T - 22)$
 
$f_5(T) = 4 + 4(T - 23)$
 
$f_6(T) = 36 + 12(T - 27)$
 
On a choisi des abscisses entre 13 et 27 car les températures intérieurs sont principalement dans ces valeurs de température. Dans tous les cas, les tangentes sous-évaluent l'inconfort réel puisqu'elles se trouvent en-dessous de la fonction objectif (qui est convexe).
 
%% Cell type:code id:57d9bdeb tags:
 
``` python
I_froid = lambda T: 3*(T-19)**2
I_chaud = lambda T: (T-21)**2
n_froid = np.linspace(12,19,100)
n_chaud = np.linspace(21,31,100)
f1 = lambda T: 108-36*(T-13)
f2 = lambda T: 12-12*(T-17)
f3 = lambda T: 3-6*(T-18)
f4 = lambda T: 1+2*(T-22)
f5 = lambda T: 4+4*(T-23)
f6 = lambda T: 36+12*(T-27)
n1 = np.linspace(12,15,500)
n2 = np.linspace(15,17.5,500)
n3 = np.linspace(17.5,18.5,500)
n4 = np.linspace(21.5,22.5,500)
n5 = np.linspace(22.5,25,500)
n6 = np.linspace(25,31,500)
plt.title("Approximation de l'inconfort en fonction de la température")
plt.plot(n_froid,I_froid(n_froid),"--b")
plt.plot(n_chaud,I_chaud(n_chaud),"--b",label="Courbe approximée")
plt.plot(n1,f1(n1),"-r",label="Tangentes")
plt.plot(n2,f2(n2),"-r")
plt.plot(n3,f3(n3),"-r")
plt.plot(n4,f4(n4),"-r")
plt.plot(n5,f5(n5),"-r")
plt.plot(n6,f6(n6),"-r")
plt.plot([13,17,18,22,23,27],[I_froid(13),I_froid(17),I_froid(18),I_chaud(22),I_chaud(23),I_chaud(27)],".k",label="Points de tangence")
plt.hlines(y=0,xmin=19,xmax=21,linestyle="dashed",color="blue")
plt.grid()
plt.legend()
plt.text(T_min-0.25,-5.5,"$T_{min}$")
plt.text(T_max-0.25,-5.5,"$T_{max}$")
plt.xlabel("$T_i$ [°C]",size=16)
plt.ylabel("$I_i$ [/]",size=16)
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:53c22b28 tags:
 
##### Résolution
 
%% Cell type:code id:07ebe9e4 tags:
 
``` python
f1 = lambda T: 108-36*(T-13)
f2 = lambda T: 12-12*(T-17)
f3 = lambda T: 3-6*(T-18)
f4 = lambda T: 1+2*(T-22)
f5 = lambda T: 4+4*(T-23)
f6 = lambda T: 36+12*(T-27)
 
 
# On retourne à l'intervalle 13050
intervalle_initial = 13050
heure_initiale = 22.5
T_ext = data[intervalle_initial:intervalle_initial+n]
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
objectif = cp.Minimize(cp.sum(I_i))
contraintes = [c.T@(p_n_i+p_r_i) <= budget, T_i[0] == T_initial, T_i[1:n+1] == T_i[0:n]+deltaT_i, (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx),
I_i >= f1(T_i[0:n]), I_i >= f2(T_i[0:n]), I_i >= f3(T_i[0:n]), I_i >= f4(T_i[0:n]), I_i >= f5(T_i[0:n]), I_i >= f6(T_i[0:n]), I_i >= f0(T_i[0:n]), I_i >= 0]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"}, verbose=False)
```
 
%% Output
 
24620.86503378199
 
%% Cell type:code id:d2739444 tags:
 
``` python
fig1, axs1 = plt.subplots(2, 2, figsize=(14, 4))
 
fig1.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs1[0,0].plot(np.arange(n+1), T_i.value, color='red', linewidth=1.2, alpha=0.4)
axs1[0,0].set_title("Température à l'intérieur du bâtiment (°C)")
axs1[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,0].grid('on', alpha=0.3)
 
axs1[0,1].plot(np.arange(n), I_i.value, color='darkcyan',alpha=0.4)
axs1[0,1].bar(np.arange(n), I_i.value, color='turquoise',alpha=0.4)
axs1[0,1].set_title("Inconfort")
axs1[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,1].grid('on', alpha=0.3)
 
axs1[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan', alpha=0.4)
axs1[1,0].bar(np.arange(n), p_n_i.value, color='turquoise', alpha=0.4)
axs1[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs1[1,0].set_ylim(-30,1030)
axs1[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,0].grid('on', alpha=0.3)
 
axs1[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs1[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs1[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs1[1,1].set_ylim(-30,1030)
axs1[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,1].grid('on', alpha=0.3)
 
title1 = fig1.suptitle("Graphes de la solution optimale (Inconfort minimal={} et budget utilisé={})".format(objectif.value, c.T @ (p_n_i.value + p_r_i.value)),y=1.05)
title1.set_fontsize(15)
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:32b36538 tags:
 
Comme on peut s'y attendre, l'inconfort est beaucoup plus grand (le budget alloué est toujours de 3$) : il est de 24620, alors qu'il était de 6681 précédemment. Un inconfort quadratique est en effet plus pénalisant qu'un inconfort linéaire. Tandis que pour le cas linéaire, le graphe de l'inconfort présentait un pic à une valeur de 25, le pic est ici à 150. Une autre différence que l'on peut constater est que dans le cas linéaire, on observait 3 intervalles principaux durant lesquels la pompe chauffait. A présent, il y en a un supplémentaire au début de la période simulée. Cela se produit parce que dans le cas linéaire, la température de la pièce descendait jusqu'à atteindre 10°C, mais aller si bas dans le cas quadratique ferait considérablement augmenter l'inconfort total. Il a donc fallu chauffer légèrement la pièce pour s'écarter un peu moins de la zone confortable. Le minimum de température est maintenant de 12°C environ.
 
%% Cell type:markdown id:2be891ab tags:
 
<div class="alert alert-block alert-info"><b>Question 2.4</b> :<br>
Pour terminez cette partie, résolvez encore une fois ce nouveau modèle, mais cette fois de façon exacte, en utilisant un solveur quadratique. Comparez avec la solution approchée obtenue précédemment (allure de la solution, temps de calcul).
</div>
 
%% Cell type:markdown id:8a8a4cf0 tags:
 
Nous avons utilisé le solveur ECOS de CVXPY pour résoudre ce problème dont la fonction objectif est maintenant quadratique. Nous avons changé cette dernière et ajouté les contraintes suivantes:
$$ \min_{f_i, c_i, p_{n_i}, p_{r_i}, T_i, \Delta T_i} \sum_{i=0}^{n-1}3f_i^2+c_i^2 \quad \text{tel que}\\ $$
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad f_i \ge T_{min}-T_i \\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad f_i \ge 0\\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad c_i \ge T_i-T_{max} \\ $
$ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad c_i \ge 0\\ $
 
%% Cell type:code id:038a0f17 tags:
 
``` python
from cvxpy import ECOS
 
froid_i = cp.Variable(n)
chaud_i = cp.Variable(n)
 
objectif = cp.Minimize(cp.sum(3*froid_i**2 + chaud_i**2))
contraintes = [c.T@(p_n_i+p_r_i) <= budget, T_i[0] == T_initial, T_i[1:n+1] == T_i[0:n]+deltaT_i, (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx),
froid_i >= 19-T_i[0:n], froid_i >=0, chaud_i >=0, chaud_i>= T_i[0:n] -21]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.ECOS, verbose=False)
```
 
%% Output
 
27390.753780967974
 
%% Cell type:code id:95132633 tags:
 
``` python
fig1, axs1 = plt.subplots(2, 2, figsize=(14, 4))
 
fig1.subplots_adjust(wspace=0.1, hspace=0.5)
 
axs1[0,0].plot(np.arange(n+1), T_i.value, color='red', linewidth=1.2, alpha=0.4)
axs1[0,0].set_title("Température à l'intérieur du bâtiment (°C)")
axs1[0,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,0].grid('on', alpha=0.3)
 
axs1[0,1].plot(np.arange(n), I_i.value, color='darkcyan',alpha=0.4)
axs1[0,1].bar(np.arange(n), I_i.value, color='turquoise',alpha=0.4)
axs1[0,1].set_title("Inconfort")
axs1[0,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[0,1].grid('on', alpha=0.3)
 
axs1[1,0].plot(np.arange(n), p_n_i.value, color='darkcyan', alpha=0.4)
axs1[1,0].bar(np.arange(n), p_n_i.value, color='turquoise', alpha=0.4)
axs1[1,0].set_title("Puissance de la pompe en mode normal (W)")
axs1[1,0].set_ylim(-30,1030)
axs1[1,0].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,0].grid('on', alpha=0.3)
 
axs1[1,1].plot(np.arange(n), p_r_i.value, color='darkcyan',alpha=0.4)
axs1[1,1].bar(np.arange(n), p_r_i.value, color='turquoise',alpha=0.4)
axs1[1,1].set_title("Puissance de la pompe en mode reverse (W)")
axs1[1,1].set_ylim(-30,1030)
axs1[1,1].set_xlabel("Intervalles", fontstyle='italic')
axs1[1,1].grid('on', alpha=0.3)
 
title1 = fig1.suptitle("Graphes de la solution optimale (Inconfort minimal={} et budget utilisé={})".format(objectif.value, c.T @ (p_n_i.value + p_r_i.value)),y=1.05)
title1.set_fontsize(15)
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:44220087 tags:
 
Nous observons que l'inconfort du problème quadratique "réel" est constamment supérieur à celui approximé par des tangentes. Cela reste parfaitement logique. En effet, le problème étant convexe, les tangentes sont toujours inférieures à la fonction quadratique. A l'inverse, pour le problème non approximé, bien que nous n'utilisons plus exactement la technique de l'épigraphe, nous formons la même figure de domaine admissible (concernant les contraintes de températures) que présenté précédemment. Seulement, ce domaine étant plus restrictif, il est cohérent d'obtenir un résultat plus élevé à notre problème de minimisation. La forme générale des graphiques entre la version "réelle" et non approximée reste très similaire ce qui nous conforte en la véracité de la modélisation de ces deux problèmes. En ce qui concerne les temps d'exécution, la différence de temps de calcul entre ces deux modèles paraît négligeable. Nous mesurons un temps d'exécution de 2.3 secondes pour l'approximation par 6 tangentes et de 2.2 secondes pour le solveur quadratique.
 
%% Cell type:markdown id:7f47806b tags:
 
**Tâche 3** : on voudrait à présent mieux comprendre le compromis qui existe entre le budget alloué et l'inconfort total qui en résulte. Proposez un **graphique représentant au mieux cette relation entre budget et inconfort**, où on fera varier le budget entre entre zéro et le coût minimal identifié lors de la tâche 1 (ce budget sera indiqué en pourcentage, de 0 à 100%). Ceci nécessitera la résolution de plusieurs problèmes, et il sera judicieux d'utiliser la fonctionnalité _warm start_ du solver pour accélérer les calculs.
 
<div class="alert alert-block alert-warning"><b>A mentionner</b> :<br>
- graphique demandé + temps de calcul (total et moyenne par problème) + bref commentaire (maximum 4 lignes)<br>
- à nouveau pour les deux périodes mentionnées lors des tâches 1 et 2
</div>
 
---
 
%% Cell type:markdown id:eb40a4fa tags:
 
### Création de la fonction $resoudre(budget\_autorise)$
 
%% Cell type:markdown id:e9b4e1d8 tags:
 
Mettons la résolution de la tâche 2 sous la forme d'une unique fonction qui prend comme paramètre le budget que l'on veut allouer à la régulation de température (ainsi que le volume de la pièce que l'on va changer pour obtenir un graphe supplémentaire, et l'heure et l'intervalle initiaux).
 
%% Cell type:code id:4f3518c7 tags:
 
``` python
def resoudre(budget_autorise, volume, intervalle, heure, dual = False):
 
T_initial = 20 # [°C]
T_final = 20 # [°C]
T_min = 19 # [°C]
T_max = 21 # [°C]
p_max = 1000 # [W]
eta = 0.99 # Isolation du bâtiment [/]
Cx = 1000/(0.4*360) # Capacité calorifique du bâtiment [Wh/(°C*m^3)]
V = volume # Volume du bâtiment [m^3]
cout_heures_creuses = 0.00018 # [$/(Wh*h)]
cout_heures_pleines = 0.00026 # [$/(Wh*h)]
budget = budget_autorise # [$]
 
heure_initiale = heure # Compris dans l'intervalle [0,24[ [h]
intervalle_initial = intervalle
n = 672 # Nombre de périodes/intervalles
data = np.load("info.npy")
T_ext = data[intervalle_initial:intervalle_initial+n]
 
I_i = cp.Variable(n)
 
COP_normal = lambda f: 3+10*abs(np.tanh(f/100))*np.tanh(f/100)
COP_reverse = 3.2
 
T_i = cp.Variable(n+1)
deltaT_i = cp.Variable(n)
 
c = np.zeros(n)
for i in range(n):
c[i]= cout_heures_creuses*0.25 if 0 <= (heure_initiale+(i+1)*0.25)%24 <= 7 or 22 < (heure_initiale+(i+1)*0.25)%24 <= 24 else cout_heures_pleines*0.25
 
p_n_i = cp.Variable(n) # Normal
p_r_i = cp.Variable(n) # Reverse
 
f_froid = lambda T: -3*T+3*T_min
f_chaud = lambda T: T-T_max
 
objectif = cp.Minimize(cp.sum(I_i))
contraintes = [c.T@(p_n_i+p_r_i) <= budget, T_i[0] == T_initial, T_i[1:n+1] == T_i[0:n]+deltaT_i, (p_n_i+p_r_i) <= p_max, 0 <= p_n_i, 0 <= p_r_i,
deltaT_i == -(1-eta)*(T_i[0:n]-T_ext)+(cp.multiply(p_n_i,COP_normal(T_ext)))*0.25/(V*Cx)-p_r_i*COP_reverse*0.25/(V*Cx),
I_i >= f_froid(T_i[0:n]), I_i >= f_chaud(T_i[0:n]), I_i >= 0]
probleme = cp.Problem(objectif, contraintes)
probleme.solve(solver=cp.SCIPY, scipy_options={"method":"highs"})
 
if (dual):
return contraintes[0].dual_value
return objectif.value
```
 
%% Cell type:markdown id:5771ea0c tags:
 
### Calcul de l'inconfort pour des fractions du budget minimal requis
 
%% Cell type:markdown id:2e0ae6ba tags:
 
Nous pouvons maintenant résoudre le même problème de la tâche 2 plusieurs fois, mais avec des budgets différents. En partant du budget minimal identifié dans la tâche 1, nous allons évaluer l'inconfort résultant lorsque nous n'utilisons qu'une fraction de ce budget minimal.
 
%% Cell type:markdown id:caedf962 tags:
 
<div class="alert alert-block alert-info"><b>Question 3.1</b> :<br>
Fournissez le graphique et les commentaires demandé ci-dessus
</div>
 
%% Cell type:markdown id:0b194fe0 tags:
 
### Intervalle 13050
 
%% Cell type:code id:e0a8c553 tags:
 
``` python
cout_minimal_obtenu_a_la_tache_1 = 8.182333695034458
pourcentage_budget = np.linspace(0, cout_minimal_obtenu_a_la_tache_1, 100)
inconfort_minimal = []
 
for budget in pourcentage_budget:
inconfort = resoudre(budget, 360, 13050, 22.5)
inconfort_minimal.append(inconfort)
```
 
%% Cell type:code id:85a5e73a tags:
 
``` python
plt.plot(np.arange(1, 101), inconfort_minimal, color='purple', linewidth=2, alpha = 0.3)
plt.plot(100, inconfort_minimal[-1], 'or', markersize=10, label=f"Budget minimum pour inconfort nul = {cout_minimal_obtenu_a_la_tache_1}")
plt.title('Inconfort en fonction du budget', fontsize=18)
plt.grid('on',alpha=0.3)
plt.xlabel("$Fraction$ $du$ $budget$ $minimal$ [%]", fontsize=12)
plt.legend(fontsize=10)
plt.tick_params(axis='both', which='major', labelsize=11)
plt.axhline(y=0, color='r', linestyle='--', alpha=0.6)
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:6896fd35 tags:
 
### Intervalle 22504
 
%% Cell type:code id:9f664d7f tags:
 
``` python
cout_minimal_obtenu_a_la_tache_1 = 1.9898040404466002
pourcentage_budget = np.linspace(0, cout_minimal_obtenu_a_la_tache_1, 100)
inconfort_minimal = []
 
for budget in pourcentage_budget:
inconfort = resoudre(budget, 360, 22504, 0)
inconfort_minimal.append(inconfort)
```
 
%% Cell type:code id:f61b216b tags:
 
``` python
plt.plot(np.arange(1, 101), inconfort_minimal, color='purple', linewidth=2, alpha = 0.3)
plt.plot(100, inconfort_minimal[-1], 'or', markersize=10, label=f"Budget minimum pour inconfort nul = {cout_minimal_obtenu_a_la_tache_1}")
plt.title('Inconfort en fonction du budget', fontsize=18)
plt.grid('on',alpha=0.3)
plt.xlabel("$Fraction$ $du$ $budget$ $minimal$ [%]", fontsize=12)
plt.legend(fontsize=10)
plt.tick_params(axis='both', which='major', labelsize=11)
plt.axhline(y=0, color='r', linestyle='--', alpha=0.6)
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:ef6aa85b tags:
 
### Quid de la relation budget-inconfort si l'on change le volume de la pièce ?
 
%% Cell type:markdown id:6d67576e tags:
 
Générons un graphe 3D permettant de visualiser l'impact du changement de volume de la pièce. Ce graphique mettra en exergue le fait qu'il n'est pas du tout équivalent de chauffer une petite pièce ou tout un auditoire... On le fait pour l'intervalle 13050.
 
%% Cell type:code id:b94f582a tags:
 
``` python
n = 1000
budget = 10*np.random.rand(n)
volume = 500*np.random.rand(n)
inconfort = [resoudre(budget[i], volume[i], 13050, 22.5) for i in range(n)]
 
x = []
y = []
for i in range(n):
if (inconfort[i] is not None):
x.append(budget[i])
y.append(volume[i])
z = [i for i in inconfort if i is not None]
 
colors = z
 
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c=colors, cmap='viridis', marker='o')
 
ax.set_title('Graphique de la relation Budget-Inconfort pour différents volumes')
ax.set_xlabel('Budget [$]')
ax.set_ylabel('Volume [m^3]')
ax.set_zlabel('Inconfort [/]')
 
cbar = fig.colorbar(ax.collections[0], location='right', pad = 0.1, shrink = 0.8)
cbar.ax.set_title('Inconfort')
 
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:1071e282 tags:
 
### Commentaires
 
%% Cell type:markdown id:8124c352 tags:
 
L'objectif était d'étudier la relation entre le budget et l'inconfort occasionné. Notre code est testé pour 100 budgets différents allant de 0\$ à la valeur pour laquelle l'inconfort est nul. Dans le cas de l'intervalle 13050, l'inconfort dépasse les 12000 sans l'intervention de la pompe, dans le cas de l'intervalle 22504 il atteint les 800. Notre code s'exécute (pour obtenir les informations du premier graphe) en respectivement 5.3625 et 7.89081 secondes pour les deux semaines testées. Pour obtenir le deuxième graphe (en 3D), il faut compter un peu moins de 2 minutes pour 1000 résolutions.
 
Dans les deux semaines testées, comme attendu, plus le budget est restreint moins le confort peut être assuré. Les deux décroissent jusqu'à arriver à leur budget pour lequel l'inconfort est nul. L'inconfort en se passant de la pompe est évidemment plus petit durant les périodes plus estivales.
 
On peut également observer l'effet du volume de la pièce sur l'inconfort. Pour un budget donné, l'inconfort monte rapidement lorsque la pièce à chauffer augmente en volume.
 
%% Cell type:markdown id:0d1eeb96 tags:
 
<div class="alert alert-block alert-info"><b>Question 3.2</b> :<br>
Expliquez la pente linéaire observée dans une grande partie du graphique obtenu. Recalculez la valeur de la pente à partir des informations fournies par le solver pour la résolution avec le budget maximal (tâche 2 initiale, Question 2.2), et comparez à celle du graphique. Enfin, expliquez pourquoi le graphique cesse à un moment d'être une droite.
</div>
 
%% Cell type:markdown id:fa986f94 tags:
 
### Explication et calcul de la pente
 
%% Cell type:markdown id:6145880c tags:
 
On constate pour l'intervalle 13050 que pour un pourcentage de budget allant de 0 à environ 50, la décroissance de l'inconfort est linéaire. Jusqu'à 100% du budget, la courbe ressemble davantage à une parabole. On peut s'aider de l'analyse post-optimale pour calculer la pente de la droite. Pour l'intervalle 22504, c'est sur l'intervalle $[0\%,30\%]$ que la droite se trouve.
 
Le problème peut être représenté sous la forme standard par : $\min_x \mathbf{c}^Tx$ tel que $Ax = b$ et $x \ge 0$. Nous avons vu que lorsque l'on obtient la valeur optimale $x^*$ de ce problème, on peut prédire l'évolution du sommet et du coût optimaux. Dans notre cas, la quantité que l'on modifie est le budget alloué. On change donc (voir formulation) la contrainte $\sum\limits_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i})\le Budget \Leftrightarrow -\sum\limits_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i})\ge -Budget$ en $\sum\limits_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i})\le Budget + \Delta b\Leftrightarrow -\sum\limits_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i})\ge -Budget + (-\Delta b)$, avec $\Delta b\gt 0$ représentant un petit incrément de budget. Or, il a été vu que si l'on modifie le membre de droite de $Ax = b$ en remplaçant $b$ par $b+\Delta b$, le nouveau sommet (primal) devient $x_{new}^* = B^{-1}(b+\Delta b)$ et le coût optimal est augmenté de $\mathbf{y^{*}}^T\Delta b$, avec $y^{*}$ la solution du dual, le tout tant que la condition d'admissibilité est satisfaite.
 
En résolvant le problème pour un pourcentage de budget dans l'intervalle qui nous intéresse, i.e. $[0\%,50\%]$, on peut demander au solveur de nous fournir la variable duale associée à la contrainte $\sum\limits_{i=0}^{n-1}c_i(p_{n_i}+p_{r_i})\le Budget$. Commme il n'y a qu'une contrainte incrémentée, il n'y qu'une variable duale d'intérêt.
 
Résolvons donc le problème pour un budget de 20% du budget minimal à allouer :
 
%% Cell type:code id:13a297f7 tags:
 
``` python
cout_minimal_obtenu_a_la_tache_1 = 8.182333695034458
print(resoudre((20/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
```
 
%% Output
 
2043.7646964520766
 
%% Cell type:markdown id:7c079cc9 tags:
 
Le solveur nous apprend que l'inconfort pour 20% est de 9456, mais surtout que $y^{*}=2044$. Donc, si l'on ajoute un petit budget $\Delta b$, c'est-à-dire que l'on ajoute $(-\Delta b)$ au membre de droite de la contrainte de budget, la solution (l'inconfort) devrait changer de $\mathbf{y^{*}}^T(-\Delta b) = -2044\Delta b$. Dans notre cas 1% du budget représente $0.0818\$$ donc l'inconfort va changer d'une valeur de $0.818\times (-2044)=-167$.
 
Donc, la pente est d'environ -167 par pourcent.
 
Vérifions cette valeur théorique. Si l'on passe d'un budget de 20% à 30% du budget minimal à allouer pour un inconfort nul, on devrait avoir une variation d'inconfort d'environ $10\times(-167) = -1670$. Par conséquent, l'inconfort devrait être de $9456-1670=7786$.
 
%% Cell type:code id:7d03cc2d tags:
 
``` python
print(resoudre((30/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5))
```
 
%% Output
 
7786.255703567915
 
%% Cell type:markdown id:5eb757f9 tags:
 
C'est bien la valeur obtenue !
 
Cette approximation de l'inconfort est en réalité exacte tant que $B^{-1}(b+\Delta b)\ge 0$, où $B$ est la matrice des variables de la base du tableau simplexe du problème. En-dehors du domaine de validité de cette inégalité, l'approximation n'est plus exacte et elle surévalue la variation du nouveau coût optimal. Le domaine hors de l'intervalle $[0\%,50\%]$ est tel que la variable duale $y^{*}$ est variable et non plus constante. On peut le voir en résolvant les problèmes ci-dessous :
 
%% Cell type:code id:1359b547 tags:
 
``` python
# La variable duale est constante
print("Variable duale =",resoudre((10/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print("Variable duale =",resoudre((20/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print("Variable duale =",resoudre((30/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print("Variable duale =",resoudre((40/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print()
# La variable duale commence à varier
print("Variable duale =",resoudre((50/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print()
# La variable duale n'est plus une constante
print("Variable duale =",resoudre((60/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print("Variable duale =",resoudre((70/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print("Variable duale =",resoudre((80/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
print("Variable duale =",resoudre((90/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True))
```
 
%% Output
 
Variable duale = 2066.038771528725
Variable duale = 2043.7646964520766
Variable duale = 2036.042620899515
Variable duale = 2015.3824537180549
Variable duale = 1992.4084547788225
Variable duale = 1727.3669914785114
Variable duale = 1118.9338873755696
Variable duale = 892.7378006683173
Variable duale = 670.4271279008
 
%% Cell type:markdown id:638e2324 tags:
 
On peut même en un graphique :
 
%% Cell type:code id:f2ffe3a6 tags:
 
``` python
pourcentage = np.arange(1,101)
y = []
for i in pourcentage:
dual = resoudre((i/100)*cout_minimal_obtenu_a_la_tache_1, 360, 13050, 22.5, True)
y.append(dual)
plt.plot(pourcentage,y)
```
 
%% Output
 
[<matplotlib.lines.Line2D at 0x18fdc57a9a0>]
 
 
%% Cell type:markdown id:741ea41c tags:
 
Cela confirme ce que l'on pensait. La variable duale est (pratiquement) constante dans l'intervalle de pourcentage $[0\%,50\%]$. Ensuite, elle décroit jusqu'à atteindre 0, ce qui signifie que la contrainte de budget associée n'est plus nécessairement serrée (c'est logique puisqu'on utilise 100$\%$ du budget minimal à allouer pour avoir un inconfort nul).
 
%% Cell type:markdown id:d5523f6e tags:
 
<div class="alert alert-block alert-info"><b>Bonus</b><br>
Estimez l'effet de l'utilisation d'une version imprécise des données de température (prévisions)<br>
</div>
Ce bonus est optionnel, et ne conduit pas à l'obtention de points supplémentaires : il est seulement destiné à attirer votre
attention sur le caractère artificiel de la situation proposée, où on connaît parfaitement et à l'avance les températures extérieures.
 
%% Cell type:markdown id:640fd558 tags:
 
## Consignes et conseils
- Le projet se réalise par groupe de (maximum) quatre étudiants (cf. groupes constitués sur Moodle).
 
- L'assistant responsable du projet est Guillaume Van Dessel. Toutes les questions sur le projet doivent être posées via Moodle dans le forum prévu pour le projet (et pas par message/mail individuel). Des permanences seront prévues, et seront annoncées via Moodle.
 
- Il est fortement suggéré d'utiliser un langage de modélisation pour formuler et résoudre vos problèmes d'optimisation linéaire. Nous conseillons d'utiliser le module CVXPY combiné au solver d'optimisation HIGHS (nous avons vérifié que cette combinaison est suffisamment performance pour le projet).
 
- Les groupes peuvent échanger leurs réflexions, partager leurs idées et comparer leurs résultats. Ils ne peuvent pas recopier les raisonnements, les solutions ou les codes informatiques. L'utilisation de toute information ou aide extérieure doit obligatoirement être mentionnée dans le rapport, en citant la source.
 
- Votre rapport final sera constitué de ce notebook complété, où vous aurez inséré vos codes, vos résultats, vos graphiques et commentaires.
 
- Ce rapport est à remettre au plus tard le **mercredi 24 mai 2023** à minuit (soir), via Moodle, sous la forme d'une archive compressée contenant votre notebook et tous les fichiers nécessaires pour le faire fonctionner (code Python, etc.). Le notebook doit contenir les cellules sous forme déjà évaluée (résultats, tableaux, graphiques, etc.), mais doit pouvoir également être ré-évalué en entier.
 
- Organisez efficacement votre travail de groupe, et répartissez vous le travail. Les tâches à effectuer durant cette seconde partie sont *largement indépendantes* les unes des autres.
 
 
### Changelog
- 2023-03-24 v1
- 2023-04-23 v1.1 avec récapitulatif des précisions apportées sur Moodle (en bleu)
- 2023-04-28 description des tâches de la seconde partie
- 2023-05-12 v2 avec le format attendu (notebook) pour le rapport final
- 2023-05-12 v2.1 précisions supplémentaire pour quelques questions
 
%% Cell type:code id:73344bf0 tags:
 
``` python
```
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter