Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initializes nrcan_446 branch: skylights & wells #1854

Open
wants to merge 10 commits into
base: nrcan
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Gemfile.lock.3.7.0
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PATH
remote: .
specs:
openstudio-standards (0.6.3)
tbd (~> 3)
tbd (3.4.4)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -73,7 +73,7 @@ GEM
openstudio-api-stubs (0.1.0)
oslg (0.3.0)
osut (0.6.0)
oslg (>= 0.3.0)
oslg (0.3.0)
parallel (1.26.3)
parallel_tests (3.7.3)
parallel
Expand Down Expand Up @@ -137,10 +137,10 @@ GEM
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
stringio (3.1.2)
tbd (3.4.3)
tbd (3.4.4)
json-schema (~> 4)
osut (~> 0)
topolys (~> 0)
osut (0.3.0)
topolys (0.6.2)
thor (1.3.2)
tilt (2.4.0)
topolys (0.6.2)
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock.3.8.0
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PATH
remote: .
specs:
openstudio-standards (0.6.3)
tbd (~> 3)
tbd (3.4.4)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -71,7 +71,7 @@ GEM
openstudio-api-stubs (0.1.0)
oslg (0.3.0)
osut (0.6.0)
oslg (>= 0.3.0)
oslg (0.3.0)
parallel (1.26.3)
parallel_tests (3.7.3)
parallel
Expand Down Expand Up @@ -135,10 +135,10 @@ GEM
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
stringio (3.1.2)
tbd (3.4.3)
tbd (3.4.4)
json-schema (~> 4)
osut (~> 0)
topolys (~> 0)
osut (0.6.0)
topolys (0.6.2)
thor (1.3.2)
tilt (2.4.0)
topolys (0.6.2)
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock.3.9.0
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PATH
remote: .
specs:
openstudio-standards (0.6.3)
tbd (~> 3)
tbd (3.4.4)

GEM
remote: http://rubygems.org/
Expand Down Expand Up @@ -71,7 +71,7 @@ GEM
openstudio-api-stubs (0.1.0)
oslg (0.3.0)
osut (0.6.0)
oslg (>= 0.3.0)
oslg (0.3.0)
parallel (1.26.3)
parallel_tests (3.7.3)
parallel
Expand Down Expand Up @@ -135,10 +135,10 @@ GEM
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
stringio (3.1.2)
tbd (3.4.3)
tbd (3.4.4)
json-schema (~> 4)
osut (~> 0)
topolys (~> 0)
osut (0.6.0)
topolys (0.6.2)
thor (1.3.2)
tilt (2.4.0)
topolys (0.6.2)
Expand Down
4 changes: 2 additions & 2 deletions lib/openstudio-standards/btap/bridging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1502,7 +1502,7 @@ def initialize(model = nil, argh = {})
next unless construction[:stypes ] == stypes
next if construction[:surfaces].empty?

construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) }
# construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) }
Copy link
Collaborator Author

@brgix brgix Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this call is attempting, but commenting it out allows BTAP thermal bridging tests to pass. A revised skylight/well distribution would affect (linear) thermal bridging calculations down the line ...

end
end

Expand Down Expand Up @@ -2115,4 +2115,4 @@ def get_material_quantities()
#
# ... yet all (public) washrooms, corridors, stairwells, etc. are
# steel-framed (regardless of building type). Overview of possible fixes.
# TO-DO.
# TO-DO.
23 changes: 13 additions & 10 deletions lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,24 @@ def load_standards_database_new
end

# Thermal zones need to be set to determine conditioned spaces when applying fdwr and srr limits.
# # fdwr_set/srr_set settings:
# # 0-1: Remove all windows/skylights and add windows/skylights to match this fdwr/srr
# # -1: Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB
# # -2: Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1)
# # -3: Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr
# # limit
# # <-3.1: Remove all the windows/skylights
# # > 1: Do nothing
def apply_fdwr_srr_daylighting(model:, fdwr_set: -2.0, srr_set: -2.0, necb_hdd: true)
#
# fdwr_set/srr_set settings:
# 0-1: Remove all windows/skylights and add windows/skylights to match this fdwr/srr
# -1: Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB
# -2: Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1)
# -3: Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr limit
# <-3.1: Remove all the windows/skylights
# > 1: Do nothing
#
# By default, :osut is set to 'false'. If :osut is set to 'true', SRR is
# instead met using OSut's addSkylights (:srr_set numeric values may apply).
def apply_fdwr_srr_daylighting(model:, fdwr_set: -2.0, srr_set: -2.0, necb_hdd: true, osut: false)
fdwr_set = -2.0 if (fdwr_set == 'NECB_default') || fdwr_set.nil? || (fdwr_set.to_f.round(0) == -1.0)
srr_set = -2.0 if (srr_set == 'NECB_default') || srr_set.nil? || (srr_set.to_f.round(0) == -1.0)
fdwr_set = fdwr_set.to_f
srr_set = srr_set.to_f
apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set, necb_hdd: true)
apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set)
apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set, osut: osut)
# model_add_daylighting_controls(model) # to be removed after refactor.
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ def apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0, necb_hdd: true)
# Reduces the SRR to the values specified by the PRM. SRR reduction
# will be done by shrinking vertices toward the centroid.
#
def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)
def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, osut: false)
# If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB. If srr_set is between 0.0 and 1.0
# apply whatever was passed. If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum
# only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled).

#
# srr_set settings:
# 0-1: Remove all skylights and add skylights to match this srr
# -1: Remove all skylights and add skylights to match max srr from NECB
# -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1)
# -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit
# <-3.1: Remove all the skylights
# > 1: Do nothing

# 0-1: Remove all skylights and add skylights to match this srr
# -1: Remove all skylights and add skylights to match max srr from NECB
# -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1)
# -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit
# <-3.1: Remove all skylights
# > 1: Do nothing
#
# By default, :osut is set to 'false'. If :osut is set to 'true', SRR is
# instead met using OSut's addSkylights (:srr_set numeric values may apply). @todo
return if srr_set.to_f > 1.0
return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f >= 0.0 && srr_set <= 1.0

Expand Down
166 changes: 121 additions & 45 deletions lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,26 +160,29 @@ def apply_limit_fdwr(model:, fdwr_lim:)

# Reduces the SRR to the values specified by the PRM. SRR reduction
# will be done by shrinking vertices toward the centroid.
#
def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)
def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, srr_opt: '')
# If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB. If srr_set is between 0.0 and 1.0
# apply whatever was passed. If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum
# only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled).

#
# srr_set settings:
# 0-1: Remove all skylights and add skylights to match this srr
# -1: Remove all skylights and add skylights to match max srr from NECB
# -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1)
# -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit
# 0-1: Remove all skylights and add skylights to match this srr
# -1: Remove all skylights and add skylights to match max srr from NECB
# -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1)
# -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit
# <-3.1: Remove all skylights
# > 1: Do nothing

# > 1: Do nothing
#
# By default, :srr_opt is an empty string (" "). If set to "osut", SRR is
# instead met using OSut's addSkylights (:srr_set numeric values may apply).
return if srr_set.to_f > 1.0
return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f >= 0.0 && srr_set.to_f <= 1.0
return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f, srr_opt: srr_opt) if srr_set.to_f >= 0.0 && srr_set <= 1.0

# Get the maximum NECB srr
return apply_max_srr_nrcan(model: model, srr_lim: get_standards_constant('skylight_to_roof_ratio_max_value').to_f) if srr_set.to_f >= -1.1 && srr_set.to_f <= -0.9
return apply_max_srr_nrcan(model: model, srr_lim: get_standards_constant('skylight_to_roof_ratio_max_value').to_f, srr_opt: srr_opt) if srr_set.to_f >= -1.1 && srr_set.to_f <= -0.9

return if srr_set.to_f >= -2.1 && srr_set.to_f <= -1.9
return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f < -3.1
return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f, srr_opt: srr_opt) if srr_set.to_f < -3.1
return unless srr_set.to_f >= -3.1 && srr_set.to_f <= -2.9

# SRR limit
Expand All @@ -197,6 +200,7 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)
sh_sky_m2 = 0
total_roof_m2 = 0.001
total_subsurface_m2 = 0

model.getSpaces.sort.each do |space|
# Loop through all surfaces in this space
wall_area_m2 = 0
Expand Down Expand Up @@ -724,44 +728,116 @@ def apply_max_fdwr_nrcan(model:, fdwr_lim:)
return true
end

# This method is similar to the 'apply_max_fdwr' method above but applies the maximum skylight to roof area ratio to a
# building as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other versions of the NECB). It first checks for all
# exterior roofs adjacent to conditioned spaces. It distinguishes between plenums and other conditioned spaces. It
# uses only the non-plenum roof area to calculate the maximum skylight area to be applied to the building.
def apply_max_srr_nrcan(model:, srr_lim:)
# First determine which roof surfaces are adjacent to heated spaces (both plenum and non-plenum).
exp_surf_info = find_exposed_conditioned_roof_surfaces(model)
# If the non-plenum roof area is very small raise a warning. It may be perfectly fine but it is probably a good
# idea to warn the user.
if exp_surf_info['exp_nonplenum_roof_area_m2'] < 0.1
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'This building has no exposed ceilings adjacent to spaces that are not attics or plenums. No skylights will be added.')
return false
end
# This method is similar to the 'apply_max_fdwr' method above, but applies a maximum skylight-to-roof-area-ratio (SRR)
# to a building model, as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other NECB vintages). There are 2 options:
#
# OPTION A: Default, initial BTAP solution. It first checks for all exterior roofs adjacent to conditioned spaces.
# It distinguishes between plenums and other conditioned spaces. It uses only the non-plenum roof area to
# calculate the maximum skylight area to be applied to the building.
#
# OPTION B: Selected if srr_opt == 'osut'. OSut's 'addSkylights' attempts to meet the requested SRR% target, even if
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method apply_max_srr_nrcan now offers two options. By default, OSut's addSkylights is not called (srr_opt: is defaulted to an empty string). If instead set to 'osut', BTAP would rely on addSkylights to match the requested :srr_lim, either to match NECB fixed SRR% requirements, or some user-set SRR%.

# occupied spaces to toplight are under unoccupied plenums or attics - skylight wells are added if needed.
# With attics, skylight well walls are considered part of the 'building envelope' (and therefore insulated
# like exterior walls). The method returns a building 'gross roof area' (see attr_reader :osut), which
# excludes the area of attic roof overhangs.
def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '')
construct_set = model.getBuilding.defaultConstructionSet.get
skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get

# If the SRR is greater than one something is seriously wrong so raise an error. If it is less than 0.001 assume
# all the skylights should go.
if srr_lim > 1
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'This building requires a larger skylight area than there is roof area.')
return false
elsif srr_lim < 0.001
exp_surf_info['exp_nonplenum_roofs'].sort.each do |exp_surf|
exp_surf.subSurfaces.sort.each(&:remove)
unless srr_opt.to_s.downcase == 'osut' # OPTION A
# First determine which roof surfaces are adjacent to heated spaces (both plenum and non-plenum).
exp_surf_info = find_exposed_conditioned_roof_surfaces(model)
# If the non-plenum roof area is very small raise a warning. It may be perfectly fine but it is probably a good
# idea to warn the user.
if exp_surf_info['exp_nonplenum_roof_area_m2'] < 0.1
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'This building has no exposed ceilings adjacent to spaces that are not attics or plenums. No skylights will be added.')
return false
end
return true
end

construct_set = model.getBuilding.defaultConstructionSet.get
skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get
# If the SRR is greater than one something is seriously wrong so raise an error. If it is less than 0.001 assume
# all the skylights should go.
if srr_lim > 1
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'This building requires a larger skylight area than there is roof area.')
return false
elsif srr_lim < 0.001
exp_surf_info['exp_nonplenum_roofs'].sort.each do |exp_surf|
exp_surf.subSurfaces.sort.each(&:remove)
end
return true
end

# Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add
# a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area
# determined by the SRR. The name of the skylight will be the surface name with the subsurface type attached
# ('skylight' in this case). Note that this method will only work if the surface does not fold into itself (like an
# L or a V).
exp_surf_info['exp_nonplenum_roofs'].sort.each do |roof|
# sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model)
sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, construction: skylight_construct_set)
end
else # OPTION B
spaces = model.getSpaces
types = model.getSpaceTypes
roofs = TBD.facets(spaces, "Outdoors", "RoofCeiling")

# A model's 'nominal' gross roof area (gra0) may be greater than its
# 'effective' gross roof area (graX). For instance, the "SmallOffice"
# prototype model has (unconditioned) attic roof overhangs that end up
# tallied as a gross roof area in OpenStudio and EnergyPlus. See:
#
# github.com/rd2/osut/blob/117c7dceb59fd8aab771da8ba672c14c97d23bd0
# /lib/osut/utils.rb#L6268
#
gra0 = roofs.sum(&:grossArea) # nominal gross roof area
graX = TBD.grossRoofArea(spaces) # effective gross roof area

unless gra0.round > 0
msg = 'Invalid nominal gross roof area. No skylights will be added.'
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', msg)
return false
end

unless graX.round > 0
msg = 'Invalid effective gross roof area. No skylights will be added.'
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', msg)
return false
end

# Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add
# a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area
# determined by the SRR. The name of the skylight will be the surface name with the subsurface type attached
# ('skylight' in this case). Note that this method will only work if the surface does not fold into itself (like an
# L or a V).
exp_surf_info['exp_nonplenum_roofs'].sort.each do |roof|
# sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model)
sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, construction: skylight_construct_set)
self.osut[:gra0] = gra0
self.osut[:graX] = graX

# Relying on the total area of attic roof surfaces (for SRR%) exagerrates
# the requested skylight area, often by 10% to 15%. This makes it unfair
# for NECBs, and more challenging when dealing with skylight wells. This
# issue only applies with attics - not plenums. Trim down SRR if required.
target = (srr_lim * graX / gra0) * graX
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OSut's addSkylights yields better results if smaller utility/technical rooms (e.g. narrow corridors, WCs) are first set aside. If feasible, addSkylights prioritizes larger spaces that aren't daylit.

hospital

Hospital prototype

For this reason, it is preferable to send as argument the actual SRR-based skylight area (addSkyLights(spaces, {area: target})), rather than the initial SRR (addSkyLights(spaces, {srr: 0.05)). In both example addSkylights calls here, spaces is a subset of model.getSpaces.

Gross roof area (GRA) calculations should exclude (unconditioned) attic roof overhangs, such as in the SmallOffice prototype model. The calculated skylight area target is adjusted if attic overhangs are detected. No overhangs (i.e. graX/gra0 equals unity)? No adjustment.


# Filtering out tiny roof surfaces, twisty corridors, etc.
types = types.reject { |tp| tp.nameString.downcase.include?("undefined") }
types = types.reject { |tp| tp.nameString.downcase.include?("mech" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("elec" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("toilet" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("locker" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("shower" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("washroom" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("corr" ) }
types = types.reject { |tp| tp.nameString.downcase.include?("stair" ) }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacetype string patterns that are currently proposed to filter out utility/technical rooms.


spaces = spaces.reject { |sp| sp.spaceType.empty? }
spaces = spaces.select { |sp| types.include?(sp.spaceType.get) }

TBD.addSkyLights(spaces, {area: target})

skys = TBD.facets(model.getSpaces, "Outdoors", "Skylight")
skm2 = skys.sum(&:grossArea)

unless skm2.round == target.round
msg = "Skylights m2: failed to meet #{target.round} (vs #{skm2.round})"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Challenging model geometry (e.g. invalid geometry, tiny rooms, heavily tessellated roof surfaces) is usually behind the rare cases where addSkylights fails to meet the requested target area. Skylights (and if required, skylight wells) may still be added - yet possibly insufficient in number to reach the target.

neduc_roof

NorthernEducation BTAP prototype, meeting the requested building 5% SRR.

neduc_nroof

Same NorthernEducation BTAP prototype, yet without rendering plenum roof surfaces - only skylight well walls

OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', msg)
return false
end
end

return true
end
end
Loading