5959"""
6060
6161import os
62+ import copy
6263import logging
6364import pandas as pd
6465import numpy as np
6566import scipy .stats
67+ import scipy .optimize
6668import matplotlib .pyplot as plt # Plotting library
6769import matplotlib as mpl
6870
@@ -342,6 +344,149 @@ def copy_and_randomize(load_curve_houses, house_name, randoms_int, sigma,
342344def copy_and_randomize_houses (load_curve_houses , houses_dict , cfg ):
343345 """Create copies of houses and randomize load where needed.
344346
347+ Args:
348+ load_curve_houses (DataFrame): Time series data to use.
349+
350+ houses_dict (dict): Dictionary with all the houses
351+
352+ cfg (dict): The configuration for the load profile aggregator
353+
354+ Returns:
355+ load_curve_houses (DataFrame): Manipulated time series data
356+
357+ """
358+ if cfg ['settings' ].get ('simultaneity_fit_sigma' , False ):
359+ load_curve_houses = _copy_and_randomize_houses_sigma_fit (
360+ load_curve_houses , houses_dict , cfg )
361+ else :
362+ load_curve_houses = _copy_and_randomize_houses (
363+ load_curve_houses , houses_dict , cfg )
364+ return load_curve_houses
365+
366+
367+ def _copy_and_randomize_houses_sigma_fit (load_curve_houses , houses_dict , cfg ):
368+ """Run the randomization while fitting the simultaneity to literature data.
369+
370+ The complete randomizer process is performed in a loop to fit
371+ the resulting simultaneity factor to a literature value derived from
372+ the number of buildings connected to a district heating grid, by
373+ choosing the standard deviation sigma.
374+
375+ Args:
376+ load_curve_houses (DataFrame): Time series data to use.
377+
378+ houses_dict (dict): Dictionary with all the houses
379+
380+ cfg (dict): The configuration for the load profile aggregator
381+
382+ Returns:
383+ load_curve_houses (DataFrame): Manipulated time series data
384+
385+ """
386+ precision_sigma = 5
387+ plot_fitting = False
388+
389+ def black_box (sigma ):
390+ _cfg = copy .deepcopy (cfg )
391+ _cfg ['settings' ]['print_GLF_stats' ] = False
392+ _cfg ['settings' ]['show_plot' ] = False
393+ _cfg ['settings' ]['save_plot_filetypes' ] = None
394+
395+ if isinstance (sigma , np .ndarray ): # Compatibility with optimizer
396+ sigma = sigma [0 ]
397+ # Sigma must not be negative. Sigma=0 needs to be prevented, because
398+ # that would skip the output of simultaneity calculations
399+ sigma = max (abs (sigma ), 0.0001 )
400+ df_houses = pd .DataFrame .from_dict (houses_dict , orient = 'index' )
401+ df_houses ['sigma' ] = round (sigma , precision_sigma )
402+ _houses_dict = df_houses .to_dict (orient = 'index' )
403+
404+ _copy_and_randomize_houses (load_curve_houses , _houses_dict , _cfg )
405+
406+ df_sf = pd .DataFrame .from_dict (
407+ _cfg .get ('simultaneity_factor_results' , dict ()), orient = 'index' )
408+ if df_sf .empty :
409+ sf_lpagg = 1
410+ else :
411+ sf_lpagg = df_sf .loc ['GLF' , 'th' ]
412+
413+ sf_formula = simultaneity_factor (len (df_houses ))
414+ f_sigma = abs (sf_lpagg - sf_formula )
415+
416+ if plot_fitting :
417+ try :
418+ df_result = pd .DataFrame ({"x" : [sigma ], "f(x)" : [f_sigma ]})
419+ ax = df_test .plot ()
420+ df_result .plot .scatter (ax = ax , x = "x" , y = "f(x)" )
421+ plt .show ()
422+ except Exception :
423+ pass
424+
425+ # print("sf_formula", sf_formula, "sf_lpagg", sf_lpagg, "sigma", sigma)
426+ return f_sigma
427+
428+ if plot_fitting :
429+ x_test = np .arange (0 , 20 , 0.1 )
430+ f_sigma_list = [black_box (sigma ) for sigma in x_test ]
431+ df_test = pd .Series (index = x_test , data = f_sigma_list )
432+
433+ opt_result = scipy .optimize .basinhopping (
434+ black_box ,
435+ x0 = [0 ],
436+ stepsize = 2.0 ,
437+ niter = 200 ,
438+ niter_success = 10 ,
439+ # disp=True,
440+ minimizer_kwargs = dict (
441+ method = "Powell" ,
442+ # tol=1e-7,
443+ options = dict (
444+ # maxfev=50,
445+ # xtol=1e-1,
446+ # ftol=1e-1,
447+ xtol = 1e0 ,
448+ ftol = 1e0 ,
449+ # disp=True,
450+ ),
451+ ),
452+ seed = np .random .default_rng (42 ),
453+ )
454+
455+ sigma_result = max (abs (opt_result .x ), 0.0001 )
456+ if isinstance (sigma_result , np .ndarray ): # Compatibility with optimizer
457+ sigma_result = sigma_result [0 ]
458+
459+ # cfg = copy.deepcopy(cfg_backup)
460+ df_houses = pd .DataFrame .from_dict (cfg ['houses' ], orient = 'index' )
461+ df_houses ['sigma' ] = round (sigma_result , precision_sigma )
462+ cfg ['houses' ].update (df_houses .to_dict (orient = 'index' ))
463+ load_curve_houses = _copy_and_randomize_houses (
464+ load_curve_houses , houses_dict , cfg )
465+
466+ return load_curve_houses
467+
468+
469+ def simultaneity_factor (n , decimals = 5 ):
470+ """Calculate simultaneity factor based on number of consumers n.
471+
472+ Winter, Walter; Haslauer, Thomas; Obernberger, Ingwald (2001)
473+ Untersuchungen der Gleichzeitigkeit in kleinen und
474+ mittleren Nahwärmenetzen. In: Euroheat & Power (9/10).
475+ """
476+ n = np .asarray (n , dtype = float )
477+ a = 0.449677646267461
478+ b = 0.551234688
479+ c = 53.84382392
480+ d = 1.762743268
481+ f = a + b / (1 + (n / c ) ** d )
482+ f = np .clip (f , a_min = 0 , a_max = 1 )
483+ f = np .round (f , decimals )
484+ return f
485+
486+
487+ def _copy_and_randomize_houses (load_curve_houses , houses_dict , cfg ):
488+ """Create copies of houses and randomize load where needed.
489+
345490 Apply a normal distribution to the houses, if a standard deviation
346491 ``sigma`` is given in the config. This function is an internal part
347492 of the load profile aggregator program.
@@ -371,7 +516,7 @@ def copy_and_randomize_houses(load_curve_houses, houses_dict, cfg):
371516 cfg (dict): The configuration for the load profile aggregator
372517
373518 Returns:
374- load_curve_houses (DataFrame): Manipulatted time series data
519+ load_curve_houses (DataFrame): Manipulated time series data
375520 """
376521 settings = cfg ['settings' ]
377522 if settings .get ('GLF_on' , True ) is False :
@@ -598,7 +743,10 @@ def plot_normal_histogram(house_name, randoms_int, save_folder=None,
598743 logger .debug ('Interval shifts applied to ' + str (house_name ) + ':' )
599744 logger .debug (randoms_int )
600745 mu = np .mean (randoms_int )
601- sigma = np .std (randoms_int , ddof = 1 )
746+ if len (randoms_int ) > 1 :
747+ sigma = np .std (randoms_int , ddof = 1 )
748+ else :
749+ sigma = np .nan
602750 text_mean_std = 'Mean = {:0.2f}, std = {:0.2f}' .format (mu , sigma )
603751 title_mu_std = r'$\mu={:0.3f},\ \sigma={:0.3f}$' .format (mu , sigma )
604752 logger .debug (text_mean_std )
@@ -615,7 +763,7 @@ def plot_normal_histogram(house_name, randoms_int, save_folder=None,
615763 ax .yaxis .grid (True ) # Activate grid on horizontal axis
616764 n , bins , patches = plt .hist (randoms_int , bins , align = 'mid' ,
617765 rwidth = 0.9 , label = txt_label_data )
618- if cfg ['settings' ].get ('GLF_stats_include_normal' , True ):
766+ if cfg ['settings' ].get ('GLF_stats_include_normal' , True ) and sigma > 0 :
619767 # Create an ideal normal distribution for reference
620768 x_norm = np .linspace (min (bins ), max (bins ), 100 )
621769 y_norm = scipy .stats .norm .pdf (x_norm , mu , sigma )
@@ -624,7 +772,7 @@ def plot_normal_histogram(house_name, randoms_int, save_folder=None,
624772 plt .title (str (len (randoms_int ))+ ' ' + str (house_name )+ ' (' + title_mu_std + ')' )
625773 plt .xlabel (txt_xlabel )
626774 plt .ylabel (txt_ylabel )
627- if cfg ['settings' ].get ('GLF_stats_include_normal' , True ):
775+ if cfg ['settings' ].get ('GLF_stats_include_normal' , True ) and sigma > 0 :
628776 plt .legend ()
629777
630778 lpagg .misc .savefig_filetypes (
0 commit comments