class BTAP::Bridging

—- —- —- —- —- —- —- —- —- —- —- —- —- —- —- #

Constants

DBG
ERR
FTL
INF
TOL
TOL2
WRN

Attributes

feedback[R]

@return [Hash] logged messages TBD reports back to BTAP

model[R]

@return [Hash] BTAP/TBD Hash, specific to an OpenStudio model

tally[R]

@return [Hash] TBD tallies e.g. total lengths of linear thermal bridges

Public Class Methods

new(model = nil, argh = {}) click to toggle source

Initialize OpenStudio model-specific BTAP/TBD data - uprates/derates.

@param model [OpenStudio::Model::Model] a model @param argh [Hash] BTAP/TBD argument hash

# File lib/openstudio-standards/btap/bridging.rb, line 1314
def initialize(model = nil, argh = {})
  @model    = {}
  @tally    = {}
  @feedback = { logs: [] }
  lgs       = @feedback[:logs]

  argh[:interpolate] = false unless argh.key?(:interpolate)

  # BTAP generates free-floating, unoccupied spaces (e.g. attics) as
  # 'indirectly conditioned', rather than 'unconditioned' (e.g. vented
  # attics). For instance, all outdoor-facing sloped roof surfaces of an
  # attic in BTAP are insulated, while attic floors remain uninsulated. BTAP
  # adds to the thermal zone of each unoccupied space a thermostat without
  # referencing heating and/or cooling setpoint schedule objects. These
  # conditions do not meet TBD's internal 'plenum' logic/check (which is
  # based on OpenStudio-Standards), and so TBD ends up tagging such spaces
  # as unconditioned. Consequently, TBD attempts to up/de-rate attic floors
  # - not sloped roof surfaces. The upstream BTAP solution will undoubtedly
  # need revision. In the meantime, and in an effort to harmonize TBD with
  # BTAP's current approach, an OpenStudio model may be temporarily
  # modified prior to TBD processes, ensuring that each attic space is
  # temporarily mistaken as a conditioned plenum. The return variable of the
  # following method is a Hash holding temporarily-modified spaces,
  # i.e. buffer zones.
  buffers = self.alter_buffers(model)

  # Populate BTAP/TBD inputs with BTAP & OpenStudio model parameters,
  # which returns 'true' if successful. Check @feedback logs if failure to
  # populate (e.g. invalid argument hash, invalid OpenStudio model).
  return unless self.populate(model, argh)

  # Initialize loop controls and flags.
  initial  = true
  complies = false
  comply   = {}     # specific to :walls, :floors & :roofs
  perform  = :lp    # Low-performance wall constructions (revise, TO-DO ...)
  quality  = :bad   # default PSI factors - BTAP users can reset to :good
  quality  = :good if argh.key?(:quality) && argh[:quality] == :good
  combo    = "#{perform.to_s}_#{quality.to_s}".to_sym # e.g. :lp_bad
  args     = {}     # initialize native TBD arguments

  # Initialize surface types & native TBD args (if uprating).
  [:walls, :floors, :roofs].each do |stypes|
    next     if @model[stypes].empty?
    next unless argh.key?(stypes)
    next unless argh[stypes].key?(:ut)

    stype  = stypes.to_s.chop
    uprate = "uprate_#{stypes.to_s}".to_sym
    option = "#{stype}_option".to_sym
    ut     = "#{stype}_ut".to_sym

    args[uprate  ] = true
    args[option  ] = "ALL #{stype} constructions"
    args[ut      ] = argh[stypes][:ut]

    comply[stypes] = false

    @model[:constructions] = {} unless @model.key?(:constructions)
  end

  args[:io_path] = @model[combo] # contents of a "tbd.json" file
  args[:option ] = ""            # safeguard

  loop do
    if initial
      initial = false
    else
      # Subsequent runs. Upgrade technologies. Reset TBD args.
      if quality == :bad
        quality = :good
        combo   = "#{perform.to_s}_#{quality.to_s}".to_sym
        args[:io_path] = @model[combo]
      elsif perform == :lp
        # Switch 'perform' from :lp to :hp - reset quality to :bad.
        perform = :hp
        quality = :bad
        combo   = "#{perform.to_s}_#{quality.to_s}".to_sym
        args[:io_path] = @model[combo]
      end

      # Delete previously-generated TBD args Uo key/value pairs.
      [:walls, :floors, :roofs].each do |stypes|
        next unless comply.key?(stypes)

        uo = "#{stypes.to_s.chop}_uo".to_sym
        args.delete(uo) if args.key?(uo)
      end

      # Reset previous @model constructions.
      @model.delete(:constructions) if @model.key?(:constructions)
      @model[:constructions] = {}
    end

    # Run TBD on cloned OpenStudio model - compliant combo?
    mdl = OpenStudio::Model::Model.new
    mdl.addObjects(model.toIdfFile.objects)
    TBD.clean!
    res = TBD.process(mdl, args)

    # Halt all processes if fatal errors raised by TBD (e.g. badly formatted
    # TBD arguments, poorly-structured OpenStudio models).
    if TBD.fatal?
      TBD.logs.each { |lg| lgs << lg[:message] if lg[:level] == TBD::FTL }
      break
    end

    complies = true
    # Check if TBD-uprated Uo factors are valid: TBD args Hash holds (new)
    # uprated Uo keys/values for :walls, :floors AND/OR :roofs if uprating
    # is successful. In most cases, uprating tends to fail for wall
    # constructions rather than roof or floor constructions, due to the
    # typically larger density of linear thermal bridging per surface type
    # area. Yet even if all constructions were successfully uprated by TBD,
    # one must then determine if BTAP holds admissible (i.e. costed)
    # assembly variants with corresponding Uo factors (see :uos key). If
    # TBD-uprated Uo factors are lower than any of these admissible BTAP Uo
    # factors, then no commercially available solution can been identified.
    [:walls, :floors, :roofs].each do |stypes|
      next unless comply.key?(stypes) # true only if uprating

      stype_uo = "#{stypes.to_s.chop}_uo".to_sym
      target   = nil # uprated Uo (if successful)
      target   = args[stype_uo] if args.key?(stype_uo) # ... may be nil

      comply[stypes] = true

      @model[stypes].each do |id, surface|
        next unless surface.key?(:sptype)

        sptype = surface[:sptype] # e.g. :office
        next unless @model[:sptypes].key?(sptype)
        next unless @model[:sptypes][sptype].key?(stypes)
        next unless @model[:sptypes][sptype][stypes].key?(perform)

        construction = @model[:sptypes][sptype][stypes][perform]
        uo = nil
        ok = true
        uo = self.costed_uo(construction, target) if target
        ok = false  if uo.nil?
        uo = target if ok && argh[:interpolate]
        uo = self.lowest_uo(construction) unless ok # fallback
        comply[stypes] = false            unless ok

        unless @model[:constructions].key?(construction)
          @model[:constructions][construction]             = {}
          @model[:constructions][construction][:stypes   ] = stypes
          @model[:constructions][construction][:uo       ] = uo
          @model[:constructions][construction][:compliant] = ok
          @model[:constructions][construction][:surfaces ] = {}
        end

        face = model.getSurfaceByName(id)
        next if face.empty?

        face = face.get
        @model[:constructions][construction][:surfaces][id] = face
      end

      complies = false unless comply[stypes]
    end

    break if complies
    # Final BTAP uprating option, yet non-compliant: TBD's uprating
    # features are requested, yet unable to locate either a physically- or
    # economically-plausible Uo + PSI combo for 1x or more surface types.
    break if combo == :hp_good
  end # of loop

  # Post-loop steps (if uprating).
  [:walls, :floors, :roofs].each do |stypes|
    next unless comply.key?(stypes) # true only if uprating

    # Cancel uprating request before final derating.
    stype  = stypes.to_s.chop
    uprate = "uprate_#{stypes.to_s}".to_sym
    option = "#{stype}_option".to_sym
    ut     = "#{stype}_ut".to_sym
    args.delete(uprate)
    args.delete(option)
    args.delete(ut    )

    # Set uprated Uo factor for each BTAP 'deratable' construction.
    @model[:constructions].each do |id, construction|
      next unless construction.key?(:stypes   )
      next unless construction.key?(:uo       )
      next unless construction.key?(:compliant)
      next unless construction.key?(:surfaces )
      next unless construction[:stypes  ] == stypes
      next     if construction[:surfaces].empty?

      construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) }
    end
  end

  @model[:comply  ] = comply
  @model[:complies] = complies
  @model[:perform ] = perform
  @model[:quality ] = quality
  @model[:combo   ] = combo

  # Run "process" TBD one last time, on "model" (not cloned "mdl").
  TBD.clean!
  res = TBD.process(model, args)

  @model[:io      ] = res[:io      ] # TBD outputs (i.e. "tbd.out.json")
  @model[:surfaces] = res[:surfaces] # TBD derated surface data
  @model[:argh    ] = argh           # method argument Hash
  @model[:args    ] = args           # last TBD inputs (i.e. "tbd.json")

  self.gen_tallies                   # tallies for BTAP costing
  self.gen_feedback                  # log success messages for BTAP

  self.purge_buffer_schedules(model, buffers)
end

Public Instance Methods

alter_buffers(model = nil) click to toggle source

Modify BTAP-generated ‘buffer zones’ (e.g. attics) to ensure TBD tags these as indirectly conditioned spaces (e.g. plenums).

@param model [OpenStudio::Model::Model] a model

@return [Array] identifiers of modified buffer spaces in model

# File lib/openstudio-standards/btap/bridging.rb, line 1537
def alter_buffers(model = nil)
  buffers = []
  sched   = nil
  lgs     = @feedback[:logs]
  cl      = OpenStudio::Model::Model
  lgs << "Invalid OpenStudio model (buffers)" unless model.is_a?(cl)
  return buffers                              unless model.is_a?(cl)

  model.getSpaces.each do |space|
    next if space.partofTotalFloorArea
    next if space.thermalZone.empty?

    id    = space.nameString
    zone  = space.thermalZone.get
    next if zone.isPlenum
    next if zone.thermostat.empty?

    tstat  = zone.thermostat.get
    staged = tstat.respond_to?(:heatingTemperatureSetpointSchedule)
    tstat  = tstat.to_ZoneControlThermostatStagedDualSetpoint.get if staged
    tstat  = tstat.to_ThermostatSetpointDualSetpoint.get      unless staged

    if sched.nil?
      name  = "TBD attic setpoint sched"
      sched = OpenStudio::Model::ScheduleCompact.new(model)
      sched.setName(name)
    end

    tstat.setHeatingTemperatureSetpointSchedule(sched)     if staged
    tstat.setHeatingSetpointTemperatureSchedule(sched) unless staged

    buffers << id
  end

  buffers
end
gen_feedback() click to toggle source

Generate BTAP/TBD post-processing feedback.

@return [Boolean] true if valid BTAP/TBD model

# File lib/openstudio-standards/btap/bridging.rb, line 1891
def gen_feedback
  lgs = @feedback[:logs]
  return false unless @model.key?(:complies) # all model constructions
  return false unless @model.key?(:comply  ) # surface type specific ...
  return false unless @model.key?(:argh    ) # BTAP/TBD inputs + ouputs

  argh = @model[:argh]

  # Uprating. Report first on surface types (compliant or not).
  [:walls, :floors, :roofs].each do |stypes|
    next unless @model[:comply].key?(stypes)

    ut  = format("%.3f", argh[stypes][:ut])
    lg  = "Compliant "         if @model[:comply][stypes]
    lg  = "Non-compliant " unless @model[:comply][stypes]
    lg += "#{stypes}: Ut #{ut} W/m2.K"
    lgs << lg

    # Report then on required Uo factor per construction (compliant or not).
    @model[:constructions].each do |id, construction|
      next unless construction.key?(:stypes   )
      next unless construction.key?(:uo       )
      next unless construction.key?(:compliant)
      next unless construction.key?(:surfaces )
      next unless construction[:stypes  ] == stypes
      next     if construction[:surfaces].empty?

      uo  = format("%.3f", construction[:uo])
      lg  = "   Compliant "         if construction[:compliant]
      lg  = "   Non-compliant " unless construction[:compliant]
      lg += "#{id} Uo #{uo} (W/K.m2)"
      lgs << lg
    end
  end

  # Summary of TBD-derated constructions.
  @model[:osm].getSurfaces.each do |s|
    next if s.construction.empty?
    next if s.construction.get.to_LayeredConstruction.empty?

    lc = s.construction.get.to_LayeredConstruction.get
    id = lc.nameString
    next unless id.include?(" c tbd")

    rsi  = TBD.rsi(lc, s.filmResistance)
    usi  = format("%.3f", 1/rsi)
    rsi  = format("%.1f", rsi)
    area = format("%.1f", lc.getNetArea) + " m2"

    lgs << "~ '#{id}' derated Rsi: #{rsi} [Usi #{usi} x #{area}]"
  end

  # Log PSI factor tallies (per thermal bridge type).
  if @tally.key?(:edges)
    @tally[:edges].each do |type, e|
      next if type == :transition

      lgs << "# '#{type}' (#{e.size}x):"

      e.each do |psi, length|
        l = format("%.2f", length)
        lgs << "... PSI set '#{psi}' : #{l} m"
      end
    end
  end

  true
end
gen_tallies() click to toggle source

Generate BTAP/TBD tallies

@return [Boolean] true if BTAP/TBD tally is successful

# File lib/openstudio-standards/btap/bridging.rb, line 1859
def gen_tallies
  edges  = {}
  return false unless @model.key?(:io)
  return false unless @model[:io].key?(:edges)

  @model[:io][:edges].each do |e|
    # Content of TBD-generated 'edges' (hashes):
    #      psi: BTAP PSI set ID, e.g. "BTAP-ExteriorWall-Mass-6 good"
    #     type: thermal bridge type, e.g. :corner
    #   length: (in m)
    # surfaces: linked OpenStudio surface IDs
    edges[e[:type]]           = {} unless edges.key?(e[:type])
    edges[e[:type]][e[:psi]]  = 0  unless edges[e[:type]].key?(e[:psi])
    edges[e[:type]][e[:psi]] += e[:length]
  end

  return false if edges.empty?

  @tally[:edges] = edges

  # Add final selection of (uprated) Uo factors per BTAP construction.
  return true unless @model.key?(:constructions)

  @tally[:constructions] = @model[:constructions]

  true
end
get_material_quantities() click to toggle source
# File lib/openstudio-standards/btap/bridging.rb, line 1960
def get_material_quantities()
  material_quantities = {}
  csv = CSV.read("#{File.dirname(__FILE__)}/../../../data/inventory/thermal_bridging.csv", headers: true)
  tally_edges  = @tally[:edges].transform_keys(&:to_s)

  #tally_edges = JSON.parse('{"edges":{"jamb":{"BTAP-ExteriorWall-SteelFramed-1 good":13.708557548340757},"sill":{"BTAP-ExteriorWall-SteelFramed-1 good":90.13000000000001},"head":{"BTAP-ExteriorWall-SteelFramed-1 good":90.13000000000001},"gradeconvex":{"BTAP-ExteriorWall-SteelFramed-1 good":90.4348},"parapetconvex":{"BTAP-ExteriorWall-SteelFramed-1 good":45.2174},"parapet":{"BTAP-ExteriorWall-SteelFramed-1 good":45.2174},"transition":{"BTAP-ExteriorWall-SteelFramed-1 good":71.16038874419307},"cornerconvex":{"BTAP-ExteriorWall-SteelFramed-1 good":12.1952}}}')['edges']
  tally_edges.each do |edge_type_full, value|
    edge_type = edge_type_full.delete_suffix('convex')
    if ['head', 'jamb', 'sill'].include?(edge_type)
      edge_type = 'fenestration'
    end
    value.each do |wall_ref_and_quality, quantity|
      /(.*)\s(.*)/ =~ wall_ref_and_quality
      wall_reference = $1
      quality = $2

      if wall_reference =='BTAP-ExteriorWall-SteelFramed-1'
        wall_reference = 'BTAP-ExteriorWall-SteelFramed-2'
      end

      if edge_type == 'transition'
        next
      end

      result = csv.find { |row| row['edge_type'] == edge_type &&
        row['quality'] == quality &&
        row['wall_reference'] == wall_reference
      }
      if result.nil?
        puts ("#{edge_type}-#{wall_reference}-#{quality}")
        puts "not found in tb database"
        next
      end

      # Split
      material_opaque_id_layers = result['material_opaque_id_layers'].split(",")
      id_layers_quantity_multipliers = result['id_layers_quantity_multipliers'].split(",")

      material_opaque_id_layers.zip(id_layers_quantity_multipliers).each do |id, scale|
        if material_quantities[id].nil? then material_quantities[id] = 0.0 end
        material_quantities[id] = material_quantities[id] + scale.to_f * quantity.to_f
      end
    end
  end
  material_opaque_id_quantities = []
  material_quantities.each do |id,quantity|
    material_opaque_id_quantities << { 'materials_opaque_id' => id, 'quantity' => quantity, 'domain'=> 'thermal_bridging' }
  end

  return material_opaque_id_quantities
end
inputs(perform = :hp, quality = :good) click to toggle source

Generate (native) TBD input hash.

@param perform [Symbol] :lp or :hp wall variant @param quality [Symbol] :bad or :good PSI-factor

@return [Hash] native TBD inputs

# File lib/openstudio-standards/btap/bridging.rb, line 1803
def inputs(perform = :hp, quality = :good)
  input   = {}
  psis    = {} # construction-specific PSI sets
  sptypes = {} # space type-specific references to previous PSI sets
  perform = :hp   unless perform == :lp  || perform == :hp
  quality = :good unless quality == :bad || quality == :good

  # Once building-type construction selection is introduced within BTAP,
  # define default TBD "building" PSI set. In the meantime, this is added
  # strictly as a backup solution (just in case).
  building = self.set(STEL1, quality) if perform == :lp
  building = self.set(STEL2, quality) if perform == :hp

  psis[ building[:id] ] = building

  # Collect unique BTAP/TBD instances.
  combo = "#{perform.to_s}_#{quality.to_s}".to_sym

  @model[:sptypes].values.each do |sptype|
    next unless sptype.key?(combo)

    psi = sptype[combo]
    next if psis.key?(psi[:id])

    psis[ psi[:id] ] = psi
  end

  # TBD JSON schema added as a reminder. No schema validation in BTAP.
  schema = "https://github.com/rd2/tbd/blob/master/tbd.schema.json"

  input[:schema     ] = schema
  input[:description] = "TBD input for BTAP"              # append run # ?
  input[:psis       ] = psis.values

  @model[:sptypes].values.each do |sptype|
    next unless sptype.key?(combo)
    next unless sptype.key?(:sptype)
    next if sptypes.key?(sptype[:sptype])

    sptypes[ sptype[:sptype] ] = { psi: sptype[combo][:id] }
  end

  sptypes.each do |id, sptype|
    input[:spacetypes] = [] unless input.key?(:spacetypes)
    input[:spacetypes] << { id: id, psi: sptype[:psi] }
  end

  input[:building] = { psi: building[:id] }

  input
end
minU(model = nil, stypes = :walls) click to toggle source

Fetch min U-factor of outdoor-facing OpenStudio model surface types.

@param model [OpenStudio::Model::Model] a model @param stype [Symbol] model surface type (e.g. :walls)

@return [Float] min U factor (default 5.678 W/m2.K)

# File lib/openstudio-standards/btap/bridging.rb, line 1632
def minU(model = nil, stypes = :walls)
  u     = UMAX
  lgs   = @feedback[:logs]
  cl    = OpenStudio::Model::Model
  stype = stypes.to_s.chop.downcase
  ok    = stype == "wall" || stype == "floor" || stype == "roof"
  stype = "wall"                                     unless ok
  lgs << "Invalid OpenStudio model (#{stypes} minU)" unless model.is_a?(cl)
  return u                                           unless model.is_a?(cl)

  model.getSurfaces.each do |s|
    next unless s.surfaceType.downcase.include?(stype)
    next unless s.outsideBoundaryCondition.downcase == "outdoors"
    next if s.construction.empty?
    next if s.construction.get.to_LayeredConstruction.empty?

    lc = s.construction.get.to_LayeredConstruction.get
    uo = 1 / TBD.rsi(lc, s.filmResistance)

    u = [uo, u].min
  end

  # u0 = format("%.3f", u) # TEMPORARY
  # puts "~~ Extracted #{stypes} minU (#{u0}) W/m2.K from OpenStudio model"

  u
end
populate(model = nil, argh = {}) click to toggle source

Populate BTAP/TBD model with BTAP & OpenStudio model parameters.

@param model [OpenStudio::Model::Model] a model @param argh [Hash] BTAP/TBD argument hash

@return [Boolean] true if valid (check @feedback logs if false)

# File lib/openstudio-standards/btap/bridging.rb, line 1667
def populate(model = nil, argh = {})
  cl     = OpenStudio::Model::Model
  args   = { option: "(non thermal bridging)" }    # for initial TBD dry run
  lgs    = @feedback[:logs]

  lgs << "Invalid OpenStudio model to de/up-rate" unless model.is_a?(cl)
  lgs << "Invalid BTAP/TBD argument Hash"         unless argh.is_a?(Hash)
  lgs << "Empty BTAP/TBD argument hash"               if argh.empty?
  return false                                    unless model.is_a?(cl)
  return false                                    unless argh.is_a?(Hash)
  return false                                        if argh.empty?

  # Fetch number of stories in OpenStudio model.
  stories = model.getBuilding.standardsNumberOfAboveGroundStories
  stories = stories.get                  unless stories.empty?
  stories = model.getBuildingStorys.size unless stories.is_a?(Integer)

  @model[:stories] = stories
  @model[:stories] = 1              if stories < 1
  @model[:stories] = 999            if stories > 999
  @model[:spaces ] = {}
  @model[:sptypes] = {}

  # Run TBD on a cloned OpenStudio model (dry run).
  mdl      = OpenStudio::Model::Model.new
  mdl.addObjects(model.toIdfFile.objects)
  TBD.clean!
  res      = TBD.process(mdl, args)
  surfaces = res[:surfaces]

  # TBD validation of OpenStudio model.
  lgs << "TBD-identified FATAL error(s):"      if TBD.fatal?
  lgs << "TBD-identified non-FATAL error(s):"  if TBD.error?
  TBD.logs.each { |log| lgs << log[:message] } if TBD.fatal? || TBD.error?
  return false                                 if TBD.fatal?

  lgs << "TBD: no deratable surfaces in model" if surfaces.nil?
  return false                                 if surfaces.nil?

  # Initialize deratable walls, exposed floors & roofs.
  [:walls, :floors, :roofs].each { |stypes| @model[stypes] = {} }

  surfaces.each do |id, surface|
    next unless surface.key?(:type     ) # :wall, :floor, :ceiling
    next unless surface.key?(:space    ) # OpenStudio space object
    next unless surface.key?(:deratable) # true/false
    next unless surface[:deratable]

    stypes = :walls  if surface[:type] == :wall
    stypes = :floors if surface[:type] == :floor
    stypes = :roofs  if surface[:type] == :ceiling
    next unless stypes == :walls || stypes == :floors || stypes == :roofs

    space  = surface[:space].nameString
    sptype = surface[:stype].nameString if surface.key?(:stype)
    sptype = ""                     unless surface.key?(:stype)
    typ    = self.spacetype(sptype, @model[:stories]) # e.g. :office

    # Keep track of individual surface's space and spacetype keyword.
    @model[stypes][id]          = {}
    @model[stypes][id][:space ] = space
    @model[stypes][id][:sptype] = typ

    # Keep track of individual spaces and spacetypes.
    exists = @model[:spaces].key?(space)
    @model[:spaces][space]          = {}     unless exists
    @model[:spaces][space][:sptype] = typ    unless exists

    exists = @model[:sptypes].key?(typ)
    @model[:sptypes][typ ]          = {}     unless exists
    @model[:sptypes][typ ][:sptype] = sptype unless exists
    next if @model[:sptypes][typ].key?(stypes)

    # Low- vs Hi-Performance BTAP assemblies.
    lo = self.assembly(typ, stypes, :lp)
    hi = self.assembly(typ, stypes, :hp)
    @model[:sptypes][typ][stypes]      = {}
    @model[:sptypes][typ][stypes][:lp] = lo
    @model[:sptypes][typ][stypes][:hp] = hi
    next unless stypes == :walls

    # Fetch bad vs good PSI factor sets - strictly a function of walls.
    @model[:sptypes][typ][:lp_bad ] = self.set(lo, :bad )
    @model[:sptypes][typ][:lp_good] = self.set(lo, :good)
    @model[:sptypes][typ][:hp_bad ] = self.set(hi, :bad )
    @model[:sptypes][typ][:hp_good] = self.set(hi, :good)
  end

  # BTAP-fed Uo (+ optional Ut) factors.
  [:walls, :floors, :roofs].each do |stypes|
    lgs << "Missing BTAP/TBD #{stypes}"    unless argh.key?(stypes)
    lgs << "Missing BTAP/TBD #{stypes} Uo" unless argh[stypes].key?(:uo)
    return false                           unless argh.key?(stypes)
    return false                           unless argh[stypes].key?(:uo)
    next                                       if @model[stypes].empty?

    uo = argh[stypes][:uo]
    ok = uo.is_a?(Numeric) && uo.between?(UMIN, UMAX)
    next if ok

    uo = self.minU(model, stypes)
    ok = uo.is_a?(Numeric) && uo.between?(UMIN, UMAX)
    lgs << "Invalid BTAP/TBD #{stypes} Uo" unless ok
    return false                           unless ok

    argh[stypes][:uo] = uo
    next                                   unless argh[stypes].key?(:ut)

    argh[stypes][:ut] = uo
  end

  # Generate native TBD input Hashes for the model, for both :good & :bad
  # PSI factor sets. The typical TBD use case involves writing out the
  # contents of either Hash (e.g. JSON::pretty_generate) as a "tbd.json"
  # input file, to save under a standard OpenStudio "files" folder. At
  # runtime, TBD then reopens the JSON file and populates its own data
  # model in memory. Yet BTAP is not a typical use case. To avoid writing
  # out (then re-reading) TBD JSON files/hashes (i.e. resource intensive),
  # BTAP/TBD instead populates the TBD data model directly.
  @model[:lp_bad ] = self.inputs(:lp, :bad )
  @model[:lp_good] = self.inputs(:lp, :good)
  @model[:hp_bad ] = self.inputs(:hp, :bad )
  @model[:hp_good] = self.inputs(:hp, :good)

  @model[:osm    ] = model

  true
end
purge_buffer_schedules(model = nil, buffers = []) click to toggle source

Remove previously BTAP/TBD-added heating setpoint schedules for ‘buffer zones’ (e.g. attics).

@param model [OpenStudio::Model::Model] a model @param buffers [Array] identifiers of modified buffer spaces in model

@return [Boolean] true if successful

# File lib/openstudio-standards/btap/bridging.rb, line 1582
def purge_buffer_schedules(model = nil, buffers = [])
  scheds = []
  lgs    = @feedback[:logs]
  cl     = OpenStudio::Model::Model
  lgs << "Invalid OpenStudio model (purge)" unless model.is_a?(cl)
  lgs << "Invalid BTAP/TBD buffers"         unless buffers.is_a?(Array)
  return false                              unless model.is_a?(cl)
  return false                              unless buffers.is_a?(Array)

  buffers.each do |id|
    space = model.getSpaceByName(id)
    next if space.empty?

    space = space.get
    next if space.thermalZone.empty?

    zone = space.thermalZone.get
    next if zone.thermostat.empty?

    tstat  = zone.thermostat.get
    staged = tstat.respond_to?(:heatingTemperatureSetpointSchedule)
    tstat  = tstat.to_ZoneControlThermostatStagedDualSetpoint.get if staged
    tstat  = tstat.to_ThermostatSetpointDualSetpoint.get      unless staged
    sched  = tstat.heatingTemperatureSetpointSchedule             if staged
    sched  = tstat.heatingSetpointTemperatureSchedule         unless staged
    next if sched.empty?

    sched = sched.get
    scheds << sched.nameString
    tstat.resetHeatingSetpointTemperatureSchedule             unless staged
    tstat.resetHeatingTemperatureSetpointSchedule                 if staged
  end

  scheds.each do |sched|
    schd = model.getScheduleByName(sched)
    next if schd.empty?
    schd = schd.get
    schd.remove
  end

  true
end