Simple Cold Demand Usage
Generating a simple cold load curve¶
from datetime import datetime
import pandas as pd
import plotly.graph_objects as go
import plotly.io as pio
from heatpro.cold.distribution import (
year_to_hour_outdoor_temperarure_distribution,
month_to_hour_outdoor_temperarure_distribution,
)
from heatpro.felt_temperature import calculate_felt_temperature
from heatpro.week_profile import apply_week_profile
# Needed for correct layout of the produced html
pio.renderers.default = "notebook"
pd.options.plotting.backend = "plotly"
power_cold_demands: dict[str, pd.Series] = dict()
Calcul en partant d'une demande annuelle¶
On définit : $E_y$ l'énergie annuelle en froid consommée sur l'année $y$ (en kWh) et $P_y$ la puissance annuelle en froid moyenne sur l'année $y$ (en kW).
$$ P_y = \frac{E_y}{|y|} $$
Où $|y|$ est la durée de l'année $y$ en heure.
YEARLY_ENERGY_COLD_DEMAND_KWH = pd.Series(
{
datetime(year=2021, month=1, day=1): 150_000.0,
datetime(year=2022, month=1, day=1): 175_000.0,
datetime(year=2023, month=1, day=1): 160_000.0,
},
name="yearly_energy_cold_demand_kwh",
)
yearly_power_cold_demand_kw = YEARLY_ENERGY_COLD_DEMAND_KWH / 8760
On définit $T_t^{(ext)}$ la température extérieure à l'instant $t$.
outdoor_temperature = pd.read_csv("../data/external_factors.csv", index_col=0, parse_dates=True)[
"external_temperature"
]
yearly_power_cold_demand_kw_reindexed = yearly_power_cold_demand_kw.reindex(
outdoor_temperature.index, method="ffill"
)
go.Figure(
[
go.Scatter(
x=outdoor_temperature.index,
y=outdoor_temperature,
name="outdoor temperature",
showlegend=False,
yaxis="y1",
),
go.Scatter(
x=yearly_power_cold_demand_kw_reindexed.index,
y=yearly_power_cold_demand_kw_reindexed,
name="yearly energy",
showlegend=False,
yaxis="y2",
),
],
).update_layout(
yaxis1=dict(title="outdoor temperature (°C)", side="left"),
yaxis2=dict(
title="yearly average power (kW)",
overlaying="y",
side="right",
range=[0, yearly_power_cold_demand_kw_reindexed.max() * 1.05],
),
hovermode="x unified",
).show()
On définit $T^{(cons)}$ la température de consigne pour l'air conditionné dans les bâtiments.
SET_TEMPERATURE_COLD = 22
Sans prise en compte de saison de refroidissement¶
La ventilation suit les formules suivantes:
Calcul de la température "ressentie" par une moyenne mobile exponnentielle sur 24 heures (.ewm(24).mean()).
Un changement de température extérieure ne va pas avoir un impact immédiat sur la demande en froid. À cause de l'inertie themique des bâtiments, le besoin de froid réagit plutôt à une température "ressentie" qui est une moyenne poudéré de la température extérieure sur les 24 dernières heures.
$$ T_{t}^{(felt)} = \frac{ \sum_{s = 0}^{23} 0.5^s \cdot T_{t-s}^{(ext)}}{\sum_{s = 0}^{23} 0.5^s} \left( = [\overline{T_{ext}[t]}]^{24h}\right) $$
Extraction de la partie positive de la différence de température (.clip(0)).
Lorsque la température ressentie est suffisament basse le besoin de froid devient nul. L'idée est que si la climatisation est réglée sur 22 °C dans les bâtiments et que la température "ressentie" (donc indirectement la température extérieure) alors l'aération suffira à maintenir le bâtiment à la température souhaitée.
$$ \left( T_{t}^{(felt)}- T^{(cons)} \right)_+ = \max(0, T_{t}^{(felt)}- T^{(cons)}) $$
Ventilation, où $\frac{1}{|y|} \int_y \left( T_{t}^{(felt)}- T^{(cons)} \right)_+$ est la valeur annuelle moyenne de $\left( T_{t}^{(felt)}- T^{(cons)} \right)_+$ pour l'année $y$ (.resample("YS").transform("mean"))
La consommation de froid sert à équilibrer un flux de froid sortant vers un extérieur plus chaud. Ce flux étant globalement proportionnel à la différence de température entre l'intérieur et l'extérieur, la consommation de froid se répartit dans le temps proportionnellement par rapport à cette différence.
$$ P_t = P_y \cdot \frac{ \left( T_{t}^{(felt)}- T^{(cons)} \right)_+ }{ \frac{1}{|y|} \int_y \left( T_{t}^{(felt)}- T^{(cons)} \right)_+ } $$
On obtient : $P_t$ la puissance en froid à l'instant $t$ (en kW).
felt_temperature = calculate_felt_temperature(outdoor_temperature)
((felt_temperature - SET_TEMPERATURE_COLD).clip(0)).plot().show()
(
(felt_temperature - SET_TEMPERATURE_COLD).clip(0)
/ (felt_temperature - SET_TEMPERATURE_COLD).clip(0).resample("YS").transform("mean")
).plot().show()
(
yearly_power_cold_demand_kw_reindexed
* (felt_temperature - SET_TEMPERATURE_COLD).clip(0)
/ (felt_temperature - SET_TEMPERATURE_COLD).clip(0).resample("YS").transform("mean")
).plot().show()
power_cold_demand = year_to_hour_outdoor_temperarure_distribution(
yearly_power_cold_demand_kw_reindexed,
felt_temperature,
)
power_cold_demands["Depuis une demande<br>annuelle<br>Sans délimitation de saison"] = (
power_cold_demand.copy()
)
go.Figure(
[
go.Scatter(
x=outdoor_temperature.index,
y=outdoor_temperature,
name="outdoor temperature",
showlegend=False,
yaxis="y1",
),
go.Scatter(
x=power_cold_demand.index,
y=power_cold_demand,
name="pwoer",
showlegend=False,
yaxis="y2",
),
],
).update_layout(
yaxis1=dict(title="outdoor temperature (°C)", side="left"),
yaxis2=dict(title="load (kW)", overlaying="y", side="right"),
).show()
power_cold_demand.resample("YS").sum()
2021-01-01 150000.0 2022-01-01 175000.0 Freq: YS-JAN, Name: yearly_energy_cold_demand_kwh, dtype: float64
Avec prise en compte de saison de refroidissement¶
La ventialation suit les formules suivantes:
Calcul de la température "ressentie" par une moyenne mobile exponnentielle sur 24 heures (.ewm(24).mean()).
Un changement de température extérieure ne va pas avoir un impact immédiat sur la demande en froid. À cause de l'inertie themique des bâtiments, le besoin de froid réagit plutôt à une température "ressentie" qui est une moyenne poudéré de la température extérieure sur les 24 dernières heures.
$$ T_{t}^{(felt)} = \frac{ \sum_{s = 0}^{23} 0.5^s \cdot T_{t-s}^{(ext)}}{\sum_{s = 0}^{23} 0.5^s} \left( = [\overline{T_{ext}[t]}]^{24h}\right) $$
Extraction de la partie positive de la différence de température (.clip(0)).
Lorsque la température ressentie est suffisament basse le besoin de froid devient nul. L'idée est que si la climatisation est réglée sur 22 °C dans les bâtiments et que la température "ressentie" (donc indirectement la température extérieure) alors l'aération suffira à maintenir le bâtiment à la température souhaitée.
$$ \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ = \max(0, T_{t}^{(felt)} - T^{(cons)}) $$
Ventilation, où $\frac{1}{|y|} \int_y \left( T_{t}^{(felt)} - T^{(cons)} \right)_+$ est la valeur annuelle moyenne de $\left( T_{t}^{(felt)} - T^{(cons)} \right)_+$ pour l'année $y$ (.resample("YS").transform("mean"))
La consommation de froid sert à équilibrer un flux de froid sortant vers un extérieur plus chaud. Ce flux étant globalement proportionnel à la différence de température entre l'intérieur et l'extérieur, la consommation de froid se répartit dans le temps proportionnellement par rapport à cette différence.
$$ P_t = P_y \cdot \frac{ \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ \cdot \mathbb{1}_{t \in \mathcal{C}} }{ \frac{1}{|y|} \int_y \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ \cdot \mathbb{1}_{t \in \mathcal{C}} } $$
Où $\mathcal{C}$ est la période de saison de refroidissement.
On obtient : $P_t$ la puissance en froid à l'instant $t$ (en kW).
from heatpro.external_factors.process.heating_season import non_heating_season_basic
cooling_season = pd.Series(False, outdoor_temperature.index, name="cooling_season")
for year in cooling_season.index.year.unique():
start_, end_ = non_heating_season_basic(
outdoor_temperature.loc[str(year)], SET_TEMPERATURE_COLD, 0.45
)
cooling_season.loc[start_:end_] = True
cooling_season.astype(int).plot()
power_cold_demand = year_to_hour_outdoor_temperarure_distribution(
yearly_power_cold_demand_kw_reindexed, felt_temperature, cooling_season.astype(int)
)
power_cold_demand.plot().show()
power_cold_demands["Depuis une demande<br>annuelle<br>Avec délimitation de saison"] = (
power_cold_demand.copy()
)
Calcul en partant d'une demande mensuelle¶
On définit : $E_m$ l'énergie mensuelle en froid consommée sur le mois $m$ (en kWh) et $P_m$ la puissance mensuelle en froid moyenne sur l'année $m$ (en kW).
$$ P_m = \frac{E_m}{|m|} $$
Où $|m|$ est la durée du mois $m$ en heure.
MONTHLY_ENERGY_COLD_DEMAND_KWH = pd.Series(
{
datetime(year=2021, month=1, day=1): 0.0,
datetime(year=2021, month=6, day=1): 35_000.0,
datetime(year=2021, month=7, day=1): 45_000.0,
datetime(year=2021, month=8, day=1): 50_000.0,
datetime(year=2021, month=9, day=1): 20_000.0,
datetime(year=2021, month=10, day=1): 0.0,
datetime(year=2022, month=6, day=1): 40_000.0,
datetime(year=2022, month=7, day=1): 50_000.0,
datetime(year=2022, month=8, day=1): 60_000.0,
datetime(year=2022, month=9, day=1): 25_000.0,
datetime(year=2022, month=10, day=1): 0.0,
datetime(year=2023, month=6, day=1): 35_000.0,
datetime(year=2023, month=7, day=1): 48_000.0,
datetime(year=2023, month=8, day=1): 53_000.0,
datetime(year=2023, month=8, day=1): 24_000.0,
datetime(year=2023, month=10, day=1): 0.0,
},
name="monthly_energy_cold_demand_kwh",
)
monthly_power_cold_demand_kw = MONTHLY_ENERGY_COLD_DEMAND_KWH / (
MONTHLY_ENERGY_COLD_DEMAND_KWH.index.days_in_month * 24
)
outdoor_temperature = pd.read_csv("../data/external_factors.csv", index_col=0, parse_dates=True)[
"external_temperature"
]
monthly_power_cold_demand_kw_reindexed = monthly_power_cold_demand_kw.reindex(
outdoor_temperature.index, method="ffill"
)
go.Figure(
[
go.Scatter(
x=outdoor_temperature.index,
y=outdoor_temperature,
name="outdoor temperature",
showlegend=False,
yaxis="y1",
),
go.Scatter(
x=monthly_power_cold_demand_kw_reindexed.index,
y=monthly_power_cold_demand_kw_reindexed,
name="yearly energy",
showlegend=False,
yaxis="y2",
),
],
).update_layout(
yaxis1=dict(title="outdoor temperature (°C)", side="left"),
yaxis2=dict(title="yearly energy (kWh)", overlaying="y", side="right"),
).show()
SET_TEMPERATURE_COLD = 22
Sans prise en compte de saison de refroidissement¶
La ventialtion suit les formules suivantes:
Calcul de la température "ressentie" par une moyenne mobile exponnentielle sur 24 heures (.ewm(24).mean()).
Un changement de température extérieure ne va pas avoir un impact immédiat sur la demande en froid. À cause de l'inertie themique des bâtiments, le besoin de froid réagit plutôt à une température "ressentie" qui est une moyenne poudéré de la température extérieure sur les 24 dernières heures.
$$ T_{t}^{(felt)} = \frac{ \sum_{s = 0}^{23} 0.5^s \cdot T_{t-s}^{(ext)}}{\sum_{s = 0}^{23} 0.5^s} \left( = [\overline{T_{ext}[t]}]^{24h}\right) $$
Extraction de la partie positive de la différence de température (.clip(0)).
Lorsque la température ressentie est suffisament basse le besoin de froid devient nul. L'idée est que si la climatisation est réglée sur 22 °C dans les bâtiments et que la température "ressentie" (donc indirectement la température extérieure) alors l'aération suffira à maintenir le bâtiment à la température souhaitée.
$$ \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ = \max(0, T_{t}^{(felt)} - T^{(cons)}) $$
Ventilation, où $\frac{1}{|m|} \int_m \left( T_{t}^{(felt)} - T^{(cons)} \right)_+$ est la valeur mensuelle moyenne de $\left( T_{t}^{(felt)} - T^{(cons)} \right)_+$ pour le mois $m$ (.resample("MS").transform("mean"))
La consommation de froid sert à équilibrer un flux de froid sortant vers un extérieur plus chaud. Ce flux étant globalement proportionnel à la différence de température entre l'intérieur et l'extérieur, la consommation de froid se répartit dans le temps proportionnellement par rapport à cette différence.
$$ P_t = P_m \cdot \frac{ \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ }{ \frac{1}{|m|} \int_m \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ } $$
On obtient : $P_t$ la puissance en froid à l'instant $t$ (en kW).
felt_temperature = calculate_felt_temperature(outdoor_temperature)
((outdoor_temperature.ewm(24).mean() - SET_TEMPERATURE_COLD).clip(0)).plot().show()
(
(outdoor_temperature.ewm(24).mean() - SET_TEMPERATURE_COLD).clip(0)
/ (outdoor_temperature.ewm(24).mean() - SET_TEMPERATURE_COLD)
.clip(0)
.resample("MS")
.transform("mean")
).plot().show()
(
monthly_power_cold_demand_kw_reindexed
* (outdoor_temperature.ewm(24).mean() - SET_TEMPERATURE_COLD).clip(0)
/ (outdoor_temperature.ewm(24).mean() - SET_TEMPERATURE_COLD)
.clip(0)
.resample("MS")
.transform("mean")
).plot().show()
power_cold_demand = month_to_hour_outdoor_temperarure_distribution(
monthly_power_cold_demand_kw_reindexed,
felt_temperature,
)
power_cold_demands["Depuis une demande<br>mensuelle<br>Sans délimitation de saison"] = (
power_cold_demand.copy()
)
go.Figure(
[
go.Scatter(
x=outdoor_temperature.index,
y=outdoor_temperature,
name="outdoor temperature",
showlegend=False,
yaxis="y1",
),
go.Scatter(
x=power_cold_demand.index,
y=power_cold_demand,
name="pwoer",
showlegend=False,
yaxis="y2",
),
],
).update_layout(
yaxis1=dict(title="outdoor temperature (°C)", side="left"),
yaxis2=dict(title="load (kW)", overlaying="y", side="right"),
).show()
power_cold_demand.resample("MS").sum()
2021-01-01 0.0 2021-02-01 0.0 2021-03-01 0.0 2021-04-01 0.0 2021-05-01 0.0 2021-06-01 35000.0 2021-07-01 45000.0 2021-08-01 50000.0 2021-09-01 20000.0 2021-10-01 0.0 2021-11-01 0.0 2021-12-01 0.0 2022-01-01 0.0 2022-02-01 0.0 2022-03-01 0.0 2022-04-01 0.0 2022-05-01 0.0 2022-06-01 40000.0 2022-07-01 50000.0 2022-08-01 60000.0 2022-09-01 25000.0 2022-10-01 0.0 2022-11-01 0.0 2022-12-01 0.0 Freq: MS, dtype: float64
power_cold_demand.resample("YS").sum()
2021-01-01 150000.0 2022-01-01 175000.0 Freq: YS-JAN, dtype: float64
Calcul en ajoutant un profil hebdomadaire¶
Calcul de la température "ressentie" par une moyenne mobile exponnentielle sur 24 heures (.ewm(24).mean()).
Un changement de température extérieure ne va pas avoir un impact immédiat sur la demande en froid. À cause de l'inertie themique des bâtiments, le besoin de froid réagit plutôt à une température "ressentie" qui est une moyenne poudéré de la température extérieure sur les 24 dernières heures.
$$ T_{t}^{(felt)} = \frac{ \sum_{s = 0}^{23} 0.5^s \cdot T_{t-s}^{(ext)}}{\sum_{s = 0}^{23} 0.5^s} \left( = [\overline{T_{ext}[t]}]^{24h}\right) $$
Extraction de la partie positive de la différence de température (.clip(0)).
Lorsque la température ressentie est suffisament basse le besoin de froid devient nul. L'idée est que si la climatisation est réglée sur 22 °C dans les bâtiments et que la température "ressentie" (donc indirectement la température extérieure) alors l'aération suffira à maintenir le bâtiment à la température souhaitée.
$$ \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ = \max(0, T_{t}^{(felt)} - T^{(cons)}) $$
Ventilation, où $\frac{1}{|m|} \int_m \left( T_{t}^{(felt)} - T^{(cons)} \right)_+$ est la valeur mensuelle moyenne de $\left( T_{t}^{(felt)} - T^{(cons)} \right)_+$ pour le mois $m$ (.resample("MS").transform("mean"))
La consommation de froid sert à équilibrer un flux de froid sortant vers un extérieur plus chaud. Ce flux étant globalement proportionnel à la différence de température entre l'intérieur et l'extérieur, la consommation de froid se répartit dans le temps proportionnellement par rapport à cette différence.
$$ P_t = P_m \cdot \frac{ \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ \cdot \text{WP}(t) }{ \frac{1}{|m|} \int_m \left( T_{t}^{(felt)} - T^{(cons)} \right)_+ \cdot \text{WP}(t) } $$
Où WP est la fonction de "Week Profile" qui va pondérer selon les instants $t$ la consommation de froid.
On obtient : $P_t$ la puissance en froid à l'instant $t$ (en kW).
def week_profile(day: int, hour: int) -> float:
if day in [5, 6]:
if 9 <= hour <= 19:
return 0.6
if 9 <= hour <= 19:
return 1.0
return 0.2
week_profile_weigth = apply_week_profile(felt_temperature.index, week_profile)
power_cold_demand = month_to_hour_outdoor_temperarure_distribution(
monthly_power_cold_demand_kw_reindexed,
felt_temperature,
week_profile_weigth,
)
power_cold_demand.plot().show()
power_cold_demands["Depuis une demande<br>mensuelle<br>avec un profil hebdomadaire"] = (
power_cold_demand.copy()
)
power_cold_demand.resample("MS").sum()
2021-01-01 0.0 2021-02-01 0.0 2021-03-01 0.0 2021-04-01 0.0 2021-05-01 0.0 2021-06-01 35000.0 2021-07-01 45000.0 2021-08-01 50000.0 2021-09-01 20000.0 2021-10-01 0.0 2021-11-01 0.0 2021-12-01 0.0 2022-01-01 0.0 2022-02-01 0.0 2022-03-01 0.0 2022-04-01 0.0 2022-05-01 0.0 2022-06-01 40000.0 2022-07-01 50000.0 2022-08-01 60000.0 2022-09-01 25000.0 2022-10-01 0.0 2022-11-01 0.0 2022-12-01 0.0 Freq: MS, dtype: float64
Affichage de toutes les variantes¶
go.Figure(
[go.Scatter(x=power.index, y=power, name=name) for name, power in power_cold_demands.items()]
).show()