Skip to content

Commit

Permalink
Move code to generate appliance profile inside a method
Browse files Browse the repository at this point in the history
The method is called generate_profile and update the daily_use attribute

Co-authored-by: Gokarna.Dhungel <[email protected]>
  • Loading branch information
Bachibouzouk and dhungelgd committed May 19, 2022
1 parent bcc0b27 commit 79b72ab
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 81 deletions.
91 changes: 90 additions & 1 deletion ramp/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,6 @@ def rand_total_time_of_use(self, rand_window_1, rand_window_2, rand_window_3):
rand_time = int(0.99 * (np.diff(rand_window_1) + np.diff(rand_window_2) + np.diff(rand_window_3)))
return rand_time


def switch_on(self, rand_window_1, rand_window_2, rand_window_3):
"""Return a random switch-on time of the Appliance instance
Expand Down Expand Up @@ -594,3 +593,93 @@ def calc_coincident_switch_on(self, peak_time_range, indexes):
# All 'n' copies of an Appliance instance are switched on altogether
coincidence = self.number
return coincidence

def generate_load_profile(self, rand_time, peak_time_range, rand_window_1, rand_window_2, rand_window_3, power):
"""Generate load profile of the Appliance instance by updating its daily_use attribute
Repeat steps 2c – 2e of [1] until the sum of the durations of all the switch-on events equals
the randomised total time of use of the Appliance
Notes
-----
[1] F. Lombardi, S. Balderrama, S. Quoilin, E. Colombo,
Generating high-resolution multi-energy load profiles for remote areas with an open-source stochastic model,
Energy, 2019, https://doi.org/10.1016/j.energy.2019.04.097.
"""
max_free_spot = rand_time # free spots are used to detect if there's still space for switch_ons. Before calculating actual free spots, the max free spot is set equal to the entire randomised func_time
rand_windows = [rand_window_1, rand_window_2, rand_window_3]
tot_time = 0
while tot_time <= rand_time:
# Identifies a random switch on time within the available functioning windows
# step 2c of [1]
switch_on = self.switch_on(*rand_windows)

if self.daily_use[switch_on] == 0.001:
# control to check if the Appliance instance is not already on
# at the randomly selected switch-on time
if switch_on in range(rand_window_1[0], rand_window_1[1]):
# random switch_on happens in rand_windows_1
rand_window = rand_window_1
elif switch_on in range(rand_window_2[0], rand_window_2[1]):
# random switch_on happens in rand_windows_2
rand_window = rand_window_2
else:
# if switch_on is not in rand_window_1 nor in rand_window_2, it shall be in rand_window_3.
rand_window = rand_window_3

indexes = self.calc_indexes_for_rand_switch_on(
switch_on=switch_on,
rand_time=rand_time,
max_free_spot=max_free_spot,
rand_window=rand_window
)
if indexes is None:
continue

# the count of total time is updated with the size of the indexes array
tot_time = tot_time + indexes.size

if tot_time > rand_time:
# the total functioning time is reached, a correction is applied to avoid overflow of indexes
indexes_adj = indexes[:-(tot_time - rand_time)]
# Computes how many of the 'n' of the Appliance instance are switched on simultaneously
coincidence = self.calc_coincident_switch_on(
peak_time_range=peak_time_range,
indexes=indexes_adj,
)
# Update the daily use depending on existence of duty cycles of the Appliance instance
self.update_daily_use(
coincidence,
power=power,
index=indexes_adj
)
break # exit cycle and go to next Appliance

else:
# the total functioning time has not yet exceeded the rand_time
# Computes how many of the 'n' of the Appliance instance are switched on simultaneously
coincidence = self.calc_coincident_switch_on(
peak_time_range=peak_time_range,
indexes=indexes,
)
# Update the daily use depending on existence of duty cycles of the Appliance instance
self.update_daily_use(
coincidence,
power=power,
index=indexes
)

free_spots = [] # calculate how many free spots remain for further switch_ons
try:
for j in ma.notmasked_contiguous(self.daily_use_masked):
free_spots.append(j.stop - j.start)
except TypeError:
free_spots = [0]
max_free_spot = max(free_spots)

else:
# if the random switch_on falls somewhere where the Appliance instance
# has been already turned on, tries again from beginning of the while cycle
# TODO not sure this code is useful as the while loop would continue anyway in that case
continue

86 changes: 6 additions & 80 deletions ramp/core/stochastic_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ def Stochastic_Process(j=None, fname=None, num_profiles=None):
pass
else:
rand_daily_pref = random.randint(1,Us.user_preference)

for App in Us.App_list: #iterates for all the App types in the given User class
#initialises variables for the cycle
tot_time = 0
App.daily_use = np.zeros(1440)

# skip this appliance in any of the following applies
Expand All @@ -88,7 +88,6 @@ def Stochastic_Process(j=None, fname=None, num_profiles=None):
):
continue


# recalculate windows start and ending times randomly, based on the inputs
rand_window_1 = App.calc_rand_window(window_idx=1)
rand_window_2 = App.calc_rand_window(window_idx=2)
Expand All @@ -100,9 +99,7 @@ def Stochastic_Process(j=None, fname=None, num_profiles=None):
# step 2a of [1]
rand_time = App.rand_total_time_of_use(*rand_windows)

max_free_spot = rand_time # free spots are used to detect if there's still space for switch_ons. Before calculating actual free spots, the max free spot is set equal to the entire randomised func_time

#redefines functioning windows based on the previous randomisation of the boundaries
# redefines functioning windows based on the previous randomisation of the boundaries
# step 2b of [1]
if App.flat == 'yes':
# for "flat" appliances the algorithm stops right after filling the newly
Expand All @@ -117,85 +114,14 @@ def Stochastic_Process(j=None, fname=None, num_profiles=None):
# the algorithm goes further on
for rand_window in rand_windows:
App.daily_use[rand_window[0]:rand_window[1]] = np.full(np.diff(rand_window), 0.001)

App.daily_use_masked = np.zeros_like(ma.masked_not_equal(App.daily_use,0.001))

# calculates randomised cycles taking the random variability in the duty cycle duration
App.assign_random_cycles()

while tot_time <= rand_time: #this is the key cycle, which runs for each App until the switch_ons and their duration equals the randomised total time of use of the App
#check how many windows to consider
# step 2c of [1]
switch_on = App.switch_on(*rand_windows)

#Identifies a random switch on time within the available functioning windows
if App.daily_use[switch_on] == 0.001: #control to check if the app is not already on at the randomly selected switch-on time
if switch_on in range(rand_window_1[0],rand_window_1[1]):
indexes = App.calc_indexes_for_rand_switch_on(
switch_on=switch_on,
rand_time=rand_time,
max_free_spot=max_free_spot,
rand_window=rand_window_1
)
elif switch_on in range(rand_window_2[0],rand_window_2[1]): #if random switch_on happens in windows2, same code as above is repeated for windows2
indexes = App.calc_indexes_for_rand_switch_on(
switch_on=switch_on,
rand_time=rand_time,
max_free_spot=max_free_spot,
rand_window=rand_window_2
)

else: #if switch_on is not in window1 nor in window2, it shall be in window3. Same code is repreated
indexes = App.calc_indexes_for_rand_switch_on(
switch_on=switch_on,
rand_time=rand_time,
max_free_spot=max_free_spot,
rand_window=rand_window_3
)

if indexes is None:
continue
tot_time = tot_time + indexes.size #the count of total time is updated with the size of the indexes array

if tot_time > rand_time: #control to check when the total functioning time is reached. It will be typically overcome, so a correction is applied to avoid this
indexes_adj = indexes[:-(tot_time-rand_time)] #correctes indexes size to avoid overcoming total time
# Computes how many of the 'n' of the Appliance instance are switched on simultaneously
coincidence = App.calc_coincident_switch_on(
peak_time_range=peak_time_range,
indexes=indexes_adj,
)
# Update the daily use depending on existence of duty cycles of the Appliance instance
App.update_daily_use(
coincidence,
power=App.power[prof_i],
index=indexes_adj
)
tot_time = (tot_time - indexes.size) + indexes_adj.size #updates the total time correcting the previous value
break #exit cycle and go to next Appliance
else: #if the tot_time has not yet exceeded the App total functioning time, the cycle does the same without applying corrections to indexes size
# Computes how many of the 'n' of the Appliance instance are switched on simultaneously
coincidence = App.calc_coincident_switch_on(
peak_time_range=peak_time_range,
indexes=indexes,
)
# Update the daily use depending on existence of duty cycles of the Appliance instance
App.update_daily_use(
coincidence,
power=App.power[prof_i],
index=indexes
)

tot_time = tot_time #no correction applied to previously calculated value

free_spots = [] #calculate how many free spots remain for further switch_ons
try:
for j in ma.notmasked_contiguous(App.daily_use_masked):
free_spots.append(j.stop-j.start)
except TypeError:
free_spots = [0]
max_free_spot = max(free_spots)

else:
continue #if the random switch_on falls somewhere where the App has been already turned on, tries again from beginning of the while cycle
# steps 2c-2e repeated until the sum of the durations of all the switch-on events equals rand_time
App.generate_load_profile(rand_time, peak_time_range, *rand_windows, power=App.power[prof_i])

Us.load = Us.load + App.daily_use #adds the App profile to the User load
Tot_Classes = Tot_Classes + Us.load #adds the User load to the total load of all User classes
Profile.append(Tot_Classes) #appends the total load to the list that will contain all the generated profiles
Expand Down

0 comments on commit 79b72ab

Please sign in to comment.