openstudio-ee 0.12.0 → 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.
- checksums.yaml +4 -4
- data/.coverage +0 -0
- data/.github/workflows/test-with-openstudio.yml +109 -74
- data/.gitignore +21 -0
- data/.rubocop.yml +15 -2
- data/CHANGELOG.md +12 -0
- data/Gemfile +7 -8
- data/README.md +5 -1
- data/WORKFLOW_CHANGES.md +74 -0
- data/doc_templates/LICENSE.md +1 -1
- data/lib/measures/AddDaylightSensors/measure.rb +79 -79
- data/lib/measures/AddDaylightSensors/measure.xml +5 -5
- data/lib/measures/AddOverhangsByProjectionFactor/measure.rb +38 -41
- data/lib/measures/AddOverhangsByProjectionFactor/measure.xml +5 -5
- data/lib/measures/EnableDemandControlledVentilation/measure.rb +37 -40
- data/lib/measures/EnableDemandControlledVentilation/measure.xml +5 -5
- data/lib/measures/EnableEconomizerControl/measure.rb +36 -37
- data/lib/measures/EnableEconomizerControl/measure.xml +5 -5
- data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.rb +27 -41
- data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.xml +5 -5
- data/lib/measures/GLHEProGFunctionImport/measure.rb +11 -15
- data/lib/measures/GLHEProGFunctionImport/measure.xml +5 -5
- data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.rb +5 -9
- data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.xml +4 -4
- data/lib/measures/ImproveFanBeltEfficiency/measure.rb +78 -95
- data/lib/measures/ImproveFanBeltEfficiency/measure.xml +7 -7
- data/lib/measures/ImproveMotorEfficiency/measure.rb +75 -100
- data/lib/measures/ImproveMotorEfficiency/measure.xml +7 -7
- data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.rb +137 -130
- data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.xml +5 -5
- data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.rb +114 -115
- data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.xml +4 -4
- data/lib/measures/IncreaseInsulationRValueForRoofs/measure.rb +137 -130
- data/lib/measures/IncreaseInsulationRValueForRoofs/measure.xml +5 -5
- data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.rb +114 -115
- data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.xml +4 -4
- data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.rb +69 -63
- data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.xml +7 -7
- data/lib/measures/ReduceLightingLoadsByPercentage/measure.rb +77 -66
- data/lib/measures/ReduceLightingLoadsByPercentage/measure.xml +7 -7
- data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.rb +45 -43
- data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.xml +5 -5
- data/lib/measures/ReduceNightTimeLightingLoads/measure.rb +45 -43
- data/lib/measures/ReduceNightTimeLightingLoads/measure.xml +5 -5
- data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.rb +58 -52
- data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.xml +7 -7
- data/lib/measures/ReduceVentilationByPercentage/measure.rb +49 -46
- data/lib/measures/ReduceVentilationByPercentage/measure.xml +7 -7
- data/lib/measures/add_variable_speed_rtu_control_logic/measure.rb +31 -23
- data/lib/measures/add_variable_speed_rtu_control_logic/measure.xml +5 -5
- data/lib/measures/create_variable_speed_rtu/measure.rb +166 -174
- data/lib/measures/create_variable_speed_rtu/measure.xml +7 -7
- data/lib/measures/fan_assist_night_ventilation/measure.rb +33 -32
- data/lib/measures/fan_assist_night_ventilation/measure.xml +5 -5
- data/lib/measures/nze_hvac/measure.rb +72 -62
- data/lib/measures/nze_hvac/measure.xml +5 -5
- data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.rb +16 -19
- data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.xml +5 -5
- data/lib/measures/window_enhancement/LICENSE.md +14 -0
- data/lib/measures/window_enhancement/README.md +112 -0
- data/lib/measures/window_enhancement/docs/.gitkeep +0 -0
- data/lib/measures/window_enhancement/measure.py +386 -0
- data/lib/measures/window_enhancement/measure.xml +128 -0
- data/lib/measures/window_enhancement/resources/EC3_lookup.py +321 -0
- data/lib/measures/window_enhancement/resources/Test_API.py +32 -0
- data/lib/measures/window_enhancement/resources/__pycache__/EC3_lookup.cpython-39.pyc +0 -0
- data/lib/measures/window_enhancement/resources/__pycache__/Original_EC3_lookup.py +322 -0
- data/lib/measures/window_enhancement/resources/__pycache__/Test_API.cpython-39.pyc +0 -0
- data/lib/measures/window_enhancement/resources/calculate_perimeter.py +39 -0
- data/lib/measures/window_enhancement/test_output.log +39 -0
- data/lib/openstudio/ee_measures/version.rb +1 -1
- data/openstudio-ee.gemspec +11 -9
- data/test-workflow-locally.sh +152 -0
- metadata +66 -37
- 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
|
-
|
15
|
+
'Fan Assist Night Ventilation'
|
16
16
|
end
|
17
17
|
|
18
18
|
# human readable description
|
19
19
|
def description
|
20
|
-
|
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
|
-
|
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',
|
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
|
-
|
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
|
101
|
-
next
|
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
|
110
|
-
next
|
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
|
-
|
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
|
-
|
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?
|
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?
|
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
|
-
|
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?
|
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?
|
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
|
-
|
298
|
+
elsif exhaust_zones.include? zone
|
294
299
|
# extra code if there is no path from entry zone
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
-
|
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>
|
7
|
-
<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>
|
@@ -95,7 +95,7 @@ To address an issue in OpenStudio zones with ZoneVentilation, this measure adds
|
|
95
95
|
<filename>LICENSE.md</filename>
|
96
96
|
<filetype>md</filetype>
|
97
97
|
<usage_type>license</usage_type>
|
98
|
-
<checksum>
|
98
|
+
<checksum>FFCBFF29</checksum>
|
99
99
|
</file>
|
100
100
|
<file>
|
101
101
|
<filename>README.md</filename>
|
@@ -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>
|
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>
|
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
|
-
|
12
|
+
'NZEHVAC'
|
13
13
|
end
|
14
14
|
|
15
15
|
# human readable description
|
16
16
|
def description
|
17
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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',
|
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,
|
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',
|
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',
|
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(
|
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,
|
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',
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
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
|
-
|
381
|
-
|
388
|
+
when 'Whole Building'
|
389
|
+
add_system_to_zones(model, runner, hvac_system_type, conditioned_zones, std, doas_dcv: doas_dcv)
|
382
390
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
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
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
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
|
-
|
396
|
-
|
397
|
-
|
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
|
-
|
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
|
-
|
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>
|
7
|
-
<version_modified>2025-
|
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>
|
@@ -235,7 +235,7 @@
|
|
235
235
|
<filename>LICENSE.md</filename>
|
236
236
|
<filetype>md</filetype>
|
237
237
|
<usage_type>license</usage_type>
|
238
|
-
<checksum>
|
238
|
+
<checksum>FFCBFF29</checksum>
|
239
239
|
</file>
|
240
240
|
<file>
|
241
241
|
<filename>README.md</filename>
|
@@ -258,13 +258,13 @@
|
|
258
258
|
<filename>measure.rb</filename>
|
259
259
|
<filetype>rb</filetype>
|
260
260
|
<usage_type>script</usage_type>
|
261
|
-
<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>
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
75
|
-
placeholder = component
|
76
|
-
puts "found #{component.name}"
|
72
|
+
next unless component.name.to_s == wh_name
|
77
73
|
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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>
|
7
|
-
<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>
|
@@ -53,7 +53,7 @@
|
|
53
53
|
<filename>LICENSE.md</filename>
|
54
54
|
<filetype>md</filetype>
|
55
55
|
<usage_type>license</usage_type>
|
56
|
-
<checksum>
|
56
|
+
<checksum>FFCBFF29</checksum>
|
57
57
|
</file>
|
58
58
|
<file>
|
59
59
|
<filename>README.md</filename>
|
@@ -76,7 +76,7 @@
|
|
76
76
|
<filename>measure.rb</filename>
|
77
77
|
<filetype>rb</filetype>
|
78
78
|
<usage_type>script</usage_type>
|
79
|
-
<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>
|
91
|
+
<checksum>38E71E0B</checksum>
|
92
92
|
</file>
|
93
93
|
</files>
|
94
94
|
</measure>
|