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