openstudio-geb 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/Rakefile +1 -1
  4. data/doc_templates/LICENSE.md +7 -21
  5. data/doc_templates/copyright_erb.txt +2 -32
  6. data/doc_templates/copyright_js.txt +2 -2
  7. data/doc_templates/copyright_ruby.txt +3 -33
  8. data/lib/measures/Add Output Variable/LICENSE.md +7 -21
  9. data/lib/measures/Add Output Variable/measure.rb +3 -33
  10. data/lib/measures/Add Output Variable/tests/AddOutputVariable_Test.rb +2 -32
  11. data/lib/measures/AddElectricVehicleChargingLoad/LICENSE.md +13 -1
  12. data/lib/measures/AddElectricVehicleChargingLoad/measure.rb +16 -52
  13. data/lib/measures/AddElectricVehicleChargingLoad/tests/add_electric_vehicle_charging_load_test.rb +2 -32
  14. data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/LICENSE.md +7 -21
  15. data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/measure.rb +14 -35
  16. data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/tests/AdjustThermostatSetpointsByDegreesForPeakHours_Test.rb +2 -32
  17. data/lib/measures/Enable Demand Controlled Ventilation/LICENSE.md +7 -21
  18. data/lib/measures/Enable Demand Controlled Ventilation/measure.rb +2 -32
  19. data/lib/measures/Enable Demand Controlled Ventilation/tests/EnableDemandControlledVentilation_Test.rb +2 -32
  20. data/lib/measures/GEB Metrics Report/LICENSE.md +13 -1
  21. data/lib/measures/GEB Metrics Report/measure.rb +4 -39
  22. data/lib/measures/GEB Metrics Report/resources/os_lib_helper_methods.rb +2 -32
  23. data/lib/measures/GEB Metrics Report/resources/os_lib_reporting.rb +2 -32
  24. data/lib/measures/GEB Metrics Report/tests/geb_metrics_report_test.rb +2 -32
  25. data/lib/measures/add_ceiling_fan/LICENSE.md +13 -0
  26. data/lib/measures/add_ceiling_fan/README.md +119 -0
  27. data/lib/measures/add_ceiling_fan/README.md.erb +45 -0
  28. data/lib/measures/add_ceiling_fan/docs/.gitkeep +0 -0
  29. data/lib/measures/add_ceiling_fan/measure.rb +565 -0
  30. data/lib/measures/add_ceiling_fan/measure.xml +226 -0
  31. data/lib/measures/add_ceiling_fan/tests/CZ06RV2.epw +8768 -0
  32. data/lib/measures/add_ceiling_fan/tests/MediumOffice-90.1-2010-ASHRAE 169-2013-5A.osm +13669 -0
  33. data/lib/measures/add_ceiling_fan/tests/SFD_1story_UB_UA_ASHP2_HPWH.osm +13110 -0
  34. data/lib/measures/add_ceiling_fan/tests/add_ceiling_fan_test.rb +82 -0
  35. data/lib/measures/add_chilled_water_storage_tank/LICENSE.md +13 -1
  36. data/lib/measures/add_chilled_water_storage_tank/measure.rb +56 -56
  37. data/lib/measures/add_chilled_water_storage_tank/tests/add_chilled_water_storage_tank_test.rb +4 -34
  38. data/lib/measures/add_electrochromic_window/LICENSE.md +13 -1
  39. data/lib/measures/add_electrochromic_window/measure.rb +2 -32
  40. data/lib/measures/add_electrochromic_window/tests/add_electrochromic_window_test.rb +2 -32
  41. data/lib/measures/add_exterior_blinds_and_control/LICENSE.md +13 -1
  42. data/lib/measures/add_exterior_blinds_and_control/measure.rb +2 -34
  43. data/lib/measures/add_exterior_blinds_and_control/tests/add_exterior_blinds_and_control_test.rb +2 -34
  44. data/lib/measures/add_heat_pump_water_heater/LICENSE.md +7 -21
  45. data/lib/measures/add_heat_pump_water_heater/measure.rb +2 -32
  46. data/lib/measures/add_heat_pump_water_heater/tests/add_hpwh_test.rb +2 -32
  47. data/lib/measures/add_interior_blinds_and_control/LICENSE.md +13 -1
  48. data/lib/measures/add_interior_blinds_and_control/measure.rb +2 -32
  49. data/lib/measures/add_interior_blinds_and_control/tests/add_exterior_blinds_and_control_test.rb +2 -32
  50. data/lib/measures/add_natural_ventilation_with_hybrid_control/LICENSE.md +13 -0
  51. data/lib/measures/add_natural_ventilation_with_hybrid_control/README.md +133 -0
  52. data/lib/measures/add_natural_ventilation_with_hybrid_control/README.md.erb +45 -0
  53. data/lib/measures/add_natural_ventilation_with_hybrid_control/docs/.gitkeep +0 -0
  54. data/lib/measures/add_natural_ventilation_with_hybrid_control/measure.rb +453 -0
  55. data/lib/measures/add_natural_ventilation_with_hybrid_control/measure.xml +241 -0
  56. data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/MediumOffice-90.1-2010-ASHRAE 169-2013-5A.osm +13669 -0
  57. data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/SmallHotel-2A.osm +42899 -0
  58. data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/USA_NY_Buffalo.Niagara.Intl.AP.725280_TMY3.epw +8768 -0
  59. data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/USA_TX_Houston-Bush.Intercontinental.AP.722430_TMY3.epw +8768 -0
  60. data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/add_natural_ventilation_with_hybrid_control_test.rb +95 -0
  61. data/lib/measures/add_rooftop_pv_simple/LICENSE.md +7 -21
  62. data/lib/measures/add_rooftop_pv_simple/measure.rb +2 -32
  63. data/lib/measures/add_rooftop_pv_simple/tests/add_rooftop_pv_test.rb +2 -32
  64. data/lib/measures/adjust_dhw_setpoint/LICENSE.md +13 -1
  65. data/lib/measures/adjust_dhw_setpoint/measure.rb +2 -32
  66. data/lib/measures/adjust_dhw_setpoint/tests/adjust_dhw_setpoint_test.rb +2 -32
  67. data/lib/measures/average_ventilation_for_peak_hours/LICENSE.md +13 -1
  68. data/lib/measures/average_ventilation_for_peak_hours/measure.rb +2 -32
  69. data/lib/measures/average_ventilation_for_peak_hours/tests/average_ventilation_for_peak_hours_test.rb +2 -32
  70. data/lib/measures/enable_occupancy_driven_lighting/LICENSE.md +13 -1
  71. data/lib/measures/enable_occupancy_driven_lighting/measure.rb +2 -32
  72. data/lib/measures/enable_occupancy_driven_lighting/tests/enable_occupancy_driven_lighting_test.rb +2 -32
  73. data/lib/measures/precooling/LICENSE.md +7 -21
  74. data/lib/measures/precooling/measure.rb +2 -33
  75. data/lib/measures/preheating/LICENSE.md +7 -21
  76. data/lib/measures/preheating/measure.rb +2 -33
  77. data/lib/measures/reduce_domestic_hot_water_use_for_peak_hours/LICENSE.md +13 -1
  78. data/lib/measures/reduce_domestic_hot_water_use_for_peak_hours/measure.rb +2 -32
  79. data/lib/measures/reduce_domestic_hot_water_use_for_peak_hours/tests/reduce_domestic_hot_water_use_for_peak_hours_test.rb +2 -32
  80. data/lib/measures/reduce_epd_by_percentage_for_peak_hours/LICENSE.md +13 -1
  81. data/lib/measures/reduce_epd_by_percentage_for_peak_hours/measure.rb +2 -32
  82. data/lib/measures/reduce_epd_by_percentage_for_peak_hours/tests/reduce_epd_by_percentage_for_peak_hours_copy_test.rb +2 -32
  83. data/lib/measures/reduce_exterior_lighting_loads/LICENSE.md +13 -0
  84. data/lib/measures/reduce_exterior_lighting_loads/README.md +79 -0
  85. data/lib/measures/reduce_exterior_lighting_loads/README.md.erb +45 -0
  86. data/lib/measures/reduce_exterior_lighting_loads/docs/.gitkeep +0 -0
  87. data/lib/measures/reduce_exterior_lighting_loads/measure.rb +385 -0
  88. data/lib/measures/reduce_exterior_lighting_loads/measure.xml +195 -0
  89. data/lib/measures/reduce_exterior_lighting_loads/tests/CZ06RV2.epw +8768 -0
  90. data/lib/measures/reduce_exterior_lighting_loads/tests/SFD_1story_UB_UA_ASHP2_HPWH.osm +13110 -0
  91. data/lib/measures/reduce_exterior_lighting_loads/tests/example_model.osm +8077 -0
  92. data/lib/measures/reduce_exterior_lighting_loads/tests/reduce_exterior_lighting_loads_test.rb +81 -0
  93. data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/LICENSE.md +13 -1
  94. data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/measure.rb +2 -32
  95. data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/tests/reduce_lpd_by_percentage_for_peak_hours_test.rb +2 -32
  96. data/lib/openstudio/geb/extension.rb +2 -50
  97. data/lib/openstudio/geb/logging.rb +2 -32
  98. data/lib/openstudio/geb/run.rb +2 -32
  99. data/lib/openstudio/geb/utilities.rb +2 -32
  100. data/lib/openstudio/geb/version.rb +3 -33
  101. data/lib/openstudio/geb.rb +3 -51
  102. data/lib/openstudio-geb.rb +6 -0
  103. data/openstudio-geb.gemspec +2 -3
  104. metadata +40 -7
@@ -0,0 +1,565 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://openstudio.net/license
4
+ # *******************************************************************************
5
+
6
+ # see the URL below for information on how to write OpenStudio measures
7
+ # http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
8
+
9
+ # start the measure
10
+ class AddCeilingFan < OpenStudio::Measure::ModelMeasure
11
+ # human readable name
12
+ def name
13
+ # Measure name should be the title case of the class name.
14
+ return 'Add Ceiling Fan'
15
+ end
16
+
17
+ # human readable description
18
+ def description
19
+ return 'Install ceiling fans in buildings to increase air circulation. Ceiling fans effectively cool by introducing slow movement to induce evaporative cooling, rather than directly conditioning the air. A diversity factor is added to consider the simultaneous usage among the household.'
20
+ end
21
+
22
+ # human readable description of modeling approach
23
+ def modeler_description
24
+ return 'Ceiling fan is modeled by increasing air velocity in the People objects and adding electric equipment to consider extra fan energy use. Cooling setpoint is increased by certain degrees in the presence of ceiling fans. A schedule is also introduced to simulate ceiling fan operation. A diversity factor (different in commercial and residential buildings) is added to consider the simultaneous usage among the building/household.'
25
+ end
26
+
27
+ # define the arguments that the user will input
28
+ def arguments(model)
29
+ args = OpenStudio::Measure::OSArgumentVector.new
30
+
31
+ # building type: residential or commercial
32
+ # this determines inputs like watts per floor area and diversity factor
33
+ bldg_type = OpenStudio::Measure::OSArgument::makeChoiceArgument('bldg_type', ['residential', 'commercial'], true)
34
+ bldg_type.setDisplayName('Select building type:')
35
+ bldg_type.setDescription('Building type (residential or commercial)')
36
+ bldg_type.setDefaultValue('commercial')
37
+ args << bldg_type
38
+
39
+ # cooling setpoint increase
40
+ # default value comes from https://www.sciencedirect.com/science/article/abs/pii/S0360132321004133
41
+ cool_stp_increase_C = OpenStudio::Measure::OSArgument.makeDoubleArgument('cool_stp_increase_C', true)
42
+ cool_stp_increase_C.setDisplayName('Cooling setpoint increase - C')
43
+ cool_stp_increase_C.setDescription('Cooling setpoint increase in degree C')
44
+ cool_stp_increase_C.setDefaultValue(3)
45
+ args << cool_stp_increase_C
46
+
47
+ # ceiling fan motor type: AC or DC
48
+ motor_type = OpenStudio::Measure::OSArgument::makeChoiceArgument('motor_type', ['DC', 'AC'], true)
49
+ motor_type.setDisplayName('Select ceiling fan motor type:')
50
+ motor_type.setDescription('Ceiling fan motor type')
51
+ motor_type.setDefaultValue('DC')
52
+ args << motor_type
53
+
54
+ # ceiling fan EUI in watts per floor area (optional)
55
+ # if not user assigned, use default values in run function below, which comes from discussion with Center for Built Environment based on their research
56
+ watts_per_m2 = OpenStudio::Measure::OSArgument.makeDoubleArgument('watts_per_m2', false)
57
+ watts_per_m2.setDisplayName('Ceiling fan EUI in watts per floor area')
58
+ watts_per_m2.setDescription('Ceiling fan watts per m2')
59
+ args << watts_per_m2
60
+
61
+ # make an argument for the start date of the reduction
62
+ start_date = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date', false)
63
+ start_date.setDisplayName('First start date for the Reduction')
64
+ start_date.setDescription('In MM-DD format')
65
+ start_date.setDefaultValue('05-01')
66
+ args << start_date
67
+
68
+ # make an argument for the end date of the reduction
69
+ end_date = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date', false)
70
+ end_date.setDisplayName('First end date for the Reduction')
71
+ end_date.setDescription('In MM-DD format')
72
+ end_date.setDefaultValue('09-30')
73
+ args << end_date
74
+
75
+ # make an argument for the start time of the reduction
76
+ start_time = OpenStudio::Measure::OSArgument.makeStringArgument('start_time', false)
77
+ start_time.setDisplayName('Start Time for the Reduction')
78
+ start_time.setDescription('In HH:MM:SS format')
79
+ start_time.setDefaultValue('08:00:00')
80
+ args << start_time
81
+
82
+ # make an argument for the end time of the reduction
83
+ end_time = OpenStudio::Measure::OSArgument.makeStringArgument('end_time', false)
84
+ end_time.setDisplayName('End Time for the Reduction')
85
+ end_time.setDescription('In HH:MM:SS format')
86
+ end_time.setDefaultValue('18:00:00')
87
+ args << end_time
88
+
89
+ # diversity factor
90
+ diversity_factor = OpenStudio::Measure::OSArgument.makeDoubleArgument('diversity_factor', false)
91
+ diversity_factor.setDisplayName('Diversity factor')
92
+ diversity_factor.setDescription('Diversity factor')
93
+ args << diversity_factor
94
+
95
+ # people air velocity
96
+ people_air_velocity = OpenStudio::Measure::OSArgument.makeDoubleArgument('people_air_velocity', false)
97
+ people_air_velocity.setDisplayName('People air velocity')
98
+ people_air_velocity.setDescription('Air velocity surrounding people (m/s)')
99
+ people_air_velocity.setDefaultValue(0.8)
100
+ args << people_air_velocity
101
+
102
+ return args
103
+ end
104
+
105
+ # define what happens when the measure is run
106
+ def run(model, runner, user_arguments)
107
+ super(model, runner, user_arguments)
108
+
109
+ # use the built-in error checking
110
+ if !runner.validateUserArguments(arguments(model), user_arguments)
111
+ return false
112
+ end
113
+
114
+ # assign the user inputs to variables
115
+ bldg_type = runner.getStringArgumentValue('bldg_type', user_arguments)
116
+ motor_type = runner.getStringArgumentValue('motor_type', user_arguments)
117
+ cool_stp_increase_C = runner.getDoubleArgumentValue('cool_stp_increase_C', user_arguments)
118
+ start_date = runner.getStringArgumentValue('start_date', user_arguments)
119
+ end_date = runner.getStringArgumentValue('end_date', user_arguments)
120
+ start_time = runner.getStringArgumentValue('start_time', user_arguments)
121
+ end_time = runner.getStringArgumentValue('end_time', user_arguments)
122
+ watts_per_m2 = runner.getOptionalDoubleArgumentValue('watts_per_m2', user_arguments)
123
+ diversity_factor = runner.getOptionalDoubleArgumentValue('diversity_factor', user_arguments)
124
+ people_air_velocity = runner.getOptionalDoubleArgumentValue('people_air_velocity', user_arguments)
125
+
126
+ if bldg_type != 'commercial' && bldg_type != 'residential'
127
+ runner.registerError("Wrong building type #{bldg_type} entered. Value must be either 'commercial' or 'residential'.")
128
+ return false
129
+ end
130
+
131
+ if motor_type != 'AC' && motor_type != 'DC'
132
+ runner.registerError("Wrong ceiling fan motor type #{motor_type} entered. Value must be either 'AC' or 'DC'.")
133
+ return false
134
+ end
135
+
136
+ # validate the cooling setpoint increase
137
+ if cool_stp_increase_C <= 0
138
+ runner.registerError('The cooling setpoint increase should be positive.')
139
+ return false
140
+ elsif cool_stp_increase_C > 10
141
+ runner.registerWarning('The cooling setpoint increase is abnormally large, normally it is between 2-5C.')
142
+ end
143
+
144
+ if watts_per_m2.empty?
145
+ # use default value based on building type and motor type
146
+ case bldg_type
147
+ when 'commercial'
148
+ if motor_type == 'DC'
149
+ watts_per_m2 = 0.16146 #0.015W/ft2
150
+ else # 'AC'
151
+ watts_per_m2 = 0.48 # 0.015W/ft2 * 3 (double/triple of DC per CBE Paul Raftery)
152
+ end
153
+ when 'residential'
154
+ if motor_type == 'DC'
155
+ watts_per_m2 = 0.35 # 0.13W/ft2 * 25% (The average power when the fan was running was between 20-25% of max)
156
+ else # 'AC'
157
+ watts_per_m2 = 1.05 # 0.13W/ft2 * 3 (double/triple of DC per CBE Paul Raftery)
158
+ end
159
+ end
160
+ elsif watts_per_m2.to_f <= 0
161
+ runner.registerError("Invalid ceiling fan watts per m2 #{watts_per_m2} entered. Value must be >0.")
162
+ return false
163
+ else
164
+ watts_per_m2 = watts_per_m2.to_f
165
+ end
166
+
167
+ # default value references: https://electricalnotes.wordpress.com/2018/04/01/thumb-rules-14-quick-reference-demand-diversity-factor/
168
+ # https://www.electricallicenserenewal.com/Electrical-Continuing-Education-Courses/NEC-Content.php?sectionID=836.0
169
+ if diversity_factor.empty?
170
+ case bldg_type
171
+ when 'commercial'
172
+ diversity_factor = 0.9
173
+ when 'residential'
174
+ diversity_factor = 0.66
175
+ end
176
+ elsif diversity_factor.to_f <= 0 or diversity_factor.to_f > 1
177
+ runner.registerError("Invalid diversity factor #{diversity_factor} entered. Value must be >0 and <=1.")
178
+ return false
179
+ else
180
+ diversity_factor = diversity_factor.to_f
181
+ end
182
+
183
+ if people_air_velocity.empty?
184
+ people_air_velocity = 0.8 # default 0.8m/s for ceiling fan operation
185
+ elsif people_air_velocity.to_f <= 0
186
+ runner.registerError("Invalid people air velocity #{people_air_velocity} entered. Value must be positive.")
187
+ return false
188
+ elsif people_air_velocity.to_f > 2
189
+ runner.registerWarning('The people air velocity is abnormally high, normally it is between 0.2-2m/s.')
190
+ people_air_velocity = people_air_velocity.to_f
191
+ elsif people_air_velocity.to_f < 0.2
192
+ runner.registerWarning('The people air velocity is abnormally low, normally it is between 0.2-2m/s.')
193
+ people_air_velocity = people_air_velocity.to_f
194
+ else
195
+ people_air_velocity = people_air_velocity.to_f
196
+ end
197
+
198
+ # validate and assign operational period date and time
199
+ if /(\d\d):(\d\d):(\d\d)/.match(start_time)
200
+ os_start_time = OpenStudio::Time.new(start_time)
201
+ else
202
+ runner.registerError('Start time must be in HH-MM-SS format.')
203
+ return false
204
+ end
205
+
206
+ if /(\d\d):(\d\d):(\d\d)/.match(end_time)
207
+ os_end_time = OpenStudio::Time.new(end_time)
208
+ else
209
+ runner.registerError('End time must be in HH-MM-SS format.')
210
+ return false
211
+ end
212
+
213
+ if start_time.to_f > end_time.to_f
214
+ runner.registerError('The start time cannot be later than the end time.')
215
+ return false
216
+ end
217
+
218
+ start_month = nil
219
+ start_day = nil
220
+ md = /(\d\d)-(\d\d)/.match(start_date)
221
+ if md
222
+ start_month = md[1].to_i
223
+ start_day = md[2].to_i
224
+ else
225
+ runner.registerError('Start date must be in MM-DD format.')
226
+ return false
227
+ end
228
+ end_month = nil
229
+ end_day = nil
230
+ md = /(\d\d)-(\d\d)/.match(end_date)
231
+ if md
232
+ end_month = md[1].to_i
233
+ end_day = md[2].to_i
234
+ else
235
+ runner.registerError('End date must be in MM-DD format.')
236
+ return false
237
+ end
238
+
239
+ # create cooling setpoint schedule and fan operation schedule for ceiling or portable fans
240
+ def create_cooling_and_fan_schedule(model, start_date, end_date, start_time, end_time, diversity_factor)
241
+ year_start = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(1), 1, model.assumedYear)
242
+ year_end = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(12), 31, model.assumedYear)
243
+
244
+ fan_sch_week = OpenStudio::Model::ScheduleWeek.new(model)
245
+ fan_sch_week.setName("Ceiling fan operation sch week")
246
+
247
+ ## Check for Schedule Type Limits and Create if Needed
248
+ if !model.getModelObjectByName('OnOff').empty?
249
+ sched_limits_onoff = model.getModelObjectByName('OnOff').get.to_ScheduleTypeLimits.get
250
+ else
251
+ sched_limits_onoff = OpenStudio::Model::ScheduleTypeLimits.new(model)
252
+ sched_limits_onoff.setName('OnOff')
253
+ sched_limits_onoff.setNumericType('Discrete')
254
+ sched_limits_onoff.setUnitType('Availability')
255
+ sched_limits_onoff.setLowerLimitValue(0.0)
256
+ sched_limits_onoff.setUpperLimitValue(1.0)
257
+ end
258
+
259
+ fan_sch_day = OpenStudio::Model::ScheduleDay.new(model)
260
+ fan_sch_day.setName("Ceiling fan operation sch default")
261
+ fan_sch_day.setScheduleTypeLimits(sched_limits_onoff)
262
+ fan_sch_day.addValue(start_time, 0)
263
+ if end_time < OpenStudio::Time.new('24:00:00')
264
+ fan_sch_day.addValue(end_time, 1.0 * diversity_factor)
265
+ fan_sch_day.addValue(OpenStudio::Time.new('24:00:00'), 0)
266
+ else
267
+ fan_sch_day.addValue(OpenStudio::Time.new('24:00:00'), 1.0 * diversity_factor)
268
+ end
269
+ fan_sch_week.setAllSchedules(fan_sch_day)
270
+
271
+ fan_off_day = OpenStudio::Model::ScheduleDay.new(model)
272
+ fan_off_day.setName("Ceiling fan off sch day")
273
+ fan_off_day.setScheduleTypeLimits(sched_limits_onoff)
274
+ fan_off_day.addValue(OpenStudio::Time.new(0,24,0,0), 0)
275
+ fan_off_week = OpenStudio::Model::ScheduleWeek.new(model)
276
+ fan_off_week.setName("Ceiling fan off sch week")
277
+ fan_off_week.setAllSchedules(fan_off_day)
278
+
279
+ fan_sch = OpenStudio::Model::ScheduleYear.new(model)
280
+ fan_sch.setName("Ceiling fan sch")
281
+ fan_sch.setScheduleTypeLimits(sched_limits_onoff)
282
+
283
+ # if ceiling fan only operates during a specific period
284
+ unless start_date == year_start
285
+ fan_sch.addScheduleWeek(start_date, fan_off_week)
286
+ end
287
+
288
+ if end_date == year_end
289
+ fan_sch.addScheduleWeek(year_end, fan_sch_week)
290
+ else
291
+ fan_sch.addScheduleWeek(end_date, fan_sch_week)
292
+ fan_sch.addScheduleWeek(year_end, fan_off_week)
293
+ end
294
+
295
+ puts "fan_sch: #{fan_sch}"
296
+
297
+ return fan_sch
298
+ end
299
+
300
+ def updateDaySchedule(sch_day, vec_time, vec_value, time_begin, time_end, add_value)
301
+ count = 0
302
+ vec_time.each_with_index do |exist_timestamp, i|
303
+ new_value = vec_value[i] + add_value
304
+ if exist_timestamp > time_begin && exist_timestamp < time_end && count == 0
305
+ sch_day.addValue(time_begin, vec_value[i])
306
+ sch_day.addValue(exist_timestamp, new_value)
307
+ count = 1
308
+ elsif exist_timestamp == time_end && count == 0
309
+ sch_day.addValue(time_begin, vec_value[i])
310
+ sch_day.addValue(exist_timestamp, new_value)
311
+ count = 2
312
+ elsif exist_timestamp == time_begin && count == 0
313
+ sch_day.addValue(exist_timestamp, vec_value[i])
314
+ count = 1
315
+ elsif exist_timestamp > time_end && count == 0
316
+ sch_day.addValue(time_begin, vec_value[i])
317
+ sch_day.addValue(time_end, new_value)
318
+ sch_day.addValue(exist_timestamp, vec_value[i])
319
+ count = 2
320
+ elsif exist_timestamp > time_begin && exist_timestamp < time_end && count==1
321
+ sch_day.addValue(exist_timestamp, new_value)
322
+ elsif exist_timestamp == time_end && count==1
323
+ sch_day.addValue(exist_timestamp, new_value)
324
+ count = 2
325
+ elsif exist_timestamp > time_end && count == 1
326
+ sch_day.addValue(time_end, new_value)
327
+ sch_day.addValue(exist_timestamp, vec_value[i])
328
+ count = 2
329
+ else
330
+ sch_day.addValue(exist_timestamp, vec_value[i])
331
+ end
332
+ end
333
+
334
+ return sch_day
335
+ end
336
+
337
+ # copy ScheduleRule sch_rule, copy ScheduleDay sch_day and assign to new schedule rule
338
+ def copy_sch_rule_for_period(model, sch_rule, sch_day, start_date, end_date)
339
+ new_rule = sch_rule.clone(model).to_ScheduleRule.get
340
+ new_rule.setStartDate(start_date)
341
+ new_rule.setEndDate(end_date)
342
+
343
+ new_day_sch = sch_day.clone(model)
344
+ new_day_sch.setParent(new_rule)
345
+
346
+ return new_rule
347
+ end
348
+
349
+ def create_sch_rule_from_default(model, sch_ruleset, default_sch_fule, start_date, end_date)
350
+ new_rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset)
351
+ new_rule.setStartDate(start_date)
352
+ new_rule.setEndDate(end_date)
353
+
354
+ new_day_sch = default_sch_fule.clone(model)
355
+ new_day_sch.setParent(new_rule)
356
+
357
+ return new_rule
358
+ end
359
+
360
+ def checkDaysCovered(sch_rule, sch_day_covered)
361
+ if sch_rule.applySunday
362
+ sch_day_covered[0] = true
363
+ end
364
+ if sch_rule.applyMonday
365
+ sch_day_covered[1] = true
366
+ end
367
+ if sch_rule.applyTuesday
368
+ sch_day_covered[2] = true
369
+ end
370
+ if sch_rule.applyWednesday
371
+ sch_day_covered[3] = true
372
+ end
373
+ if sch_rule.applyThursday
374
+ sch_day_covered[4] = true
375
+ end
376
+ if sch_rule.applyFriday
377
+ sch_day_covered[5] = true
378
+ end
379
+ if sch_rule.applySaturday
380
+ sch_day_covered[6] = true
381
+ end
382
+ end
383
+
384
+ def coverMissingDays(sch_rule, sch_day_covered)
385
+ if sch_day_covered[0] == false
386
+ sch_rule.setApplySunday(true)
387
+ end
388
+ if sch_day_covered[1] == false
389
+ sch_rule.setApplyMonday(true)
390
+ end
391
+ if sch_day_covered[2] == false
392
+ sch_rule.setApplyTuesday(true)
393
+ end
394
+ if sch_day_covered[3] == false
395
+ sch_rule.setApplyWednesday(true)
396
+ end
397
+ if sch_day_covered[4] == false
398
+ sch_rule.setApplyThursday(true)
399
+ end
400
+ if sch_day_covered[5] == false
401
+ sch_rule.setApplyFriday(true)
402
+ end
403
+ if sch_day_covered[6] == false
404
+ sch_rule.setApplySaturday(true)
405
+ end
406
+
407
+ end
408
+
409
+ def adjust_cool_sch(model, sch, cool_stp_inc, start_date, end_date, start_time, end_time)
410
+ new_sch = sch.get.clone(model)
411
+ new_sch = new_sch.to_Schedule.get
412
+ new_sch.setName("#{sch.get.name.to_s} increased by #{cool_stp_inc}C due to ceiling fan")
413
+ year_start = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(1), 1, model.assumedYear)
414
+ year_end = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(12), 31, model.assumedYear)
415
+
416
+ # make cooling schedule adjustments and rename. Put in check to skip and warn if schedule not ruleset
417
+ if !new_sch.to_ScheduleRuleset.empty?
418
+ schedule = new_sch.to_ScheduleRuleset.get
419
+ default_rule = schedule.defaultDaySchedule
420
+ rules = schedule.scheduleRules
421
+ days_covered = Array.new(7, false)
422
+
423
+ # TODO: when ruleset has multiple rules for each month or couple of months instead of a full year, should first see if the period overlaps with summer/winter
424
+ if rules.length > 0
425
+ rules.each do |rule|
426
+ unless start_date == year_start
427
+ unchanged_rule1 = copy_sch_rule_for_period(model, rule, rule.daySchedule, year_start, start_date)
428
+ end
429
+
430
+ unless end_date == year_end
431
+ unchanged_rule2 = copy_sch_rule_for_period(model, rule, rule.daySchedule, end_date, year_end)
432
+ end
433
+
434
+ change_rule = rule
435
+ checkDaysCovered(change_rule, days_covered)
436
+ change_rule.setStartDate(start_date)
437
+ change_rule.setEndDate(end_date)
438
+
439
+ change_day = change_rule.daySchedule
440
+ day_time_vector = change_day.times
441
+ day_value_vector = change_day.values
442
+ change_day.clearValues
443
+
444
+ change_day = updateDaySchedule(change_day, day_time_vector, day_value_vector, start_time, end_time, cool_stp_inc)
445
+ end
446
+ else
447
+ runner.registerWarning("Cooling setpoint schedule '#{sch.get.name.to_s}' is a ScheduleRuleSet, but has no ScheduleRules associated. It won't be altered by this measure.")
448
+ end
449
+
450
+ # for days not covered (not defined specifically), modify default schedule
451
+ if days_covered.include?(false)
452
+ unless start_date == year_start
453
+ unchanged_rule1 = create_sch_rule_from_default(model, schedule, default_rule, year_start, start_date)
454
+ end
455
+
456
+ unless end_date == year_end
457
+ unchanged_rule2 = create_sch_rule_from_default(model, schedule, default_rule, end_date, year_end)
458
+ end
459
+
460
+ coverMissingDays(unchanged_rule1, days_covered)
461
+ checkDaysCovered(unchanged_rule1, days_covered)
462
+
463
+ change_rule = copy_sch_rule_for_period(model, unchanged_rule1, default_rule, start_date, end_date)
464
+
465
+ change_day = change_rule.daySchedule
466
+ day_time_vector = change_day.times
467
+ day_value_vector = change_day.values
468
+ change_day.clearValues
469
+
470
+ change_day = updateDaySchedule(change_day, day_time_vector, day_value_vector, start_time, end_time, cool_stp_inc)
471
+ end
472
+
473
+ ######################################################################
474
+ else
475
+ runner.registerWarning("Schedule '#{sch.get.name.to_s}' isn't a ScheduleRuleset object and won't be altered by this measure.")
476
+ new_sch.remove # remove un-used clone
477
+ end
478
+
479
+ return new_sch
480
+ end
481
+
482
+ # add model's assumed year to make sure addScheduleWeek to scheduleYear can go through
483
+ os_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month), start_day, model.assumedYear)
484
+ os_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month), end_day, model.assumedYear)
485
+
486
+ # generate fan on and off week schedule
487
+ ceiling_fan_sch = create_cooling_and_fan_schedule(model, os_start_date, os_end_date, os_start_time, os_end_time, diversity_factor)
488
+
489
+ # add ceiling fan electric equipment object
490
+ ceiling_fan_def = OpenStudio::Model::ElectricEquipmentDefinition.new(model)
491
+ ceiling_fan_def.setName("Ceiling fan def")
492
+ ceiling_fan_def.setWattsperSpaceFloorArea(watts_per_m2)
493
+ ceiling_fan_def.setFractionLatent(0.0)
494
+ ceiling_fan_def.setFractionRadiant(0.0)
495
+ ceiling_fan_def.setFractionLost(0.0) # all convective heat gain
496
+
497
+ # create people air velocity schedule
498
+ air_velo_sch = OpenStudio::Model::ScheduleRuleset.new(model)
499
+ air_velo_sch.setName('Air Velocity Schedule')
500
+ air_velo_sch.defaultDaySchedule.setName('Air Velocity Schedule Default')
501
+ air_velo_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), people_air_velocity)
502
+
503
+ ceiling_fan_count = 0 # init
504
+
505
+ # add ceiling fan to occupied spaces
506
+ # don't need to loop through space types as thermostat is attached to thermalZone only, which is connected to space
507
+ model.getSpaces.each do |space|
508
+ # only add to occupied zones
509
+ unless space.numberOfPeople > 0
510
+ runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because it is not occupied.")
511
+ next
512
+ end
513
+
514
+ # only add to space => thermal zone that has cooling setpoint assigned
515
+ if space.thermalZone.empty?
516
+ runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because it is not associated with a thermal zone.")
517
+ next
518
+ end
519
+
520
+ thermal_zone = space.thermalZone.get
521
+ if thermal_zone.thermostatSetpointDualSetpoint.empty?
522
+ runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because it doesn't have thermostat assigned.")
523
+ next
524
+ end
525
+
526
+ thermostat = thermal_zone.thermostatSetpointDualSetpoint.get
527
+ # setup new cooling setpoint schedule
528
+ clg_set_sch = thermostat.coolingSetpointTemperatureSchedule
529
+ if clg_set_sch.empty?
530
+ runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because its thermostat doesn't have cooling setpoint schedule assigned.")
531
+ next
532
+ end
533
+
534
+ runner.registerInfo("adjusting schedule #{clg_set_sch.get.name.to_s}")
535
+ new_sch = adjust_cool_sch(model, clg_set_sch, cool_stp_increase_C, os_start_date, os_end_date, os_start_time, os_end_time)
536
+
537
+ # hook up clone to thermostat
538
+ thermostat.setCoolingSetpointTemperatureSchedule(new_sch)
539
+
540
+ # add ceiling fan electric equipment
541
+ ceiling_fan = OpenStudio::Model::ElectricEquipment.new(ceiling_fan_def)
542
+ ceiling_fan.setName("#{space.name.to_s} ceiling fan")
543
+ ceiling_fan.setSchedule(ceiling_fan_sch)
544
+ ceiling_fan.setSpace(space)
545
+
546
+ # assign people air velocity schedule
547
+ space.people.each do |people_inst|
548
+ people_inst.setAirVelocitySchedule(air_velo_sch)
549
+ end
550
+
551
+ ceiling_fan_count += 1
552
+ end
553
+
554
+ # echo the new space's name back to the user
555
+ runner.registerInfo("#{ceiling_fan_count} ceiling fans were added to occupied conditioned spaces with #{watts_per_m2}w/m2.")
556
+
557
+ # report final condition of model
558
+ runner.registerFinalCondition("Ceiling fans were added to the building.")
559
+
560
+ return true
561
+ end
562
+ end
563
+
564
+ # register the measure to be used by the application
565
+ AddCeilingFan.new.registerWithApplication