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