From de03da5231890a112ad19fba894518ea905dd22e Mon Sep 17 00:00:00 2001 From: nader-00 Date: Tue, 16 Jun 2026 12:12:13 +0200 Subject: [PATCH 1/2] Replace fixed CP/HP worst-case simultaneity factors with n-dependent curves --- edisgo/config/config_timeseries_default.cfg | 25 +++ edisgo/network/timeseries.py | 206 +++++++++++++++++++- tests/network/test_timeseries.py | 174 +++++++++++++++-- 3 files changed, 383 insertions(+), 22 deletions(-) diff --git a/edisgo/config/config_timeseries_default.cfg b/edisgo/config/config_timeseries_default.cfg index 84c6074bc..889c89261 100644 --- a/edisgo/config/config_timeseries_default.cfg +++ b/edisgo/config/config_timeseries_default.cfg @@ -82,6 +82,31 @@ lv_feed-in_case_hp = 0.0 mv_load_case_hp = 0.8 lv_load_case_hp = 1.0 +[simultaneity_curves] +# Stützstellen für anzahl- und ladeleistungsabhängige Gleichzeitigkeitsfaktoren +# für Ladepunkte (CP) im Lastfall. +# Quelle: VDE FNN (2021): "Ermittlung von Gleichzeitigkeitsfaktoren für +# Ladevorgänge an privaten Ladepunkten", Planungshilfe. +# Gewählter Fall: vorstädtisch, Wohngebiet, Abends (höchste GZF = konservativer Worst Case). +# Interpolation über n (logarithmisch). Für n > 150 wird der Faktor bei n = 150 +# beibehalten (Clamping), da die VDE-FNN-Studie nur bis n = 150 reicht. +cp_simultaneity_n = 1, 5, 10, 20, 50, 150 +cp_simultaneity_3.7kW = 1.0, 1.0, 0.8, 0.55, 0.4, 0.313 +cp_simultaneity_11kW = 1.0, 0.8, 0.6, 0.35, 0.26, 0.173 +cp_simultaneity_22kW = 1.0, 0.8, 0.5, 0.3, 0.18, 0.12 +cp_simultaneity_power_classes = 3.7, 11, 22 + +# --------------------------------------------------------------------------- +# Wärmepumpen (HP): anzahlabhängiger Gleichzeitigkeitsfaktor (eindimensional). +# Mangels einer der VDE-FNN-EV-Studie äquivalenten Tabellenquelle wurden die +# Stützstellen über die Kerber-Gleichzeitigkeitsfunktion berechnet: +# g(n) = g_inf + (1 - g_inf) * n^(-3/4) (Kerber 2011, Gl. 3.2, S. 23) +# WP-Grenzwert g_inf = 0.9 (gestützt auf Consentec/E.ON-Verteilnetzstudie, +# Starklastfall kalter Wintertag). Gleiches Stützstellenraster und Clamping +# (n > 150 -> Faktor bei n = 150) wie bei den Ladepunkten. +hp_simultaneity_n = 1, 5, 10, 20, 50, 150 +hp_simultaneity_factor = 1.0, 0.93, 0.92, 0.91, 0.9, 0.9 + [reactive_power_factor] # power factors diff --git a/edisgo/network/timeseries.py b/edisgo/network/timeseries.py index ba6974123..e91701b6d 100644 --- a/edisgo/network/timeseries.py +++ b/edisgo/network/timeseries.py @@ -31,6 +31,161 @@ logger = logging.getLogger(__name__) +def _parse_float_list(value: str) -> np.ndarray: + """ + Parse a comma-separated config string into a float array. + + Used to read simultaneity curve support points (``n`` and factor values) + from the ``[simultaneity_curves]`` config section, where eDisGo's config + parser keeps comma-separated values as raw strings. + + Parameters + ---------- + value : str + Comma-separated string, e.g. ``"1, 5, 10, 20, 50, 150"``. + + Returns + ------- + :numpy:`numpy.ndarray` + Parsed float values. + """ + return np.array([float(x.strip()) for x in value.split(",")]) + + +def _interp_log_n(n: int, ns: np.ndarray, factors: np.ndarray) -> float: + """ + Interpolate a simultaneity factor in log-space over device count ``n``. + + Shared by :func:`_cp_simultaneity_factor` and + :func:`_hp_simultaneity_factor`. Interpolation is performed on + ``log(n)`` because the underlying support points are logarithmically + spaced and simultaneity factors decay roughly logarithmically with + device count. ``numpy.interp`` clamps to ``factors[0]`` / ``factors[-1]`` + for ``n`` outside the range of ``ns`` (no extrapolation). + + Parameters + ---------- + n : int + Device count to interpolate at. Must be >= 1. + ns : :numpy:`numpy.ndarray` + Support points for device count (ascending). + factors : :numpy:`numpy.ndarray` + Simultaneity factor at each support point in `ns`. + + Returns + ------- + float + Interpolated (or clamped) simultaneity factor. + """ + return float(np.interp(np.log(n), np.log(ns), factors)) + + +def _cp_simultaneity_factor(n: int, charging_power_kw: float, config) -> float: + """ + Return the simultaneity factor for *n* charging points at a given nominal + charging power. + + Simultaneity factors are read from the ``[simultaneity_curves]`` section of + the eDisGo config and express the ratio of peak collective demand to the sum + of individual nominal powers as a function of device count. + + Interpolation is performed in log-space (``numpy.interp`` on ``log(n)``). + Clamping: for ``n`` below the smallest support point the factor of that + point is used; for ``n`` above the largest support point (n = 150 in the + default config) the factor at that point is used. This matches the + validity range of the source data. + + Source + ------ + VDE FNN (2021): "Ermittlung von Gleichzeitigkeitsfaktoren für Ladevorgänge + an privaten Ladepunkten", Planungshilfe. Chosen scenario: suburban + residential area, evening peak (highest simultaneity = conservative worst + case). + + Parameters + ---------- + n : int + Number of charging points in the group (same voltage level and use + case). Must be >= 1. + charging_power_kw : float + Nominal charging power per charging point in **kW**. The nearest + available power class from ``cp_simultaneity_power_classes`` is + selected. + config : :class:`~edisgo.tools.config.Config` + eDisGo configuration object containing section + ``simultaneity_curves`` with keys ``cp_simultaneity_n``, + ``cp_simultaneity_power_classes``, and one curve entry per power class + (e.g. ``cp_simultaneity_11kw``). + + Returns + ------- + float + Simultaneity factor in [0, 1]. + """ + curves = config["simultaneity_curves"] + + ns = _parse_float_list(curves["cp_simultaneity_n"]) + power_classes = _parse_float_list(curves["cp_simultaneity_power_classes"]) + + nearest_class = power_classes[np.argmin(np.abs(power_classes - charging_power_kw))] + key = f"cp_simultaneity_{nearest_class:g}kw" + factors = _parse_float_list(curves[key]) + + return _interp_log_n(n, ns, factors) + + +def _hp_simultaneity_factor(n: int, config) -> float: + """ + Return the simultaneity factor for *n* heat pumps. + + One-dimensional analogue of :func:`_cp_simultaneity_factor`: heat pumps + have no equivalent of a charging-power class, so the factor depends only + on device count. Simultaneity factors are read from the + ``[simultaneity_curves]`` section of the eDisGo config and express the + ratio of peak collective demand to the sum of individual nominal powers + as a function of device count. + + Interpolation is performed in log-space (``numpy.interp`` on ``log(n)``), + using the same method as for charging points. Clamping: for ``n`` below + the smallest support point the factor of that point is used; for ``n`` + above the largest support point (n = 150 in the default config) the + factor at that point is used (no extrapolation). + + Source + ------ + Support points are derived from the Kerber simultaneity function:: + + g(n) = g_inf + (1 - g_inf) * n ** (-3 / 4) + + (Kerber, G. (2011): "Aufnahmefähigkeit von Niederspannungsverteilnetzen + für die Einspeisung aus Photovoltaikkleinanlagen", TU München, eq. 3.2, + p. 23). The asymptotic value ``g_inf = 0.9`` is based on the + Consentec/E.ON distribution grid study assumption for the peak load case + on a cold winter day. + + Parameters + ---------- + n : int + Number of heat pumps in the group (same voltage level). Must be + >= 1. + config : :class:`~edisgo.tools.config.Config` + eDisGo configuration object containing section + ``simultaneity_curves`` with keys ``hp_simultaneity_n`` and + ``hp_simultaneity_factor``. + + Returns + ------- + float + Simultaneity factor in [0, 1]. + """ + curves = config["simultaneity_curves"] + + ns = _parse_float_list(curves["hp_simultaneity_n"]) + factors = _parse_float_list(curves["hp_simultaneity_factor"]) + + return _interp_log_n(n, ns, factors) + + class TimeSeries: """ Holds component-specific active and reactive power time series. @@ -996,9 +1151,32 @@ def _worst_case_charging_points(self, cases, df, configs): for s in sectors: for case in cases: for voltage_level in ["mv", "lv"]: - power_scaling.at[f"{case}_{voltage_level}", s] = ( - worst_case_scale_factors[f"{voltage_level}_{case}_cp_{s}"] - ) + if case == "feed-in_case": + # feed-in case unchanged: assumed 0.0 per config + power_scaling.at[f"{case}_{voltage_level}", s] = ( + worst_case_scale_factors[f"{voltage_level}_{case}_cp_{s}"] + ) + else: + # load_case: simultaneity factor depends on group size and + # mean charging power (source: VDE FNN 2021). + group = df[ + (df.voltage_level == voltage_level) & (df.sector == s) + ] + n = len(group) + if n == 0: + # 0.0 here means "no charging point exists for this + # voltage_level/sector combination", not the + # feed-in case's deliberately-zero config factor + # above; this is just an empty-group placeholder. + power_scaling.at[f"{case}_{voltage_level}", s] = 0.0 + else: + # For mixed-power groups the mean p_set is used for + # power-class selection (VDE FNN 2021, simplified + # assignment over mean charging power). + charging_power_kw = group["p_set"].mean() * 1000 + power_scaling.at[f"{case}_{voltage_level}", s] = ( + _cp_simultaneity_factor(n, charging_power_kw, configs) + ) # calculate active power of charging points active_power = pd.concat( @@ -1082,9 +1260,25 @@ def _worst_case_heat_pumps(self, cases, df, configs): power_scaling = pd.Series(dtype=float) for case in cases: for voltage_level in ["mv", "lv"]: - power_scaling.at[f"{case}_{voltage_level}"] = worst_case_scale_factors[ - f"{voltage_level}_{case}_hp" - ] + if case == "feed-in_case": + # feed-in case unchanged: assumed 0.0 per config + power_scaling.at[f"{case}_{voltage_level}"] = ( + worst_case_scale_factors[f"{voltage_level}_{case}_hp"] + ) + else: + # load_case: simultaneity factor depends on group size + # (source: Kerber 2011 / Consentec, see _hp_simultaneity_factor). + n = len(df[df.voltage_level == voltage_level]) + if n == 0: + # 0.0 here means "no heat pump exists in this + # voltage_level", not the feed-in case's + # deliberately-zero config factor above; this is just + # an empty-group placeholder. + power_scaling.at[f"{case}_{voltage_level}"] = 0.0 + else: + power_scaling.at[f"{case}_{voltage_level}"] = ( + _hp_simultaneity_factor(n, configs) + ) # calculate active power of heat pumps active_power = power_scaling.to_frame("p_set").dot(df.loc[:, ["p_set"]].T) diff --git a/tests/network/test_timeseries.py b/tests/network/test_timeseries.py index 15b9c1071..529b7ef0b 100644 --- a/tests/network/test_timeseries.py +++ b/tests/network/test_timeseries.py @@ -13,6 +13,7 @@ from edisgo import EDisGo from edisgo.network import timeseries +from edisgo.tools.config import Config from edisgo.tools.tools import assign_voltage_level_to_component @@ -401,10 +402,11 @@ def test_set_worst_case(self): check_dtype=False, ) # check charging point + # load_case_mv = 0.0: only LV HPC CP exists, MV-HPC group is empty (n=0) comp = "Load_residential_LVGrid_1_4" # lv, hpc p_set = 0.001397 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 0.0 * p_set, 1.0 * p_set], name=comp, index=timeindex, ) @@ -420,10 +422,15 @@ def test_set_worst_case(self): check_dtype=False, ) # check heat pump - comp = "Load_retail_MVGrid_1_Load_aggregated_retail_MVGrid_1_1" # mv + # despite the "MVGrid_1" name (refers to the parent grid hierarchy), + # this load's actual voltage_level is "lv" (bus=BusBar_lac_1). + # load_case_lv=1.0: only LV HP exists, group n=1 (was fixed 1.0, + # unchanged here); load_case_mv=0.0: MV-HP group is empty (n=0, was + # fixed 0.8) + comp = "Load_retail_MVGrid_1_Load_aggregated_retail_MVGrid_1_1" # lv p_set = 0.31 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 0.8 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 0.0 * p_set, 1.0 * p_set], name=comp, index=timeindex, ) @@ -895,10 +902,10 @@ def test_worst_case_charging_points(self): # check values index = ["feed-in_case_mv", "feed-in_case_lv", "load_case_mv", "load_case_lv"] - comp = "CP1" # mv, hpc + comp = "CP1" # mv, hpc — load_case_lv=0.0: no LV HPC CPs (n=0) p_set = 0.1 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 0.0 * p_set], name=comp, index=index, ) @@ -906,10 +913,10 @@ def test_worst_case_charging_points(self): pf = tan(acos(1.0)) assert_series_equal(q_ts.loc[:, comp], exp * pf, check_dtype=False) - comp = "CP2" # mv, public + comp = "CP2" # mv, public — load_case_lv=0.0: no LV public CPs (n=0) p_set = 0.2 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 0.0 * p_set], name=comp, index=index, ) @@ -917,10 +924,10 @@ def test_worst_case_charging_points(self): pf = tan(acos(1.0)) assert_series_equal(q_ts.loc[:, comp], exp * pf, check_dtype=False) - comp = "CP3" # lv, home + comp = "CP3" # lv, home — load_case_mv=0.0: no MV home CPs (n=0) p_set = 0.3 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 0.2 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 0.0 * p_set, 1.0 * p_set], name=comp, index=index, ) @@ -928,10 +935,10 @@ def test_worst_case_charging_points(self): pf = tan(acos(1.0)) assert_series_equal(q_ts.loc[:, comp], exp * pf, check_dtype=False) - comp = "CP4" # lv, work + comp = "CP4" # lv, work — load_case_mv=0.0: no MV work CPs (n=0) p_set = 0.4 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 0.2 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 0.0 * p_set, 1.0 * p_set], name=comp, index=index, ) @@ -986,10 +993,10 @@ def test_worst_case_charging_points(self): # check values index = ["load_case_mv", "load_case_lv"] - comp = "CP2" # mv, public + comp = "CP2" # mv, public — load_case_lv=0.0: no LV public CPs (n=0) p_set = 0.2 exp = pd.Series( - data=[1.0 * p_set, 1.0 * p_set], + data=[1.0 * p_set, 0.0 * p_set], name=comp, index=index, ) @@ -1037,10 +1044,12 @@ def test_worst_case_heat_pumps(self): # check values index = ["feed-in_case_mv", "feed-in_case_lv", "load_case_mv", "load_case_lv"] + # load_case_mv=1.0: MV-HP group has n=1 -> simultaneity factor 1.0 (was + # fixed constant 0.8) comp = "HP1" # mv p_set = 0.1 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 0.8 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 1.0 * p_set], name=comp, index=index, ) @@ -1051,7 +1060,7 @@ def test_worst_case_heat_pumps(self): comp = "HP2" # lv p_set = 0.2 exp = pd.Series( - data=[0.0 * p_set, 0.0 * p_set, 0.8 * p_set, 1.0 * p_set], + data=[0.0 * p_set, 0.0 * p_set, 1.0 * p_set, 1.0 * p_set], name=comp, index=index, ) @@ -1106,10 +1115,11 @@ def test_worst_case_heat_pumps(self): # check values index = ["load_case_mv", "load_case_lv"] + # load_case_mv=1.0: MV-HP group has n=1 -> simultaneity factor 1.0 comp = "HP1" # mv p_set = 0.1 exp = pd.Series( - data=[0.8 * p_set, 1.0 * p_set], + data=[1.0 * p_set, 1.0 * p_set], name=comp, index=index, ) @@ -2643,3 +2653,135 @@ def test_from_csv(self): ) shutil.rmtree(save_dir) + + +class TestCpSimultaneityFactor: + """Unit tests for the module-level helper _cp_simultaneity_factor. + + All expected values are taken directly from the VDE FNN 2021 support-point + table (suburban residential, evening peak) stored in + config_timeseries_default.cfg under [simultaneity_curves]. + """ + + @pytest.fixture(autouse=True) + def setup_class(self): + self.config = Config() + + # ------------------------------------------------------------------ + # 1. Exact support-point values + # ------------------------------------------------------------------ + + def test_exact_support_points_11kw(self): + f = timeseries._cp_simultaneity_factor + assert f(1, 11, self.config) == pytest.approx(1.0) + assert f(20, 11, self.config) == pytest.approx(0.35) + assert f(50, 11, self.config) == pytest.approx(0.26) + assert f(150, 11, self.config) == pytest.approx(0.173) + + def test_exact_support_points_other_classes(self): + f = timeseries._cp_simultaneity_factor + assert f(10, 3.7, self.config) == pytest.approx(0.8) + assert f(10, 22, self.config) == pytest.approx(0.5) + + # ------------------------------------------------------------------ + # 2. Logarithmic interpolation between support points + # ------------------------------------------------------------------ + + def test_log_interpolation_between_n10_and_n20(self): + # n=15 lies between support points n=10 (factor=0.6) and n=20 (factor=0.35). + # Log-interpolation yields a value closer to 0.35 than the linear midpoint + # (0.475), because log(15) is proportionally nearer to log(20) than to log(10). + factor = timeseries._cp_simultaneity_factor(15, 11, self.config) + linear_midpoint = (0.6 + 0.35) / 2 # 0.475 + assert factor > 0.35, "factor must be above the n=20 support point" + assert factor < linear_midpoint, "log-interp must be below the linear midpoint" + + # ------------------------------------------------------------------ + # 3. Clamping at boundaries + # ------------------------------------------------------------------ + + def test_clamping_above_max_support_point(self): + # n=200 exceeds the largest support point (n=150); factor must stay at 0.173. + assert timeseries._cp_simultaneity_factor(200, 11, self.config) == pytest.approx( + 0.173 + ) + + def test_clamping_at_min_support_point(self): + # n=1 is the smallest support point; factor must be 1.0. + assert timeseries._cp_simultaneity_factor(1, 11, self.config) == pytest.approx( + 1.0 + ) + + # ------------------------------------------------------------------ + # 4. Power-class selection: nearest class wins + # ------------------------------------------------------------------ + + def test_power_class_selection_near_11kw(self): + # 10 kW is closer to 11 kW than to 3.7 kW → uses 11 kW curve (n=20 → 0.35) + assert timeseries._cp_simultaneity_factor(20, 10, self.config) == pytest.approx( + 0.35 + ) + + def test_power_class_selection_near_3_7kw(self): + # 4 kW is closer to 3.7 kW than to 11 kW → uses 3.7 kW curve (n=10 → 0.8) + assert timeseries._cp_simultaneity_factor(10, 4, self.config) == pytest.approx( + 0.8 + ) + + def test_power_class_selection_near_22kw(self): + # 18 kW is closer to 22 kW than to 11 kW → uses 22 kW curve (n=10 → 0.5) + assert timeseries._cp_simultaneity_factor(10, 18, self.config) == pytest.approx( + 0.5 + ) + + +class TestHpSimultaneityFactor: + """Unit tests for the module-level helper _hp_simultaneity_factor. + + All expected values are taken directly from the Kerber-derived support-point + table stored in config_timeseries_default.cfg under [simultaneity_curves]. + """ + + @pytest.fixture(autouse=True) + def setup_class(self): + self.config = Config() + + # ------------------------------------------------------------------ + # 1. Exact support-point values + # ------------------------------------------------------------------ + + def test_exact_support_points(self): + f = timeseries._hp_simultaneity_factor + assert f(1, self.config) == pytest.approx(1.0) + assert f(20, self.config) == pytest.approx(0.91) + assert f(50, self.config) == pytest.approx(0.9) + assert f(150, self.config) == pytest.approx(0.9) + + # ------------------------------------------------------------------ + # 2. Logarithmic interpolation between support points + # ------------------------------------------------------------------ + + def test_log_interpolation_between_n20_and_n50(self): + # n=30 lies between support points n=20 (factor=0.91) and n=50 + # (factor=0.9). The interpolated factor must lie strictly within + # that range. + factor = timeseries._hp_simultaneity_factor(30, self.config) + assert factor < 0.91, "factor must be below the n=20 support point" + assert factor > 0.9, "factor must be above the n=50 support point" + + # ------------------------------------------------------------------ + # 3. Clamping at boundaries + # ------------------------------------------------------------------ + + def test_clamping_above_max_support_point(self): + # n=300 exceeds the largest support point (n=150); factor must stay + # at 0.9. + assert timeseries._hp_simultaneity_factor(300, self.config) == pytest.approx( + 0.9 + ) + + def test_clamping_at_min_support_point(self): + # n=1 is the smallest support point; factor must be 1.0. + assert timeseries._hp_simultaneity_factor(1, self.config) == pytest.approx( + 1.0 + ) From 7e71b5a5cf45cc4abff86831f22aa78f64049ed7 Mon Sep 17 00:00:00 2001 From: nader-00 Date: Tue, 23 Jun 2026 14:19:08 +0200 Subject: [PATCH 2/2] docs: translate simultaneity_curves config comments to English, add usage docs --- doc/userguide/time_series.rst | 25 ++++++++++++++++++ edisgo/config/config_timeseries_default.cfg | 29 +++++++++++---------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/doc/userguide/time_series.rst b/doc/userguide/time_series.rst index 72ac05838..b0c591f34 100644 --- a/doc/userguide/time_series.rst +++ b/doc/userguide/time_series.rst @@ -62,6 +62,31 @@ which time step maps to which case. Note that this **overwrites** any previously (non-worst-case) time series. The definition of load and feed-in case is explained in :ref:`load-feedin-case`. +**Simultaneity factors** + +Conventional loads and generators use fixed scale factors from the config file +:ref:`config_timeseries` in section ``worst_case_scale_factor``. + +For **charging points (CP)** in the load case, the simultaneity factor is determined +dynamically based on the number of charging points *n* and their nominal charging power. +Three power classes are supported: 3.7 kW (slow AC), 11 kW (fast AC) and 22 kW (fast AC). +Support points are taken from VDE FNN (2021): *Ermittlung von Gleichzeitigkeitsfaktoren +fuer Ladevorgaenge an privaten Ladepunkten* (suburban residential evening scenario, +conservative worst case). Interpolation is performed in log-space over *n*; for *n > 150* +the factor is clamped to the value at *n = 150*. In the feed-in case the CP factor is +always 0.0 (no reverse power flow from charging assumed), consistent with the dena study +*Integrierte Energiewende* (p. 90). + +For **heat pumps (HP)** in the load case, the simultaneity factor depends only on the +number of heat pumps *n*. Support points are derived from the Kerber simultaneity +function *g(n) = g_inf + (1 − g_inf) · n^(−3/4)* (Kerber 2011, eq. 3.2) with an +asymptote *g_inf = 0.9* based on the Consentec/E.ON distribution network study (peak +load cold winter day). The same log-space interpolation and clamping scheme as for +charging points is applied. In the feed-in case the HP factor is always 0.0. + +Support point values for both CP and HP are defined in the config file +:ref:`config_timeseries` in section ``simultaneity_curves``. + Predefined ~~~~~~~~~~ diff --git a/edisgo/config/config_timeseries_default.cfg b/edisgo/config/config_timeseries_default.cfg index 889c89261..e46a494c7 100644 --- a/edisgo/config/config_timeseries_default.cfg +++ b/edisgo/config/config_timeseries_default.cfg @@ -83,13 +83,14 @@ mv_load_case_hp = 0.8 lv_load_case_hp = 1.0 [simultaneity_curves] -# Stützstellen für anzahl- und ladeleistungsabhängige Gleichzeitigkeitsfaktoren -# für Ladepunkte (CP) im Lastfall. -# Quelle: VDE FNN (2021): "Ermittlung von Gleichzeitigkeitsfaktoren für -# Ladevorgänge an privaten Ladepunkten", Planungshilfe. -# Gewählter Fall: vorstädtisch, Wohngebiet, Abends (höchste GZF = konservativer Worst Case). -# Interpolation über n (logarithmisch). Für n > 150 wird der Faktor bei n = 150 -# beibehalten (Clamping), da die VDE-FNN-Studie nur bis n = 150 reicht. +# Support points for count- and charging-power-dependent simultaneity factors +# for charging points (CP) in the load case. +# Source: VDE FNN (2021): "Ermittlung von Gleichzeitigkeitsfaktoren fuer +# Ladevorgaenge an privaten Ladepunkten" (Determination of simultaneity factors +# for charging processes at private charging points), planning guide. +# Chosen scenario: suburban, residential area, evening (highest factor = conservative worst case). +# Interpolation over n in log-space. For n > 150 the factor is clamped to the value at n = 150, +# as the VDE FNN study covers only up to n = 150. cp_simultaneity_n = 1, 5, 10, 20, 50, 150 cp_simultaneity_3.7kW = 1.0, 1.0, 0.8, 0.55, 0.4, 0.313 cp_simultaneity_11kW = 1.0, 0.8, 0.6, 0.35, 0.26, 0.173 @@ -97,13 +98,13 @@ cp_simultaneity_22kW = 1.0, 0.8, 0.5, 0.3, 0.18, 0.12 cp_simultaneity_power_classes = 3.7, 11, 22 # --------------------------------------------------------------------------- -# Wärmepumpen (HP): anzahlabhängiger Gleichzeitigkeitsfaktor (eindimensional). -# Mangels einer der VDE-FNN-EV-Studie äquivalenten Tabellenquelle wurden die -# Stützstellen über die Kerber-Gleichzeitigkeitsfunktion berechnet: -# g(n) = g_inf + (1 - g_inf) * n^(-3/4) (Kerber 2011, Gl. 3.2, S. 23) -# WP-Grenzwert g_inf = 0.9 (gestützt auf Consentec/E.ON-Verteilnetzstudie, -# Starklastfall kalter Wintertag). Gleiches Stützstellenraster und Clamping -# (n > 150 -> Faktor bei n = 150) wie bei den Ladepunkten. +# Heat pumps (HP): count-dependent simultaneity factor (one-dimensional). +# As no equivalent tabular source to the VDE FNN EV study exists for heat pumps, +# the support points were derived from the Kerber simultaneity function: +# g(n) = g_inf + (1 - g_inf) * n^(-3/4) (Kerber 2011, eq. 3.2, p. 23) +# HP asymptote g_inf = 0.9 (based on Consentec/E.ON distribution network study, +# peak load cold winter day). Same support point grid and clamping +# (n > 150 -> factor at n = 150) as for charging points. hp_simultaneity_n = 1, 5, 10, 20, 50, 150 hp_simultaneity_factor = 1.0, 0.93, 0.92, 0.91, 0.9, 0.9