From 97dfc85995db3e3d0eb360fb9e74d39718a96dc5 Mon Sep 17 00:00:00 2001 From: FLomb Date: Fri, 19 Apr 2019 18:00:03 +0200 Subject: [PATCH] paper code --- ...stochastic_process.py => RAMP v.0.1-pre.py | 178 +++++++++++++++++- RAMP_v02-pre/RAMP_run.py | 26 --- RAMP_v02-pre/VERSION.txt | 1 - RAMP_v02-pre/__pycache__/core.cpython-36.pyc | Bin 3181 -> 0 bytes .../__pycache__/initialise.cpython-36.pyc | Bin 448 -> 0 bytes .../__pycache__/inputs.cpython-36.pyc | Bin 5245 -> 0 bytes .../__pycache__/model_run.cpython-36.pyc | Bin 1168 -> 0 bytes .../__pycache__/post_process.cpython-36.pyc | Bin 955 -> 0 bytes .../stochastic_process.cpython-36.pyc | Bin 7569 -> 0 bytes RAMP_v02-pre/core.py | 91 --------- RAMP_v02-pre/initialise.py | 22 --- RAMP_v02-pre/inputs.py | 39 ---- RAMP_v02-pre/post_process.py | 47 ----- 13 files changed, 177 insertions(+), 227 deletions(-) rename RAMP_v02-pre/stochastic_process.py => RAMP v.0.1-pre.py (73%) delete mode 100644 RAMP_v02-pre/RAMP_run.py delete mode 100644 RAMP_v02-pre/VERSION.txt delete mode 100644 RAMP_v02-pre/__pycache__/core.cpython-36.pyc delete mode 100644 RAMP_v02-pre/__pycache__/initialise.cpython-36.pyc delete mode 100644 RAMP_v02-pre/__pycache__/inputs.cpython-36.pyc delete mode 100644 RAMP_v02-pre/__pycache__/model_run.cpython-36.pyc delete mode 100644 RAMP_v02-pre/__pycache__/post_process.cpython-36.pyc delete mode 100644 RAMP_v02-pre/__pycache__/stochastic_process.cpython-36.pyc delete mode 100644 RAMP_v02-pre/core.py delete mode 100644 RAMP_v02-pre/initialise.py delete mode 100644 RAMP_v02-pre/inputs.py delete mode 100644 RAMP_v02-pre/post_process.py diff --git a/RAMP_v02-pre/stochastic_process.py b/RAMP v.0.1-pre.py similarity index 73% rename from RAMP_v02-pre/stochastic_process.py rename to RAMP v.0.1-pre.py index 084ebcec..a880e28b 100644 --- a/RAMP_v02-pre/stochastic_process.py +++ b/RAMP v.0.1-pre.py @@ -1,11 +1,146 @@ # -*- coding: utf-8 -*- +""" +Created on Fri Jun 08 11:46:00 2018 +This is the code for the open-source stochastic model for the generation of +multi-energy load profiles in off-grid areas, called RAMP, v.0.1-pre. +@authors: +- Francesco Lombardi, Politecnico di Milano +- Sergio Balderrama, Université de Liège +- Sylvain Quoilin, KU Leuven +- Emanuela Colombo, Politecnico di Milano + +Copyright 2019 RAMP, contributors listed above. +Licensed under the European Union Public Licence (EUPL), Version 1.1; +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations +under the License. + +""" #%% Import required libraries +import matplotlib.pyplot as plt import numpy as np import numpy.ma as ma import random import math -from initialise import User_list, peak_enlarg, mu_peak, s_peak, Profile, num_profiles +#%% Definition of Python classes that constitute the model architecture +''' +The code is based on two concatenated python classes, namely 'User' and +'Appliance', which are used to define at the outer level the User classes and +at the inner level all the available appliances within each user class, with +their own characteristics. Within the Appliance class, some other functions are +created to define windows of use and, if needed, specific duty cycles +''' + +#Define the outer python class that represents 'User classes' +class User(): + + def __init__(self, name = "", n_users = 1, us_pref = 0): + self.user_name = name + self.num_users = n_users #specifies the number of users within the class + self.user_preference = us_pref #allows to check if random number coincides with user preference, to distinguish between various appliance_use options (e.g. different cooking options) + self.App_list = [] #each instance of User (i.e. each user class) has its own list of Appliances + +#Define the inner class for modelling user's appliances within the correspoding user class + class Appliance(): + + def __init__(self,user, n = 1, P = 0, w = 1, t = 0, r_t = 0, c = 0, fixed = 'no', fixed_cycle = 0, occasional_use = 1, flat = 'no', thermal_P_var = 0, pref_index = 0): + self.user = user #user to which the appliance is bounded + self.number = n #number of appliances of the specified kind + self.POWER = P #nominal power of appliances of the specified kind + self.num_windows = w #number of functioning windows to be considered + self.func_time = t #total time the appliance is on during the day + self.r_t = r_t #percentage of total time of use that is subject to random variability + self.func_cycle = c #minimum time the appliance is kept on after switch-on event + self.fixed = fixed #if 'yes', all the 'n' appliances of this kind are always switched-on together + self.activate = fixed_cycle #if equal to 1,2 or 3, respectively 1,2 or 3 duty cycles can be modelled, for different periods of the day + self.occasional_use = occasional_use #probability that the appliance is always (i.e. everyday) included in the mix of appliances that the user actually switches-on during the day + self.flat = flat #allows to model appliances that are not subject to any kind of random variability, such as public lighting + self.Thermal_P_var = thermal_P_var #allows to randomly variate the App power within a range + self.Pref_index = pref_index + + def windows(self, w1 = np.array([0,0]), w2 = np.array([0,0]),r_w = 0, w3 = np.array([0,0])): + self.window_1 = w1 #array of start and ending time for window of use #1 + self.window_2 = w2 #array of start and ending time for window of use #2 + self.window_3 = w3 #array of start and ending time for window of use #3 + self.random_var_w = r_w #percentage of variability in the start and ending times of the windows + self.daily_use = np.zeros(1440) #create an empty daily use profile + self.daily_use[w1[0]:(w1[1])] = np.full(np.diff(w1),0.001) #fills the daily use profile with infinitesimal values that are just used to identify the functioning windows + self.daily_use[w2[0]:(w2[1])] = np.full(np.diff(w2),0.001) #same as above for window2 + self.daily_use[w3[0]:(w3[1])] = np.full(np.diff(w3),0.001) #same as above for window3 + self.daily_use_masked = np.zeros_like(ma.masked_not_equal(self.daily_use,0.001)) #apply a python mask to the daily_use array to make only functioning windows 'visibile' + self.random_var_1 = int(r_w*np.diff(w1)) #calculate the random variability of window1, i.e. the maximum range of time they can be enlarged or shortened + self.random_var_2 = int(r_w*np.diff(w2)) #same as above + self.random_var_3 = int(r_w*np.diff(w3)) #same as above + self.user.App_list.append(self) #automatically appends the appliance to the user's appliance list + + #if needed, specific duty cycles can be defined for each Appliance, for a maximum of three different ones + def specific_cycle_1(self, P_11 = 0, t_11 = 0, P_12 = 0, t_12 = 0, r_c1 = 0): + self.P_11 = P_11 #power absorbed during first part of the duty cycle + self.t_11 = t_11 #duration of first part of the duty cycle + self.P_12 = P_12 #power absorbed during second part of the duty cycle + self.t_12 = t_12 #duration of second part of the duty cycle + self.r_c1 = r_c1 #random variability of duty cycle segments duration + self.fixed_cycle1 = np.concatenate(((np.ones(t_11)*P_11),(np.ones(t_12)*P_12))) #create numpy array representing the duty cycle + + def specific_cycle_2(self, P_21 = 0, t_21 = 0, P_22 = 0, t_22 = 0, r_c2 = 0): + self.P_21 = P_21 #same as for cycle1 + self.t_21 = t_21 + self.P_22 = P_22 + self.t_22 = t_22 + self.r_c2 = r_c2 + self.fixed_cycle2 = np.concatenate(((np.ones(t_21)*P_21),(np.ones(t_22)*P_22))) + + def specific_cycle_3(self, P_31 = 0, t_31 = 0, P_32 = 0, t_32 = 0, r_c3 = 0): + self.P_31 = P_31 #same as for cycle1 + self.t_31 = t_31 + self.P_32 = P_32 + self.t_32 = t_32 + self.r_c3 = r_c3 + self.fixed_cycle3 = np.concatenate(((np.ones(t_31)*P_31),(np.ones(t_32)*P_32))) + + #different time windows can be associated with different specific duty cycles + def cycle_behaviour(self, cw11 = np.array([0,0]), cw12 = np.array([0,0]), cw21 = np.array([0,0]), cw22 = np.array([0,0]), cw31 = np.array([0,0]), cw32 = np.array([0,0])): + self.cw11 = cw11 #first window associated with cycle1 + self.cw12 = cw12 #second window associated with cycle1 + self.cw21 = cw21 #same for cycle2 + self.cw22 = cw22 + self.cw31 = cw31 #same for cycle 3 + self.cw32 = cw32 + +#%% Initialisation of a model instance +''' +The model is ready to be initialised +''' +User_list = [] #creates an empty list to store all the needed User classes +num_profiles = int(input("please indicate the number of profiles to be generated: ")) #asks the user how many profiles (i.e. code runs) he wants +print('Please wait...') +Profile = [] #creates an empty list to store the results of each code run, i.e. each stochastically generated profile + +#%% Definition of the inputs +''' +Input data definition (this is planned to be externalised in a separate script) +''' + +#User classes definition +HI = User("high income",1) +User_list.append(HI) + +HI_Phone_charger = HI.Appliance(HI,1,10,2,300,0.2,5, thermal_P_var=0.2) +HI_Phone_charger.windows([1110,1440],[0,30],0.35) + +#%% +''' +Calibration parameters. These can be changed in case the user has some real data against which the model can be calibrated +They regulate the probabilities defining the largeness of the peak window and the probability of coincident switch-on within the peak window +''' +peak_enlarg = 0 #percentage random enlargement or reduction of peak time range length +mu_peak = 0.5 #median value of gaussian distribution [0,1] by which the number of coincident switch_ons is randomly selected +s_peak = 1 #standard deviation (as percentage of the median value) of the gaussian distribution [0,1] above mentioned #%% Core model stochastic script ''' @@ -276,3 +411,44 @@ Profile.append(Tot_Classes) #appends the total load to the list that will contain all the generated profiles print('Profile',prof_i+1,'/',num_profiles,'completed') #screen update about progress of computation +#%% Post-processing +''' +Just some additional code lines to calculate useful indicators and generate plots +''' + +Profile_avg = np.zeros(1440) +for pr in Profile: + Profile_avg = Profile_avg + pr +Profile_avg = Profile_avg/len(Profile) +Profile_avg_kW = Profile_avg/1000 + +Profile_kW = [] +for kW in Profile: + Profile_kW.append(kW/1000) + +Profile_series = np.array([]) +for iii in Profile: + Profile_series = np.append(Profile_series,iii) + +x = np.arange(0,1440,5) +plt.figure(figsize=(10,5)) +for n in Profile: + plt.plot(np.arange(1440),n,'#b0c4de') + plt.xlabel('Time (hours)') + plt.ylabel('Power (W)') + plt.ylim(ymin=0) + #plt.ylim(ymax=5000) + plt.margins(x=0) + plt.margins(y=0) +plt.plot(np.arange(1440),Profile_avg,'#4169e1') +plt.xticks([0,240,480,(60*12),(60*16),(60*20),(60*24)],[0,4,8,12,16,20,24]) +#plt.savefig('profile.eps', format='eps', dpi=1000) +plt.show() + + +#%% Export individual profiles +''' +for i in range (len(Profile)): + np.save('p0%d.npy' % (i), Profile[i]) +''' + diff --git a/RAMP_v02-pre/RAMP_run.py b/RAMP_v02-pre/RAMP_run.py deleted file mode 100644 index 2dcdcd3e..00000000 --- a/RAMP_v02-pre/RAMP_run.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 19 14:35:00 2019 -This is the code for the open-source stochastic model for the generation of -multi-energy load profiles in off-grid areas, called RAMP, v.0.2-pre. - -@authors: -- Francesco Lombardi, Politecnico di Milano -- Sergio Balderrama, Université de Liège -- Sylvain Quoilin, KU Leuven -- Emanuela Colombo, Politecnico di Milano - -Copyright 2019 RAMP, contributors listed above. -Licensed under the European Union Public Licence (EUPL), Version 1.1; -you may not use this file except in compliance with the License. - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and limitations -under the License. -""" - -#%% Import required libraries - -from post_process import* \ No newline at end of file diff --git a/RAMP_v02-pre/VERSION.txt b/RAMP_v02-pre/VERSION.txt deleted file mode 100644 index d40b44e0..00000000 --- a/RAMP_v02-pre/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -RAMP v.0.2-pre \ No newline at end of file diff --git a/RAMP_v02-pre/__pycache__/core.cpython-36.pyc b/RAMP_v02-pre/__pycache__/core.cpython-36.pyc deleted file mode 100644 index 7e20a9060dcf369f92ed150ad66b8f9691db7c49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3181 zcmcguOOG2x5T5sp$M&w1EP)^tIE06JEV0cagpda$;efKSHbF?Em0FYO_GX;%*z}Co z+11=O{12}A14!IB@C!Khl~ayLTtFPCsHYeoIOic z1X&0SUf~wEALwMo;VxKl$%+S7d}V!`38}P4Iv9}%ku^1kwE^WTDA{WOgCjim5HRM zESlq{(;4nKW2pa7lO8Pm_qnU*cW$QQs*Lx<&bG)zD5JYOH?G}!V=&|*N#b;OXZy;H zE&bUdG+4zzpYoK-+O7;`L+>ys-juckk zhJYBm#-Y9_b7Qg9vpBk24;+4+d%OaY%TI8hS3&al2CwltNR`Do|3~gQe2uSzMV~hy zATJ%P(FVcSNgRUPB?gp2tc-LRtw9SPtmk3ghQ{MzE-ValVFOx)185g6pi_8&ZczdB z3LmgiQ~~{>23RfXfVCMp>dHbICzox*}eF_;K~c5Jl+d)q1J{`6lU=-4HIOIvU*9F2ld-`g&aUvm+gfz@Vmew zU;r2Qn`_d6-ARnC(o>~(l|E5=F4w6ek`1VXDlNlkO?e#kkvKs;Up9;6EX@)@OQ@i_ z4}i&%y$UenTFJOYJa~CII7d9RaUMKaW1;l>6NII>U_=Z`LNiA;>W7$|4#+MPuYfi; z4k1?j_&T3lr-ZcQ5KJTJ>}CVoJ;qF zG6tb)48rW5;4B^HOng5Mlb|u5$jUKg#%aE^P;05xUTSrSdlG}HoNzP}DR0_DN@Yx1 zP}%{|ei9gy&I+rPJ57=IVD1XDYWf*^PwE@=9-LfZ)^uuw?8gUdJPL?d*Fjh!7M<&h z37^}22Z~!@fFV>QL(mFyL+h16_hw06QC63=TFS}^L2i=_C|T5!r(iWzjfQCi8JuDk zTf5M!aE2}1U$u$GEOzJk%;169Fe28!sp@r}ax3nqV&+C@< z3v)|{+=AF;9dZjIxTW(nw{&K1>7WZfLiiZr6NFC@p5d0xH=y*~Z0STUOv4B3`R@}s z`t)onf1k5_ag-HJFkb*+v(W@u0Z1~sBARsCO}EU4&%n4mkAMW17ZAQcxQMW_&2{>N zu{+{!xEBw{@;2x^zRS}qIk7Tnt64j`G%QH-yP~Y(($u8O#ZeRc;BY(AT$cXhP!;cU zcvWOf`HaEkZ=9f9V+;@Ia)f@t5xwW2Hsiul);<|Ily% diff --git a/RAMP_v02-pre/__pycache__/initialise.cpython-36.pyc b/RAMP_v02-pre/__pycache__/initialise.cpython-36.pyc deleted file mode 100644 index bda0851a60111c0d05054ad1b30e8625dc89a6a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 448 zcmYjNK~BOz6rG`^Ed|sg%*q58jm8Zj1~tJYN&q)alSyGdWnyWYnHB?EF5ophf+z3} zvodkT6Q0wHRw;v+a7hG&Y(w&t%z*HO=RAzL z1m;Iw- zR`@I`GlV{__gIJfPE*;GvUEZ|;7P9PJjse&wX`7pl~VOgls$WN&{kG8 zBvmtm@QFeahayt;s-WdvS+a_+NE~am3cmV-Uht5>K=3sLV~`Mv`7#*ZO%8^{Jc)ww o?Qm29U7ueBf1Y^RMmbm7xFUwWrJs~18cb~B7Iv^%bL=wz20>+omjD0& diff --git a/RAMP_v02-pre/__pycache__/inputs.cpython-36.pyc b/RAMP_v02-pre/__pycache__/inputs.cpython-36.pyc deleted file mode 100644 index 1b02e31098e8d11a6b8710eaf649f33dfe9a7175..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5245 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 GyZR5M3HXQJ>%V( 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