lib/measures/tbd/resources/psi.rb in tbd-3.3.0 vs lib/measures/tbd/resources/psi.rb in tbd-3.4.0
- old
+ new
@@ -1,8 +1,8 @@
# MIT License
#
-# Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
@@ -154,10 +154,11 @@
@set["poor (BETBG)"] =
{
rimjoist: 1.000000, # re: BETBG
parapet: 0.800000, # re: BETBG
roof: 0.800000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.500000, # re: BETBG
door: 0.500000, # inferred, same as (vertical) fenestration
skylight: 0.500000, # inferred, same as (vertical) fenestration
spandrel: 0.155000, # Detail 5.4.4
corner: 0.850000, # re: BETBG
@@ -174,10 +175,11 @@
@set["regular (BETBG)"] =
{
rimjoist: 0.500000, # re: BETBG
parapet: 0.450000, # re: BETBG
roof: 0.450000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.350000, # re: BETBG
door: 0.350000, # inferred, same as (vertical) fenestration
skylight: 0.350000, # inferred, same as (vertical) fenestration
spandrel: 0.155000, # Detail 5.4.4
corner: 0.450000, # re: BETBG
@@ -194,10 +196,11 @@
@set["efficient (BETBG)"] =
{
rimjoist: 0.200000, # re: BETBG
parapet: 0.200000, # re: BETBG
roof: 0.200000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.199999, # re: BETBG
door: 0.199999, # inferred, same as (vertical) fenestration
skylight: 0.199999, # inferred, same as (vertical) fenestration
spandrel: 0.155000, # Detail 5.4.4
corner: 0.200000, # re: BETBG
@@ -214,10 +217,11 @@
@set["spandrel (BETBG)"] =
{
rimjoist: 0.615000, # Detail 1.2.1
parapet: 1.000000, # Detail 1.3.2
roof: 1.000000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.000000, # inferred, generally part of clear-field RSi
door: 0.000000, # inferred, generally part of clear-field RSi
skylight: 0.350000, # same as "regular (BETBG)"
spandrel: 0.155000, # Detail 5.4.4
corner: 0.425000, # Detail 1.4.1
@@ -234,10 +238,11 @@
@set["spandrel HP (BETBG)"] =
{
rimjoist: 0.170000, # Detail 1.2.7
parapet: 0.660000, # Detail 1.3.2
roof: 0.660000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.000000, # inferred, generally part of clear-field RSi
door: 0.000000, # inferred, generally part of clear-field RSi
skylight: 0.350000, # same as "regular (BETBG)"
spandrel: 0.155000, # Detail 5.4.4
corner: 0.200000, # Detail 1.4.2
@@ -254,10 +259,11 @@
@set["code (Quebec)"] =
{
rimjoist: 0.300000, # re I1
parapet: 0.325000, # re I1
roof: 0.325000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.200000, # re I1
door: 0.200000, # re I1
skylight: 0.200000, # re I1
spandrel: 0.155000, # BETBG Detail 5.4.4 (same as uncompliant)
corner: 0.300000, # inferred from description, not explicitely set
@@ -274,10 +280,11 @@
@set["uncompliant (Quebec)"] =
{
rimjoist: 0.850000, # re I1
parapet: 0.800000, # re I1
roof: 0.800000, # same as parapet
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.500000, # re I1
door: 0.500000, # re I1
skylight: 0.500000, # re I1
spandrel: 0.155000, # BETBG Detail 5.4.4 (same as compliant)
corner: 0.850000, # inferred from description, not explicitely set
@@ -294,10 +301,11 @@
@set["90.1.22|steel.m|default"] =
{
rimjoist: 0.307000, # "intermediate floor to wall intersection"
parapet: 0.260000, # "parapet" edge
roof: 0.020000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.194000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000001, # (unspecified, defaults to 0)
corner: 0.000002, # (unspecified, defaults to 0)
@@ -314,10 +322,11 @@
@set["90.1.22|steel.m|unmitigated"] =
{
rimjoist: 0.842000, # "intermediate floor to wall intersection"
parapet: 0.500000, # "parapet" edge
roof: 0.650000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.505000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000001, # (unspecified, defaults to 0)
corner: 0.000002, # (unspecified, defaults to 0)
@@ -334,10 +343,11 @@
@set["90.1.22|mass.ex|default"] =
{
rimjoist: 0.205000, # "intermediate floor to wall intersection"
parapet: 0.217000, # "parapet" edge
roof: 0.150000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.226000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000001, # (unspecified, defaults to 0)
corner: 0.000002, # (unspecified, defaults to 0)
@@ -354,10 +364,11 @@
@set["90.1.22|mass.ex|unmitigated"] =
{
rimjoist: 0.824000, # "intermediate floor to wall intersection"
parapet: 0.412000, # "parapet" edge
roof: 0.750000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.325000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000001, # (unspecified, defaults to 0)
corner: 0.000002, # (unspecified, defaults to 0)
@@ -374,10 +385,11 @@
@set["90.1.22|mass.in|default"] =
{
rimjoist: 0.495000, # "intermediate floor to wall intersection"
parapet: 0.393000, # "parapet" edge
roof: 0.150000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.143000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000000, # (unspecified, defaults to 0)
corner: 0.000001, # (unspecified, defaults to 0)
@@ -394,10 +406,11 @@
@set["90.1.22|mass.in|unmitigated"] =
{
rimjoist: 0.824000, # "intermediate floor to wall intersection"
parapet: 0.884000, # "parapet" edge
roof: 0.750000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.543000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000000, # (unspecified, defaults to 0)
corner: 0.000001, # (unspecified, defaults to 0)
@@ -414,10 +427,11 @@
@set["90.1.22|wood.fr|default"] =
{
rimjoist: 0.084000, # "intermediate floor to wall intersection"
parapet: 0.056000, # "parapet" edge
roof: 0.020000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.171000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000000, # (unspecified, defaults to 0)
corner: 0.000001, # (unspecified, defaults to 0)
@@ -434,10 +448,11 @@
@set["90.1.22|wood.fr|unmitigated"] =
{
rimjoist: 0.582000, # "intermediate floor to wall intersection"
parapet: 0.056000, # "parapet" edge
roof: 0.150000, # (non-parapet) "roof" edge
+ ceiling: 0.000000, # e.g. suspended ceiling tiles
fenestration: 0.260000, # "wall to vertical fenestration intersection"
door: 0.000000, # (unspecified, defaults to 0)
skylight: 0.000000, # (unspecified, defaults to 0)
spandrel: 0.000000, # (unspecified, defaults to 0)
corner: 0.000001, # (unspecified, defaults to 0)
@@ -450,24 +465,26 @@
transition: 0.000000 # defaults to 0
}.freeze
@set["(non thermal bridging)"] =
{
- rimjoist: 0.000000, # defaults to 0
- parapet: 0.000000, # defaults to 0
- roof: 0.000000, # defaults to 0
- fenestration: 0.000000, # defaults to 0
- door: 0.000000, # defaults to 0
- skylight: 0.000000, # defaults to 0
- spandrel: 0.000000, # defaults to 0
- corner: 0.000000, # defaults to 0
- balcony: 0.000000, # defaults to 0
- balconysill: 0.000000, # defaults to 0
- party: 0.000000, # defaults to 0
- grade: 0.000000, # defaults to 0
- joint: 0.000000, # defaults to 0
- transition: 0.000000 # defaults to 0
+ rimjoist: 0.000000, # defaults to 0
+ parapet: 0.000000, # defaults to 0
+ roof: 0.000000, # defaults to 0
+ ceiling: 0.000000, # defaults to 0
+ fenestration: 0.000000, # defaults to 0
+ door: 0.000000, # defaults to 0
+ skylight: 0.000000, # defaults to 0
+ spandrel: 0.000000, # defaults to 0
+ corner: 0.000000, # defaults to 0
+ balcony: 0.000000, # defaults to 0
+ balconysill: 0.000000, # defaults to 0
+ balconydoorsill: 0.000000, # defaults to 0
+ party: 0.000000, # defaults to 0
+ grade: 0.000000, # defaults to 0
+ joint: 0.000000, # defaults to 0
+ transition: 0.000000 # defaults to 0
}.freeze
@set.keys.each { |k| self.gen(k) }
end
@@ -528,10 +545,13 @@
h[:partyconcave ] = @set[id].key?(:parapetconcave)
h[:parapetconvex ] = @set[id].key?(:parapetconvex)
h[:roof ] = @set[id].key?(:roof)
h[:roofconcave ] = @set[id].key?(:roofconcave)
h[:roofconvex ] = @set[id].key?(:roofconvex)
+ h[:ceiling ] = @set[id].key?(:ceiling)
+ h[:ceilingconcave ] = @set[id].key?(:ceilingconcave)
+ h[:ceilingconvex ] = @set[id].key?(:ceilingconvex)
h[:grade ] = @set[id].key?(:grade)
h[:gradeconcave ] = @set[id].key?(:gradeconcave)
h[:gradeconvex ] = @set[id].key?(:gradeconvex)
h[:balcony ] = @set[id].key?(:balcony)
h[:balconyconcave ] = @set[id].key?(:balconyconcave)
@@ -560,10 +580,11 @@
v[:skylightjamb ] = 0; v[:skylightjambconcave ] = 0; v[:skylightjambconvex ] = 0
v[:spandrel ] = 0; v[:spandrelconcave ] = 0; v[:spandrelconvex ] = 0
v[:corner ] = 0; v[:cornerconcave ] = 0; v[:cornerconvex ] = 0
v[:parapet ] = 0; v[:parapetconcave ] = 0; v[:parapetconvex ] = 0
v[:roof ] = 0; v[:roofconcave ] = 0; v[:roofconvex ] = 0
+ v[:ceiling ] = 0; v[:ceilingconcave ] = 0; v[:ceilingconvex ] = 0
v[:party ] = 0; v[:partyconcave ] = 0; v[:partyconvex ] = 0
v[:grade ] = 0; v[:gradeconcave ] = 0; v[:gradeconvex ] = 0
v[:balcony ] = 0; v[:balconyconcave ] = 0; v[:balconyconvex ] = 0
v[:balconysill ] = 0; v[:balconysillconcave ] = 0; v[:balconysillconvex ] = 0
v[:balconydoorsill] = 0; v[:balconydoorsillconcave] = 0; v[:balconydoorsillconvex] = 0
@@ -695,10 +716,15 @@
v[:roof ] = @set[id][:roof ] if h[:roof ]
v[:roofconcave ] = @set[id][:roof ] if h[:roof ]
v[:roofconvex ] = @set[id][:roof ] if h[:roof ]
v[:roofconcave ] = @set[id][:roofconcave ] if h[:roofconcave ]
v[:roofconvex ] = @set[id][:roofconvex ] if h[:roofconvex ]
+ v[:ceiling ] = @set[id][:ceiling ] if h[:ceiling ]
+ v[:ceilingconcave ] = @set[id][:ceiling ] if h[:ceiling ]
+ v[:ceilingconvex ] = @set[id][:ceiling ] if h[:ceiling ]
+ v[:ceilingconcave ] = @set[id][:ceilingconcave ] if h[:ceilingconcave ]
+ v[:ceilingconvex ] = @set[id][:ceilingconvex ] if h[:ceilingconvex ]
v[:party ] = @set[id][:party ] if h[:party ]
v[:partyconcave ] = @set[id][:party ] if h[:party ]
v[:partyconvex ] = @set[id][:party ] if h[:party ]
v[:partyconcave ] = @set[id][:partyconcave ] if h[:partyconcave ]
v[:partyconvex ] = @set[id][:partyconvex ] if h[:partyconvex ]
@@ -762,10 +788,11 @@
max = [v[:parapetconcave], v[:parapetconvex]].max
v[:parapet] = max unless @has[:parapet]
max = [v[:roofconcave], v[:roofconvex]].max
v[:roof] = max unless @has[:roof]
+
@val[id] = v
true
end
@@ -781,10 +808,13 @@
# @option set [#to_f] :parapetconcave basilaire variant
# @option set [#to_f] :parapetconvex typical
# @option set [#to_f] :roof roof-to-wall intersection
# @option set [#to_f] :roofconcave basilaire variant
# @option set [#to_f] :roofconvex typical
+ # @option set [#to_f] :ceiling intermediate (uninsulated) ceiling perimeter
+ # @option set [#to_f] :ceilingconcave cantilever variant
+ # @option set [#to_f] :ceilingconvex colonnade variant
# @option set [#to_f] :fenestration head/sill/jamb interface
# @option set [#to_f] :head (fenestrated) header interface
# @option set [#to_f] :headconcave (fenestrated) basilaire variant
# @option set [#to_f] :headconvex (fenestrated) parapet variant
# @option set [#to_f] :sill (fenestrated) threshold/sill interface
@@ -868,10 +898,13 @@
s[:parapetconcave ] = set[:parapetconcave ] if set.key?(:parapetconcave)
s[:parapetconvex ] = set[:parapetconvex ] if set.key?(:parapetconvex)
s[:roof ] = set[:roof ] if set.key?(:roof)
s[:roofconcave ] = set[:roofconcave ] if set.key?(:roofconcave)
s[:roofconvex ] = set[:roofconvex ] if set.key?(:roofconvex)
+ s[:ceiling ] = set[:ceiling ] if set.key?(:ceiling)
+ s[:ceilingconcave ] = set[:ceilingconcave ] if set.key?(:ceilingconcave)
+ s[:ceilingconvex ] = set[:ceilingconvex ] if set.key?(:ceilingconvex)
s[:fenestration ] = set[:fenestration ] if set.key?(:fenestration)
s[:head ] = set[:head ] if set.key?(:head)
s[:headconcave ] = set[:headconcave ] if set.key?(:headconcave)
s[:headconvex ] = set[:headconvex ] if set.key?(:headconvex)
s[:sill ] = set[:sill ] if set.key?(:sill)
@@ -924,10 +957,11 @@
s[:joint ] = set[:joint ] if set.key?(:joint)
s[:transition ] = set[:transition ] if set.key?(:transition)
s[:joint ] = 0.000 unless set.key?(:joint)
s[:transition ] = 0.000 unless set.key?(:transition)
+ s[:ceiling ] = 0.000 unless set.key?(:ceiling)
@set[id] = s
self.gen(id)
true
@@ -939,17 +973,17 @@
# Hash of PSI-factors (values) for any admissible PSI type (keys).
# PSI-factors default to 0 W/K per linear meter if missing from set.
#
# @param id [#to_s] PSI set identifier
# @example intermediate floor slab intersection
- # shorthands("A901")
+ # shorthands("90.1.22|steel.m|default")
#
# @return [Hash] has: Hash (Bool), val: Hash (PSI factors) see logs if empty
def shorthands(id = "")
mth = "TBD::#{__callee__}"
sh = { has: {}, val: {} }
- id = trim(id)
+ id = trim(id)
return mismatch("set ID", id, String, mth, ERR, a) if id.empty?
return hashkey(id, @set , id, mth, ERR, sh) unless @set.key?(id)
return hashkey(id, @has , id, mth, ERR, sh) unless @has.key?(id)
return hashkey(id, @val , id, mth, ERR, sh) unless @val.key?(id)
@@ -967,11 +1001,11 @@
# @return [Bool] whether provided PSI set is held in memory and is complete
# @return [false] if invalid input (see logs)
def complete?(id = "")
mth = "TBD::#{__callee__}"
a = false
- id = trim(id)
+ id = trim(id)
return mismatch("set ID", id, String, mth, ERR, a) if id.empty?
return hashkey(id, @set , id, mth, ERR, a) unless @set.key?(id)
return hashkey(id, @has , id, mth, ERR, a) unless @has.key?(id)
return hashkey(id, @val , id, mth, ERR, a) unless @val.key?(id)
@@ -1851,30 +1885,34 @@
next unless tbd[:surfaces].key?(id)
next unless deratables.include?(id)
# Evaluate current set content before processing a new linked surface.
is = {}
- is[:head ] = set.keys.to_s.include?("head")
- is[:sill ] = set.keys.to_s.include?("sill")
- is[:jamb ] = set.keys.to_s.include?("jamb")
is[:doorhead ] = set.keys.to_s.include?("doorhead")
is[:doorsill ] = set.keys.to_s.include?("doorsill")
is[:doorjamb ] = set.keys.to_s.include?("doorjamb")
is[:skylighthead ] = set.keys.to_s.include?("skylighthead")
is[:skylightsill ] = set.keys.to_s.include?("skylightsill")
is[:skylightjamb ] = set.keys.to_s.include?("skylightjamb")
is[:spandrel ] = set.keys.to_s.include?("spandrel")
is[:corner ] = set.keys.to_s.include?("corner")
is[:parapet ] = set.keys.to_s.include?("parapet")
is[:roof ] = set.keys.to_s.include?("roof")
+ is[:ceiling ] = set.keys.to_s.include?("ceiling")
is[:party ] = set.keys.to_s.include?("party")
is[:grade ] = set.keys.to_s.include?("grade")
is[:balcony ] = set.keys.to_s.include?("balcony")
is[:balconysill ] = set.keys.to_s.include?("balconysill")
is[:balconydoorsill ] = set.keys.to_s.include?("balconydoorsill")
is[:rimjoist ] = set.keys.to_s.include?("rimjoist")
+ if is.empty?
+ is[:head] = set.keys.to_s.include?("head")
+ is[:sill] = set.keys.to_s.include?("sill")
+ is[:jamb] = set.keys.to_s.include?("jamb")
+ end
+
# Label edge as ...
# :head, :sill, :jamb (vertical fenestration)
# :doorhead, :doorsill, :doorjamb (opaque door)
# :skylighthead, :skylightsill, :skylightjamb (all other cases)
#
@@ -2090,10 +2128,45 @@
set[:cornerconcave] = shorts[:val][:cornerconcave] if concave
set[:cornerconvex ] = shorts[:val][:cornerconvex ] if convex
is[:corner ] = true
end
+ # Label edge as :ceiling if linked to:
+ # +1 deratable surfaces
+ # 1x underatable CONDITIONED floor linked to an unoccupied space
+ # 1x adjacent CONDITIONED ceiling linked to an occupied space
+ edge[:surfaces].keys.each do |i|
+ break if is[:ceiling]
+ break unless deratables.size > 0
+ break if floors.key?(id)
+ next if i == id
+ next unless floors.key?(i)
+ next if floors[i][:ground ]
+ next unless floors[i][:conditioned]
+ next if floors[i][:occupied ]
+
+ ceiling = floors[i][:boundary]
+ next unless ceilings.key?(ceiling)
+ next unless ceilings[ceiling][:conditioned]
+ next unless ceilings[ceiling][:occupied ]
+
+ other = deratables.first unless deratables.first == id
+ other = deratables.last unless deratables.last == id
+ other = id if deratables.size == 1
+
+ s1 = edge[:surfaces][id]
+ s2 = edge[:surfaces][other]
+ concave = concave?(s1, s2)
+ convex = convex?(s1, s2)
+ flat = !concave && !convex
+
+ set[:ceiling ] = shorts[:val][:ceiling ] if flat
+ set[:ceilingconcave] = shorts[:val][:ceilingconcave] if concave
+ set[:ceilingconvex ] = shorts[:val][:ceilingconvex ] if convex
+ is[:ceiling ] = true
+ end
+
# Label edge as :parapet/:roof if linked to:
# 1x deratable wall
# 1x deratable ceiling
edge[:surfaces].keys.each do |i|
break if is[:parapet]
@@ -2170,26 +2243,27 @@
set[:gradeconcave] = shorts[:val][:gradeconcave] if concave
set[:gradeconvex ] = shorts[:val][:gradeconvex ] if convex
is[:grade ] = true
end
- # Label edge as :rimjoist, :balcony, :balconysill or :balconydoorsill if linked to:
+ # Label edge as :rimjoist, :balcony, :balconysill or :balconydoorsill,
+ # if linked to:
# 1x deratable surface
# 1x CONDITIONED floor
# 1x shade (optional)
# 1x subsurface (optional)
- #
- # Despite referring to 'sill' or 'doorsill', a 'balconysill' or
- # 'balconydoorsill' edge may instead link (rarer) cases of balcony and a
- # fenestratio/door head. ASHRAE 90.1 2022 does not make the distinction
- # between sill vs head when intermediatre floor, balcony and vertical
- # fenestration meet. 'Sills' are simply the most common occurrence.
balcony = false
balconysill = false # vertical fenestration
balconydoorsill = false # opaque door
+ # Despite referring to 'sill' or 'doorsill', a 'balconysill' or
+ # 'balconydoorsill' edge may instead link (rarer) cases of balcony and a
+ # fenestration/door head. ASHRAE 90.1 2022 does not make the distinction
+ # between sill vs head when intermediate floor, balcony and vertical
+ # fenestration meet. 'Sills' are simply the most common occurrence.
edge[:surfaces].keys.each do |i|
+ break if is[:ceiling]
break if balcony
next if i == id
balcony = shades.key?(i)
end
@@ -2257,18 +2331,20 @@
balconydoorsill = true
end
end
edge[:surfaces].keys.each do |i|
- break if is[:rimjoist ] || is[:balcony ] ||
- is[:balconysill] || is[:balconydoorsill]
+ break if is[:ceiling ]
+ break if is[:rimjoist ]
+ break if is[:balcony ]
+ break if is[:balconysill ]
+ break if is[:balconydoorsill]
break unless deratables.size > 0
break if floors.key?(id)
next if i == id
next unless floors.key?(i)
- next unless floors[i].key?(:conditioned)
- next unless floors[i][:conditioned]
next if floors[i][:ground ]
+ next unless floors[i][:conditioned]
other = deratables.first unless deratables.first == id
other = deratables.last unless deratables.last == id
other = id if deratables.size == 1