openstudio-geb 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@
9
9
  # http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
10
10
 
11
11
  # start the measure
12
+ require 'json'
12
13
  class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
13
14
  # human readable name
14
15
  def name
@@ -23,7 +24,7 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
23
24
 
24
25
  # human readable description of modeling approach
25
26
  def modeler_description
26
- return 'This measure will clone all of the lighting schedules for each zone. Then the schedules are adjusted by the specified percentage during the specified time period.'
27
+ return 'This measure will clone all of the lighting schedules for each zone. Then the schedules are adjusted by the specified percentage during the specified time period. Only schedules defined in scheduleRuleSet format will be modified.'
27
28
  end
28
29
 
29
30
  # define the arguments that the user will input
@@ -32,73 +33,138 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
32
33
  lpd_reduce_percent = OpenStudio::Measure::OSArgument.makeDoubleArgument('lpd_reduce_percent', true)
33
34
  lpd_reduce_percent.setDisplayName('Percentage Reduction of Lighting Power (%)')
34
35
  lpd_reduce_percent.setDescription('Enter a value between 0 and 100')
35
- lpd_reduce_percent.setDefaultValue(50.0)
36
+ lpd_reduce_percent.setDefaultValue(30.0)
36
37
  args << lpd_reduce_percent
37
38
 
38
- # make an argument for the start time of the reduction
39
- start_time = OpenStudio::Measure::OSArgument.makeStringArgument('start_time', false)
40
- start_time.setDisplayName('Start Time for the Reduction')
41
- start_time.setDescription('In HH:MM:SS format')
42
- start_time.setDefaultValue('17:00:00')
43
- args << start_time
39
+ lpd_reduce_percent_unoccupied = OpenStudio::Measure::OSArgument.makeDoubleArgument('lpd_reduce_percent_unoccupied', false)
40
+ lpd_reduce_percent_unoccupied.setDisplayName('Percentage Reduction of Lighting Power (%) for Unoccupied Spaces (if different, optional)')
41
+ lpd_reduce_percent_unoccupied.setDescription('Enter a value between 0 and 100')
42
+ lpd_reduce_percent_unoccupied.setDefaultValue(60.0)
43
+ args << lpd_reduce_percent_unoccupied
44
44
 
45
- # make an argument for the end time of the reduction
46
- end_time = OpenStudio::Measure::OSArgument.makeStringArgument('end_time', false)
47
- end_time.setDisplayName('End Time for the Reduction')
48
- end_time.setDescription('In HH:MM:SS format')
49
- end_time.setDefaultValue('21:00:00')
50
- args << end_time
51
-
52
- # Use alternative default start and end time for different climate zone
53
- alt_periods = OpenStudio::Measure::OSArgument.makeBoolArgument('alt_periods', false)
54
- alt_periods.setDisplayName('Use alternative default start and end time based on the climate zone of the model?')
55
- alt_periods.setDescription('This will overwrite the star and end time you input')
56
- alt_periods.setDefaultValue(false)
57
- args << alt_periods
58
-
59
- # make an argument for the start date of the reduction
60
- start_date1 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date1', false)
61
- start_date1.setDisplayName('First start date for the Reduction')
45
+ start_date1 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date1', true)
46
+ start_date1.setDisplayName('First start date for the reduction')
62
47
  start_date1.setDescription('In MM-DD format')
63
48
  start_date1.setDefaultValue('06-01')
64
49
  args << start_date1
65
-
66
- # make an argument for the end date of the reduction
67
- end_date1 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date1', false)
68
- end_date1.setDisplayName('First end date for the Reduction')
50
+ end_date1 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date1', true)
51
+ end_date1.setDisplayName('First end date for the reduction')
69
52
  end_date1.setDescription('In MM-DD format')
70
53
  end_date1.setDefaultValue('09-30')
71
54
  args << end_date1
72
55
 
73
-
74
- # make an argument for the second start date of the reduction
75
56
  start_date2 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date2', false)
76
- start_date2.setDisplayName('Second start date for the Reduction (optional)')
77
- start_date2.setDescription('Specify a date in MM-DD format if you want a second period of reduction; leave blank if not needed.')
57
+ start_date2.setDisplayName('Second start date for the reduction (optional)')
58
+ start_date2.setDescription('Specify a date in MM-DD format if you want a second season of reduction; leave blank if not needed.')
78
59
  start_date2.setDefaultValue('')
79
60
  args << start_date2
80
-
81
- # make an argument for the second end date of the reduction
82
61
  end_date2 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date2', false)
83
- end_date2.setDisplayName('Second end date for the Reduction')
84
- end_date2.setDescription('Specify a date in MM-DD format if you want a second period of reduction; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
62
+ end_date2.setDisplayName('Second end date for the reduction')
63
+ end_date2.setDescription('Specify a date in MM-DD format if you want a second season of reduction; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
85
64
  end_date2.setDefaultValue('')
86
65
  args << end_date2
87
66
 
88
- # make an argument for the third start date of the reduction
67
+
89
68
  start_date3 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date3', false)
90
- start_date3.setDisplayName('Second start date for the Reduction (optional)')
91
- start_date3.setDescription('Specify a date in MM-DD format if you want a third period of reduction; leave blank if not needed.')
69
+ start_date3.setDisplayName('Third start date for the reduction (optional)')
70
+ start_date3.setDescription('Specify a date in MM-DD format if you want a third season of reduction; leave blank if not needed.')
92
71
  start_date3.setDefaultValue('')
93
72
  args << start_date3
94
-
95
- # make an argument for the third end date of the reduction
96
73
  end_date3 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date3', false)
97
- end_date3.setDisplayName('Second end date for the Reduction')
98
- end_date3.setDescription('Specify a date in MM-DD format if you want a third period of reduction; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
74
+ end_date3.setDisplayName('Third end date for the reduction')
75
+ end_date3.setDescription('Specify a date in MM-DD format if you want a third season of reduction; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
99
76
  end_date3.setDefaultValue('')
100
77
  args << end_date3
101
78
 
79
+ start_date4 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date4', false)
80
+ start_date4.setDisplayName('Fourth start date for the reduction (optional)')
81
+ start_date4.setDescription('Specify a date in MM-DD format if you want a fourth season of reduction; leave blank if not needed.')
82
+ start_date4.setDefaultValue('')
83
+ args << start_date4
84
+ end_date4 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date4', false)
85
+ end_date4.setDisplayName('Fourth end date for the reduction')
86
+ end_date4.setDescription('Specify a date in MM-DD format if you want a fourth season of reduction; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
87
+ end_date4.setDefaultValue('')
88
+ args << end_date4
89
+
90
+
91
+ start_date5 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date5', false)
92
+ start_date5.setDisplayName('Fifth start date for the reduction (optional)')
93
+ start_date5.setDescription('Specify a date in MM-DD format if you want a fifth season of reduction; leave blank if not needed.')
94
+ start_date5.setDefaultValue('')
95
+ args << start_date5
96
+ end_date5 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date5', false)
97
+ end_date5.setDisplayName('Fifth end date for the reduction')
98
+ end_date5.setDescription('Specify a date in MM-DD format if you want a fifth season of reduction; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
99
+ end_date5.setDefaultValue('')
100
+ args << end_date5
101
+
102
+ start_time1 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time1', true)
103
+ start_time1.setDisplayName('Start time of the reduction for the first season')
104
+ start_time1.setDescription('In HH:MM:SS format')
105
+ start_time1.setDefaultValue('17:00:00')
106
+ args << start_time1
107
+ end_time1 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time1', true)
108
+ end_time1.setDisplayName('End time of the reduction for the first season')
109
+ end_time1.setDescription('In HH:MM:SS format')
110
+ end_time1.setDefaultValue('21:00:00')
111
+ args << end_time1
112
+
113
+
114
+ start_time2 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time2', false)
115
+ start_time2.setDisplayName('Start time of the reduction for the second season (optional)')
116
+ start_time2.setDescription('In HH:MM:SS format')
117
+ start_time2.setDefaultValue('')
118
+ args << start_time2
119
+ end_time2 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time2', false)
120
+ end_time2.setDisplayName('End time of the reduction for the second season (optional)')
121
+ end_time2.setDescription('In HH:MM:SS format')
122
+ end_time2.setDefaultValue('')
123
+ args << end_time2
124
+
125
+
126
+ start_time3 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time3', false)
127
+ start_time3.setDisplayName('Start time of the reduction for the third season (optional)')
128
+ start_time3.setDescription('In HH:MM:SS format')
129
+ start_time3.setDefaultValue('')
130
+ args << start_time3
131
+ end_time3 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time3', false)
132
+ end_time3.setDisplayName('End time of the reduction for the third season (optional)')
133
+ end_time3.setDescription('In HH:MM:SS format')
134
+ end_time3.setDefaultValue('')
135
+ args << end_time3
136
+
137
+
138
+ start_time4 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time4', false)
139
+ start_time4.setDisplayName('Start time of the reduction for the fourth season (optional)')
140
+ start_time4.setDescription('In HH:MM:SS format')
141
+ start_time4.setDefaultValue('')
142
+ args << start_time4
143
+ end_time4 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time4', false)
144
+ end_time4.setDisplayName('End time of the reduction for the fourth season (optional)')
145
+ end_time4.setDescription('In HH:MM:SS format')
146
+ end_time4.setDefaultValue('')
147
+ args << end_time4
148
+
149
+
150
+ start_time5 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time5', false)
151
+ start_time5.setDisplayName('Start time of the reduction for the fifth season (optional)')
152
+ start_time5.setDescription('In HH:MM:SS format')
153
+ start_time5.setDefaultValue('')
154
+ args << start_time5
155
+ end_time5 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time5', false)
156
+ end_time5.setDisplayName('End time of the reduction for the fifth season (optional)')
157
+ end_time5.setDescription('In HH:MM:SS format')
158
+ end_time5.setDefaultValue('')
159
+ args << end_time5
160
+
161
+
162
+ # Use alternative default start and end time for different climate zone
163
+ alt_periods = OpenStudio::Measure::OSArgument.makeBoolArgument('alt_periods', true)
164
+ alt_periods.setDisplayName('Use alternative default start and end time based on the state of the model from the Cambium load profile peak period?')
165
+ alt_periods.setDescription('This will overwrite the start and end time and date provided by the user')
166
+ alt_periods.setDefaultValue(false)
167
+ args << alt_periods
102
168
 
103
169
  return args
104
170
  end
@@ -112,14 +178,27 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
112
178
  return false
113
179
  end
114
180
  lpd_reduce_percent = runner.getDoubleArgumentValue('lpd_reduce_percent', user_arguments)
115
- start_time = runner.getStringArgumentValue('start_time', user_arguments)
116
- end_time = runner.getStringArgumentValue('end_time', user_arguments)
181
+ lpd_reduce_percent_unoccupied = runner.getOptionalDoubleArgumentValue('lpd_reduce_percent_unoccupied', user_arguments)
182
+ start_time1 = runner.getStringArgumentValue('start_time1', user_arguments)
183
+ end_time1 = runner.getStringArgumentValue('end_time1', user_arguments)
184
+ start_time2 = runner.getStringArgumentValue('start_time2', user_arguments)
185
+ end_time2 = runner.getStringArgumentValue('end_time2', user_arguments)
186
+ start_time3 = runner.getStringArgumentValue('start_time3', user_arguments)
187
+ end_time3 = runner.getStringArgumentValue('end_time3', user_arguments)
188
+ start_time4 = runner.getStringArgumentValue('start_time4', user_arguments)
189
+ end_time4 = runner.getStringArgumentValue('end_time4', user_arguments)
190
+ start_time5 = runner.getStringArgumentValue('start_time5', user_arguments)
191
+ end_time5 = runner.getStringArgumentValue('end_time5', user_arguments)
117
192
  start_date1 = runner.getStringArgumentValue('start_date1', user_arguments)
118
193
  end_date1 = runner.getStringArgumentValue('end_date1', user_arguments)
119
194
  start_date2 = runner.getStringArgumentValue('start_date2', user_arguments)
120
195
  end_date2 = runner.getStringArgumentValue('end_date2', user_arguments)
121
196
  start_date3 = runner.getStringArgumentValue('start_date3', user_arguments)
122
197
  end_date3 = runner.getStringArgumentValue('end_date3', user_arguments)
198
+ start_date4 = runner.getStringArgumentValue('start_date4', user_arguments)
199
+ end_date4 = runner.getStringArgumentValue('end_date4', user_arguments)
200
+ start_date5 = runner.getStringArgumentValue('start_date5', user_arguments)
201
+ end_date5 = runner.getStringArgumentValue('end_date5', user_arguments)
123
202
  alt_periods = runner.getBoolArgumentValue('alt_periods', user_arguments)
124
203
 
125
204
  # validate the percentage
@@ -130,192 +209,172 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
130
209
  runner.registerWarning('The percentage reduction of lighting power is negative. This will increase the lighting power.')
131
210
  end
132
211
 
133
- # set the default start and end time based on climate zone
212
+ if lpd_reduce_percent_unoccupied.empty?
213
+ runner.registerInfo("Unoccupied percentage reduction is not provided. Use the same value for all spaces")
214
+ lpd_reduce_percent_unoccupied = lpd_reduce_percent
215
+ elsif lpd_reduce_percent_unoccupied.to_f > 100
216
+ runner.registerError('The percentage reduction of lighting power cannot be larger than 100.')
217
+ return false
218
+ elsif lpd_reduce_percent_unoccupied.to_f < 0
219
+ runner.registerWarning('The percentage reduction of lighting power is negative. This will increase the lighting power.')
220
+ else
221
+ lpd_reduce_percent_unoccupied = lpd_reduce_percent_unoccupied.to_f
222
+ end
223
+
224
+
225
+ # set the default start and end time based on state
134
226
  if alt_periods
135
- ashraeClimateZone = nil
136
- #climateZoneNUmber = ''
137
- climateZones = model.getClimateZones
138
- climateZones.climateZones.each do |climateZone|
139
- if climateZone.institution == 'ASHRAE'
140
- ashraeClimateZone = climateZone.value
141
- runner.registerInfo("Using ASHRAE Climate zone #{ashraeClimateZone}.")
142
- end
227
+ state = model.getWeatherFile.stateProvinceRegion
228
+ if state == ''
229
+ runner.registerError('Unable to find state in model WeatherFile. The measure cannot be applied.')
230
+ return false
143
231
  end
144
-
145
- unless ashraeClimateZone # should this be not applicable or error?
146
- runner.registerError("If you select to use alternative default start and end time based on the climate zone, please assign an ASHRAE climate zone to your model.")
147
- return false # note - for this to work need to check for false in measure.rb and add return false there as well.
232
+ file = File.open(File.join(File.dirname(__FILE__), "../../../files/seasonal_shedding_peak_hours.json"))
233
+ default_peak_periods = JSON.load(file)
234
+ file.close
235
+ unless default_peak_periods.key?state
236
+ runner.registerAsNotApplicable("No default inputs for the state of the WeatherFile #{state}")
237
+ return false
148
238
  end
239
+ peak_periods = default_peak_periods[state]
240
+ start_time1 = peak_periods["winter_peak_start"].split[1]
241
+ end_time1 = peak_periods["winter_peak_end"].split[1]
242
+ start_time2 = peak_periods["intermediate_peak_start"].split[1]
243
+ end_time2 = peak_periods["intermediate_peak_end"].split[1]
244
+ start_time3 = peak_periods["summer_peak_start"].split[1]
245
+ end_time3 = peak_periods["summer_peak_end"].split[1]
246
+ start_time4 = peak_periods["intermediate_peak_start"].split[1]
247
+ end_time4 = peak_periods["intermediate_peak_end"].split[1]
248
+ start_time5 = peak_periods["winter_peak_start"].split[1]
249
+ end_time5 = peak_periods["winter_peak_end"].split[1]
250
+ start_date1 = '01-01'
251
+ end_date1 = '03-31'
252
+ start_date2 = '04-01'
253
+ end_date2 = '05-31'
254
+ start_date3 = '06-01'
255
+ end_date3 = '09-30'
256
+ start_date4 = '10-01'
257
+ end_date4 = '11-30'
258
+ start_date5 = '12-01'
259
+ end_date5 = '12-31'
260
+ end
149
261
 
150
- case ashraeClimateZone
151
- when '3A'
152
- start_time = '18:01:00'
153
- end_time = '21:59:00'
154
- when '4A'
155
- start_time = '18:01:00'
156
- end_time = '21:59:00'
157
- when '5A'
158
- start_time = '14:01:00'
159
- end_time = '17:59:00'
160
- when '6A'
161
- start_time = '13:01:00'
162
- end_time = '16:59:00'
163
- when '2A'
164
- start_time = '17:01:00'
165
- end_time = '20:59:00'
166
- when '2B'
167
- start_time = '17:01:00'
168
- end_time = '20:59:00'
169
- when '3A'
170
- start_time = '19:01:00'
171
- end_time = '22:59:00'
172
- when '3B'
173
- start_time = '18:01:00'
174
- end_time = '21:59:00'
175
- when '3C'
176
- start_time = '19:01:00'
177
- end_time = '22:59:00'
178
- when '4A'
179
- start_time = '12:01:00'
180
- end_time = '15:59:00'
181
- when '4B'
182
- start_time = '17:01:00'
183
- end_time = '20:59:00'
184
- when '4C'
185
- start_time = '17:01:00'
186
- end_time = '20:59:00'
187
- when '5A'
188
- start_time = '20:01:00'
189
- end_time = '23:59:00'
190
- when '5B'
191
- start_time = '17:01:00'
192
- end_time = '20:59:00'
193
- when '5C'
194
- start_time = '17:01:00'
195
- end_time = '20:59:00'
196
- when '6A'
197
- start_time = '16:01:00'
198
- end_time = '19:59:00'
199
- when '6B'
200
- start_time = '17:01:00'
201
- end_time = '20:59:00'
202
- when '7A'
203
- start_time = '16:01:00'
204
- end_time = '19:59:00'
262
+ def validate_time_format(star_time, end_time, runner)
263
+ time1 = /(\d\d):(\d\d):(\d\d)/.match(star_time)
264
+ time2 = /(\d\d):(\d\d):(\d\d)/.match(end_time)
265
+ if time1 and time2
266
+ os_starttime = OpenStudio::Time.new(star_time)
267
+ os_endtime = OpenStudio::Time.new(end_time)
268
+ if star_time >= end_time
269
+ runner.registerError('The start time needs to be earlier than the end time.')
270
+ return false
271
+ else
272
+ return os_starttime, os_endtime
273
+ end
205
274
  else
206
- runner.registerError('Invalid ASHRAE climate zone.')
275
+ runner.registerError('The provided time is not in HH-MM-SS format.')
207
276
  return false
208
277
  end
209
278
  end
210
279
 
211
- if /(\d\d):(\d\d):(\d\d)/.match(start_time)
212
- shift_time1 = OpenStudio::Time.new(start_time)
213
- else
214
- runner.registerError('Start time must be in HH-MM-SS format.')
215
- return false
280
+ def validate_date_format(start_date1, end_date1, runner, model)
281
+ smd = /(\d\d)-(\d\d)/.match(start_date1)
282
+ emd = /(\d\d)-(\d\d)/.match(end_date1)
283
+ if smd.nil? or emd.nil?
284
+ runner.registerError('The provided date is not in MM-DD format.')
285
+ return false
286
+ else
287
+ start_month = smd[1].to_i
288
+ start_day = smd[2].to_i
289
+ end_month = emd[1].to_i
290
+ end_day = emd[2].to_i
291
+ if start_date1 > end_date1
292
+ runner.registerError('The start date cannot be later date the end time.')
293
+ return false
294
+ else
295
+ # os_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month), start_day)
296
+ # os_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month), end_day)
297
+ os_start_date = model.getYearDescription.makeDate(start_month, start_day)
298
+ os_end_date = model.getYearDescription.makeDate(end_month, end_day)
299
+ return os_start_date, os_end_date
300
+ end
301
+ end
216
302
  end
217
303
 
218
- if /(\d\d):(\d\d):(\d\d)/.match(end_time)
219
- shift_time2 = OpenStudio::Time.new(end_time)
304
+ # First time period
305
+ time_result1 = validate_time_format(start_time1, end_time1, runner)
306
+ if time_result1
307
+ shift_time_start1, shift_time_end1 = time_result1
220
308
  else
221
- runner.registerError('End time must be in HH-MM-SS format.')
309
+ runner.registerError('The required time period for the reduction is not in correct format!')
222
310
  return false
223
311
  end
224
-
225
- if start_time.to_f > end_time.to_f
226
- runner.registerError('The start time cannot be later than the end time.')
227
- return false
312
+ # The other optional time periods
313
+ shift_time_start2,shift_time_end2,shift_time_start3,shift_time_end3,shift_time_start4,shift_time_end4,shift_time_start5,shift_time_end5 = [nil]*8
314
+ if (not start_time2.empty?) and (not end_time2.empty?)
315
+ time_result2 = validate_time_format(start_time2, end_time2, runner)
316
+ if time_result2
317
+ shift_time_start2, shift_time_end2 = time_result2
318
+ end
319
+ end
320
+ if (not start_time3.empty?) and (not end_time3.empty?)
321
+ time_result3 = validate_time_format(start_time3, end_time3, runner)
322
+ if time_result3
323
+ shift_time_start3, shift_time_end3 = time_result3
324
+ end
325
+ end
326
+ if (not start_time4.empty?) and (not end_time4.empty?)
327
+ time_result4 = validate_time_format(start_time4, end_time4, runner)
328
+ if time_result4
329
+ shift_time_start4, shift_time_end4 = time_result4
330
+ end
331
+ end
332
+ if (not start_time5.empty?) and (not end_time5.empty?)
333
+ time_result5 = validate_time_format(start_time5, end_time5, runner)
334
+ if time_result5
335
+ shift_time_start5, shift_time_end5 = time_result5
336
+ end
228
337
  end
229
338
 
230
- start_month1 = nil
231
- start_day1 = nil
232
- md = /(\d\d)-(\d\d)/.match(start_date1)
233
- if md
234
- start_month1 = md[1].to_i
235
- start_day1 = md[2].to_i
339
+ # First date period
340
+ date_result1 = validate_date_format(start_date1, end_date1, runner, model)
341
+ if date_result1
342
+ os_start_date1, os_end_date1 = date_result1
236
343
  else
237
- runner.registerError('Start date must be in MM-DD format.')
344
+ runner.registerError('The required date period for the reduction is not in correct format!')
238
345
  return false
239
346
  end
240
- end_month1 = nil
241
- end_day1 = nil
242
- md = /(\d\d)-(\d\d)/.match(end_date1)
243
- if md
244
- end_month1 = md[1].to_i
245
- end_day1 = md[2].to_i
246
- else
247
- runner.registerError('End date must be in MM-DD format.')
248
- return false
347
+ # Other optional date period
348
+ os_start_date2, os_end_date2, os_start_date3, os_end_date3, os_start_date4, os_end_date4, os_start_date5, os_end_date5 = [nil]*8
349
+ if (not start_date2.empty?) and (not end_date2.empty?)
350
+ date_result2 = validate_date_format(start_date2, end_date2, runner, model)
351
+ if date_result2
352
+ os_start_date2, os_end_date2 = date_result2
353
+ end
249
354
  end
250
355
 
251
- start_month2 = nil
252
- start_day2 = nil
253
- end_month2 = nil
254
- end_day2 = nil
255
- if (start_date2.empty? and not end_date2.empty?) or (end_date2.empty? and not start_date2.empty?)
256
- runner.registerWarning("Either start date or end date for the second period of reduction is not specified, so the second period of reduction will not be used.")
257
- elsif not start_date2.empty? and (not end_date2.empty?)
258
- smd = /(\d\d)-(\d\d)/.match(start_date2)
259
- if smd
260
- start_month2 = smd[1].to_i
261
- start_day2 = smd[2].to_i
262
- else
263
- runner.registerError('Start date must be in MM-DD format. If you do not want a second period, leave both the start and end date blank.')
264
- return false
265
- end
266
- emd = /(\d\d)-(\d\d)/.match(end_date2)
267
- if emd
268
- end_month2 = emd[1].to_i
269
- end_day2 = emd[2].to_i
270
- else
271
- runner.registerError('End date must be in MM-DD format. If you do not want a second period, leave both the start and end date blank.')
272
- return false
356
+ if (not start_date3.empty?) and (not end_date3.empty?)
357
+ date_result3 = validate_date_format(start_date3, end_date3, runner, model)
358
+ if date_result3
359
+ os_start_date3, os_end_date3 = date_result3
273
360
  end
274
- else
275
-
276
361
  end
277
362
 
278
-
279
- start_month3 = nil
280
- start_day3 = nil
281
- end_month3 = nil
282
- end_day3 = nil
283
- if (start_date3.empty? and not end_date3.empty?) or (end_date3.empty? and not start_date3.empty?)
284
- runner.registerWarning("Either start date or end date for the third period of reduction is not specified, so the second period of reduction will not be used.")
285
- elsif not start_date3.empty? and (not end_date3.empty?)
286
- smd = /(\d\d)-(\d\d)/.match(start_date3)
287
- if smd
288
- start_month3 = smd[1].to_i
289
- start_day3 = smd[2].to_i
290
- else
291
- runner.registerError('Start date must be in MM-DD format. If you do not want a third period, leave both the start and end date blank.')
292
- return false
293
- end
294
- emd = /(\d\d)-(\d\d)/.match(end_date3)
295
- if emd
296
- end_month3 = emd[1].to_i
297
- end_day3 = emd[2].to_i
298
- else
299
- runner.registerError('End date must be in MM-DD format. If you do not want a third period, leave both the start and end date blank.')
300
- return false
363
+ if (not start_date4.empty?) and (not end_date4.empty?)
364
+ date_result4 = validate_date_format(start_date4, end_date4, runner, model)
365
+ if date_result4
366
+ os_start_date4, os_end_date4 = date_result4
301
367
  end
302
368
  end
303
369
 
304
- os_start_date1 = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month1), start_day1)
305
- os_end_date1 = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month1), end_day1)
306
- os_start_date2 = nil
307
- os_end_date2 = nil
308
- if [start_month2, start_day2, end_month2, end_day2].all?
309
- os_start_date2 = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month2), start_day2)
310
- os_end_date2 = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month2), end_day2)
311
- end
312
- os_start_date3 = nil
313
- os_end_date3 = nil
314
- if [start_month3, start_day3, end_month3, end_day3].all?
315
- os_start_date3 = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month3), start_day3)
316
- os_end_date3 = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month3), end_day3)
370
+ if (not start_date5.empty?) and (not end_date5.empty?)
371
+ date_result5 = validate_date_format(start_date5, end_date5, runner, model)
372
+ if date_result5
373
+ os_start_date5, os_end_date5 = date_result5
374
+ end
317
375
  end
318
376
 
377
+
319
378
  # daylightsaving adjustment added in visualization, so deprecated here
320
379
  # # Check model's daylight saving period, if cooling period is within daylight saving period, shift the cooling start time and end time by one hour later
321
380
  # # only check the first period now, assuming majority will only modify for a single period
@@ -329,145 +388,220 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
329
388
  # end
330
389
  # end
331
390
 
332
- lpd_factor = 1 - (lpd_reduce_percent/100)
391
+ adjust_period_inputs = { "period1" => {"date_start"=>os_start_date1, "date_end"=>os_end_date1,
392
+ "time_start"=>shift_time_start1, "time_end"=>shift_time_end1},
393
+ "period2" => {"date_start"=>os_start_date2, "date_end"=>os_end_date2,
394
+ "time_start"=>shift_time_start2, "time_end"=>shift_time_end2},
395
+ "period3" => {"date_start"=>os_start_date3, "date_end"=>os_end_date3,
396
+ "time_start"=>shift_time_start3, "time_end"=>shift_time_end3},
397
+ "period4" => {"date_start"=>os_start_date4, "date_end"=>os_end_date4,
398
+ "time_start"=>shift_time_start4, "time_end"=>shift_time_end4},
399
+ "period5" => {"date_start"=>os_start_date5, "date_end"=>os_end_date5,
400
+ "time_start"=>shift_time_start5, "time_end"=>shift_time_end5} }
401
+
402
+
403
+ exclude_space_types = ["ER_Exam", "ER_NurseStn", "ER_Trauma", "ER_Triage", "ICU_NurseStn", "ICU_Open", "ICU_PatRm", "Lab",
404
+ "OR", "Anesthesia", "BioHazard", "Exam", "MedGas", "OR", "PACU", "PreOp", "ProcedureRoom", "Lab with fume hood", 'HspSurgOutptLab']
405
+
333
406
  applicable = false
334
- lights = model.getLightss
335
407
  # create a hash to map the old schedule name to the new schedule
336
408
  light_schedules = {}
337
- lights.each do |light|
338
- puts "light: #{light.name}"
409
+ model.getLightss.each do |light|
339
410
  light_sch = light.schedule
340
- puts "light_sch: #{light_sch}"
341
411
  if light_sch.empty?
342
412
  runner.registerWarning("#{light.name} doesn't have a schedule.")
343
413
  else
414
+ occupied = false
415
+ if light.spaceType.is_initialized
416
+ space_type = light.spaceType.get
417
+ peoples = space_type.people
418
+ peoples.each do |people|
419
+ if people.numberOfPeople.is_initialized and people.numberOfPeople.get > 0
420
+ occupied = true
421
+ break
422
+ elsif people. peoplePerFloorArea.is_initialized and people.peoplePerFloorArea.get > 0
423
+ occupied = true
424
+ break
425
+ elsif people. spaceFloorAreaPerPerson.is_initialized and people.spaceFloorAreaPerPerson.get > 0
426
+ occupied = true
427
+ break
428
+ end
429
+ end
430
+ elsif light.space.is_initialized
431
+ space = light.space.get
432
+ # Any of the peoplePerFloorArea, numberOfPeople or floorAreaPerPerson will calculate the people automatically
433
+ # despite the Number of People Calculation Method specified
434
+ if space.peoplePerFloorArea > 0
435
+ occupied = true
436
+ end
437
+ end
438
+ if occupied
439
+ lpd_factor = 1 - (lpd_reduce_percent/100)
440
+ runner.registerInfo("The space/space type associated with the light #{light.name.to_s} is occupied, so the LPD is reduced for #{lpd_reduce_percent}%")
441
+ else
442
+ lpd_factor = 1 - (lpd_reduce_percent_unoccupied/100)
443
+ runner.registerInfo("The space/space type associated with the light #{light.name.to_s} is unoccupied, so the LPD is reduced for #{lpd_reduce_percent_unoccupied}%")
444
+ end
445
+
344
446
  if light_schedules.key?(light_sch.get.name.to_s)
345
- new_light_sch = light_schedules[light_sch.get.name.to_s]
447
+ new_light_sch = light_schedules[light_sch.get.name.to_s]["schedule"]
346
448
  else
347
449
  new_light_sch = light_sch.get.clone(model)
348
450
  new_light_sch = new_light_sch.to_Schedule.get
349
451
  new_light_sch.setName("#{light_sch.get.name.to_s} adjusted #{lpd_factor}")
350
452
  # add to the hash
351
- light_schedules[light_sch.get.name.to_s] = new_light_sch
453
+ light_schedules[light_sch.get.name.to_s] = {"schedule"=>new_light_sch, "lpd_factor" => lpd_factor}
352
454
  end
353
455
  light.setSchedule(new_light_sch)
354
456
  end
457
+
458
+
355
459
  end
356
460
 
357
- light_schedules.each do |old_name, light_sch|
358
- puts "sch name: #{old_name}"
359
- if light_sch.to_ScheduleRuleset.empty?
461
+ light_schedules.each do |old_name, sch_info|
462
+ cloned_light_sch = sch_info["schedule"]
463
+ lpd_factor = sch_info["lpd_factor"]
464
+ if cloned_light_sch.to_ScheduleRuleset.empty?
360
465
  runner.registerWarning("Schedule #{old_name} isn't a ScheduleRuleset object and won't be altered by this measure.")
361
- light_sch.remove # remove un-used cloned schedule
466
+ cloned_light_sch.remove # remove un-used cloned schedule
362
467
  else
363
- schedule_set = light_sch.to_ScheduleRuleset.get
364
- default_rule = schedule_set.defaultDaySchedule
468
+ schedule_set = cloned_light_sch.to_ScheduleRuleset.get
365
469
  rules = schedule_set.scheduleRules
366
470
  days_covered = Array.new(7, false)
367
471
  original_rule_number = rules.length
472
+ current_index = 0
368
473
  if original_rule_number > 0
369
- runner.registerInfo("------------ schedule rule set #{old_name} has #{original_rule_number} rules.")
370
- current_index = 0
474
+ runner.registerInfo("schedule rule set #{old_name} has #{original_rule_number} rules.")
371
475
  # rules are in order of priority
372
476
  rules.each do |rule|
373
- runner.registerInfo("------------ Rule #{rule.ruleIndex}: #{rule.daySchedule.name.to_s}")
374
- rule_period1 = rule.clone(model).to_ScheduleRule.get # OpenStudio::Model::ScheduleRule.new(schedule_set, rule.daySchedule)
375
- rule_period1.setStartDate(os_start_date1)
376
- rule_period1.setEndDate(os_end_date1)
377
- checkDaysCovered(rule_period1, days_covered)
378
- runner.registerInfo("--------------- current days of week coverage: #{days_covered}")
379
-
380
- day_rule_period1 = rule_period1.daySchedule
381
- day_time_vector1 = day_rule_period1.times
382
- day_value_vector1 = day_rule_period1.values
383
- runner.registerInfo(" ------------ time: #{day_time_vector1.map {|os_time| os_time.toString}}")
384
- runner.registerInfo(" ------------ values: #{day_value_vector1}")
385
- unless day_value_vector1.empty?
386
- applicable = true
387
- end
388
- day_rule_period1.clearValues
389
- day_rule_period1 = updateDaySchedule(day_rule_period1, day_time_vector1, day_value_vector1, shift_time1, shift_time2, lpd_factor)
390
- # set the order of the new cloned schedule rule, to make sure the modified rule has a higher priority than the original one
391
- # and different copies keep the same priority as their original orders
392
- unless schedule_set.setScheduleRuleIndex(rule_period1, current_index)
393
- runner.registerError("Fail to set rule index for #{day_rule_period1.name.to_s}.")
394
- end
395
- current_index += 1
396
- runner.registerInfo(" ------------ updated time: #{day_rule_period1.times.map {|os_time| os_time.toString}}")
397
- runner.registerInfo(" ------------ updated values: #{day_rule_period1.values}")
398
- runner.registerInfo(" ------------ schedule updated for #{rule_period1.startDate.get} to #{rule_period1.endDate.get}")
399
-
400
- if os_start_date2 and os_end_date2
401
- # If has a second period, make another copy and modify the order as well
402
- rule_period2 = copy_sch_rule_for_period(model, rule_period1, rule_period1.daySchedule, os_start_date2, os_end_date2)
403
- unless schedule_set.setScheduleRuleIndex(rule_period2, 0)
404
- runner.registerError("Fail to set rule index for #{rule_period2.daySchedule.name.to_s}.")
477
+ runner.registerInfo("---- Rule No.#{rule.ruleIndex}: #{rule.name.to_s}")
478
+ if rule.dateSpecificationType == "SpecificDates"
479
+ ## if the rule applies to SpecificDates, collect the dates that fall into each adjustment date period,
480
+ ## and create a new rule for each date period with covered specific dates
481
+ runner.registerInfo("======= The rule #{rule.name.to_s} only covers specific dates.")
482
+ ## the specificDates cannot be modified in place because it's a frozen array
483
+ all_specific_dates = []
484
+ rule.specificDates.each { |date| all_specific_dates << date }
485
+ adjust_period_inputs.each do |period, period_inputs|
486
+ period_inputs["specific_dates"] = []
487
+ os_start_date = period_inputs["date_start"]
488
+ os_end_date = period_inputs["date_end"]
489
+ shift_time_start = period_inputs["time_start"]
490
+ shift_time_end = period_inputs["time_end"]
491
+ if [os_start_date, os_end_date, shift_time_start, shift_time_end].all?
492
+ rule.specificDates.each do |covered_date|
493
+ if covered_date >= os_start_date and covered_date <= os_end_date
494
+ period_inputs["specific_dates"] << covered_date
495
+ all_specific_dates.delete(covered_date)
496
+ end
497
+ end
498
+ runner.registerInfo("========= Specific dates within date range #{os_start_date.to_s} to #{os_end_date.to_s}: #{period_inputs["specific_dates"].map(&:to_s)}")
499
+ runner.registerInfo("!!! Specific dates haven't been covered: #{all_specific_dates.map(&:to_s)}")
500
+ next if period_inputs["specific_dates"].empty?
501
+ rule_period = modify_rule_for_specific_dates(rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
502
+ lpd_factor, period_inputs["specific_dates"])
503
+ if rule_period
504
+ applicable = true
505
+ if schedule_set.setScheduleRuleIndex(rule_period, current_index)
506
+ current_index += 1
507
+ runner.registerInfo("-------- The rule #{rule_period.name.to_s} for #{rule_period.dateSpecificationType} is added as priority #{current_index}")
508
+ else
509
+ runner.registerError("Fail to set rule index for #{rule_period.name.to_s}.")
510
+ end
511
+ end
512
+ end
405
513
  end
406
- current_index += 1
407
- runner.registerInfo(" ------------ schedule updated for #{rule_period2.startDate.get} to #{rule_period2.endDate.get}")
408
- end
409
-
410
- if os_start_date3 and os_end_date3
411
- # If has a third period, make another copy and modify the order as well
412
- rule_period3 = copy_sch_rule_for_period(model, rule_period1, rule_period1.daySchedule, os_start_date3, os_end_date3)
413
- unless schedule_set.setScheduleRuleIndex(rule_period3, 0)
414
- runner.registerError("Fail to set rule index for #{rule_period3.daySchedule.name.to_s}.")
514
+ if all_specific_dates.empty?
515
+ ## if all specific dates have been covered by new rules for each adjustment date period, remove the original rule
516
+ runner.registerInfo("The original rule is removed since no specific date left")
517
+ else
518
+ ## if there's still dates left to be covered, modify the original rule to only cover these dates
519
+ ## (this is just in case that the rule order was not set correctly, and the original rule is still applied to all specific dates;
520
+ ## also to make the logic in OSM more clearer)
521
+ ## the specificDates cannot be modified in place, so create a new rule with the left dates to replace the original rule
522
+ original_rule_update = clone_rule_with_new_dayschedule(rule, rule.name.to_s + " - dates left")
523
+ schedule_set.setScheduleRuleIndex(original_rule_update, current_index)
524
+ current_index += 1
525
+ all_specific_dates.each do |date|
526
+ original_rule_update.addSpecificDate(date)
527
+ end
528
+ runner.registerInfo("-------- The original rule #{rule.name.to_s} is modified to only cover the rest of the dates: #{all_specific_dates.map(&:to_s)}")
529
+ runner.registerInfo("-------- and is shifted to priority #{current_index}")
530
+ end
531
+ rule.remove
532
+
533
+ else
534
+ ## If the rule applies to a DateRange, check if the DateRange overlaps with each adjustment date period
535
+ ## if so, create a new rule for that adjustment date period
536
+ runner.registerInfo("******* The rule #{rule.name.to_s} covers date range #{rule.startDate.get} - #{rule.endDate.get}.")
537
+ # runner.registerInfo("Before adjustment, the rule #{rule.name.to_s} is #{rule.daySchedule.values}")
538
+ adjust_period_inputs.each do |period, period_inputs|
539
+ os_start_date = period_inputs["date_start"]
540
+ os_end_date = period_inputs["date_end"]
541
+ shift_time_start = period_inputs["time_start"]
542
+ shift_time_end = period_inputs["time_end"]
543
+ if [os_start_date, os_end_date, shift_time_start, shift_time_end].all?
544
+ overlapped, new_start_dates, new_end_dates = check_date_ranges_overlap(rule, os_start_date, os_end_date)
545
+ if overlapped
546
+ new_start_dates.each_with_index do |start_date, i|
547
+ rule_period = modify_rule_for_date_period(rule, start_date, new_end_dates[i], shift_time_start, shift_time_end, lpd_factor)
548
+ if rule_period
549
+ # runner.registerInfo("After adjustment, the rule #{rule.name.to_s} becomes #{rule.daySchedule.values}")
550
+ # runner.registerInfo("After adjustment, the new rule is #{rule_period.daySchedule.values}")
551
+ applicable = true
552
+ if period == "period1"
553
+ checkDaysCovered(rule_period, days_covered)
554
+ end
555
+ if schedule_set.setScheduleRuleIndex(rule_period, current_index)
556
+ runner.registerInfo("-------- The rule #{rule_period.name.to_s} is added as priority #{current_index}")
557
+ current_index += 1
558
+ else
559
+ runner.registerError("-------- Fail to set rule index for #{rule_period.name.to_s}.")
560
+ end
561
+ end
562
+ end
563
+ end
564
+ end
565
+ end
566
+ ## The original rule will be shifted to the currently lowest priority
567
+ ## Setting the rule to an existing index will automatically push all other rules after it down
568
+ if schedule_set.setScheduleRuleIndex(rule, current_index)
569
+ runner.registerInfo("-------- The original rule #{rule.name.to_s} is shifted to priority #{current_index}")
570
+ current_index += 1
571
+ else
572
+ runner.registerError("Fail to set rule index for #{rule.name.to_s}.")
415
573
  end
416
- current_index += 1
417
- runner.registerInfo(" ------------ schedule updated for #{rule_period3.startDate.get} to #{rule_period3.endDate.get}")
418
- end
419
-
420
- # The original rule will be shifted to have the currently lowest priority
421
- unless schedule_set.setScheduleRuleIndex(rule, original_rule_number + current_index - 1)
422
- runner.registerError("Fail to set rule index for #{rule.daySchedule.name.to_s}.")
423
574
  end
424
- # runner.registerInfo("--------------- Current sule index of the original rule: #{rule.ruleIndex}")
425
575
  end
426
576
  else
427
577
  runner.registerWarning("Lighting schedule #{old_name} is a ScheduleRuleSet, but has no ScheduleRules associated. It won't be altered by this measure.")
428
578
  end
429
579
 
580
+ # if all rules not covered all days in a week, then the rest of days use defaultDaySchedule
581
+ # defaultDaySchedule cannot specify date range, so clone it to a new rule to set date range and cover the rest of days
582
+ default_day = schedule_set.defaultDaySchedule
430
583
  if days_covered.include?(false)
431
- new_default_rule = OpenStudio::Model::ScheduleRule.new(schedule_set)
432
- new_default_rule.setStartDate(os_start_date1)
433
- new_default_rule.setEndDate(os_end_date1)
434
- coverMissingDays(new_default_rule, days_covered)
435
- checkDaysCovered(new_default_rule, days_covered)
436
-
437
- cloned_default_day = default_rule.clone(model)
438
- cloned_default_day.setParent(new_default_rule)
439
-
440
- new_default_day = new_default_rule.daySchedule
441
- day_time_vector = new_default_day.times
442
- day_value_vector = new_default_day.values
443
- new_default_day.clearValues
444
- new_default_day = updateDaySchedule(new_default_day, day_time_vector, day_value_vector, shift_time1, shift_time2, lpd_factor)
445
- if os_start_date2 and os_end_date2
446
- copy_sch_rule_for_period(model, new_default_rule, new_default_day, os_start_date2, os_end_date2)
447
- end
448
- if os_start_date3 and os_end_date3
449
- copy_sch_rule_for_period(model, new_default_rule, new_default_day, os_start_date3, os_end_date3)
584
+ runner.registerInfo("-------- Some days use default day. Adding new scheduleRule from defaultDaySchedule for applicable date period.")
585
+ adjust_period_inputs.each do |period, period_inputs|
586
+ runner.registerInfo("******** current covered days: #{days_covered}")
587
+ os_start_date = period_inputs["date_start"]
588
+ os_end_date = period_inputs["date_end"]
589
+ shift_time_start = period_inputs["time_start"]
590
+ shift_time_end = period_inputs["time_end"]
591
+ if [os_start_date, os_end_date, shift_time_start, shift_time_end].all?
592
+ new_default_rule_period = modify_default_day_for_date_period(schedule_set, default_day, days_covered, os_start_date, os_end_date,
593
+ shift_time_start, shift_time_end, lpd_factor)
594
+ schedule_set.setScheduleRuleIndex(new_default_rule_period, current_index)
595
+ end
450
596
  end
451
-
452
597
  end
453
-
598
+ # schedule_set.scheduleRules.each do |final_rule|
599
+ # runner.registerInfo("Finally rules: #{final_rule.name.to_s} is in priority #{final_rule.ruleIndex}")
600
+ # end
454
601
  end
455
602
 
456
603
  end
457
604
 
458
- # # Check the final rules within each schedule
459
- # runner.registerInfo("------------------------FINAL--------------------")
460
- # space_type.lights.each do |light|
461
- # lgt_schedule_set = light.schedule
462
- # unless lgt_schedule_set.empty?
463
- # runner.registerInfo("Schedule #{lgt_schedule_set.get.name.to_s}:")
464
- # sch_set = lgt_schedule_set.get.to_Schedule.get
465
- # sch_set.to_ScheduleRuleset.get.scheduleRules.each do |rule|
466
- # runner.registerInfo(" rule #{rule.ruleIndex}: #{rule.daySchedule.name.to_s} from #{rule.startDate.get} to #{rule.endDate.get}")
467
- # end
468
- # end
469
- # end
470
-
471
605
  unless applicable
472
606
  runner.registerAsNotApplicable('No lighting schedule in the model could be altered.')
473
607
  end
@@ -475,15 +609,107 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
475
609
  return true
476
610
  end
477
611
 
478
- def copy_sch_rule_for_period(model, sch_rule, sch_day, start_date, end_date)
479
- new_rule = sch_rule.clone(model).to_ScheduleRule.get
480
- new_rule.setStartDate(start_date)
481
- new_rule.setEndDate(end_date)
612
+ def check_date_ranges_overlap(rule, adjust_start_date, adjust_end_date)
613
+ ## check if the original rule applied DateRange overlaps with the adjustment date period
614
+ overlapped = false
615
+ new_start_dates = []
616
+ new_end_dates = []
617
+ if rule.endDate.get >= rule.startDate.get and rule.startDate.get <= adjust_end_date and rule.endDate.get >= adjust_start_date
618
+ overlapped = true
619
+ new_start_dates << [adjust_start_date, rule.startDate.get].max
620
+ new_end_dates << [adjust_end_date, rule.endDate.get].min
621
+ elsif rule.endDate.get < rule.startDate.get
622
+ ## If the DateRange has a endDate < startDate, the range wraps around the year.
623
+ if rule.endDate.get >= adjust_start_date
624
+ overlapped = true
625
+ new_start_dates << adjust_start_date
626
+ new_end_dates << rule.endDate.get
627
+ end
628
+ if rule.startDate.get <= adjust_end_date
629
+ overlapped = true
630
+ new_start_dates << rule.startDate.get
631
+ new_end_dates << adjust_end_date
632
+ end
633
+ end
634
+ return overlapped, new_start_dates, new_end_dates
635
+ end
636
+
637
+ def clone_rule_with_new_dayschedule(original_rule, new_rule_name)
638
+ ## Cloning a scheduleRule will automatically clone the daySchedule associated with it, but it's a shallow copy,
639
+ ## because the daySchedule is a resource that can be used by many scheduleRule
640
+ ## Therefore, once the daySchedule is modified for the cloned scheduleRule, the original daySchedule is also changed
641
+ ## Also, there's no function to assign a new daySchedule to the existing scheduleRule,
642
+ ## so the only way to clone the scheduleRule but change the daySchedule is to construct a new scheduleRule with a daySchedule passed in
643
+ ## and copy all other settings from the original scheduleRule
644
+ rule_period = OpenStudio::Model::ScheduleRule.new(original_rule.scheduleRuleset, original_rule.daySchedule)
645
+ rule_period.setName(new_rule_name)
646
+ rule_period.setApplySunday(original_rule.applySunday)
647
+ rule_period.setApplyMonday(original_rule.applyMonday)
648
+ rule_period.setApplyTuesday(original_rule.applyTuesday)
649
+ rule_period.setApplyWednesday(original_rule.applyWednesday)
650
+ rule_period.setApplyThursday(original_rule.applyThursday)
651
+ rule_period.setApplyFriday(original_rule.applyFriday)
652
+ rule_period.setApplySaturday(original_rule.applySaturday)
653
+ return rule_period
654
+ end
482
655
 
483
- new_day_sch = sch_day.clone(model)
484
- new_day_sch.setParent(new_rule)
656
+ def modify_rule_for_date_period(original_rule, os_start_date, os_end_date, shift_time_start, shift_time_end, adjustment)
657
+ # The cloned scheduleRule will automatically belongs to the originally scheduleRuleSet
658
+ # rule_period = original_rule.clone(model).to_ScheduleRule.get
659
+ # rule_period.daySchedule = original_rule.daySchedule.clone(model)
660
+ new_rule_name = "#{original_rule.name.to_s} with DF for #{os_start_date.to_s} to #{os_end_date.to_s}"
661
+ rule_period = clone_rule_with_new_dayschedule(original_rule, new_rule_name)
662
+ day_rule_period = rule_period.daySchedule
663
+ day_time_vector = day_rule_period.times
664
+ day_value_vector = day_rule_period.values
665
+ if day_time_vector.empty?
666
+ return false
667
+ end
668
+ day_rule_period.clearValues
669
+ updateDaySchedule(day_rule_period, day_time_vector, day_value_vector, shift_time_start, shift_time_end, adjustment)
670
+ if rule_period
671
+ rule_period.setStartDate(os_start_date)
672
+ rule_period.setEndDate(os_end_date)
673
+ end
674
+ return rule_period
675
+ end
485
676
 
486
- return new_rule
677
+ def modify_rule_for_specific_dates(original_rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
678
+ adjustment, applied_dates)
679
+ new_rule_name = "#{original_rule.name.to_s} with DF for #{os_start_date.to_s} to #{os_end_date.to_s}"
680
+ rule_period = clone_rule_with_new_dayschedule(original_rule, new_rule_name)
681
+ day_rule_period = rule_period.daySchedule
682
+ day_time_vector = day_rule_period.times
683
+ day_value_vector = day_rule_period.values
684
+ if day_time_vector.empty?
685
+ return false
686
+ end
687
+ day_rule_period.clearValues
688
+ updateDaySchedule(day_rule_period, day_time_vector, day_value_vector, shift_time_start, shift_time_end, adjustment)
689
+ if rule_period
690
+ applied_dates.each do |date|
691
+ rule_period.addSpecificDate(date)
692
+ end
693
+ end
694
+ return rule_period
695
+ end
696
+
697
+ def modify_default_day_for_date_period(schedule_set, default_day, days_covered, os_start_date, os_end_date,
698
+ shift_time_start, shift_time_end, lpd_factor)
699
+ # the new rule created for the ScheduleRuleSet by default has the highest priority (ruleIndex=0)
700
+ new_default_rule = OpenStudio::Model::ScheduleRule.new(schedule_set, default_day)
701
+ new_default_rule.setName("#{schedule_set.name.to_s} default day with DF for #{os_start_date.to_s} to #{os_end_date.to_s}")
702
+ new_default_rule.setStartDate(os_start_date)
703
+ new_default_rule.setEndDate(os_end_date)
704
+ coverMissingDays(new_default_rule, days_covered)
705
+ new_default_day = new_default_rule.daySchedule
706
+ day_time_vector = new_default_day.times
707
+ day_value_vector = new_default_day.values
708
+ new_default_day.clearValues
709
+ new_default_day = updateDaySchedule(new_default_day, day_time_vector, day_value_vector, shift_time_start, shift_time_end, lpd_factor)
710
+ # schedule_set.setScheduleRuleIndex(new_default_rule, 0)
711
+ return new_default_rule
712
+ # TODO: if the scheduleRuleSet has holidaySchedule (which is a ScheduleDay), it cannot be altered
487
713
  end
488
714
 
489
715
  def checkDaysCovered(sch_rule, sch_day_covered)
@@ -568,7 +794,6 @@ class ReduceLPDByPercentageForPeakHours < OpenStudio::Measure::ModelMeasure
568
794
  sch_day.addValue(exist_timestamp, vec_value[i])
569
795
  end
570
796
  end
571
-
572
797
  return sch_day
573
798
  end
574
799