lib/osut/utils.rb in osut-0.2.2 vs lib/osut/utils.rb in osut-0.2.3
- old
+ new
@@ -33,16 +33,16 @@
module OSut
extend OSlg # DEBUG for devs; WARN/ERROR for users (bad OS input)
TOL = 0.01
TOL2 = TOL * TOL
- NS = "nameString" # OpenStudio IdfObject nameString method
DBG = OSut::DEBUG # mainly to flag invalid arguments to devs (buggy code)
INF = OSut::INFO # not currently used in OSut
WRN = OSut::WARN # WARN users of 'iffy' .osm inputs (yet not critical)
ERR = OSut::ERROR # flag invalid .osm inputs (then exit via 'return')
FTL = OSut::FATAL # not currently used in OSut
+ NS = "nameString" # OpenStudio IdfObject nameString method
# This first set of utilities (~750 lines) help distinguishing spaces that
# are directly vs indirectly CONDITIONED, vs SEMI-HEATED. The solution here
# relies as much as possible on space conditioning categories found in
# standards like ASHRAE 90.1 and energy codes like the Canadian NECB editions.
@@ -125,36 +125,36 @@
#
# @param sched [OpenStudio::Model::ScheduleRuleset] schedule
#
# @return [Hash] min: (Float), max: (Float)
# @return [Hash] min: nil, max: nil (if invalid input)
- def scheduleRulesetMinMax(sched)
+ def scheduleRulesetMinMax(sched = nil)
# Largely inspired from David Goldwasser's
# "schedule_ruleset_annual_min_max_value":
#
# github.com/NREL/openstudio-standards/blob/
# 99cf713750661fe7d2082739f251269c2dfd9140/lib/openstudio-standards/
# standards/Standards.ScheduleRuleset.rb#L124
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::ScheduleRuleset
res = { min: nil, max: nil }
- return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
+ return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
id = sched.nameString
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
profiles = []
profiles << sched.defaultDaySchedule
sched.scheduleRules.each { |rule| profiles << rule.daySchedule }
profiles.each do |profile|
id = profile.nameString
+
profile.values.each do |val|
- unless val.is_a?(Numeric)
- log(WRN, "Skipping non-numeric profile values in '#{id}' (#{mth})")
- next
- end
+ ok = val.is_a?(Numeric)
+ log(WRN, "Skipping non-numeric value in '#{id}' (#{mth})") unless ok
+ next unless ok
res[:min] = val unless res[:min]
res[:min] = val if res[:min] > val
res[:max] = val unless res[:max]
res[:max] = val if res[:max] < val
@@ -172,31 +172,29 @@
#
# @param sched [OpenStudio::Model::ScheduleConstant] schedule
#
# @return [Hash] min: (Float), max: (Float)
# @return [Hash] min: nil, max: nil (if invalid input)
- def scheduleConstantMinMax(sched)
+ def scheduleConstantMinMax(sched = nil)
# Largely inspired from David Goldwasser's
# "schedule_constant_annual_min_max_value":
#
# github.com/NREL/openstudio-standards/blob/
# 99cf713750661fe7d2082739f251269c2dfd9140/lib/openstudio-standards/
# standards/Standards.ScheduleConstant.rb#L21
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::ScheduleConstant
res = { min: nil, max: nil }
- return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
+ return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
id = sched.nameString
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
- unless sched.value.is_a?(Numeric)
- return mismatch("'#{id}' value", sched.value, Numeric, mth, ERR, res)
- else
- res[:min] = sched.value
- res[:max] = sched.value
- end
+ valid = sched.value.is_a?(Numeric)
+ mismatch("'#{id}' value", sched.value, Numeric, mth, ERR, res) unless valid
+ res[:min] = sched.value
+ res[:max] = sched.value
res
end
##
@@ -204,11 +202,11 @@
#
# @param sched [OpenStudio::Model::ScheduleCompact] schedule
#
# @return [Hash] min: (Float), max: (Float)
# @return [Hash] min: nil, max: nil (if invalid input)
- def scheduleCompactMinMax(sched)
+ def scheduleCompactMinMax(sched = nil)
# Largely inspired from Andrew Parker's
# "schedule_compact_annual_min_max_value":
#
# github.com/NREL/openstudio-standards/blob/
# 99cf713750661fe7d2082739f251269c2dfd9140/lib/openstudio-standards/
@@ -217,11 +215,11 @@
cl = OpenStudio::Model::ScheduleCompact
vals = []
prev_str = ""
res = { min: nil, max: nil }
- return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
+ return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
id = sched.nameString
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
sched.extensibleGroups.each do |eg|
if prev_str.include?("until")
@@ -231,46 +229,42 @@
str = eg.getString(0)
prev_str = str.get.downcase unless str.empty?
end
return empty("'#{id}' values", mth, ERR, res) if vals.empty?
+ ok = vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
+ log(ERR, "Non-numeric values in '#{id}' (#{mth})") unless ok
+ return res unless ok
+ res[:min] = vals.min
+ res[:max] = vals.max
- if vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
- res[:min] = vals.min
- res[:max] = vals.max
- else
- log(ERR, "Non-numeric values in '#{id}' (#{mth})")
- end
-
res
end
##
# Return min & max values for schedule (interval).
#
# @param sched [OpenStudio::Model::ScheduleInterval] schedule
#
# @return [Hash] min: (Float), max: (Float)
# @return [Hash] min: nil, max: nil (if invalid input)
- def scheduleIntervalMinMax(sched)
+ def scheduleIntervalMinMax(sched = nil)
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::ScheduleInterval
vals = []
prev_str = ""
res = { min: nil, max: nil }
- return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
+ return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
id = sched.nameString
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
-
vals = sched.timeSeries.values
- if vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
- res[:min] = vals.min
- res[:max] = vals.max
- else
- log(ERR, "Non-numeric values in '#{id}' (#{mth})")
- end
+ ok = vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
+ log(ERR, "Non-numeric values in '#{id}' (#{mth})") unless ok
+ return res unless ok
+ res[:min] = vals.min
+ res[:max] = vals.max
res
end
##
@@ -279,11 +273,11 @@
#
# @param zone [OpenStudio::Model::ThermalZone] a thermal zone
#
# @return [Hash] spt: (Float), dual: (Bool)
# @return [Hash] spt: nil, dual: false (if invalid input)
- def maxHeatScheduledSetpoint(zone)
+ def maxHeatScheduledSetpoint(zone = nil)
# Largely inspired from Parker & Marrec's "thermal_zone_heated?" procedure.
# The solution here is a tad more relaxed to encompass SEMI-HEATED zones as
# per Canadian NECB criteria (basically any space with at least 10 W/m2 of
# installed heating equipement, i.e. below freezing in Canada).
#
@@ -292,11 +286,11 @@
# standards/Standards.ThermalZone.rb#L910
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::ThermalZone
res = { spt: nil, dual: false }
- return invalid("zone", mth, 1, DBG, res) unless zone.respond_to?(NS)
+ return invalid("zone", mth, 1, DBG, res) unless zone.respond_to?(NS)
id = zone.nameString
return mismatch(id, zone, cl, mth, DBG, res) unless zone.is_a?(cl)
# Zone radiant heating? Get schedule from radiant system.
zone.equipment.each do |equip|
@@ -308,13 +302,20 @@
unless equip.heatingSetpointTemperatureSchedule.empty?
sched = equip.heatingSetpointTemperatureSchedule.get
end
end
+ # unless equip.to_ZoneHVACLowTemperatureRadiantElectric.empty?
+ # equip = equip.to_ZoneHVACLowTemperatureRadiantElectric.get
+ #
+ # unless equip.heatingSetpointTemperatureSchedule.empty?
+ # sched = equip.heatingSetpointTemperatureSchedule.get
+ # end
+ # end
+
unless equip.to_ZoneHVACLowTemperatureRadiantElectric.empty?
equip = equip.to_ZoneHVACLowTemperatureRadiantElectric.get
-
unless equip.heatingSetpointTemperatureSchedule.empty?
sched = equip.heatingSetpointTemperatureSchedule.get
end
end
@@ -375,26 +376,26 @@
res[:spt] = max if res[:spt] < max
end
end
end
- return res if res[:spt]
return res if zone.thermostat.empty?
- tstat = zone.thermostat.get
+ tstat = zone.thermostat.get
+ res[:spt] = nil
unless tstat.to_ThermostatSetpointDualSetpoint.empty? &&
tstat.to_ZoneControlThermostatStagedDualSetpoint.empty?
- res[:dual] = true
unless tstat.to_ThermostatSetpointDualSetpoint.empty?
tstat = tstat.to_ThermostatSetpointDualSetpoint.get
else
tstat = tstat.to_ZoneControlThermostatStagedDualSetpoint.get
end
unless tstat.heatingSetpointTemperatureSchedule.empty?
- sched = tstat.heatingSetpointTemperatureSchedule.get
+ res[:dual] = true
+ sched = tstat.heatingSetpointTemperatureSchedule.get
unless sched.to_ScheduleRuleset.empty?
sched = sched.to_ScheduleRuleset.get
max = scheduleRulesetMinMax(sched)[:max]
@@ -454,15 +455,14 @@
#
# @param model [OpenStudio::Model::Model] a model
#
# @return [Bool] true if valid heating temperature setpoints
# @return [Bool] false if invalid input
- def heatingTemperatureSetpoints?(model)
+ def heatingTemperatureSetpoints?(model = nil)
mth = "OSut::#{__callee__}"
- cl = OpenStudio::Model::Model
+ cl = OpenStudio::Model::Model
- return invalid("model", mth, 1, DBG, false) unless model
return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
model.getThermalZones.each do |zone|
return true if maxHeatScheduledSetpoint(zone)[:spt]
end
@@ -476,21 +476,21 @@
#
# @param zone [OpenStudio::Model::ThermalZone] a thermal zone
#
# @return [Hash] spt: (Float), dual: (Bool)
# @return [Hash] spt: nil, dual: false (if invalid input)
- def minCoolScheduledSetpoint(zone)
+ def minCoolScheduledSetpoint(zone = nil)
# Largely inspired from Parker & Marrec's "thermal_zone_cooled?" procedure.
#
# github.com/NREL/openstudio-standards/blob/
# 99cf713750661fe7d2082739f251269c2dfd9140/lib/openstudio-standards/
# standards/Standards.ThermalZone.rb#L1058
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::ThermalZone
res = { spt: nil, dual: false }
- return invalid("zone", mth, 1, DBG, res) unless zone.respond_to?(NS)
+ return invalid("zone", mth, 1, DBG, res) unless zone.respond_to?(NS)
id = zone.nameString
return mismatch(id, zone, cl, mth, DBG, res) unless zone.is_a?(cl)
# Zone radiant cooling? Get schedule from radiant system.
zone.equipment.each do |equip|
@@ -553,26 +553,26 @@
res[:spt] = min if res[:spt] > min
end
end
end
- return res if res[:spt]
return res if zone.thermostat.empty?
- tstat = zone.thermostat.get
+ tstat = zone.thermostat.get
+ res[:spt] = nil
unless tstat.to_ThermostatSetpointDualSetpoint.empty? &&
tstat.to_ZoneControlThermostatStagedDualSetpoint.empty?
- res[:dual] = true
unless tstat.to_ThermostatSetpointDualSetpoint.empty?
tstat = tstat.to_ThermostatSetpointDualSetpoint.get
else
tstat = tstat.to_ZoneControlThermostatStagedDualSetpoint.get
end
unless tstat.coolingSetpointTemperatureSchedule.empty?
- sched = tstat.coolingSetpointTemperatureSchedule.get
+ res[:dual] = true
+ sched = tstat.coolingSetpointTemperatureSchedule.get
unless sched.to_ScheduleRuleset.empty?
sched = sched.to_ScheduleRuleset.get
min = scheduleRulesetMinMax(sched)[:min]
@@ -632,15 +632,14 @@
#
# @param model [OpenStudio::Model::Model] a model
#
# @return [Bool] true if valid cooling temperature setpoints
# @return [Bool] false if invalid input
- def coolingTemperatureSetpoints?(model)
+ def coolingTemperatureSetpoints?(model = nil)
mth = "OSut::#{__callee__}"
- cl = OpenStudio::Model::Model
+ cl = OpenStudio::Model::Model
- return invalid("model", mth, 1, DBG, false) unless model
return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
model.getThermalZones.each do |zone|
return true if minCoolScheduledSetpoint(zone)[:spt]
end
@@ -653,15 +652,14 @@
#
# @param model [OpenStudio::Model::Model] a model
#
# @return [Bool] true if model has one or more HVAC air loops
# @return [Bool] false if invalid input
- def airLoopsHVAC?(model)
+ def airLoopsHVAC?(model = nil)
mth = "OSut::#{__callee__}"
- cl = OpenStudio::Model::Model
+ cl = OpenStudio::Model::Model
- return invalid("model", mth, 1, DBG, false) unless model
return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
model.getThermalZones.each do |zone|
next if zone.canBePlenum
return true unless zone.airLoopHVACs.empty?
@@ -678,93 +676,83 @@
# @param loops [Bool] true if model has airLoopHVAC object(s)
# @param setpoints [Bool] true if model has valid temperature setpoints
#
# @return [Bool] true if should be tagged as plenum
# @return [Bool] false if invalid input
- def plenum?(space, loops, setpoints)
+ def plenum?(space = nil, loops = nil, setpoints = nil)
# Largely inspired from NREL's "space_plenum?" procedure:
#
# github.com/NREL/openstudio-standards/blob/
# 58964222d25783e9da4ae292e375fb0d5c902aa5/lib/openstudio-standards/
# standards/Standards.Space.rb#L1384
-
+ #
# A space may be tagged as a plenum if:
#
# CASE A: its zone's "isPlenum" == true (SDK method) for a fully-developed
# OpenStudio model (complete with HVAC air loops);
#
- # CASE B: it's excluded from building's total floor area yet linked to a
- # zone holding an "inactive" thermostat (i.e., can't extract
- # valid setpoints);
+ # CASE B: (IN ABSENCE OF HVAC AIRLOOPS) if it's excluded from a building's
+ # total floor area yet linked to a zone holding an 'inactive'
+ # thermostat, i.e. can't extract valid setpoints; OR
#
- # CASE C: it has a spacetype whose name holds "plenum", or a spacetype with
- # a 'standards spacetype' holding "plenum" (case insensitive); OR
- #
- # CASE D: its name string holds "plenum" (also case insensitive).
-
+ # CASE C: (IN ABSENCE OF HVAC AIRLOOPS & VALID SETPOINTS) it has "plenum"
+ # (case insensitive) as a spacetype (or as a spacetype's
+ # 'standards spacetype').
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::Space
- return invalid("space", mth, 1, DBG, false) unless space.respond_to?(NS)
+ return invalid("space", mth, 1, DBG, false) unless space.respond_to?(NS)
id = space.nameString
return mismatch(id, space, cl, mth, DBG, false) unless space.is_a?(cl)
-
valid = loops == true || loops == false
- return invalid("loops", mth, 2, DBG, false) unless valid
-
+ return invalid("loops", mth, 2, DBG, false) unless valid
valid = setpoints == true || setpoints == false
return invalid("setpoints", mth, 3, DBG, false) unless valid
unless space.thermalZone.empty?
zone = space.thermalZone.get
- return true if zone.isPlenum && loops # CASE A
+ return zone.isPlenum if loops # A
if setpoints
- heating = maxHeatScheduledSetpoint(zone)
- cooling = minCoolScheduledSetpoint(zone)
-
- return false if heating[:spt] || cooling[:spt] # directly conditioned
-
- unless space.partofTotalFloorArea
- return true if heating[:dual] || cooling[:dual] # CASE B
- end
+ heat = maxHeatScheduledSetpoint(zone)
+ cool = minCoolScheduledSetpoint(zone)
+ return false if heat[:spt] || cool[:spt] # directly conditioned
+ return heat[:dual] || cool[:dual] unless space.partofTotalFloorArea # B
+ return false
end
end
unless space.spaceType.empty?
type = space.spaceType.get
- return true if type.nameString.downcase.include?("plenum") # CASE C
+ return type.nameString.downcase == "plenum" # C
unless type.standardsSpaceType.empty?
type = type.standardsSpaceType.get
- return true if type.downcase.include?("plenum") # CASE C
+ return type.downcase == "plenum" # C
end
end
- return true if space.nameString.downcase.include?("plenum")
-
false
end
##
# Generate an HVAC availability schedule.
#
# @param model [OpenStudio::Model::Model] a model
# @param avl [String] seasonal availability choice (optional, default "ON")
#
# @return [OpenStudio::Model::Schedule] HVAC availability sched
- # @return [nil] if invalid input
- def availabilitySchedule(model, avl = "")
- mth = "OSut::#{__callee__}"
- cl = OpenStudio::Model::Model
+ # @return [NilClass] if invalid input
+ def availabilitySchedule(model = nil, avl = "")
+ mth = "OSut::#{__callee__}"
+ cl = OpenStudio::Model::Model
+ limits = nil
- return invalid("model", mth, 1) unless model
- return mismatch("model", model, cl, mth) unless model.is_a?(cl)
+ return mismatch("model", model, cl, mth) unless model.is_a?(cl)
+ return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_s)
# Either fetch availability ScheduleTypeLimits object, or create one.
- limits = nil
-
model.getScheduleTypeLimitss.each do |l|
break if limits
next if l.lowerLimitValue.empty?
next if l.upperLimitValue.empty?
next if l.numericType.empty?
@@ -795,11 +783,11 @@
return empty("yearDescription", mth, ERR) if year.empty?
year = year.get
may01 = year.makeDate(OpenStudio::MonthOfYear.new("May"), 1)
oct31 = year.makeDate(OpenStudio::MonthOfYear.new("Oct"), 31)
- case avl.downcase
+ case avl.to_s.downcase
when "winter" # available from November 1 to April 30 (6 months)
val = 1
sch = off
nom = "WINTER Availability SchedRuleset"
dft = "WINTER Availability dftDaySched"
@@ -836,120 +824,106 @@
schedule = schedule.get.to_ScheduleRuleset
unless schedule.empty?
schedule = schedule.get
default = schedule.defaultDaySchedule
- ok = ok && default.nameString == dft
- ok = ok && default.times.size == 1
- ok = ok && default.values.size == 1
- ok = ok && default.times.first == time
- ok = ok && default.values.first == val
+ ok = ok && default.nameString == dft
+ ok = ok && default.times.size == 1
+ ok = ok && default.values.size == 1
+ ok = ok && default.times.first == time
+ ok = ok && default.values.first == val
rules = schedule.scheduleRules
ok = ok && (rules.size == 0 || rules.size == 1)
if rules.size == 1
rule = rules.first
- ok = ok && rule.nameString == tag
+ ok = ok && rule.nameString == tag
ok = ok && !rule.startDate.empty?
ok = ok && !rule.endDate.empty?
- ok = ok && rule.startDate.get == may01
- ok = ok && rule.endDate.get == oct31
+ ok = ok && rule.startDate.get == may01
+ ok = ok && rule.endDate.get == oct31
ok = ok && rule.applyAllDays
d = rule.daySchedule
- ok = ok && d.nameString == day
- ok = ok && d.times.size == 1
- ok = ok && d.values.size == 1
+ ok = ok && d.nameString == day
+ ok = ok && d.times.size == 1
+ ok = ok && d.values.size == 1
ok = ok && d.times.first.totalSeconds == secs
- ok = ok && d.values.first.to_i != val
+ ok = ok && d.values.first.to_i != val
end
return schedule if ok
end
end
schedule = OpenStudio::Model::ScheduleRuleset.new(model)
schedule.setName(nom)
-
- unless schedule.setScheduleTypeLimits(limits)
- log(ERR, "'#{nom}': Can't set schedule type limits (#{mth})")
- return nil
- end
-
- unless schedule.defaultDaySchedule.addValue(time, val)
- log(ERR, "'#{nom}': Can't set default day schedule (#{mth})")
- return nil
- end
-
+ ok = schedule.setScheduleTypeLimits(limits)
+ log(ERR, "'#{nom}': Can't set schedule type limits (#{mth})") unless ok
+ return nil unless ok
+ ok = schedule.defaultDaySchedule.addValue(time, val)
+ log(ERR, "'#{nom}': Can't set default day schedule (#{mth})") unless ok
+ return nil unless ok
schedule.defaultDaySchedule.setName(dft)
unless tag.empty?
rule = OpenStudio::Model::ScheduleRule.new(schedule, sch)
rule.setName(tag)
-
- unless rule.setStartDate(may01)
- log(ERR, "'#{tag}': Can't set start date (#{mth})")
- return nil
- end
-
- unless rule.setEndDate(oct31)
- log(ERR, "'#{tag}': Can't set end date (#{mth})")
- return nil
- end
-
- unless rule.setApplyAllDays(true)
- log(ERR, "'#{tag}': Can't apply to all days (#{mth})")
- return nil
- end
-
+ ok = rule.setStartDate(may01)
+ log(ERR, "'#{tag}': Can't set start date (#{mth})") unless ok
+ return nil unless ok
+ ok = rule.setEndDate(oct31)
+ log(ERR, "'#{tag}': Can't set end date (#{mth})") unless ok
+ return nil unless ok
+ ok = rule.setApplyAllDays(true)
+ log(ERR, "'#{tag}': Can't apply to all days (#{mth})") unless ok
+ return nil unless ok
rule.daySchedule.setName(day)
end
schedule
end
##
# Validate if default construction set holds a base ground construction.
#
# @param set [OpenStudio::Model::DefaultConstructionSet] a default set
- # @param base [OpensStudio::Model::ConstructionBase] a construction base
- # @param ground [Bool] true if ground-facing surface
- # @param exterior [Bool] true if exterior-facing surface
- # @param type [String] a surface type
+ # @param bse [OpensStudio::Model::ConstructionBase] a construction base
+ # @param gr [Bool] true if ground-facing surface
+ # @param ex [Bool] true if exterior-facing surface
+ # @param typ [String] a surface type
#
# @return [Bool] true if default construction set holds construction
# @return [Bool] false if invalid input
- def holdsConstruction?(set, base, ground = false, exterior = false, type = "")
+ def holdsConstruction?(set = nil, bse = nil, gr = false, ex = false, typ = "")
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Model::DefaultConstructionSet
cl2 = OpenStudio::Model::ConstructionBase
- return invalid("set", mth, 1, DBG, false) unless set.respond_to?(NS)
+ return invalid("set", mth, 1, DBG, false) unless set.respond_to?(NS)
id = set.nameString
- return mismatch(id, set, cl1, mth, DBG, false) unless set.is_a?(cl1)
-
- return invalid("base", mth, 2, DBG, false) unless base.respond_to?(NS)
- id = base.nameString
- return mismatch(id, base, cl2, mth, DBG, false) unless base.is_a?(cl2)
-
- valid = ground == true || ground == false
- return invalid("ground", mth, 3, DBG, false) unless valid
-
- valid = exterior == true || exterior == false
- return invalid("exterior", mth, 4, DBG, false) unless valid
-
- typ = type.to_s.downcase
- valid = typ == "floor" || typ == "wall" || typ == "roofceiling"
+ return mismatch(id, set, cl1, mth, DBG, false) unless set.is_a?(cl1)
+ return invalid("base", mth, 2, DBG, false) unless bse.respond_to?(NS)
+ id = bse.nameString
+ return mismatch(id, bse, cl2, mth, DBG, false) unless bse.is_a?(cl2)
+ valid = gr == true || gr == false
+ return invalid("ground", mth, 3, DBG, false) unless valid
+ valid = ex == true || ex == false
+ return invalid("exterior", mth, 4, DBG, false) unless valid
+ valid = typ.respond_to?(:to_s)
+ return invalid("surface typ", mth, 4, DBG, false) unless valid
+ type = typ.to_s.downcase
+ valid = type == "floor" || type == "wall" || type == "roofceiling"
return invalid("surface type", mth, 5, DBG, false) unless valid
constructions = nil
- if ground
+ if gr
unless set.defaultGroundContactSurfaceConstructions.empty?
constructions = set.defaultGroundContactSurfaceConstructions.get
end
- elsif exterior
+ elsif ex
unless set.defaultExteriorSurfaceConstructions.empty?
constructions = set.defaultExteriorSurfaceConstructions.get
end
else
unless set.defaultInteriorSurfaceConstructions.empty?
@@ -957,25 +931,25 @@
end
end
return false unless constructions
- case typ
+ case type
when "roofceiling"
unless constructions.roofCeilingConstruction.empty?
construction = constructions.roofCeilingConstruction.get
- return true if construction == base
+ return true if construction == bse
end
when "floor"
unless constructions.floorConstruction.empty?
construction = constructions.floorConstruction.get
- return true if construction == base
+ return true if construction == bse
end
else
unless constructions.wallConstruction.empty?
construction = constructions.wallConstruction.get
- return true if construction == base
+ return true if construction == bse
end
end
false
end
@@ -985,34 +959,29 @@
#
# @param model [OpenStudio::Model::Model] a model
# @param s [OpenStudio::Model::Surface] a surface
#
# @return [OpenStudio::Model::DefaultConstructionSet] default set
- # @return [nil] if invalid input
- def defaultConstructionSet(model, s)
+ # @return [NilClass] if invalid input
+ def defaultConstructionSet(model = nil, s = nil)
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Model::Model
cl2 = OpenStudio::Model::Surface
- return invalid("model", mth, 1) unless model
return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
-
- return invalid("s", mth, 2) unless s.respond_to?(NS)
+ return invalid("s", mth, 2) unless s.respond_to?(NS)
id = s.nameString
- return mismatch(id, s, cl2, mth) unless s.is_a?(cl2)
+ return mismatch(id, s, cl2, mth) unless s.is_a?(cl2)
- unless s.isConstructionDefaulted
- log(ERR, "'#{id}' construction not defaulted (#{mth})")
- return nil
- end
-
+ ok = s.isConstructionDefaulted
+ log(ERR, "'#{id}' construction not defaulted (#{mth})") unless ok
+ return nil unless ok
return empty("'#{id}' construction", mth, ERR) if s.construction.empty?
base = s.construction.get
return empty("'#{id}' space", mth, ERR) if s.space.empty?
space = s.space.get
type = s.surfaceType
-
ground = false
exterior = false
if s.isGroundSurface
ground = true
@@ -1058,43 +1027,43 @@
#
# @param lc [OpenStudio::LayeredConstruction] a layered construction
#
# @return [Bool] true if all layers are valid
# @return [Bool] false if invalid input
- def standardOpaqueLayers?(lc)
+ def standardOpaqueLayers?(lc = nil)
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::LayeredConstruction
return invalid("lc", mth, 1, DBG, false) unless lc.respond_to?(NS)
return mismatch(lc.nameString, lc, cl, mth, DBG, false) unless lc.is_a?(cl)
lc.layers.each { |m| return false if m.to_StandardOpaqueMaterial.empty? }
+
true
end
##
# Total (standard opaque) layered construction thickness (in m).
#
# @param lc [OpenStudio::LayeredConstruction] a layered construction
#
# @return [Double] total layered construction thickness
# @return [Double] 0 if invalid input
- def thickness(lc)
+ def thickness(lc = nil)
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::LayeredConstruction
- return invalid("lc", mth, 1, DBG, 0) unless lc.respond_to?(NS)
+ return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
id = lc.nameString
- return mismatch(id, lc, cl, mth, DBG, 0) unless lc.is_a?(cl)
+ return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl)
- unless standardOpaqueLayers?(lc)
- log(ERR, "'#{id}' holds non-StandardOpaqueMaterial(s) (#{mth})")
- return 0
- end
-
+ ok = standardOpaqueLayers?(lc)
+ log(ERR, "'#{id}' holds non-StandardOpaqueMaterial(s) (#{mth})") unless ok
+ return 0.0 unless ok
thickness = 0.0
lc.layers.each { |m| thickness += m.thickness }
+
thickness
end
##
# Return total air film resistance for fenestration.
@@ -1120,15 +1089,16 @@
# layer thickness of 2mm & k = ~0.6 W/m.K.
#
# The EnergyPlus Engineering calculations were designed for vertical windows
# - not horizontal, slanted or domed surfaces - use with caution.
mth = "OSut::#{__callee__}"
- cl = Numeric
+ cl = Numeric
- return invalid("usi", mth, 1, DBG, 0.1216) unless usi
- return mismatch("usi", usi, cl, mth, DBG, 0.1216) unless usi.is_a?(cl)
- return invalid("usi", mth, 1, WRN, 0.1216) if usi > 8.0
+ return mismatch("usi", usi, cl, mth, DBG, 0.1216) unless usi.is_a?(cl)
+ return invalid("usi", mth, 1, WRN, 0.1216) if usi > 8.0
+ return negative("usi", mth, WRN, 0.1216) if usi < 0
+ return zero("usi", mth, WRN, 0.1216) if usi.abs < TOL
rsi = 1 / (0.025342 * usi + 29.163853) # exterior film, next interior film
return rsi + 1 / (0.359073 * Math.log(usi) + 6.949915) if usi < 5.85
return rsi + 1 / (1.788041 * usi - 2.886625)
@@ -1140,68 +1110,53 @@
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
# @param film [Float] thermal resistance of surface air films (m2•K/W)
# @param t [Float] gas temperature (°C) (optional)
#
# @return [Float] calculated RSi at standard conditions (0 if error)
- def rsi(lc, film, t = 0.0)
+ def rsi(lc = nil, film = 0.0, t = 0.0)
# This is adapted from BTAP's Material Module's "get_conductance" (P. Lopez)
#
# https://github.com/NREL/OpenStudio-Prototype-Buildings/blob/
# c3d5021d8b7aef43e560544699fb5c559e6b721d/lib/btap/measures/
# btap_equest_converter/envelope.rb#L122
-
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Model::LayeredConstruction
cl2 = Numeric
- return invalid("lc", mth, 1, DBG, 0) unless lc.respond_to?(NS)
+ return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
id = lc.nameString
- return mismatch(id, lc, cl1, mth, DBG, 0) unless lc.is_a?(cl1)
+ return mismatch(id, lc, cl1, mth, DBG, 0.0) unless lc.is_a?(cl1)
+ return mismatch("film", film, cl2, mth, DBG, 0.0) unless film.is_a?(cl2)
+ return mismatch("temp K", t, cl2, mth, DBG, 0.0) unless t.is_a?(cl2)
+ t += 273.0 # °C to K
+ return negative("temp K", mth, DBG, 0.0) if t < 0
+ return negative("film", mth, DBG, 0.0) if film < 0
- return invalid("film", mth, 2, DBG, 0) unless film
- return invalid("temperature", mth, 3, DBG, 0) unless t
-
- return mismatch("film", film, cl2, mth, DBG, 0) unless film.is_a?(cl2)
- return mismatch("temperature", t, cl2, mth, DBG, 0) unless t.is_a?(cl2)
-
- tt = t + 273.0 # °C to K
- return negative("temp K", mth, DBG, 0) if tt < 0
- return negative("film", mth, DBG, 0) if film < 0
-
rsi = film
lc.layers.each do |m|
# Fenestration materials first (ignoring shades, screens, etc.)
- unless m.to_SimpleGlazing.empty?
- return 1 / m.to_SimpleGlazing.get.uFactor # no need to loop
- end
- unless m.to_StandardGlazing.empty?
- rsi += m.to_StandardGlazing.get.thermalResistance
- end
- unless m.to_RefractionExtinctionGlazing.empty?
- rsi += m.to_RefractionExtinctionGlazing.get.thermalResistance
- end
- unless m.to_Gas.empty?
- rsi += m.to_Gas.get.getThermalResistance(tt)
- end
- unless m.to_GasMixture.empty?
- rsi += m.to_GasMixture.get.getThermalResistance(tt)
- end
+ empty = m.to_SimpleGlazing.empty?
+ return 1 / m.to_SimpleGlazing.get.uFactor unless empty
+ empty = m.to_StandardGlazing.empty?
+ rsi += m.to_StandardGlazing.get.thermalResistance unless empty
+ empty = m.to_RefractionExtinctionGlazing.empty?
+ rsi += m.to_RefractionExtinctionGlazing.get.thermalResistance unless empty
+ empty = m.to_Gas.empty?
+ rsi += m.to_Gas.get.getThermalResistance(t) unless empty
+ empty = m.to_GasMixture.empty?
+ rsi += m.to_GasMixture.get.getThermalResistance(t) unless empty
# Opaque materials next.
- unless m.to_StandardOpaqueMaterial.empty?
- rsi += m.to_StandardOpaqueMaterial.get.thermalResistance
- end
- unless m.to_MasslessOpaqueMaterial.empty?
- rsi += m.to_MasslessOpaqueMaterial.get.thermalResistance
- end
- unless m.to_RoofVegetation.empty?
- rsi += m.to_RoofVegetation.get.thermalResistance
- end
- unless m.to_AirGap.empty?
- rsi += m.to_AirGap.get.thermalResistance
- end
+ empty = m.to_StandardOpaqueMaterial.empty?
+ rsi += m.to_StandardOpaqueMaterial.get.thermalResistance unless empty
+ empty = m.to_MasslessOpaqueMaterial.empty?
+ rsi += m.to_MasslessOpaqueMaterial.get.thermalResistance unless empty
+ empty = m.to_RoofVegetation.empty?
+ rsi += m.to_RoofVegetation.get.thermalResistance unless empty
+ empty = m.to_AirGap.empty?
+ rsi += m.to_AirGap.get.thermalResistance unless empty
end
rsi
end
@@ -1213,22 +1168,21 @@
#
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
#
# @return [Hash] index: (Integer), type: (:standard or :massless), r: (Float)
# @return [Hash] index: nil, type: nil, r: 0 (if invalid input)
- def insulatingLayer(lc)
+ def insulatingLayer(lc = nil)
mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::LayeredConstruction
res = { index: nil, type: nil, r: 0.0 }
i = 0 # iterator
- return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
+ return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
id = lc.nameString
return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl)
lc.layers.each do |m|
-
unless m.to_MasslessOpaqueMaterial.empty?
m = m.to_MasslessOpaqueMaterial.get
if m.thermalResistance < 0.001 || m.thermalResistance < res[:r]
i += 1
@@ -1267,19 +1221,17 @@
# @param model [OpenStudio::Model::Model] a model
# @param group [OpenStudio::Model::PlanarSurfaceGroup] a group
#
# @return [Hash] t: (OpenStudio::Transformation), r: Float
# @return [Hash] t: nil, r: nil (if invalid input)
- def transforms(model, group)
+ def transforms(model = nil, group = nil)
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Model::Model
cl2 = OpenStudio::Model::PlanarSurfaceGroup
res = { t: nil, r: nil }
- return invalid("model", mth, 1, DBG, res) unless model
return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
-
return invalid("group", mth, 2, DBG, res) unless group.respond_to?(NS)
id = group.nameString
return mismatch(id, group, cl2, mth, DBG, res) unless group.is_a?(cl2)
res[:t] = group.siteTransformation
@@ -1292,20 +1244,18 @@
# Flatten OpenStudio 3D points vs Z-axis (Z=0).
#
# @param pts [Array] an OpenStudio Point3D array/vector
#
# @return [Array] flattened OpenStudio 3D points
- def flatZ(pts)
+ def flatZ(pts = nil)
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Point3dVector
cl2 = OpenStudio::Point3d
- v = OpenStudio::Point3dVector.new
+ v = OpenStudio::Point3dVector.new
- return invalid("points", mth, 1, DBG, v) unless pts
valid = pts.is_a?(cl1) || pts.is_a?(Array)
- return mismatch("points", pts, cl1, mth, DBG, v) unless valid
-
+ return mismatch("points", pts, cl1, mth, DBG, v) unless valid
pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) }
pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, 0) }
v
end
@@ -1318,57 +1268,53 @@
# @param id1 [String] polygon #1 identifier (optional)
# @param id2 [String] polygon #2 identifier (optional)
#
# @return [Bool] true if 1st polygon fits entirely within the 2nd polygon
# @return [Bool] false if invalid input
- def fits?(p1, p2, id1 = "", id2 = "")
+ def fits?(p1 = nil, p2 = nil, id1 = "", id2 = "")
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Point3dVector
cl2 = OpenStudio::Point3d
a = false
+
+ return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s)
+ return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s)
i1 = id1.to_s
i2 = id2.to_s
i1 = "poly1" if i1.empty?
i2 = "poly2" if i2.empty?
-
- return invalid(i1, mth, 1, DBG, a) unless p1
- valid = p1.is_a?(cl1) || p1.is_a?(Array)
- return mismatch(i1, p1, cl1, mth, DBG, a) unless valid
+ valid1 = p1.is_a?(cl1) || p1.is_a?(Array)
+ valid2 = p2.is_a?(cl1) || p2.is_a?(Array)
+ return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1
+ return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2
return empty(i1, mth, ERR, a) if p1.empty?
-
- return invalid(i2, mth, 2, DBG, a) unless p2
- valid = p2.is_a?(cl1) || p2.is_a?(Array)
- return mismatch(i2, p2, cl1, mth, DBG, a) unless valid
return empty(i2, mth, ERR, a) if p2.empty?
-
p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
ft = OpenStudio::Transformation::alignFace(p1).inverse
-
ft_p1 = flatZ( (ft * p1).reverse )
return false if ft_p1.empty?
area1 = OpenStudio::getArea(ft_p1)
- return empty(i1, mth, ERR, a) if area1.empty?
+ return empty("#{i1} area", mth, ERR, a) if area1.empty?
area1 = area1.get
-
ft_p2 = flatZ( (ft * p2).reverse )
return false if ft_p2.empty?
area2 = OpenStudio::getArea(ft_p2)
- return empty(i2, mth, ERR, a) if area2.empty?
+ return empty("#{i2} area", mth, ERR, a) if area2.empty?
area2 = area2.get
-
union = OpenStudio::join(ft_p1, ft_p2, TOL2)
return false if union.empty?
union = union.get
area = OpenStudio::getArea(union)
- return empty("union", mth, ERR, a) if area.empty?
+ return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
area = area.get
return false if area < TOL
return true if (area - area2).abs < TOL
return false if (area - area2).abs > TOL
+
true
end
##
# Validate whether an OpenStudio polygon overlaps another.
@@ -1378,53 +1324,57 @@
# @param id1 [String] polygon #1 identifier (optional)
# @param id2 [String] polygon #2 identifier (optional)
#
# @return Returns true if polygons overlaps (or either fits into the other)
# @return [Bool] false if invalid input
- def overlaps?(p1, p2, id1 = "", id2 = "")
+ def overlaps?(p1 = nil, p2 = nil, id1 = "", id2 = "")
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Point3dVector
cl2 = OpenStudio::Point3d
a = false
- i1 = id1.to_s
- i2 = id2.to_s
- i1 = "poly1" if i1.empty?
- i2 = "poly2" if i2.empty?
- return invalid(i1, mth, 1, DBG, a) unless p1
- valid = p1.is_a?(cl1) || p1.is_a?(Array)
- return mismatch(i1, p1, cl1, mth, DBG, a) unless valid
+ return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s)
+ return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s)
+ i1 = id1.to_s
+ i2 = id2.to_s
+ i1 = "poly1" if i1.empty?
+ i2 = "poly2" if i2.empty?
+ valid1 = p1.is_a?(cl1) || p1.is_a?(Array)
+ valid2 = p2.is_a?(cl1) || p2.is_a?(Array)
+ return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1
+ return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2
return empty(i1, mth, ERR, a) if p1.empty?
-
- return invalid(i2, mth, 2, DBG, a) unless p2
- valid = p2.is_a?(cl1) || p2.is_a?(Array)
- return mismatch(i2, p2, cl1, mth, DBG, a) unless valid
return empty(i2, mth, ERR, a) if p2.empty?
-
p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
ft = OpenStudio::Transformation::alignFace(p1).inverse
-
ft_p1 = flatZ( (ft * p1).reverse )
return false if ft_p1.empty?
area1 = OpenStudio::getArea(ft_p1)
- return empty(i1, mth, ERR, a) if area1.empty?
+ return empty("#{i1} area", mth, ERR, a) if area1.empty?
area1 = area1.get
-
ft_p2 = flatZ( (ft * p2).reverse )
return false if ft_p2.empty?
area2 = OpenStudio::getArea(ft_p2)
- return empty(i2, mth, ERR, a) if area2.empty?
+ return empty("#{i2} area", mth, ERR, a) if area2.empty?
area2 = area2.get
-
union = OpenStudio::join(ft_p1, ft_p2, TOL2)
return false if union.empty?
union = union.get
area = OpenStudio::getArea(union)
- return empty("union", mth, ERR, a) if area.empty?
+ return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
area = area.get
return false if area < TOL
+
true
+ end
+
+ ##
+ # Callback when other modules extend OSlg
+ #
+ # @param base [Object] instance or class object
+ def self.extended(base)
+ base.send(:include, self)
end
end