# frozen_string_literal: true class Geometry def self.get_temperature_scheduled_space_values(space_type) if space_type == HPXML::LocationOtherHeatedSpace # Average of indoor/outdoor temperatures with minimum of heating setpoint return { temp_min: 68, indoor_weight: 0.5, outdoor_weight: 0.5, ground_weight: 0.0, f_regain: 0.0 } elsif space_type == HPXML::LocationOtherMultifamilyBufferSpace # Average of indoor/outdoor temperatures with minimum of 50 deg-F return { temp_min: 50, indoor_weight: 0.5, outdoor_weight: 0.5, ground_weight: 0.0, f_regain: 0.0 } elsif space_type == HPXML::LocationOtherNonFreezingSpace # Floating with outdoor air temperature with minimum of 40 deg-F return { temp_min: 40, indoor_weight: 0.0, outdoor_weight: 1.0, ground_weight: 0.0, f_regain: 0.0 } elsif space_type == HPXML::LocationOtherHousingUnit # Indoor air temperature return { temp_min: nil, indoor_weight: 1.0, outdoor_weight: 0.0, ground_weight: 0.0, f_regain: 0.0 } elsif space_type == HPXML::LocationExteriorWall # Average of indoor/outdoor temperatures return { temp_min: nil, indoor_weight: 0.5, outdoor_weight: 0.5, ground_weight: 0.0, f_regain: 0.5 } # From LBNL's "Technical Background for default values used for Forced Air Systems in Proposed ASHRAE Standard 152P" elsif space_type == HPXML::LocationUnderSlab # Ground temperature return { temp_min: nil, indoor_weight: 0.0, outdoor_weight: 0.0, ground_weight: 1.0, f_regain: 0.83 } # From LBNL's "Technical Background for default values used for Forced Air Systems in Proposed ASHRAE Standard 152P" end fail "Unhandled space type: #{space_type}." end # Calculates space heights as the max z coordinate minus the min z coordinate def self.get_height_of_spaces(spaces) minzs = [] maxzs = [] spaces.each do |space| zvalues = getSurfaceZValues(space.surfaces) minzs << zvalues.min + UnitConversions.convert(space.zOrigin, 'm', 'ft') maxzs << zvalues.max + UnitConversions.convert(space.zOrigin, 'm', 'ft') end return maxzs.max - minzs.min end def self.get_max_z_of_spaces(spaces) maxzs = [] spaces.each do |space| zvalues = getSurfaceZValues(space.surfaces) maxzs << zvalues.max + UnitConversions.convert(space.zOrigin, 'm', 'ft') end return maxzs.max end # Return an array of z values for surfaces passed in. The values will be relative to the parent origin. This was intended for spaces. def self.getSurfaceZValues(surfaceArray) zValueArray = [] surfaceArray.each do |surface| surface.vertices.each do |vertex| zValueArray << UnitConversions.convert(vertex.z, 'm', 'ft') end end return zValueArray end def self.get_z_origin_for_zone(zone) z_origins = [] zone.spaces.each do |space| z_origins << UnitConversions.convert(space.zOrigin, 'm', 'ft') end return z_origins.min end def self.get_roof_pitch(surfaces) tilts = [] surfaces.each do |surface| next if surface.surfaceType.downcase != 'roofceiling' next if (surface.outsideBoundaryCondition.downcase != 'outdoors') && (surface.outsideBoundaryCondition.downcase != 'adiabatic') tilts << surface.tilt end return UnitConversions.convert(tilts.max, 'rad', 'deg') end # TODO: Remove these methods def self.is_living(space_or_zone) return space_or_zone_is_of_type(space_or_zone, HPXML::LocationLivingSpace) end def self.is_unconditioned_basement(space_or_zone) return space_or_zone_is_of_type(space_or_zone, HPXML::LocationBasementUnconditioned) end def self.is_garage(space_or_zone) return space_or_zone_is_of_type(space_or_zone, HPXML::LocationGarage) end def self.space_or_zone_is_of_type(space_or_zone, space_type) if space_or_zone.is_a? OpenStudio::Model::Space return space_is_of_type(space_or_zone, space_type) elsif space_or_zone.is_a? OpenStudio::Model::ThermalZone return zone_is_of_type(space_or_zone, space_type) end end def self.space_is_of_type(space, space_type) unless space.isPlenum if space.spaceType.is_initialized if space.spaceType.get.standardsSpaceType.is_initialized return true if space.spaceType.get.standardsSpaceType.get == space_type end end end return false end def self.zone_is_of_type(zone, space_type) zone.spaces.each do |space| return space_is_of_type(space, space_type) end end def self.process_occupants(model, num_occ, occ_gain, sens_frac, lat_frac, weekday_sch, weekend_sch, monthly_sch, cfa, nbeds, space) # Error checking if (sens_frac < 0) || (sens_frac > 1) fail 'Sensible fraction must be greater than or equal to 0 and less than or equal to 1.' end if (lat_frac < 0) || (lat_frac > 1) fail 'Latent fraction must be greater than or equal to 0 and less than or equal to 1.' end if lat_frac + sens_frac > 1 fail 'Sum of sensible and latent fractions must be less than or equal to 1.' end activity_per_person = UnitConversions.convert(occ_gain, 'Btu/hr', 'W') # Hard-coded convective, radiative, latent, and lost fractions occ_lat = lat_frac occ_sens = sens_frac occ_conv = 0.442 * occ_sens occ_rad = 0.558 * occ_sens occ_lost = 1 - occ_lat - occ_conv - occ_rad space_obj_name = "#{Constants.ObjectNameOccupants}" space_num_occ = num_occ * UnitConversions.convert(space.floorArea, 'm^2', 'ft^2') / cfa # Create schedule people_sch = MonthWeekdayWeekendSchedule.new(model, Constants.ObjectNameOccupants + ' schedule', weekday_sch, weekend_sch, monthly_sch, 1.0, 1.0, true, true, Constants.ScheduleTypeLimitsFraction) # Create schedule activity_sch = OpenStudio::Model::ScheduleRuleset.new(model, activity_per_person) # Add people definition for the occ occ_def = OpenStudio::Model::PeopleDefinition.new(model) occ = OpenStudio::Model::People.new(occ_def) occ.setName(space_obj_name) occ.setSpace(space) occ_def.setName(space_obj_name) occ_def.setNumberOfPeopleCalculationMethod('People', 1) occ_def.setNumberofPeople(space_num_occ) occ_def.setFractionRadiant(occ_rad) occ_def.setSensibleHeatFraction(occ_sens) occ_def.setMeanRadiantTemperatureCalculationType('ZoneAveraged') occ_def.setCarbonDioxideGenerationRate(0) occ_def.setEnableASHRAE55ComfortWarnings(false) occ.setActivityLevelSchedule(activity_sch) occ.setNumberofPeopleSchedule(people_sch.schedule) end def self.get_occupancy_default_num(nbeds) return Float(nbeds) end def self.get_occupancy_default_values() # Table 4.2.2(3). Internal Gains for Reference Homes hrs_per_day = 16.5 # hrs/day sens_gains = 3716.0 # Btu/person/day lat_gains = 2884.0 # Btu/person/day tot_gains = sens_gains + lat_gains heat_gain = tot_gains / hrs_per_day # Btu/person/hr sens_frac = sens_gains / tot_gains lat_frac = lat_gains / tot_gains return heat_gain, hrs_per_day, sens_frac, lat_frac end end