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.
- checksums.yaml +4 -4
- data/.gitignore +7 -3
- data/CHANGELOG.md +7 -0
- data/Gemfile +7 -17
- data/files/seasonal_shedding_peak_hours.json +1 -0
- data/files/seasonal_shifting_peak_hours.json +1 -0
- data/files/seasonal_shifting_take_hours.json +1 -0
- data/lib/measures/AddElectricVehicleChargingLoad/measure.rb +247 -21
- data/lib/measures/AddElectricVehicleChargingLoad/tests/CZ06RV2.epw +8768 -0
- data/lib/measures/AddElectricVehicleChargingLoad/tests/add_electric_vehicle_charging_load_test.rb +40 -141
- data/lib/measures/AddElectricVehicleChargingLoad/tests/test.osm +55 -55
- data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/measure.rb +963 -442
- data/lib/measures/add_ceiling_fan/measure.rb +2 -2
- data/lib/measures/add_chilled_water_storage_tank/measure.rb +2 -2
- data/lib/measures/add_exterior_blinds_and_control/measure.rb +6 -5
- data/lib/measures/add_interior_blinds_and_control/measure.rb +5 -5
- data/lib/measures/add_rooftop_pv_simple/measure.rb +13 -10
- data/lib/measures/apply_dynamic_coating_to_roof_wall/measure.rb +2 -2
- data/lib/measures/average_ventilation_for_peak_hours/measure.rb +5 -5
- data/lib/measures/enable_occupancy_driven_lighting/measure.rb +2 -2
- data/lib/measures/precooling/measure.rb +964 -354
- data/lib/measures/preheating/measure.rb +940 -356
- data/lib/measures/reduce_epd_by_percentage_for_peak_hours/measure.rb +509 -300
- data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/measure.rb +534 -309
- data/lib/openstudio/geb/run.rb +5 -1
- data/lib/openstudio/geb/utilities.rb +3 -2
- data/lib/openstudio/geb/version.rb +1 -1
- data/openstudio-geb.gemspec +7 -6
- metadata +24 -48
@@ -4,6 +4,8 @@
|
|
4
4
|
# *******************************************************************************
|
5
5
|
|
6
6
|
# start the measure
|
7
|
+
require 'json'
|
8
|
+
require 'set'
|
7
9
|
class Precooling < OpenStudio::Measure::ModelMeasure
|
8
10
|
# define the name that a user will see
|
9
11
|
def name
|
@@ -15,7 +17,7 @@ class Precooling < OpenStudio::Measure::ModelMeasure
|
|
15
17
|
end
|
16
18
|
# human readable description of modeling approach
|
17
19
|
def modeler_description
|
18
|
-
return "This measure will clone all of the schedules that are used
|
20
|
+
return "This measure will clone all of the schedules that are used for cooling setpoints for thermal zones. The clones are hooked up to the thermostat in place of the original schedules. Then the schedules are adjusted by the specified values. HVAC operation schedule will also be changed. There is a checkbox to determine if the thermostat for design days should be altered."
|
19
21
|
end
|
20
22
|
# define the arguments that the user will input
|
21
23
|
def arguments(model)
|
@@ -24,66 +26,131 @@ class Precooling < OpenStudio::Measure::ModelMeasure
|
|
24
26
|
# make an argument for adjustment to cooling setpoint
|
25
27
|
cooling_adjustment = OpenStudio::Measure::OSArgument.makeDoubleArgument('cooling_adjustment', true)
|
26
28
|
cooling_adjustment.setDisplayName('Degrees Fahrenheit to Adjust Cooling Setpoint By')
|
29
|
+
cooling_adjustment.setDescription('Use negative value for reducing cooling setpoint during precooling period')
|
27
30
|
cooling_adjustment.setDefaultValue(-4.0)
|
28
31
|
args << cooling_adjustment
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
33
|
+
start_date1 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date1', true)
|
34
|
+
start_date1.setDisplayName('First start date for precooling')
|
35
|
+
start_date1.setDescription('In MM-DD format')
|
36
|
+
start_date1.setDefaultValue('06-01')
|
37
|
+
args << start_date1
|
38
|
+
end_date1 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date1', true)
|
39
|
+
end_date1.setDisplayName('First end date for precooling')
|
40
|
+
end_date1.setDescription('In MM-DD format')
|
41
|
+
end_date1.setDefaultValue('09-30')
|
42
|
+
args << end_date1
|
43
|
+
|
44
|
+
start_date2 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date2', false)
|
45
|
+
start_date2.setDisplayName('Second start date for precooling (optional)')
|
46
|
+
start_date2.setDescription('Specify a date in MM-DD format if you want a second season of precooling; leave blank if not needed.')
|
47
|
+
start_date2.setDefaultValue('')
|
48
|
+
args << start_date2
|
49
|
+
end_date2 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date2', false)
|
50
|
+
end_date2.setDisplayName('Second end date for precooling')
|
51
|
+
end_date2.setDescription('Specify a date in MM-DD format if you want a second season of precooling; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
|
52
|
+
end_date2.setDefaultValue('')
|
53
|
+
args << end_date2
|
54
|
+
|
55
|
+
|
56
|
+
start_date3 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date3', false)
|
57
|
+
start_date3.setDisplayName('Third start date for precooling (optional)')
|
58
|
+
start_date3.setDescription('Specify a date in MM-DD format if you want a third season of precooling; leave blank if not needed.')
|
59
|
+
start_date3.setDefaultValue('')
|
60
|
+
args << start_date3
|
61
|
+
end_date3 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date3', false)
|
62
|
+
end_date3.setDisplayName('Third end date for precooling')
|
63
|
+
end_date3.setDescription('Specify a date in MM-DD format if you want a third season of precooling; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
|
64
|
+
end_date3.setDefaultValue('')
|
65
|
+
args << end_date3
|
66
|
+
|
67
|
+
start_date4 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date4', false)
|
68
|
+
start_date4.setDisplayName('Fourth start date for precooling (optional)')
|
69
|
+
start_date4.setDescription('Specify a date in MM-DD format if you want a fourth season of precooling; leave blank if not needed.')
|
70
|
+
start_date4.setDefaultValue('')
|
71
|
+
args << start_date4
|
72
|
+
end_date4 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date4', false)
|
73
|
+
end_date4.setDisplayName('Fourth end date for precooling')
|
74
|
+
end_date4.setDescription('Specify a date in MM-DD format if you want a fourth season of precooling; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
|
75
|
+
end_date4.setDefaultValue('')
|
76
|
+
args << end_date4
|
77
|
+
|
78
|
+
|
79
|
+
start_date5 = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date5', false)
|
80
|
+
start_date5.setDisplayName('Fifth start date for precooling (optional)')
|
81
|
+
start_date5.setDescription('Specify a date in MM-DD format if you want a fifth season of precooling; leave blank if not needed.')
|
82
|
+
start_date5.setDefaultValue('')
|
83
|
+
args << start_date5
|
84
|
+
end_date5 = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date5', false)
|
85
|
+
end_date5.setDisplayName('Fifth end date for precooling')
|
86
|
+
end_date5.setDescription('Specify a date in MM-DD format if you want a fifth season of precooling; leave blank if not needed. If either the start or end date is blank, the period is considered not used.')
|
87
|
+
end_date5.setDefaultValue('')
|
88
|
+
args << end_date5
|
89
|
+
|
90
|
+
start_time1 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time1', true)
|
91
|
+
start_time1.setDisplayName('Start time of precooling for the first season')
|
92
|
+
start_time1.setDescription('In HH:MM:SS format')
|
93
|
+
start_time1.setDefaultValue('17:00:00')
|
94
|
+
args << start_time1
|
95
|
+
end_time1 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time1', true)
|
96
|
+
end_time1.setDisplayName('End time of precooling for the first season')
|
97
|
+
end_time1.setDescription('In HH:MM:SS format')
|
98
|
+
end_time1.setDefaultValue('21:00:00')
|
99
|
+
args << end_time1
|
100
|
+
|
101
|
+
|
102
|
+
start_time2 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time2', false)
|
103
|
+
start_time2.setDisplayName('Start time of precooling for the second season (optional)')
|
104
|
+
start_time2.setDescription('In HH:MM:SS format')
|
105
|
+
start_time2.setDefaultValue('')
|
106
|
+
args << start_time2
|
107
|
+
end_time2 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time2', false)
|
108
|
+
end_time2.setDisplayName('End time of precooling for the second season (optional)')
|
109
|
+
end_time2.setDescription('In HH:MM:SS format')
|
110
|
+
end_time2.setDefaultValue('')
|
111
|
+
args << end_time2
|
112
|
+
|
113
|
+
|
114
|
+
start_time3 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time3', false)
|
115
|
+
start_time3.setDisplayName('Start time of precooling for the third season (optional)')
|
116
|
+
start_time3.setDescription('In HH:MM:SS format')
|
117
|
+
start_time3.setDefaultValue('')
|
118
|
+
args << start_time3
|
119
|
+
end_time3 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time3', false)
|
120
|
+
end_time3.setDisplayName('End time of precooling for the third season (optional)')
|
121
|
+
end_time3.setDescription('In HH:MM:SS format')
|
122
|
+
end_time3.setDefaultValue('')
|
123
|
+
args << end_time3
|
124
|
+
|
125
|
+
|
126
|
+
start_time4 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time4', false)
|
127
|
+
start_time4.setDisplayName('Start time of precooling for the fourth season (optional)')
|
128
|
+
start_time4.setDescription('In HH:MM:SS format')
|
129
|
+
start_time4.setDefaultValue('')
|
130
|
+
args << start_time4
|
131
|
+
end_time4 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time4', false)
|
132
|
+
end_time4.setDisplayName('End time of precooling for the fourth season (optional)')
|
133
|
+
end_time4.setDescription('In HH:MM:SS format')
|
134
|
+
end_time4.setDefaultValue('')
|
135
|
+
args << end_time4
|
136
|
+
|
137
|
+
|
138
|
+
start_time5 = OpenStudio::Measure::OSArgument.makeStringArgument('start_time5', false)
|
139
|
+
start_time5.setDisplayName('Start time of precooling for the fifth season (optional)')
|
140
|
+
start_time5.setDescription('In HH:MM:SS format')
|
141
|
+
start_time5.setDefaultValue('')
|
142
|
+
args << start_time5
|
143
|
+
end_time5 = OpenStudio::Measure::OSArgument.makeStringArgument('end_time5', false)
|
144
|
+
end_time5.setDisplayName('End time of precooling for the fifth season (optional)')
|
145
|
+
end_time5.setDescription('In HH:MM:SS format')
|
146
|
+
end_time5.setDefaultValue('')
|
147
|
+
args << end_time5
|
148
|
+
|
149
|
+
|
150
|
+
# Use alternative default start and end time for different climate zone
|
85
151
|
alt_periods = OpenStudio::Measure::OSArgument.makeBoolArgument('alt_periods', true)
|
86
|
-
alt_periods.setDisplayName('
|
152
|
+
alt_periods.setDisplayName('Use alternative default start and end time based on the state of the model from the Cambium load profile peak period?')
|
153
|
+
alt_periods.setDescription('This will overwrite the start and end time and date provided by the user')
|
87
154
|
alt_periods.setDefaultValue(false)
|
88
155
|
args << alt_periods
|
89
156
|
|
@@ -101,259 +168,326 @@ class Precooling < OpenStudio::Measure::ModelMeasure
|
|
101
168
|
|
102
169
|
# assign the user inputs to variables
|
103
170
|
cooling_adjustment = runner.getDoubleArgumentValue('cooling_adjustment', user_arguments)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
171
|
+
start_time1 = runner.getStringArgumentValue('start_time1', user_arguments)
|
172
|
+
end_time1 = runner.getStringArgumentValue('end_time1', user_arguments)
|
173
|
+
start_time2 = runner.getStringArgumentValue('start_time2', user_arguments)
|
174
|
+
end_time2 = runner.getStringArgumentValue('end_time2', user_arguments)
|
175
|
+
start_time3 = runner.getStringArgumentValue('start_time3', user_arguments)
|
176
|
+
end_time3 = runner.getStringArgumentValue('end_time3', user_arguments)
|
177
|
+
start_time4 = runner.getStringArgumentValue('start_time4', user_arguments)
|
178
|
+
end_time4 = runner.getStringArgumentValue('end_time4', user_arguments)
|
179
|
+
start_time5 = runner.getStringArgumentValue('start_time5', user_arguments)
|
180
|
+
end_time5 = runner.getStringArgumentValue('end_time5', user_arguments)
|
181
|
+
start_date1 = runner.getStringArgumentValue('start_date1', user_arguments)
|
182
|
+
end_date1 = runner.getStringArgumentValue('end_date1', user_arguments)
|
183
|
+
start_date2 = runner.getStringArgumentValue('start_date2', user_arguments)
|
184
|
+
end_date2 = runner.getStringArgumentValue('end_date2', user_arguments)
|
185
|
+
start_date3 = runner.getStringArgumentValue('start_date3', user_arguments)
|
186
|
+
end_date3 = runner.getStringArgumentValue('end_date3', user_arguments)
|
187
|
+
start_date4 = runner.getStringArgumentValue('start_date4', user_arguments)
|
188
|
+
end_date4 = runner.getStringArgumentValue('end_date4', user_arguments)
|
189
|
+
start_date5 = runner.getStringArgumentValue('start_date5', user_arguments)
|
190
|
+
end_date5 = runner.getStringArgumentValue('end_date5', user_arguments)
|
111
191
|
alt_periods = runner.getBoolArgumentValue('alt_periods', user_arguments)
|
112
192
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
193
|
+
if alt_periods
|
194
|
+
state = model.getWeatherFile.stateProvinceRegion
|
195
|
+
if state == ''
|
196
|
+
runner.registerError('Unable to find state in model WeatherFile. The measure cannot be applied.')
|
197
|
+
return false
|
198
|
+
end
|
199
|
+
file = File.open(File.join(File.dirname(__FILE__), "../../../files/seasonal_shifting_take_hours.json"))
|
200
|
+
default_peak_periods = JSON.load(file)
|
201
|
+
file.close
|
202
|
+
unless default_peak_periods.key?state
|
203
|
+
runner.registerAsNotApplicable("No default inputs for the state of the WeatherFile #{state}")
|
204
|
+
return false
|
205
|
+
end
|
206
|
+
peak_periods = default_peak_periods[state]
|
207
|
+
start_time1 = peak_periods["winter_take_start"].split[1]
|
208
|
+
end_time1 = peak_periods["winter_take_end"].split[1]
|
209
|
+
start_time2 = peak_periods["intermediate_take_start"].split[1]
|
210
|
+
end_time2 = peak_periods["intermediate_take_end"].split[1]
|
211
|
+
start_time3 = peak_periods["summer_take_start"].split[1]
|
212
|
+
end_time3 = peak_periods["summer_take_end"].split[1]
|
213
|
+
start_time4 = peak_periods["intermediate_take_start"].split[1]
|
214
|
+
end_time4 = peak_periods["intermediate_take_end"].split[1]
|
215
|
+
start_time5 = peak_periods["winter_take_start"].split[1]
|
216
|
+
end_time5 = peak_periods["winter_take_end"].split[1]
|
217
|
+
start_date1 = '01-01'
|
218
|
+
end_date1 = '03-31'
|
219
|
+
start_date2 = '04-01'
|
220
|
+
end_date2 = '05-31'
|
221
|
+
start_date3 = '06-01'
|
222
|
+
end_date3 = '09-30'
|
223
|
+
start_date4 = '10-01'
|
224
|
+
end_date4 = '11-30'
|
225
|
+
start_date5 = '12-01'
|
226
|
+
end_date5 = '12-31'
|
123
227
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
cooling_end_day = md[2].to_i
|
130
|
-
else
|
131
|
-
runner.registerError('End date must be in MM-DD format.')
|
228
|
+
|
229
|
+
if cooling_adjustment > 0
|
230
|
+
runner.registerWarning('Raising cooling setpoint will not do pre-cooling.')
|
231
|
+
elsif cooling_adjustment.abs > 500
|
232
|
+
runner.registerError("Adjustment #{cooling_adjustment} is too large to be correct. Please double check your input")
|
132
233
|
return false
|
234
|
+
elsif cooling_adjustment.abs > 50
|
235
|
+
runner.registerWarning("Adjustment #{cooling_adjustment} is larger than typical setpoint adjustment. Please double check your input")
|
133
236
|
end
|
134
237
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
climateZones.climateZones.each do |climateZone|
|
149
|
-
if climateZone.institution == 'ASHRAE'
|
150
|
-
ashraeClimateZone = climateZone.value
|
151
|
-
runner.registerInfo("Using ASHRAE Climate zone #{ashraeClimateZone}.")
|
238
|
+
cooling_adjustment_si = cooling_adjustment * 5 / 9.0
|
239
|
+
|
240
|
+
def validate_time_format(star_time, end_time, runner)
|
241
|
+
time1 = /(\d\d):(\d\d):(\d\d)/.match(star_time)
|
242
|
+
time2 = /(\d\d):(\d\d):(\d\d)/.match(end_time)
|
243
|
+
if time1 and time2
|
244
|
+
os_starttime = OpenStudio::Time.new(star_time)
|
245
|
+
os_endtime = OpenStudio::Time.new(end_time)
|
246
|
+
if star_time >= end_time
|
247
|
+
runner.registerError('The start time needs to be earlier than the end time.')
|
248
|
+
return false
|
249
|
+
else
|
250
|
+
return os_starttime, os_endtime
|
152
251
|
end
|
252
|
+
else
|
253
|
+
runner.registerError('The provided time is not in HH-MM-SS format.')
|
254
|
+
return false
|
153
255
|
end
|
256
|
+
end
|
154
257
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
case ashraeClimateZone
|
162
|
-
when '3A','4A'
|
163
|
-
starttime_cooling = '14:01:00'
|
164
|
-
endtime_cooling = '17:59:00'
|
165
|
-
when '5A'
|
166
|
-
starttime_cooling = '10:01:00'
|
167
|
-
endtime_cooling = '13:59:00'
|
168
|
-
when '6A'
|
169
|
-
starttime_cooling = '9:01:00'
|
170
|
-
endtime_cooling = '12:59:00'
|
171
|
-
end
|
172
|
-
|
258
|
+
def validate_date_format(start_date1, end_date1, runner, model)
|
259
|
+
smd = /(\d\d)-(\d\d)/.match(start_date1)
|
260
|
+
emd = /(\d\d)-(\d\d)/.match(end_date1)
|
261
|
+
if smd.nil? or emd.nil?
|
262
|
+
runner.registerError('The provided date is not in MM-DD format.')
|
263
|
+
return false
|
173
264
|
else
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
when '5A'
|
188
|
-
starttime_cooling = '16:01:00'
|
189
|
-
endtime_cooling = '19:59:00'
|
190
|
-
when '6A', '7A'
|
191
|
-
starttime_cooling = '12:01:00'
|
192
|
-
endtime_cooling = '15:59:00'
|
265
|
+
start_month = smd[1].to_i
|
266
|
+
start_day = smd[2].to_i
|
267
|
+
end_month = emd[1].to_i
|
268
|
+
end_day = emd[2].to_i
|
269
|
+
if start_date1 > end_date1
|
270
|
+
runner.registerError('The start date cannot be later date the end time.')
|
271
|
+
return false
|
272
|
+
else
|
273
|
+
# os_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month), start_day)
|
274
|
+
# os_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month), end_day)
|
275
|
+
os_start_date = model.getYearDescription.makeDate(start_month, start_day)
|
276
|
+
os_end_date = model.getYearDescription.makeDate(end_month, end_day)
|
277
|
+
return os_start_date, os_end_date
|
193
278
|
end
|
194
|
-
|
195
279
|
end
|
196
280
|
end
|
197
281
|
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
# shift_time5 = OpenStudio::Time.new(endtime_heating)
|
203
|
-
|
204
|
-
if /(\d\d):(\d\d):(\d\d)/.match(starttime_cooling)
|
205
|
-
shift_time1 = OpenStudio::Time.new(starttime_cooling)
|
282
|
+
# First time period
|
283
|
+
time_result1 = validate_time_format(start_time1, end_time1, runner)
|
284
|
+
if time_result1
|
285
|
+
shift_time_start1, shift_time_end1 = time_result1
|
206
286
|
else
|
207
|
-
runner.registerError('
|
287
|
+
runner.registerError('The required time period for the reduction is not in correct format!')
|
208
288
|
return false
|
209
289
|
end
|
290
|
+
# The other optional time periods
|
291
|
+
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
|
292
|
+
if (not start_time2.empty?) and (not end_time2.empty?)
|
293
|
+
time_result2 = validate_time_format(start_time2, end_time2, runner)
|
294
|
+
if time_result2
|
295
|
+
shift_time_start2, shift_time_end2 = time_result2
|
296
|
+
end
|
297
|
+
end
|
298
|
+
if (not start_time3.empty?) and (not end_time3.empty?)
|
299
|
+
time_result3 = validate_time_format(start_time3, end_time3, runner)
|
300
|
+
if time_result3
|
301
|
+
shift_time_start3, shift_time_end3 = time_result3
|
302
|
+
end
|
303
|
+
end
|
304
|
+
if (not start_time4.empty?) and (not end_time4.empty?)
|
305
|
+
time_result4 = validate_time_format(start_time4, end_time4, runner)
|
306
|
+
if time_result4
|
307
|
+
shift_time_start4, shift_time_end4 = time_result4
|
308
|
+
end
|
309
|
+
end
|
310
|
+
if (not start_time5.empty?) and (not end_time5.empty?)
|
311
|
+
time_result5 = validate_time_format(start_time5, end_time5, runner)
|
312
|
+
if time_result5
|
313
|
+
shift_time_start5, shift_time_end5 = time_result5
|
314
|
+
end
|
315
|
+
end
|
210
316
|
|
211
|
-
|
212
|
-
|
317
|
+
# First date period
|
318
|
+
date_result1 = validate_date_format(start_date1, end_date1, runner, model)
|
319
|
+
if date_result1
|
320
|
+
os_start_date1, os_end_date1 = date_result1
|
213
321
|
else
|
214
|
-
runner.registerError('
|
322
|
+
runner.registerError('The required date period for the reduction is not in correct format!')
|
215
323
|
return false
|
216
324
|
end
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
325
|
+
# Other optional date period
|
326
|
+
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
|
327
|
+
if (not start_date2.empty?) and (not end_date2.empty?)
|
328
|
+
date_result2 = validate_date_format(start_date2, end_date2, runner, model)
|
329
|
+
if date_result2
|
330
|
+
os_start_date2, os_end_date2 = date_result2
|
331
|
+
end
|
221
332
|
end
|
222
333
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
# if model.getObjectsByType('OS:RunPeriodControl:DaylightSavingTime'.to_IddObjectType).size >= 1
|
229
|
-
# runperiodctrl_daylgtsaving = model.getRunPeriodControlDaylightSavingTime
|
230
|
-
# daylight_saving_startdate = runperiodctrl_daylgtsaving.startDate
|
231
|
-
# daylight_saving_enddate = runperiodctrl_daylgtsaving.endDate
|
232
|
-
# if summerStartDate >= OpenStudio::Date.new(daylight_saving_startdate.monthOfYear, daylight_saving_startdate.dayOfMonth, summerStartDate.year) && summerEndDate <= OpenStudio::Date.new(daylight_saving_enddate.monthOfYear, daylight_saving_enddate.dayOfMonth, summerStartDate.year)
|
233
|
-
# shift_time1 += OpenStudio::Time.new(0,1,0,0)
|
234
|
-
# shift_time2 += OpenStudio::Time.new(0,1,0,0)
|
235
|
-
# end
|
236
|
-
# end
|
237
|
-
|
238
|
-
puts "shift_time1: #{shift_time1}"
|
239
|
-
puts "shift_time2: #{shift_time2}"
|
240
|
-
|
241
|
-
|
242
|
-
# ruby test to see if first charter of string is uppercase letter
|
243
|
-
if cooling_adjustment > 0
|
244
|
-
runner.registerWarning('Raising cooling setpoint will not do pre-cooling.')
|
245
|
-
elsif cooling_adjustment.abs > 500
|
246
|
-
runner.registerError("Adjustment #{cooling_adjustment} is too large to be correct. Please double check your input")
|
247
|
-
return false
|
248
|
-
elsif cooling_adjustment.abs > 50
|
249
|
-
runner.registerWarning("Adjustment #{cooling_adjustment} is larger than typical setpoint adjustment. Please double check your input")
|
334
|
+
if (not start_date3.empty?) and (not end_date3.empty?)
|
335
|
+
date_result3 = validate_date_format(start_date3, end_date3, runner, model)
|
336
|
+
if date_result3
|
337
|
+
os_start_date3, os_end_date3 = date_result3
|
338
|
+
end
|
250
339
|
end
|
251
340
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
cooling_adjustment_ip = OpenStudio::Quantity.new(cooling_adjustment, temperature_ip_unit)
|
257
|
-
cooling_adjustment_si = cooling_adjustment*5/9
|
258
|
-
|
259
|
-
# update the availability schedule
|
260
|
-
air_loop_avail_schs = {}
|
261
|
-
air_loops = model.getAirLoopHVACs
|
262
|
-
air_loops.each do |air_loop|
|
263
|
-
avail_sch = air_loop.availabilitySchedule
|
264
|
-
|
265
|
-
if air_loop_avail_schs.key?(avail_sch.name.to_s)
|
266
|
-
new_avail_sch = air_loop_avail_schs[avail_sch.name.to_s]
|
267
|
-
else
|
268
|
-
new_avail_sch = avail_sch.clone(model).to_Schedule.get
|
269
|
-
new_avail_sch.setName("#{avail_sch.name.to_s} adjusted")
|
270
|
-
# add to the hash
|
271
|
-
air_loop_avail_schs[avail_sch.name.to_s] = new_avail_sch
|
341
|
+
if (not start_date4.empty?) and (not end_date4.empty?)
|
342
|
+
date_result4 = validate_date_format(start_date4, end_date4, runner, model)
|
343
|
+
if date_result4
|
344
|
+
os_start_date4, os_end_date4 = date_result4
|
272
345
|
end
|
273
|
-
air_loop.setAvailabilitySchedule(new_avail_sch)
|
274
|
-
|
275
346
|
end
|
276
347
|
|
277
|
-
|
278
|
-
|
279
|
-
if
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
schedule = air_loop_sch.to_ScheduleRuleset.get
|
284
|
-
default_rule = schedule.defaultDaySchedule
|
285
|
-
rules = schedule.scheduleRules
|
286
|
-
days_covered = Array.new(7, false)
|
287
|
-
|
288
|
-
rules.each do |rule|
|
289
|
-
winter_avail_rule1 = copy_sch_rule_for_period(model, rule, rule.daySchedule, winterStartDate1, winterEndDate1)
|
290
|
-
winter_avail_rule2 = copy_sch_rule_for_period(model, rule, rule.daySchedule, winterStartDate2, winterEndDate2)
|
291
|
-
runner.registerInfo(" ------------ time: #{rule.daySchedule.times.map {|os_time| os_time.toString}}")
|
292
|
-
runner.registerInfo(" ------------ values: #{rule.daySchedule.values}")
|
293
|
-
summer_avail_rule = rule.clone(model).to_ScheduleRule.get
|
294
|
-
summer_avail_rule.setStartDate(summerStartDate)
|
295
|
-
summer_avail_rule.setEndDate(summerEndDate)
|
296
|
-
|
297
|
-
checkDaysCovered(summer_avail_rule, days_covered)
|
298
|
-
|
299
|
-
summer_avail_day = summer_avail_rule.daySchedule
|
300
|
-
day_time_vector = summer_avail_day.times
|
301
|
-
day_value_vector = summer_avail_day.values
|
302
|
-
summer_avail_day.clearValues
|
303
|
-
|
304
|
-
summer_avail_day = updateAvailDaySchedule(summer_avail_day, day_time_vector, day_value_vector, shift_time1, shift_time2)
|
305
|
-
runner.registerInfo(" ------------ updated time: #{summer_avail_day.times.map {|os_time| os_time.toString}}")
|
306
|
-
runner.registerInfo(" ------------ uodated values: #{summer_avail_day.values}")
|
307
|
-
|
308
|
-
end
|
309
|
-
|
310
|
-
if days_covered.include?(false)
|
311
|
-
|
312
|
-
winter_rule1 = create_sch_rule_from_default(model, schedule, default_rule, winterStartDate1, winterEndDate1)
|
313
|
-
winter_rule2 = create_sch_rule_from_default(model, schedule, default_rule, winterStartDate2, winterEndDate2)
|
314
|
-
|
315
|
-
coverMissingDays(winter_rule1, days_covered)
|
316
|
-
checkDaysCovered(winter_rule1, days_covered)
|
348
|
+
if (not start_date5.empty?) and (not end_date5.empty?)
|
349
|
+
date_result5 = validate_date_format(start_date5, end_date5, runner, model)
|
350
|
+
if date_result5
|
351
|
+
os_start_date5, os_end_date5 = date_result5
|
352
|
+
end
|
353
|
+
end
|
317
354
|
|
318
|
-
summer_rule = copy_sch_rule_for_period(model, winter_rule1, default_rule, summerStartDate, summerEndDate)
|
319
355
|
|
320
|
-
summer_day = summer_rule.daySchedule
|
321
|
-
day_time_vector = summer_day.times
|
322
|
-
day_value_vector = summer_day.values
|
323
|
-
summer_day.clearValues
|
324
356
|
|
325
|
-
|
357
|
+
# update the availability schedule
|
358
|
+
# air_loop_avail_schs = {}
|
359
|
+
# air_loops = model.getAirLoopHVACs
|
360
|
+
# air_loops.each do |air_loop|
|
361
|
+
# avail_sch = air_loop.availabilitySchedule
|
362
|
+
#
|
363
|
+
# if air_loop_avail_schs.key?(avail_sch.name.to_s)
|
364
|
+
# new_avail_sch = air_loop_avail_schs[avail_sch.name.to_s]
|
365
|
+
# else
|
366
|
+
# new_avail_sch = avail_sch.clone(model).to_Schedule.get
|
367
|
+
# new_avail_sch.setName("#{avail_sch.name.to_s} adjusted")
|
368
|
+
# # add to the hash
|
369
|
+
# air_loop_avail_schs[avail_sch.name.to_s] = new_avail_sch
|
370
|
+
# end
|
371
|
+
# air_loop.setAvailabilitySchedule(new_avail_sch)
|
372
|
+
#
|
373
|
+
# end
|
326
374
|
|
327
|
-
|
328
|
-
|
329
|
-
|
375
|
+
# air_loop_avail_schs.each do |sch_name, air_loop_sch|
|
376
|
+
# runner.registerInfo("Air Loop Schedule #{sch_name}:")
|
377
|
+
# if air_loop_sch.to_ScheduleRuleset.empty?
|
378
|
+
# runner.registerWarning("Schedule #{sch_name} isn't a ScheduleRuleset object and won't be altered by this measure.")
|
379
|
+
# air_loop_sch.remove # remove un-used clone
|
380
|
+
# else
|
381
|
+
# schedule = air_loop_sch.to_ScheduleRuleset.get
|
382
|
+
# default_rule = schedule.defaultDaySchedule
|
383
|
+
# rules = schedule.scheduleRules
|
384
|
+
# days_covered = Array.new(7, false)
|
385
|
+
#
|
386
|
+
# rules.each do |rule|
|
387
|
+
# winter_avail_rule1 = copy_sch_rule_for_period(model, rule, rule.daySchedule, winterStartDate1, winterEndDate1)
|
388
|
+
# winter_avail_rule2 = copy_sch_rule_for_period(model, rule, rule.daySchedule, winterStartDate2, winterEndDate2)
|
389
|
+
# runner.registerInfo(" ---- time: #{rule.daySchedule.times.map {|os_time| os_time.toString}}")
|
390
|
+
# runner.registerInfo(" ---- values: #{rule.daySchedule.values}")
|
391
|
+
# summer_avail_rule = rule.clone(model).to_ScheduleRule.get
|
392
|
+
# summer_avail_rule.setStartDate(summerStartDate)
|
393
|
+
# summer_avail_rule.setEndDate(summerEndDate)
|
394
|
+
#
|
395
|
+
# checkDaysCovered(summer_avail_rule, days_covered)
|
396
|
+
#
|
397
|
+
# summer_avail_day = summer_avail_rule.daySchedule
|
398
|
+
# day_time_vector = summer_avail_day.times
|
399
|
+
# day_value_vector = summer_avail_day.values
|
400
|
+
# summer_avail_day.clearValues
|
401
|
+
#
|
402
|
+
# summer_avail_day = updateAvailDaySchedule(summer_avail_day, day_time_vector, day_value_vector, shift_time1, shift_time2)
|
403
|
+
# runner.registerInfo(" ---- updated time: #{summer_avail_day.times.map {|os_time| os_time.toString}}")
|
404
|
+
# runner.registerInfo(" ---- updated values: #{summer_avail_day.values}")
|
405
|
+
#
|
406
|
+
# end
|
407
|
+
#
|
408
|
+
# if days_covered.include?(false)
|
409
|
+
#
|
410
|
+
# winter_rule1 = create_sch_rule_from_default(model, schedule, default_rule, winterStartDate1, winterEndDate1)
|
411
|
+
# winter_rule2 = create_sch_rule_from_default(model, schedule, default_rule, winterStartDate2, winterEndDate2)
|
412
|
+
#
|
413
|
+
# coverMissingDays(winter_rule1, days_covered)
|
414
|
+
# checkDaysCovered(winter_rule1, days_covered)
|
415
|
+
#
|
416
|
+
# summer_rule = copy_sch_rule_for_period(model, winter_rule1, default_rule, summerStartDate, summerEndDate)
|
417
|
+
#
|
418
|
+
# summer_day = summer_rule.daySchedule
|
419
|
+
# day_time_vector = summer_day.times
|
420
|
+
# day_value_vector = summer_day.values
|
421
|
+
# summer_day.clearValues
|
422
|
+
#
|
423
|
+
# summer_day = updateAvailDaySchedule(summer_day, day_time_vector, day_value_vector, shift_time1, shift_time2)
|
424
|
+
#
|
425
|
+
# end
|
426
|
+
# end
|
427
|
+
# end
|
330
428
|
|
331
429
|
|
332
|
-
applicable = false
|
333
430
|
# push schedules to hash to avoid making unnecessary duplicates
|
334
|
-
|
335
|
-
#
|
431
|
+
# one cooling schedule set can correspond to multiple different heating schedule set
|
432
|
+
# each unique pair would be cloned into a new cooling schedule set because of the potential deadband conflict
|
433
|
+
sch_set_mapping = {}
|
434
|
+
# sch_set_mapping = {old_cool_sch_name: {corresponding_heat_sch_handle1: [cloned_cool_sch1, corresponding_heat_sch1],
|
435
|
+
# corresponding_heat_sch_handle2: [cloned_cool_sch2, corresponding_heat_sch2]} }
|
436
|
+
|
336
437
|
thermostats = model.getThermostatSetpointDualSetpoints
|
337
438
|
thermostats.each do |thermostat|
|
338
439
|
# setup new cooling setpoint schedule
|
339
440
|
clg_set_sch = thermostat.coolingSetpointTemperatureSchedule
|
441
|
+
htg_set_sch = thermostat.heatingSetpointTemperatureSchedule
|
340
442
|
if clg_set_sch.empty?
|
341
|
-
runner.registerWarning("Thermostat '#{thermostat.name}' doesn't have a cooling setpoint schedule")
|
443
|
+
runner.registerWarning("Thermostat '#{thermostat.name.to_s}' doesn't have a cooling setpoint schedule")
|
342
444
|
else
|
343
|
-
|
344
|
-
|
345
|
-
|
445
|
+
old_clg_schedule = clg_set_sch.get
|
446
|
+
old_schedule_name = old_clg_schedule.name.to_s
|
447
|
+
if old_clg_schedule.to_ScheduleRuleset.is_initialized
|
448
|
+
# clone if not already in hash
|
449
|
+
if sch_set_mapping.key?(old_schedule_name)
|
450
|
+
if htg_set_sch.empty? || htg_set_sch.get.to_ScheduleRuleset.empty?
|
451
|
+
if sch_set_mapping[old_schedule_name].key?"nil"
|
452
|
+
new_clg_set_sch = sch_set_mapping[old_schedule_name]["nil"][0]
|
453
|
+
else
|
454
|
+
new_clg_set_sch = old_clg_schedule.clone(model).to_Schedule.get
|
455
|
+
n_new_cool_sch = sch_set_mapping[old_schedule_name].size
|
456
|
+
new_clg_set_sch.setName("#{old_schedule_name} adjusted by #{cooling_adjustment}F_#{n_new_cool_sch}")
|
457
|
+
sch_set_mapping[old_schedule_name]["nil"] = [new_clg_set_sch, nil]
|
458
|
+
end
|
459
|
+
else
|
460
|
+
htg_sch_handle = htg_set_sch.get.handle.to_s
|
461
|
+
if sch_set_mapping[old_schedule_name].key?htg_sch_handle
|
462
|
+
new_clg_set_sch = sch_set_mapping[old_schedule_name][htg_sch_handle][0]
|
463
|
+
else
|
464
|
+
new_clg_set_sch = old_clg_schedule.clone(model).to_Schedule.get
|
465
|
+
n_new_cool_sch = sch_set_mapping[old_schedule_name].size
|
466
|
+
new_clg_set_sch.setName("#{old_schedule_name} adjusted by #{cooling_adjustment}F_#{n_new_cool_sch}")
|
467
|
+
sch_set_mapping[old_schedule_name][htg_sch_handle] = [new_clg_set_sch, htg_set_sch.get]
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
else
|
472
|
+
new_clg_set_sch = old_clg_schedule.clone(model).to_Schedule.get
|
473
|
+
new_clg_set_sch.setName("#{old_schedule_name} adjusted by #{cooling_adjustment}F")
|
474
|
+
if htg_set_sch.is_initialized
|
475
|
+
sch_set_mapping[old_schedule_name] = {htg_set_sch.get.handle.to_s => [new_clg_set_sch, htg_set_sch.get]}
|
476
|
+
else
|
477
|
+
sch_set_mapping[old_schedule_name] = {"nil" => [new_clg_set_sch, nil]}
|
478
|
+
end
|
479
|
+
end
|
480
|
+
# hook up clone to thermostat
|
481
|
+
thermostat.setCoolingSetpointTemperatureSchedule(new_clg_set_sch)
|
346
482
|
else
|
347
|
-
|
348
|
-
new_clg_set_sch.setName("#{clg_set_sch.get.name.to_s} adjusted by #{cooling_adjustment}")
|
349
|
-
clg_set_schs[clg_set_sch.get.name.to_s] = new_clg_set_sch
|
483
|
+
runner.registerWarning("Schedule '#{old_schedule_name}' isn't a ScheduleRuleset object and won't be altered by this measure.")
|
350
484
|
end
|
351
|
-
# hook up clone to thermostat
|
352
|
-
thermostat.setCoolingSetpointTemperatureSchedule(new_clg_set_sch)
|
353
485
|
end
|
354
|
-
|
355
486
|
end
|
356
487
|
|
488
|
+
sch_set_mapping.each do |old_sch_name, new_sch_hash|
|
489
|
+
runner.registerInfo("The original cooling schedule ruleset #{old_sch_name} is paired with #{new_sch_hash.size} unique heating schedules, so it will be cloned as #{new_sch_hash.size} new schedules")
|
490
|
+
end
|
357
491
|
|
358
492
|
# consider issuing a warning if the model has un-conditioned thermal zones (no ideal air loads or hvac)
|
359
493
|
zones = model.getThermalZones
|
@@ -364,64 +498,320 @@ class Precooling < OpenStudio::Measure::ModelMeasure
|
|
364
498
|
end
|
365
499
|
end
|
366
500
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
501
|
+
cooling_adjust_period_inputs = { "period1" => {"date_start"=>os_start_date1, "date_end"=>os_end_date1,
|
502
|
+
"time_start"=>shift_time_start1, "time_end"=>shift_time_end1},
|
503
|
+
"period2" => {"date_start"=>os_start_date2, "date_end"=>os_end_date2,
|
504
|
+
"time_start"=>shift_time_start2, "time_end"=>shift_time_end2},
|
505
|
+
"period3" => {"date_start"=>os_start_date3, "date_end"=>os_end_date3,
|
506
|
+
"time_start"=>shift_time_start3, "time_end"=>shift_time_end3},
|
507
|
+
"period4" => {"date_start"=>os_start_date4, "date_end"=>os_end_date4,
|
508
|
+
"time_start"=>shift_time_start4, "time_end"=>shift_time_end4},
|
509
|
+
"period5" => {"date_start"=>os_start_date5, "date_end"=>os_end_date5,
|
510
|
+
"time_start"=>shift_time_start5, "time_end"=>shift_time_end5} }
|
511
|
+
applicable = false
|
512
|
+
# make cooling schedule adjustments and rename
|
513
|
+
sch_set_mapping.each do |old_sch_name, new_sch_hash|
|
514
|
+
new_sch_hash.each do |paired_heat_sch_handle, os_schs|
|
515
|
+
schedule = os_schs[0].to_ScheduleRuleset.get
|
516
|
+
if os_schs[1].nil?
|
517
|
+
heating_set = nil
|
518
|
+
else
|
519
|
+
heating_set = os_schs[1].to_ScheduleRuleset.get
|
520
|
+
end
|
375
521
|
rules = schedule.scheduleRules
|
376
522
|
days_covered = Array.new(7, false)
|
377
|
-
|
378
|
-
|
523
|
+
current_index = 0
|
524
|
+
if rules.length <= 0
|
525
|
+
runner.registerWarning("Cooling setpoint schedule '#{old_sch_name}' is a ScheduleRuleSet, but has no ScheduleRules associated. It won't be altered by this measure.")
|
526
|
+
else
|
527
|
+
runner.registerInfo("Cooling schedule ruleset #{schedule.name.to_s} cloned from #{old_sch_name} has #{rules.length} rules.")
|
379
528
|
rules.each do |rule|
|
380
|
-
runner.registerInfo("
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
529
|
+
runner.registerInfo("---- Rule No.#{rule.ruleIndex}: #{rule.name.to_s}")
|
530
|
+
if rule.dateSpecificationType == "SpecificDates"
|
531
|
+
## if the rule applies to SpecificDates, collect the dates that fall into each adjustment date period,
|
532
|
+
## and create a new rule for each date period with covered specific dates
|
533
|
+
runner.registerInfo("======= The rule #{rule.name.to_s} only covers specific dates.")
|
534
|
+
## the specificDates cannot be modified in place because it's a frozen array
|
535
|
+
all_specific_dates = []
|
536
|
+
rule.specificDates.each { |date| all_specific_dates << date }
|
537
|
+
cooling_adjust_period_inputs.each do |period, period_inputs|
|
538
|
+
period_inputs["specific_dates"] = []
|
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
|
+
# active_heating_rule = heating_set.getDaySchedules(os_start_date, os_end_date)
|
545
|
+
# runner.registerInfo("! #{heating_set.name.to_s} in between: #{active_heating_rule}")
|
546
|
+
rule.specificDates.each do |covered_date|
|
547
|
+
if covered_date >= os_start_date and covered_date <= os_end_date
|
548
|
+
period_inputs["specific_dates"] << covered_date
|
549
|
+
all_specific_dates.delete(covered_date)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
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)}")
|
553
|
+
runner.registerInfo("!!! Specific dates haven't been covered: #{all_specific_dates.map(&:to_s)}")
|
554
|
+
next if period_inputs["specific_dates"].empty?
|
555
|
+
## If there's no corresponding heating set, no need to compare the cooling & heating setpoint to avoid deadband issue
|
556
|
+
if heating_set.nil?
|
557
|
+
runner.registerInfo("---- Before, cooling schedule: #{rule.daySchedule.times.map(&:to_s)}, #{rule.daySchedule.values}")
|
558
|
+
runner.registerInfo(" NO PAIRED HEATING SCHEDULE!")
|
559
|
+
rule_period = modify_rule_for_specific_dates(rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
560
|
+
cooling_adjustment_si, period_inputs["specific_dates"], compared_day_sch=nil)
|
561
|
+
runner.registerInfo("---- After, adjusted schedule: #{rule_period.daySchedule.times.map(&:to_s)}, #{rule_period.daySchedule.values}")
|
562
|
+
if rule_period
|
563
|
+
applicable = true
|
564
|
+
schedule.setScheduleRuleIndex(rule_period, current_index)
|
565
|
+
current_index += 1
|
566
|
+
runner.registerInfo("-------- The rule #{rule_period.name.to_s} for #{rule_period.dateSpecificationType} is added as priority #{current_index}")
|
567
|
+
end
|
568
|
+
else
|
569
|
+
heat_day_mapping = {}
|
570
|
+
period_inputs["specific_dates"].each do |date|
|
571
|
+
corresponding_heat_day = heating_set.getDaySchedules(date, date)[0]
|
572
|
+
if heat_day_mapping.key?(corresponding_heat_day.name.to_s)
|
573
|
+
heat_day_mapping[corresponding_heat_day.name.to_s]["dates"] << date
|
574
|
+
else
|
575
|
+
heat_day_mapping[corresponding_heat_day.name.to_s] = {"heat_day_schedule" => corresponding_heat_day, "dates" => [date]}
|
576
|
+
end
|
577
|
+
end
|
578
|
+
runner.registerInfo("#{period} has #{heat_day_mapping.size} heating setpoint days. ")
|
579
|
+
heat_day_mapping.each do |schedule_day_name, day_info|
|
580
|
+
runner.registerInfo("Heating sch #{schedule_day_name} applies to #{day_info["dates"].size} days")
|
581
|
+
heat_day = day_info["heat_day_schedule"]
|
582
|
+
runner.registerInfo("---- Before, heating schedule: #{heat_day.times.map(&:to_s)}, #{heat_day.values}")
|
583
|
+
runner.registerInfo(" cooling schedule: #{rule.daySchedule.times.map(&:to_s)}, #{rule.daySchedule.values}")
|
584
|
+
rule_period = modify_rule_for_specific_dates(rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
585
|
+
cooling_adjustment_si, day_info["dates"], compared_day_sch=heat_day)
|
586
|
+
runner.registerInfo("---- After, adjusted schedule: #{rule_period.daySchedule.times.map(&:to_s)}, #{rule_period.daySchedule.values}")
|
587
|
+
if rule_period
|
588
|
+
applicable = true
|
589
|
+
schedule.setScheduleRuleIndex(rule_period, current_index)
|
590
|
+
current_index += 1
|
591
|
+
runner.registerInfo("-------- The rule #{rule_period.name.to_s} for #{rule_period.dateSpecificationType} is added as priority #{current_index}")
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
end
|
597
|
+
end
|
598
|
+
if all_specific_dates.empty?
|
599
|
+
## if all specific dates have been covered by new rules for each adjustment date period, remove the original rule
|
600
|
+
runner.registerInfo("The original rule is removed since no specific date left")
|
601
|
+
else
|
602
|
+
## if there's still dates left to be covered, modify the original rule to only cover these dates
|
603
|
+
## (this is just in case that the rule order was not set correctly, and the original rule is still applied to all specific dates;
|
604
|
+
## also to make the logic in OSM more clearer)
|
605
|
+
## the specificDates cannot be modified in place, so create a new rule with the left dates to replace the original rule
|
606
|
+
original_rule_update = clone_rule_with_new_dayschedule(rule, rule.name.to_s + " - dates left")
|
607
|
+
schedule.setScheduleRuleIndex(original_rule_update, current_index)
|
608
|
+
current_index += 1
|
609
|
+
all_specific_dates.each do |date|
|
610
|
+
original_rule_update.addSpecificDate(date)
|
611
|
+
end
|
612
|
+
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)}")
|
613
|
+
runner.registerInfo("-------- and is shifted to priority #{current_index}")
|
614
|
+
end
|
615
|
+
rule.remove
|
616
|
+
|
617
|
+
|
618
|
+
else
|
619
|
+
## If the rule applies to a DateRange, check if the DateRange overlaps with each adjustment date period
|
620
|
+
## if so, create a new rule for that adjustment date period
|
621
|
+
runner.registerInfo("******* The rule #{rule.name.to_s} covers date range #{rule.startDate.get} - #{rule.endDate.get}.")
|
622
|
+
cooling_adjust_period_inputs.each do |period, period_inputs|
|
623
|
+
os_start_date = period_inputs["date_start"]
|
624
|
+
os_end_date = period_inputs["date_end"]
|
625
|
+
shift_time_start = period_inputs["time_start"]
|
626
|
+
shift_time_end = period_inputs["time_end"]
|
627
|
+
if [os_start_date, os_end_date, shift_time_start, shift_time_end].all?
|
628
|
+
## check if the original rule applied DateRange overlaps with the adjustment date period
|
629
|
+
overlapped, new_start_dates, new_end_dates = check_date_ranges_overlap(rule, os_start_date, os_end_date)
|
630
|
+
next unless overlapped
|
631
|
+
#############################################################
|
632
|
+
## If there's no corresponding heating set, no need to compare the cooling & heating setpoint to avoid deadband issue
|
633
|
+
if heating_set.nil?
|
634
|
+
runner.registerInfo("---- Before, cooling schedule: #{rule.daySchedule.times.map(&:to_s)}, #{rule.daySchedule.values}")
|
635
|
+
runner.registerInfo(" NO PAIRED HEATING SCHEDULE!")
|
636
|
+
new_start_dates.each_with_index do |start_date, i|
|
637
|
+
rule_period = modify_rule_for_date_period(rule, start_date, new_end_dates[i], shift_time_start, shift_time_end,
|
638
|
+
cooling_adjustment_si, applied_dow=nil, compared_day_sch=nil)
|
639
|
+
runner.registerInfo("A new rule is cloned from the original cooling rule:")
|
640
|
+
runner.registerInfo("#{rule_period.daySchedule.times.map(&:to_s)}, #{rule_period.daySchedule.values}")
|
641
|
+
if rule_period
|
642
|
+
applicable = true
|
643
|
+
if period == "period1"
|
644
|
+
checkDaysCovered(rule_period, days_covered)
|
645
|
+
end
|
646
|
+
schedule.setScheduleRuleIndex(rule_period, current_index)
|
647
|
+
current_index += 1
|
648
|
+
runner.registerInfo("-------- The rule #{rule_period.name.to_s} is added as priority #{current_index}")
|
649
|
+
end
|
650
|
+
end
|
651
|
+
else
|
652
|
+
cool_sch_applied_dates = get_applied_dates_in_range(os_start_date, os_end_date, rule)
|
653
|
+
heat_day_mapping = {}
|
654
|
+
day_of_week_mapping = {}
|
655
|
+
cool_sch_applied_dates.each do |date|
|
656
|
+
corresponding_heat_day = heating_set.getDaySchedules(date, date)[0]
|
657
|
+
if heat_day_mapping.key?(corresponding_heat_day.name.to_s)
|
658
|
+
heat_day_mapping[corresponding_heat_day.name.to_s]["dates"] << date
|
659
|
+
else
|
660
|
+
heat_day_mapping[corresponding_heat_day.name.to_s] = {"heat_day_schedule" => corresponding_heat_day, "dates" => [date]}
|
661
|
+
end
|
662
|
+
if day_of_week_mapping.key?date.dayOfWeek.valueName
|
663
|
+
day_of_week_mapping[date.dayOfWeek.valueName] << date
|
664
|
+
else
|
665
|
+
day_of_week_mapping[date.dayOfWeek.valueName] = [date]
|
666
|
+
end
|
667
|
+
end
|
668
|
+
runner.registerInfo("#{period} has #{heat_day_mapping.size} heating setpoint days.")
|
669
|
+
heat_day_mapping.each do |schedule_day_name, day_info|
|
670
|
+
runner.registerInfo("Heating sch #{schedule_day_name} applies to #{day_info["dates"].size} days")
|
671
|
+
if heat_day_mapping.size == 1
|
672
|
+
## If there's only one corresponding heating scheduleday, no need to clone multiple cooling schedules
|
673
|
+
applied_day_of_week = nil
|
674
|
+
heat_sch_dates = []
|
675
|
+
else
|
676
|
+
heat_sch_dates = day_info["dates"].map(&:to_s)
|
677
|
+
applied_day_of_week = []
|
678
|
+
day_of_week_mapping.each do |day_of_week, dates|
|
679
|
+
week_day_dates = dates.map(&:to_s)
|
680
|
+
if (week_day_dates - heat_sch_dates).empty?
|
681
|
+
applied_day_of_week << day_of_week
|
682
|
+
heat_sch_dates -= week_day_dates
|
683
|
+
runner.registerInfo("Heating sch #{schedule_day_name} applies to #{day_of_week}")
|
684
|
+
if heat_sch_dates.empty?
|
685
|
+
break
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
heat_day = day_info["heat_day_schedule"]
|
692
|
+
runner.registerInfo("---- Before, heating schedule: #{heat_day.times.map(&:to_s)}, #{heat_day.values}")
|
693
|
+
runner.registerInfo(" cooling schedule: #{rule.daySchedule.times.map(&:to_s)}, #{rule.daySchedule.values}")
|
694
|
+
new_start_dates.each_with_index do |start_date, i|
|
695
|
+
## If applied_day_of_week is nil (only one cooling scheduleday), or the applied_day_of_week is not empty
|
696
|
+
unless applied_day_of_week&.empty?
|
697
|
+
rule_period = modify_rule_for_date_period(rule, start_date, new_end_dates[i], shift_time_start, shift_time_end, cooling_adjustment_si,
|
698
|
+
applied_dow=applied_day_of_week, compared_day_sch=heat_day)
|
699
|
+
runner.registerInfo("A new rule is created for #{applied_day_of_week}:")
|
700
|
+
runner.registerInfo("#{rule_period.daySchedule.times.map(&:to_s)}, #{rule_period.daySchedule.values}")
|
701
|
+
if rule_period
|
702
|
+
applicable = true
|
703
|
+
if period == "period1"
|
704
|
+
checkDaysCovered(rule_period, days_covered)
|
705
|
+
end
|
706
|
+
schedule.setScheduleRuleIndex(rule_period, current_index)
|
707
|
+
current_index += 1
|
708
|
+
runner.registerInfo("-------- The rule #{rule_period.name.to_s} is added as priority #{current_index}")
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
unless heat_sch_dates.empty?
|
713
|
+
left_os_dates = heat_sch_dates.map {|str_date| OpenStudio::Date.new(str_date)}
|
714
|
+
runner.registerInfo("Heating sch #{schedule_day_name} still covers other days. These days will be added as a rule for specific dates.")
|
715
|
+
rule_for_left_dates = modify_rule_for_specific_dates(rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
716
|
+
cooling_adjustment_si, left_os_dates, compared_day_sch=heat_day)
|
717
|
+
schedule.setScheduleRuleIndex(rule_for_left_dates, current_index)
|
718
|
+
current_index += 1
|
719
|
+
runner.registerInfo("-------- The rule #{rule_for_left_dates.name.to_s} is added as priority #{current_index}")
|
720
|
+
end
|
721
|
+
end
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
end
|
726
|
+
end
|
727
|
+
## The original rule will be shifted to the currently lowest priority
|
728
|
+
## Setting the rule to an existing index will automatically push all other rules after it down
|
729
|
+
schedule.setScheduleRuleIndex(rule, current_index)
|
730
|
+
runner.registerInfo("-------- The original rule #{rule.name.to_s} is shifted to priority #{current_index}")
|
731
|
+
current_index += 1
|
394
732
|
end
|
395
|
-
summer_day.clearValues
|
396
|
-
|
397
|
-
summer_day = updateDaySchedule(summer_day, day_time_vector, day_value_vector, shift_time1, shift_time2, cooling_adjustment_si)
|
398
733
|
|
399
734
|
end
|
400
|
-
else
|
401
|
-
runner.registerWarning("Cooling setpoint schedule #{sch_name} is a ScheduleRuleSet, but has no ScheduleRules associated. It won't be altered by this measure.")
|
402
735
|
end
|
403
736
|
|
737
|
+
default_day = schedule.defaultDaySchedule
|
404
738
|
if days_covered.include?(false)
|
739
|
+
runner.registerInfo("Some days use default day. Adding new scheduleRule from defaultDaySchedule for applicable date period.")
|
740
|
+
cooling_adjust_period_inputs.each do |period, period_inputs|
|
741
|
+
os_start_date = period_inputs["date_start"]
|
742
|
+
os_end_date = period_inputs["date_end"]
|
743
|
+
shift_time_start = period_inputs["time_start"]
|
744
|
+
shift_time_end = period_inputs["time_end"]
|
745
|
+
if [os_start_date, os_end_date, shift_time_start, shift_time_end].all?
|
746
|
+
modify_default_day_for_date_period(schedule, default_day, days_covered, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
747
|
+
cooling_adjustment_si, current_index, heating_set, runner)
|
748
|
+
# schedule.setScheduleRuleIndex(new_default_rule_period, current_index)
|
749
|
+
applicable = true
|
750
|
+
end
|
751
|
+
end
|
405
752
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
coverMissingDays(winter_rule1, days_covered)
|
410
|
-
checkDaysCovered(winter_rule1, days_covered)
|
411
|
-
|
412
|
-
summer_rule = copy_sch_rule_for_period(model, winter_rule1, default_rule, summerStartDate, summerEndDate)
|
413
|
-
|
414
|
-
summer_day = summer_rule.daySchedule
|
415
|
-
day_time_vector = summer_day.times
|
416
|
-
day_value_vector = summer_day.values
|
417
|
-
summer_day.clearValues
|
753
|
+
end
|
754
|
+
end
|
418
755
|
|
419
|
-
|
756
|
+
end
|
420
757
|
|
758
|
+
# Hook up the actuators with the new modified schedules
|
759
|
+
affected_actuators = {}
|
760
|
+
model.getEnergyManagementSystemActuators.each do |ems_actuator|
|
761
|
+
if ems_actuator.actuatedComponent.is_initialized
|
762
|
+
actuated_comp_name = ems_actuator.actuatedComponent.get.name.to_s
|
763
|
+
if sch_set_mapping.key?actuated_comp_name
|
764
|
+
new_sch_hash = sch_set_mapping[actuated_comp_name]
|
765
|
+
new_sch_hash.each_with_index do |(key, schs), index|
|
766
|
+
if index == 0
|
767
|
+
ems_actuator.setActuatedComponent(schs[0])
|
768
|
+
runner.registerInfo("The actuator component for EMS actuator #{ems_actuator.name.to_s} has been changed from #{actuated_comp_name} to #{schs[0].name.to_s}")
|
769
|
+
affected_actuators[ems_actuator.handle.to_s] = []
|
770
|
+
else
|
771
|
+
new_actuator = ems_actuator.clone(model).to_EnergyManagementSystemActuator.get
|
772
|
+
new_actuator.setActuatedComponent(schs[0])
|
773
|
+
runner.registerInfo("A new actuator #{new_actuator.name.to_s} has been added for the newly added schedule #{schs[0].name.to_s}")
|
774
|
+
affected_actuators[ems_actuator.handle.to_s] << new_actuator.handle.to_s
|
775
|
+
end
|
776
|
+
end
|
421
777
|
end
|
422
778
|
end
|
423
779
|
end
|
424
780
|
|
781
|
+
# If the OptimumStart EMS program is for an affected schedule, modify the program if it conflicts with the precooling
|
782
|
+
model.getEnergyManagementSystemPrograms.each do |ems_program|
|
783
|
+
next unless ems_program.name.to_s.include?"OptimumStart"
|
784
|
+
referenced_obj_handles = (ems_program.referencedObjects.map {|obj| obj.handle.to_s}).uniq
|
785
|
+
common_actuator = referenced_obj_handles & affected_actuators.keys.map(&:to_s)
|
786
|
+
next if common_actuator.empty?
|
787
|
+
runner.registerInfo("EMS program #{ems_program.name.to_s} is associated with affected EMS actuators")
|
788
|
+
# runner.registerInfo("#{ems_program.lines}")
|
789
|
+
common_actuator.each do |actuator_handle|
|
790
|
+
cooling_adjust_period_inputs.each do |period, period_inputs|
|
791
|
+
os_start_date = period_inputs["date_start"]
|
792
|
+
os_end_date = period_inputs["date_end"]
|
793
|
+
shift_time_start = period_inputs["time_start"]
|
794
|
+
shift_time_end = period_inputs["time_end"]
|
795
|
+
if [os_start_date, os_end_date, shift_time_start, shift_time_end].all?
|
796
|
+
## The OptimumStartProg EMS is applied to hour 5 and 7 during non-daylight saving time, and hour 4 and 6 during daylight saving time
|
797
|
+
## It's because the Hour function doesn't take daylight saving time into consideration, while the schedule objects do
|
798
|
+
## So the actual time EMS functions is 5:00 and 7:00 (if you output the thermostat setpoint variable to check)
|
799
|
+
if shift_time_start.totalHours <= 7 and shift_time_end.totalHours > 5
|
800
|
+
ems_program.addLine("IF DayOfYear >= #{os_start_date.dayOfYear} && DayOfYear <= #{os_end_date.dayOfYear}")
|
801
|
+
ems_program.addLine("SET #{actuator_handle} = NULL")
|
802
|
+
ems_program.addLine("ENDIF")
|
803
|
+
runner.registerInfo("EMS program #{ems_program.name.to_s} has been revised to avoid the conflict with precooling")
|
804
|
+
end
|
805
|
+
end
|
806
|
+
end
|
807
|
+
affected_actuators[actuator_handle].each do |new_actuator_handle|
|
808
|
+
new_program = ems_program.clone(model).to_EnergyManagementSystemProgram.get
|
809
|
+
new_program_body = new_program.body.gsub(actuator_handle, new_actuator_handle)
|
810
|
+
new_program.setBody(new_program_body)
|
811
|
+
runner.registerInfo("A new EMS program #{new_program.name.to_s} has been created for the newly added actuator #{new_actuator_handle}")
|
812
|
+
end
|
813
|
+
end
|
814
|
+
end
|
425
815
|
|
426
816
|
# not applicable if no schedules can be altered
|
427
817
|
unless applicable
|
@@ -431,68 +821,289 @@ class Precooling < OpenStudio::Measure::ModelMeasure
|
|
431
821
|
return true
|
432
822
|
end
|
433
823
|
|
824
|
+
def get_applied_dates_in_range(os_start_date, os_end_date, rule)
|
825
|
+
total_dates = (os_end_date - os_start_date).totalDays.to_i
|
826
|
+
dates_vector = (0..total_dates).map{|delta| os_start_date + OpenStudio::Time.new(delta)}
|
827
|
+
apply_flag = rule.containsDates(dates_vector)
|
828
|
+
rule_applied_dates = dates_vector.select.with_index { |value, index| apply_flag[index] }
|
829
|
+
return rule_applied_dates
|
830
|
+
end
|
434
831
|
|
832
|
+
def merge_day_sch_with_max(day_schedule_left, day_schedule_right)
|
435
833
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
sch_day.addValue(time_begin, vec_value[i])
|
442
|
-
sch_day.addValue(exist_timestamp, adjusted_value)
|
443
|
-
count = 1
|
444
|
-
elsif exist_timestamp == time_end && count == 0
|
445
|
-
sch_day.addValue(time_begin, vec_value[i])
|
446
|
-
sch_day.addValue(exist_timestamp, adjusted_value)
|
447
|
-
count = 2
|
448
|
-
elsif exist_timestamp == time_begin && count == 0
|
449
|
-
sch_day.addValue(exist_timestamp, vec_value[i])
|
450
|
-
count = 1
|
451
|
-
elsif exist_timestamp > time_end && count == 0
|
452
|
-
sch_day.addValue(time_begin, vec_value[i])
|
453
|
-
sch_day.addValue(time_end, adjusted_value)
|
454
|
-
sch_day.addValue(exist_timestamp, vec_value[i])
|
455
|
-
count = 2
|
456
|
-
elsif exist_timestamp > time_begin && exist_timestamp < time_end && count==1
|
457
|
-
sch_day.addValue(exist_timestamp, adjusted_value)
|
458
|
-
elsif exist_timestamp == time_end && count==1
|
459
|
-
sch_day.addValue(exist_timestamp, adjusted_value)
|
460
|
-
count = 2
|
461
|
-
elsif exist_timestamp > time_end && count == 1
|
462
|
-
sch_day.addValue(time_end, adjusted_value)
|
463
|
-
sch_day.addValue(exist_timestamp, vec_value[i])
|
464
|
-
count = 2
|
465
|
-
else
|
466
|
-
sch_day.addValue(exist_timestamp, vec_value[i])
|
834
|
+
def value_at_time(target_time, times, values)
|
835
|
+
times.each_with_index do |until_time, index|
|
836
|
+
if target_time <= until_time
|
837
|
+
return values[index] # Return the corresponding value for the interval
|
838
|
+
end
|
467
839
|
end
|
468
840
|
end
|
469
|
-
|
470
|
-
|
841
|
+
combined_times = (day_schedule_left.times+day_schedule_right.times).map(&:toString).uniq.sort.map {|str_time| OpenStudio::Time.new(str_time)}
|
842
|
+
original_day_times = day_schedule_left.times
|
843
|
+
original_day_values = day_schedule_left.values
|
844
|
+
# new_day_schedule = OpenStudio::Model::ScheduleDay.new(model)
|
845
|
+
|
846
|
+
day_schedule_left.clearValues
|
847
|
+
updated_times, updated_values = [], []
|
848
|
+
combined_times.each do |time|
|
849
|
+
# Get the current value from each series
|
850
|
+
value_left = value_at_time(time, original_day_times, original_day_values)
|
851
|
+
value_right = value_at_time(time, day_schedule_right.times, day_schedule_right.values)
|
852
|
+
|
853
|
+
# Calculate the maximum value at this time
|
854
|
+
max_value = [value_left, value_right].max
|
855
|
+
updated_times << time
|
856
|
+
updated_values << max_value
|
857
|
+
end
|
858
|
+
# Add to result only if the value changes to avoid redundant values
|
859
|
+
updated_values.each_with_index do |entry, index|
|
860
|
+
if index == updated_values.size || entry != updated_values[index + 1]
|
861
|
+
day_schedule_left.addValue(updated_times[index], entry)
|
862
|
+
end
|
863
|
+
end
|
864
|
+
return day_schedule_left
|
471
865
|
end
|
472
866
|
|
867
|
+
def check_date_ranges_overlap(rule, adjust_start_date, adjust_end_date)
|
868
|
+
## check if the original rule applied DateRange overlaps with the adjustment date period
|
869
|
+
overlapped = false
|
870
|
+
new_start_dates = []
|
871
|
+
new_end_dates = []
|
872
|
+
if rule.endDate.get >= rule.startDate.get and rule.startDate.get <= adjust_end_date and rule.endDate.get >= adjust_start_date
|
873
|
+
overlapped = true
|
874
|
+
new_start_dates << [adjust_start_date, rule.startDate.get].max
|
875
|
+
new_end_dates << [adjust_end_date, rule.endDate.get].min
|
876
|
+
elsif rule.endDate.get < rule.startDate.get
|
877
|
+
## If the DateRange has a endDate < startDate, the range wraps around the year.
|
878
|
+
if rule.endDate.get >= adjust_start_date
|
879
|
+
overlapped = true
|
880
|
+
new_start_dates << adjust_start_date
|
881
|
+
new_end_dates << rule.endDate.get
|
882
|
+
end
|
883
|
+
if rule.startDate.get <= adjust_end_date
|
884
|
+
overlapped = true
|
885
|
+
new_start_dates << rule.startDate.get
|
886
|
+
new_end_dates << adjust_end_date
|
887
|
+
end
|
888
|
+
end
|
889
|
+
return overlapped, new_start_dates, new_end_dates
|
890
|
+
end
|
473
891
|
|
474
|
-
def
|
475
|
-
|
476
|
-
|
477
|
-
|
892
|
+
def clone_rule_with_new_dayschedule(original_rule, new_rule_name)
|
893
|
+
## Cloning a scheduleRule will automatically clone the daySchedule associated with it, but it's a shallow copy,
|
894
|
+
## because the daySchedule is a resource that can be used by many scheduleRule
|
895
|
+
## Therefore, once the daySchedule is modified for the cloned scheduleRule, the original daySchedule is also changed
|
896
|
+
## Also, there's no function to assign a new daySchedule to the existing scheduleRule,
|
897
|
+
## so the only way to clone the scheduleRule but change the daySchedule is to construct a new scheduleRule with a daySchedule passed in
|
898
|
+
## and copy all other settings from the original scheduleRule
|
899
|
+
rule_period = OpenStudio::Model::ScheduleRule.new(original_rule.scheduleRuleset, original_rule.daySchedule)
|
900
|
+
rule_period.setName(new_rule_name)
|
901
|
+
rule_period.setApplySunday(original_rule.applySunday)
|
902
|
+
rule_period.setApplyMonday(original_rule.applyMonday)
|
903
|
+
rule_period.setApplyTuesday(original_rule.applyTuesday)
|
904
|
+
rule_period.setApplyWednesday(original_rule.applyWednesday)
|
905
|
+
rule_period.setApplyThursday(original_rule.applyThursday)
|
906
|
+
rule_period.setApplyFriday(original_rule.applyFriday)
|
907
|
+
rule_period.setApplySaturday(original_rule.applySaturday)
|
908
|
+
return rule_period
|
909
|
+
end
|
478
910
|
|
479
|
-
|
480
|
-
|
911
|
+
def modify_rule_for_date_period(original_rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
912
|
+
adjustment, applied_dow=nil, compared_day_sch=nil)
|
913
|
+
# The cloned scheduleRule will automatically belongs to the originally scheduleRuleSet
|
914
|
+
# rule_period = original_rule.clone(model).to_ScheduleRule.get
|
915
|
+
# rule_period.daySchedule = original_rule.daySchedule.clone(model)
|
916
|
+
new_rule_name = "#{original_rule.name.to_s} with DF for #{os_start_date.to_s} to #{os_end_date.to_s}"
|
917
|
+
rule_period = clone_rule_with_new_dayschedule(original_rule, new_rule_name)
|
918
|
+
## if applied_dow is nil, do not change the original applied day of week
|
919
|
+
## Otherwise, overwrite if the rule is applied to a certain day of week
|
920
|
+
unless applied_dow.nil?
|
921
|
+
rule_period.setApplySunday(applied_dow.include?"Sunday")
|
922
|
+
rule_period.setApplyMonday(applied_dow.include?"Monday")
|
923
|
+
rule_period.setApplyTuesday(applied_dow.include?"Tuesday")
|
924
|
+
rule_period.setApplyWednesday(applied_dow.include?"Wednesday")
|
925
|
+
rule_period.setApplyThursday(applied_dow.include?"Monday")
|
926
|
+
rule_period.setApplyFriday(applied_dow.include?"Friday")
|
927
|
+
rule_period.setApplySaturday(applied_dow.include?"Saturday")
|
928
|
+
end
|
929
|
+
day_rule_period = rule_period.daySchedule
|
930
|
+
day_time_vector = day_rule_period.times
|
931
|
+
day_value_vector = day_rule_period.values
|
932
|
+
if day_time_vector.empty?
|
933
|
+
return false
|
934
|
+
end
|
935
|
+
day_rule_period.clearValues
|
936
|
+
updateDaySchedule(day_rule_period, day_time_vector, day_value_vector, shift_time_start, shift_time_end, adjustment)
|
937
|
+
unless compared_day_sch.nil?
|
938
|
+
merge_day_sch_with_max(day_rule_period, compared_day_sch)
|
939
|
+
end
|
940
|
+
if rule_period
|
941
|
+
rule_period.setStartDate(os_start_date)
|
942
|
+
rule_period.setEndDate(os_end_date)
|
943
|
+
end
|
944
|
+
return rule_period
|
945
|
+
end
|
481
946
|
|
482
|
-
|
947
|
+
def modify_rule_for_specific_dates(original_rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
948
|
+
adjustment, applied_dates, compared_day_sch = nil)
|
949
|
+
new_rule_name = "#{original_rule.name.to_s} with DF for #{os_start_date.to_s} to #{os_end_date.to_s}"
|
950
|
+
rule_period = clone_rule_with_new_dayschedule(original_rule, new_rule_name)
|
951
|
+
day_rule_period = rule_period.daySchedule
|
952
|
+
day_time_vector = day_rule_period.times
|
953
|
+
day_value_vector = day_rule_period.values
|
954
|
+
if day_time_vector.empty?
|
955
|
+
return false
|
956
|
+
end
|
957
|
+
day_rule_period.clearValues
|
958
|
+
updateDaySchedule(day_rule_period, day_time_vector, day_value_vector, shift_time_start, shift_time_end, adjustment)
|
959
|
+
unless compared_day_sch.nil?
|
960
|
+
merge_day_sch_with_max(day_rule_period, compared_day_sch)
|
961
|
+
end
|
962
|
+
if rule_period
|
963
|
+
applied_dates.each do |date|
|
964
|
+
rule_period.addSpecificDate(date)
|
965
|
+
end
|
966
|
+
end
|
967
|
+
return rule_period
|
483
968
|
end
|
484
969
|
|
485
|
-
def
|
486
|
-
|
487
|
-
|
488
|
-
|
970
|
+
def modify_default_day_for_date_period(schedule_set, default_day, days_covered, os_start_date, os_end_date,
|
971
|
+
shift_time_start, shift_time_end, adjustment, current_index,
|
972
|
+
corresponding_heat_set, runner)
|
973
|
+
# the new rule created for the ScheduleRuleSet by default has the highest priority (ruleIndex=0)
|
974
|
+
new_default_rule = OpenStudio::Model::ScheduleRule.new(schedule_set, default_day)
|
975
|
+
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}")
|
976
|
+
new_default_rule.setStartDate(os_start_date)
|
977
|
+
new_default_rule.setEndDate(os_end_date)
|
978
|
+
coverMissingDays(new_default_rule, days_covered)
|
979
|
+
# days_of_week = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
980
|
+
# days_of_week.select.with_index { |value, index| !days_covered[index] }
|
981
|
+
if corresponding_heat_set.nil?
|
982
|
+
runner.registerInfo("---- Before, cooling schedule: #{default_day.times.map(&:to_s)}, #{default_day.values}")
|
983
|
+
runner.registerInfo(" NO PAIRED HEATING SCHEDULE!")
|
984
|
+
new_default_day = new_default_rule.daySchedule
|
985
|
+
day_time_vector = new_default_day.times
|
986
|
+
day_value_vector = new_default_day.values
|
987
|
+
new_default_day.clearValues
|
988
|
+
updateDaySchedule(new_default_day, day_time_vector, day_value_vector, shift_time_start, shift_time_end, adjustment)
|
989
|
+
runner.registerInfo("The default rule is created:")
|
990
|
+
runner.registerInfo("#{new_default_day.times.map(&:to_s)}, #{new_default_day.values}")
|
991
|
+
else
|
992
|
+
cool_sch_applied_dates = get_applied_dates_in_range(os_start_date, os_end_date, new_default_rule)
|
993
|
+
heat_day_mapping = {}
|
994
|
+
day_of_week_mapping = {}
|
995
|
+
cool_sch_applied_dates.each do |date|
|
996
|
+
corresponding_heat_day = corresponding_heat_set.getDaySchedules(date, date)[0]
|
997
|
+
if heat_day_mapping.key?(corresponding_heat_day.name.to_s)
|
998
|
+
heat_day_mapping[corresponding_heat_day.name.to_s]["dates"] << date
|
999
|
+
else
|
1000
|
+
heat_day_mapping[corresponding_heat_day.name.to_s] = {"heat_day_schedule" => corresponding_heat_day, "dates" => [date]}
|
1001
|
+
end
|
1002
|
+
if day_of_week_mapping.key?date.dayOfWeek.valueName
|
1003
|
+
day_of_week_mapping[date.dayOfWeek.valueName] << date
|
1004
|
+
else
|
1005
|
+
day_of_week_mapping[date.dayOfWeek.valueName] = [date]
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
if heat_day_mapping.size == 1
|
1009
|
+
heat_day = heat_day_mapping.values.first["heat_day_schedule"]
|
1010
|
+
runner.registerInfo("---- Before, heating schedule: #{heat_day.times.map(&:to_s)}, #{heat_day.values}")
|
1011
|
+
runner.registerInfo(" cooling schedule: #{default_day.times.map(&:to_s)}, #{default_day.values}")
|
1012
|
+
new_default_day = new_default_rule.daySchedule
|
1013
|
+
day_time_vector = new_default_day.times
|
1014
|
+
day_value_vector = new_default_day.values
|
1015
|
+
new_default_day.clearValues
|
1016
|
+
updateDaySchedule(new_default_day, day_time_vector, day_value_vector, shift_time_start, shift_time_end, adjustment)
|
1017
|
+
merge_day_sch_with_max(new_default_day, heat_day)
|
1018
|
+
runner.registerInfo("The default rule is created:")
|
1019
|
+
runner.registerInfo("#{new_default_day.times.map(&:to_s)}, #{new_default_day.values}")
|
1020
|
+
else
|
1021
|
+
heat_day_mapping.each do |schedule_day_name, day_info|
|
1022
|
+
heat_day = day_info["heat_day_schedule"]
|
1023
|
+
runner.registerInfo("---- Before, heating schedule: #{heat_day.times.map(&:to_s)}, #{heat_day.values}")
|
1024
|
+
runner.registerInfo(" cooling schedule: #{default_day.times.map(&:to_s)}, #{default_day.values}")
|
1025
|
+
applied_day_of_week = []
|
1026
|
+
heat_sch_dates = day_info["dates"].map(&:to_s)
|
1027
|
+
day_of_week_mapping.each do |day_of_week, dates|
|
1028
|
+
week_day_dates = dates.map(&:to_s)
|
1029
|
+
if (week_day_dates - heat_sch_dates).empty?
|
1030
|
+
applied_day_of_week << day_of_week
|
1031
|
+
heat_sch_dates -= week_day_dates
|
1032
|
+
if heat_sch_dates.empty?
|
1033
|
+
break
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
unless applied_day_of_week.empty?
|
1038
|
+
new_default_rule_dow = modify_rule_for_date_period(new_default_rule, os_start_date, os_end_date, shift_time_start, shift_time_end, adjustment,
|
1039
|
+
applied_dow=applied_day_of_week, compared_day_sch=heat_day)
|
1040
|
+
runner.registerInfo("A new rule is created for #{applied_day_of_week}:")
|
1041
|
+
runner.registerInfo("#{new_default_rule_dow.daySchedule.times.map(&:to_s)}, #{new_default_rule_dow.daySchedule.values}")
|
1042
|
+
schedule_set.setScheduleRuleIndex(new_default_rule_dow, current_index)
|
1043
|
+
current_index += 1
|
1044
|
+
runner.registerInfo("-------- The rule #{new_default_rule_dow.name.to_s} is added as priority #{current_index}")
|
1045
|
+
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
unless heat_sch_dates.empty?
|
1049
|
+
left_os_dates = heat_sch_dates.map {|str_date| OpenStudio::Date.new(str_date)}
|
1050
|
+
runner.registerInfo("Heating sch #{schedule_day_name} still covers other days. These days will be added as a rule for specific dates.")
|
1051
|
+
new_default_rule_specific_dates = modify_rule_for_specific_dates(new_default_rule, os_start_date, os_end_date, shift_time_start, shift_time_end,
|
1052
|
+
adjustment, left_os_dates, compared_day_sch=heat_day)
|
1053
|
+
runner.registerInfo("A new rule is created for specific dates:")
|
1054
|
+
runner.registerInfo("#{new_default_rule_specific_dates.daySchedule.times.map(&:to_s)}, #{new_default_rule_specific_dates.daySchedule.values}")
|
1055
|
+
schedule_set.setScheduleRuleIndex(new_default_rule_specific_dates, current_index)
|
1056
|
+
current_index += 1
|
1057
|
+
runner.registerInfo("-------- The rule #{new_default_rule_specific_dates.name.to_s} is added as priority #{current_index}")
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
new_default_rule.remove
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
489
1064
|
|
490
|
-
new_day_sch = default_sch_fule.clone(model)
|
491
|
-
new_day_sch.setParent(new_rule)
|
492
1065
|
|
493
|
-
|
1066
|
+
# TODO: if the scheduleRuleSet has holidaySchedule (which is a ScheduleDay), it cannot be altered
|
494
1067
|
end
|
495
1068
|
|
1069
|
+
# def updateAvailDaySchedule(sch_day, vec_time, vec_value, time_begin, time_end)
|
1070
|
+
# count = 0
|
1071
|
+
# vec_time.each_with_index do |exist_timestamp, i|
|
1072
|
+
# adjusted_value = 1
|
1073
|
+
# if exist_timestamp > time_begin && exist_timestamp < time_end && count == 0
|
1074
|
+
# sch_day.addValue(time_begin, vec_value[i])
|
1075
|
+
# sch_day.addValue(exist_timestamp, adjusted_value)
|
1076
|
+
# count = 1
|
1077
|
+
# elsif exist_timestamp == time_end && count == 0
|
1078
|
+
# sch_day.addValue(time_begin, vec_value[i])
|
1079
|
+
# sch_day.addValue(exist_timestamp, adjusted_value)
|
1080
|
+
# count = 2
|
1081
|
+
# elsif exist_timestamp == time_begin && count == 0
|
1082
|
+
# sch_day.addValue(exist_timestamp, vec_value[i])
|
1083
|
+
# count = 1
|
1084
|
+
# elsif exist_timestamp > time_end && count == 0
|
1085
|
+
# sch_day.addValue(time_begin, vec_value[i])
|
1086
|
+
# sch_day.addValue(time_end, adjusted_value)
|
1087
|
+
# sch_day.addValue(exist_timestamp, vec_value[i])
|
1088
|
+
# count = 2
|
1089
|
+
# elsif exist_timestamp > time_begin && exist_timestamp < time_end && count==1
|
1090
|
+
# sch_day.addValue(exist_timestamp, adjusted_value)
|
1091
|
+
# elsif exist_timestamp == time_end && count==1
|
1092
|
+
# sch_day.addValue(exist_timestamp, adjusted_value)
|
1093
|
+
# count = 2
|
1094
|
+
# elsif exist_timestamp > time_end && count == 1
|
1095
|
+
# sch_day.addValue(time_end, adjusted_value)
|
1096
|
+
# sch_day.addValue(exist_timestamp, vec_value[i])
|
1097
|
+
# count = 2
|
1098
|
+
# else
|
1099
|
+
# sch_day.addValue(exist_timestamp, vec_value[i])
|
1100
|
+
# end
|
1101
|
+
# end
|
1102
|
+
#
|
1103
|
+
# return sch_day
|
1104
|
+
# end
|
1105
|
+
|
1106
|
+
|
496
1107
|
def updateDaySchedule(sch_day, vec_time, vec_value, time_begin, time_end, adjustment)
|
497
1108
|
count = 0
|
498
1109
|
vec_time.each_with_index do |exist_timestamp, i|
|
@@ -576,7 +1187,6 @@ class Precooling < OpenStudio::Measure::ModelMeasure
|
|
576
1187
|
if sch_day_covered[6] == false
|
577
1188
|
sch_rule.setApplySaturday(true)
|
578
1189
|
end
|
579
|
-
|
580
1190
|
end
|
581
1191
|
|
582
1192
|
|