openstudio-ee 0.12.3 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.coverage +0 -0
  3. data/.github/workflows/test-with-openstudio.yml +109 -74
  4. data/.gitignore +21 -0
  5. data/.rubocop.yml +15 -2
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +7 -8
  8. data/README.md +1 -0
  9. data/WORKFLOW_CHANGES.md +74 -0
  10. data/lib/measures/AddDaylightSensors/measure.rb +79 -79
  11. data/lib/measures/AddDaylightSensors/measure.xml +4 -4
  12. data/lib/measures/AddOverhangsByProjectionFactor/measure.rb +38 -41
  13. data/lib/measures/AddOverhangsByProjectionFactor/measure.xml +4 -4
  14. data/lib/measures/EnableDemandControlledVentilation/measure.rb +37 -40
  15. data/lib/measures/EnableDemandControlledVentilation/measure.xml +4 -4
  16. data/lib/measures/EnableEconomizerControl/measure.rb +36 -37
  17. data/lib/measures/EnableEconomizerControl/measure.xml +4 -4
  18. data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.rb +27 -41
  19. data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.xml +4 -4
  20. data/lib/measures/GLHEProGFunctionImport/measure.rb +11 -15
  21. data/lib/measures/GLHEProGFunctionImport/measure.xml +4 -4
  22. data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.rb +5 -9
  23. data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.xml +3 -3
  24. data/lib/measures/ImproveFanBeltEfficiency/measure.rb +78 -95
  25. data/lib/measures/ImproveFanBeltEfficiency/measure.xml +6 -6
  26. data/lib/measures/ImproveMotorEfficiency/measure.rb +75 -100
  27. data/lib/measures/ImproveMotorEfficiency/measure.xml +6 -6
  28. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.rb +137 -130
  29. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.xml +4 -4
  30. data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.rb +114 -115
  31. data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.xml +3 -3
  32. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.rb +137 -130
  33. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.xml +4 -4
  34. data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.rb +114 -115
  35. data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.xml +3 -3
  36. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.rb +69 -63
  37. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.xml +6 -6
  38. data/lib/measures/ReduceLightingLoadsByPercentage/measure.rb +77 -66
  39. data/lib/measures/ReduceLightingLoadsByPercentage/measure.xml +6 -6
  40. data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.rb +45 -43
  41. data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.xml +4 -4
  42. data/lib/measures/ReduceNightTimeLightingLoads/measure.rb +45 -43
  43. data/lib/measures/ReduceNightTimeLightingLoads/measure.xml +4 -4
  44. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.rb +58 -52
  45. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.xml +6 -6
  46. data/lib/measures/ReduceVentilationByPercentage/measure.rb +49 -46
  47. data/lib/measures/ReduceVentilationByPercentage/measure.xml +6 -6
  48. data/lib/measures/add_variable_speed_rtu_control_logic/measure.rb +31 -23
  49. data/lib/measures/add_variable_speed_rtu_control_logic/measure.xml +4 -4
  50. data/lib/measures/create_variable_speed_rtu/measure.rb +166 -174
  51. data/lib/measures/create_variable_speed_rtu/measure.xml +6 -6
  52. data/lib/measures/fan_assist_night_ventilation/measure.rb +33 -32
  53. data/lib/measures/fan_assist_night_ventilation/measure.xml +4 -4
  54. data/lib/measures/nze_hvac/measure.rb +72 -62
  55. data/lib/measures/nze_hvac/measure.xml +4 -4
  56. data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.rb +16 -19
  57. data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.xml +4 -4
  58. data/lib/measures/window_enhancement/LICENSE.md +14 -0
  59. data/lib/measures/window_enhancement/README.md +112 -0
  60. data/lib/measures/window_enhancement/docs/.gitkeep +0 -0
  61. data/lib/measures/window_enhancement/measure.py +386 -0
  62. data/lib/measures/window_enhancement/measure.xml +128 -0
  63. data/lib/measures/window_enhancement/resources/EC3_lookup.py +321 -0
  64. data/lib/measures/window_enhancement/resources/Test_API.py +32 -0
  65. data/lib/measures/window_enhancement/resources/__pycache__/EC3_lookup.cpython-39.pyc +0 -0
  66. data/lib/measures/window_enhancement/resources/__pycache__/Original_EC3_lookup.py +322 -0
  67. data/lib/measures/window_enhancement/resources/__pycache__/Test_API.cpython-39.pyc +0 -0
  68. data/lib/measures/window_enhancement/resources/calculate_perimeter.py +39 -0
  69. data/lib/measures/window_enhancement/test_output.log +39 -0
  70. data/lib/openstudio/ee_measures/version.rb +1 -1
  71. data/openstudio-ee.gemspec +10 -8
  72. data/test-workflow-locally.sh +152 -0
  73. metadata +64 -35
  74. data/Jenkinsfile +0 -11
@@ -12,17 +12,17 @@
12
12
  class FanAssistNightVentilation < OpenStudio::Measure::ModelMeasure
13
13
  # human readable name
14
14
  def name
15
- return 'Fan Assist Night Ventilation'
15
+ 'Fan Assist Night Ventilation'
16
16
  end
17
17
 
18
18
  # human readable description
19
19
  def description
20
- return "This measure is meant to roughly model the impact of fan assisted night ventilation. The user needs to have a ventilation schedule in the model, operable windows where natural ventilation is desired, and air walls or interior operable windows in walls and floors to define the path of air through the building. The user specified flow rate is proportionally split up based on the area of exterior operable windows. The size of interior air walls and windows doesn't matter."
20
+ "This measure is meant to roughly model the impact of fan assisted night ventilation. The user needs to have a ventilation schedule in the model, operable windows where natural ventilation is desired, and air walls or interior operable windows in walls and floors to define the path of air through the building. The user specified flow rate is proportionally split up based on the area of exterior operable windows. The size of interior air walls and windows doesn't matter."
21
21
  end
22
22
 
23
23
  # human readable description of modeling approach
24
24
  def modeler_description
25
- return "It's up to the modeler to choose a flow rate that is approriate for the fenestration and interior openings within the building. Each zone with operable windows will get a zone ventilation object. The measure will first look for a celing opening to find a connection for zone a zone mixing object. If a ceiling isn't found, then it looks for a wall. Don't provide more than one ceiling paths or more than one wall path. The end result is zone ventilation object followed by a path of zone mixing objects. The fan consumption is modeled in the zone ventilation object, but no heat is brought in from the fan. There is no zone ventilation object at the end of the path of zones. In addition to schedule, the zone ventilation is controlled by a minimum outdoor temperature.
25
+ "It's up to the modeler to choose a flow rate that is approriate for the fenestration and interior openings within the building. Each zone with operable windows will get a zone ventilation object. The measure will first look for a celing opening to find a connection for zone a zone mixing object. If a ceiling isn't found, then it looks for a wall. Don't provide more than one ceiling paths or more than one wall path. The end result is zone ventilation object followed by a path of zone mixing objects. The fan consumption is modeled in the zone ventilation object, but no heat is brought in from the fan. There is no zone ventilation object at the end of the path of zones. In addition to schedule, the zone ventilation is controlled by a minimum outdoor temperature.
26
26
 
27
27
  The measure was developed for use in un-conditioned models. Has not been tested in conjunction with mechanical systems.
28
28
 
@@ -71,6 +71,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
71
71
  # looping through sorted hash of schedules to find air velocity schedules
72
72
  schedule_args_hash.sort.map do |key, value|
73
73
  next if value.scheduleTypeLimits.empty?
74
+
74
75
  if value.scheduleTypeLimits.get.unitType == 'Dimensionless'
75
76
  ventilation_schedule_handles << value.handle.to_s
76
77
  ventilation_schedule_display_names << key
@@ -78,7 +79,8 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
78
79
  end
79
80
 
80
81
  # make a choice argument for Air Velocity Schedule Name
81
- ventilation_schedule = OpenStudio::Measure::OSArgument.makeChoiceArgument('ventilation_schedule', ventilation_schedule_handles, ventilation_schedule_display_names, true)
82
+ ventilation_schedule = OpenStudio::Measure::OSArgument.makeChoiceArgument('ventilation_schedule',
83
+ ventilation_schedule_handles, ventilation_schedule_display_names, true)
82
84
  ventilation_schedule.setDisplayName('Choose a Ventilation Schedule.')
83
85
  args << ventilation_schedule
84
86
 
@@ -89,7 +91,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
89
91
  min_outdoor_temp.setDefaultValue(55.0)
90
92
  args << min_outdoor_temp
91
93
 
92
- return args
94
+ args
93
95
  end
94
96
 
95
97
  def inspect_airflow_surfaces(zone)
@@ -97,8 +99,9 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
97
99
  zone.spaces.each do |space|
98
100
  space.surfaces.each do |surface|
99
101
  next if surface.adjacentSurface.is_initialized != true
100
- next if !surface.adjacentSurface.get.space.is_initialized
101
- next if !surface.adjacentSurface.get.space.get.thermalZone.is_initialized
102
+ next unless surface.adjacentSurface.get.space.is_initialized
103
+ next unless surface.adjacentSurface.get.space.get.thermalZone.is_initialized
104
+
102
105
  adjacent_zone = surface.adjacentSurface.get.space.get.thermalZone.get
103
106
  if surface.surfaceType == 'RoofCeiling' || surface.surfaceType == 'Wall'
104
107
  if surface.isAirWall
@@ -106,8 +109,9 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
106
109
  else
107
110
  surface.subSurfaces.each do |sub_surface|
108
111
  next if sub_surface.adjacentSubSurface.is_initialized != true
109
- next if !sub_surface.adjacentSubSurface.get.surface.get.space.is_initialized
110
- next if !sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.is_initialized
112
+ next unless sub_surface.adjacentSubSurface.get.surface.get.space.is_initialized
113
+ next unless sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.is_initialized
114
+
111
115
  adjacent_zone = sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.get
112
116
  if sub_surface.isAirWall || sub_surface.subSurfaceType == 'OperableWindow'
113
117
  array << [adjacent_zone, surface.surfaceType]
@@ -118,7 +122,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
118
122
  end
119
123
  end
120
124
 
121
- return array
125
+ array
122
126
  end
123
127
 
124
128
  # define what happens when the measure is run
@@ -126,9 +130,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
126
130
  super(model, runner, user_arguments)
127
131
 
128
132
  # use the built-in error checking
129
- if !runner.validateUserArguments(arguments(model), user_arguments)
130
- return false
131
- end
133
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
132
134
 
133
135
  # assign the user inputs to variables
134
136
  design_flow_rate = runner.getDoubleArgumentValue('design_flow_rate', user_arguments)
@@ -156,9 +158,11 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
156
158
  zone.spaces.each do |space|
157
159
  space.surfaces.each do |surface|
158
160
  next if surface.surfaceType != 'Wall'
161
+
159
162
  surface.subSurfaces.each do |sub_surface|
160
163
  next if sub_surface.outsideBoundaryCondition != 'Outdoors'
161
164
  next if sub_surface.subSurfaceType != 'OperableWindow'
165
+
162
166
  zone_area_counter += sub_surface.netArea * sub_surface.multiplier
163
167
  end
164
168
  end
@@ -169,6 +173,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
169
173
 
170
174
  # add to operable_ext_window_hash if non-zero area
171
175
  next if zone_area_counter == 0.0
176
+
172
177
  bldg_area_counter += zone_area_counter
173
178
  operable_ext_window_hash[zone] = zone_area_counter
174
179
  end
@@ -226,9 +231,10 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
226
231
  until found_path_end == true
227
232
  found_ceiling = false
228
233
  path_objects[current_zone].each do |object|
229
- next if zones_used_for_this_path.include? (object[0])
234
+ next if zones_used_for_this_path.include?(object[0])
230
235
  next if object[1].to_s != 'RoofCeiling'
231
- next if operable_ext_window_hash.include? (object[0])
236
+ next if operable_ext_window_hash.include?(object[0])
237
+
232
238
  if found_ceiling
233
239
  runner.registerWarning("Found more than one possible airflow path for #{current_zone.name}")
234
240
  else
@@ -238,12 +244,13 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
238
244
  found_ceiling = true
239
245
  end
240
246
  end
241
- if !found_ceiling
247
+ unless found_ceiling
242
248
  found_wall = false
243
249
  path_objects[current_zone].each do |object|
244
- next if zones_used_for_this_path.include? (object[0])
250
+ next if zones_used_for_this_path.include?(object[0])
245
251
  next if object[1].to_s != 'Wall'
246
- next if operable_ext_window_hash.include? (object[0])
252
+ next if operable_ext_window_hash.include?(object[0])
253
+
247
254
  if found_wall
248
255
  runner.registerWarning("Found more than one possible airflow path for #{current_zone.name}")
249
256
  else
@@ -254,9 +261,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
254
261
  end
255
262
  end
256
263
  end
257
- if (found_ceiling == false) && (found_wall == false)
258
- found_path_end = true
259
- end
264
+ found_path_end = true if (found_ceiling == false) && (found_wall == false)
260
265
  end
261
266
 
262
267
  # add one way air mixing objects along path zones
@@ -290,14 +295,12 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
290
295
  else
291
296
  exhaust_zones[flow_paths[zone].last] = fraction_flow
292
297
  end
293
- else
298
+ elsif exhaust_zones.include? zone
294
299
  # extra code if there is no path from entry zone
295
- if exhaust_zones.include? zone
296
- exhaust_zones[zone] += fraction_flow
297
- else
298
- exhaust_zones[zone] = fraction_flow
299
- runner.registerWarning("#{zone.name} doesn't have path to other zones. Exhaust assumed to be with the same zone as air enters.")
300
- end
300
+ exhaust_zones[zone] += fraction_flow
301
+ else
302
+ exhaust_zones[zone] = fraction_flow
303
+ runner.registerWarning("#{zone.name} doesn't have path to other zones. Exhaust assumed to be with the same zone as air enters.")
301
304
  end
302
305
  end
303
306
 
@@ -316,11 +319,9 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
316
319
  # warn if zone multiplier are used
317
320
  non_default_multiplier = []
318
321
  model.getThermalZones.each do |zone|
319
- if zone.multiplier > 1
320
- non_default_multiplier << zone
321
- end
322
+ non_default_multiplier << zone if zone.multiplier > 1
322
323
  end
323
- if !non_default_multiplier.empty?
324
+ unless non_default_multiplier.empty?
324
325
  runner.registerWarning("This measure is not intended to be use when thermal zones have a non 1 multiplier. #{non_default_multiplier.size} zones in this model have multipliers greater than one. Results are likley invalid.")
325
326
  end
326
327
 
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.1</schema_version>
4
4
  <name>fan_assist_night_ventilation</name>
5
5
  <uid>5e77cb11-0fa9-432b-97bd-87c40949ee1f</uid>
6
- <version_id>a06b24ea-d2cf-4397-931c-4d72ff15fce1</version_id>
7
- <version_modified>2025-08-08T15:15:58Z</version_modified>
6
+ <version_id>9faaaac3-a100-4359-9833-cbef50454be7</version_id>
7
+ <version_modified>2025-09-25T15:33:44Z</version_modified>
8
8
  <xml_checksum>26DDAE64</xml_checksum>
9
9
  <class_name>FanAssistNightVentilation</class_name>
10
10
  <display_name>Fan Assist Night Ventilation</display_name>
@@ -118,7 +118,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
118
118
  <filename>measure.rb</filename>
119
119
  <filetype>rb</filetype>
120
120
  <usage_type>script</usage_type>
121
- <checksum>E96BE908</checksum>
121
+ <checksum>3DBADD15</checksum>
122
122
  </file>
123
123
  <file>
124
124
  <filename>FanAssistNightVentilationMeasureGuide.pdf</filename>
@@ -154,7 +154,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
154
154
  <filename>fan_assist_night_ventilation_test.rb</filename>
155
155
  <filetype>rb</filetype>
156
156
  <usage_type>test</usage_type>
157
- <checksum>CC3F5531</checksum>
157
+ <checksum>8F5F0305</checksum>
158
158
  </file>
159
159
  <file>
160
160
  <filename>no_opp_win.osm</filename>
@@ -9,26 +9,26 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
9
9
  require 'openstudio-standards'
10
10
 
11
11
  def name
12
- return 'NZEHVAC'
12
+ 'NZEHVAC'
13
13
  end
14
14
 
15
15
  # human readable description
16
16
  def description
17
- return 'This measure replaces the existing HVAC system if any with the user selected HVAC system. The user can select how to partition the system, applying it to the whole building, a system per building type, a system per building story, or automatically partition based on residential/non-residential occupany types and space loads.'
17
+ 'This measure replaces the existing HVAC system if any with the user selected HVAC system. The user can select how to partition the system, applying it to the whole building, a system per building type, a system per building story, or automatically partition based on residential/non-residential occupany types and space loads.'
18
18
  end
19
19
 
20
20
  # human readable description of modeling approach
21
21
  def modeler_description
22
- return 'HVAC system creation logic uses [openstudio-standards](https://github.com/NREL/openstudio-standards) and efficiency values are defined in the openstudio-standards Standards spreadsheet under the *NREL ZNE Ready 2017* template.'
22
+ 'HVAC system creation logic uses [openstudio-standards](https://github.com/NREL/openstudio-standards) and efficiency values are defined in the openstudio-standards Standards spreadsheet under the *NREL ZNE Ready 2017* template.'
23
23
  end
24
24
 
25
25
  def add_system_to_zones(model, runner, hvac_system_type, zones, standard,
26
26
  doas_dcv: false)
27
- if doas_dcv
28
- doas_system_type = 'DOAS with DCV'
29
- else
30
- doas_system_type = 'DOAS'
31
- end
27
+ doas_system_type = if doas_dcv
28
+ 'DOAS with DCV'
29
+ else
30
+ 'DOAS'
31
+ end
32
32
 
33
33
  # create HVAC system
34
34
  # use methods in openstudio-standards
@@ -129,7 +129,8 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
129
129
  standard.model_add_hvac_system(model, doas_system_type, ht = 'AirSourceHeatPump', znht = nil, cl = 'Electricity', zones,
130
130
  air_loop_heating_type: 'Water',
131
131
  air_loop_cooling_type: 'Water')
132
- standard.model_add_hvac_system(model, 'Radiant Slab', ht = 'AirSourceHeatPump', znht = nil, cl = 'Electricity', zones)
132
+ standard.model_add_hvac_system(model, 'Radiant Slab', ht = 'AirSourceHeatPump', znht = nil, cl = 'Electricity',
133
+ zones)
133
134
  chilled_water_loop = model.getPlantLoopByName('Chilled Water Loop').get
134
135
  condenser_water_loop = model.getPlantLoopByName('Condenser Water Loop').get
135
136
  standard.model_add_waterside_economizer(model, chilled_water_loop, condenser_water_loop,
@@ -184,7 +185,8 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
184
185
  heat_pump_loop_cooling_type: 'CoolingTower')
185
186
 
186
187
  when 'Water source heat pumps with ground source heat pump'
187
- standard.model_add_hvac_system(model, 'Ground Source Heat Pumps', ht = 'Electricity', znht = nil, cl = 'Electricity', zones)
188
+ standard.model_add_hvac_system(model, 'Ground Source Heat Pumps', ht = 'Electricity', znht = nil,
189
+ cl = 'Electricity', zones)
188
190
 
189
191
  # PVAV systems by default use a DX coil for cooling
190
192
  when 'PVAV with gas boiler reheat'
@@ -192,7 +194,8 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
192
194
  hot_water_loop_type: 'LowTemperature')
193
195
 
194
196
  when 'PVAV with central air source heat pump reheat'
195
- standard.model_add_hvac_system(model, 'PVAV Reheat', ht = 'AirSourceHeatPump', znht = 'AirSourceHeatPump', cl = 'Electricity', zones)
197
+ standard.model_add_hvac_system(model, 'PVAV Reheat', ht = 'AirSourceHeatPump', znht = 'AirSourceHeatPump',
198
+ cl = 'Electricity', zones)
196
199
 
197
200
  when 'VAV chiller with gas boiler reheat'
198
201
  standard.model_add_hvac_system(model, 'VAV Reheat', ht = 'NaturalGas', znht = 'NaturalGas', cl = 'Electricity', zones,
@@ -203,7 +206,8 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
203
206
  integrated: true)
204
207
 
205
208
  when 'VAV chiller with central air source heat pump reheat'
206
- standard.model_add_hvac_system(model, 'VAV Reheat', ht = 'AirSourceHeatPump', znht = 'AirSourceHeatPump', cl = 'Electricity', zones)
209
+ standard.model_add_hvac_system(model, 'VAV Reheat', ht = 'AirSourceHeatPump', znht = 'AirSourceHeatPump',
210
+ cl = 'Electricity', zones)
207
211
  chilled_water_loop = model.getPlantLoopByName('Chilled Water Loop').get
208
212
  condenser_water_loop = model.getPlantLoopByName('Condenser Water Loop').get
209
213
  standard.model_add_waterside_economizer(model, chilled_water_loop, condenser_water_loop,
@@ -227,7 +231,7 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
227
231
  runner.registerInfo("Added HVAC System type #{hvac_system_type} to the model for #{zones.size} zones")
228
232
  end
229
233
 
230
- def arguments(model)
234
+ def arguments(_model)
231
235
  args = OpenStudio::Measure::OSArgumentVector.new
232
236
 
233
237
  # argument to remove existing hvac system
@@ -263,7 +267,8 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
263
267
  hvac_system_type_choices << 'PVAV with gas boiler reheat'
264
268
  hvac_system_type_choices << 'PVAV with central air source heat pump reheat'
265
269
 
266
- hvac_system_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('hvac_system_type', hvac_system_type_choices, true)
270
+ hvac_system_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('hvac_system_type', hvac_system_type_choices,
271
+ true)
267
272
  hvac_system_type.setDisplayName('HVAC System Type:')
268
273
  hvac_system_type.setDescription('Details on HVAC system type in measure documentation.')
269
274
  hvac_system_type.setDefaultValue('DOAS with fan coil chiller with central air source heat pump')
@@ -283,7 +288,8 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
283
288
  hvac_system_partition_choices << 'One System Per Building Story'
284
289
  hvac_system_partition_choices << 'One System Per Building Type'
285
290
 
286
- hvac_system_partition = OpenStudio::Measure::OSArgument.makeChoiceArgument('hvac_system_partition', hvac_system_partition_choices, true)
291
+ hvac_system_partition = OpenStudio::Measure::OSArgument.makeChoiceArgument('hvac_system_partition',
292
+ hvac_system_partition_choices, true)
287
293
  hvac_system_partition.setDisplayName('HVAC System Partition:')
288
294
  hvac_system_partition.setDescription('Automatic Partition will separate the HVAC system by residential/non-residential and if loads and schedules are substantially different.')
289
295
  hvac_system_partition.setDefaultValue('Automatic Partition')
@@ -291,16 +297,14 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
291
297
 
292
298
  # add an argument for ventilation schedule
293
299
 
294
- return args
300
+ args
295
301
  end # end the arguments method
296
302
 
297
303
  def run(model, runner, user_arguments)
298
304
  super(model, runner, user_arguments)
299
305
 
300
306
  # use the built-in error checking
301
- if !runner.validateUserArguments(arguments(model), user_arguments)
302
- return false
303
- end
307
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
304
308
 
305
309
  # assign user inputs
306
310
  remove_existing_hvac = runner.getBoolArgumentValue('remove_existing_hvac', user_arguments)
@@ -325,9 +329,7 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
325
329
 
326
330
  # get the climate zone
327
331
  climate_zone_obj = model.getClimateZones.getClimateZone('ASHRAE', 2006)
328
- if climate_zone_obj.empty
329
- climate_zone_obj = model.getClimateZones.getClimateZone('ASHRAE', 2013)
330
- end
332
+ climate_zone_obj = model.getClimateZones.getClimateZone('ASHRAE', 2013) if climate_zone_obj.empty
331
333
 
332
334
  if climate_zone_obj.empty
333
335
  runner.registerError('Please assign an ASHRAE climate zone to the model before running the measure.')
@@ -346,59 +348,67 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
346
348
  conditioned_zones = []
347
349
  model.getThermalZones.each do |zone|
348
350
  next if OpenstudioStandards::ThermalZone.thermal_zone_plenum?(zone)
349
- next if !OpenstudioStandards::ThermalZone.thermal_zone_heated?(zone) && !OpenstudioStandards::ThermalZone.thermal_zone_cooled?(zone)
351
+ if !OpenstudioStandards::ThermalZone.thermal_zone_heated?(zone) && !OpenstudioStandards::ThermalZone.thermal_zone_cooled?(zone)
352
+ next
353
+ end
354
+
350
355
  conditioned_zones << zone
351
356
  end
352
357
 
353
358
  # logic to partition thermal zones to be served by different HVAC systems
354
359
  case hvac_system_partition
355
360
 
356
- when 'Automatic Partition'
357
- # group zones by occupancy type (residential/nonresidential)
358
- # split non-dominant groups if their total area exceeds 20,000 ft2.
359
- sys_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_occupancy_type(model, min_area_m2: OpenStudio.convert(20000, 'ft^2', 'm^2').get)
360
-
361
- # assume secondary system type is PSZ-AC for VAV Reheat otherwise assume same hvac system type
362
- sec_sys_type = hvac_system_type # same as primary system type
363
- sec_sys_type = 'PSZ-HP' if (hvac_system_type.to_s == 'VAV Reheat') || (hvac_system_type.to_s == 'PVAV Reheat')
364
-
365
- sys_groups.each do |sys_group|
366
- # add the primary system to the primary zones and the secondary system to any zones that are different
367
- # differentiate primary and secondary zones based on operating hours and internal loads (same as 90.1 PRM)
368
- pri_sec_zone_lists = std.model_differentiate_primary_secondary_thermal_zones(model, sys_group['zones'])
369
-
370
- # add the primary system to the primary zones
371
- add_system_to_zones(model, runner, hvac_system_type, pri_sec_zone_lists['primary'], std, doas_dcv: doas_dcv)
372
-
373
- # add the secondary system to the secondary zones (if any)
374
- if !pri_sec_zone_lists['secondary'].empty?
375
- runner.registerInfo("Secondary system type is #{sec_sys_type}")
376
- add_system_to_zones(model, runner, sec_sys_type, pri_sec_zone_lists['secondary'], std, doas_dcv: doas_dcv)
377
- end
361
+ when 'Automatic Partition'
362
+ # group zones by occupancy type (residential/nonresidential)
363
+ # split non-dominant groups if their total area exceeds 20,000 ft2.
364
+ sys_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_occupancy_type(model,
365
+ min_area_m2: OpenStudio.convert(
366
+ 20_000, 'ft^2', 'm^2'
367
+ ).get)
368
+
369
+ # assume secondary system type is PSZ-AC for VAV Reheat otherwise assume same hvac system type
370
+ sec_sys_type = hvac_system_type # same as primary system type
371
+ sec_sys_type = 'PSZ-HP' if (hvac_system_type.to_s == 'VAV Reheat') || (hvac_system_type.to_s == 'PVAV Reheat')
372
+
373
+ sys_groups.each do |sys_group|
374
+ # add the primary system to the primary zones and the secondary system to any zones that are different
375
+ # differentiate primary and secondary zones based on operating hours and internal loads (same as 90.1 PRM)
376
+ pri_sec_zone_lists = std.model_differentiate_primary_secondary_thermal_zones(model, sys_group['zones'])
377
+
378
+ # add the primary system to the primary zones
379
+ add_system_to_zones(model, runner, hvac_system_type, pri_sec_zone_lists['primary'], std, doas_dcv: doas_dcv)
380
+
381
+ # add the secondary system to the secondary zones (if any)
382
+ unless pri_sec_zone_lists['secondary'].empty?
383
+ runner.registerInfo("Secondary system type is #{sec_sys_type}")
384
+ add_system_to_zones(model, runner, sec_sys_type, pri_sec_zone_lists['secondary'], std, doas_dcv: doas_dcv)
378
385
  end
386
+ end
379
387
 
380
- when 'Whole Building'
381
- add_system_to_zones(model, runner, hvac_system_type, conditioned_zones, std, doas_dcv: doas_dcv)
388
+ when 'Whole Building'
389
+ add_system_to_zones(model, runner, hvac_system_type, conditioned_zones, std, doas_dcv: doas_dcv)
382
390
 
383
- when 'One System Per Building Story'
384
- story_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_building_story(model, conditioned_zones)
385
- story_groups.each do |story_zones|
386
- add_system_to_zones(model, runner, hvac_system_type, story_zones, std, doas_dcv: doas_dcv)
387
- end
391
+ when 'One System Per Building Story'
392
+ story_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_building_story(model,
393
+ conditioned_zones)
394
+ story_groups.each do |story_zones|
395
+ add_system_to_zones(model, runner, hvac_system_type, story_zones, std, doas_dcv: doas_dcv)
396
+ end
388
397
 
389
- when 'One System Per Building Type'
390
- system_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_building_type(model, min_area_m2: 0.0)
391
- system_groups.each do |system_group|
392
- add_system_to_zones(model, runner, hvac_system_type, system_group['zones'], std, doas_dcv: doas_dcv)
393
- end
398
+ when 'One System Per Building Type'
399
+ system_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_building_type(model,
400
+ min_area_m2: 0.0)
401
+ system_groups.each do |system_group|
402
+ add_system_to_zones(model, runner, hvac_system_type, system_group['zones'], std, doas_dcv: doas_dcv)
403
+ end
394
404
 
395
- else
396
- runner.registerError('Invalid HVAC system partition choice')
397
- return false
405
+ else
406
+ runner.registerError('Invalid HVAC system partition choice')
407
+ return false
398
408
  end
399
409
 
400
410
  # check that weather file exists for a sizing run
401
- if !model.weatherFile.is_initialized
411
+ unless model.weatherFile.is_initialized
402
412
  runner.registerError('Weather file not set. Cannot perform sizing run.')
403
413
  return false
404
414
  end
@@ -429,7 +439,7 @@ class NzeHvac < OpenStudio::Measure::ModelMeasure
429
439
 
430
440
  runner.registerFinalCondition("Added system type #{hvac_system_type} to model.")
431
441
 
432
- return true
442
+ true
433
443
  end # end the run method
434
444
  end # end the measure
435
445
 
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.1</schema_version>
4
4
  <name>nze_hvac</name>
5
5
  <uid>f060dff2-b28d-4194-a6af-e66d88ddb33c</uid>
6
- <version_id>c7353d7f-49d4-4c3b-bce8-f0e6434a8af5</version_id>
7
- <version_modified>2025-08-08T15:15:56Z</version_modified>
6
+ <version_id>9ef6d0b1-beec-4b75-a119-476f2fa9d4dc</version_id>
7
+ <version_modified>2025-09-25T16:10:25Z</version_modified>
8
8
  <xml_checksum>0E5E4776</xml_checksum>
9
9
  <class_name>NzeHvac</class_name>
10
10
  <display_name>NZEHVAC</display_name>
@@ -258,13 +258,13 @@
258
258
  <filename>measure.rb</filename>
259
259
  <filetype>rb</filetype>
260
260
  <usage_type>script</usage_type>
261
- <checksum>7EAD5ED0</checksum>
261
+ <checksum>59C247DF</checksum>
262
262
  </file>
263
263
  <file>
264
264
  <filename>NZEHVAC_Test.rb</filename>
265
265
  <filetype>rb</filetype>
266
266
  <usage_type>test</usage_type>
267
- <checksum>4522D5A7</checksum>
267
+ <checksum>721443D9</checksum>
268
268
  </file>
269
269
  <file>
270
270
  <filename>USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw</filename>
@@ -12,21 +12,21 @@
12
12
  class ReplaceWaterHeaterMixedWithThermalStorageChilledWater < OpenStudio::Measure::ModelMeasure
13
13
  # human readable name
14
14
  def name
15
- return 'Replace Water Heater Mixed with Thermal Storage Chilled Water'
15
+ 'Replace Water Heater Mixed with Thermal Storage Chilled Water'
16
16
  end
17
17
 
18
18
  # human readable description
19
19
  def description
20
- return 'This measure is a quick fix for GUI issue that prevents putting thermal storage on two plant loops.'
20
+ 'This measure is a quick fix for GUI issue that prevents putting thermal storage on two plant loops.'
21
21
  end
22
22
 
23
23
  # human readable description of modeling approach
24
24
  def modeler_description
25
- return 'The model in this case used a water heater mixed as a place holder. This measure will take a string argument, and will replace the water heater with a new thermal storage chilled water object.'
25
+ 'The model in this case used a water heater mixed as a place holder. This measure will take a string argument, and will replace the water heater with a new thermal storage chilled water object.'
26
26
  end
27
27
 
28
28
  # define the arguments that the user will input
29
- def arguments(model)
29
+ def arguments(_model)
30
30
  args = OpenStudio::Measure::OSArgumentVector.new
31
31
 
32
32
  # the name of the water heater to replace
@@ -36,7 +36,7 @@ class ReplaceWaterHeaterMixedWithThermalStorageChilledWater < OpenStudio::Measur
36
36
  wh_name.setDefaultValue('CHW Tank Placeholder')
37
37
  args << wh_name
38
38
 
39
- return args
39
+ args
40
40
  end
41
41
 
42
42
  # define what happens when the measure is run
@@ -44,9 +44,7 @@ class ReplaceWaterHeaterMixedWithThermalStorageChilledWater < OpenStudio::Measur
44
44
  super(model, runner, user_arguments)
45
45
 
46
46
  # use the built-in error checking
47
- if !runner.validateUserArguments(arguments(model), user_arguments)
48
- return false
49
- end
47
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
50
48
 
51
49
  # assign the user inputs to variables
52
50
  wh_name = runner.getStringArgumentValue('wh_name', user_arguments)
@@ -71,22 +69,21 @@ class ReplaceWaterHeaterMixedWithThermalStorageChilledWater < OpenStudio::Measur
71
69
  puts "Checking #{plant_loop.name}"
72
70
 
73
71
  plant_loop.supplyComponents.each do |component|
74
- if component.name.to_s == wh_name
75
- placeholder = component
76
- puts "found #{component.name}"
72
+ next unless component.name.to_s == wh_name
77
73
 
78
- # swap components
79
- supply_inlet_node = component.to_WaterToWaterComponent.get.supplyInletModelObject.get.to_Node.get
80
- new_chilled_water.addToNode(supply_inlet_node)
81
- demand_inlet_node = component.to_WaterToWaterComponent.get.demandInletModelObject.get.to_Node.get
82
- new_chilled_water.addToNode(demand_inlet_node)
74
+ placeholder = component
75
+ puts "found #{component.name}"
83
76
 
84
- end
77
+ # swap components
78
+ supply_inlet_node = component.to_WaterToWaterComponent.get.supplyInletModelObject.get.to_Node.get
79
+ new_chilled_water.addToNode(supply_inlet_node)
80
+ demand_inlet_node = component.to_WaterToWaterComponent.get.demandInletModelObject.get.to_Node.get
81
+ new_chilled_water.addToNode(demand_inlet_node)
85
82
  end
86
83
  end
87
84
 
88
85
  # remove unused water heater from the model
89
- if !placeholder.nil?
86
+ unless placeholder.nil?
90
87
  puts 'Removing water heater'
91
88
  placeholder.remove
92
89
  end
@@ -94,7 +91,7 @@ class ReplaceWaterHeaterMixedWithThermalStorageChilledWater < OpenStudio::Measur
94
91
  # report final condition of model
95
92
  runner.registerFinalCondition("The building finished with #{model.getThermalStorageChilledWaterStratifieds.size} chilled water objects.")
96
93
 
97
- return true
94
+ true
98
95
  end
99
96
  end
100
97
 
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.1</schema_version>
4
4
  <name>replace_water_heater_mixed_with_thermal_storage_chilled_water</name>
5
5
  <uid>f219279a-96b6-4636-b663-467d554e42a5</uid>
6
- <version_id>d2c093ca-564f-45ac-bdfe-092b87465cac</version_id>
7
- <version_modified>2025-08-08T15:15:56Z</version_modified>
6
+ <version_id>82185c9d-bc51-49c0-91f5-4960d1473438</version_id>
7
+ <version_modified>2025-09-25T15:33:42Z</version_modified>
8
8
  <xml_checksum>D48F381B</xml_checksum>
9
9
  <class_name>ReplaceWaterHeaterMixedWithThermalStorageChilledWater</class_name>
10
10
  <display_name>Replace Water Heater Mixed with Thermal Storage Chilled Water</display_name>
@@ -76,7 +76,7 @@
76
76
  <filename>measure.rb</filename>
77
77
  <filetype>rb</filetype>
78
78
  <usage_type>script</usage_type>
79
- <checksum>A4A9061E</checksum>
79
+ <checksum>65E821E7</checksum>
80
80
  </file>
81
81
  <file>
82
82
  <filename>WaterHeaterToChilledWater.osm</filename>
@@ -88,7 +88,7 @@
88
88
  <filename>replace_water_heater_mixed_with_thermal_storage_chilled_water_test.rb</filename>
89
89
  <filetype>rb</filetype>
90
90
  <usage_type>test</usage_type>
91
- <checksum>8ECE4661</checksum>
91
+ <checksum>38E71E0B</checksum>
92
92
  </file>
93
93
  </files>
94
94
  </measure>
@@ -0,0 +1,14 @@
1
+ OpenStudio(R), Copyright (c) 2008, 2025 Alliance for Sustainable Energy, LLC.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Redistribution of this software, without modification, must refer to the software by the same designation. Redistribution of a modified version of this software (i) may not refer to the modified version by the same designation, or by any confusingly similar designation, and (ii) must refer to the underlying software originally provided by Alliance as “OpenStudio®”. Except to comply with the foregoing, the term “OpenStudio®”, or any confusingly similar designation may not be used to refer to any modified version of this software or any modified version of the underlying software originally provided by Alliance without the prior written consent of Alliance.
10
+
11
+ 4. The name of the copyright holder(s), any contributors, the United States Government, the United States Department of Energy, or any of their employees may not be used to endorse or promote products derived from this software without specific prior written permission from the respective party.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
+