Draf stands for "Demand Response Analysis Framework" and is an easy-to-use open source decision support tool that enables the economic and ecological evaluation of industrial demand response potential. It is devoloped in Python and uses the power of Gurobi to build and solve multi-objective optimization models of industrial energy systems and production processes to quantify the cost and green-house-gas saving potentials of price based demand response. Target groups are researchers, engineers, and decision-makers of companies in industrial or commercial sectors with some basic knowledge of the Python programming language.
Draf is developed by Markus Fleschutz in a cooperative PhD between Cork Institute of Technology and the Karlsruhe University of Applied Sciences and under the supervision of Dr. Michael D. Murphy and Dr.-Ing Marco Braun. More information can be found on the website.
Draf combines several task areas that are important for the quantification of demand response potentials. The most important ones are:
We start with a straightforward model. Let's say we have a 100 kWpeak Photovoltaic System (PV), a 1 MWhel Battery Energy System (BES), and given electricity demand and want to know the cost and carbon reduction potential for price based demand response considering a 50 €/kW peak-load price and a day-ahead market price. We can use the "PV_BES"-model of draf:
These few lines of codes import the model PV_BES
, initializes a case study, adds a reference scenario, sets the default parameters from the PV_BES
-model, changes the user-defined parameters P_PV_CAPx_
(existing 'PV power) and c_GRID_peak_
(electricity peak price), builds 8 scenarios (2 pricing tariffs with 4 pareto points each), improves the pareto weighting factors so that the pareto points are more equally distributed, sets the model, optimizes it and saves the whole case study.
import draf
from models import PV_BES as mod
cs = draf.CaseStudy(name="ShowCase", year=2017, freq="60min", country="DE")
cs.add_REF_scen(doc="no BES").set_params(mod.params_func).update_params(
P_PV_CAPx_=100, c_GRID_peak_=50)
cs.add_scens([("c_GRID_T", "t", ["c_GRID_RTP_T", "c_GRID_TOU_T"]),
("E_BES_CAPx_", "b", [1000])], nParetoPoints=4)
cs.improve_pareto_and_set_model(mod.model_func).optimize(mod.postprocess_func).save();
Using license file D:\Programme\Anaconda3\gurobi.lic Academic license - for non-commercial use only Successfully solved 9 scenarios with an average solving time of 0.793 seconds. CaseStudy saved to D:/mf/draf/results/ShowCase/2019-12-10_143654_.p ( 24.3 MB)
The pareto curve is the main resulting diagram of the draf-analysis. It plots the total costs versus the total carbon emissions for all scenarios. Scenarios can be grouped according to key-words in order to help identify individual pareto fronts. Here, the pareto-weighting factors are denoted with α.
cs.plot.pareto_curves(groups=["REF", "TOU", "RTP"], label_verbosity=4)
This table shows all parameters of all scenarios with its values, unit and description.
cs.plot.table("p")
Note: Bold font indicate deviation from first/reference scenario.
REF | sc1 | sc2 | sc3 | sc4 | sc5 | sc6 | sc7 | sc8 | unit | doc | |
---|---|---|---|---|---|---|---|---|---|---|---|
p | |||||||||||
alpha_ | 0 | 0 | 0.333333 | 0.666667 | 1 | 0 | 0.333333 | 0.666667 | 1 | pareto weighting factor | |
n_C_ | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | 1.38897e-05 | normalization factor | |
n_CE_ | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | 4.31839e-09 | normalization factor | |
c_GRID_peak_ | 50 | 50 | 50 | 50 | 50 | 50 | 50 | 50 | 50 | €/kW_el | peak price |
P_PV_CAPx_ | 100 | 100 | 100 | 100 | 100 | 100 | 100 | 100 | 100 | kW_peak | existing capacity PV |
E_BES_CAPx_ | 0 | 1000 | 1000 | 1000 | 1000 | 1000 | 1000 | 1000 | 1000 | kW_el | existing capacity BES |
eta_BES_time_ | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | storing efficiency | |
eta_BES_in_ | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | 0.999 | loading efficiency | |
k_BES_in_per_capa_ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ratio of charging power / capacity | |
k_BES_out_per_capa_ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ratio of discharging power / capacity (aka C rate) | |
timelog_params_ | 0.838508 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | seconds | time for building the params |
timelog_model_ | 2.79629 | 2.08576 | 2.0242 | 2.09764 | 2.09404 | 2.35387 | 2.24089 | 2.36363 | 2.07149 | seconds | time for building the model |
mipGap_ | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | % | Gap to relaxed solution |
timelog_solve_ | 0.324877 | 1.15911 | 1.09521 | 1.14603 | 0.508448 | 0.905575 | 0.906073 | 0.782783 | 0.310037 | seconds | time for solving the model |
For all non-scalars entities, the describe
function gives an overview of one ore many scenarios:
cs.plot
<draf.plotting.cs_plotting.CsPlotter at 0x26543d2a308>
sc = cs.scens.sc1
sc.plot.describe(include_vars=False)
================================================================================ tc_GRID_RTP_T_b1000_a0.0: Parameters doc = c_GRID_T=c_GRID_RTP_T; E_BES_CAPx_=1000; alpha_=0.0 ================================================================================ --------- alpha_ 0.00000 n_C_ 0.00001 n_CE_ 0.00000 c_GRID_peak_ 50.00000 €/kW_el P_PV_CAPx_ 100.00000 kW_peak E_BES_CAPx_ 1.00000 MW_el eta_BES_time_ 0.99900 eta_BES_in_ 0.99900 k_BES_in_per_capa_ 1.00000 k_BES_out_per_capa_ 1.00000 timelog_params_ 0.00000 seconds timelog_model_ 2.08576 seconds mipGap_ 0.10000 % timelog_solve_ 1.15911 seconds T --------- sum mean min max \ ce_GRID_T 4.835891e+06 552.042355 168.163680 846.098705 E_dem_T 5.000000e+05 57.077626 40.242549 76.965997 c_GRID_addon_T 1.148261e+03 0.131080 0.131080 0.131080 E_PV_profile_T 9.415523e+02 0.107483 0.000000 0.814534 c_GRID_TOU_T 2.994915e+02 0.034189 0.029372 0.043550 c_GRID_RTP_T 2.994915e+02 0.034189 -0.083060 0.163520 c_GRID_T 2.994915e+02 0.034189 -0.083060 0.163520 ce_GRID_T gCO2eq/kWh_el E_dem_T kWh_el c_GRID_addon_T €/kWh_el E_PV_profile_T kW_el/kW_peak c_GRID_TOU_T €/kWh_el c_GRID_RTP_T €/kWh_el c_GRID_T €/kWh_el
Heatmaps are the perfect plot type to see seasonal and daily patterns of a whole year's timeseries.
sc.plot.heatmap(ent_name="E_dem_T")
sc.plot.heatmap(ent_name="E_PV_T")
sc.plot.heatmap(ent_name="c_GRID_RTP_T")
sc.plot.heatmap(ent_name="ce_GRID_T")
sc.plot.heatmap(ent_name="E_BES_in_T")
sc.plot.heatmap(ent_name="E_BES_out_T")
This code produces a table of all scalar variables of the model with its resulting values, units, and descriptions.
cs.plot.table("v")
Note: Bold font indicate deviation from first/reference scenario.
REF | sc1 | sc2 | sc3 | sc4 | sc5 | sc6 | sc7 | sc8 | unit | doc | |
---|---|---|---|---|---|---|---|---|---|---|---|
v | |||||||||||
C_ | 71995.9 | 69008.8 | 69770 | 72336.9 | 118511 | 69541.1 | 70940 | 75039.7 | 121804 | €/a | total costs |
C_inv_ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | € | investment costs |
C_op_ | 71995.9 | 69008.8 | 69770 | 72336.9 | 118511 | 69541.1 | 70940 | 75039.7 | 121804 | €/a | operating costs |
CE_ | 2.31568e+08 | 2.14108e+08 | 2.01786e+08 | 1.93712e+08 | 1.89929e+08 | 2.28803e+08 | 2.07706e+08 | 1.94421e+08 | 1.90349e+08 | gCO2eq/a | total emissions |
C_GRID_peak_ | 3799.13 | 3732.58 | 4930.15 | 7867.6 | 53522.1 | 2943.47 | 3843.64 | 7214.17 | 53553.6 | €/a | costs for peak price |
P_GRID_peak_ | 75.9826 | 74.6517 | 98.603 | 157.352 | 1070.44 | 58.8694 | 76.8728 | 144.283 | 1071.07 | kW_el | peak electricity |
C_GRID_buy_ | 68210.1 | 65276.2 | 64839.8 | 64469.3 | 64988.9 | 66597.6 | 67096.4 | 67825.5 | 68250.3 | €/a | cost for bought electricity |
C_GRID_sell_ | 13.4039 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | €/a | earnings for bought electricity |
cs.scens.sc1.plot.describe(include_pars=False)
================================================================================ tc_GRID_RTP_T_b1000_a0.0: Variables doc = c_GRID_T=c_GRID_RTP_T; E_BES_CAPx_=1000; alpha_=0.0 ================================================================================ --------- C_ 69.00879 Thousand €/a C_inv_ 0.00000 € C_op_ 69.00879 Thousand €/a CE_ 214.10811 tCO2eq/a C_GRID_peak_ 3.73258 Thousand €/a P_GRID_peak_ 74.65168 kW_el C_GRID_buy_ 65.27621 Thousand €/a C_GRID_sell_ 0.00000 €/a T --------- sum mean min max E_BES_T 2.548201e+06 290.890549 0.0 1000.000000 kWh_el E_GRID_buy_T 4.083512e+05 46.615433 0.0 74.651680 kWh_el E_BES_in_T 1.523552e+05 17.392150 0.0 101.877687 kWh_el E_BES_out_T 1.498488e+05 17.106028 -0.0 75.982645 kWh_el E_PV_T 9.415523e+04 10.748314 0.0 81.453351 kWh_el E_PV_OC_T 9.415523e+04 10.748314 0.0 81.453351 kWh_el E_GRID_sell_T 0.000000e+00 0.000000 0.0 0.000000 kWh_el E_PV_FI_T 0.000000e+00 0.000000 0.0 0.000000 kWh_el
cs.plot.pareto_curves(groups=["REF", "TOU", "RTP"])
The sankey plot shows the yearly energy sums of key energy flows from left (source) to right (cradle). You can hover over to see the numbers:
cs.scens.sc1.plot.sankey(mod.sankey_func)
At any time, you can save the case study to hard disk like so:
cs.save("test");
CaseStudy saved to D:/mf/draf/results/ShowCase/2019-12-10_143657_test.p ( 33.7 MB)
... and open it later again:
import draf
cs = draf.CaseStudy(fp="D:/mf/draf/results/ShowCase/2019-12-10_143657_test.p")
Draf is compatible with the powerful xarray
-module. Here, parameters and results are comined into one big dataset:
cs.scens.REF.get_xarray_dataset()
<xarray.Dataset> Dimensions: (T: 8760) Coordinates: * T (T) int64 0 1 2 3 4 5 6 ... 8754 8755 8756 8757 8758 8759 Data variables: E_GRID_buy_T (T) float64 44.33 46.08 43.76 41.85 ... 47.83 45.88 43.15 E_GRID_sell_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 E_PV_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 E_PV_OC_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 E_PV_FI_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 E_BES_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 E_BES_in_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 E_BES_out_T (T) float64 -0.0 -0.0 -0.0 -0.0 -0.0 ... -0.0 -0.0 -0.0 -0.0 ce_GRID_T (T) float64 507.9 518.0 514.5 525.0 ... 241.2 253.3 241.1 c_GRID_RTP_T (T) float64 0.02096 0.0209 0.01813 ... 0.00186 -0.00092 c_GRID_TOU_T (T) float64 0.02937 0.02937 0.02937 ... 0.02937 0.02937 c_GRID_T (T) float64 0.02096 0.0209 0.01813 ... 0.00186 -0.00092 c_GRID_addon_T (T) float64 0.1311 0.1311 0.1311 ... 0.1311 0.1311 0.1311 E_dem_T (T) float64 44.33 46.08 43.76 41.85 ... 47.83 45.88 43.15 E_PV_profile_T (T) float64 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0
Draf provides day ahead spot market prices for several European countries and years.
sc = cs.scens.REF
sc.plot.fig_size = (12,1)
sc.prep.add_c_GRID_RTP_T()
sc.plot.heatmap(ent_name="c_GRID_RTP_T")
Draf provides dynamic carbon emission factors for several European countries and years.
sc.prep.add_ce_GRID_T(name="ce_GRID_avg_T", method="average")
sc.plot.heatmap(ent_name="ce_GRID_avg_T")
sc.prep.add_ce_GRID_T(name="ce_GRID_mar_T", method="marginal")
sc.plot.heatmap(ent_name="ce_GRID_mar_T")
Draf provides some useful tools for demand estimation. These are especially useful when only aggregated values are availible.
First, we prepare an electricity demand from a industrial standard load profile "G1" which stands for Business on weekdays 08:00 - 18:00 and an given annual energy demand of 5 GWh/a.
sc = draf.CaseStudy(year=2017).add_REF_scen()
sc.prep.add_E_dem_T(profile="G1", annual_energy=5e6, holidays_province="BY")
sc.plot.fig_size = (12,1)
sc.plot.heatmap(ent_name="E_dem_T")
sc.params.E_dem_T.sum()
5000000.0
Now let's create an heat demand timeseries from the annual energy, the target_temperatur, the heating threshold and the location where the ambient temperature is taken.
sc.prep.add_H_dem_T(annual_energy=5e6, target_temp=20.0, threshold_temp=10.0, location='Rheinstetten')
sc.plot.heatmap(ent_name="H_dem_T")
This is the ambient temperature that was used as basis:
sc.plot.heatmap(timeseries=draf.prep.io.get_amb_temp(year=cs.year, freq=cs.freq, location="Rheinstetten"))
Draf comes with some out-of-the-box models for different relevant aggregates in the industrial environment that can easily be parameterized. One of the models is "DER_HUT" - Distribted Energy Ressources(DER) and Heat upgrading Technologies (HUT). Here is a schematic:
In the following, access and preview of draf-objects (CaseStudy, Scenario, etc.) is demonstrated.
cs
<CaseStudy object> • name: ShowCase • doc: No doc available. • freq: 60min • country: DE • is_dry_run: False • scens: [...] • plot: <draf.plotting.cs_plotting.CsPlotter object at 0x0000026543D2A308> • dims: <Dimensions object> (empty) • params: <Params object> (empty) • obj_vars: ['C_', 'CE_'] • year: 2017 • dtindex: [...] • dtindex_custom: [...] • steps_per_day: 24 • scen_vars: [('c_GRID_T', 't', ['c_GRID_RTP_T', 'c_GRID_TOU_T']), ('E_BES_CAPx_', 'b', [1000]), ('alpha_', 'a', array([0. , 0.33333333, 0.66666667, 1. ]))] • scen_df: [...] • dt_info: ⤷ t1 = 0 (Sunday, 2017-01-01 00:00:00), ⤷ t2 = 8759 (Sunday, 2017-12-31 23:00:00) ⤷ Length = 8760
cs.scens
<Scenarios object> name doc ⤷ REF | no BES ⤷ sc1 | c_GRID_T=c_GRID_RTP_T; E_BES_CAPx_=1000; alpha_=0.0 ⤷ sc2 | c_GRID_T=c_GRID_RTP_T; E_BES_CAPx_=1000; alpha_=0.3333333333333333 ⤷ sc3 | c_GRID_T=c_GRID_RTP_T; E_BES_CAPx_=1000; alpha_=0.6666666666666666 ⤷ sc4 | c_GRID_T=c_GRID_RTP_T; E_BES_CAPx_=1000; alpha_=1.0 ⤷ sc5 | c_GRID_T=c_GRID_TOU_T; E_BES_CAPx_=1000; alpha_=0.0 ⤷ sc6 | c_GRID_T=c_GRID_TOU_T; E_BES_CAPx_=1000; alpha_=0.3333333333333333 ⤷ sc7 | c_GRID_T=c_GRID_TOU_T; E_BES_CAPx_=1000; alpha_=0.6666666666666666 ⤷ sc8 | c_GRID_T=c_GRID_TOU_T; E_BES_CAPx_=1000; alpha_=1.0
cs.scens.REF
<Scenario object> • id: REF • name: REF • doc: no BES • is_dry_run: False • dims: [...] • params: [...] • plot: <draf.plotting.scen_plotting.ScenPlotter object at 0x0000026543E0E4C8> • prep: <draf.prep.params_prepping.Prepper object at 0x0000026543E0EF08> • vars: [...] • dtindex: [...] • dtindex_custom: [...] • res: [...]
cs.scens.REF.params
<Params object> name dims unit doc ⤷ alpha_ | |pareto weighting factor ⤷ n_C_ | |normalization factor ⤷ n_CE_ | |normalization factor ⤷ ce_GRID_T T|gCO2eq/kWh_el|Average carbon emission factors for 2017, 60min, DE. Source:entsoe ⤷ c_GRID_peak_ | €/kW_el|peak price ⤷ c_GRID_RTP_T T| €/kWh_el|Day-ahead-market-prices 2017, 60min, DE ⤷ c_GRID_TOU_T T| €/kWh_el|Time-Of-Use-tariff with the prices 0.029€ and 0.044€ ⤷ c_GRID_T T| €/kWh_el|chosen electricity tariff ⤷ c_GRID_addon_T T| €/kWh_el|add-on electricity price component ⤷ E_dem_T T| kWh_el|electricity demand from standard load profile G3 ⤷ P_PV_CAPx_ | kW_peak|existing capacity PV ⤷ E_PV_profile_T T|kW_el/kW_peak|produced PV-power for 1 kW_peak ⤷ E_BES_CAPx_ | kW_el|existing capacity BES ⤷ eta_BES_time_ | |storing efficiency ⤷ eta_BES_in_ | |loading efficiency ⤷ k_BES_in_per_capa_ | |ratio of charging power / capacity ⤷ k_BES_out_per_capa_ | |ratio of discharging power / capacity (aka C rate) ⤷ timelog_params_ | seconds|time for building the params ⤷ timelog_model_ | seconds|time for building the model ⤷ mipGap_ | %|Gap to relaxed solution ⤷ timelog_solve_ | seconds|time for solving the model ⤷ ce_GRID_avg_T T|gCO2eq/kWh_el|Average carbon emission factors for 2017, 60min, DE. Source:entsoe ⤷ ce_GRID_mar_T T|gCO2eq/kWh_el|Marginal carbon emission factors for 2017, 60min, DE. Source:entsoe
choose one of them, e.g. P_PV_CAPx_
:
cs.scens.REF.params.P_PV_CAPx_
100
cs.scens.REF.res
<Results object> name dims unit doc ⤷ C_ | €/a|total costs ⤷ C_inv_ | €|investment costs ⤷ C_op_ | €/a|operating costs ⤷ CE_ | gCO2eq/a|total emissions ⤷ C_GRID_peak_ | €/a|costs for peak price ⤷ E_GRID_buy_T T| kWh_el|bought el. from the grid ⤷ E_GRID_sell_T T| kWh_el|sold el. to the grid ⤷ P_GRID_peak_ | kW_el|peak electricity ⤷ C_GRID_buy_ | €/a|cost for bought electricity ⤷ C_GRID_sell_ | €/a|earnings for bought electricity ⤷ E_PV_T T| kWh_el|produced electricity ⤷ E_PV_OC_T T| kWh_el|own consumption ⤷ E_PV_FI_T T| kWh_el|feed-in ⤷ E_BES_T T| kWh_el|electricity stored ⤷ E_BES_in_T T| kWh_el|electricity charged ⤷ E_BES_out_T T| kWh_el|electricity uncharged
choose one of them, e.g. C_
:
cs.scens.REF.res.C_
71995.86036638569