honeybee-openstudio 2.31.0 → 2.31.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/honeybee-openstudio.gemspec +1 -1
- data/lib/from_openstudio/geometry/room.rb +35 -13
- data/lib/from_openstudio/load/service_hot_water.rb +57 -0
- data/lib/from_openstudio.rb +1 -0
- data/lib/honeybee/_defaults/model.json +21 -41
- data/lib/honeybee/model.rb +1 -0
- data/lib/to_openstudio/geometry/aperture.rb +1 -1
- data/lib/to_openstudio/geometry/door.rb +1 -1
- data/lib/to_openstudio/hvac/radiant.rb +385 -11
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c847b160e1187c597ff518d513e30b259c15b069d5023e34cf7167aecdad270f
|
4
|
+
data.tar.gz: 8776b858a13b15deff5ceabc6738ab851915d28875fd539fc081b035fe4a9c46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c13aaea4ba8a471519a7ef3971ac07dbe16a24c07420af232e68ba6f0845557bc48b726aad9ef2e2bdba5e55fe8713aaf14516af1a82f1e24edf9ee75e5d58b8
|
7
|
+
data.tar.gz: d0b1b82283d64f41247b4cac1263f98ef74e909b556534779b2cff42d014c86ec3587a9bbafbbce0d5e09673858d593e1a90c9ede3da79d44bfe1e18c355b8da
|
data/honeybee-openstudio.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'honeybee-openstudio'
|
7
|
-
spec.version = '2.31.
|
7
|
+
spec.version = '2.31.4'
|
8
8
|
spec.authors = ['Tanushree Charan', 'Dan Macumber', 'Chris Mackey', 'Mostapha Sadeghipour Roudsari']
|
9
9
|
spec.email = ['tanushree.charan@nrel.gov', 'chris@ladybug.tools']
|
10
10
|
|
@@ -85,7 +85,8 @@ module Honeybee
|
|
85
85
|
space_type = space.spaceType.get
|
86
86
|
hash[:program_type] = space_type.nameString
|
87
87
|
end
|
88
|
-
# TODO: These are loads assigned to the space directly.
|
88
|
+
# TODO: These are only loads assigned to the space directly.
|
89
|
+
# Duplicates and other definitions created in programtype should be summed
|
89
90
|
unless space.people.empty?
|
90
91
|
space.people.each do |people|
|
91
92
|
people_def = people.peopleDefinition
|
@@ -100,6 +101,7 @@ module Honeybee
|
|
100
101
|
end
|
101
102
|
end
|
102
103
|
end
|
104
|
+
|
103
105
|
unless space.lights.empty?
|
104
106
|
space.lights.each do |light|
|
105
107
|
light_def = light.lightsDefinition
|
@@ -114,6 +116,7 @@ module Honeybee
|
|
114
116
|
end
|
115
117
|
end
|
116
118
|
end
|
119
|
+
|
117
120
|
unless space.electricEquipment.empty?
|
118
121
|
space.electricEquipment.each do |electric_eq|
|
119
122
|
electric_eq_def = electric_eq.electricEquipmentDefinition
|
@@ -128,6 +131,7 @@ module Honeybee
|
|
128
131
|
end
|
129
132
|
end
|
130
133
|
end
|
134
|
+
|
131
135
|
unless space.gasEquipment.empty?
|
132
136
|
space.gasEquipment.each do |gas_eq|
|
133
137
|
gas_eq_def = gas_eq.gasEquipmentDefinition
|
@@ -142,18 +146,7 @@ module Honeybee
|
|
142
146
|
end
|
143
147
|
end
|
144
148
|
end
|
145
|
-
|
146
|
-
hash[:process_loads] = []
|
147
|
-
space.otherEquipment.each do |other_eq|
|
148
|
-
other_eq_def = other_eq.otherEquipmentDefinition
|
149
|
-
if !other_eq_def.designLevel.empty? && !other_eq.schedule.empty?
|
150
|
-
sch = other_eq.schedule.get
|
151
|
-
if sch.to_ScheduleRuleset.is_initialized or sch.to_ScheduleFixedInterval.is_initialized
|
152
|
-
hash[:process_loads] << Honeybee::ProcessAbridged.from_load(other_eq)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
149
|
+
|
157
150
|
unless space.spaceInfiltrationDesignFlowRates.empty?
|
158
151
|
space.spaceInfiltrationDesignFlowRates.each do |infiltration|
|
159
152
|
# Only translate if flow per exterior area is specified
|
@@ -167,12 +160,41 @@ module Honeybee
|
|
167
160
|
end
|
168
161
|
end
|
169
162
|
end
|
163
|
+
|
164
|
+
unless space.waterUseEquipment.empty?
|
165
|
+
space.waterUseEquipment.each do |shw_equipment|
|
166
|
+
# Check if schedule exists and is of the correct type
|
167
|
+
unless shw_equipment.flowRateFractionSchedule.empty?
|
168
|
+
sch = shw_equipment.flowRateFractionSchedule.get
|
169
|
+
if sch.to_ScheduleRuleset.is_initialized or sch.to_ScheduleFixedInterval.is_initialized
|
170
|
+
floor_area = space.floorArea # Get floor area
|
171
|
+
hash[:service_hot_water] = Honeybee::ServiceHotWaterAbridged.from_load(shw_equipment, floor_area)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
170
177
|
unless space.designSpecificationOutdoorAir.empty?
|
171
178
|
hash[:ventilation] = Honeybee::VentilationAbridged.from_load(space.designSpecificationOutdoorAir.get)
|
172
179
|
end
|
180
|
+
|
181
|
+
unless space.otherEquipment.empty?
|
182
|
+
hash[:process_loads] = []
|
183
|
+
space.otherEquipment.each do |other_eq|
|
184
|
+
other_eq_def = other_eq.otherEquipmentDefinition
|
185
|
+
if !other_eq_def.designLevel.empty? && !other_eq.schedule.empty?
|
186
|
+
sch = other_eq.schedule.get
|
187
|
+
if sch.to_ScheduleRuleset.is_initialized or sch.to_ScheduleFixedInterval.is_initialized
|
188
|
+
hash[:process_loads] << Honeybee::ProcessAbridged.from_load(other_eq)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
173
194
|
unless space.daylightingControls.empty?
|
174
195
|
hash[:daylighting_control] = Honeybee::DaylightingControl.from_load(space.daylightingControls[0])
|
175
196
|
end
|
197
|
+
|
176
198
|
thermal_zone = space.thermalZone
|
177
199
|
unless thermal_zone.empty?
|
178
200
|
thermal_zone = space.thermalZone.get
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# Honeybee OpenStudio Gem, Copyright (c) 2020, Alliance for Sustainable
|
3
|
+
# Energy, LLC, Ladybug Tools LLC and other contributors. All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
#
|
11
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
#
|
15
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
16
|
+
# may be used to endorse or promote products derived from this software without
|
17
|
+
# specific prior written permission from the respective party.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
20
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
21
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
23
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
24
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
25
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
26
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
27
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
28
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
29
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
# *******************************************************************************
|
31
|
+
|
32
|
+
require 'honeybee/load/service_hot_water'
|
33
|
+
require 'to_openstudio/model_object'
|
34
|
+
|
35
|
+
module Honeybee
|
36
|
+
class ServiceHotWaterAbridged
|
37
|
+
|
38
|
+
def self.from_load(load, floor_area)
|
39
|
+
# create an empty hash
|
40
|
+
hash = {}
|
41
|
+
hash[:type] = 'ServiceHotWaterAbridged'
|
42
|
+
# set hash values from OpenStudio Object
|
43
|
+
hash[:identifier] = clean_name(load.nameString)
|
44
|
+
load_def = load.waterUseEquipmentDefinition
|
45
|
+
# units of peak flow rate are m3/s
|
46
|
+
peak_flow = load_def.peakFlowRate
|
47
|
+
# unit for flow per area is L/h-m2 (m3/s = 3600000 L/h)
|
48
|
+
hash[:flow_per_area] = (peak_flow * 3600000) / floor_area
|
49
|
+
unless load.flowRateFractionSchedule.empty?
|
50
|
+
schedule = load.flowRateFractionSchedule.get
|
51
|
+
hash[:schedule] = schedule.nameString
|
52
|
+
end
|
53
|
+
|
54
|
+
hash
|
55
|
+
end
|
56
|
+
end #ServiceHotWaterAbridged
|
57
|
+
end #Honeybee
|
data/lib/from_openstudio.rb
CHANGED
@@ -80,6 +80,7 @@ require 'from_openstudio/load/infiltration'
|
|
80
80
|
require 'from_openstudio/load/ventilation'
|
81
81
|
require 'from_openstudio/load/daylight'
|
82
82
|
require 'from_openstudio/load/process'
|
83
|
+
require 'from_openstudio/load/service_hot_water'
|
83
84
|
|
84
85
|
# extend the program type objects
|
85
86
|
require 'from_openstudio/program_type'
|
@@ -3,7 +3,7 @@
|
|
3
3
|
"servers": [],
|
4
4
|
"info": {
|
5
5
|
"description": "Honeybee model schema.",
|
6
|
-
"version": "1.
|
6
|
+
"version": "1.49.0",
|
7
7
|
"title": "Honeybee Model Schema",
|
8
8
|
"contact": {
|
9
9
|
"name": "Ladybug Tools",
|
@@ -3531,10 +3531,7 @@
|
|
3531
3531
|
},
|
3532
3532
|
"story": {
|
3533
3533
|
"title": "Story",
|
3534
|
-
"description": "Text string for the story identifier to which this Room belongs. Rooms sharing the same story identifier are considered part of the same story in a Model.",
|
3535
|
-
"maxLength": 100,
|
3536
|
-
"minLength": 1,
|
3537
|
-
"pattern": "[.A-Za-z0-9_-]",
|
3534
|
+
"description": "Text string for the story identifier to which this Room belongs. Rooms sharing the same story identifier are considered part of the same story in a Model. Note that this property has no character restrictions.",
|
3538
3535
|
"type": "string"
|
3539
3536
|
}
|
3540
3537
|
},
|
@@ -8251,7 +8248,8 @@
|
|
8251
8248
|
"enum": [
|
8252
8249
|
"Floor",
|
8253
8250
|
"Ceiling",
|
8254
|
-
"FloorWithCarpet"
|
8251
|
+
"FloorWithCarpet",
|
8252
|
+
"CeilingMetalPanel"
|
8255
8253
|
],
|
8256
8254
|
"type": "string"
|
8257
8255
|
},
|
@@ -8334,14 +8332,14 @@
|
|
8334
8332
|
}
|
8335
8333
|
]
|
8336
8334
|
},
|
8337
|
-
"
|
8338
|
-
"
|
8339
|
-
"
|
8340
|
-
"
|
8341
|
-
|
8342
|
-
|
8343
|
-
|
8344
|
-
|
8335
|
+
"radiant_face_type": {
|
8336
|
+
"description": "Text to indicate which faces are thermally active by default. Note that this property has no effect when the rooms to which the HVAC system is assigned have constructions with internal source materials. In this case, those constructions will dictate the thermally active surfaces.",
|
8337
|
+
"default": "Floor",
|
8338
|
+
"allOf": [
|
8339
|
+
{
|
8340
|
+
"$ref": "#/components/schemas/RadiantFaceTypes"
|
8341
|
+
}
|
8342
|
+
]
|
8345
8343
|
},
|
8346
8344
|
"minimum_operation_time": {
|
8347
8345
|
"title": "Minimum Operation Time",
|
@@ -8358,15 +8356,6 @@
|
|
8358
8356
|
"exclusiveMinimum": 0,
|
8359
8357
|
"type": "number",
|
8360
8358
|
"format": "double"
|
8361
|
-
},
|
8362
|
-
"radiant_face_type": {
|
8363
|
-
"description": "Text to indicate which faces are thermally active by default. Note that this property has no effect when the rooms to which the HVAC system is assigned have constructions with internal source materials. In this case, those constructions will dictate the thermally active surfaces.",
|
8364
|
-
"default": "Floor",
|
8365
|
-
"allOf": [
|
8366
|
-
{
|
8367
|
-
"$ref": "#/components/schemas/RadiantFaceTypes"
|
8368
|
-
}
|
8369
|
-
]
|
8370
8359
|
}
|
8371
8360
|
},
|
8372
8361
|
"required": [
|
@@ -8977,14 +8966,14 @@
|
|
8977
8966
|
}
|
8978
8967
|
]
|
8979
8968
|
},
|
8980
|
-
"
|
8981
|
-
"
|
8982
|
-
"
|
8983
|
-
"
|
8984
|
-
|
8985
|
-
|
8986
|
-
|
8987
|
-
|
8969
|
+
"radiant_face_type": {
|
8970
|
+
"description": "Text to indicate which faces are thermally active by default. Note that this property has no effect when the rooms to which the HVAC system is assigned have constructions with internal source materials. In this case, those constructions will dictate the thermally active surfaces.",
|
8971
|
+
"default": "Floor",
|
8972
|
+
"allOf": [
|
8973
|
+
{
|
8974
|
+
"$ref": "#/components/schemas/RadiantFaceTypes"
|
8975
|
+
}
|
8976
|
+
]
|
8988
8977
|
},
|
8989
8978
|
"minimum_operation_time": {
|
8990
8979
|
"title": "Minimum Operation Time",
|
@@ -9001,15 +8990,6 @@
|
|
9001
8990
|
"exclusiveMinimum": 0,
|
9002
8991
|
"type": "number",
|
9003
8992
|
"format": "double"
|
9004
|
-
},
|
9005
|
-
"radiant_face_type": {
|
9006
|
-
"description": "Text to indicate which faces are thermally active by default. Note that this property has no effect when the rooms to which the HVAC system is assigned have constructions with internal source materials. In this case, those constructions will dictate the thermally active surfaces.",
|
9007
|
-
"default": "Floor",
|
9008
|
-
"allOf": [
|
9009
|
-
{
|
9010
|
-
"$ref": "#/components/schemas/RadiantFaceTypes"
|
9011
|
-
}
|
9012
|
-
]
|
9013
8993
|
}
|
9014
8994
|
},
|
9015
8995
|
"required": [
|
@@ -14118,7 +14098,7 @@
|
|
14118
14098
|
"version": {
|
14119
14099
|
"title": "Version",
|
14120
14100
|
"description": "Text string for the current version of the schema.",
|
14121
|
-
"default": "1.
|
14101
|
+
"default": "1.49.0",
|
14122
14102
|
"pattern": "([0-9]+)\\.([0-9]+)\\.([0-9]+)",
|
14123
14103
|
"type": "string",
|
14124
14104
|
"readOnly": true
|
data/lib/honeybee/model.rb
CHANGED
@@ -54,7 +54,7 @@ module Honeybee
|
|
54
54
|
final_vertices_list = []
|
55
55
|
matching_os_subsurfaces = []
|
56
56
|
matching_os_subsurface_indices = []
|
57
|
-
if os_vertices.size > 4
|
57
|
+
if $triangulate_sub_faces && os_vertices.size > 4
|
58
58
|
|
59
59
|
# if this apeture has a matched apeture, see if the other one has already been created
|
60
60
|
# the matched apeture should have been converted to multiple subsurfaces
|
@@ -54,7 +54,7 @@ module Honeybee
|
|
54
54
|
final_vertices_list = []
|
55
55
|
matching_os_subsurfaces = []
|
56
56
|
matching_os_subsurface_indices = []
|
57
|
-
if os_vertices.size > 4
|
57
|
+
if $triangulate_sub_faces && os_vertices.size > 4
|
58
58
|
|
59
59
|
# if this door has a matched door, see if the other one has already been created
|
60
60
|
# the matched door should have been converted to multiple subsurfaces
|
@@ -54,20 +54,28 @@ class OpenStudio::Model::Model
|
|
54
54
|
case climate_zone
|
55
55
|
when '0', '1'
|
56
56
|
radiant_htg_dsgn_sup_wtr_temp_f = 90.0
|
57
|
+
cz_mult = 2
|
57
58
|
when '2', '2A', '2B'
|
58
59
|
radiant_htg_dsgn_sup_wtr_temp_f = 100.0
|
60
|
+
cz_mult = 2
|
59
61
|
when '3', '3A', '3B', '3C'
|
60
62
|
radiant_htg_dsgn_sup_wtr_temp_f = 100.0
|
63
|
+
cz_mult = 3
|
61
64
|
when '4', '4A', '4B', '4C'
|
62
65
|
radiant_htg_dsgn_sup_wtr_temp_f = 100.0
|
66
|
+
cz_mult = 4
|
63
67
|
when '5', '5A', '5B', '5C'
|
64
68
|
radiant_htg_dsgn_sup_wtr_temp_f = 110.0
|
69
|
+
cz_mult = 4
|
65
70
|
when '6', '6A', '6B'
|
66
71
|
radiant_htg_dsgn_sup_wtr_temp_f = 120.0
|
72
|
+
cz_mult = 4
|
67
73
|
when '7', '8'
|
68
74
|
radiant_htg_dsgn_sup_wtr_temp_f = 120.0
|
75
|
+
cz_mult = 5
|
69
76
|
else # unrecognized climate zone; default to climate zone 4
|
70
77
|
radiant_htg_dsgn_sup_wtr_temp_f = 100.0
|
78
|
+
cz_mult = 4
|
71
79
|
end
|
72
80
|
|
73
81
|
# create the hot water loop
|
@@ -120,11 +128,6 @@ class OpenStudio::Model::Model
|
|
120
128
|
end
|
121
129
|
|
122
130
|
# get the various controls for the radiant system
|
123
|
-
if rad_props[:proportional_gain]
|
124
|
-
proportional_gain = rad_props[:proportional_gain]
|
125
|
-
else
|
126
|
-
proportional_gain = 0.3
|
127
|
-
end
|
128
131
|
if rad_props[:minimum_operation_time]
|
129
132
|
minimum_operation = rad_props[:minimum_operation_time]
|
130
133
|
else
|
@@ -136,24 +139,34 @@ class OpenStudio::Model::Model
|
|
136
139
|
switch_over_time = 24
|
137
140
|
end
|
138
141
|
|
142
|
+
# get the start and end hour from the input zones
|
143
|
+
start_hour, end_hour = start_end_hour_from_zones_occupancy(zones)
|
144
|
+
|
139
145
|
# add radiant system to the conditioned zones
|
140
146
|
include_carpet = false
|
141
|
-
|
142
|
-
|
147
|
+
control_strategy = 'proportional_control'
|
148
|
+
if rad_props[:radiant_type]
|
149
|
+
radiant_type = rad_props[:radiant_type].downcase
|
143
150
|
if radiant_type == 'floorwithcarpet'
|
144
151
|
radiant_type = 'floor'
|
145
152
|
include_carpet = true
|
153
|
+
elsif radiant_type == 'ceilingmetalpanel'
|
154
|
+
control_strategy = 'default'
|
146
155
|
end
|
147
156
|
else
|
148
157
|
radiant_type = 'floor'
|
149
158
|
end
|
150
|
-
|
151
|
-
|
159
|
+
|
160
|
+
radiant_loops = model_add_low_temp_radiant(
|
161
|
+
std, conditioned_zones, hot_water_loop, chilled_water_loop,
|
152
162
|
radiant_type: radiant_type,
|
163
|
+
control_strategy: control_strategy,
|
153
164
|
include_carpet: include_carpet,
|
154
|
-
|
165
|
+
model_occ_hr_start: start_hour,
|
166
|
+
model_occ_hr_end: end_hour,
|
155
167
|
minimum_operation: minimum_operation,
|
156
|
-
switch_over_time: switch_over_time
|
168
|
+
switch_over_time: switch_over_time,
|
169
|
+
cz_mult: cz_mult)
|
157
170
|
|
158
171
|
# if the equipment includes a DOAS, then add it
|
159
172
|
if system_type.include? 'DOAS_'
|
@@ -161,4 +174,365 @@ class OpenStudio::Model::Model
|
|
161
174
|
end
|
162
175
|
|
163
176
|
end
|
177
|
+
|
178
|
+
# get the start and end hour from the occupancy schedules of thermal zones
|
179
|
+
def start_end_hour_from_zones_occupancy(thermal_zones, threshold: 0.1)
|
180
|
+
# set the default start and end hour in the event there's no occupancy
|
181
|
+
start_hour = 12
|
182
|
+
end_hour = 12
|
183
|
+
# loop through the occupancy schedules and get the lowest start hour; highest end hour
|
184
|
+
thermal_zones.each do |zone|
|
185
|
+
zone.spaces.each do |space|
|
186
|
+
# gather all of the people objects assigned to the sapce
|
187
|
+
peoples = []
|
188
|
+
unless space.spaceType.empty?
|
189
|
+
space_type = space.spaceType.get
|
190
|
+
unless space_type.people.empty?
|
191
|
+
space_type.people.each do |ppl|
|
192
|
+
peoples << ppl
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
space.people.each do |ppl|
|
197
|
+
peoples << ppl
|
198
|
+
end
|
199
|
+
# loop through the pople and gather all occupancy schedules
|
200
|
+
peoples.each do |people|
|
201
|
+
occupancy_sch_opt = people.numberofPeopleSchedule
|
202
|
+
unless occupancy_sch_opt.empty?
|
203
|
+
occupancy_sch = occupancy_sch_opt.get
|
204
|
+
if occupancy_sch.to_ScheduleRuleset.is_initialized
|
205
|
+
occupancy_sch = occupancy_sch.to_ScheduleRuleset.get
|
206
|
+
# gather all of the day schedules across the schedule ruleset
|
207
|
+
schedule_days, day_ids = [], []
|
208
|
+
required_days = [
|
209
|
+
occupancy_sch.defaultDaySchedule,
|
210
|
+
occupancy_sch.summerDesignDaySchedule,
|
211
|
+
occupancy_sch.winterDesignDaySchedule,
|
212
|
+
occupancy_sch.holidaySchedule
|
213
|
+
]
|
214
|
+
required_days.each do |day_sch|
|
215
|
+
unless day_ids.include? day_sch.nameString
|
216
|
+
schedule_days << day_sch
|
217
|
+
day_ids << day_sch.nameString
|
218
|
+
end
|
219
|
+
end
|
220
|
+
occupancy_sch.scheduleRules.each do |schedule_rule|
|
221
|
+
day_sch = schedule_rule.daySchedule
|
222
|
+
unless day_ids.include? day_sch.nameString
|
223
|
+
schedule_days << day_sch
|
224
|
+
day_ids << day_sch.nameString
|
225
|
+
end
|
226
|
+
end
|
227
|
+
# loop through the day schedules and see if the start and end hours should be changed
|
228
|
+
schedule_days.each do |day_sch|
|
229
|
+
time_until = [1]
|
230
|
+
day_sch.times.each do |time|
|
231
|
+
time_until << time.hours
|
232
|
+
end
|
233
|
+
final_time = time_until[-2]
|
234
|
+
day_sch.values.zip(time_until).each do |value, time|
|
235
|
+
if value > threshold
|
236
|
+
if time < start_hour
|
237
|
+
start_hour = time
|
238
|
+
end
|
239
|
+
if time > end_hour
|
240
|
+
end_hour = time
|
241
|
+
end
|
242
|
+
if time == final_time
|
243
|
+
start_hour = 1
|
244
|
+
end_hour = 24
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
# if no values were set, just set the system to be on all of the time
|
255
|
+
if start_hour == 12 or start_hour == 0
|
256
|
+
start_hour = 1
|
257
|
+
end
|
258
|
+
if end_hour == 12
|
259
|
+
end_hour = 24
|
260
|
+
end
|
261
|
+
return start_hour, end_hour
|
262
|
+
end
|
263
|
+
|
264
|
+
def model_add_low_temp_radiant(std,
|
265
|
+
thermal_zones,
|
266
|
+
hot_water_loop,
|
267
|
+
chilled_water_loop,
|
268
|
+
radiant_type: 'floor',
|
269
|
+
include_carpet: true,
|
270
|
+
carpet_thickness_in: 0.25,
|
271
|
+
model_occ_hr_start: 1.0,
|
272
|
+
model_occ_hr_end: 24.0,
|
273
|
+
control_strategy: 'proportional_control',
|
274
|
+
proportional_gain: 0.3,
|
275
|
+
minimum_operation: 1,
|
276
|
+
weekend_temperature_reset: 2,
|
277
|
+
early_reset_out_arg: 20,
|
278
|
+
switch_over_time: 24.0,
|
279
|
+
cz_mult: 4)
|
280
|
+
|
281
|
+
# create internal source constructions for surfaces
|
282
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "Replacing constructions with new radiant slab constructions.")
|
283
|
+
|
284
|
+
# create materials
|
285
|
+
mat_concrete_3_5in = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'MediumRough', 0.0889, 2.31, 2322, 832)
|
286
|
+
mat_concrete_3_5in.setName('Radiant Slab Concrete - 3.5 in.')
|
287
|
+
mat_concrete_1_5in = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'MediumRough', 0.0381, 2.31, 2322, 832)
|
288
|
+
mat_concrete_1_5in.setName('Radiant Slab Concrete - 1.5 in')
|
289
|
+
|
290
|
+
metal_mat = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'MediumSmooth', 0.003175, 30, 7680, 418)
|
291
|
+
metal_mat.setName('Radiant Metal Layer - 0.125 in')
|
292
|
+
air_gap_map = OpenStudio::Model::MasslessOpaqueMaterial.new(self, 'Smooth', 0.004572)
|
293
|
+
air_gap_map.setName('Generic Ceiling Air Gap - R 0.025')
|
294
|
+
|
295
|
+
mat_refl_roof_membrane = self.getStandardOpaqueMaterialByName('Roof Membrane - Highly Reflective')
|
296
|
+
if mat_refl_roof_membrane.is_initialized
|
297
|
+
mat_refl_roof_membrane = self.getStandardOpaqueMaterialByName('Roof Membrane - Highly Reflective').get
|
298
|
+
else
|
299
|
+
mat_refl_roof_membrane = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'VeryRough', 0.0095, 0.16, 1121.29, 1460)
|
300
|
+
mat_refl_roof_membrane.setThermalAbsorptance(0.75)
|
301
|
+
mat_refl_roof_membrane.setSolarAbsorptance(0.45)
|
302
|
+
mat_refl_roof_membrane.setVisibleAbsorptance(0.7)
|
303
|
+
mat_refl_roof_membrane.setName('Roof Membrane - Highly Reflective')
|
304
|
+
end
|
305
|
+
|
306
|
+
if include_carpet
|
307
|
+
carpet_thickness_m = OpenStudio.convert(carpet_thickness_in / 12.0, 'ft', 'm').get
|
308
|
+
conductivity_si = 0.06
|
309
|
+
conductivity_ip = OpenStudio.convert(conductivity_si, 'W/m*K', 'Btu*in/hr*ft^2*R').get
|
310
|
+
r_value_ip = carpet_thickness_in * (1 / conductivity_ip)
|
311
|
+
mat_thin_carpet_tile = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'MediumRough', carpet_thickness_m, conductivity_si, 288, 1380)
|
312
|
+
mat_thin_carpet_tile.setThermalAbsorptance(0.9)
|
313
|
+
mat_thin_carpet_tile.setSolarAbsorptance(0.7)
|
314
|
+
mat_thin_carpet_tile.setVisibleAbsorptance(0.8)
|
315
|
+
mat_thin_carpet_tile.setName("Radiant Slab Thin Carpet Tile R-#{r_value_ip.round(2)}")
|
316
|
+
end
|
317
|
+
|
318
|
+
# set exterior slab insulation thickness based on climate zone
|
319
|
+
slab_insulation_thickness_m = 0.0254 * cz_mult
|
320
|
+
mat_slab_insulation = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'Rough', slab_insulation_thickness_m, 0.02, 56.06, 1210)
|
321
|
+
mat_slab_insulation.setName("Radiant Ground Slab Insulation - #{cz_mult} in.")
|
322
|
+
|
323
|
+
ext_insulation_thickness_m = 0.0254 * (cz_mult + 1)
|
324
|
+
mat_ext_insulation = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'Rough', ext_insulation_thickness_m, 0.02, 56.06, 1210)
|
325
|
+
mat_ext_insulation.setName("Radiant Exterior Slab Insulation - #{cz_mult + 1} in.")
|
326
|
+
|
327
|
+
roof_insulation_thickness_m = 0.0254 * (cz_mult + 1) * 2
|
328
|
+
mat_roof_insulation = OpenStudio::Model::StandardOpaqueMaterial.new(self, 'Rough', roof_insulation_thickness_m, 0.02, 56.06, 1210)
|
329
|
+
mat_roof_insulation.setName("Radiant Exterior Ceiling Insulation - #{(cz_mult + 1) * 2} in.")
|
330
|
+
|
331
|
+
# create radiant internal source constructions
|
332
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', 'New constructions exclude the metal deck, as high thermal diffusivity materials cause errors in EnergyPlus internal source construction calculations.')
|
333
|
+
|
334
|
+
layers = []
|
335
|
+
layers << mat_slab_insulation
|
336
|
+
layers << mat_concrete_3_5in
|
337
|
+
layers << mat_concrete_1_5in
|
338
|
+
layers << mat_thin_carpet_tile if include_carpet
|
339
|
+
radiant_ground_slab_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
340
|
+
radiant_ground_slab_construction.setName('Radiant Ground Slab Construction')
|
341
|
+
radiant_ground_slab_construction.setSourcePresentAfterLayerNumber(2)
|
342
|
+
radiant_ground_slab_construction.setTemperatureCalculationRequestedAfterLayerNumber(3)
|
343
|
+
radiant_ground_slab_construction.setTubeSpacing(0.2286) # 9 inches
|
344
|
+
|
345
|
+
layers = []
|
346
|
+
layers << mat_ext_insulation
|
347
|
+
layers << mat_concrete_3_5in
|
348
|
+
layers << mat_concrete_1_5in
|
349
|
+
layers << mat_thin_carpet_tile if include_carpet
|
350
|
+
radiant_exterior_slab_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
351
|
+
radiant_exterior_slab_construction.setName('Radiant Exterior Slab Construction')
|
352
|
+
radiant_exterior_slab_construction.setSourcePresentAfterLayerNumber(2)
|
353
|
+
radiant_exterior_slab_construction.setTemperatureCalculationRequestedAfterLayerNumber(3)
|
354
|
+
radiant_exterior_slab_construction.setTubeSpacing(0.2286) # 9 inches
|
355
|
+
|
356
|
+
layers = []
|
357
|
+
layers << mat_concrete_3_5in
|
358
|
+
layers << mat_concrete_1_5in
|
359
|
+
layers << mat_thin_carpet_tile if include_carpet
|
360
|
+
radiant_interior_floor_slab_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
361
|
+
radiant_interior_floor_slab_construction.setName('Radiant Interior Floor Slab Construction')
|
362
|
+
radiant_interior_floor_slab_construction.setSourcePresentAfterLayerNumber(1)
|
363
|
+
radiant_interior_floor_slab_construction.setTemperatureCalculationRequestedAfterLayerNumber(2)
|
364
|
+
radiant_interior_floor_slab_construction.setTubeSpacing(0.2286) # 9 inches
|
365
|
+
|
366
|
+
layers = []
|
367
|
+
layers << mat_thin_carpet_tile if include_carpet
|
368
|
+
layers << mat_concrete_3_5in
|
369
|
+
layers << mat_concrete_1_5in
|
370
|
+
radiant_interior_ceiling_slab_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
371
|
+
radiant_interior_ceiling_slab_construction.setName('Radiant Interior Ceiling Slab Construction')
|
372
|
+
slab_src_loc = include_carpet ? 2 : 1
|
373
|
+
radiant_interior_ceiling_slab_construction.setSourcePresentAfterLayerNumber(slab_src_loc)
|
374
|
+
radiant_interior_ceiling_slab_construction.setTemperatureCalculationRequestedAfterLayerNumber(slab_src_loc + 1)
|
375
|
+
radiant_interior_ceiling_slab_construction.setTubeSpacing(0.2286) # 9 inches
|
376
|
+
|
377
|
+
layers = []
|
378
|
+
layers << mat_refl_roof_membrane
|
379
|
+
layers << mat_roof_insulation
|
380
|
+
layers << mat_concrete_3_5in
|
381
|
+
layers << mat_concrete_1_5in
|
382
|
+
radiant_ceiling_slab_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
383
|
+
radiant_ceiling_slab_construction.setName('Radiant Exterior Ceiling Slab Construction')
|
384
|
+
radiant_ceiling_slab_construction.setSourcePresentAfterLayerNumber(3)
|
385
|
+
radiant_ceiling_slab_construction.setTemperatureCalculationRequestedAfterLayerNumber(4)
|
386
|
+
radiant_ceiling_slab_construction.setTubeSpacing(0.2286) # 9 inches
|
387
|
+
|
388
|
+
layers = []
|
389
|
+
layers << mat_concrete_3_5in
|
390
|
+
layers << air_gap_map
|
391
|
+
layers << metal_mat
|
392
|
+
layers << metal_mat
|
393
|
+
radiant_interior_ceiling_metal_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
394
|
+
radiant_interior_ceiling_metal_construction.setName('Radiant Interior Ceiling Metal Construction')
|
395
|
+
radiant_interior_ceiling_metal_construction.setSourcePresentAfterLayerNumber(3)
|
396
|
+
radiant_interior_ceiling_metal_construction.setTemperatureCalculationRequestedAfterLayerNumber(4)
|
397
|
+
radiant_interior_ceiling_metal_construction.setTubeSpacing(0.1524) # 6 inches
|
398
|
+
|
399
|
+
layers = []
|
400
|
+
layers << mat_refl_roof_membrane
|
401
|
+
layers << mat_roof_insulation
|
402
|
+
layers << mat_concrete_3_5in
|
403
|
+
layers << air_gap_map
|
404
|
+
layers << metal_mat
|
405
|
+
layers << metal_mat
|
406
|
+
radiant_ceiling_metal_construction = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
407
|
+
radiant_ceiling_metal_construction.setName('Radiant Ceiling Metal Construction')
|
408
|
+
radiant_ceiling_metal_construction.setSourcePresentAfterLayerNumber(5)
|
409
|
+
radiant_ceiling_metal_construction.setTemperatureCalculationRequestedAfterLayerNumber(6)
|
410
|
+
radiant_ceiling_metal_construction.setTubeSpacing(0.1524) # 6 inches
|
411
|
+
|
412
|
+
# default temperature controls for radiant system
|
413
|
+
zn_radiant_htg_dsgn_temp_f = 68.0
|
414
|
+
zn_radiant_htg_dsgn_temp_c = OpenStudio.convert(zn_radiant_htg_dsgn_temp_f, 'F', 'C').get
|
415
|
+
zn_radiant_clg_dsgn_temp_f = 74.0
|
416
|
+
zn_radiant_clg_dsgn_temp_c = OpenStudio.convert(zn_radiant_clg_dsgn_temp_f, 'F', 'C').get
|
417
|
+
|
418
|
+
htg_control_temp_sch = std.model_add_constant_schedule_ruleset(
|
419
|
+
self,
|
420
|
+
zn_radiant_htg_dsgn_temp_c,
|
421
|
+
name = "Zone Radiant Loop Heating Threshold Temperature Schedule - #{zn_radiant_htg_dsgn_temp_f.round(0)}F")
|
422
|
+
clg_control_temp_sch = std.model_add_constant_schedule_ruleset(
|
423
|
+
self,
|
424
|
+
zn_radiant_clg_dsgn_temp_c,
|
425
|
+
name = "Zone Radiant Loop Cooling Threshold Temperature Schedule - #{zn_radiant_clg_dsgn_temp_f.round(0)}F")
|
426
|
+
throttling_range_f = 4.0 # 2 degF on either side of control temperature
|
427
|
+
throttling_range_c = OpenStudio.convert(throttling_range_f, 'F', 'C').get
|
428
|
+
|
429
|
+
# make a low temperature radiant loop for each zone
|
430
|
+
radiant_loops = []
|
431
|
+
thermal_zones.each do |zone|
|
432
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding radiant loop for #{zone.name}.")
|
433
|
+
if zone.name.to_s.include? ':'
|
434
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "Thermal zone '#{zone.name}' has a restricted character ':' in the name and will not work with some EMS and output reporting objects. Please rename the zone.")
|
435
|
+
end
|
436
|
+
|
437
|
+
# create radiant coils
|
438
|
+
if hot_water_loop
|
439
|
+
radiant_loop_htg_coil = OpenStudio::Model::CoilHeatingLowTempRadiantVarFlow.new(self, htg_control_temp_sch)
|
440
|
+
radiant_loop_htg_coil.setName("#{zone.name} Radiant Loop Heating Coil")
|
441
|
+
radiant_loop_htg_coil.setHeatingControlThrottlingRange(throttling_range_c)
|
442
|
+
hot_water_loop.addDemandBranchForComponent(radiant_loop_htg_coil)
|
443
|
+
else
|
444
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', 'Radiant loops require a hot water loop, but none was provided.')
|
445
|
+
end
|
446
|
+
|
447
|
+
if chilled_water_loop
|
448
|
+
radiant_loop_clg_coil = OpenStudio::Model::CoilCoolingLowTempRadiantVarFlow.new(self, clg_control_temp_sch)
|
449
|
+
radiant_loop_clg_coil.setName("#{zone.name} Radiant Loop Cooling Coil")
|
450
|
+
radiant_loop_clg_coil.setCoolingControlThrottlingRange(throttling_range_c)
|
451
|
+
chilled_water_loop.addDemandBranchForComponent(radiant_loop_clg_coil)
|
452
|
+
else
|
453
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', 'Radiant loops require a chilled water loop, but none was provided.')
|
454
|
+
end
|
455
|
+
|
456
|
+
radiant_avail_sch = self.alwaysOnDiscreteSchedule
|
457
|
+
radiant_loop = OpenStudio::Model::ZoneHVACLowTempRadiantVarFlow.new(self,
|
458
|
+
radiant_avail_sch,
|
459
|
+
radiant_loop_htg_coil,
|
460
|
+
radiant_loop_clg_coil)
|
461
|
+
|
462
|
+
# assign internal source construction to floors in zone
|
463
|
+
zone.spaces.each do |space|
|
464
|
+
space.surfaces.each do |surface|
|
465
|
+
if radiant_type == 'floor'
|
466
|
+
if surface.surfaceType == 'Floor'
|
467
|
+
if surface.outsideBoundaryCondition == 'Ground'
|
468
|
+
surface.setConstruction(radiant_ground_slab_construction)
|
469
|
+
elsif surface.outsideBoundaryCondition == 'Outdoors'
|
470
|
+
surface.setConstruction(radiant_exterior_slab_construction)
|
471
|
+
else # interior floor
|
472
|
+
surface.setConstruction(radiant_interior_floor_slab_construction)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
elsif radiant_type == 'ceiling'
|
476
|
+
if surface.surfaceType == 'RoofCeiling'
|
477
|
+
if surface.outsideBoundaryCondition == 'Outdoors'
|
478
|
+
surface.setConstruction(radiant_ceiling_slab_construction)
|
479
|
+
else # interior ceiling
|
480
|
+
surface.setConstruction(radiant_interior_ceiling_slab_construction)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
elsif radiant_type == 'ceilingmetalpanel'
|
484
|
+
if surface.surfaceType == 'RoofCeiling'
|
485
|
+
if surface.outsideBoundaryCondition == 'Outdoors'
|
486
|
+
surface.setConstruction(radiant_ceiling_metal_construction)
|
487
|
+
else # interior ceiling
|
488
|
+
surface.setConstruction(radiant_interior_ceiling_metal_construction)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# radiant loop surfaces
|
496
|
+
radiant_loop.setName("#{zone.name} Radiant Loop")
|
497
|
+
if radiant_type == 'floor'
|
498
|
+
radiant_loop.setRadiantSurfaceType('Floors')
|
499
|
+
elsif radiant_type == 'ceiling'
|
500
|
+
radiant_loop.setRadiantSurfaceType('Ceilings')
|
501
|
+
elsif radiant_type == 'ceilingmetalpanel'
|
502
|
+
radiant_loop.setRadiantSurfaceType('Ceilings')
|
503
|
+
end
|
504
|
+
|
505
|
+
# radiant loop layout details
|
506
|
+
radiant_loop.setHydronicTubingInsideDiameter(0.015875) # 5/8 in. ID, 3/4 in. OD
|
507
|
+
# @todo include a method to determine tubing length in the zone
|
508
|
+
# loop_length = 7*zone.floorArea
|
509
|
+
# radiant_loop.setHydronicTubingLength()
|
510
|
+
radiant_loop.setNumberofCircuits('CalculateFromCircuitLength')
|
511
|
+
radiant_loop.setCircuitLength(106.7)
|
512
|
+
|
513
|
+
# radiant loop controls
|
514
|
+
radiant_loop.setTemperatureControlType('MeanAirTemperature')
|
515
|
+
radiant_loop.addToThermalZone(zone)
|
516
|
+
radiant_loops << radiant_loop
|
517
|
+
|
518
|
+
# rename nodes before adding EMS code
|
519
|
+
std.rename_plant_loop_nodes(self)
|
520
|
+
|
521
|
+
# set radiant loop controls
|
522
|
+
if control_strategy == 'proportional_control'
|
523
|
+
std.model_add_radiant_proportional_controls(self, zone, radiant_loop,
|
524
|
+
radiant_type: radiant_type,
|
525
|
+
model_occ_hr_start: model_occ_hr_start,
|
526
|
+
model_occ_hr_end: model_occ_hr_end,
|
527
|
+
proportional_gain: proportional_gain,
|
528
|
+
minimum_operation: minimum_operation,
|
529
|
+
weekend_temperature_reset: weekend_temperature_reset,
|
530
|
+
early_reset_out_arg: early_reset_out_arg,
|
531
|
+
switch_over_time: switch_over_time)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
return radiant_loops
|
536
|
+
end
|
537
|
+
|
164
538
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: honeybee-openstudio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.31.
|
4
|
+
version: 2.31.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tanushree Charan
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2022-
|
14
|
+
date: 2022-04-05 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -184,6 +184,7 @@ files:
|
|
184
184
|
- lib/from_openstudio/load/lighting.rb
|
185
185
|
- lib/from_openstudio/load/people.rb
|
186
186
|
- lib/from_openstudio/load/process.rb
|
187
|
+
- lib/from_openstudio/load/service_hot_water.rb
|
187
188
|
- lib/from_openstudio/load/ventilation.rb
|
188
189
|
- lib/from_openstudio/material/opaque.rb
|
189
190
|
- lib/from_openstudio/material/opaque_no_mass.rb
|