class NECB2011
This class holds methods that apply NECB2011
rules. @ref [References::NECB2011]
Attributes
Public Class Methods
Standard::new
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 146 def initialize super() @template = self.class.name @standards_data = load_standards_database_new corrupt_standards_database @tbd = nil # puts "loaded these tables..." # puts @standards_data.keys.size # raise("tables not all loaded in parent #{}") if @standards_data.keys.size < 24 end
Public Instance Methods
# File lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb, line 2 def add_all_spacetypes_to_model(model) # Get the space Type data from @standards data spacetype_data = nil if @standards_data['space_types'].is_a?(Hash) == true spacetype_data = @standards_data['space_types']['table'] else spacetype_data = @standards_data['space_types'] end spacetype_data.each do |spacedata| space_type = OpenStudio::Model::SpaceType.new(model) space_type.setStandardsSpaceType(spacedata['space_type']) space_type.setStandardsBuildingType(spacedata['building_type']) space_type.setName("#{spacedata['building_type']} #{spacedata['space_type']}") # Loads space_type_apply_internal_loads(space_type: space_type) # Schedules space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true) end end
Create a new DX cooling coil with NECB curve characteristics
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1933 def add_onespeed_DX_coil(model, always_on) # clg_cap_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model) # clg_cap_f_of_temp = model_add_curve("DXCOOL-NECB2011-REF-CAPFT") clg_cap_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model) clg_cap_f_of_temp.setCoefficient1Constant(0.867905) clg_cap_f_of_temp.setCoefficient2x(0.0142459) clg_cap_f_of_temp.setCoefficient3xPOW2(0.000554364) clg_cap_f_of_temp.setCoefficient4y(-0.00755748) clg_cap_f_of_temp.setCoefficient5yPOW2(3.3048e-05) clg_cap_f_of_temp.setCoefficient6xTIMESY(-0.000191808) clg_cap_f_of_temp.setMinimumValueofx(13.0) clg_cap_f_of_temp.setMaximumValueofx(24.0) clg_cap_f_of_temp.setMinimumValueofy(24.0) clg_cap_f_of_temp.setMaximumValueofy(46.0) # clg_cap_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model) clg_cap_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model) clg_cap_f_of_flow.setCoefficient1Constant(1.0) clg_cap_f_of_flow.setCoefficient2x(0.0) clg_cap_f_of_flow.setCoefficient3xPOW2(0.0) clg_cap_f_of_flow.setMinimumValueofx(0.0) clg_cap_f_of_flow.setMaximumValueofx(1.0) # clg_energy_input_ratio_f_of_temp = = model_add_curve(""DXCOOL-NECB2011-REF-COOLEIRFT") # clg_energy_input_ratio_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model) clg_energy_input_ratio_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model) clg_energy_input_ratio_f_of_temp.setCoefficient1Constant(0.116936) clg_energy_input_ratio_f_of_temp.setCoefficient2x(0.0284933) clg_energy_input_ratio_f_of_temp.setCoefficient3xPOW2(-0.000411156) clg_energy_input_ratio_f_of_temp.setCoefficient4y(0.0214108) clg_energy_input_ratio_f_of_temp.setCoefficient5yPOW2(0.000161028) clg_energy_input_ratio_f_of_temp.setCoefficient6xTIMESY(-0.000679104) clg_energy_input_ratio_f_of_temp.setMinimumValueofx(13.0) clg_energy_input_ratio_f_of_temp.setMaximumValueofx(24.0) clg_energy_input_ratio_f_of_temp.setMinimumValueofy(24.0) clg_energy_input_ratio_f_of_temp.setMaximumValueofy(46.0) # clg_energy_input_ratio_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model) # clg_energy_input_ratio_f_of_flow = = model_add_curve("DXCOOL-NECB2011-REF-CAPFFLOW") clg_energy_input_ratio_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model) clg_energy_input_ratio_f_of_flow.setCoefficient1Constant(1.0) clg_energy_input_ratio_f_of_flow.setCoefficient2x(0.0) clg_energy_input_ratio_f_of_flow.setCoefficient3xPOW2(0.0) clg_energy_input_ratio_f_of_flow.setMinimumValueofx(0.0) clg_energy_input_ratio_f_of_flow.setMaximumValueofx(1.0) # NECB curve modified to take into account how PLF is used in E+, and PLF ranges (> 0.7) # clg_part_load_ratio = model_add_curve("DXCOOL-NECB2011-REF-COOLPLFFPLR") clg_part_load_ratio = OpenStudio::Model::CurveCubic.new(model) clg_part_load_ratio.setCoefficient1Constant(0.0277) clg_part_load_ratio.setCoefficient2x(4.9151) clg_part_load_ratio.setCoefficient3xPOW2(-8.184) clg_part_load_ratio.setCoefficient4xPOW3(4.2702) clg_part_load_ratio.setMinimumValueofx(0.7) clg_part_load_ratio.setMaximumValueofx(1.0) return OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model, always_on, clg_cap_f_of_temp, clg_cap_f_of_flow, clg_energy_input_ratio_f_of_temp, clg_energy_input_ratio_f_of_flow, clg_part_load_ratio) end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1998 def add_onespeed_htg_DX_coil(model, sch) htg_cap_f_of_temp = OpenStudio::Model::CurveCubic.new(model) htg_cap_f_of_temp.setCoefficient1Constant(0.729009) htg_cap_f_of_temp.setCoefficient2x(0.0319275) htg_cap_f_of_temp.setCoefficient3xPOW2(0.000136404) htg_cap_f_of_temp.setCoefficient4xPOW3(-8.748e-06) htg_cap_f_of_temp.setMinimumValueofx(-20.0) htg_cap_f_of_temp.setMaximumValueofx(20.0) htg_cap_f_of_flow = OpenStudio::Model::CurveCubic.new(model) htg_cap_f_of_flow.setCoefficient1Constant(0.84) htg_cap_f_of_flow.setCoefficient2x(0.16) htg_cap_f_of_flow.setCoefficient3xPOW2(0.0) htg_cap_f_of_flow.setCoefficient4xPOW3(0.0) htg_cap_f_of_flow.setMinimumValueofx(0.5) htg_cap_f_of_flow.setMaximumValueofx(1.5) htg_energy_input_ratio_f_of_temp = OpenStudio::Model::CurveCubic.new(model) htg_energy_input_ratio_f_of_temp.setCoefficient1Constant(1.2183) htg_energy_input_ratio_f_of_temp.setCoefficient2x(-0.03612) htg_energy_input_ratio_f_of_temp.setCoefficient3xPOW2(0.00142) htg_energy_input_ratio_f_of_temp.setCoefficient4xPOW3(-2.68e-05) htg_energy_input_ratio_f_of_temp.setMinimumValueofx(-20.0) htg_energy_input_ratio_f_of_temp.setMaximumValueofx(20.0) htg_energy_input_ratio_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model) htg_energy_input_ratio_f_of_flow.setCoefficient1Constant(1.3824) htg_energy_input_ratio_f_of_flow.setCoefficient2x(-0.4336) htg_energy_input_ratio_f_of_flow.setCoefficient3xPOW2(0.0512) htg_energy_input_ratio_f_of_flow.setMinimumValueofx(0.0) htg_energy_input_ratio_f_of_flow.setMaximumValueofx(1.0) htg_part_load_ratio = OpenStudio::Model::CurveCubic.new(model) htg_part_load_ratio.setCoefficient1Constant(0.3696) htg_part_load_ratio.setCoefficient2x(2.3362) htg_part_load_ratio.setCoefficient3xPOW2(-2.9577) htg_part_load_ratio.setCoefficient4xPOW3(1.2596) htg_part_load_ratio.setMinimumValueofx(0.7) htg_part_load_ratio.setMaximumValueofx(1.0) dx_htg_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model, sch, htg_cap_f_of_temp, htg_cap_f_of_flow, htg_energy_input_ratio_f_of_temp, htg_energy_input_ratio_f_of_flow, htg_part_load_ratio) dx_htg_coil.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(-10) return dx_htg_coil end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2073 def add_ptac_dx_cooling(model, zone, zero_outdoor_air) # Create a PTAC for each zone: # PTAC DX Cooling with electric heating coil; electric heating coil is always off # TO DO: need to apply this system to space types: # (1) data processing area: control room, data centre # when cooling capacity <= 20kW and # (2) residential/accommodation: murb, hotel/motel guest room # when building/space heated only (this as per NECB; apply to # all for initial work? CAN-QUEST limitation) # TO DO: PTAC characteristics: sizing, fan schedules, temperature setpoints, interaction with MAU always_on = model.alwaysOnDiscreteSchedule always_off = BTAP::Resources::Schedules::StandardSchedules::ON_OFF.always_off(model) htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_off) # Set up PTAC DX coil with NECB performance curve characteristics; clg_coil = add_onespeed_DX_coil(model, always_on) # Set up PTAC constant volume supply fan fan = OpenStudio::Model::FanOnOff.new(model) fan.setPressureRise(640) # This method will seem like an error in number of args..but this is due to swig voodoo. ptac = OpenStudio::Model::ZoneHVACPackagedTerminalAirConditioner.new(model, always_on, fan, htg_coil, clg_coil) ptac.setName("#{zone.name} PTAC") ptac.setSupplyAirFanOperatingModeSchedule(always_off) if zero_outdoor_air ptac.setOutdoorAirFlowRateWhenNoCoolingorHeatingisNeeded 1.0e-5 ptac.setOutdoorAirFlowRateDuringCoolingOperation(1.0e-5) ptac.setOutdoorAirFlowRateDuringHeatingOperation(1.0e-5) end ptac.addToThermalZone(zone) end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb, line 2 def add_sys1_unitary_ac_baseboard_heating(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:, multispeed: false) if multispeed add_sys1_unitary_ac_baseboard_heating_multi_speed(model: model, zones: zones, mau_type: mau_type, mau_heating_coil_type: mau_heating_coil_type, baseboard_type: baseboard_type, hw_loop: hw_loop) else add_sys1_unitary_ac_baseboard_heating_single_speed(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: zones, mau_type: mau_type, mau_heating_coil_type: mau_heating_coil_type, baseboard_type: baseboard_type, hw_loop: hw_loop) end end
At this point the only way to implement multi-stage cooling and heating in OS is through the use of object âAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeedâ. This component uses as an argument a control zone and then it responds to a call for heating or cooling for that control zone. This aspect of this component makes it incompatible with how a system_1 make up air unit works where a constant supply air temperature is delivered to the spaces. It is therefore not recommended to use this method and to use the single speed implementation of systems_1.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb, line 8 def add_sys1_unitary_ac_baseboard_heating_multi_speed(model:, zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:) # Keep all data and assumptions for both systems on the top here for easy reference. system_data = {} system_data[:name] = 'Sys_1_Make-up air unit' system_data[:PreheatDesignTemperature] = 7.0 system_data[:PreheatDesignHumidityRatio] = 0.008 system_data[:PrecoolDesignTemperature] = 13.0 system_data[:PrecoolDesignHumidityRatio] = 0.008 system_data[:SizingOption] = 'NonCoincident' system_data[:CoolingDesignAirFlowMethod] = 'DesignDay' system_data[:CoolingDesignAirFlowRate] = 0.0 system_data[:HeatingDesignAirFlowMethod] = 'DesignDay' system_data[:HeatingDesignAirFlowRate] = 0.0 system_data[:SystemOutdoorAirMethod] = 'ZoneSum' system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085 system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080 system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0 system_data[:AllOutdoorAirinCooling] = true system_data[:AllOutdoorAirinHeating] = true system_data[:TypeofLoadtoSizeOn] = 'VentilationRequirement' system_data[:MinimumSystemAirFlowRatio] = 1.0 system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0 # Zone data system_data[:system_supply_air_temperature] = 20.0 system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 # System Type 1: PTAC with no heating (unitary AC) # Zone baseboards, electric or hot water depending on argument baseboard_type # baseboard_type choices are "Hot Water" or "Electric" # PSZ to represent make-up air unit (if present) # This measure creates: # a PTAC unit for each zone in the building; DX cooling coil # and heating coil that is always off # Baseboards ("Hot Water or "Electric") in zones connected to hot water loop # MAU is present if argument mau == true, not present if argument mau == false # MAU is PSZ; DX cooling # MAU heating coil: hot water coil or electric, depending on argument mau_heating_coil_type # mau_heating_coil_type choices are "Hot Water", "Electric" # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e. # "NaturalGas","Electricity","PropaneGas","FuelOil#1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1" # Some system parameters are set after system is set up; by applying method 'apply_hvac_efficiency_standard' always_on = model.alwaysOnDiscreteSchedule # define always off schedule for ptac heating coil always_off = BTAP::Resources::Schedules::StandardSchedules::ON_OFF.always_off(model) # Create MAU # TO DO: MAU sizing, characteristics (fan operation schedules, temperature setpoints, outdoor air, etc) if mau_type == true mau_air_loop = common_air_loop(model: model, system_data: system_data) mau_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) # Setup heating and cooling coils mau_clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model) mau_clg_coil.setFuelType('Electricity') mau_clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model) mau_clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model) mau_clg_coil.addStage(mau_clg_stage_1) mau_clg_coil.addStage(mau_clg_stage_2) mau_clg_coil.setApplyPartLoadFractiontoSpeedsGreaterthan1(false) mau_htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model) mau_htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model) mau_htg_coil.addStage(mau_htg_stage_1) mau_htg_stage_1.setNominalCapacity(0.001) if mau_heating_coil_type == 'Electric' mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) elsif mau_heating_coil_type == 'Hot Water' mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) hw_loop.addDemandBranchForComponent(mau_supplemental_htg_coil) else raise("#{mau_heating_coil_type} is not a valid heating coil type.)") end # @todo other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types) # This method will seem like an error in number of args..but this is due to swig voodoo. air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, mau_fan, mau_htg_coil, mau_clg_coil, mau_supplemental_htg_coil) air_to_air_heatpump.setName("#{zones[0].name} ASHP") air_to_air_heatpump.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation]) air_to_air_heatpump.setControllingZoneorThermostatLocation(zones[0]) air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on) air_to_air_heatpump.setNumberofSpeedsforHeating(1) air_to_air_heatpump.setNumberofSpeedsforCooling(2) # oa_controller oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') # oa_system oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = mau_air_loop.supplyInletNode air_to_air_heatpump.addToNode(supply_inlet_node) oa_system.addToNode(supply_inlet_node) # Add a setpoint manager to control the supply air temperature sat_sch = OpenStudio::Model::ScheduleRuleset.new(model) sat_sch.setName('Makeup-Air Unit Supply Air Temp') sat_sch.defaultDaySchedule.setName('Makeup Air Unit Supply Air Temp Default') sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature]) setpoint_mgr = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch) setpoint_mgr.addToNode(mau_air_loop.supplyOutletNode) # Create MAU end zones.each do |zone| # Zone sizing temperature difference sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod('TemperatureDifference') sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(11.0) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod('TemperatureDifference') sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(21.0) sizing_zone.setZoneCoolingSizingFactor(1.1) sizing_zone.setZoneHeatingSizingFactor(1.3) # Set up PTAC heating coil; apply always off schedule # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on) zero_outdoor_air = true # flag to set outside air flow to zero add_ptac_dx_cooling(model, zone, zero_outdoor_air) # add zone baseboards add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone) # # Create a diffuser and attach the zone/diffuser pair to the MAU air loop, if applicable if mau_type == true diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on) mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent) # components for MAU end # of zone loop end return true end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb, line 30 def add_sys1_unitary_ac_baseboard_heating_single_speed(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:) # Keep all data and assumptions for both systems on the top here for easy reference. system_data = {} system_data[:name] = 'Sys_1_Make-up air unit' system_data[:PreheatDesignTemperature] = 7.0 system_data[:PreheatDesignHumidityRatio] = 0.008 system_data[:PrecoolDesignTemperature] = 13.0 system_data[:PrecoolDesignHumidityRatio] = 0.008 system_data[:SizingOption] = 'NonCoincident' system_data[:CoolingDesignAirFlowMethod] = 'DesignDay' system_data[:CoolingDesignAirFlowRate] = 0.0 system_data[:HeatingDesignAirFlowMethod] = 'DesignDay' system_data[:HeatingDesignAirFlowRate] = 0.0 system_data[:SystemOutdoorAirMethod] = 'ZoneSum' system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085 system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080 system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0 system_data[:AllOutdoorAirinCooling] = true system_data[:AllOutdoorAirinHeating] = true if necb_reference_hp system_data[:TypeofLoadtoSizeOn] = 'Total' else system_data[:TypeofLoadtoSizeOn] = 'VentilationRequirement' end system_data[:MinimumSystemAirFlowRatio] = 1.0 # Zone Sizing data system_data[:system_supply_air_temperature] = 20.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:ZoneDXCoolingSizingFactor] = 1.0 system_data[:ZoneDXHeatingSizingFactor] = 1.3 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 # System Type 1: PTAC with no heating (unitary AC) # Zone baseboards, electric or hot water depending on argument baseboard_type # baseboard_type choices are "Hot Water" or "Electric" # PSZ to represent make-up air unit (if present) # This measure creates: # a PTAC unit for each zone in the building; DX cooling coil # and heating coil that is always off # Baseboards ("Hot Water or "Electric") in zones connected to hot water loop # MAU is present if argument mau == true, not present if argument mau == false # MAU is PSZ; DX cooling # MAU heating coil: hot water coil or electric, depending on argument mau_heating_coil_type # mau_heating_coil_type choices are "Hot Water", "Electric" # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e. # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1" # If reference_hp = true, NECB 8.4.4.13 Heat Pump System Type 1: CAV Packaged rooftop heat pump with # zone baseboard (electric or hot water depending on argument baseboard_type) # Some system parameters are set after system is set up; by applying method 'apply_hvac_efficiency_standard' always_on = model.alwaysOnDiscreteSchedule always_off = BTAP::Resources::Schedules::StandardSchedules::ON_OFF.always_off(model) # Create MAU # TO DO: MAU sizing, characteristics (fan operation schedules, temperature setpoints, outdoor air, etc) if mau_type == true mau_air_loop = common_air_loop(model: model, system_data: system_data) #if reference_hp # AirLoopHVACUnitaryHeatPumpAirToAir needs FanOnOff in order for the fan to turn off during off hours # mau_fan = OpenStudio::Model::FanOnOff.new(model, always_on) #else mau_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) #end # MAU Heating type selection. raise("Flag 'necb_reference_hp' is true while 'mau_heating_coil_type' is not set to type DX") if (necb_reference_hp && (mau_heating_coil_type != 'DX')) if mau_heating_coil_type == 'Electric' # electric coil mau_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) elsif mau_heating_coil_type == 'Hot Water' mau_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) hw_loop.addDemandBranchForComponent(mau_htg_coil) elsif mau_heating_coil_type == 'DX' mau_htg_coil = add_onespeed_htg_DX_coil(model, always_on) mau_htg_coil.setName('CoilHeatingDXSingleSpeed_ashp') end # Set up Single Speed DX coil with mau_clg_coil = add_onespeed_DX_coil(model, always_on) mau_clg_coil.setName('CoilCoolingDXSingleSpeed_dx') mau_clg_coil.setName('CoilCoolingDXSingleSpeed_ashp') if necb_reference_hp # Set up OA system oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = mau_air_loop.supplyInletNode # Reference HP requires slight changes to default MAU heating #if reference_hp # Create supplemental heating coil based on default regional fuel type # epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) #primary_heating_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] #if primary_heating_fuel == 'NaturalGas' # supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) #elsif primary_heating_fuel == 'Electricity' or primary_heating_fuel == 'FuelOilNo2' # supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) #else #hot water coils is an option in the future # raise('Invalid fuel type selected for heat pump supplemental coil') #end #air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, mau_fan, mau_htg_coil, mau_clg_coil, supplemental_htg_coil) #air_to_air_heatpump.setName("#{control_zone.name} ASHP") #air_to_air_heatpump.setControllingZone(control_zone) #air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on) #air_to_air_heatpump.addToNode(supply_inlet_node) #else mau_fan.addToNode(supply_inlet_node) mau_htg_coil.addToNode(supply_inlet_node) mau_clg_coil.addToNode(supply_inlet_node) #end oa_system.addToNode(supply_inlet_node) # Add a setpoint manager to control the supply air temperature if necb_reference_hp setpoint_mgr = OpenStudio::Model::SetpointManagerWarmest.new(model) setpoint_mgr.setMinimumSetpointTemperature(13) setpoint_mgr.setMaximumSetpointTemperature(20) setpoint_mgr.addToNode(mau_air_loop.supplyOutletNode) else sat_sch = OpenStudio::Model::ScheduleRuleset.new(model) sat_sch.setName('Makeup-Air Unit Supply Air Temp') sat_sch.defaultDaySchedule.setName('Makeup Air Unit Supply Air Temp Default') sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature]) setpoint_mgr = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch) setpoint_mgr.addToNode(mau_air_loop.supplyOutletNode) end end zones.each do |zone| # Zone sizing temperature difference sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) # Different sizing factors for reference HP capacity if necb_reference_hp sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) else sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) end # Create a PTAC for each zone: # PTAC DX Cooling with electric heating coil; electric heating coil is always off # TO DO: need to apply this system to space types: # (1) data processing area: control room, data centre # when cooling capacity <= 20kW and # (2) residential/accommodation: murb, hotel/motel guest room # when building/space heated only (this as per NECB; apply to # all for initial work? CAN-QUEST limitation) # TO DO: PTAC characteristics: sizing, fan schedules, temperature setpoints, interaction with MAU # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on) zero_outdoor_air = true # flag to set outside air flow to 0.0 # Reference HP system does not use PTAC unless necb_reference_hp add_ptac_dx_cooling(model, zone, zero_outdoor_air) end # add zone baseboards add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone) # # Create a diffuser and attach the zone/diffuser pair to the MAU air loop, if applicable if necb_reference_hp # Create CAV RH (RH based on region's default fuel type or user input) if necb_reference_hp_supp_fuel == 'DefaultFuel' epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] end if necb_reference_hp_supp_fuel == 'NaturalGas' rh_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) elsif necb_reference_hp_supp_fuel == 'Electricity' or necb_reference_hp_supp_fuel == 'FuelOilNo2' rh_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) else #hot water coils is an option in the future raise('Invalid fuel type selected for heat pump supplemental coil') end cav_rh_terminal = OpenStudio::Model::AirTerminalSingleDuctConstantVolumeReheat.new(model, always_on, rh_coil) mau_air_loop.addBranchForZone(zone, cav_rh_terminal.to_StraightComponent) elsif mau_type == true diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on) mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent) # components for MAU end # of zone loop end if mau_type sys_name_pars = {} sys_name_pars['sys_hr'] = 'none' sys_name_pars['sys_clg'] = 'dx' sys_name_pars['sys_clg'] = 'ashp' if necb_reference_hp sys_name_pars['sys_htg'] = mau_heating_coil_type sys_name_pars['sys_htg'] = 'ashp' if necb_reference_hp sys_name_pars['sys_sf'] = 'cv' sys_name_pars['zone_htg'] = baseboard_type sys_oa = 'doas' if necb_reference_hp sys_name_pars['zone_clg'] = 'none' sys_oa = 'mixed' else sys_name_pars['zone_clg'] = 'ptac' sys_oa = 'doas' end sys_name_pars['sys_rf'] = 'none' assign_base_sys_name(mau_air_loop, sys_abbr: 'sys_1', sys_oa: sys_oa, sys_name_pars: sys_name_pars) end return true end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb, line 2 def add_sys2_FPFC_sys5_TPFC(model:, zones:, chiller_type:, fan_coil_type:, mau_cooling_type:, hw_loop:) # System 2 AHU data system_data = {} system_data[:name] = 'Sys_2_Make-up air unit' system_data[:PreheatDesignTemperature] = 7.0 system_data[:PreheatDesignHumidityRatio] = 0.008 system_data[:PrecoolDesignTemperature] = 13.0 system_data[:PrecoolDesignHumidityRatio] = 0.008 system_data[:SizingOption] = 'NonCoincident' system_data[:CoolingDesignAirFlowMethod] = 'DesignDay' system_data[:CoolingDesignAirFlowRate] = 0.0 system_data[:HeatingDesignAirFlowMethod] = 'DesignDay' system_data[:HeatingDesignAirFlowRate] = 0.0 system_data[:SystemOutdoorAirMethod] = 'ZoneSum' system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085 system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080 system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 13.1 system_data[:AllOutdoorAirinCooling] = false system_data[:AllOutdoorAirinHeating] = false system_data[:TypeofLoadtoSizeOn] = 'Sensible' system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 13.0 system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.1 system_data[:MinimumSystemAirFlowRatio] = 1.0 # System 2 Zone data system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 # System Type 2: FPFC or System 5: TPFC # This measure creates: # -a four pipe or a two pipe fan coil unit for each zone in the building; # -a make up air-unit to provide ventilation to each zone; # -a heating loop, cooling loop and condenser loop to serve four pipe fan coil units # Arguments: # boiler_fueltype: "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNO2","Coal","Diesel","Gasoline","OtherFuel1" # chiller_type: "Scroll";"Centrifugal";"Rotary Screw";"Reciprocating" # mua_cooling_type: make-up air unit cooling type "DX";"Hydronic" # fan_coil_type options are "TPFC" or "FPFC" # @todo Add arguments as needed when the sizing routine is finalized. For example we will need to know the # required size of the boilers to decide on how many units are needed based on NECB rules. always_on = model.alwaysOnDiscreteSchedule # schedule for two-pipe fan coil operation. 3 seasons for heating/cooling. tpfc_clg_availability_sch, tpfc_htg_availability_sch = create_heating_cooling_on_off_availability_schedule(model) # Create a chilled water loop chw_loop = OpenStudio::Model::PlantLoop.new(model) chiller1, chiller2 = setup_chw_loop_with_components(model, chw_loop, chiller_type) # Create a condenser Loop cw_loop = OpenStudio::Model::PlantLoop.new(model) ctower = setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2) # Set up make-up air unit for ventilation # TO DO: Need to investigate characteristics of make-up air unit for NECB reference # and define them here air_loop = mau_air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName(system_data[:name]) fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) # Assume direct-fired gas heating coil for now; need to add logic # to set up hydronic or electric coil depending on proposed? htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) # Add DX or hydronic cooling coil if mau_cooling_type == 'DX' clg_coil = add_onespeed_DX_coil(model, tpfc_clg_availability_sch) clg_coil.setName('CoilCoolingDXSingleSpeed_dx') elsif mau_cooling_type == 'Hydronic' clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, tpfc_clg_availability_sch) chw_loop.addDemandBranchForComponent(clg_coil) end # does MAU have an economizer? oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') # oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model,oa_controller) oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode fan.addToNode(supply_inlet_node) htg_coil.addToNode(supply_inlet_node) clg_coil.addToNode(supply_inlet_node) oa_system.addToNode(supply_inlet_node) # Add a setpoint manager single zone reheat to control the # supply air temperature based on the needs of default zone (OpenStudio picks one) # TO DO: need to have method to pick appropriate control zone? setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model) setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin]) setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax]) setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode) # Set up zonal FC (ZoneHVAC,cooling coil, heating coil, fan) in each zone zones.each do |zone| # Zone sizing temperature difference sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) # fc supply fan fc_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) if fan_coil_type == 'FPFC' # heating coil fc_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) # cooling coil fc_clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, always_on) elsif fan_coil_type == 'TPFC' # heating coil fc_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, tpfc_htg_availability_sch) # cooling coil fc_clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, tpfc_clg_availability_sch) end # connect heating coil to hot water loop and cooling coil to chw loop. hw_loop.addDemandBranchForComponent(fc_htg_coil) chw_loop.addDemandBranchForComponent(fc_clg_coil) # add connections to FPFC. # This method will seem like an error in number of args..but this is due to swig voodoo. zone_fc = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model, always_on, fc_fan, fc_clg_coil, fc_htg_coil) zone_fc.addToThermalZone(zone) # Create a diffuser and attach the zone/diffuser pair to the air loop (make-up air unit) diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on) air_loop.addBranchForZone(zone, diffuser.to_StraightComponent) # zone loop end sys_abbr = 'sys_2' sys_abbr = 'sys_5' if fan_coil_type == 'TPFC' sys_name_pars = {} sys_name_pars['sys_hr'] = 'none' sys_name_pars['sys_clg'] = mau_cooling_type sys_name_pars['sys_htg'] = 'g' sys_name_pars['sys_sf'] = 'cv' sys_name_pars['zone_htg'] = fan_coil_type sys_name_pars['zone_clg'] = fan_coil_type sys_name_pars['sys_rf'] = 'none' assign_base_sys_name(mau_air_loop, sys_abbr: sys_abbr, sys_oa: 'doas', sys_name_pars: sys_name_pars) end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 250 def add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) always_on = model.alwaysOnDiscreteSchedule add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone) diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on) air_loop.addBranchForZone(zone, diffuser.to_StraightComponent) end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 2 def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true, multispeed: false) if multispeed add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model: model, zones: zones, heating_coil_type: heating_coil_type, baseboard_type: baseboard_type, hw_loop: hw_loop, new_auto_zoner: new_auto_zoner) else add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: zones, heating_coil_type: heating_coil_type, baseboard_type: baseboard_type, hw_loop: hw_loop, new_auto_zoner: new_auto_zoner) end end
Some tests still require a simple way to set up a system without sizing.. so we are keeping the auto_zoner flag for this method.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb, line 3 def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model:, zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true) system_data = {} system_data[:name] = 'Sys_3_PSZ' system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0 system_data[:AllOutdoorAirinCooling] = false system_data[:AllOutdoorAirinHeating] = false system_data[:TypeofLoadtoSizeOn] = 'Sensible' system_data[:MinimumSystemAirFlowRatio] = 1.0 system_data[:PreheatDesignTemperature] = 7.0 system_data[:PreheatDesignHumidityRatio] = 0.008 system_data[:PrecoolDesignTemperature] = 13.0 system_data[:PrecoolDesignHumidityRatio] = 0.008 system_data[:SizingOption] = 'NonCoincident' system_data[:CoolingDesignAirFlowMethod] = 'DesignDay' system_data[:CoolingDesignAirFlowRate] = 0.0 system_data[:HeatingDesignAirFlowMethod] = 'DesignDay' system_data[:HeatingDesignAirFlowRate] = 0.0 system_data[:SystemOutdoorAirMethod] = 'ZoneSum' system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085 system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080 # System 3 Zone data system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0 system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0 system_data[:ZoneDXCoolingSizingFactor] = 1.0 system_data[:ZoneDXHeatingSizingFactor] = 1.3 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0 if new_auto_zoner == true # Create system airloop # Add Air Loop air_loop = add_system_3_and_8_airloop_multi_speed(heating_coil_type, model, system_data, determine_control_zone(zones)) # Add Zone equipment zones.each do |zone| # Zone sizing temperature difference sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) end return true else zones.each do |zone| air_loop = add_system_3_and_8_airloop_multi_speed(heating_coil_type, model, system_data, zone) add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) end return true end end
Some tests still require a simple way to set up a system without sizing.. so we are keeping the auto_zoner flag for this method.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 33 def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true) system_data = {} system_data[:name] = 'Sys_3_PSZ' system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0 system_data[:AllOutdoorAirinCooling] = false system_data[:AllOutdoorAirinHeating] = false system_data[:TypeofLoadtoSizeOn] = 'Sensible' system_data[:MinimumSystemAirFlowRatio] = 1.0 system_data[:PreheatDesignTemperature] = 7.0 system_data[:PreheatDesignHumidityRatio] = 0.008 system_data[:PrecoolDesignTemperature] = 13.0 system_data[:PrecoolDesignHumidityRatio] = 0.008 system_data[:SizingOption] = 'NonCoincident' system_data[:CoolingDesignAirFlowMethod] = 'DesignDay' system_data[:CoolingDesignAirFlowRate] = 0.0 system_data[:HeatingDesignAirFlowMethod] = 'DesignDay' system_data[:HeatingDesignAirFlowRate] = 0.0 system_data[:SystemOutdoorAirMethod] = 'ZoneSum' system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085 system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080 # System 3 Zone data system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0 system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0 system_data[:ZoneDXCoolingSizingFactor] = 1.0 system_data[:ZoneDXHeatingSizingFactor] = 1.3 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0 if new_auto_zoner == true # Create system airloop # Add Air Loop air_loop = add_system_3_and_8_airloop(heating_coil_type, model, system_data, determine_control_zone(zones), necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel) # Add Zone equipment zones.each do |zone| # Zone sizing temperature difference sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) if necb_reference_hp sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) else sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) end add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) end else zones.each do |zone| air_loop = add_system_3_and_8_airloop(heating_coil_type, model, system_data, zone, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel) add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) end end sys_name_pars = {} sys_name_pars['sys_hr'] = 'none' sys_name_pars['sys_clg'] = 'dx' sys_name_pars['sys_clg'] = 'ashp' if necb_reference_hp sys_name_pars['sys_htg'] = heating_coil_type sys_name_pars['sys_htg'] = 'ashp' if necb_reference_hp sys_name_pars['sys_sf'] = 'cv' sys_name_pars['zone_htg'] = baseboard_type sys_name_pars['zone_clg'] = 'none' sys_name_pars['sys_rf'] = 'none' assign_base_sys_name(air_loop, sys_abbr: 'sys_3', sys_oa: 'mixed', sys_name_pars: sys_name_pars) return true end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb, line 2 def add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, heating_coil_type:, baseboard_type:, hw_loop:) system_data = {} system_data[:name] = 'Sys_4_PSZ' system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0 system_data[:AllOutdoorAirinCooling] = false system_data[:AllOutdoorAirinHeating] = false system_data[:TypeofLoadtoSizeOn] = 'Sensible' system_data[:MinimumSystemAirFlowRatio] = 1.0 system_data[:PreheatDesignTemperature] = 7.0 system_data[:PreheatDesignHumidityRatio] = 0.008 system_data[:PrecoolDesignTemperature] = 13.0 system_data[:PrecoolDesignHumidityRatio] = 0.008 system_data[:SizingOption] = 'NonCoincident' system_data[:CoolingDesignAirFlowMethod] = 'DesignDay' system_data[:CoolingDesignAirFlowRate] = 0.0 system_data[:HeatingDesignAirFlowMethod] = 'DesignDay' system_data[:HeatingDesignAirFlowRate] = 0.0 system_data[:SystemOutdoorAirMethod] = 'ZoneSum' system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085 system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080 # zone system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0 system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0 system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0 system_data[:ZoneDXCoolingSizingFactor] = 1.0 system_data[:ZoneDXHeatingSizingFactor] = 1.3 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 # System Type 4: PSZ-AC # This measure creates: # -a constant volume packaged single-zone A/C unit # for each zone in the building; DX cooling with # heating coil: fuel-fired or electric, depending on argument heating_coil_type # heating_coil_type choices are "Electric", "Gas" # zone baseboards: hot water or electric, depending on argument baseboard_type # baseboard_type choices are "Hot Water" or "Electric" # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e. # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1" # NOTE: This is the same as system type 3 (single zone make-up air unit and single zone rooftop unit are both PSZ systems) # SHOULD WE COMBINE sys3 and sys4 into one script? # # control_zone = determine_control_zone(zones) # Todo change this when control zone method is working. control_zone = zones.first always_on = model.alwaysOnDiscreteSchedule # Create a PSZ for each zone # TO DO: need to apply this system to space types: # (1) automotive area: repair/parking garage, fire engine room, indoor truck bay # (2) supermarket/food service: food preparation with kitchen hood/vented appliance # (3) warehouse area (non-refrigerated spaces) air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName("#{system_data[:name]}_#{control_zone.name}") # Zone sizing temperature difference sizing_zone = control_zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) if necb_reference_hp sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) else sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) end if necb_reference_hp # AirLoopHVACUnitaryHeatPumpAirToAir needs FanOnOff in order for the fan to turn off during off hours fan = OpenStudio::Model::FanOnOff.new(model, always_on) else fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) end # Set up DX coil with NECB performance curve characteristics; clg_coil = add_onespeed_DX_coil(model, always_on) clg_coil.setName('CoilCoolingDXSingleSpeed_dx') clg_coil.setName('CoilCoolingDXSingleSpeed_ashp') if necb_reference_hp raise("Flag 'necb_reference_hp' is set to true while parameter 'heating_coil_type' is not set to DX") if (necb_reference_hp && (heating_coil_type != 'DX')) if heating_coil_type == 'Electric' # electric coil htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) elsif heating_coil_type == 'Gas' htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) elsif heating_coil_type == 'DX' htg_coil = add_onespeed_htg_DX_coil(model, always_on) htg_coil.setName('CoilHeatingDXSingleSpeed_ashp') end # TO DO: other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types) # oa_controller oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') # oa_system oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode if necb_reference_hp #create supplemental heating coil based on default regional fuel type if necb_reference_hp_supp_fuel == 'DefaultFuel' epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] end if necb_reference_hp_supp_fuel == 'NaturalGas' supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) elsif necb_reference_hp_supp_fuel == 'Electricity' or necb_reference_hp_supp_fuel == 'FuelOilNo2' supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) else #hot water coils is an option in the future raise('Invalid fuel type selected for heat pump supplemental coil') end # This method will seem like an error in number of args..but this is due to swig voodoo. air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil) air_to_air_heatpump.setName("#{control_zone.name} ASHP") air_to_air_heatpump.setControllingZone(control_zone) air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on) air_to_air_heatpump.addToNode(supply_inlet_node) else fan.addToNode(supply_inlet_node) htg_coil.addToNode(supply_inlet_node) clg_coil.addToNode(supply_inlet_node) end oa_system.addToNode(supply_inlet_node) # Add a setpoint manager single zone reheat to control the # supply air temperature based on the needs of this zone setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model) setpoint_mgr_single_zone_reheat.setControlZone(control_zone) setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin]) setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax]) setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode) # Create sensible heat exchanger # heat_exchanger = BTAP::Resources::HVAC::Plant::add_hrv(model) # heat_exchanger.setSensibleEffectivenessat100HeatingAirFlow(0.5) # heat_exchanger.setSensibleEffectivenessat75HeatingAirFlow(0.5) # heat_exchanger.setSensibleEffectivenessat100CoolingAirFlow(0.5) # heat_exchanger.setSensibleEffectivenessat75CoolingAirFlow(0.5) # heat_exchanger.setLatentEffectivenessat100HeatingAirFlow(0.0) # heat_exchanger.setLatentEffectivenessat75HeatingAirFlow(0.0) # heat_exchanger.setLatentEffectivenessat100CoolingAirFlow(0.0) # heat_exchanger.setLatentEffectivenessat75CoolingAirFlow(0.0) # heat_exchanger.setSupplyAirOutletTemperatureControl(false) # # Connect heat exchanger # oa_node = oa_system.outboardOANode # heat_exchanger.addToNode(oa_node.get) zones.each do |zone| sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) if necb_reference_hp sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) else sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) end # Create a diffuser and attach the zone/diffuser pair to the air loop # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on) add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone) diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on) air_loop.addBranchForZone(zone, diffuser.to_StraightComponent) # zone loop end sys_name_pars = {} sys_name_pars['sys_hr'] = 'none' sys_name_pars['sys_clg'] = 'dx' sys_name_pars['sys_clg'] = 'ashp' if necb_reference_hp sys_name_pars['sys_htg'] = heating_coil_type sys_name_pars['sys_htg'] = 'ashp' if necb_reference_hp sys_name_pars['sys_sf'] = 'cv' sys_name_pars['zone_htg'] = baseboard_type sys_name_pars['zone_clg'] = 'none' sys_name_pars['sys_rf'] = 'none' assign_base_sys_name(air_loop, sys_abbr: 'sys_4', sys_oa: 'mixed', sys_name_pars: sys_name_pars) return true end
end add_sys4_single_zone_make_up_air_unit_with_baseboard_heating
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb, line 4 def add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, chiller_type:, fan_type:, hw_loop:) # System Type 6: VAV w/ Reheat # This measure creates: # a single hot water loop with a natural gas or electric boiler or for the building # a single chilled water loop with water cooled chiller for the building # a single condenser water loop for heat rejection from the chiller # a VAV system w/ hot water or electric heating, chilled water cooling, and # hot water or electric reheat for each story of the building # Arguments: # "boiler_fueltype" choices match OS choices for boiler fuel type: # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1" # "heating_coil_type": "Electric" or "Hot Water" # "baseboard_type": "Electric" and "Hot Water" # "chiller_type": "Scroll";"Centrifugal";""Screw";"Reciprocating" # "fan_type": "AF_or_BI_rdg_fancurve";"AF_or_BI_inletvanes";"fc_inletvanes";"var_speed_drive" # system_data = {} system_data[:name] = 'Sys_6_VAV with Reheat' system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 13.1 system_data[:AllOutdoorAirinCooling] = false system_data[:AllOutdoorAirinHeating] = false system_data[:MinimumSystemAirFlowRatio] = 0.3 # zone data system_data[:system_supply_air_temperature] = 13.0 system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:ZoneCoolingSizingFactor] = 1.1 system_data[:ZoneHeatingSizingFactor] = 1.3 system_data[:ZoneVAVMinFlowFactorPerFloorArea] = 0.002 system_data[:ZoneVAVMaxReheatTemp] = 43.0 system_data[:ZoneVAVDamperAction] = 'Normal' always_on = model.alwaysOnDiscreteSchedule # Chilled Water Plant chw_loop = OpenStudio::Model::PlantLoop.new(model) chiller1, chiller2 = setup_chw_loop_with_components(model, chw_loop, chiller_type) # Condenser System cw_loop = OpenStudio::Model::PlantLoop.new(model) ctower = setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2) # Make a Packaged VAV w/ PFP Boxes for each story of the building model.getBuildingStorys.sort.each do |story| unless (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).empty? air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName('Sys_6_VAV with Reheat') supply_fan = OpenStudio::Model::FanVariableVolume.new(model, always_on) supply_fan.setName('Sys6 Supply Fan') return_fan = OpenStudio::Model::FanVariableVolume.new(model, always_on) return_fan.setName('Sys6 Return Fan') if heating_coil_type == 'Hot Water' htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) hw_loop.addDemandBranchForComponent(htg_coil) end if heating_coil_type == 'Electric' htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) end clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, always_on) chw_loop.addDemandBranchForComponent(clg_coil) oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode supply_outlet_node = air_loop.supplyOutletNode supply_fan.addToNode(supply_inlet_node) htg_coil.addToNode(supply_inlet_node) clg_coil.addToNode(supply_inlet_node) oa_system.addToNode(supply_inlet_node) returnAirNode = oa_system.returnAirModelObject.get.to_Node.get return_fan.addToNode(returnAirNode) # Add a setpoint manager to control the # supply air to a constant temperature sat_sch = OpenStudio::Model::ScheduleRuleset.new(model) sat_sch.setName('Supply Air Temp') sat_sch.defaultDaySchedule.setName('Supply Air Temp Default') sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature]) sat_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch) sat_stpt_manager.addToNode(supply_outlet_node) # Make a VAV terminal with HW reheat for each zone on this story that is in intersection with the zones array. # and hook the reheat coil to the HW loop (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).each do |zone| # Zone sizing parameters sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) if heating_coil_type == 'Hot Water' reheat_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) hw_loop.addDemandBranchForComponent(reheat_coil) elsif heating_coil_type == 'Electric' reheat_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) end # Set zone baseboards add_zone_baseboards(model: model, zone: zone, baseboard_type: baseboard_type, hw_loop: hw_loop) vav_terminal = OpenStudio::Model::AirTerminalSingleDuctVAVReheat.new(model, always_on, reheat_coil) air_loop.addBranchForZone(zone, vav_terminal.to_StraightComponent) # NECB2011 minimum zone airflow setting vav_terminal.setFixedMinimumAirFlowRate(system_data[:ZoneVAVMinFlowFactorPerFloorArea] * zone.floorArea) vav_terminal.setMaximumReheatAirTemperature(system_data[:ZoneVAVMaxReheatTemp]) vav_terminal.setDamperHeatingAction(system_data[:ZoneVAVDamperAction]) end sys_name_pars = {} sys_name_pars['sys_hr'] = 'none' sys_name_pars['sys_htg'] = heating_coil_type sys_name_pars['sys_clg'] = 'Chilled Water' sys_name_pars['sys_sf'] = 'vv' sys_name_pars['zone_htg'] = baseboard_type sys_name_pars['zone_clg'] = 'none' sys_name_pars['sys_rf'] = 'vv' assign_base_sys_name(air_loop, sys_abbr: 'sys_6', sys_oa: 'mixed', sys_name_pars: sys_name_pars) end # next story end # for debugging # puts "end add_sys6_multi_zone_built_up_with_baseboard_heating" return true end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb, line 316 def add_sys6_multi_zone_reference_hp_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, hw_loop:, necb_reference_hp_supp_fuel:'DefaultFuel') #system data system_data = {} system_data[:name] = 'Sys_6_VAV with Reheat' system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_data[:CentralHeatingDesignSupplyAirTemperature] = 13.1 system_data[:AllOutdoorAirinCooling] = false system_data[:AllOutdoorAirinHeating] = false # zone data system_data[:system_supply_air_temperature] = 13.0 system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_data[:ZoneDXCoolingSizingFactor] = 1.0 system_data[:ZoneDXHeatingSizingFactor] = 1.3 always_on = model.alwaysOnDiscreteSchedule model.getBuildingStorys.sort.each do |story| unless (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).empty? air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName('Sys_6_CAV') supply_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) supply_fan.setName('Sys6 Supply Fan') return_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) return_fan.setName('Sys6 Return Fan') htg_coil = add_onespeed_htg_DX_coil(model, always_on) htg_coil.setName('CoilHeatingDXSingleSpeed_ashp') clg_coil = add_onespeed_DX_coil(model, always_on) clg_coil.setName('CoilCoolingDXSingleSpeed_ashp') oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode supply_outlet_node = air_loop.supplyOutletNode supply_fan.addToNode(supply_inlet_node) htg_coil.addToNode(supply_inlet_node) clg_coil.addToNode(supply_inlet_node) oa_system.addToNode(supply_inlet_node) returnAirNode = oa_system.returnAirModelObject.get.to_Node.get return_fan.addToNode(returnAirNode) # Add a setpoint manager to control the # supply air to a constant temperature sat_sch = OpenStudio::Model::ScheduleRuleset.new(model) sat_sch.setName('Supply Air Temp') #sat_sch.defaultDaySchedule.setName('Supply Air Temp Default') #sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature]) #sat_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch) sat_stpt_manager = OpenStudio::Model::SetpointManagerWarmest.new(model) sat_stpt_manager.setControlVariable("Temperature") sat_stpt_manager.setMinimumSetpointTemperature(13) sat_stpt_manager.setMaximumSetpointTemperature(24) sat_stpt_manager.addToNode(supply_outlet_node) # Make CAV terminals for each zone on this story that is in intersection with the zones array. (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).each do |zone| # Zone sizing parameters sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) # Set zone baseboards add_zone_baseboards(model: model, zone: zone, baseboard_type: baseboard_type, hw_loop: hw_loop) # Create CAV RH (RH based on region's default fuel type) if necb_reference_hp_supp_fuel == 'DefaultFuel' epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] end if necb_reference_hp_supp_fuel == 'NaturalGas' rh_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) elsif necb_reference_hp_supp_fuel == 'Electricity' or necb_reference_hp_supp_fuel == 'FuelOilNo2' rh_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) else #hot water coils is an option in the future raise('Invalid fuel type selected for heat pump supplemental coil') end cav_rh_terminal = OpenStudio::Model::AirTerminalSingleDuctConstantVolumeReheat.new(model, always_on, rh_coil) air_loop.addBranchForZone(zone, cav_rh_terminal.to_StraightComponent) end sys_name_pars = {} sys_name_pars['sys_hr'] = 'none' sys_name_pars['sys_htg'] = 'ashp' sys_name_pars['sys_clg'] = 'ashp' sys_name_pars['sys_sf'] = 'cv' sys_name_pars['zone_htg'] = baseboard_type sys_name_pars['zone_clg'] = 'none' sys_name_pars['sys_rf'] = 'cv' assign_base_sys_name(air_loop, sys_abbr: 'sys_6', sys_oa: 'mixed', sys_name_pars: sys_name_pars) end end end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 133 def add_system_3_and_8_airloop(heating_coil_type, model, system_data, control_zone, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel') # System Type 3: PSZ-AC # This measure creates: # -a constant volume packaged single-zone A/C unit # for each zone in the building; DX cooling with # heating coil: fuel-fired or electric, depending on argument heating_coil_type # heating_coil_type choices are "Electric", "Gas", "DX" # zone baseboards: hot water or electric, depending on argument baseboard_type # baseboard_type choices are "Hot Water" or "Electric" # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e. # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1" always_on = model.alwaysOnDiscreteSchedule air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName("#{system_data[:name]} #{control_zone.name}") # Zone sizing temperature difference sizing_zone = control_zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) if necb_reference_hp sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) else sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) end if necb_reference_hp #AirLoopHVACUnitaryHeatPumpAirToAir needs FanOnOff in order for the fan to turn off during off hours fan = OpenStudio::Model::FanOnOff.new(model, always_on) else fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) end # Set up DX coil if necb_reference_hp #NECB curve characteristics clg_coil = add_onespeed_DX_coil(model, always_on) clg_coil.setName('CoilCoolingDXSingleSpeed_ashp') else clg_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model) #sets default OS curve (but will be replaced with NECB curves later) clg_coil.setName('CoilCoolingDXSingleSpeed_dx') end raise("Flag 'necb_reference_hp' is set to true while parameter 'heating_coil_type' is not set to DX") if (necb_reference_hp && (heating_coil_type != 'DX')) case heating_coil_type when 'Electric' # electric coil htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) when 'Gas' htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) when 'DX' #create main DX heating coil htg_coil = add_onespeed_htg_DX_coil(model, always_on) htg_coil.setName('CoilHeatingDXSingleSpeed_ashp') sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor]) else raise("#{heating_coil_type} is not a valid heating coil type.)") end # TO DO: other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types) # oa_controller oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') # oa_system oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode if necb_reference_hp #create supplemental heating coil based on default regional fuel type if necb_reference_hp_supp_fuel == 'DefaultFuel' epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] end if necb_reference_hp_supp_fuel == 'NaturalGas' supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) elsif necb_reference_hp_supp_fuel == 'Electricity' or necb_reference_hp_supp_fuel == 'FuelOilNo2' supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) else #hot water coils is an option in the future raise('Invalid fuel type selected for heat pump supplemental coil') end # This method will seem like an error in number of args..but this is due to swig voodoo. air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil) air_to_air_heatpump.setName("#{control_zone.name} ASHP") air_to_air_heatpump.setControllingZone(control_zone) air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on) air_to_air_heatpump.addToNode(supply_inlet_node) else fan.addToNode(supply_inlet_node) htg_coil.addToNode(supply_inlet_node) clg_coil.addToNode(supply_inlet_node) end oa_system.addToNode(supply_inlet_node) # Add a setpoint manager single zone reheat to control the # supply air temperature based on the needs of this zone setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model) setpoint_mgr_single_zone_reheat.setControlZone(control_zone) setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin]) setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax]) setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode) return air_loop end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb, line 81 def add_system_3_and_8_airloop_multi_speed(heating_coil_type, model, system_data, control_zone) # System Type 3: PSZ-AC # This measure creates: # -a constant volume packaged single-zone A/C unit # for each zone in the building; DX cooling with # heating coil: fuel-fired or electric, depending on argument heating_coil_type # heating_coil_type choices are "Electric", "Gas", "DX" # zone baseboards: hot water or electric, depending on argument baseboard_type # baseboard_type choices are "Hot Water" or "Electric" # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e. # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1" always_on = model.alwaysOnDiscreteSchedule air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName("#{system_data[:name]} #{control_zone.name}") # Zone sizing temperature difference sizing_zone = control_zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod]) sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) fan = OpenStudio::Model::FanConstantVolume.new(model, always_on) # Setup heating and cooling coils if (heating_coil_type == 'Gas') || (heating_coil_type == 'Electric') clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model) clg_coil.setFuelType('Electricity') clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model) clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model) clg_coil.addStage(clg_stage_1) clg_coil.addStage(clg_stage_2) clg_coil.setApplyPartLoadFractiontoSpeedsGreaterthan1(false) htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model) htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model) htg_coil.addStage(htg_stage_1) if heating_coil_type == 'Gas' supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on) supplemental_htg_coil.setNominalCapacity(0.001) elsif heating_coil_type == 'Electric' supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) htg_stage_1.setNominalCapacity(0.001) end # Single stage DX and Electric heating elsif heating_coil_type == 'DX' clg_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model) clg_coil.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation]) htg_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model) supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) else raise("#{heating_coil_type} is not a valid heating coil type.)") end # oa_controller oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') # oa_system oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode if (heating_coil_type == 'Gas') || (heating_coil_type == 'Electric') # This method will seem like an error in number of args..but this is due to swig voodoo. air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, fan, htg_coil, clg_coil, supplemental_htg_coil) air_to_air_heatpump.setControllingZoneorThermostatLocation(control_zone) air_to_air_heatpump.setNumberofSpeedsforHeating(1) air_to_air_heatpump.setNumberofSpeedsforCooling(2) air_to_air_heatpump.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation]) elsif heating_coil_type == 'DX' # This method will seem like an error in number of args..but this is due to swig voodoo. air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil) air_to_air_heatpump.setControllingZone(zone) end air_to_air_heatpump.setName("#{control_zone.name} ASHP") air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on) air_to_air_heatpump.addToNode(supply_inlet_node) oa_system.addToNode(supply_inlet_node) # Add a setpoint manager single zone reheat to control the # supply air temperature based on the needs of this zone setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model) setpoint_mgr_single_zone_reheat.setControlZone(control_zone) setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin]) setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax]) setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode) return air_loop end
Zonal systems
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2053 def add_zone_baseboards(baseboard_type:, hw_loop:, model:, zone:) always_on = model.alwaysOnDiscreteSchedule if baseboard_type == 'Electric' zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model) zone_elec_baseboard.addToThermalZone(zone) end return unless baseboard_type == 'Hot Water' baseboard_coil = OpenStudio::Model::CoilHeatingWaterBaseboard.new(model) # Connect baseboard coil to hot water loop hw_loop.addDemandBranchForComponent(baseboard_coil) zone_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveWater.new(model, always_on, baseboard_coil) # add zone_baseboard to zone zone_baseboard.addToThermalZone(zone) end
Set wildcard spactype schedule to NECB letter index.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 728 def adjust_wildcard_spacetype_schedule(space:, schedule:, lights_type: 'NECB_Default', lights_scale: 1.0) if space.spaceType.empty? OpenStudio.logFree(OpenStudio::Error, "Error: No spacetype assigned for #{space.name.get}. This must be assigned. Aborting.") end # Get current spacetype name space_type_name = space.spaceType.get.standardsSpaceType.get.to_s # Determine new spacetype name. regex = /^(.*sch-)(\S)$/ new_spacetype_name = "#{space_type_name.match(regex).captures.first}#{schedule}" new_spacetype = nil # if the new spacetype does not match the old space type. we gotta update the space with the new spacetype. if space_type_name != new_spacetype_name new_spacetype = space.model.getSpaceTypes.detect do |spacetype| !spacetype.standardsBuildingType.empty? && # need to do this to prevent an exception. (spacetype.standardsBuildingType.get == space.spaceType.get.standardsBuildingType.get) && !spacetype.standardsSpaceType.empty? && # need to do this to prevent an exception. (spacetype.standardsSpaceType.get == new_spacetype_name) end if new_spacetype.nil? # Space type is not in model. need to create from scratch. new_spacetype = OpenStudio::Model::SpaceType.new(space.model) new_spacetype.setStandardsBuildingType(space.spaceType.get.standardsBuildingType.get) new_spacetype.setStandardsSpaceType(new_spacetype_name) new_spacetype.setName("#{space.spaceType.get.standardsBuildingType.get} #{new_spacetype_name}") space_type_apply_internal_loads(space_type: new_spacetype, lights_type: lights_type, lights_scale: lights_scale) space_type_apply_internal_load_schedules(new_spacetype, true, true, true, true, true, true, true) end space.setSpaceType(new_spacetype) # sanity check. raise 'could not reassign space type schedule.' if schedule != space.spaceType.get.name.get.match(regex)[2] end return space end
NECB always requires an integrated economizer (NoLockout); as per 5.2.2.8(3) this means that compressor allowed to turn on when economizer is open
@note this method assumes you previously checked that an economizer is required at all
via #economizer_required?
@param (see economizer_required?) @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 90 def air_loop_hvac_apply_economizer_integration(air_loop_hvac, climate_zone) # Get the OA system and OA controller oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem # No OA system return false if !oa_sys.is_initialized oa_sys = oa_sys.get oa_control = oa_sys.getControllerOutdoorAir # Apply integrated economizer oa_control.setLockoutType('NoLockout') return true end
Add an ERV to this airloop. Will be a rotary-type HX
@param (see economizer_required?) @return [Boolean] Returns true if required, false if not. @todo Add exception logic for systems serving parking garage, warehouse, or multifamily
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 290 def air_loop_hvac_apply_energy_recovery_ventilator(air_loop_hvac, climate = nil) # Get the oa system oa_system = nil if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized oa_system = air_loop_hvac.airLoopHVACOutdoorAirSystem.get else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV cannot be added because the system has no OA intake.") return false end # Create an ERV erv = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(air_loop_hvac.model) erv.setName("#{air_loop_hvac.name} ERV") erv.setSensibleEffectivenessat100HeatingAirFlow(0.5) erv.setLatentEffectivenessat100HeatingAirFlow(0.5) erv.setSensibleEffectivenessat75HeatingAirFlow(0.5) erv.setLatentEffectivenessat75HeatingAirFlow(0.5) erv.setSensibleEffectivenessat100CoolingAirFlow(0.5) erv.setLatentEffectivenessat100CoolingAirFlow(0.5) erv.setSensibleEffectivenessat75CoolingAirFlow(0.5) erv.setLatentEffectivenessat75CoolingAirFlow(0.5) erv.setSupplyAirOutletTemperatureControl(true) erv.setHeatExchangerType('Rotary') erv.setFrostControlType('ExhaustOnly') erv.setEconomizerLockout(true) erv.setThresholdTemperature(-23.3) # -10F erv.setInitialDefrostTimeFraction(0.167) erv.setRateofDefrostTimeFractionIncrease(1.44) # Add the ERV to the OA system erv.addToNode(oa_system.outboardOANode.get) # Add a setpoint manager OA pretreat # to control the ERV spm_oa_pretreat = OpenStudio::Model::SetpointManagerOutdoorAirPretreat.new(air_loop_hvac.model) spm_oa_pretreat.setMinimumSetpointTemperature(-99.0) spm_oa_pretreat.setMaximumSetpointTemperature(99.0) spm_oa_pretreat.setMinimumSetpointHumidityRatio(0.00001) spm_oa_pretreat.setMaximumSetpointHumidityRatio(1.0) # Reference setpoint node and # Mixed air stream node are outlet # node of the OA system mixed_air_node = oa_system.mixedAirModelObject.get.to_Node.get spm_oa_pretreat.setReferenceSetpointNode(mixed_air_node) spm_oa_pretreat.setMixedAirStreamNode(mixed_air_node) # Outdoor air node is # the outboard OA node of teh OA system spm_oa_pretreat.setOutdoorAirStreamNode(oa_system.outboardOANode.get) # Return air node is the inlet # node of the OA system return_air_node = oa_system.returnAirModelObject.get.to_Node.get spm_oa_pretreat.setReturnAirStreamNode(return_air_node) # Attach to the outlet of the ERV erv_outlet = erv.primaryAirOutletModelObject.get.to_Node.get spm_oa_pretreat.addToNode(erv_outlet) # Apply the prototype Heat Exchanger power assumptions. heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_nominal_electric_power(erv) # Determine if the system is a DOAS based on # whether there is 100% OA in heating and cooling sizing. is_doas = false sizing_system = air_loop_hvac.sizingSystem if sizing_system.allOutdoorAirinCooling && sizing_system.allOutdoorAirinHeating is_doas = true end # Set the bypass control type # If DOAS system, BypassWhenWithinEconomizerLimits # to disable ERV during economizing. # Otherwise, BypassWhenOAFlowGreaterThanMinimum # to disable ERV during economizing and when OA # is also greater than minimum. bypass_ctrl_type = if is_doas 'BypassWhenWithinEconomizerLimits' else 'BypassWhenOAFlowGreaterThanMinimum' end oa_system.getControllerOutdoorAir.setHeatRecoveryBypassControlType(bypass_ctrl_type) return true end
NECB does not change damper positions
return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 13 def air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac) # Do not change anything. return true end
NECB has no single zone air loop control requirements
@return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 475 def air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: No special economizer controls were modeled.") return true end
Set the VAV damper control to single maximum or dual maximum control depending on the standard.
@return [Boolean] Returns true if successful, false if not @todo see if this impacts the sizing run.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 427 def air_loop_hvac_apply_vav_damper_action(air_loop_hvac) damper_action = 'Single Maximum' # Interpret this as an EnergyPlus input damper_action_eplus = nil if damper_action == 'Single Maximum' damper_action_eplus = 'Normal' elsif damper_action == 'Dual Maximum' # EnergyPlus 8.7 changed the meaning of 'Reverse'. # For versions of OpenStudio using E+ 8.6 or lower damper_action_eplus = if air_loop_hvac.model.version < OpenStudio::VersionString.new('2.0.5') 'Reverse' # For versions of OpenStudio using E+ 8.7 or higher else 'ReverseWithLimits' end end # Set the control for any VAV reheat terminals # on this airloop. control_type_set = false air_loop_hvac.demandComponents.each do |equip| if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVReheat.get # Dual maximum only applies to terminals with HW reheat coils if damper_action == 'Dual Maximum' if term.reheatCoil.to_CoilHeatingWater.is_initialized term.setDamperHeatingAction(damper_action_eplus) control_type_set = true end else term.setDamperHeatingAction(damper_action_eplus) control_type_set = true term.setMaximumFlowFractionDuringReheat(0.5) end end end if control_type_set OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: VAV damper action was set to #{damper_action} control.") end return true end
Determine if demand control ventilation (DCV) is required for this air loop.
@param (see economizer_required?) @return [Boolean] Returns true if required, false if not. @todo Add exception logic for
systems that serve multifamily, parking garage, warehouse
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 416 def air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}: #{air_loop_hvac.name}: DCV is not required for any system.") dcv_required = false return dcv_required end
Determine whether or not this system is required to have an economizer.
@return [Boolean] returns true if an economizer is required, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 22 def air_loop_hvac_economizer_required?(air_loop_hvac) economizer_required = false # need a better way to determine if an economizer is needed. return economizer_required if ((air_loop_hvac.name.to_s.include? 'Outpatient F1' ) || (air_loop_hvac.sizingSystem.typeofLoadtoSizeOn.to_s == "VentilationRequirement")) # A big number of btu per hr as the minimum requirement infinity_btu_per_hr = 999_999_999_999 minimum_capacity_btu_per_hr = infinity_btu_per_hr # Determine if the airloop serves any computer rooms # / data centers, which changes the economizer. is_dc = false if air_loop_hvac_data_center_area_served(air_loop_hvac) > 0 is_dc = true end # Determine the minimum capacity that requires an economizer minimum_capacity_btu_per_hr = 68_243 # NECB requires economizer for cooling cap > 20 kW # puts air_loop_hvac.name.to_s # Design Supply Air Flow Rate: This method below reads the value from the sql file. dsafr_m3_per_s = air_loop_hvac.autosizedDesignSupplyAirFlowRate min_dsafr_l_per_s = 1500 unless dsafr_m3_per_s.empty? dsafr_l_per_s = dsafr_m3_per_s.get * 1000 if dsafr_l_per_s > min_dsafr_l_per_s economizer_required = true puts "economizer_required = true for #{air_loop_hvac.name} because dsafr_l_per_s(#{dsafr_l_per_s}) > 1500" if is_dc OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the 'Design Supply Air Flow Rate' of #{dsafr_l_per_s} L/s exceeds the minimum air flow rate of #{min_dsafr_l_per_s} L/s for data centers.") else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the 'Design Supply Air Flow Rate' of #{dsafr_l_per_s} L/s exceeds the minimum air flow rate of #{min_dsafr_l_per_s} L/s.") end end end # Check whether the system requires an economizer by comparing # the system capacity to the minimum capacity. total_cooling_capacity_w = air_loop_hvac_total_cooling_capacity(air_loop_hvac) total_cooling_capacity_btu_per_hr = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/hr').get if total_cooling_capacity_btu_per_hr >= minimum_capacity_btu_per_hr if is_dc OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.") else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.") end puts "economizer_required = true for #{air_loop_hvac.name} because total_cooling_capacity_btu_per_hr(#{total_cooling_capacity_btu_per_hr}) >= #{minimum_capacity_btu_per_hr}" economizer_required = true else if is_dc OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.") else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.") end end return economizer_required end
Shut off the system during unoccupied periods. During these times, systems will cycle on briefly if temperature drifts below setpoint. For systems with fan-powered terminals, the whole system (not just the terminal fans) will cycle on. Terminal-only night cycling is not used because the terminals cannot provide cooling, so terminal-only night cycling leads to excessive unmet cooling hours during unoccupied periods. If the system already has a schedule other than Always-On, no change will be made. If the system has an Always-On schedule assigned, a new schedule will be created. In this case, occupied is defined as the total percent occupancy for the loop for all zones served.
@param min_occ_pct [Double] the fractional value below which the system will be considered unoccupied. @return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1095 def air_loop_hvac_enable_unoccupied_fan_shutoff(air_loop_hvac, min_occ_pct = 0.05) # Set the system to night cycle air_loop_hvac.setNightCycleControlType('CycleOnAny') # Check if already using a schedule other than always on avail_sch = air_loop_hvac.availabilitySchedule unless avail_sch == air_loop_hvac.model.alwaysOnDiscreteSchedule OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Availability schedule is already set to #{avail_sch.name}. Will assume this includes unoccupied shut down; no changes will be made.") return true end # Get the airloop occupancy schedule loop_occ_sch = air_loop_hvac_get_occupancy_schedule(air_loop_hvac, occupied_percentage_threshold: min_occ_pct) flh = OpenstudioStandards::Schedules.schedule_get_equivalent_full_load_hours(loop_occ_sch) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Annual occupied hours = #{flh.round} hr/yr, assuming a #{min_occ_pct} occupancy threshold. This schedule will be used as the HVAC operation schedule.") # Set HVAC availability schedule to follow occupancy air_loop_hvac.setAvailabilitySchedule(loop_occ_sch) air_loop_hvac.supplyComponents.each do |comp| if comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get.setSupplyAirFanOperatingModeSchedule(loop_occ_sch) elsif comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get.setAvailabilitySchedule(loop_occ_sch) end end return true end
Check if ERV is required on this airloop.
@param (see economizer_required?) @return [Boolean] Returns true if required, false if not. @todo Add exception logic for systems serving parking garage, warehouse, or multifamily
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 110 def air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone) # ERV Not Applicable for AHUs that serve # parking garage, warehouse, or multifamily # if space_types_served_names.include?('PNNL_Asset_Rating_Apartment_Space_Type') || # space_types_served_names.include?('PNNL_Asset_Rating_LowRiseApartment_Space_Type') || # space_types_served_names.include?('PNNL_Asset_Rating_ParkingGarage_Space_Type') || # space_types_served_names.include?('PNNL_Asset_Rating_Warehouse_Space_Type') # OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not applicable because it because it serves parking garage, warehouse, or multifamily.") # return false # end erv_required = nil # ERV not applicable for medical AHUs (AHU1 in Outpatient), per AIA 2001 - 7.31.D2. if air_loop_hvac.name.to_s.include? 'Outpatient F1' erv_required = false return erv_required end # ERV not applicable for medical AHUs, per AIA 2001 - 7.31.D2. if air_loop_hvac.name.to_s.include? 'VAV_ER' erv_required = false return erv_required elsif air_loop_hvac.name.to_s.include? 'VAV_OR' erv_required = false return erv_required end # ERV Not Applicable for AHUs that have DCV # or that have no OA intake. controller_oa = nil controller_mv = nil oa_system = nil if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized oa_system = air_loop_hvac.airLoopHVACOutdoorAirSystem.get controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation if controller_mv.demandControlledVentilation == true OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not applicable because DCV enabled.") return false end else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not applicable because it has no OA intake.") return false end # Get the AHU design supply air flow rate dsn_flow_m3_per_s = nil if air_loop_hvac.designSupplyAirFlowRate.is_initialized dsn_flow_m3_per_s = air_loop_hvac.designSupplyAirFlowRate.get elsif air_loop_hvac.autosizedDesignSupplyAirFlowRate.is_initialized dsn_flow_m3_per_s = air_loop_hvac.autosizedDesignSupplyAirFlowRate.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name} design supply air flow rate is not available, cannot apply efficiency standard.") return false end dsn_flow_cfm = OpenStudio.convert(dsn_flow_m3_per_s, 'm^3/s', 'cfm').get # Get the minimum OA flow rate min_oa_flow_m3_per_s = nil if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.autosizedMinimumOutdoorAirFlowRate.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{controller_oa.name}: minimum OA flow rate is not available, cannot apply efficiency standard.") return false end min_oa_flow_cfm = OpenStudio.convert(min_oa_flow_m3_per_s, 'm^3/s', 'cfm').get # Calculate the percent OA at design airflow pct_oa = min_oa_flow_m3_per_s / dsn_flow_m3_per_s # The NECB2011 requirement is that systems with an exhaust heat content > 150 kW require an HRV # The calculation for this is done below, to modify erv_required # erv_cfm set to nil here as placeholder, will lead to erv_required = false erv_cfm = nil # Determine if an ERV is required # erv_required = nil if erv_cfm.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}.") erv_required = false elsif dsn_flow_cfm < erv_cfm OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Does not exceed minimum flow requirement of #{erv_cfm}cfm.") erv_required = false else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Exceeds minimum flow requirement of #{erv_cfm}cfm.") erv_required = true end # This code modifies erv_required for NECB2011 # Calculation of exhaust heat content and check whether it is > 150 kW # get all zones in the model zones = air_loop_hvac.thermalZones # initialize counters sum_zone_oa = 0.0 sum_zone_oa_times_heat_design_t = 0.0 # zone loop zones.each do |zone| # get design heat temperature for each zone; this is equivalent to design exhaust temperature heat_design_t = 21.0 zone_thermostat = zone.thermostat.get if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get if dual_thermostat.heatingSetpointTemperatureSchedule.is_initialized htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get htg_temp_sch_ruleset = htg_temp_sch.to_ScheduleRuleset.get winter_dd_sch = htg_temp_sch_ruleset.winterDesignDaySchedule heat_design_t = winter_dd_sch.values.max end end # initialize counter zone_oa = 0.0 # outdoor defined at space level; get OA flow for all spaces within zone spaces = zone.spaces # space loop spaces.each do |space| unless space.designSpecificationOutdoorAir.empty? # if empty, don't do anything outdoor_air = space.designSpecificationOutdoorAir.get # in bTAP, outdoor air specified as outdoor air per oa_flow_per_floor_area = outdoor_air.outdoorAirFlowperFloorArea oa_flow = oa_flow_per_floor_area * space.floorArea * zone.multiplier # oa flow for the space zone_oa += oa_flow # add up oa flow for all spaces to get zone air flow end # space loop end sum_zone_oa += zone_oa # sum of all zone oa flows to get system oa flow sum_zone_oa_times_heat_design_t += (zone_oa * heat_design_t) # calculated to get oa flow weighted average of design exhaust temperature # zone loop end # Calculate average exhaust temperature (oa flow weighted average) avg_exhaust_temp = sum_zone_oa_times_heat_design_t / sum_zone_oa # for debugging/testing # puts "average exhaust temp = #{avg_exhaust_temp}" # puts "sum_zone_oa = #{sum_zone_oa}" # Get January winter design temperature # get model weather file name weather_file_path = air_loop_hvac.model.weatherFile.get.path.get.to_s stat_file_path = weather_file_path.gsub('.epw', '.stat') stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) # get winter(heating) design temp stored in array # Note that the NECB2011 specifies using the 2.5% january design temperature # The outdoor temperature used here is the 0.4% heating design temperature of the coldest month, available in stat file outdoor_temp = stat_file.heating_design_info[1] # for debugging/testing # puts "outdoor design temp = #{outdoor_temp}" # Calculate exhaust heat content exhaust_heat_content = 0.00123 * sum_zone_oa * 1000.0 * (avg_exhaust_temp - outdoor_temp) # for debugging/testing # puts "exhaust heat content = #{exhaust_heat_content}" # Modify erv_required based on exhaust heat content if exhaust_heat_content > 150.0 erv_required = true OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV required based on exhaust heat content.") else erv_required = false OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not required based on exhaust heat content.") end return erv_required end
Determine the air flow and number of story limits for whether motorized OA damper is required. @return [Array<Double>] [minimum_oa_flow_cfm, maximum_stories]. If both nil, never required
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 493 def air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone) minimum_oa_flow_cfm = 0 maximum_stories = 0 return [minimum_oa_flow_cfm, maximum_stories] end
NECB doesnât require static pressure reset.
return [Boolean] returns true if static pressure reset is required, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 483 def air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc) # static pressure reset not required sp_reset_required = false return sp_reset_required end
Sets the capacity of the reheat coil based on the minimum flow fraction, and the maximum flow rate.
@param air_terminal_single_duct_vav_reheat [OpenStudio::Model::AirTerminalSingleDuctVAVReheat] the air terminal object @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2505 def air_terminal_single_duct_vav_reheat_set_heating_cap(air_terminal_single_duct_vav_reheat) flow_rate_fraction = 0.0 if air_terminal_single_duct_vav_reheat.constantMinimumAirFlowFraction.is_initialized flow_rate_fraction = air_terminal_single_duct_vav_reheat.constantMinimumAirFlowFraction.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirTerminalSingleDuctVAVReheat', \ "Minimum flow fraction is not defined for terminal device #{air_terminal_single_duct_vav_reheat.name}") return false end cap = 1.2 * 1000.0 * flow_rate_fraction * air_terminal_single_duct_vav_reheat.autosizedMaximumAirFlowRate.to_f * (43.0 - 13.0) if air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingElectric.is_initialized reheat_coil = air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingElectric.get reheat_coil.setNominalCapacity(cap) elsif air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingWater.is_initialized reheat_coil = air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingWater.get reheat_coil.setPerformanceInputMethod('NominalCapacity') reheat_coil.setRatedCapacity(cap) end air_terminal_single_duct_vav_reheat.setMaximumReheatAirTemperature(43.0) return true end
Top level method that merges spaces into zones where possible. This requires a sizing run. This follows the spirit of the EE4 modelling manual found here www.nrcan.gc.ca/energy/software-tools/7457 where the A zone includes those areas in the building that meet three criteria:
-
Served by the same HVAC system
-
Similar operation and function
-
Similar heating/cooling loads
Some expections are dwelling units wet zone and wild zones. These spaces will have special considerations when autozoning a building.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 54 def apply_auto_zoning(model:, sizing_run_dir: Dir.pwd, lights_type: 'NECB_Default', lights_scale: 1.0) raise('validation of model failed.') unless validate_initial_model(model) # Check to see if model is using another vintage of spacetypes. If so overwrite the @standards for the object with the # other spacetype data. This is required for correct system mapping. template = determine_spacetype_vintage(model) unless template == self.class.name # Frankenstein the standards data wrt spacetype data. @standards_data['space_types'] = Standard.build(template).standards_data['space_types'] end # The first thing we need to do is get a sizing run to determine the heating loads of all the spaces. The default # btap geometry has a one to one relationship of zones to spaces.. So we simply create the thermal zones for all the spaces. # to do this we need to create thermals zone for each space. # Remove any Thermal zones assigned before model.getThermalZones.each(&:remove) # create new thermal zones one to one with spaces. model_create_thermal_zones(model) # do a sizing run. if model_run_sizing_run(model, "#{sizing_run_dir}/autozone") == false raise('autorun sizing run failed!') end # collect sizing information on each space. store_space_sizing_loads(model) # Remove any Thermal zones assigned again to start fresh. model.getThermalZones.each(&:remove) auto_zone_dwelling_units(model) auto_zone_wet_spaces(model: model, lights_type: lights_type, lights_scale: lights_scale) auto_zone_all_other_spaces(model) auto_zone_wild_spaces(model: model, lights_type: lights_type, lights_scale: lights_scale) # This will color the spaces and zones. random = Random.new(1234) # Set ideal hvac in case we want to not implement the hvac yet and still run osm right after this function. # model.getThermalZones.each { |zone| zone.setUseIdealAirLoads(true) } model.getThermalZones.sort.each { |item| item.setRenderingColor(set_random_rendering_color(item, random)) } model.getSpaceTypes.sort.each { |item| item.setRenderingColor(set_random_rendering_color(item, random)) } end
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 486 def apply_building_default_constructionset(model) bldg_def_const_set = model_add_construction_set_from_osm(model: model) model.getBuilding.setDefaultConstructionSet(bldg_def_const_set) end
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 491 def apply_default_constructionsets_to_spacetypes(climate_zone, model) model.getSpaceTypes.sort.each do |space_type| # Get the standards building type stds_building_type = nil if space_type.standardsBuildingType.is_initialized stds_building_type = space_type.standardsBuildingType.get else OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.") end # Get the standards space type stds_spc_type = nil if space_type.standardsSpaceType.is_initialized stds_spc_type = space_type.standardsSpaceType.get else OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards space type.") end # If the standards space type is Attic, # the building type should be blank. if stds_spc_type == 'Attic' stds_building_type = '' end # Attempt to make a construction set for this space type # and assign it if it can be created. spc_type_const_set = model_add_construction_set_from_osm(model: model) if spc_type_const_set.is_initialized space_type.setDefaultConstructionSet(spc_type_const_set.get) end end end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1558 def apply_economizers(climate_zone, model) # NECB2011 prescribes ability to provide 100% OA (5.2.2.7-5.2.2.9) econ_max_100_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(model) econ_max_100_pct_oa_sch.setName('Economizer Max OA Fraction 100 pct') econ_max_100_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 100 pct Default') econ_max_100_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0) # Check each airloop model.getAirLoopHVACs.sort.each do |air_loop| if air_loop_hvac_economizer_required?(air_loop) == true # If an economizer is required, determine the economizer type # in the prototype buildings, which depends on climate zone. economizer_type = nil # NECB 5.2.2.8 states that economizer can be controlled based on difference betweeen # return air temperature and outside air temperature OR return air enthalpy # and outside air enthalphy; latter chosen to be consistent with MNECB and CAN-QUEST implementation economizer_type = 'DifferentialEnthalpy' # Set the economizer type # Get the OA system and OA controller oa_sys = air_loop.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but it has no OA system.") next end oa_control = oa_sys.getControllerOutdoorAir oa_control.setEconomizerControlType(economizer_type) end end end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 712 def apply_envelope(model:, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, door_construction_cond: nil, fixed_window_cond: nil, glass_door_cond: nil, overhead_door_cond: nil, skylight_cond: nil, glass_door_solar_trans: nil, fixed_wind_solar_trans: nil, skylight_solar_trans: nil, infiltration_scale: nil, necb_hdd: true) raise('validation of model failed.') unless validate_initial_model(model) model_apply_infiltration_standard(model) ecm = ECMS.new ecm.scale_infiltration_loads(model: model, scale: infiltration_scale) model.getInsideSurfaceConvectionAlgorithm.setAlgorithm('TARP') model.getOutsideSurfaceConvectionAlgorithm.setAlgorithm('TARP') model_add_constructions(model) apply_standard_construction_properties(model: model, ext_wall_cond: ext_wall_cond, ext_floor_cond: ext_floor_cond, ext_roof_cond: ext_roof_cond, ground_wall_cond: ground_wall_cond, ground_floor_cond: ground_floor_cond, ground_roof_cond: ground_roof_cond, door_construction_cond: door_construction_cond, fixed_window_cond: fixed_window_cond, glass_door_cond: glass_door_cond, overhead_door_cond: overhead_door_cond, skylight_cond: skylight_cond, glass_door_solar_trans: glass_door_solar_trans, fixed_wind_solar_trans: fixed_wind_solar_trans, skylight_solar_trans: skylight_solar_trans, necb_hdd: necb_hdd) model_create_thermal_zones(model, @space_multiplier_map) 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
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 983 def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: true) fdwr_set = -1.0 if (fdwr_set == 'NECB_default') || fdwr_set.nil? srr_set = -1.0 if (srr_set == 'NECB_default') || srr_set.nil? 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: necb_hdd) apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set) # model_add_daylighting_controls(model) # to be removed after refactor. end
apply the Kiva foundation model to floors and walls with ground boundary condition created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 758 def apply_kiva_foundation(model) # define a Kiva model for the whole bldg that's used for the first floor in contact with ground in each zone bldg_kiva_model = OpenStudio::Model::FoundationKiva.new(model) bldg_kiva_model.setName("Bldg Kiva Foundation") bldg_kiva_model.setWallHeightAboveGrade(0.0) bldg_kiva_model.setWallDepthBelowSlab(0.0) model.getThermalZones.sort.each do |zone| zone_kiva_models = [bldg_kiva_model] zone_grd_flr_counter = 0 zone.spaces.sort.each do |space| # store space floors and walls in contact with ground and exterior walls space_ground_floors = [] space_ground_walls = [] space_ext_walls = [] space_ground_floors += space.surfaces.select {|surf| surf.surfaceType.downcase == 'floor' && surf.isGroundSurface } space_ground_walls += space.surfaces.select {|surf| surf.surfaceType.downcase == 'wall' && surf.isGroundSurface } space_ext_walls += space.surfaces.select {|surf| surf.surfaceType.downcase == 'wall' && surf.outsideBoundaryCondition.downcase == 'outdoors'} # loop through space floors in contact with ground and assing a Kiva model for each space_ground_floors.each do |gfloor| zone_grd_flr_counter += 1 if zone_grd_flr_counter > 1 # a new Kiva model is needed for each additional floor in contact with the ground in the zone kiva_model = OpenStudio::Model::FoundationKiva.new(model) kiva_model.setName("#{gfloor.name.to_s} Kiva Foundation") kiva_model.setWallHeightAboveGrade(0.0) kiva_model.setWallDepthBelowSlab(0.0) zone_kiva_models << kiva_model end # Kiva model only works with standard materials. Replace constructions massless materials with standard ones. replace_massless_material_with_std_material(model,gfloor) gfloor.setOutsideBoundaryCondition('Foundation') gfloor.setAdjacentFoundation(zone_kiva_models.last) # Set the exposed perimeter for space floors in contact with the ground. floor_exp_per = 0.0 if !space_ground_walls.empty? floor_exp_per += get_surface_exp_per(gfloor,space_ground_walls) elsif !space_ext_walls.empty? floor_exp_per += get_surface_exp_per(gfloor,space_ext_walls) end gfloor.createSurfacePropertyExposedFoundationPerimeter('TotalExposedPerimeter',floor_exp_per) # specify a foundation boundary condition for space walls in contact with the ground and in # contact with the space floor in contact with ground 'gfloor' space_ground_walls.each do |gwall| if surfaces_are_in_contact?(gfloor,gwall) replace_massless_material_with_std_material(model,gwall) gwall.setOutsideBoundaryCondition('Foundation') gwall.setAdjacentFoundation(zone_kiva_models.last) end end end end end kiva_settings = model.getFoundationKivaSettings if !model.getFoundationKivas.empty? end
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 25 def apply_limit_fdwr(model:, fdwr_lim:) # Loop through all spaces in the model, and # per the PNNL PRM Reference Manual, find the areas # of each space conditioning category (res, nonres, semi-heated) # separately. Include space multipliers. nr_wall_m2 = 0.001 # Avoids divide by zero errors later nr_wind_m2 = 0 res_wall_m2 = 0.001 res_wind_m2 = 0 sh_wall_m2 = 0.001 sh_wind_m2 = 0 total_wall_m2 = 0.001 total_subsurface_m2 = 0.0 # Store the space conditioning category for later use space_cats = {} model.getSpaces.sort.each do |space| # Loop through all surfaces in this space wall_area_m2 = 0 wind_area_m2 = 0 space.surfaces.sort.each do |surface| # Skip non-outdoor surfaces next unless surface.outsideBoundaryCondition == 'Outdoors' # Skip non-walls next unless surface.surfaceType.casecmp('wall').zero? # This wall's gross area (including window area) wall_area_m2 += surface.grossArea * space.multiplier # Subsurfaces in this surface surface.subSurfaces.sort.each do |ss| wind_area_m2 += ss.netArea * space.multiplier end end # Determine the space category # @todo This should really use the heating/cooling loads # from the proposed building. However, in an attempt # to avoid another sizing run just for this purpose, # conditioned status is based on heating/cooling # setpoints. If heated-only, will be assumed Semiheated. # The full-bore method is on the next line in case needed. # cat = thermal_zone_conditioning_category(space, template, climate_zone) cooled = OpenstudioStandards::Space.space_cooled?(space) heated = OpenstudioStandards::Space.space_heated?(space) cat = 'Unconditioned' # Unconditioned if !heated && !cooled cat = 'Unconditioned' # Heated-Only elsif heated && !cooled cat = 'Semiheated' # Heated and Cooled else res = OpenstudioStandards::ThermalZone.thermal_zone_residential?(space.thermalZone.get) cat = if res 'ResConditioned' else 'NonResConditioned' end end space_cats[space] = cat # NECB2011 keep track of totals for NECB regardless of conditioned or not. total_wall_m2 += wall_area_m2 total_subsurface_m2 += wind_area_m2 # this contains doors as well. # Add to the correct category case cat when 'Unconditioned' next # Skip unconditioned spaces when 'NonResConditioned' nr_wall_m2 += wall_area_m2 nr_wind_m2 += wind_area_m2 when 'ResConditioned' res_wall_m2 += wall_area_m2 res_wind_m2 += wind_area_m2 when 'Semiheated' sh_wall_m2 += wall_area_m2 sh_wind_m2 += wind_area_m2 end end # Calculate the WWR of each category wwr_nr = ((nr_wind_m2 / nr_wall_m2) * 100.0).round(1) wwr_res = ((res_wind_m2 / res_wall_m2) * 100).round(1) wwr_sh = ((sh_wind_m2 / sh_wall_m2) * 100).round(1) fdwr = ((total_subsurface_m2 / total_wall_m2) * 100).round(1) # used by NECB2011 # Convert to IP and report nr_wind_ft2 = OpenStudio.convert(nr_wind_m2, 'm^2', 'ft^2').get nr_wall_ft2 = OpenStudio.convert(nr_wall_m2, 'm^2', 'ft^2').get res_wind_ft2 = OpenStudio.convert(res_wind_m2, 'm^2', 'ft^2').get res_wall_ft2 = OpenStudio.convert(res_wall_m2, 'm^2', 'ft^2').get sh_wind_ft2 = OpenStudio.convert(sh_wind_m2, 'm^2', 'ft^2').get sh_wall_ft2 = OpenStudio.convert(sh_wall_m2, 'm^2', 'ft^2').get OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR NonRes = #{wwr_nr.round}%; window = #{nr_wind_ft2.round} ft2, wall = #{nr_wall_ft2.round} ft2.") OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Res = #{wwr_res.round}%; window = #{res_wind_ft2.round} ft2, wall = #{res_wall_ft2.round} ft2.") OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Semiheated = #{wwr_sh.round}%; window = #{sh_wind_ft2.round} ft2, wall = #{sh_wall_ft2.round} ft2.") # WWR limit wwr_lim = 40.0 # Check against WWR limit red_nr = wwr_nr > wwr_lim red_res = wwr_res > wwr_lim red_sh = wwr_sh > wwr_lim # puts "Current FDWR is #{fdwr}, must be less than #{fdwr_lim}." # puts "Current subsurf area is #{total_subsurface_m2} and gross surface area is #{total_wall_m2}" # Stop here unless windows / doors need reducing return true unless fdwr > fdwr_lim OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{wwr_lim.round}%.") # Determine the factors by which to reduce the window / door area mult = fdwr_lim / fdwr # Reduce the window area if any of the categories necessary model.getSpaces.sort.each do |space| # Loop through all surfaces in this space space.surfaces.sort.each do |surface| # Skip non-outdoor surfaces next unless surface.outsideBoundaryCondition == 'Outdoors' # Skip non-walls next unless surface.surfaceType == 'Wall' # Subsurfaces in this surface surface.subSurfaces.sort.each do |ss| # Reduce the size of the window red = 1.0 - mult OpenstudioStandards::Geometry.sub_surface_reduce_area_by_percent_by_raising_sill(ss, red) end end end return true end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 668 def apply_loads(model:, lights_type: 'NECB_Default', lights_scale: 1.0, validate: true, occupancy_loads_scale: nil, electrical_loads_scale: nil, oa_scale: nil) # Create ECM object. ecm = ECMS.new lights_scale = convert_arg_to_f(variable: lights_scale, default: 1.0) if validate raise('validation of model failed.') unless validate_initial_model(model) raise('validation of spacetypes failed.') unless validate_and_upate_space_types(model) end # this sets/stores the template version loads that the model uses. model.getBuilding.setStandardsTemplate(self.class.name) set_occ_sensor_spacetypes(model, @space_type_map) model_add_loads(model, lights_type, lights_scale) ecm.scale_occupancy_loads(model: model, scale: occupancy_loads_scale) ecm.scale_electrical_loads(model: model, scale: electrical_loads_scale) ecm.scale_oa_loads(model: model, scale: oa_scale) end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1129 def apply_loop_pump_power(model:, sizing_run_dir:) # Remove duplicate materials and constructions # Note For NECB2015 This is the 2nd time this method is bieng run. # First time it ran in the super() within model_apply_standard() method # model = return BTAP::FileIO::remove_duplicate_materials_and_constructions(model) return model end
This method applies the maximum fenestration and door to wall 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 al exterior walls adjacent to conditioned spaces. It distinguishes between plenums and other conditioned spaces. It uses both to calculate the maximum window area to be applied to the building but attempts to put these windows only on non-plenum conditioned spaces (if possible).
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 651 def apply_max_fdwr_nrcan(model:, fdwr_lim:) # First determine which vertical (between 89 and 91 degrees from horizontal) walls are adjacent to conditioned # spaces. exp_surf_info = find_exposed_conditioned_vertical_surfaces(model) # If there are none (or very few) then throw a warning. if exp_surf_info['total_exp_wall_area_m2'] < 0.1 OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', 'This building has no exposed walls adjacent to heated spaces.') return false end construct_set = model.getBuilding.defaultConstructionSet.get fixed_window_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.fixedWindowConstruction.get # IF FDWR is greater than 1 then something is wrong raise an error. If it is less than 0.001 assume all the windows # should go. if fdwr_lim > 1 OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'This building requires a larger window area than there is wall area.') return false elsif fdwr_lim < 0.001 exp_surf_info['exp_nonplenum_walls'].sort.each do |exp_surf| exp_surf.subSurfaces.sort.each(&:remove) end return true end # Get the required window area. win_area = fdwr_lim * exp_surf_info['total_exp_wall_area_m2'] # Try to put the windows on non-plenum walls if possible. So determine if you can fit the required window area # on the non-plenum wall area. if win_area <= exp_surf_info['exp_nonplenum_wall_area_m2'] # If you can fit the windows on the non-plenum wall area then recalculate the window ratio so that is is only for # the non-plenum walls. nonplenum_fdwr = win_area / exp_surf_info['exp_nonplenum_wall_area_m2'] exp_surf_info['exp_nonplenum_walls'].sort.each do |exp_surf| # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface # type (which will be 'fixedwindow') exp_surf.subSurfaces.sort.each(&:remove) exp_surf.setWindowToWallRatio(nonplenum_fdwr) exp_surf.subSurfaces.sort.each do |sub_surf| sub_surf.setSubSurfaceType('FixedWindow') sub_surf.setConstruction(fixed_window_construct_set) new_name = exp_surf.name.to_s + '_' + sub_surf.subSurfaceType.to_s sub_surf.setName(new_name) end end else # There was not enough non-plenum wall area so add the windows to both the plenum and non-plenum walls. This is # done separately because the 'find_exposed_conditioned_vertical_surfaces' method returns the plenum and # non-plenum walls separately. exp_surf_info['exp_nonplenum_walls'].sort.each do |exp_surf| # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface # type (which will be 'fixedwindow') exp_surf.subSurfaces.sort.each(&:remove) exp_surf.setWindowToWallRatio(fdwr_lim) exp_surf.subSurfaces.sort.each do |sub_surf| sub_surf.setSubSurfaceType('FixedWindow') sub_surf.setConstruction(fixed_window_construct_set) new_name = exp_surf.name.to_s + '_' + sub_surf.subSurfaceType.to_s sub_surf.setName(new_name) end end exp_surf_info['exp_plenum_walls'].sort.each do |exp_surf| # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface # type (which will be 'fixedwindow') exp_surf.subSurfaces.sort.each(&:remove) exp_surf.setWindowToWallRatio(fdwr_lim) exp_surf.subSurfaces.sort.each do |sub_surf| sub_surf.setSubSurfaceType('FixedWindow') sub_surf.setConstruction(fixed_window_construct_set) new_name = exp_surf.name.to_s + '_' + sub_surf.subSurfaceType.to_s sub_surf.setName(new_name) end end end 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.
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 731 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 # 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 construct_set = model.getBuilding.defaultConstructionSet.get skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get # 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 return true end
Go through the default construction sets and hard-assigned constructions. Clone the existing constructions and set their intended surface type and standards construction type per the PRM. For some standards, this will involve making modifications. For others, it will not.
90.1-2007, 90.1-2010, 90.1-2013 @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 300 def apply_standard_construction_properties(model:, runner: nil, # ext surfaces ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, # ground surfaces ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, # fixed Windows fixed_window_cond: nil, fixed_wind_solar_trans: nil, fixed_wind_vis_trans: nil, # operable windows operable_wind_solar_trans: nil, operable_window_cond: nil, operable_wind_vis_trans: nil, # glass doors glass_door_cond: nil, glass_door_solar_trans: nil, glass_door_vis_trans: nil, # opaque doors door_construction_cond: nil, overhead_door_cond: nil, # skylights skylight_cond: nil, skylight_solar_trans: nil, skylight_vis_trans: nil, # tubular daylight dome tubular_daylight_dome_cond: nil, tubular_daylight_dome_solar_trans: nil, tubular_daylight_dome_vis_trans: nil, # tubular daylight diffuser tubular_daylight_diffuser_cond: nil, tubular_daylight_diffuser_solar_trans: nil, tubular_daylight_diffuser_vis_trans: nil, necb_hdd: true) model.getDefaultConstructionSets.sort.each do |default_surface_construction_set| BTAP.runner_register('Info', 'apply_standard_construction_properties', runner) if model.weatherFile.empty? || model.weatherFile.get.path.empty? || !File.exist?(model.weatherFile.get.path.get.to_s) BTAP.runner_register('Error', 'Weather file is not defined. Please ensure the weather file is defined and exists.', runner) return false end # hdd required in scope for eval function. hdd = get_necb_hdd18(model: model, necb_hdd: necb_hdd) # Lambdas are preferred over methods in methods for small utility methods. correct_cond = lambda do |conductivity, surface_type| return conductivity.nil? || conductivity.to_f <= 0.0 || conductivity == 'NECB_Default' ? eval(model_find_objects(@standards_data['surface_thermal_transmittance'], surface_type)[0]['formula']) : conductivity.to_f end # Converts trans and vis to nil if requesting default.. or casts the string to a float. correct_vis_trans = lambda do |value| return value.nil? || value.to_f <= 0.0 || value == 'NECB_Default' ? nil : value.to_f end BTAP::Resources::Envelope::ConstructionSets.customize_default_surface_construction_set!(model: model, name: "#{default_surface_construction_set.name.get} at hdd = #{get_necb_hdd18(model: model, necb_hdd: necb_hdd)}", default_surface_construction_set: default_surface_construction_set, # ext surfaces ext_wall_cond: correct_cond.call(ext_wall_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Wall'), ext_floor_cond: correct_cond.call(ext_floor_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Floor'), ext_roof_cond: correct_cond.call(ext_roof_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'RoofCeiling'), # ground surfaces ground_wall_cond: correct_cond.call(ground_wall_cond, 'boundary_condition' => 'Ground', 'surface' => 'Wall'), ground_floor_cond: correct_cond.call(ground_floor_cond, 'boundary_condition' => 'Ground', 'surface' => 'Floor'), ground_roof_cond: correct_cond.call(ground_roof_cond, 'boundary_condition' => 'Ground', 'surface' => 'RoofCeiling'), # fixed Windows fixed_window_cond: correct_cond.call(fixed_window_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'), fixed_wind_solar_trans: correct_vis_trans.call(fixed_wind_solar_trans), fixed_wind_vis_trans: correct_vis_trans.call(fixed_wind_vis_trans), # operable windows operable_wind_solar_trans: correct_vis_trans.call(operable_wind_solar_trans), operable_window_cond: correct_cond.call(fixed_window_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'), operable_wind_vis_trans: correct_vis_trans.call(operable_wind_vis_trans), # glass doors glass_door_cond: correct_cond.call(glass_door_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'), glass_door_solar_trans: correct_vis_trans.call(glass_door_solar_trans), glass_door_vis_trans: correct_vis_trans.call(glass_door_vis_trans), # opaque doors door_construction_cond: correct_cond.call(door_construction_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Door'), overhead_door_cond: correct_cond.call(overhead_door_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Door'), # skylights skylight_cond: correct_cond.call(skylight_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'), skylight_solar_trans: correct_vis_trans.call(skylight_solar_trans), skylight_vis_trans: correct_vis_trans.call(skylight_vis_trans), # tubular daylight dome tubular_daylight_dome_cond: correct_cond.call(skylight_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'), tubular_daylight_dome_solar_trans: correct_vis_trans.call(tubular_daylight_dome_solar_trans), tubular_daylight_dome_vis_trans: correct_vis_trans.call(tubular_daylight_dome_vis_trans), # tubular daylight diffuser tubular_daylight_diffuser_cond: correct_cond.call(skylight_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'), tubular_daylight_diffuser_solar_trans: correct_vis_trans.call(tubular_daylight_diffuser_solar_trans), tubular_daylight_diffuser_vis_trans: correct_vis_trans.call(tubular_daylight_diffuser_vis_trans)) end # sets all surfaces to use default constructions sets except adiabatic, where it does a hard assignment of the interior wall construction type. model.getPlanarSurfaces.sort.each(&:resetConstruction) # if the default construction set is defined..try to assign the interior wall to the adiabatic surfaces BTAP::Resources::Envelope.assign_interior_surface_construction_to_adiabatic_surfaces(model, nil) BTAP.runner_register('Info', ' apply_standard_construction_properties was sucessful.', runner) end
@param necb_reference_hp [Boolean] if true, NECB reference model rules for heat pumps will be used.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1060 def apply_standard_efficiencies(model:, sizing_run_dir:, dcv_type: 'NECB_Default', necb_reference_hp: false) raise('validation of model failed.') unless validate_initial_model(model) climate_zone = 'NECB HDD Method' raise("sizing run 1 failed! check #{sizing_run_dir}") if model_run_sizing_run(model, "#{sizing_run_dir}/plant_loops") == false # This is needed for NECB2011 as a workaround for sizing the reheat boxes. model.getAirTerminalSingleDuctVAVReheats.each { |iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj) } # Apply the prototype HVAC assumptions model_apply_prototype_hvac_assumptions(model, nil, climate_zone) # Apply the HVAC efficiency standard sql_db_vars_map = {} model_apply_hvac_efficiency_standard(model, climate_zone, sql_db_vars_map: sql_db_vars_map, necb_ref_hp: necb_reference_hp) model_enable_demand_controlled_ventilation(model, dcv_type) return sql_db_vars_map end
# File lib/openstudio-standards/standards/necb/NECB2011/lighting.rb, line 2 def apply_standard_lights(set_lights: true, space_type:, space_type_properties:, lights_type:, lights_scale:) ##### Remove leading or trailing whitespace in case users add them in inputs if lights_scale.instance_of?(String) lights_scale = lights_scale.strip end if lights_type.nil? || lights_type == 'none' lights_type = 'NECB_Default' end if lights_scale.nil? || lights_scale == 'none' || lights_scale == 'NECB_Default' lights_scale = 1.0 end ##### Convert a string to a float if lights_scale.instance_of?(String) lights_scale = lights_scale.to_f end lights_have_info = false lighting_per_area = space_type_properties['lighting_per_area'].to_f lighting_per_person = space_type_properties['lighting_per_person'].to_f lights_frac_to_return_air = space_type_properties['lighting_fraction_to_return_air'].to_f lights_frac_radiant = space_type_properties['lighting_fraction_radiant'].to_f lights_frac_visible = space_type_properties['lighting_fraction_visible'].to_f lights_frac_replaceable = space_type_properties['lighting_fraction_replaceable'].to_f lights_have_info = true if !lighting_per_area.zero? || !lighting_per_person.zero? ##### NOTE: Reference for LED lighting's return air, radiant, and visible fraction values is: page 142, NREL (2014), "Proven Energy-Saving Technologies for Commercial Properties", available at https://www.nrel.gov/docs/fy15osti/63807.pdf if lights_type == 'LED' led_lights_have_info = false led_spacetype_data = @standards_data['tables']['led_lighting_data']['table'] standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil led_space_type_properties = led_spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) } if led_space_type_properties.nil? raise("#{standards_building_type} for #{standards_space_type} was not found please verify the led lighting database names match the space type names.") end lighting_per_area_led_lighting = led_space_type_properties['lighting_per_area'].to_f lights_frac_to_return_air_led_lighting = led_space_type_properties['lighting_fraction_to_return_air'].to_f lights_frac_radiant_led_lighting = led_space_type_properties['lighting_fraction_radiant'].to_f lights_frac_visible_led_lighting = led_space_type_properties['lighting_fraction_visible'].to_f led_lights_have_info = true unless lighting_per_area_led_lighting.zero? end return unless set_lights && lights_have_info # Remove all but the first instance instances = space_type.lights.sort if instances.empty? definition = OpenStudio::Model::LightsDefinition.new(space_type.model) if lights_type == 'NECB_Default' definition.setName("#{space_type.name} Lights Definition") elsif lights_type == 'LED' definition.setName("#{space_type.name} Lights Definition - LED lighting") end # puts definition.name().to_s instance = OpenStudio::Model::Lights.new(definition) instance.setName("#{space_type.name} Lights") # puts instance.name.to_s instance.setSpaceType(space_type) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no lights, one has been created.") instances << instance elsif instances.size > 1 instances.each_with_index do |inst, i| next if i.zero? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.") inst.remove end end # Modify the definition of the instance space_type.lights.sort.each do |inst| definition = inst.lightsDefinition unless lighting_per_area.zero? if lights_type == 'NECB_Default' set_lighting_per_area(space_type: space_type, definition: definition, lighting_per_area: lighting_per_area, lights_scale: lights_scale) elsif lights_type == 'LED' set_lighting_per_area_led_lighting(space_type: space_type, definition: definition, lighting_per_area_led_lighting: lighting_per_area_led_lighting, lights_scale: lights_scale) end end unless lighting_per_person.zero? definition.setWattsperPerson(OpenStudio.convert(lighting_per_person.to_f, 'W/person', 'W/person').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting to #{lighting_per_person} W/person.") end unless lights_frac_to_return_air.zero? if lights_type == 'NECB_Default' definition.setReturnAirFraction(lights_frac_to_return_air) elsif lights_type == 'LED' definition.setReturnAirFraction(lights_frac_to_return_air_led_lighting) end end unless lights_frac_radiant.zero? if lights_type == 'NECB_Default' definition.setFractionRadiant(lights_frac_radiant) elsif lights_type == 'LED' definition.setFractionRadiant(lights_frac_radiant_led_lighting) end end unless lights_frac_visible.zero? if lights_type == 'NECB_Default' definition.setFractionVisible(lights_frac_visible) elsif lights_type == 'LED' definition.setFractionVisible(lights_frac_visible_led_lighting) end end # unless lights_frac_replaceable.zero? # definition.setFractionReplaceable(lights_frac_replaceable) # end end # If additional lights are specified, add those too additional_lighting_per_area = space_type_properties['additional_lighting_per_area'].to_f # If there are none then exit method return if additional_lighting_per_area.zero? # Create the lighting definition additional_lights_def = OpenStudio::Model::LightsDefinition.new(space_type.model) additional_lights_def.setName("#{space_type.name} Additional Lights Definition") additional_lights_def.setWattsperSpaceFloorArea(OpenStudio.convert(additional_lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get) additional_lights_def.setReturnAirFraction(lights_frac_to_return_air) additional_lights_def.setFractionRadiant(lights_frac_radiant) additional_lights_def.setFractionVisible(lights_frac_visible) # Create the lighting instance and hook it up to the space type additional_lights = OpenStudio::Model::Lights.new(additional_lights_def) additional_lights.setName("#{space_type.name} Additional Lights") additional_lights.setSpaceType(space_type) end
Reduces the SRR to the values specified by the PRM. SRR reduction will be done by shrinking vertices toward the centroid.
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 164 def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0) # 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 skylights # > 1: Do nothing 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 # 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 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 unless srr_set.to_f >= -3.1 && srr_set.to_f <= -2.9 # SRR limit srr_lim = get_standards_constant('skylight_to_roof_ratio_max_value') * 100.0 # Loop through all spaces in the model, and # per the PNNL PRM Reference Manual, find the areas # of each space conditioning category (res, nonres, semi-heated) # separately. Include space multipliers. nr_wall_m2 = 0.001 # Avoids divide by zero errors later nr_sky_m2 = 0 res_wall_m2 = 0.001 res_sky_m2 = 0 sh_wall_m2 = 0.001 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 sky_area_m2 = 0 space.surfaces.sort.each do |surface| # Skip non-outdoor surfaces next unless surface.outsideBoundaryCondition == 'Outdoors' # Skip non-walls next unless surface.surfaceType == 'RoofCeiling' # This wall's gross area (including skylight area) wall_area_m2 += surface.grossArea * space.multiplier # Subsurfaces in this surface surface.subSurfaces.sort.each do |ss| sky_area_m2 += ss.netArea * space.multiplier end end # Determine the space category cat = 'NonRes' if OpenstudioStandards::Space.space_residential?(space) cat = 'Res' end # if space.is_semiheated # cat = 'Semiheated' # end # Add to the correct category case cat when 'NonRes' nr_wall_m2 += wall_area_m2 nr_sky_m2 += sky_area_m2 when 'Res' res_wall_m2 += wall_area_m2 res_sky_m2 += sky_area_m2 when 'Semiheated' sh_wall_m2 += wall_area_m2 sh_sky_m2 += sky_area_m2 end total_roof_m2 += wall_area_m2 total_subsurface_m2 += sky_area_m2 end # Calculate the SRR of each category srr_nr = ((nr_sky_m2 / nr_wall_m2) * 100).round(1) srr_res = ((res_sky_m2 / res_wall_m2) * 100).round(1) srr_sh = ((sh_sky_m2 / sh_wall_m2) * 100).round(1) srr = ((total_subsurface_m2 / total_roof_m2) * 100.0).round(1) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The skylight to roof ratios (SRRs) are: NonRes: #{srr_nr.round}%, Res: #{srr_res.round}%.") # Check against SRR limit red_nr = srr_nr > srr_lim red_res = srr_res > srr_lim red_sh = srr_sh > srr_lim # Stop here unless windows need reducing return true unless srr > srr_lim OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{srr_lim.round}%.") # Determine the factors by which to reduce the window / door area mult = srr_lim / srr # Reduce the subsurface areas model.getSpaces.sort.each do |space| # Loop through all surfaces in this space space.surfaces.sort.each do |surface| # Skip non-outdoor surfaces next unless surface.outsideBoundaryCondition == 'Outdoors' # Skip non-walls next unless surface.surfaceType == 'RoofCeiling' # Subsurfaces in this surface surface.subSurfaces.sort.each do |ss| # Reduce the size of the subsurface red = 1.0 - mult OpenstudioStandards::Geometry.sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red) end end end return true end
Reduces the WWR to the values specified by the NECB NECB 3.2.1.4
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 4 def apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0, necb_hdd: true) # NECB FDWR limit hdd = get_necb_hdd18(model: model, necb_hdd: necb_hdd) # Get the maximum NECB fdwr # fdwr_set settings: # 0-1: Remove all windows and add windows to match this fdwr # -1: Remove all windows and add windows to match max fdwr from NECB # -2: Do not apply any fdwr changes, leave windows alone (also works for fdwr > 1) # -3: Use old method which reduces existing window size (if necessary) to meet maximum NECB fdwr limit # <-3.1: Remove all windows and doors # > 1: Do nothing return if fdwr_set.to_f > 1.0 return apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f) if fdwr_set.to_f >= 0.0 && fdwr_set.to_f <= 1.0 return apply_max_fdwr_nrcan(model: model, fdwr_lim: max_fwdr(hdd).round(3)) if fdwr_set.to_f >= -1.1 && fdwr_set.to_f <= -0.9 return if fdwr_set.to_f >= -2.1 && fdwr_set.to_f <= -1.9 return apply_limit_fdwr(model: model, fdwr_lim: (max_fwdr(hdd) * 100.0).round(1)) if fdwr_set.to_f >= -3.1 && fdwr_set.to_f <= -2.9 return apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f) if fdwr_set.to_f < -3.1 end
Organizes Zones and assigns them to appropriate systems according to NECB 2011-17 systems spacetype rules in Sec 8. requires requires fuel type to be assigned for each system aspect. Defaults to gas hydronic.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 96 def apply_systems(model:, primary_heating_fuel:, swh_fuel:, sizing_run_dir:, shw_scale:, baseline_system_zones_map_option:) raise('validation of model failed.') unless validate_initial_model(model) # Check to see if model is using another vintage of spacetypes. If so overwrite the @standards for the object with the # other spacetype data. This is required for correct system mapping. template = determine_spacetype_vintage(model) unless template == self.class.name # Frankenstein the standards data wrt spacetype data. @standards_data['space_types'] = Standard.build(template).standards_data['space_types'] end # do a sizing run. if model_run_sizing_run(model, "#{sizing_run_dir}/autozone_systems") == false raise('autorun sizing run failed!') end # collect sizing information on each space. store_space_sizing_loads(model) # remove idealair from zones if any. model.getZoneHVACIdealLoadsAirSystems.each(&:remove) @hw_loop = create_hw_loop_if_required(self.fuel_type_set.baseboard_type, self.fuel_type_set.boiler_fueltype, self.fuel_type_set.backup_boiler_fueltype, self.fuel_type_set.mau_heating_coil_type, model) # Rule that all dwelling units have their own zone and system. auto_system_dwelling_units(model: model, necb_reference_hp: self.fuel_type_set.necb_reference_hp, necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel, baseboard_type: self.fuel_type_set.baseboard_type, boiler_fueltype: self.fuel_type_set.boiler_fueltype, chiller_type: self.fuel_type_set.chiller_type, fan_type: self.fuel_type_set.fan_type, heating_coil_type_sys3: self.fuel_type_set.heating_coil_type_sys3, heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4, hw_loop: @hw_loop, heating_coil_type_sys6: self.fuel_type_set.heating_coil_type_sys6, mau_cooling_type: self.fuel_type_set.mau_cooling_type, mau_heating_coil_type: self.fuel_type_set.mau_heating_coil_type, mau_type: self.fuel_type_set.mau_type, baseline_system_zones_map_option: baseline_system_zones_map_option) # Assign a single system 4 for all wet spaces.. and assign the control zone to the one with the largest load. auto_system_wet_spaces(baseboard_type: self.fuel_type_set.baseboard_type, necb_reference_hp: self.fuel_type_set.necb_reference_hp, necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel, boiler_fueltype: self.fuel_type_set.boiler_fueltype, heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4, model: model) # Assign a single system 4 for all storage spaces.. and assign the control zone to the one with the largest load. auto_system_storage_spaces(baseboard_type: self.fuel_type_set.baseboard_type, necb_reference_hp: self.fuel_type_set.necb_reference_hp, necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel, boiler_fueltype: self.fuel_type_set.boiler_fueltype, heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4, model: model) # Assign the wild spaces to a single system 4 system with a control zone with the largest load. auto_system_wild_spaces(baseboard_type: self.fuel_type_set.baseboard_type, necb_reference_hp: self.fuel_type_set.necb_reference_hp, necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel, heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4, model: model) # do the regular assignment for the rest and group where possible. auto_system_all_other_spaces(model: model, necb_reference_hp: self.fuel_type_set.necb_reference_hp, necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel, baseboard_type: self.fuel_type_set.baseboard_type, boiler_fueltype: self.fuel_type_set.boiler_fueltype, chiller_type: self.fuel_type_set.chiller_type, fan_type: self.fuel_type_set.fan_type, heating_coil_type_sys3: self.fuel_type_set.heating_coil_type_sys3, heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4, hw_loop: @hw_loop, heating_coil_type_sys6: self.fuel_type_set.heating_coil_type_sys6, mau_cooling_type: self.fuel_type_set.mau_cooling_type, mau_heating_coil_type: self.fuel_type_set.mau_heating_coil_type, mau_type: self.fuel_type_set.mau_type ) model_add_swh(model: model, swh_fueltype: swh_fuel, shw_scale: shw_scale) model_apply_sizing_parameters(model) # set a larger tolerance for unmet hours from default 0.2 to 1.0C model.getOutputControlReportingTolerances.setToleranceforTimeHeatingSetpointNotMet(1.0) model.getOutputControlReportingTolerances.setToleranceforTimeCoolingSetpointNotMet(1.0) end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 548 def apply_systems_and_efficiencies(model:, sizing_run_dir:, primary_heating_fuel:, swh_fuel:, dcv_type: 'NECB_Default', ecm_system_name: 'NECB_Default', ecm_system_zones_map_option: 'NECB_Default', erv_package: 'NECB_Default', boiler_eff: nil, furnace_eff: nil, unitary_cop: nil, shw_eff: nil, daylighting_type: 'NECB_Default', nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, nv_delta_temp_in_out: nil, pv_ground_type:, pv_ground_total_area_pv_panels_m2:, pv_ground_tilt_angle:, pv_ground_azimuth_angle:, pv_ground_module_description:, chiller_type: 'NECB_Default', shw_scale:, airloop_economizer_type: nil, baseline_system_zones_map_option:) # Create ECM object. ecm = ECMS.new # -------- Systems Layout----------- # Create Default Systems. apply_systems(model: model, primary_heating_fuel: primary_heating_fuel, swh_fuel: swh_fuel, sizing_run_dir: sizing_run_dir, shw_scale: shw_scale, baseline_system_zones_map_option: baseline_system_zones_map_option) # Apply new ECM system. Overwrite standard as required. ecm.apply_system_ecm(model: model, ecm_system_name: ecm_system_name, template_standard: self, ecm_system_zones_map_option: ecm_system_zones_map_option) # -------- Performace, Efficiencies, Controls and Sensors ------------ # # Set code standard equipment characteristics. sql_db_vars_map = apply_standard_efficiencies(model: model, sizing_run_dir: sizing_run_dir, necb_reference_hp: self.fuel_type_set.necb_reference_hp) # Apply System ecm.apply_system_efficiencies_ecm(model: model, ecm_system_name: ecm_system_name, template_standard: self) # Apply ECM ERV charecteristics as required. Part 2 of above ECM. ecm.apply_erv_ecm_efficiency(model: model, erv_package: erv_package) # Apply DCV as required model_enable_demand_controlled_ventilation(model, dcv_type) # Apply Boiler Efficiency ecm.modify_boiler_efficiency(model: model, boiler_eff: boiler_eff) # Apply Furnace Efficiency ecm.modify_furnace_efficiency(model: model, furnace_eff: furnace_eff) # Apply Unitary curves ecm.modify_unitary_cop(model: model, unitary_cop: unitary_cop, sizing_done: false, sql_db_vars_map: sql_db_vars_map) # Apply SHW Efficiency ecm.modify_shw_efficiency(model: model, shw_eff: shw_eff) # Apply daylight controls. model_add_daylighting_controls(model: model, daylighting_type: daylighting_type) # Apply Chiller efficiency ecm.modify_chiller_efficiency(model: model, chiller_type: chiller_type) # Apply airloop economizer ecm.add_airloop_economizer(model: model, airloop_economizer_type: airloop_economizer_type) # Perform a second sizing run if needed if (!unitary_cop.nil? && unitary_cop != 'NECB_Default') || !model.getPlantLoops.empty? if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false raise('sizing run 2 failed!') end end # apply unitary cop ecm.modify_unitary_cop(model: model, unitary_cop: unitary_cop, sizing_done: true, sql_db_vars_map: sql_db_vars_map) # set capacities of district heating and cooling equipment for ground-source heat pump ecm district_heat = false if model.version < OpenStudio::VersionString.new('3.7.0') district_heat = !model.getDistrictHeatings.empty? else district_heat = !model.getDistrictHeatingWaters.empty? end ecm.set_ghx_loop_district_cap(model) if (district_heat && !model.getDistrictCoolings.empty?) # -------Pump sizing required by some vintages---------------- # Apply Pump power as required. apply_loop_pump_power(model: model, sizing_run_dir: sizing_run_dir) # -------Natural ventilation---------------- # Apply natural ventilation using simplified method. if nv_type == 'add_nv' ecm.apply_nv(model: model, nv_type: nv_type, nv_opening_fraction: nv_opening_fraction, nv_temp_out_min: nv_temp_out_min, nv_delta_temp_in_out: nv_delta_temp_in_out) end # -------Ground-mounted PV panels---------------- # Apply ground-mounted PV panels as required. if pv_ground_type == 'add_pv_ground' ecm.apply_pv_ground(model: model, pv_ground_type: pv_ground_type, pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2, pv_ground_tilt_angle: pv_ground_tilt_angle, pv_ground_azimuth_angle: pv_ground_azimuth_angle, pv_ground_module_description: pv_ground_module_description) end # Rename air loop and plant loop nodes to accommodate coming OpenStudio version rename_air_loop_nodes(model) rename_plant_loop_nodes(model) end
(Optionally) uprates, then derates, envelope surface constructions due to MAJOR thermal bridges (e.g. roof parapets, corners, fenestration perimeters). See lib/openstudio-standards/btap/bridging.rb, which relies on the Thermal Bridging & Derating (TBD) gem.
@param model [OpenStudio::Model::Model] an OpenStudio model @param tbd_option [String] BTAP/TBD option @param tbd_interpolate [Boolean] true if TBD interpolates between costed Uo @param wall_U [Double] wall conductance in W/m2.K (nil by default) @param floor_U [Double] floor conductance in W/m2.K (nil by default) @param roof_U [Double] roof conductance in W/m2.K (nil by default)
@return [Boolean] true if successful, e.g. no errors, compliant if uprated
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1018 def apply_thermal_bridging(model: nil, tbd_option: 'none', tbd_interpolate: false, wall_U: nil, floor_U: nil, roof_U: nil) return false unless model.is_a?(OpenStudio::Model::Model) return false unless tbd_option.respond_to?(:to_s) tbd_option = tbd_option.to_s # 4x options: # - 'none' (TBD is ignored) # - derate using 'bad' PSI factors (BTAP-costed) # - derate using 'good' PSI factors (BTAP-costed) # - 'uprate' (then derate), i.e. iterative process (BTAP-costed) ok = tbd_option == 'bad' || tbd_option == 'good' || tbd_option == 'uprate' return true if tbd_option == 'none' return false unless ok argh = {} # BTAP/TBD arguments ok = tbd_interpolate == true || tbd_interpolate == false argh[:interpolate] = tbd_interpolate if ok argh[:interpolate] = false unless ok argh[:walls ] = { uo: wall_U } argh[:floors] = { uo: floor_U } argh[:roofs ] = { uo: roof_U } if tbd_option == 'uprate' argh[:walls ][:ut] = wall_U argh[:floors ][:ut] = floor_U argh[:roofs ][:ut] = roof_U elsif tbd_option == 'good' argh[:quality] = :good end # default == :bad @tbd = BTAP::Bridging.new(model, argh) true end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 691 def apply_weather_data(model:, epw_file:, custom_weather_folder: nil) # Create full path to weather file weather_files = File.absolute_path(File.join(__FILE__, '..', '..', '..', '..', '..' , '..', "data/weather")) weather_file = File.join(weather_files, epw_file) # Check if the weather file exists. If it does continue as normal, otherwise try to dowload it from the # canmet-energy/btap_weather repository unless File.exist?(weather_file) # Check if btap_batch transferred the weather file weather_transfer = check_datapoint_weather_folder(epw_file: epw_file, weather_folder: weather_files, custom_weather_folder: custom_weather_folder) # If btap_batch didn't transfer the weather file, download it. get_weather_file_from_repo(epw_file: epw_file) unless weather_transfer end # Fix EMS references. Temporary workaround for OS issue #2598 model_temp_fix_ems_references(model) model.getThermostatSetpointDualSetpoints(&:remove) model.getYearDescription.setDayofWeekforStartDay('Sunday') weather_file_path = OpenstudioStandards::Weather.get_standards_weather_file_path(epw_file) OpenstudioStandards::Weather.model_set_building_location(model, weather_file_path: weather_file_path) end
This method will determine if the loads on a space are similar. (Exposure, space type, space loads, and schedules, etc)
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 543 def are_space_loads_similar?(space_1:, space_2:, surface_percent_difference_tolerance: 0.01, angular_percent_difference_tolerance: 0.001, heating_load_percent_difference_tolerance: 15.0) # Do they have the same space type? return false unless space_1.multiplier == space_2.multiplier # Ensure that they both have defined spacetypes return false if space_1.spaceType.empty? return false if space_2.spaceType.empty? # ensure that they have the same spacetype. return false unless space_1.spaceType.get == space_2.spaceType.get # Perform surface comparision. If ranges are within percent_difference_tolerance.. they can be considered the same. space_1_floor_area = space_1.floorArea space_2_floor_area = space_2.floorArea space_1_surface_report = space_surface_report(space_1) space_2_surface_report = space_surface_report(space_2) # Spaces should have the same number of surface orientations. return false unless space_1_surface_report.size == space_2_surface_report.size # spaces should have similar loads return false unless percentage_difference(stored_space_heating_load(space_1), stored_space_heating_load(space_2)) <= heating_load_percent_difference_tolerance # Each surface should match space_1_surface_report.each do |space_1_surface| surface_match = space_2_surface_report.detect do |space_2_surface| space_1_surface[:surface_type] == space_2_surface[:surface_type] && space_1_surface[:boundary_condition] == space_2_surface[:boundary_condition] && percentage_difference(space_1_surface[:tilt], space_2_surface[:tilt]) <= angular_percent_difference_tolerance && percentage_difference(space_1_surface[:azimuth], space_2_surface[:azimuth]) <= angular_percent_difference_tolerance && percentage_difference(space_1_surface[:surface_area_to_floor_ratio], space_2_surface[:surface_area_to_floor_ratio]) <= surface_percent_difference_tolerance && percentage_difference(space_1_surface[:glazed_subsurface_area_to_floor_ratio], space_2_surface[:glazed_subsurface_area_to_floor_ratio]) <= surface_percent_difference_tolerance && percentage_difference(space_1_surface[:opaque_subsurface_area_to_floor_ratio], space_2_surface[:opaque_subsurface_area_to_floor_ratio]) <= surface_percent_difference_tolerance end return false if surface_match.nil? end return true end
This method will determine if the loads on a zone are similar. (Exposure, space type, space loads, and schedules, etc)
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 526 def are_zone_loads_similar?(zone_1:, zone_2:) # make sure they have the same number of spaces. truthes = [] return false if zone_1.spaces.size != zone_2.spaces.size zone_1.spaces.each do |space_1| zone_2.spaces.each do |space_2| if are_space_loads_similar?(space_1: space_1, space_2: space_2) truthes << true end end end # truthes sizes should be the same as the # of spaces if all spaces are similar. return truthes.size == zone_1.spaces.size end
Method to set the base system name based on the following syntax: |sys_abbr|sys_oa|shr>?|sc>?|sh>?|ssf>?|zh>?|zc>?|srf>?| âsys_abbrâ designates the NECB system type (âsys_1, sys_2, ⊠sys_6â) âsys_oaâ: âmixedâ or âdoasâ âsys_name_parsâ is a hash for the remaining system name parts for heat recovery, heating, cooling, supply fan, zone heating, zone cooling, and return fan
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2194 def assign_base_sys_name(airloop, sys_abbr:, sys_oa:, sys_name_pars:) sys_name = "#{sys_abbr}|#{sys_oa}|" sys_name_pars.each do |key, value| case key.downcase when 'sys_hr' case value.downcase when 'none' sys_name += 'shr>none' end when 'sys_htg' case value.downcase when 'none' sys_name += 'sh>none' when 'electric' sys_name += 'sh>c-e' when 'hot water' sys_name += 'sh>c-hw' when 'gas' sys_name += 'sh>c-g' when 'dx' sys_name += 'sh>ashp' when 'ccashp' sys_name += 'sh>ccashp' when 'ashp' sys_name += 'sh>ashp' end when 'sys_clg' case value.downcase when 'none' sys_name += 'sc>none' when 'chilled water' sys_name += 'sc>c-chw' when 'dx' if sys_name_pars['sys_htg'] == 'dx' sys_name += 'sc>ashp' else sys_name += 'sc>dx' end when 'ccashp' sys_name += 'sc>ccashp' when 'ashp' sys_name += 'sc>ashp' end when 'sys_sf' case value.downcase when 'none' sys_name += 'ssf>none' when 'cv' sys_name += 'ssf>cv' when 'vv' sys_name += 'ssf>vv' end when 'zone_htg' case value.downcase when 'none' sys_name += 'zh>none' when 'electric' sys_name += 'zh>b-e' when 'hot water' sys_name += 'zh>b-hw' when 'tpfc' sys_name += 'zh>fpfc' when 'fpfc' sys_name += 'zh>tpfc' when 'pthp' sys_name += 'zh>pthp' end when 'zone_clg' case value.downcase when 'none' sys_name += 'zc>none' when 'tpfc' sys_name += 'zc>tpfc' when 'fpfc' sys_name += 'zc>fpfc' when 'ptac' sys_name += 'zc>ptac' when 'pthp' sys_name += 'zc>pthp' end when 'sys_rf' case value.downcase when 'none' sys_name += 'srf>none' when 'cv' sys_name += 'srf>cv' when 'vv' sys_name += 'srf>vv' end end sys_name += '|' end airloop.setName(sys_name) end
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 539 def assign_contruction_to_adiabatic_surfaces(model) cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(model) cp02_carpet_pad.setName('CP02 CARPET PAD') cp02_carpet_pad.setRoughness('VeryRough') cp02_carpet_pad.setThermalResistance(0.21648) cp02_carpet_pad.setThermalAbsorptance(0.9) cp02_carpet_pad.setSolarAbsorptance(0.7) cp02_carpet_pad.setVisibleAbsorptance(0.8) normalweight_concrete_floor = OpenStudio::Model::StandardOpaqueMaterial.new(model) normalweight_concrete_floor.setName('100mm Normalweight concrete floor') normalweight_concrete_floor.setRoughness('MediumSmooth') normalweight_concrete_floor.setThickness(0.1016) normalweight_concrete_floor.setConductivity(2.31) normalweight_concrete_floor.setDensity(2322) normalweight_concrete_floor.setSpecificHeat(832) nonres_floor_insulation = OpenStudio::Model::MasslessOpaqueMaterial.new(model) nonres_floor_insulation.setName('Nonres_Floor_Insulation') nonres_floor_insulation.setRoughness('MediumSmooth') nonres_floor_insulation.setThermalResistance(2.88291975297193) nonres_floor_insulation.setThermalAbsorptance(0.9) nonres_floor_insulation.setSolarAbsorptance(0.7) nonres_floor_insulation.setVisibleAbsorptance(0.7) floor_adiabatic_construction = OpenStudio::Model::Construction.new(model) floor_adiabatic_construction.setName('Floor Adiabatic construction') floor_layers = OpenStudio::Model::MaterialVector.new floor_layers << cp02_carpet_pad floor_layers << normalweight_concrete_floor floor_layers << nonres_floor_insulation floor_adiabatic_construction.setLayers(floor_layers) g01_13mm_gypsum_board = OpenStudio::Model::StandardOpaqueMaterial.new(model) g01_13mm_gypsum_board.setName('G01 13mm gypsum board') g01_13mm_gypsum_board.setRoughness('Smooth') g01_13mm_gypsum_board.setThickness(0.0127) g01_13mm_gypsum_board.setConductivity(0.1600) g01_13mm_gypsum_board.setDensity(800) g01_13mm_gypsum_board.setSpecificHeat(1090) g01_13mm_gypsum_board.setThermalAbsorptance(0.9) g01_13mm_gypsum_board.setSolarAbsorptance(0.7) g01_13mm_gypsum_board.setVisibleAbsorptance(0.5) wall_adiabatic_construction = OpenStudio::Model::Construction.new(model) wall_adiabatic_construction.setName('Wall Adiabatic construction') wall_layers = OpenStudio::Model::MaterialVector.new wall_layers << g01_13mm_gypsum_board wall_layers << g01_13mm_gypsum_board wall_adiabatic_construction.setLayers(wall_layers) m10_200mm_concrete_block_basement_wall = OpenStudio::Model::StandardOpaqueMaterial.new(model) m10_200mm_concrete_block_basement_wall.setName('M10 200mm concrete block basement wall') m10_200mm_concrete_block_basement_wall.setRoughness('MediumRough') m10_200mm_concrete_block_basement_wall.setThickness(0.2032) m10_200mm_concrete_block_basement_wall.setConductivity(1.326) m10_200mm_concrete_block_basement_wall.setDensity(1842) m10_200mm_concrete_block_basement_wall.setSpecificHeat(912) basement_wall_construction = OpenStudio::Model::Construction.new(model) basement_wall_construction.setName('Basement Wall construction') basement_wall_layers = OpenStudio::Model::MaterialVector.new basement_wall_layers << m10_200mm_concrete_block_basement_wall basement_wall_construction.setLayers(basement_wall_layers) basement_floor_construction = OpenStudio::Model::Construction.new(model) basement_floor_construction.setName('Basement Floor construction') basement_floor_layers = OpenStudio::Model::MaterialVector.new basement_floor_layers << m10_200mm_concrete_block_basement_wall basement_floor_layers << cp02_carpet_pad basement_floor_construction.setLayers(basement_floor_layers) model.getSurfaces.sort.each do |surface| if surface.outsideBoundaryCondition.to_s == 'Adiabatic' if surface.surfaceType.to_s == 'Wall' surface.setConstruction(wall_adiabatic_construction) else surface.setConstruction(floor_adiabatic_construction) end elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients' # Ground if surface.surfaceType.to_s == 'Wall' surface.setOutsideBoundaryCondition('Ground') surface.setConstruction(basement_wall_construction) else surface.setOutsideBoundaryCondition('Ground') surface.setConstruction(basement_floor_construction) end end end end
This calculates the volume and capacity of one mixed tank that is assumed to service all shw in the building u is the tank insulation in W/(m^2*K), height_to_radius is the ratio of tank radius to tank height and is dimensionless
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 218 def auto_size_shw_capacity(model:, u: 0.45, height_to_radius: 2, shw_scale: 'NECB_Default') peak_flow_rate = 0 shw_space_types = [] space_peak_flows = [] water_use = 0 weekly_peak_flow = { 'Default|Wkdy' => Array.new(24, 0), 'Sat' => Array.new(24, 0), 'Sun|Hol' => Array.new(24, 0) } peak_day_sched = nil peak_hour_sched = 0 peak_flow_sched = 0 next_hour_day = nil next_hour_hour = 0 next_hour_flow = 0 total_peak_flow_rate = 0 shw_spaces = [] shw_sched_names = [] ##### Modify shw_scale if required if shw_scale.instance_of?(String) shw_scale = shw_scale.strip # remove leading or trailing whitespace in case users add them in shw_scale end if shw_scale == 'NECB_Default' or shw_scale.nil? or shw_scale == 'none' or shw_scale == false shw_scale = 1.0 elsif shw_scale.instance_of?(String) # Convert a string to a float shw_scale = shw_scale.to_f end # First go through all the spaces in the building and determine and determine their shw requirements space_types_table = @standards_data['space_types'] model.getSpaces.sort.each do |space| space_peak_flow = 0 data = nil space_type_name = space.spaceType.get.standardsSpaceType.get.to_s tank_temperature = 60 # find the specific space_type properties from standard.json space_types_table.each do |space_type| if (space_type['building_type'] + ' ' + space_type_name) == (space_type['building_type'] + ' ' + space_type['space_type']) break if space_type['necb_hvac_system_selection_type'] == '- undefined -' # If there is no service hot water load.. Don't bother adding anything. break if (space_type['service_water_heating_peak_flow_per_area'].to_f == 0.0 && space_type['service_water_heating_peak_flow_rate'].to_f == 0.0) || space_type['service_water_heating_schedule'].nil? # If there is a service hot water load collect the space information data = space_type break end end # If there is no service hot water load.. Don't bother adding anything. # Skip space types with no data next if data.nil? space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2 # Calculate the peak shw flow rate for the space. Peak flow from JSON file is in US Gal/hr/ft^2 # space_peak_flow_ind is the peak flow rate for the space while space_peak_flow is the peak flow # rate for the space multiplied by the space (ultimately thermal zone) multiplier. space_peak_flow is used for # much of the rest as it reflects how much water is used. space_peak_flow_ind is recorded and used later on # when defining water use equipment. When when water use equipment is assigned to spaces then the water use # by the equipment is multiplied by the space multiplier. Note that there is a separate water use equipment # multiplier as well which is different than the space (ultimately thermal zone) multiplier. space_peak_flow_ind = data['service_water_heating_peak_flow_per_area'].to_f * space_area * shw_scale space_peak_flow = space_peak_flow_ind * space.multiplier # space_peak_flows << space_peak_flow # Add the peak shw flow rate for the space to the total for the entire building total_peak_flow_rate += space_peak_flow # Get the tank temperature for the space. This should always be 60 C but I added this part in case something changes in the future. if data['service_water_heating_target_temperature'].nil? || (data['service_water_heating_target_temperature'] <= 16) tank_temperature = 60 else tank_temperature = data['service_water_heating_target_temperature'] end # Get the shw schedule # shw_sched_names << data['service_water_heating_schedule'] # 'shw_peakflow_ind_SI' is the shw peak flow rate of the individual space (without the space multiplier) space_info = { 'shw_spaces' => space, 'shw_peakflow_SI' => OpenStudio.convert(space_peak_flow, 'gal/hr', 'm^3/s').get, 'shw_peakflow_ind_SI' => OpenStudio.convert(space_peak_flow_ind, 'gal/hr', 'm^3/s').get, 'shw_temp_SI' => tank_temperature, 'shw_sched' => data['service_water_heating_schedule'] } shw_spaces << space_info # The following gets the water use schedule for space and applies it to the peak flow rate for the space. This # creates an array containing the hourly shw consumption for the space for each day type (Weekday/default, Saturday, # Sunday/Holiday). The hourly shw consumption for each space is added to the array ultimately producing an array # containing the hourly shw demand for the entire building. This is used to determine the peak shw demand # hour and rate for the building. This is different than the overall peak shw demand for the building in that it # takes into account the shw schedule. The peak shw demand hour and rate should always be less than the overall peak # shw demand. # Cycle through the hash accumulating the shw rates for each day type. weekly_peak_flow.sort.each do |day_peak_sched| day_sched = [] # Create the search criteria and retrieve the schedule for the current space and current day type. search_criteria = { 'template' => template, 'name' => data['service_water_heating_schedule'], 'day_types' => day_peak_sched[0] } schedules_table = @standards_data['schedules'] day_sched = model_find_object(schedules_table, search_criteria) # Make sure the schedule is not empty and contains 24 hours. if day_sched.empty? || day_sched['values'].size != 24 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.model_add_swh', "The water use schedule called #{data['service_water_heating_schedule']} for #{space_type_name} is corrupted or could not be found. Please check that the schedules.json file is available and that the schedule names are spelled correctly") return false end # For each hour of the current day type multiply the shw schedule fractional multiplier (representing the fraction of the total shw # rate used in that hour) times the overall peak shw rate for the current space. Add the resulting values to the # array tracking hourly shw demand for the building. Also, determine what the highest hourly demand is for the # building. day_peak_sched[1].sort.each_with_index do |hour_flow, hour_index| weekly_peak_flow[day_peak_sched[0]][hour_index] += day_sched['values'][hour_index] * space_peak_flow if weekly_peak_flow[day_peak_sched[0]][hour_index] > peak_flow_sched peak_flow_sched = weekly_peak_flow[day_peak_sched[0]][hour_index] end end end end if shw_spaces.empty? space_info = { 'shw_spaces' => nil, 'shw_peakflow_SI' => 0, 'shw_peakflow_ind_SI' => 0, 'shw_temp_SI' => 60, 'shw_sched' => [] } shw_spaces << space_info tank_param = { 'tank_volume_SI' => 0, 'tank_capacity_SI' => 0, 'max_temp_SI' => 60, 'loop_peak_flow_rate_SI' => 0, 'parasitic_loss' => 0, 'spaces_w_dhw' => shw_spaces } return tank_param end next_day_test = nil next_hour_test = 0 # The following loop goes through each hour in the array tracking hourly shw demand to find which hours contain the # peak hourly shw demand (this is in case the peak hourly shw demand occurs more than once). It then determines what # the hourly shw demand is for the following hour. It is meant to determine, of the peak hourly shw times, which has # the highest shw demand the following hour. This is used to determine shw capacity and volume. weekly_peak_flow.sort.each do |day_peak_sched| day_peak_sched[1].sort.each_with_index do |hour_flow, hour_index| if hour_flow == peak_flow_sched if hour_index == 23 next_hour_test = 0 case day_peak_sched[0] when 'Default|Wkdy' next_day_test = 'Sat' when 'Sat' next_day_test = 'Sun|Hol' when 'Sun|Hol' next_day_test = 'Default|Wkdy' end else next_hour_test = hour_index + 1 next_day_test = day_peak_sched[0] end if next_hour_flow < weekly_peak_flow[next_day_test][next_hour_test] next_hour_flow = weekly_peak_flow[next_day_test][next_hour_test] next_hour_hour = next_hour_test next_hour_day = next_day_test peak_day_sched = day_peak_sched[0] peak_hour_sched = hour_index end end end end # The shw tank is sized so that it can fulfill the hour with the highest shw needs. Since the flow is in US Gal/hr # No conversion is necessary. tank_volume = peak_flow_sched # Interperite the fractional shw schedules as being the fraction of the hour that the maximum shw rate is used and determine # what this fraction is for the entire building. peak_time_fraction = 1 - (peak_flow_sched / total_peak_flow_rate) # Assume the shw tank needs some minimum amount of time to recover (avoids requiring a ridiculously high capacity). # If the recovery time is to short then the tank needs to hold enough water to service the peak shw hour and the one # after. Then give the tank the entire hour to heat up again. Note again that since peak flows are per hour, and # we are only looking at an hour, no conversion is necessary. if peak_time_fraction <= 0.2 tank_volume += next_hour_flow peak_time_fraction = 1 end tank_volume_SI = OpenStudio.convert(tank_volume, 'gal', 'm^3').get # Determine the tank capacity as the heat output required to heat up the entire volume of the tank in time remaining # in the hour after the peak shw draw is stopped (assume water is provided to the building at 15C ). max_temp = -273 shw_spaces.each do |shw_space| if shw_space['shw_temp_SI'] > max_temp max_temp = shw_space['shw_temp_SI'] end end tank_capacity_SI = tank_volume_SI * 1000 * 4180 * (max_temp - 15) / (3600 * peak_time_fraction) tank_radius = (tank_volume_SI / (height_to_radius * Math::PI))**(1.0 / 3) tank_area = 2 * (1 + height_to_radius) * Math::PI * (tank_radius**2) room_temp = OpenStudio.convert(70, 'F', 'C').get parasitic_loss = u * tank_area * (max_temp - room_temp) tank_param = { 'tank_volume_SI' => tank_volume_SI, 'tank_capacity_SI' => tank_capacity_SI, 'max_temp_SI' => max_temp, 'loop_peak_flow_rate_SI' => OpenStudio.convert(total_peak_flow_rate, 'gal/hr', 'm^3/s').get, 'parasitic_loss' => parasitic_loss, 'spaces_w_dhw' => shw_spaces } return tank_param end
Autosize the pump head by calculating the piping longest piping length and deriving required head from that. If default is set to true then it returns a default pump head of 179532 Pa which is based on the OpenStudio 2.4.1 defaults for a constant speed pump. The method first assumes that the tank and pump are located in the space closest to the center of the bottom of the building. It then assumes that water is delivered to the bottom center of every space that has a demand for shw. It calculates the x, y, and z components of the vector between the shw space and the spaces with demand for shw. The distance of the piping run is calculated by adding the x, y, and z components of the vector (rather than the magnitude of the vector). For the purposes of calculating pressure loss along the pipe bends, and other minor losses are accounted by doubling the calculated length of the pipe. The default kinematic viscosity of water is assumed to be that at 60 C (in m^2/s). The default density of water is assumed to be 983 kg/m^3 as per hypertextbook.com/facts/2007/AllenMa.shtml accessed 2018-07-27. The pipe is assumed to be made out of PVC and have a roughness height of 1.5*10^-6 m as per: www.pipeflow.com/pipe-pressure-drop-calculations/pipe-roughness accessed on 2018-07-25. The default maximum velocity is from the table from âThe Engineering Toolboxâ link: www.engineeringtoolbox.com/flow-velocity-water-pipes-d_385.html Chris Kirney 2018-07-27.
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 444 def auto_size_shw_pump_head(model, default: true, pipe_vel: 1.75, kin_visc_SI: 0.000004736, density_SI: 983, pipe_rough_m: 0.0000015) return 179532 if default mech_room, cond_spaces = find_mech_room(model) return 179532 if mech_room.nil? || cond_spaces.nil? space_coord_dists = [] total_peak_flow = 0 hl_Pas = [] # Now go through each space with a shw load and determine the x, y, and z components of a vector from the centroid # of the floor of the space containing the shw_tank and the centroid of the floor of the given space cond_spaces.each do |cond_space| # Find the specific space_type properties from standard.json spaceType_name = cond_space['space'].spaceType.get.nameString sp_type = spaceType_name[15..-1] # Including regular expressions in the following match for cases where extra characters, which do not belong, are # added to either the space type in the model or the space type reference file. sp_type_info = @standards_data['tables']['space_types']['table'].detect do |data| (Regexp.new(data['space_type'].to_s.upcase).match(sp_type.upcase) || Regexp.new(sp_type.upcase).match(data['space_type'].to_s.upcase) || (data['space_type'].to_s.upcase == sp_type.upcase)) && (data['building_type'].to_s == 'Space Function') end # If the space type could not be found let the use know and go on to the next space. if sp_type_info.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', "The space type called #{sp_type} could not be found. Please check that the schedules.json file is available and that the space types are spelled correctly") next end next if sp_type_info['service_water_heating_peak_flow_per_area'].to_f == 0.0 && sp_type_info['service_water_heating_peak_flow_rate'].to_f == 0.0 || sp_type_info['service_water_heating_schedule'].nil? space_area = OpenStudio.convert(cond_space['space'].floorArea, 'm^2', 'ft^2').get # ft2 # Calculate the peak shw flow rate for the space space_peak_flow = (sp_type_info['service_water_heating_peak_flow_per_area'].to_f * space_area) * cond_space['space'].multiplier space_peak_flow_SI = OpenStudio.convert(space_peak_flow, 'gal/hr', 'm^3/s').get # Determine the total shw peak flow for the building. total_peak_flow += space_peak_flow_SI # I use centroid for the floor as the location of the source or point of use for the shw system. if space_peak_flow_SI > 0 space_coord_dist = [] cond_space['space_centroid'].each_with_index do |dist, coord| space_coord_dist << (dist - mech_room['space_centroid'][coord]).abs end space_coord_dists << space_coord_dist end end # The piping run length from the shw tank to a given space is assumed to be the sum of the coordinates of the vector # described above. The longest piping run becomes the one used for sizing. Note that I double the length of this # piping run below when calculating head loss. space_coord_dists.sort.each do |space_coord_dist| sizing_pipe_length = space_coord_dist[0] + space_coord_dist[1] + space_coord_dist[2] # The shw pump is sized by assuming that the sum of the peak shw volume flow rates for each space has to be fed # through the longest piping run. So for the sizing calculations below, the flow rate is the sum of the peak volume # flow rates for the entire building. The length of the piping run is twice the calculated longest piping run # described above. # Step 1: Calculate the Reynold's number. Note kinematic viscosity is set for water at 60 C and pipe diameter is # set to 3/4". These can be changed by passing different values to the method. I got the kinematic # viscosity from www.engineeringtoolbox.com/water-dynamic-kinematic-viscosity-d_596.html accessed 2018-07-05. # I got the pipe roughness from www.pipeflow.com/pipe-pressure-drop-calculations/pipe-roughness accessed on # 2018-07-25. I assume 3/4" pipe because that is what Mike Lubun says is used in most cases (unless it # it is for process water but we assume that is not the case). # Determine the bulk velocity of the shw through the pipe. # find pipe diameter for the peak flow pipe_dia_m = (4.0 * total_peak_flow / (Math::PI * pipe_vel))**0.5 # Get the Reynolds number. re_pipe = (pipe_vel * pipe_dia_m) / kin_visc_SI # Step 2: Figure out what the Darcy-Weisbach friction factor is. relative_rough = pipe_rough_m / pipe_dia_m f = friction_factor(re_pipe, relative_rough) # Step 3: Calculate the major head loss # Note that you may be thinking that I forgot to divide the last term by 2 in the equation below. I didn't. # I multiplied the piping length by 2 because I did not take pipe bends etc. into account and I calculate the # maximum piping run in a really approximate way. Thus I multiply the piping run by 2. If you can think # of something better please replace what I have. # hl is taken from https://neutrium.net/fluid_flow/pressure-loss-in-pipe accessed 2018-07-26 (I added the height # component). Note that while I allow all of the other physical values to be set I assume that you are building on # earth hence g is hard coded to 9.81 m/s^2. hl_Pa = (f * (sizing_pipe_length / pipe_dia_m) * (pipe_vel**2) * density_SI) + density_SI * space_coord_dist[2] * 9.81 if hl_Pa < 1 hl_Pa = 1 end hl_Pas << hl_Pa end # If no spaces with shw were found return the default pump head. return 179532 if hl_Pas.empty? return hl_Pas.max_by { |hl| hl } end
This method will deal with all non wet, non-wild, and non-dwelling units thermal zones.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 984 def auto_system_all_other_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:) zones = [] other_spaces = model.getSpaces.select do |space| !is_a_necb_dwelling_unit?(space) && !is_an_necb_wildcard_space?(space) && !is_an_necb_storage_space?(space) end other_spaces.each do |space| zones << space.thermalZone.get end zones.uniq! # since dwelling units are all zoned 1:1 to space:zone we simply add the zone to the appropriate btap system. create_necb_system(baseboard_type: baseboard_type, boiler_fueltype: boiler_fueltype, chiller_type: chiller_type, fan_type: fan_type, heating_coil_type_sys3: heating_coil_type_sys3, heating_coil_type_sys4: heating_coil_type_sys4, heating_coil_type_sys6: heating_coil_type_sys6, hw_loop: @hw_loop, mau_cooling_type: mau_cooling_type, mau_heating_coil_type: mau_heating_coil_type, mau_type: mau_type, model: model, zones: zones, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel) end
This method will ensure that all dwelling units are assigned to a system 1 or 3. There is an option to have a shared AHU or not.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1031 def auto_system_dwelling_units(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:, baseline_system_zones_map_option:) system_zones_hash = {} # Determine if dwelling units have a shared AHU. If user entered building stories > 4 then set to true. if baseline_system_zones_map_option == 'one_sys_per_dwelling_unit' dwelling_shared_ahu = false elsif baseline_system_zones_map_option == 'one_sys_per_bldg' || baseline_system_zones_map_option == 'NECB_Default' || baseline_system_zones_map_option == 'none' || baseline_system_zones_map_option == nil || necb_reference_hp dwelling_shared_ahu = true end # store dwelling zones into array zones = [] model.getSpaces.select { |space| is_a_necb_dwelling_unit?(space) }.each do |space| zones << space.thermalZone.get end zones.uniq! # sort system 1 or 3 used for each dwelling unit as per T8.4.4.8.A NECB 2011-17 zones.each do |zone| system_zones_hash[get_necb_thermal_zone_system_selection(zone)] = [] if system_zones_hash[get_necb_thermal_zone_system_selection(zone)].nil? system_zones_hash[get_necb_thermal_zone_system_selection(zone)] << zone end # go through each system and zones pairs to system_zones_hash.each_pair do |system, sys_zones| case system when 1 if dwelling_shared_ahu add_sys1_unitary_ac_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: sys_zones, mau_type: mau_type, mau_heating_coil_type: mau_heating_coil_type, baseboard_type: baseboard_type, hw_loop: @hw_loop, multispeed: false) else # Create a separate air loop for each unit. sys_zones.each do |zone| add_sys1_unitary_ac_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: [zone], mau_type: mau_type, mau_heating_coil_type: mau_heating_coil_type, baseboard_type: baseboard_type, hw_loop: @hw_loop, multispeed: false) end end when 3 if dwelling_shared_ahu add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: sys_zones, heating_coil_type: heating_coil_type_sys3, baseboard_type: baseboard_type, hw_loop: @hw_loop, multispeed: false) else # Create a separate air loop for each unit. sys_zones.each do |zone| add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: [zone], heating_coil_type: heating_coil_type_sys3, baseboard_type: baseboard_type, hw_loop: @hw_loop, multispeed: false) end end end end end
All wet spaces will be on their own system 4 AHU.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1153 def auto_system_storage_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, heating_coil_type_sys4:, model:) # Determine what zones are storage zones. tz = [] storage_spaces = model.getSpaces.select { |space| is_an_necb_storage_space?(space) } storage_spaces.each { |space| tz << space.thermalZone.get } tz.uniq! return if tz.empty? # create a system 4 for the zones. add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: tz, heating_coil_type: heating_coil_type_sys4, baseboard_type: baseboard_type, hw_loop: @hw_loop) # add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, # zones: tz, # heating_coil_type: heating_coil_type_sys4, # baseboard_type: baseboard_type, # hw_loop: @hw_loop, # multispeed: true) end
All wet spaces will be on their own system 4 AHU.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1123 def auto_system_wet_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, heating_coil_type_sys4:, model:) # Determine what zones are wet zones. wet_tz = [] wet_spaces = model.getSpaces.select { |space| is_an_necb_wet_space?(space) } wet_spaces.each { |space| wet_tz << space.thermalZone.get } wet_tz.uniq! # create a system 4 for the wet zones. return if wet_tz.empty? add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: wet_tz, heating_coil_type: heating_coil_type_sys4, baseboard_type: baseboard_type, hw_loop: @hw_loop) # add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, # zones: wet_tz, # heating_coil_type: heating_coil_type_sys4, # baseboard_type: baseboard_type, # hw_loop: @hw_loop, # multispeed: false) end
All wild spaces will be on a single system 4 ahu with the largests heating load zone being the control zone.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1184 def auto_system_wild_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'Defaultfuel', heating_coil_type_sys4:, model:) zones = [] wild_spaces = model.getSpaces.select { |space| !is_an_necb_wet_space?(space) && is_an_necb_wildcard_space?(space) } wild_spaces.each { |space| zones << space.thermalZone.get } zones.uniq! return if zones.empty? # create a system 4 for the wild zones. add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: zones, heating_coil_type: heating_coil_type_sys4, baseboard_type: baseboard_type, hw_loop: @hw_loop) # add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, # zones: zones, # heating_coil_type: heating_coil_type_sys4, # baseboard_type: baseboard_type, # hw_loop: @hw_loop, # multispeed: true) end
This method will find all the spaces that are not wet, wild or dwelling units and zone them. It will try to determine if the spaces are similar based on exposure and load and blend those spaces into the same zone. It will not merge spaces from different floors, since this will impact Chris Kirneys costing algorithms.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 340 def auto_zone_all_other_spaces(model) other_tz_array = [] # iterate through all non wildcard spaces. model.getSpaces.select { |space| !is_a_necb_dwelling_unit?(space) && !is_an_necb_wildcard_space?(space) }.each do |space| # skip if already assigned to a thermal zone. next unless space.thermalZone.empty? # create new zone for this space based on the space name. zone = OpenStudio::Model::ThermalZone.new(model) tz_name = "ALL_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH=#{determine_dominant_schedule([space])}" zone.setName(tz_name) # sets space mulitplier unless it is nil or 1. unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1) zone.setMultiplier(space_multiplier_map[space.name.to_s]) end # Assign space to the new zone. space.setThermalZone(zone) # Add a thermostat space_type_name = space.spaceType.get.name.get thermostat_name = space_type_name + ' Thermostat' thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name) if thermostat.empty? # The thermostat name for the spacetype should exist. OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}") else thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model) ideal_loads.addToThermalZone(zone) end # Go through other spaces and if you find something with similar loads on the same floor, add it to the zone. model.getSpaces.select { |curr_space| !is_a_necb_dwelling_unit?(curr_space) && !is_an_necb_wildcard_space?(curr_space) }.each do |space_target| if space_target.thermalZone.empty? if are_space_loads_similar?(space_1: space, space_2: space_target) && space.buildingStory.get == space_target.buildingStory.get # added since chris needs zones to not span floors for costing. space_target.setThermalZone(zone) end end end other_tz_array << zone end return other_tz_array end
Dwelling unit spaces need to have their own HVAC system. Thankfully NECB defines what spacetypes are considering dwelling units and have been defined as spaces that are openstudio-standards/standards/necb/NECB2011/data/necb_hvac_system_selection_type.json as spaces that are Residential/Accomodation and Sleeping areaâ this is determine by the is_a_necb_dwelling_unit? method. The thermostat is set by the space-type schedule. This will return an array of TZ.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 255 def auto_zone_dwelling_units(model) dwelling_tz_array = [] # ----Dwelling units----------- will always have their own system per unit, so they should have their own thermal zone. model.getSpaces.select { |space| is_a_necb_dwelling_unit?(space) }.each do |space| zone = OpenStudio::Model::ThermalZone.new(model) zone.setName("DU_BT=#{space.spaceType.get.standardsBuildingType.get}_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH#{determine_dominant_schedule([space])}") unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1) zone.setMultiplier(space_multiplier_map[space.name.to_s]) end space.setThermalZone(zone) # Add a thermostat based on the space type. space_type_name = space.spaceType.get.name.get thermostat_name = space_type_name + ' Thermostat' thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name) if thermostat.empty? # The thermostat name for the spacetype should exist. OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}") else thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model).addToThermalZone(zone) end dwelling_tz_array << zone end return dwelling_tz_array end
Something that the code is silent on are smelly humid areas that should not be on the same system as the rest of the
building.. These are the 'wet' spaces and have been defined as locker and washroom areas.. These will be put under
their own single system 4 system. These will be set to the dominant floor schedule.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 288 def auto_zone_wet_spaces(model:, lights_type: 'NECB_Default', lights_scale: 1.0) wet_zone_array = [] model.getSpaces.select { |space| is_an_necb_wet_space?(space) }.each do |space| # if this space was already assigned to something skip it. next unless space.thermalZone.empty? # get space to dominant schedule dominant_schedule = determine_dominant_schedule(space.model.getSpaces) # create new TZ and set space to the zone. zone = OpenStudio::Model::ThermalZone.new(model) space.setThermalZone(zone) tz_name = "WET_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH#{dominant_schedule}" zone.setName(tz_name) # Set multiplier from the original tz multiplier. unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1) zone.setMultiplier(space_multiplier_map[space.name.to_s]) end # this method will determine if the right schedule was used for this wet & wild space if not.. it will reset the space # to use the correct schedule version of the wet and wild space type. adjust_wildcard_spacetype_schedule(space: space, schedule: dominant_schedule, lights_type: lights_type, lights_scale: lights_scale) # Find spacetype thermostat and assign it to the zone. thermostat_name = space.spaceType.get.name.get + ' Thermostat' thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name) if thermostat.empty? # The thermostat name for the spacetype should exist. OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}-") else thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model) ideal_loads.addToThermalZone(zone) end # Go through other spaces to see if there are similar spaces with similar loads on the same floor that can be grouped. model.getSpaces.select { |s| is_an_necb_wet_space?(s) }.each do |space_target| if space_target.thermalZone.empty? if are_space_loads_similar?(space_1: space, space_2: space_target) && space.buildingStory.get == space_target.buildingStory.get # added since chris needs zones to not span floors for costing. adjust_wildcard_spacetype_schedule(space: space_target, schedule: dominant_schedule, lights_type: lights_type, lights_scale: lights_scale) space_target.setThermalZone(zone) end end end wet_zone_array << zone end return wet_zone_array end
This will take all the wildcard spaces and merge them to be supported by a system 4. The control zone will be the zone that has the largest heating load per area.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 387 def auto_zone_wild_spaces(model:, lights_type: 'NECB_Default', lights_scale: 1.0) other_tz_array = [] # iterate through wildcard spaces. model.getSpaces.select { |space| is_an_necb_wildcard_space?(space) && !is_an_necb_wet_space?(space) }.each do |space| # skip if already assigned to a thermal zone. next unless space.thermalZone.empty? # create new zone for this space based on the space name. zone = OpenStudio::Model::ThermalZone.new(model) tz_name = "WILD_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH=#{determine_dominant_schedule(space.model.getSpaces)}" zone.setName(tz_name) # sets space mulitplier unless it is nil or 1. unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1) zone.setMultiplier(space_multiplier_map[space.name.to_s]) end # Assign space to the new zone. space.setThermalZone(zone) # lets keep the wild schedules to be the same as what dominate the floor. dominant_floor_schedule = determine_dominant_schedule(space.model.getSpaces) adjust_wildcard_spacetype_schedule(space: space, schedule: dominant_floor_schedule, lights_type: lights_type, lights_scale: lights_scale) # Add a thermostat space_type_name = space.spaceType.get.name.get thermostat_name = space_type_name + ' Thermostat' thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name) if thermostat.empty? # The thermostat name for the spacetype should exist. OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}") else thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model) ideal_loads.addToThermalZone(zone) end # Go through other spaces and if you find something with similar loads on the same floor, add it to the zone. model.getSpaces.select { |curr_space| is_an_necb_wildcard_space?(curr_space) && !is_an_necb_wet_space?(curr_space) }.each do |space_target| if space_target.thermalZone.empty? if are_space_loads_similar?(space_1: space, space_2: space_target) && (space.buildingStory.get == space_target.buildingStory.get) # added since chris needs zones to not span floors for costing. space_target.setThermalZone(zone) end end end other_tz_array << zone end return other_tz_array wild_zone_array = [] # Get a list of all the wild spaces. model.getSpaces.select { |space| is_an_necb_wildcard_space?(space) && !is_an_necb_wet_space?(space) }.each do |space| # if this space was already assigned to something skip it. next unless space.thermalZone.empty? # find adjacent spaces to the current space. adj_spaces = OpenstudioStandards::Geometry.space_get_adjacent_spaces_with_shared_wall_areas(space, true) adj_spaces = adj_spaces.map { |key, value| key } # find unassigned adjacent wild spaces that have not been assigned that have the same multiplier these will be # lumped together in the same zone. wild_adjacent_spaces = adj_spaces.select do |adj_space| is_an_necb_wildcard_space?(adj_space) && !is_an_necb_wet_space?(adj_space) && adj_space.thermalZone.empty? && (space_multiplier_map[space.name.to_s] == space_multiplier_map[adj_space.name.to_s]) end # put them all together. wild_adjacent_spaces << space # Get adjacent candidate foster zones. Must not be a wildcard space and must not be linked to another space incase # it is part of a mirrored space. other_adjacent_spaces = adj_spaces.select do |adj_space| (is_an_necb_wildcard_space?(adj_space) == false) && (adj_space.thermalZone.get.spaces.size == 1) && (space_multiplier_map[space.name.to_s] == space_multiplier_map[adj_space.name.to_s]) end # If there are adjacent spaces that fit the above criteria. # We will need to set each space to the dominant floor schedule by setting the spaces spacetypes to that # schedule version and eventually set it to a system 4 unless other_adjacent_spaces.empty? # assign the space(s) to the adjacent thermal zone. schedule_type = determine_dominant_schedule(space.buildingStory.get.spaces) zone = other_adjacent_spaces.first.thermalZone.get wild_adjacent_spaces.each do |curr_space| adjust_wildcard_spacetype_schedule(curr_space, schedule_type, @lights_type, @lights_scale, @space_height) curr_space.setThermalZone(zone) end end # create new TZ and set space to the zone. zone = OpenStudio::Model::ThermalZone.new(model) space.setThermalZone(zone) zone.setName("Wild-ZN:BT=#{space.spaceType.get.standardsBuildingType.get}:ST=#{space.spaceType.get.standardsSpaceType.get}:FL=#{space.buildingStory.get.name}:") # Set multiplier from the original tz multiplier. unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1) zone.setMultiplier(space_multiplier_map[space.name.to_s]) end # Set space to dominant dominant_floor_schedule = determine_dominant_schedule(space.buildingStory.get.spaces) # this method will determine if the right schedule was used for this wet & wild space if not.. it will reset the space # to use the correct schedule version of the wet and wild space type. adjust_wildcard_spacetype_schedule(space: space, schedule: dominant_floor_schedule, lights_type: @lights_type, lights_scale: @lights_scale) # Find spacetype thermostat and assign it to the zone. thermostat_name = space.spaceType.get.name.get + ' Thermostat' thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name) if thermostat.empty? # The thermostat name for the spacetype should exist. OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}") else thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model) ideal_loads.addToThermalZone(zone) end # Go through other spaces to see if there are similar spaces with similar loads on the same floor that can be grouped. model.getSpaces.select { |s| is_an_necb_wildcard_space?(s) && !is_an_necb_wet_space?(s) }.each do |space_target| if space_target.thermalZone.empty? if are_space_loads_similar?(space_1: space, space_2: space_target) && space.buildingStory.get == space_target.buildingStory.get # added since chris needs zones to not span floors for costing. adjust_wildcard_spacetype_schedule(space: space_target, schedule: dominant_floor_schedule, lights_type: @lights_type, lights_scale: @lights_scale) space_target.setThermalZone(zone) end end end wild_zone_array << zone end return wild_zone_array end
Applies the standard efficiency ratings and typical performance curves to this object.
@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] the object to modify @return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 533 def boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water) successfully_set_all_properties = false # Define the criteria to find the boiler properties # in the hvac standards data set. search_criteria = boiler_hot_water_find_search_criteria(boiler_hot_water) fuel_type = search_criteria['fuel_type'] fluid_type = search_criteria['fluid_type'] # Get the capacity capacity_w = boiler_hot_water_find_capacity(boiler_hot_water) boiler_capacity = capacity_w # Use the NECB capacities if the SystemFuels class is not defined (i.e. this method was not called from something # that created a SystemFuels object) or if either primary or secondary boiler capacity fractions are not defined. if !self.fuel_type_set.is_a?(SystemFuels) || self.fuel_type_set.primary_boiler_cap_frac.nil? || self.fuel_type_set.secondary_boiler_cap_frac.nil? # Check if secondary and/or modulating boiler required # If boiler names include 'Primary Boiler' or 'Secondary Boiler' then NECB rules are applied if boiler_hot_water.name.to_s.include?('Primary Boiler') || boiler_hot_water.name.to_s.include?('Secondary Boiler') if capacity_w / 1000.0 >= 352.0 if boiler_hot_water.name.to_s.include?('Primary Boiler') boiler_capacity = capacity_w boiler_hot_water.setBoilerFlowMode('LeavingSetpointModulated') boiler_hot_water.setMinimumPartLoadRatio(0.25) elsif boiler_hot_water.name.to_s.include?('Secondary Boiler') boiler_capacity = 0.001 end elsif ((capacity_w / 1000.0) >= 176.0) && ((capacity_w / 1000.0) < 352.0) boiler_capacity = capacity_w / 2 elsif (capacity_w / 1000.0) <= 176.0 if boiler_hot_water.name.to_s.include?('Primary Boiler') if capacity_w <= 1.0 boiler_capacity = 1.0 else boiler_capacity = capacity_w end elsif boiler_hot_water.name.to_s.include?('Secondary Boiler') boiler_capacity = 0.001 end end end else boiler_capacity = capacity_w * self.fuel_type_set.primary_boiler_cap_frac if boiler_hot_water.name.to_s.include?('Primary Boiler') boiler_capacity = capacity_w * self.fuel_type_set.secondary_boiler_cap_frac if boiler_hot_water.name.to_s.include?('Secondary Boiler') end boiler_hot_water.setNominalCapacity(boiler_capacity) # Convert capacity to Btu/hr capacity_btu_per_hr = OpenStudio.convert(boiler_capacity, 'W', 'Btu/hr').get capacity_kbtu_per_hr = OpenStudio.convert(boiler_capacity, 'W', 'kBtu/hr').get # Get the boiler properties boiler_table = @standards_data['boilers'] blr_props = model_find_object(boiler_table, search_criteria, capacity_btu_per_hr) unless blr_props OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.BoilerHotWater', "For #{boiler_hot_water.name}, cannot find boiler properties, cannot apply efficiency standard.") successfully_set_all_properties = false return successfully_set_all_properties end # Make the EFFFPLR curve eff_fplr = model_add_curve(boiler_hot_water.model, blr_props['efffplr']) if eff_fplr boiler_hot_water.setNormalizedBoilerEfficiencyCurve(eff_fplr) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.BoilerHotWater', "For #{boiler_hot_water.name}, cannot find eff_fplr curve, will not be set.") successfully_set_all_properties = false end # Get the minimum efficiency standards thermal_eff = nil # If specified as AFUE unless blr_props['minimum_annual_fuel_utilization_efficiency'].nil? min_afue = blr_props['minimum_annual_fuel_utilization_efficiency'] thermal_eff = afue_to_thermal_eff(min_afue) new_comp_name = "#{boiler_hot_water.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_afue} AFUE" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{boiler_hot_water.name}: #{fuel_type} #{fluid_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; AFUE = #{min_afue}") end # If specified as thermal efficiency unless blr_props['minimum_thermal_efficiency'].nil? thermal_eff = blr_props['minimum_thermal_efficiency'] new_comp_name = "#{boiler_hot_water.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{thermal_eff} Thermal Eff" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{boiler_hot_water.name}: #{fuel_type} #{fluid_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Thermal Efficiency = #{thermal_eff}") end # If specified as combustion efficiency unless blr_props['minimum_combustion_efficiency'].nil? min_comb_eff = blr_props['minimum_combustion_efficiency'] thermal_eff = combustion_eff_to_thermal_eff(min_comb_eff) new_comp_name = "#{boiler_hot_water.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_comb_eff} Combustion Eff" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{boiler_hot_water.name}: #{fuel_type} #{fluid_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Combustion Efficiency = #{min_comb_eff}") end # Set the name boiler_hot_water.setName(new_comp_name) # Set the efficiency values unless thermal_eff.nil? boiler_hot_water.setNominalThermalEfficiency(thermal_eff) end return successfully_set_all_properties end
find search criteria
@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] hot water boiler object @return [Hash] used for standards_lookup_table(model)
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 503 def boiler_hot_water_find_search_criteria(boiler_hot_water) # Define the criteria to find the boiler properties # in the hvac standards data set. search_criteria = {} search_criteria['template'] = template # Get fuel type fuel_type = nil case boiler_hot_water.fuelType when 'NaturalGas' fuel_type = 'Gas' when 'Electricity' fuel_type = 'Electric' when 'FuelOilNo1', 'FuelOilNo2' fuel_type = 'Oil' else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.BoilerHotWater', "For #{boiler_hot_water.name}, a fuel type of #{fuel_type} is not yet supported. Assuming 'Gas.'") fuel_type = 'Gas' end search_criteria['fuel_type'] = fuel_type # Get the fluid type fluid_type = 'Hot Water' search_criteria['fluid_type'] = fluid_type return search_criteria end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1908 def check_boolean_value(value, varname) return true if value =~ /^(true|t|yes|y|1)$/i return false if value.empty? || value =~ /^(false|f|no|n|0)$/i raise ArgumentError, "invalid value for #{varname}: #{value}" end
This method checks if a zip file containing weather data is stored in an external directory. If it is, then it checks if the name of the zip file (without extension) matches the name of the desired epw file (without extension). If it does then it copies the zip file to the openstudio-standards weather directory and expands the file. Presumably the zip file contains the .ddy, .epw, and .stat files needed by the rest of BTAP
. If no appropriate weather zip files are present in the external directory then the method returns false. Arguments: epw_file (string): The name of the .epw file that BTAP
will use. weather_folder (string): The path to the openstudio-standards weather folder. custom_weather_folder (string): The path to the external folder presumably containing the weather data zip file.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2368 def check_datapoint_weather_folder(epw_file:, weather_folder:, custom_weather_folder: nil) # Check if the external weather directory exists and return false if there isn't one return false if custom_weather_folder.nil? # Check if there are any zip files in the external weather directory and return false if there isn't any. zip_search_term = File.join(custom_weather_folder.to_s, '*.zip') zip_files = Dir[zip_search_term] return false if zip_files.empty? # Look for a zip file in the external directory named after the .epw file. If there isn't one return false. weather_loc = epw_file[0..-5] weather_zip = weather_loc + '.zip' zip_find = zip_files.select{ |check_file | File.basename(check_file).to_s.downcase == weather_zip.to_s.downcase } return false if zip_find.empty? # Copy the zip file we want from the external weather directory to the openstudio-standards weather directory and # extract the weather data in the file. puts "Copying: #{zip_find[0]} from the btap_cli weather folder to the openstudio-standards weather folder: #{weather_folder}" FileUtils.cp(zip_find[0], weather_folder) dest_zip = File.join(weather_folder, weather_zip) # Return true if everything goes well. return extract_weather_data(zipped_file: dest_zip, weather_dir: weather_folder) end
Applies the standard efficiency ratings and typical performance curves to this object.
@return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 642 def chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs) # Define the criteria to find the chiller properties # in the hvac standards data set. search_criteria = chiller_electric_eir_find_search_criteria(chiller_electric_eir) cooling_type = search_criteria['cooling_type'] condenser_type = search_criteria['condenser_type'] compressor_type = search_criteria['compressor_type'] # Get the chiller capacity capacity_w = chiller_electric_eir_find_capacity(chiller_electric_eir) # All chillers must be modulating down to 25% of their capacity chiller_electric_eir.setChillerFlowMode('LeavingSetpointModulated') chiller_electric_eir.setMinimumPartLoadRatio(0.25) chiller_electric_eir.setMinimumUnloadingRatio(0.25) chiller_capacity = capacity_w # If the chiller name includes 'Primary' or 'Secondary' then apply NECB rules if (chiller_electric_eir.name.to_s.include? 'Primary') || (chiller_electric_eir.name.to_s.include? 'Secondary') if (capacity_w / 1000.0) < 2100.0 if chiller_electric_eir.name.to_s.include? 'Primary Chiller' chiller_capacity = capacity_w elsif chiller_electric_eir.name.to_s.include? 'Secondary Chiller' chiller_capacity = 0.001 end else chiller_capacity = capacity_w / 2.0 end end chiller_electric_eir.setReferenceCapacity(chiller_capacity) # Convert capacity to tons capacity_tons = OpenStudio.convert(chiller_capacity, 'W', 'ton').get # Get chiller compressor type if needed chiller_types = ['reciprocating','scroll','rotary screw','centrifugal'] chiller_name_has_type = chiller_types.any? {|type| chiller_electric_eir.name.to_s.downcase.include? type} unless chiller_name_has_type chlr_type_search_criteria = {} chlr_type_search_criteria['cooling_type'] = cooling_type chlr_types_table = @standards_data['chiller_types'] chlr_type_props = model_find_object(chlr_types_table, chlr_type_search_criteria, capacity_tons) unless chlr_type_props OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find chiller type information") successfully_set_all_properties = false return successfully_set_all_properties end compressor_type = chlr_type_props['compressor_type'] chiller_electric_eir.setName(chiller_electric_eir.name.to_s + ' ' + compressor_type) end # Get the chiller properties search_criteria['compressor_type'] = compressor_type chlr_table = @standards_data['chillers'] chlr_props = model_find_object(chlr_table, search_criteria, capacity_tons, Date.today) unless chlr_props OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find chiller properties, cannot apply standard efficiencies or curves.") successfully_set_all_properties = false return successfully_set_all_properties end # Make the CAPFT curve cool_cap_ft = model_add_curve(chiller_electric_eir.model, chlr_props['capft']) if cool_cap_ft chiller_electric_eir.setCoolingCapacityFunctionOfTemperature(cool_cap_ft) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_cap_ft curve, will not be set.") successfully_set_all_properties = false end # Make the EIRFT curve cool_eir_ft = model_add_curve(chiller_electric_eir.model, chlr_props['eirft']) if cool_eir_ft chiller_electric_eir.setElectricInputToCoolingOutputRatioFunctionOfTemperature(cool_eir_ft) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_eir_ft curve, will not be set.") successfully_set_all_properties = false end # Make the EIRFPLR curve # which may be either a CurveBicubic or a CurveQuadratic based on chiller type cool_plf_fplr = model_add_curve(chiller_electric_eir.model, chlr_props['eirfplr']) if cool_plf_fplr chiller_electric_eir.setElectricInputToCoolingOutputRatioFunctionOfPLR(cool_plf_fplr) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_plf_fplr curve, will not be set.") successfully_set_all_properties = false end # Set the efficiency value kw_per_ton = nil cop = nil if chlr_props['minimum_full_load_efficiency'] kw_per_ton = chlr_props['minimum_full_load_efficiency'] cop = kw_per_ton_to_cop(kw_per_ton) chiller_electric_eir.setReferenceCOP(cop) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find minimum full load efficiency, will not be set.") successfully_set_all_properties = false end # Set cooling tower properties now that the new COP of the chiller is set if chiller_electric_eir.name.to_s.include? 'Primary Chiller' # Single speed tower model assumes 25% extra for compressor power tower_cap = capacity_w * (1.0 + 1.0 / chiller_electric_eir.referenceCOP) if (tower_cap / 1000.0) < 1750 clg_tower_objs[0].setNumberofCells(1) else clg_tower_objs[0].setNumberofCells((tower_cap / (1000 * 1750) + 0.5).round) end clg_tower_objs[0].setFanPoweratDesignAirFlowRate(0.015 * tower_cap) end # Append the name with size and kw/ton chiller_electric_eir.setName("#{chiller_electric_eir.name} #{capacity_tons.round}tons #{kw_per_ton.round(1)}kW/ton") OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.ChillerElectricEIR', "For #{template}: #{chiller_electric_eir.name}: #{cooling_type} #{condenser_type} #{compressor_type} Capacity = #{capacity_tons.round}tons; COP = #{cop.round(1)} (#{kw_per_ton.round(1)}kW/ton)") return successfully_set_all_properties end
This method cleans the model of any existing HVAC systems and applies any desired ratation or scaling to the model.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 508 def clean_and_scale_model(model:, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil) # clean model.. BTAP::Resources::Envelope::remove_all_envelope_information(model) model = remove_all_hvac(model) model.getThermalZones.sort.each { |zone| zone.setUseIdealAirLoads(true) } model.getZoneHVACPackagedTerminalAirConditioners.each(&:remove) model.getCoilCoolingDXSingleSpeeds.each(&:remove) model.getZoneHVACBaseboardConvectiveWaters.each(&:remove) model.getAirLoopHVACZoneMixers.each(&:remove) model.getAirLoopHVACZoneSplitters.each(&:remove) model.getAirTerminalSingleDuctConstantVolumeNoReheats.each(&:remove) model.getWaterUseEquipmentDefinitions.each(&:remove) model.getWaterUseEquipments.each(&:remove) model.getWaterUseConnectionss.each(&:remove) model.getPumpConstantSpeeds.each(&:remove) model.getPumpVariableSpeeds.each(&:remove) model.getBoilerHotWaters.each(&:remove) model.getBoilerSteams.each(&:remove) model.getPlantLoops.each(&:remove) model.getSchedules.each(&:remove) model.getThermalZones.sort.each { |zone| zone.thermostat(&:remove) } model.getSpaces.sort.each { |space| space.designSpecificationOutdoorAir(&:remove) } model.getThermostatSetpointDualSetpoints.each(&:remove) scale_x = 1.0 scale_y = 1.0 scale_z = 1.0 # Rotate to model if requested rotation_degrees = convert_arg_to_f(variable: rotation_degrees, default: 0.0) BTAP::Geometry.rotate_building(model: model, degrees: rotation_degrees) unless rotation_degrees == 0.0 # Scale model if requested scale_x = convert_arg_to_f(variable: scale_x, default: 1.0) scale_y = convert_arg_to_f(variable: scale_y, default: 1.0) scale_z = convert_arg_to_f(variable: scale_z, default: 1.0) return unless scale_x != 1.0 || scale_y != 1.0 || scale_z != 1.0 BTAP::Geometry.scale_model(model, scale_x, scale_y, scale_z) end
Applies the standard efficiency ratings and typical performance curves to this object.
@return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 899 def coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map) successfully_set_all_properties = true model = coil_cooling_dx_multi_speed.model multi_speed_heat_pump = coil_cooling_dx_multi_speed.containingHVACComponent.get.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get airloop = multi_speed_heat_pump.airLoopHVAC.get # Define the criteria to find the properties in the hvac standards data set search_criteria = coil_dx_find_search_criteria(coil_cooling_dx_multi_speed) capacity_w = coil_cooling_dx_multi_speed_find_capacity(coil_cooling_dx_multi_speed) # Find design outside air flow rate and flow fraction controller_oa = nil if airloop.airLoopHVACOutdoorAirSystem.is_initialized oa_system = airloop.airLoopHVACOutdoorAirSystem.get controller_oa = oa_system.getControllerOutdoorAir end min_oa_flow_rate = 0.0 oaf = 0.0 if controller_oa min_oa_flow_rate = nil if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_rate = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized min_oa_flow_rate = controller_oa.autosizedMinimumOutdoorAirFlowRate.get end if min_oa_flow_rate then oaf = min_oa_flow_rate.to_f / airloop.autosizedDesignSupplyAirFlowRate.to_f end end # Find required capacity of each stage and total number of stages based on NECB rules # This implementation is limited to 4 stages only. The capacity of stages 1-3 is set to # 66 kW as stipulated by NECB. The capacity of the 4th stage is then allowed to exceed 66 kW # up to the design capacity. stage_cap = [] num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round max_cap = 66.0 * 1000.0 * num_stages final_num_stages = num_stages case num_stages when 1 stage_cap[0] = capacity_w / 2.0 stage_cap[1] = 2.0 * stage_cap[0] final_num_stages = 2 else stage_cap[0] = 66.0 * 1000.0 stage_cap[1] = 2.0 * stage_cap[0] case num_stages when 2 when 3 stage_cap[2] = 3.0 * stage_cap[0] else final_num_stages = 4 stage_cap[2] = 3.0 * stage_cap[0] stage_cap[3] = max_cap end end # Set final number of cooling stages and create missing stages if needed for istage in 2..final_num_stages - 1 new_clg_stage = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model) coil_cooling_dx_multi_speed.addStage(new_clg_stage) end multi_speed_heat_pump.setNumberofSpeedsforCooling(final_num_stages) # Set final capacities for each of the stages. The flow rate for each of the stages # is maintained above the outside air flow rate coil_cooling_dx_multi_speed.stages[0].setGrossRatedTotalCoolingCapacity(stage_cap[0]) coil_cooling_dx_multi_speed.stages[1].setGrossRatedTotalCoolingCapacity(stage_cap[1]) case coil_cooling_dx_multi_speed.stages.size when 2 if oaf > 0.5 then multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) end when 3 coil_cooling_dx_multi_speed.stages[2].setGrossRatedTotalCoolingCapacity(stage_cap[2]) if (oaf > 0.333) && (oaf <= 0.666) multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) elsif oaf > 0.666 multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) end when 4 coil_cooling_dx_multi_speed.stages[2].setGrossRatedTotalCoolingCapacity(stage_cap[2]) coil_cooling_dx_multi_speed.stages[3].setGrossRatedTotalCoolingCapacity(stage_cap[3]) if (oaf > 0.25) && (oaf <= 0.5) multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) elsif (oaf > 0.5) && (oaf <= 0.75) multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) elsif oaf > 0.75 multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed3SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) end end capacity_btu_per_hr = OpenStudio.convert(stage_cap.last, 'W', 'Btu/hr').get capacity_kbtu_per_hr = OpenStudio.convert(stage_cap.last, 'W', 'kBtu/hr').get # Lookup efficiencies depending on whether it is a unitary AC or a heat pump ac_props = nil ac_props = if coil_dx_heat_pump?(coil_cooling_dx_multi_speed) model_find_object(standards_data['heat_pumps'], search_criteria, capacity_btu_per_hr, Date.today) else model_find_object(standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) end # Check to make sure properties were found if ac_props.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find efficiency info, cannot apply efficiency standard.") successfully_set_all_properties = false return sql_db_vars_map end # get clg stages clg_stages = coil_cooling_dx_multi_speed.stages # Make the COOL-CAP-FT curve cool_cap_ft = model_add_curve(model, ac_props['cool_cap_ft']) if cool_cap_ft clg_stages.sort.each do |stage| stage.setTotalCoolingCapacityFunctionofTemperatureCurve(cool_cap_ft) end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_cap_ft curve, will not be set.") successfully_set_all_properties = false return sql_db_vars_map end # Make the COOL-CAP-FFLOW curve cool_cap_fflow = model_add_curve(model, ac_props['cool_cap_fflow']) if cool_cap_fflow clg_stages.sort.each do |stage| stage.setTotalCoolingCapacityFunctionofFlowFractionCurve(cool_cap_fflow) end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_cap_fflow curve, will not be set.") successfully_set_all_properties = false return sql_db_vars_map end # Make the COOL-EIR-FT curve cool_eir_ft = model_add_curve(model, ac_props['cool_eir_ft']) if cool_eir_ft clg_stages.sort.each do |stage| stage.setEnergyInputRatioFunctionofTemperatureCurve(cool_eir_ft) end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_eir_ft curve, will not be set.") successfully_set_all_properties = false return sql_db_vars_map end # Make the COOL-EIR-FFLOW curve cool_eir_fflow = model_add_curve(model, ac_props['cool_eir_fflow']) if cool_eir_fflow clg_stages.sort.each do |stage| stage.setEnergyInputRatioFunctionofFlowFractionCurve(cool_eir_fflow) end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_eir_fflow curve, will not be set.") successfully_set_all_properties = false return sql_db_vars_map end # Make the COOL-PLF-FPLR curve cool_plf_fplr = model_add_curve(model, ac_props['cool_plf_fplr']) if cool_plf_fplr clg_stages.sort.each do |stage| stage.setPartLoadFractionCorrelationCurve(cool_plf_fplr) end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_plf_fplr curve, will not be set.") successfully_set_all_properties = false return sql_db_vars_map end # Set the COP values cop, new_comp_name = coil_cooling_dx_multi_speed_standard_minimum_cop(coil_cooling_dx_multi_speed) unless cop.nil? clg_stages.sort.each do |curr_istage| curr_istage.setGrossRatedCoolingCOP(cop) end end sql_db_vars_map[new_comp_name] = coil_cooling_dx_multi_speed.name.to_s coil_cooling_dx_multi_speed.setName(new_comp_name) # It was found that the heat pump OS object doesn't respond to the call to turn on from the # system availability manager night cycle. This EMS script is then implemented to check the status # of the system availability manager night cycle and force the heat pump to turn on when needed. The # heat pump is still turned on when its availability schedule calls for it. create_ems_to_turn_on_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_for_night_cycle(multi_speed_heat_pump) return sql_db_vars_map end
NECB reference heat pump system heating type rules need to be flexible to account for
-
DX htg/cooling + gas supplement htg
-
Potential lack of AirLoopHVACUnitaryHeatPumpAirToAir or AirLoopHVACUnitarySystem
@param necb_reference_hp [Boolean] if true, NECB reference model rules for heat pumps will be used.
CoilDX#coil_dx_heating_type
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2435 def coil_dx_heating_type(coil_dx, necb_reference_hp = false) supp_htg_type = nil # If not heat pump reference case use the standard implementation. if !necb_reference_hp return super(coil_dx) else if coil_dx.airLoopHVAC.empty? if coil_dx.containingHVACComponent.is_initialized containing_comp = coil_dx.containingHVACComponent.get if containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized supp_htg_coil = containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get.supplementalHeatingCoil if supp_htg_coil.to_CoilHeatingElectric.is_initialized supp_htg_type = 'Electric Resistance or None' elsif supp_htg_coil.to_CoilHeatingGas.is_initialized or supp_htg_coil.to_CoilHeatingWater.is_initialized supp_htg_type = 'All Other' else # None supp_htg_type = 'Electric Resistance or None' end else # For other virtual wrapper, use method in Standard.DXCoil # Or add future wrappers here return super end end elsif coil_dx.airLoopHVAC.is_initialized # Heat pumps without a wrapper (lone DX coils in the air loop) airloop = coil_dx.airLoopHVAC.get num_of_DX_Coils = 0 num_of_supp_coils = 0 supp_htg_type = '' # Go through and determine number of each type of coils in air loop to determine supp_htg_type airloop.supplyComponents.each do |supply_component| if supply_component.to_CoilHeatingDXSingleSpeed.is_initialized or supply_component.to_CoilHeatingDXMultiSpeed.is_initialized supply_component.to_CoilHeatingDXVariableSpeed.is_initialized num_of_DX_Coils = num_of_DX_Coils + 1 elsif supply_component.to_CoilCoolingDXSingleSpeed.is_initialized or supply_component.to_CoilCoolingDXTwoSpeed.is_initialized or supply_component.to_CoilCoolingDXTwoSpeed.is_initialized or supply_component.to_CoilCoolingDXVariableSpeed.is_initialized or supply_component.to_CoilCoolingDXMultiSpeed.is_initialized or supply_component.to_CoilCoolingDXCurveFitPerformance.is_initialized or supply_component.to_CoilCoolingDXTwoStageWithHumidityControlMode.is_initialized num_of_DX_Coils = num_of_DX_Coils + 1 elsif supply_component.to_CoilHeatingGas.is_initialized or supply_component.to_CoilHeatingGasMultiStage.is_initialized or supply_component.to_CoilHeatingWater.is_initialized num_of_supp_coils = num_of_supp_coils + 1 supp_htg_type = 'All Other' elsif supply_component.to_CoilHeatingElectric.is_initialized num_of_supp_coils = num_of_supp_coils + 1 supp_htg_type = 'Electric Resistance or None' end end #Two possible heat pump configuration if num_of_DX_Coils == 2 && num_of_supp_coils == 1 #Scenario 1: 1 DX htg + 1 DX clg + 1 Non-DX htg coil puts "scenario 1 supp_htg_type #{supp_htg_type}" return supp_htg_type # return supplmental heating type else #Scenario 2: num_of_DX_Coils < 2 or num_of_supp_coils = 0; puts "scenario 2 supp_htg_type #{supp_htg_type}" puts "num_of_DX_Coils #{num_of_DX_Coils}" puts "num_of_supp_coils #{num_of_supp_coils}" return supp_htg_type = 'Electric Resistance or None' end end end end
Standard#coil_heating_dx_single_speed_find_capacity
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2342 def coil_heating_dx_single_speed_find_capacity(coil_heating_dx_single_speed, necb_reference_hp = false) # Set Rated heating capacity = 50% cooling coil capacity at -8.3 C outdoor [8.4.4.13 (2)(c)] if necb_reference_hp #NECB reference heat pump rules apply # grab paired cooling coil if coil_heating_dx_single_speed.airLoopHVAC.empty? if coil_heating_dx_single_speed.containingHVACComponent.is_initialized containing_comp = coil_heating_dx_single_speed.containingHVACComponent.get if containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized clg_coil = containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get.coolingCoil elsif containing_comp.to_AirLoopHVACUnitarySystem.is_initialized unitary = containing_comp.to_AirLoopHVACUnitarySystem.get if unitary.coolingCoil.is_initialized clg_coil = unitary.coolingCoil.get end end # @todo Add other unitary systems elsif coil_heating_dx_single_speed.containingZoneHVACComponent.is_initialized containing_comp = coil_heating_dx_single_speed.containingZoneHVACComponent.get # PTHP if containing_comp.to_ZoneHVACPackagedTerminalHeatPump.is_initialized pthp = containing_comp.to_ZoneHVACPackagedTerminalHeatPump.get clg_coil = containing_comp.to_ZoneHVACPackagedTerminalHeatPump.get.coolingCoil end end elsif coil_heating_dx_single_speed.airLoopHVAC.is_initialized air_loop = coil_heating_dx_single_speed.airLoopHVAC.get # Check for the presence of any other type of cooling coil clg_types = ['OS:Coil:Cooling:DX:SingleSpeed', 'OS:Coil:Cooling:DX:TwoSpeed', 'OS:Coil:Cooling:DX:MultiSpeed'] clg_types.each do |ct| coils = air_loop.supplyComponents(ct.to_IddObjectType) next if coils.empty? clg_coil = coils[0] puts "coils = air_loop.supplyComponents(ct.to_IddObjectType) #{}" break # Stop on first DX cooling coil found end end # Paired cooling coil parameters clg_coil = clg_coil.to_CoilCoolingDXSingleSpeed.get capacity_w = coil_cooling_dx_single_speed_find_capacity(clg_coil) indoor_wb = 19.4 #rated indoor wb outdoor_db = -8.3 # outdoor db # heating capacity = capacity factor (function of temp) from biquadratic curve # with curve limits on minimum y/outdoor db (no extrapolation) cooling_cap_f_temp_curve = clg_coil.totalCoolingCapacityFunctionOfTemperatureCurve cooling_cap_f_temp_factor_min_y = cooling_cap_f_temp_curve.evaluate(indoor_wb,outdoor_db) htg_cap_w_min_y = capacity_w*0.5*cooling_cap_f_temp_factor_min_y # heating capacity = capacity factor (function of temp) from biquadratic curve # without curve limits on minimum y/outdoor db (extrapolate) cooling_cap_f_temp_const = 0.867905 cooling_cap_f_temp_x = 0.0142459 cooling_cap_f_temp_x2 = 0.00055436 cooling_cap_f_temp_y = -0.0075575 cooling_cap_f_temp_y2 = 3.3e-05 cooling_cap_f_temp_xy = -0.0001918 cooling_cap_f_temp_factor_no_min_y = cooling_cap_f_temp_const + cooling_cap_f_temp_x*indoor_wb + cooling_cap_f_temp_x2*indoor_wb**2 + cooling_cap_f_temp_y*outdoor_db + cooling_cap_f_temp_y2*outdoor_db**2 + cooling_cap_f_temp_xy*indoor_wb*outdoor_db htg_cap_w_no_min_y = capacity_w*0.5*cooling_cap_f_temp_factor_no_min_y puts "capacity_w #{capacity_w}" puts "cooling_cap_f_temp_factor_no_min_y #{cooling_cap_f_temp_factor_no_min_y}" puts "cooling_cap_f_temp_factor_min_y #{cooling_cap_f_temp_factor_min_y}" puts "htg_cap_w_no_min_y #{htg_cap_w_no_min_y}" puts "htg_cap_w_min_y #{htg_cap_w_min_y}" # use actual factor from -8.3 to compute rated heating capacity unless it's < 0 if cooling_cap_f_temp_factor_no_min_y>0 htg_cap_w = htg_cap_w_no_min_y else htg_cap_w = htg_cap_w_min_y end # Hardsize rated capacity of heating coil coil_heating_dx_single_speed.setRatedTotalHeatingCapacity(htg_cap_w) return htg_cap_w else # Do not follow NECB reference HP rule; proceed as usual return super(coil_heating_dx_single_speed) end end
Applies the standard efficiency ratings and typical performance curves to this object.
@return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 858 def coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas) successfully_set_all_properties = true # Define the search criteria search_criteria = coil_heating_gas_find_search_criteria # Get the coil capacity capacity_w = coil_heating_gas_find_capacity(coil_heating_gas) capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get # lookup properties coil_table = @standards_data['furnaces'] coil_props = model_find_object(coil_table, search_criteria, [capacity_btu_per_hr, 0.001].max, Date.today) # Check to make sure properties were found if coil_props.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name}, cannot find efficiency info using #{search_criteria}, cannot apply efficiency standard.") successfully_set_all_properties = false end # Make the plf vs plr curve plffplr_curve = model_add_curve(coil_heating_gas.model, coil_props['efffplr']) if plffplr_curve coil_heating_gas.setPartLoadFractionCorrelationCurve(plffplr_curve) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name}, cannot find plffplr curve, will not be set.") successfully_set_all_properties = false end # Thermal efficiency thermal_eff = coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas) # Set the efficiency values coil_heating_gas.setGasBurnerEfficiency(thermal_eff.to_f) return successfully_set_all_properties end
find furnace capacity
@return [Hash] used for standards_lookup_table(model)
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 776 def coil_heating_gas_find_capacity(coil_heating_gas) # Get the coil capacity capacity_w = nil if coil_heating_gas.nominalCapacity.is_initialized capacity_w = coil_heating_gas.nominalCapacity.get elsif coil_heating_gas.autosizedNominalCapacity.is_initialized capacity_w = coil_heating_gas.autosizedNominalCapacity.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name} capacity is not available, cannot apply efficiency standard.") successfully_set_all_properties = false return successfully_set_all_properties end return capacity_w end
find search criteria
@return [Hash] used for standards_lookup_table(model)
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 763 def coil_heating_gas_find_search_criteria # Define the criteria to find the furnace properties # in the hvac standards data set. search_criteria = {} search_criteria['fluid_type'] = 'Air' search_criteria['fuel_type'] = 'Gas' return search_criteria end
Applies the standard efficiency ratings and typical performance curves to this object.
@return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1147 def coil_heating_gas_multi_stage_apply_efficiency_and_curves(coil_heating_gas_multi_stage) successfully_set_all_properties = true model = coil_heating_gas_multi_stage.model # get multi speed heat pump and air loop multi_speed_heat_pump = nil multi_speed_heat_pumps = model.getAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds multi_speed_heat_pumps.sort.each do |iheat_pump| htg_coil = iheat_pump.heatingCoil if htg_coil.name.to_s.strip == coil_heating_gas_multi_stage.name.to_s.strip multi_speed_heat_pump = iheat_pump break end end airloop = multi_speed_heat_pump.airLoopHVAC.get # Define the criteria to find the properties in the hvac standards data set. search_criteria = coil_heating_gas_multi_stage_find_search_criteria(coil_heating_gas_multi_stage) fuel_type = search_criteria['fuel_type'] capacity_w = coil_heating_gas_multi_stage_find_capacity(coil_heating_gas_multi_stage) # Find system design outside air flow rate and fraction controller_oa = nil if airloop.airLoopHVACOutdoorAirSystem.is_initialized oa_system = airloop.airLoopHVACOutdoorAirSystem.get controller_oa = oa_system.getControllerOutdoorAir end min_oa_flow_rate = 0.0 oaf = 0.0 if controller_oa min_oa_flow_rate = nil if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_rate = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized min_oa_flow_rate = controller_oa.autosizedMinimumOutdoorAirFlowRate.get end if min_oa_flow_rate then oaf = min_oa_flow_rate.to_f / airloop.autosizedDesignSupplyAirFlowRate.to_f end end # Find capacities of each of the stages and the total number of stages required based on NECB rules. # This implementation is limited to 4 stages. The capacity of stages 1-3 is set to 66 kW as stipulated # by NECB. The capacity of the 4th stage can exceed 66 kW up to the design capacity. htg_stages = coil_heating_gas_multi_stage.stages num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round max_cap = 66.0 * 1000.0 * num_stages stage_cap = [] final_num_stages = num_stages if capacity_w == 0.001 final_num_stages = 1 stage_cap[0] = capacity_w else case num_stages when 1 stage_cap[0] = capacity_w / 2.0 stage_cap[1] = 2.0 * stage_cap[0] final_num_stages = 2 else stage_cap[0] = 66.0 * 1000.0 stage_cap[1] = 2.0 * stage_cap[0] case num_stages when 2 when 3 stage_cap[2] = 3.0 * stage_cap[0] else final_num_stages = 4 stage_cap[2] = 3.0 * stage_cap[0] stage_cap[3] = max_cap end end end # Set final number of stages and create missing stages if needed for istage in 1..final_num_stages - 1 new_htg_stage = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model) coil_heating_gas_multi_stage.addStage(new_htg_stage) end multi_speed_heat_pump.setNumberofSpeedsforHeating(final_num_stages) # Set final capacities for each of the stages. The air flow rate for each of the stages # is maintained above the outside air flow rate coil_heating_gas_multi_stage.stages[0].setNominalCapacity(stage_cap[0]) case coil_heating_gas_multi_stage.stages.size when 2 coil_heating_gas_multi_stage.stages[1].setNominalCapacity(stage_cap[1]) if oaf > 0.5 then multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) end when 3 coil_heating_gas_multi_stage.stages[1].setNominalCapacity(stage_cap[1]) coil_heating_gas_multi_stage.stages[2].setNominalCapacity(stage_cap[2]) if (oaf > 0.333) && (oaf <= 0.666) multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) elsif oaf > 0.666 multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) end when 4 coil_heating_gas_multi_stage.stages[1].setNominalCapacity(stage_cap[1]) coil_heating_gas_multi_stage.stages[2].setNominalCapacity(stage_cap[2]) coil_heating_gas_multi_stage.stages[3].setNominalCapacity(stage_cap[3]) if (oaf > 0.25) && (oaf <= 0.5) multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) elsif (oaf > 0.5) && (oaf <= 0.75) multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) elsif oaf > 0.75 multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) multi_speed_heat_pump.setSpeed3SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) end end # Convert capacity to Btu/hr capacity_btu_per_hr = OpenStudio.convert(stage_cap.last, 'W', 'Btu/hr').get capacity_kbtu_per_hr = OpenStudio.convert(stage_cap.last, 'W', 'kBtu/hr').get # Lookup efficiencies heater_props = nil heater_props = model_find_object(standards_data['furnaces'], search_criteria, capacity_btu_per_hr, Date.today) # Check to make sure properties were found if heater_props.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGasMultiSpeed', "For #{coil_heating_gas_multi_stage.name}, cannot find efficiency info, cannot apply efficiency standard.") successfully_set_all_properties = false return successfully_set_all_properties end # Make the EFFPLR curve efffplr = model_add_curve(coil_heating_gas_multi_stage.model, heater_props['efffplr']) if efffplr coil_heating_gas_multi_stage.setPartLoadFractionCorrelationCurve(efffplr) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGasMultiStage', "For #{coil_heating_gas_multi_stage.name}, cannot find efffplr curve, will not be set.") successfully_set_all_properties = false return successfully_set_all_properties end # Get the minimum efficiency standards thermal_eff = nil # If specified as AFUE unless heater_props['minimum_annual_fuel_utilization_efficiency'].nil? min_afue = heater_props['minimum_annual_fuel_utilization_efficiency'] thermal_eff = afue_to_thermal_eff(min_afue) new_comp_name = "#{coil_heating_gas_multi_stage.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_afue} AFUE" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilHeatingGasMultiStage', "For #{template}: #{coil_heating_gas_multi_stage.name}: #{fuel_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; AFUE = #{min_afue}") end # If specified as thermal efficiency unless heater_props['minimum_thermal_efficiency'].nil? thermal_eff = heater_props['minimum_thermal_efficiency'] new_comp_name = "#{coil_heating_gas_multi_stage.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{thermal_eff} Thermal Eff" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilHeatingGasMultiStage', "For #{template}: #{coil_heating_gas_multi_stage.name}: #{fuel_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Thermal Efficiency = #{thermal_eff}") end # If specified as combustion efficiency unless heater_props['minimum_combustion_efficiency'].nil? min_comb_eff = heater_props['minimum_combustion_efficiency'] thermal_eff = combustion_eff_to_thermal_eff(min_comb_eff) new_comp_name = "#{coil_heating_gas_multi_stage.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_comb_eff} Combustion Eff" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{coil_heating_gas_multi_stage.name}: #{fuel_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Combustion Efficiency = #{min_comb_eff}") end coil_heating_gas_multi_stage.setName(new_comp_name) # Set the name coil_heating_gas_multi_stage.setName(new_comp_name) # Get heating stages htg_stages = coil_heating_gas_multi_stage.stages # Set the efficiency values unless thermal_eff.nil? htg_stages.sort.each do |stage| stage.setGasBurnerEfficiency(thermal_eff) end end return successfully_set_all_properties end
Finds lookup object in standards and return minimum thermal efficiency
@return [Double] minimum thermal efficiency
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 795 def coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas, rename = false) # Get the coil properties search_criteria = coil_heating_gas_find_search_criteria capacity_w = coil_heating_gas_find_capacity(coil_heating_gas) capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get # Get the minimum efficiency standards thermal_eff = nil # Get the coil properties coil_table = @standards_data['furnaces'] coil_props = model_find_object(coil_table, search_criteria, [capacity_btu_per_hr, 0.001].max) unless coil_props OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name}, cannot find coil props, cannot apply efficiency standard.") successfully_set_all_properties = false return successfully_set_all_properties end # New name initial value new_comp_name = coil_heating_gas.name # If specified as AFUE unless coil_props['minimum_annual_fuel_utilization_efficiency'].nil? min_afue = coil_props['minimum_annual_fuel_utilization_efficiency'] thermal_eff = afue_to_thermal_eff(min_afue) new_comp_name = "#{coil_heating_gas.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_afue} AFUE" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilHeatingGas', "For #{template}: #{coil_heating_gas.name}: Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; AFUE = #{min_afue}") end # If specified as thermal efficiency unless coil_props['minimum_thermal_efficiency'].nil? thermal_eff = coil_props['minimum_thermal_efficiency'] new_comp_name = "#{coil_heating_gas.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{thermal_eff} Thermal Eff" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilHeatingGas', "For #{template}: #{coil_heating_gas.name}: Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Thermal Efficiency = #{thermal_eff}") end # If specified as combustion efficiency unless coil_props['minimum_combustion_efficiency'].nil? min_comb_eff = coil_props['minimum_combustion_efficiency'] thermal_eff = combustion_eff_to_thermal_eff(min_comb_eff) new_comp_name = "#{coil_heating_gas.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_comb_eff} Combustion Eff" OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilHeatingGas', "For #{template}: #{coil_heating_gas.name}: Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Combustion Efficiency = #{min_comb_eff}") end unless thermal_eff OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{CoilHeatingGas.name}, cannot find coil efficiency, cannot apply efficiency standard.") successfully_set_all_properties = false return successfully_set_all_properties end # Rename if rename coil_heating_gas.setName(new_comp_name) end return thermal_eff end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2112 def common_air_loop(model:, system_data:) mau_air_loop = OpenStudio::Model::AirLoopHVAC.new(model) mau_air_loop.setName(system_data[:name]) air_loop_sizing = mau_air_loop.sizingSystem air_loop_sizing.autosizeDesignOutdoorAirFlowRate air_loop_sizing.setPreheatDesignTemperature(system_data[:PreheatDesignTemperature]) unless system_data[:PreheatDesignTemperature].nil? air_loop_sizing.setPreheatDesignHumidityRatio(system_data[:PreheatDesignHumidityRatio]) unless system_data[:PreheatDesignHumidityRatio].nil? air_loop_sizing.setPrecoolDesignTemperature(system_data[:PrecoolDesignTemperature]) unless system_data[:PrecoolDesignTemperature].nil? air_loop_sizing.setPrecoolDesignHumidityRatio(system_data[:PrecoolDesignHumidityRatio]) unless system_data[:PrecoolDesignHumidityRatio].nil? air_loop_sizing.setSizingOption(system_data[:SizingOption]) unless system_data[:SizingOption].nil? air_loop_sizing.setCoolingDesignAirFlowMethod(system_data[:CoolingDesignAirFlowMethod]) unless system_data[:CoolingDesignAirFlowMethod].nil? air_loop_sizing.setCoolingDesignAirFlowRate(system_data[:CoolingDesignAirFlowRate]) unless system_data[:CoolingDesignAirFlowRate].nil? air_loop_sizing.setHeatingDesignAirFlowMethod(system_data[:HeatingDesignAirFlowMethod]) unless system_data[:HeatingDesignAirFlowMethod].nil? air_loop_sizing.setHeatingDesignAirFlowRate(system_data[:HeatingDesignAirFlowRate]) unless system_data[:HeatingDesignAirFlowRate].nil? air_loop_sizing.setSystemOutdoorAirMethod(system_data[:SystemOutdoorAirMethod]) unless system_data[:SystemOutdoorAirMethod].nil? air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(system_data[:CentralCoolingDesignSupplyAirHumidityRatio]) unless system_data[:CentralCoolingDesignSupplyAirHumidityRatio].nil? air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(system_data[:CentralHeatingDesignSupplyAirHumidityRatio]) unless system_data[:CentralHeatingDesignSupplyAirHumidityRatio].nil? air_loop_sizing.setTypeofLoadtoSizeOn(system_data[:TypeofLoadtoSizeOn]) unless system_data[:TypeofLoadtoSizeOn].nil? air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(system_data[:CentralCoolingDesignSupplyAirTemperature]) unless system_data[:CentralCoolingDesignSupplyAirTemperature].nil? air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(system_data[:CentralHeatingDesignSupplyAirTemperature]) unless system_data[:CentralHeatingDesignSupplyAirTemperature].nil? air_loop_sizing.setAllOutdoorAirinCooling(system_data[:AllOutdoorAirinCooling]) unless system_data[:AllOutdoorAirinCooling].nil? air_loop_sizing.setAllOutdoorAirinHeating(system_data[:AllOutdoorAirinHeating]) unless system_data[:AllOutdoorAirinHeating].nil? if model.version < OpenStudio::VersionString.new('2.7.0') air_loop_sizing.setMinimumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio]) unless system_data[:MinimumSystemAirFlowRatio].nil? else air_loop_sizing.setCentralHeatingMaximumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio]) unless system_data[:MinimumSystemAirFlowRatio].nil? end return mau_air_loop end
This method converts arguments to bool. Anything other than a bool false or string âfalseâ is converted to a bool true. Bool false and case insesitive string false are turned into bool false.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 28 def convert_arg_to_bool(variable:, default:) return default if variable.nil? if variable.is_a? String return default if variable.to_s.downcase == 'necb_default' return false if variable.to_s.downcase == 'false' return true if variable.to_s.downcase == 'true' end return false if variable == false return variable end
This is a helper method to convert arguments that may support âNECB_Default, and nils to convert to floatâ
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 17 def convert_arg_to_f(variable:, default:) return variable if variable.kind_of?(Numeric) return default if variable.nil? || (variable.to_s == 'NECB_Default') return unless variable.kind_of?(String) variable = variable.strip return variable.to_f end
This method checks if a variable is a string. If it is anything but a string it returns the default. If it is a string set to âNECB_Defaultâ it return the default. Otherwise it returns the strirng set to it.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 41 def convert_arg_to_string(variable:, default:) return default if variable.nil? if variable.is_a? String return default if variable.to_s.downcase == 'necb_default' return variable end return default end
2019-05-23 ckirney This is an ugly, disgusting, hack (hence the name) that I dreamed out so that we could quickly and easily finish the merge from the nrcan branch (using OS 2.6.0) to master (using OS 2.8.0). This must be revised and a more elegant solution found.
This method takes everything in the @standards_data hash and adds it to the main @standards_data hash. This was done because other contributors insist on using the âmodel_find_objectâ method which is passed a hash and some search criteria. The âmodel_find_objectsâ then looks through the hash to information matching the search criteria. NECB standards assumes that the âstandards_lookup_table_firstâ method is used. This does basically the some thing as âmodel_find_objectsâ only it assumes that you are looking in the standards hash and you tell it which table in the standards hash to look for.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1323 def corrupt_standards_database @standards_data['tables'].each do |table| @standards_data[table[0]] = table[1]['table'] end end
Generates the base data hash mainly used to perform qaqc.
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 183 def create_base_data(model) # construct command with local libs os_version = OpenStudio.openStudioLongVersion eplus_version = OpenStudio.energyPlusVersion puts "\n\n\nOS_version is [#{os_version.strip}]" puts "\n\n\nEP_version is [#{eplus_version.strip}]" # Ensure all surfaces are unique. surfaces = model.getSurfaces.sort # Sort surfaces by type interior_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Surface', 'Adiabatic']) interior_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(interior_surfaces, 'Floor') outdoor_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, 'Outdoors') outdoor_walls = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'Wall') outdoor_roofs = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'RoofCeiling') outdoor_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'Floor') outdoor_subsurfaces = outdoor_surfaces.flat_map(&:subSurfaces) ground_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Ground', 'Foundation']) ground_walls = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'Wall') ground_roofs = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'RoofCeiling') ground_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'Floor') windows = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['FixedWindow', 'OperableWindow']) skylights = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['Skylight', 'TubularDaylightDiffuser', 'TubularDaylightDome']) doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['Door', 'GlassDoor']) overhead_doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['OverheadDoor']) # Peaks electric_peak = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters'" \ " AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Electricity' AND RowName='Electricity:Facility'" \ " AND ColumnName='Electricity Maximum Value' AND Units='W'") natural_gas_peak = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters'" \ " AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Natural Gas' AND RowName='NaturalGas:Facility'" \ " AND ColumnName='Natural Gas Maximum Value' AND Units='W'") get_sql_tables_to_json(model) # Create hash to store all the collected data. qaqc = {} qaqc[:sql_data] = get_sql_tables_to_json(model) error_warning = [] qaqc[:os_standards_revision] = OpenstudioStandards.git_revision qaqc[:os_standards_version] = OpenstudioStandards::VERSION qaqc[:openstudio_version] = os_version.strip qaqc[:energyplus_version] = eplus_version.strip # Store Building data. qaqc[:building] = {} qaqc[:building][:name] = model.building.get.name.get qaqc[:building][:conditioned_floor_area_m2] = nil if model.building.get.conditionedFloorArea.empty? error_warning << "model.building.get.conditionedFloorArea() is empty for #{model.building.get.name.get}" else qaqc[:building][:conditioned_floor_area_m2] = model.building.get.conditionedFloorArea.get end qaqc[:building][:exterior_area_m2] = model.building.get.exteriorSurfaceArea # m2 qaqc[:building][:volume] = model.building.get.airVolume # m3 qaqc[:building][:number_of_stories] = model.getBuildingStorys.size qaqc[:building][:standards_number_of_stories] = nil qaqc[:building][:standards_number_of_stories] = model.building.get.standardsNumberOfStories.get unless model.building.get.standardsNumberOfStories.empty? qaqc[:building][:standards_number_of_above_ground_stories] = nil qaqc[:building][:standards_number_of_above_ground_stories] = model.building.get.standardsNumberOfAboveGroundStories.get unless model.building.get.standardsNumberOfAboveGroundStories.empty? qaqc[:building][:standards_number_of_living_units] = nil qaqc[:building][:standards_number_of_living_units] = model.building.get.standardsNumberOfLivingUnits.get unless model.building.get.standardsNumberOfLivingUnits.empty? qaqc[:building][:nominal_floor_to_ceiling_height] = nil qaqc[:building][:nominal_floor_to_ceiling_height] = model.building.get.nominalFloortoCeilingHeight.get unless model.building.get.nominalFloortoCeilingHeight.empty? qaqc[:building][:nominal_floor_to_floor_height] = nil qaqc[:building][:nominal_floor_to_floor_height] = model.building.get.nominalFloortoFloorHeight.get unless model.building.get.nominalFloortoFloorHeight.empty? # Store Geography Data qaqc[:geography] = {} qaqc[:geography][:hdd_necb] = get_necb_hdd18(model: model, necb_hdd: true) qaqc[:geography][:hdd] = get_necb_hdd18(model: model, necb_hdd: false) weather_file_path = model.weatherFile.get.path.get.to_s stat_file_path = weather_file_path.gsub('.epw', '.stat') stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) qaqc[:geography][:cdd] = stat_file.cdd18 qaqc[:geography][:climate_zone] = NECB2011.new.get_climate_zone_name(qaqc[:geography][:hdd]) qaqc[:geography][:city] = model.getWeatherFile.city qaqc[:geography][:state_province_region] = model.getWeatherFile.stateProvinceRegion qaqc[:geography][:country] = model.getWeatherFile.country qaqc[:geography][:latitude] = model.getWeatherFile.latitude qaqc[:geography][:longitude] = model.getWeatherFile.longitude # Spacetype Breakdown qaqc[:spacetype_area_breakdown] = {} model.getSpaceTypes.sort.each do |spaceType| next if spaceType.floorArea == 0 # data for space type breakdown display = spaceType.name.get floor_area_si = 0 # loop through spaces so I can skip if not included in floor area spaceType.spaces.sort.each do |space| next if !space.partofTotalFloorArea floor_area_si += space.floorArea * space.multiplier end qaqc[:spacetype_area_breakdown][spaceType.name.get.gsub(/\s+/, '_').downcase.to_sym] = floor_area_si end # Economics Section qaqc[:economics] = {} provinces_names_map = { 'QC' => 'Quebec', 'NL' => 'Newfoundland and Labrador', 'NS' => 'Nova Scotia', 'PE' => 'Prince Edward Island', 'ON' => 'Ontario', 'MB' => 'Manitoba', 'SK' => 'Saskatchewan', 'AB' => 'Alberta', 'BC' => 'British Columbia', 'YT' => 'Yukon', 'NT' => 'Northwest Territories', 'NB' => 'New Brunswick', 'NU' => 'Nunavut' } neb_prices_csv_file_name = "#{File.dirname(__FILE__)}/qaqc_resources/neb_end_use_prices.csv" puts neb_prices_csv_file_name building_type = 'Commercial' province = provinces_names_map[qaqc[:geography][:state_province_region]] neb_fuel_list = ['Electricity', 'Natural Gas', 'Oil'] neb_eplus_fuel_map = { 'Electricity' => 'Electricity', 'Natural Gas' => 'Gas', 'Oil' => 'FuelOilNo2' } qaqc[:economics][:total_neb_cost] = 0.0 qaqc[:economics][:total_neb_cost_per_m2] = 0.0 neb_eplus_fuel_map.each do |neb_fuel, ep_fuel| search_info = { 0 => building_type, 1 => province, 2 => neb_fuel } row = look_up_csv_data(neb_prices_csv_file_name, search_info) neb_fuel_cost = row['2020'] fuel_consumption_gj = 0.0 if neb_fuel == 'Electricity' || neb_fuel == 'Natural Gas' if model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - #{ep_fuel}' AND RowName='#{ep_fuel}:Facility' AND ColumnName='#{ep_fuel} Annual Value' AND Units='GJ'").is_initialized fuel_consumption_gj = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - #{ep_fuel}' AND RowName='#{ep_fuel}:Facility' AND ColumnName='#{ep_fuel} Annual Value' AND Units='GJ'").get end else if model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Other' AND RowName='#{ep_fuel}:Facility' AND ColumnName='Annual Value' AND Units='GJ'").is_initialized fuel_consumption_gj = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Other' AND RowName='#{ep_fuel}:Facility' AND ColumnName='Annual Value' AND Units='GJ'").get end end qaqc[:economics][:"#{neb_fuel}_neb_cost"] = fuel_consumption_gj * neb_fuel_cost.to_f qaqc[:economics][:"#{neb_fuel}_neb_cost_per_m2"] = qaqc[:economics][:"#{neb_fuel}_neb_cost"] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty? qaqc[:economics][:total_neb_cost] += qaqc[:economics][:"#{neb_fuel}_neb_cost"] qaqc[:economics][:total_neb_cost_per_m2] += qaqc[:economics][:"#{neb_fuel}_neb_cost_per_m2"] || 0.0 end # Fuel cost based local utility rates costing_rownames = model.sqlFile.get.execAndReturnVectorOfString("SELECT RowName FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost'") #==> ["Electricity", "Natural Gas", "Additional", "Total"] costing_rownames = validate_optional(costing_rownames, model, 'N/A') if costing_rownames != 'N/A' costing_rownames.each do |rowname| case rowname when 'Electricity' qaqc[:economics][:electricity_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get qaqc[:economics][:electricity_cost_per_m2] = qaqc[:economics][:electricity_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty? when 'Natural Gas' qaqc[:economics][:natural_gas_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get qaqc[:economics][:natural_gas_cost_per_m2] = qaqc[:economics][:natural_gas_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty? when 'Additional' qaqc[:economics][:additional_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get qaqc[:economics][:additional_cost_per_m2] = qaqc[:economics][:additional_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty? when 'Total' qaqc[:economics][:total_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get qaqc[:economics][:total_cost_per_m2] = qaqc[:economics][:total_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty? end end else error_warning << "costing is unavailable because the sql statement is nil RowName FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost'" end # Store end_use data end_uses = [ 'Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps', 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators', 'Total End Uses' ] fuels = [ ['Electricity', 'GJ'], ['Natural Gas', 'GJ'], ['Additional Fuel', 'GJ'], ['District Cooling', 'GJ'], ['District Heating', 'GJ'] ] qaqc[:end_uses] = {} qaqc[:end_uses_eui] = {} end_uses.each do |use_type| qaqc[:end_uses]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj"] = 0 qaqc[:end_uses_eui]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj_per_m2"] = 0 fuels.each do |fuel_type| value = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND RowName='#{use_type}' AND ColumnName='#{fuel_type[0]}' AND Units='#{fuel_type[1]}'") if value.empty? || (value.get == 0) else qaqc[:end_uses]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj"] += value.get unless qaqc[:building][:conditioned_floor_area_m2].nil? qaqc[:end_uses_eui]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj_per_m2"] += value.get / qaqc[:building][:conditioned_floor_area_m2] end end end value = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND RowName='#{use_type}' AND ColumnName='Water' AND Units='m3'") if value.empty? || (value.get == 0) else qaqc[:end_uses]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_water_m3"] = value.get unless qaqc[:building][:conditioned_floor_area_m2].nil? qaqc[:end_uses_eui]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_water_m3_per_m2"] = value.get / qaqc[:building][:conditioned_floor_area_m2] end end end # Store Peak Data qaqc[:meter_peaks] = {} qaqc[:meter_peaks][:electric_w] = electric_peak.empty? ? 'NA' : electric_peak.get qaqc[:meter_peaks][:natural_gas_w] = natural_gas_peak.empty? ? 'NA' : natural_gas_peak.get # Store unmet hour data qaqc[:unmet_hours] = {} qaqc[:unmet_hours][:cooling] = model.getFacility.hoursCoolingSetpointNotMet.get unless model.getFacility.hoursCoolingSetpointNotMet.empty? qaqc[:unmet_hours][:heating] = model.getFacility.hoursHeatingSetpointNotMet.get unless model.getFacility.hoursHeatingSetpointNotMet.empty? # puts "\n\n\n#{costing_rownames}\n\n\n" # Padmassun's Code -- Tarrif end # Padmassun's Code -- Service Hotwater Heating *start* qaqc[:service_water_heating] = {} qaqc[:service_water_heating][:total_nominal_occupancy] = -1 # qaqc[:service_water_heating][:total_nominal_occupancy]=model.sqlFile().get().execAndReturnVectorOfDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='OutdoorAirSummary' AND ReportForString='Entire Facility' AND TableName='Average Outdoor Air During Occupied Hours' AND ColumnName='Nominal Number of Occupants'").get.inject(0, :+) qaqc[:service_water_heating][:total_nominal_occupancy] = get_total_nominal_capacity(model) qaqc[:service_water_heating][:electricity_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Electricity' AND RowName='Water Systems'") qaqc[:service_water_heating][:electricity_per_year] = validate_optional(qaqc[:service_water_heating][:electricity_per_year], model, -1) qaqc[:service_water_heating][:electricity_per_day] = qaqc[:service_water_heating][:electricity_per_year] / 365.5 qaqc[:service_water_heating][:electricity_per_day_per_occupant] = qaqc[:service_water_heating][:electricity_per_day] / qaqc[:service_water_heating][:total_nominal_occupancy] qaqc[:service_water_heating][:natural_gas_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Natural Gas' AND RowName='Water Systems'") qaqc[:service_water_heating][:natural_gas_per_year] = validate_optional(qaqc[:service_water_heating][:natural_gas_per_year], model, -1) qaqc[:service_water_heating][:additional_fuel_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Additional Fuel' AND RowName='Water Systems'") qaqc[:service_water_heating][:additional_fuel_per_year] = validate_optional(qaqc[:service_water_heating][:additional_fuel_per_year], model, -1) qaqc[:service_water_heating][:water_m3_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Water' AND RowName='Water Systems'") qaqc[:service_water_heating][:water_m3_per_year] = validate_optional(qaqc[:service_water_heating][:water_m3_per_year], model, -1) qaqc[:service_water_heating][:water_m3_per_day] = qaqc[:service_water_heating][:water_m3_per_year] / 365.5 qaqc[:service_water_heating][:water_m3_per_day_per_occupant] = qaqc[:service_water_heating][:water_m3_per_day] / qaqc[:service_water_heating][:total_nominal_occupancy] # puts qaqc[:service_water_heating][:total_nominal_occupancy] # Padmassun's Code -- Service Hotwater Heating *end* # Store Envelope data. qaqc[:envelope] = {} # Get Areas qaqc[:envelope][:outdoor_walls_area_m2] = outdoor_walls.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } qaqc[:envelope][:outdoor_roofs_area_m2] = outdoor_roofs.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } qaqc[:envelope][:outdoor_floors_area_m2] = outdoor_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } qaqc[:envelope][:ground_walls_area_m2] = ground_walls.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } qaqc[:envelope][:ground_roofs_area_m2] = ground_roofs.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } qaqc[:envelope][:ground_floors_area_m2] = ground_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } qaqc[:envelope][:interior_floors_area_m2] = interior_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier } # Subsurface areas qaqc[:envelope][:windows_area_m2] = windows.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier } qaqc[:envelope][:skylights_area_m2] = skylights.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier } qaqc[:envelope][:doors_area_m2] = doors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier } qaqc[:envelope][:overhead_doors_area_m2] = overhead_doors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier } # Total Building Surface Area. qaqc[:envelope][:total_exterior_area_m2] = qaqc[:envelope][:outdoor_walls_area_m2] + qaqc[:envelope][:outdoor_roofs_area_m2] + qaqc[:envelope][:outdoor_floors_area_m2] + qaqc[:envelope][:ground_walls_area_m2] + qaqc[:envelope][:ground_roofs_area_m2] + qaqc[:envelope][:ground_floors_area_m2] + qaqc[:envelope][:windows_area_m2] + qaqc[:envelope][:skylights_area_m2] + qaqc[:envelope][:doors_area_m2] + qaqc[:envelope][:overhead_doors_area_m2] # Total Building Ground Surface Area. qaqc[:envelope][:total_ground_area_m2] = qaqc[:envelope][:ground_walls_area_m2] + qaqc[:envelope][:ground_roofs_area_m2] + qaqc[:envelope][:ground_floors_area_m2] # Total Building Outdoor Surface Area. qaqc[:envelope][:total_outdoor_area_m2] = qaqc[:envelope][:outdoor_walls_area_m2] + qaqc[:envelope][:outdoor_roofs_area_m2] + qaqc[:envelope][:outdoor_floors_area_m2] + qaqc[:envelope][:windows_area_m2] + qaqc[:envelope][:skylights_area_m2] + qaqc[:envelope][:doors_area_m2] + qaqc[:envelope][:overhead_doors_area_m2] # Average Conductances by surface Type qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_walls).round(4) if !outdoor_walls.empty? qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_roofs).round(4) if !outdoor_roofs.empty? qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_floors).round(4) if !outdoor_floors.empty? qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_walls).round(4) if !ground_walls.empty? qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_roofs).round(4) if !ground_roofs.empty? qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_floors).round(4) if !ground_floors.empty? qaqc[:envelope][:windows_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(windows).round(4) if !windows.empty? qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(skylights).round(4) if !skylights.empty? qaqc[:envelope][:doors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(doors).round(4) if !doors.empty? qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(overhead_doors).round(4) if !overhead_doors.empty? # #Average Conductances for building whole weight factors !outdoor_walls.empty? ? o_wall_cond_weight = qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k] * qaqc[:envelope][:outdoor_walls_area_m2] : o_wall_cond_weight = 0 !outdoor_roofs.empty? ? o_roof_cond_weight = qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k] * qaqc[:envelope][:outdoor_roofs_area_m2] : o_roof_cond_weight = 0 !outdoor_floors.empty? ? o_floor_cond_weight = qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k] * qaqc[:envelope][:outdoor_floors_area_m2] : o_floor_cond_weight = 0 !ground_walls.empty? ? g_wall_cond_weight = qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] * qaqc[:envelope][:ground_walls_area_m2] : g_wall_cond_weight = 0 !ground_roofs.empty? ? g_roof_cond_weight = qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] * qaqc[:envelope][:ground_roofs_area_m2] : g_roof_cond_weight = 0 !ground_floors.empty? ? g_floor_cond_weight = qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] * qaqc[:envelope][:ground_floors_area_m2] : g_floor_cond_weight = 0 !windows.empty? ? win_cond_weight = qaqc[:envelope][:windows_average_conductance_w_per_m2_k] * qaqc[:envelope][:windows_area_m2] : win_cond_weight = 0 # doors.size > 0 ? sky_cond_weight = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] * qaqc[:envelope][:skylights_area_m2] : sky_cond_weight = 0 if !doors.empty? && !qaqc[:envelope][:skylights_average_conductance_w_per_m2_k].nil? && !qaqc[:envelope][:skylights_area_m2].nil? sky_cond_weight = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] * qaqc[:envelope][:skylights_area_m2] else sky_cond_weight = 0 end !overhead_doors.empty? ? door_cond_weight = qaqc[:envelope][:doors_average_conductance_w_per_m2_k] * qaqc[:envelope][:doors_area_m2] : door_cond_weight = 0 !overhead_doors.empty? ? overhead_door_cond_weight = qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k] * qaqc[:envelope][:overhead_doors_area_m2] : overhead_door_cond_weight = 0 # Building Average Conductance qaqc[:envelope][:building_outdoor_average_conductance_w_per_m2_k] = ( o_floor_cond_weight + o_roof_cond_weight + o_wall_cond_weight + win_cond_weight + sky_cond_weight + door_cond_weight + overhead_door_cond_weight) / qaqc[:envelope][:total_outdoor_area_m2] # Building Average Ground Conductance qaqc[:envelope][:building_ground_average_conductance_w_per_m2_k] = ( g_floor_cond_weight + g_roof_cond_weight + g_wall_cond_weight) / qaqc[:envelope][:total_ground_area_m2] # Building Average Conductance qaqc[:envelope][:building_average_conductance_w_per_m2_k] = ( (qaqc[:envelope][:building_ground_average_conductance_w_per_m2_k] * qaqc[:envelope][:total_ground_area_m2]) + (qaqc[:envelope][:building_outdoor_average_conductance_w_per_m2_k] * qaqc[:envelope][:total_outdoor_area_m2]) ) / (qaqc[:envelope][:total_ground_area_m2] + qaqc[:envelope][:total_outdoor_area_m2]) qaqc[:envelope][:fdwr] = (BTAP::Geometry.get_fwdr(model) * 100.0).round(1) qaqc[:envelope][:srr] = (BTAP::Geometry.get_srr(model) * 100.0).round(1) qaqc[:envelope][:constructions] = {} qaqc[:envelope][:constructions][:exterior_fenestration] = [] constructions = [] outdoor_subsurfaces.each { |surface| constructions << surface.construction.get } ext_const_base = Hash.new(0) constructions.each { |name| ext_const_base[name.to_Construction.get] += 1 } # iterate thought each construction and get store data ext_const_base.sort.each do |construction, count| construction_info = {} qaqc[:envelope][:constructions][:exterior_fenestration] << construction_info construction_info[:name] = construction.name.get construction_info[:net_area_m2] = construction.getNetArea.round(2) construction_info[:thermal_conductance_m2_w_per_k] = OpenstudioStandards::Constructions.construction_get_conductance(construction).round(3) construction_info[:solar_transmittance] = OpenstudioStandards::Constructions.construction_get_solar_transmittance(construction).round(3) construction_info[:visible_tranmittance] = OpenstudioStandards::Constructions.construction_get_visible_transmittance(construction).round(3) end # Exterior qaqc[:envelope][:constructions][:exterior_opaque] = [] constructions = [] outdoor_surfaces.each { |surface| constructions << surface.construction.get } ext_const_base = Hash.new(0) constructions.each { |name| ext_const_base[name.to_Construction.get] += 1 } # iterate thought each construction and get store data ext_const_base.sort.each do |construction, count| construction_info = {} qaqc[:envelope][:constructions][:exterior_opaque] << construction_info construction_info[:name] = construction.name.get construction_info[:net_area_m2] = construction.getNetArea.round(2) construction_info[:thermal_conductance_m2_w_per_k] = construction.thermalConductance.get.round(3) if construction.thermalConductance.is_initialized construction_info[:net_area_m2] = construction.to_Construction.get.getNetArea.round(2) construction_info[:solar_absorptance] = construction.to_Construction.get.layers[0].exteriorVisibleAbsorptance.get end # Ground qaqc[:envelope][:constructions][:ground] = [] constructions = [] ground_surfaces.each { |surface| constructions << surface.construction.get } ext_const_base = Hash.new(0) constructions.each { |name| ext_const_base[name.to_Construction.get] += 1 } # iterate thought each construction and get store data ext_const_base.sort.each do |construction, count| construction_info = {} qaqc[:envelope][:constructions][:ground] << construction_info construction_info[:name] = construction.name.get construction_info[:net_area_m2] = construction.getNetArea.round(2) construction_info[:thermal_conductance_m2_w_per_k] = construction.thermalConductance.get.round(3) if construction.thermalConductance.is_initialized construction_info[:net_area_m2] = construction.to_Construction.get.getNetArea.round(2) construction_info[:solar_absorptance] = construction.to_Construction.get.layers[0].exteriorVisibleAbsorptance.get end qaqc[:envelope][:average_thermal_conductance_m2_w_per_k] = # Store Space data. qaqc[:spaces] = [] model.getSpaces.sort.each do |space| spaceinfo = {} qaqc[:spaces] << spaceinfo spaceinfo[:name] = space.name.get # name should be defined test spaceinfo[:multiplier] = space.multiplier spaceinfo[:volume] = space.volume # should be greater than zero spaceinfo[:exterior_wall_area] = space.exteriorWallArea # just for information. spaceinfo[:space_type_name] = space.spaceType.get.name.get unless space.spaceType.empty? # should have a space types name defined. spaceinfo[:thermal_zone] = space.thermalZone.get.name.get unless space.thermalZone.empty? # should be assigned a thermalzone name. # puts space.name.get # puts space.thermalZone.empty? spaceinfo[:breathing_zone_outdoor_airflow_vbz] = -1 breathing_zone_outdoor_airflow_vbz = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='Zone Ventilation Parameters' AND ColumnName='Breathing Zone Outdoor Airflow - Vbz' AND Units='m3/s' AND RowName='#{spaceinfo[:thermal_zone].to_s.upcase}' ") spaceinfo[:breathing_zone_outdoor_airflow_vbz] = breathing_zone_outdoor_airflow_vbz.get unless breathing_zone_outdoor_airflow_vbz.empty? spaceinfo[:infiltration_method] = 'N/A' spaceinfo[:infiltration_flow_per_m2] = -1.0 if space.spaceInfiltrationDesignFlowRates[0].nil? error_warning << "space.spaceInfiltrationDesignFlowRates[0] is empty for #{spaceinfo[:name]}" error_warning << "space.spaceInfiltrationDesignFlowRates[0].designFlowRateCalculationMethod is empty for #{spaceinfo[:name]}" error_warning << "space.spaceInfiltrationDesignFlowRates[0].flowperExteriorSurfaceArea is empty for #{spaceinfo[:name]}" else spaceinfo[:infiltration_method] = space.spaceInfiltrationDesignFlowRates[0].designFlowRateCalculationMethod spaceinfo[:infiltration_flow_per_m2] = 'N/A' spaceinfo[:infiltration_flow_per_m2] = space.spaceInfiltrationDesignFlowRates[0].flowperExteriorSurfaceArea.get.round(5) unless space.spaceInfiltrationDesignFlowRates[0].flowperExteriorSurfaceArea.empty? end # the following should have values unless the spacetype is "undefined" other they should be set to the correct NECB values. if space.spaceType.empty? error_warning << "space.spaceType is empty for #{space.name.get}" else spaceinfo[:occupancy_schedule] = nil if space.spaceType.get.defaultScheduleSet.empty? error_warning << "space.spaceType.get.defaultScheduleSet is empty for #{space.name.get}" else if space.spaceType.get.defaultScheduleSet.get.numberofPeopleSchedule.empty? error_warning << "space.spaceType.get.defaultScheduleSet.get.numberofPeopleSchedule is empty for #{space.name.get}" else spaceinfo[:occupancy_schedule] = space.spaceType.get.defaultScheduleSet.get.numberofPeopleSchedule.get.name.get # should not empty. end end spaceinfo[:occ_per_m2] = space.spaceType.get.people[0].peopleDefinition.peopleperSpaceFloorArea.get.round(3) unless space.spaceType.get.people[0].nil? || space.spaceType.get.people[0].peopleDefinition.peopleperSpaceFloorArea.empty? if space.spaceType.get.lights[0].nil? error_warning << "space.spaceType.get.lights[0] is nil for Space:[#{space.name.get}] Space Type:[#{spaceinfo[:space_type_name]}]" else spaceinfo[:lighting_w_per_m2] = space.spaceType.get.lights[0].lightsDefinition.wattsperSpaceFloorArea # .get.round(3) unless space.spaceType.get.lights[0].nil? spaceinfo[:lighting_w_per_m2] = validate_optional(spaceinfo[:lighting_w_per_m2], model, -1.0) unless spaceinfo[:lighting_w_per_m2].nil? spaceinfo[:lighting_w_per_m2] = spaceinfo[:lighting_w_per_m2].round(3) end end # spaceinfo[:electric_w_per_m2] = space.spaceType.get.electricEquipment[0].electricEquipmentDefinition.wattsperSpaceFloorArea.get.round(3) unless space.spaceType.get.electricEquipment[0].nil? unless space.spaceType.get.electricEquipment[0].nil? unless space.spaceType.get.electricEquipment[0].electricEquipmentDefinition.wattsperSpaceFloorArea.empty? spaceinfo[:electric_w_per_m2] = space.spaceType.get.electricEquipment[0].electricEquipmentDefinition.wattsperSpaceFloorArea.get.round(3) end end spaceinfo[:shw_m3_per_s] = space.waterUseEquipment[0].waterUseEquipmentDefinition.peakFlowRate.round(3) unless space.waterUseEquipment[0].nil? spaceinfo[:waterUseEquipment] = [] if !space.waterUseEquipment.empty? waterUseEquipment_info = {} spaceinfo[:waterUseEquipment] << waterUseEquipment_info waterUseEquipment_info[:peak_flow_rate] = space.waterUseEquipment[0].waterUseEquipmentDefinition.peakFlowRate waterUseEquipment_info[:peak_flow_rate_per_area] = waterUseEquipment_info[:peak_flow_rate] / space.floorArea area_per_occ = space.spaceType.get.people[0].nil? ? 0.0 : validate_optional(space.spaceType.get.people[0].spaceFloorAreaPerPerson, model, -1.0) # Watt per person = m3/s/m3 * 1000W/kW * (specific heat * dT) * m2/person waterUseEquipment_info[:shw_watts_per_person] = waterUseEquipment_info[:peak_flow_rate_per_area] * 1000 * (4.19 * 44.4) * 1000 * area_per_occ # puts waterUseEquipment_info[:shw_watts_per_ponce the erson] # puts "\n\n\n" end end end # Store Thermal zone data qaqc[:thermal_zones] = [] model.getThermalZones.sort.each do |zone| zoneinfo = {} qaqc[:thermal_zones] << zoneinfo zoneinfo[:name] = zone.name.get zoneinfo[:floor_area] = zone.floorArea zoneinfo[:multiplier] = zone.multiplier zoneinfo[:is_conditioned] = 'N/A' if zone.isConditioned.empty? error_warning << "zone.isConditioned is empty for #{zone.name.get}" else zoneinfo[:is_conditioned] = zone.isConditioned.get end zoneinfo[:is_ideal_air_loads] = zone.useIdealAirLoads zoneinfo[:heating_sizing_factor] = -1.0 if zone.sizingZone.zoneHeatingSizingFactor.empty? error_warning << "zone.sizingZone.zoneHeatingSizingFactor is empty for #{zone.name.get}" else zoneinfo[:heating_sizing_factor] = zone.sizingZone.zoneHeatingSizingFactor.get end zoneinfo[:cooling_sizing_factor] = -1.0 # zone.sizingZone.zoneCoolingSizingFactor.get if zone.sizingZone.zoneCoolingSizingFactor.empty? error_warning << "zone.sizingZone.zoneCoolingSizingFactor is empty for #{zone.name.get}" else zoneinfo[:cooling_sizing_factor] = zone.sizingZone.zoneCoolingSizingFactor.get end zoneinfo[:zone_heating_design_supply_air_temperature] = zone.sizingZone.zoneHeatingDesignSupplyAirTemperature zoneinfo[:zone_cooling_design_supply_air_temperature] = zone.sizingZone.zoneCoolingDesignSupplyAirTemperature zoneinfo[:spaces] = [] zone.spaces.sort.each do |space| spaceinfo = {} zoneinfo[:spaces] << spaceinfo spaceinfo[:name] = space.name.get spaceinfo[:type] = space.spaceType.get.name.get unless space.spaceType.empty? end zoneinfo[:equipment] = [] zone.equipmentInHeatingOrder.each do |equipment| item = {} zoneinfo[:equipment] << item item[:name] = equipment.name.get if equipment.to_ZoneHVACComponent.is_initialized item[:type] = 'ZoneHVACComponent' elsif equipment.to_StraightComponent.is_initialized item[:type] = 'StraightComponent' end end # zone end # Store Air Loop Information qaqc[:air_loops] = [] model.getAirLoopHVACs.sort.each do |air_loop| air_loop_info = {} air_loop_info[:name] = air_loop.name.get air_loop_info[:thermal_zones] = [] air_loop_info[:total_floor_area_served] = 0.0 air_loop_info[:total_breathing_zone_outdoor_airflow_vbz] = 0.0 air_loop.thermalZones.sort.each do |zone| air_loop_info[:thermal_zones] << zone.name.get vbz = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='Zone Ventilation Parameters' AND ColumnName='Breathing Zone Outdoor Airflow - Vbz' AND Units='m3/s' AND RowName='#{zone.name.get.to_s.upcase}' ") vbz = validate_optional(vbz, model, 0) air_loop_info[:total_breathing_zone_outdoor_airflow_vbz] += vbz air_loop_info[:total_floor_area_served] += zone.floorArea * zone.multiplier.to_f end air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='System Ventilation Parameters' AND ColumnName='Area Outdoor Air Rate - Ra' AND Units='m3/s-m2' AND RowName='#{air_loop_info[:name].to_s.upcase}' ") air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] = validate_optional(air_loop_info[:area_outdoor_air_rate_m3_per_s_m2], model, -1.0) air_loop_info[:outdoor_air_L_per_s] = -1.0 unless air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] == -1.0 air_loop_info[:outdoor_air_L_per_s] = air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] * air_loop_info[:total_floor_area_served] * 1000 end # Fan unless air_loop.supplyFan.empty? air_loop_info[:supply_fan] = {} if air_loop.supplyFan.get.to_FanConstantVolume.is_initialized air_loop_info[:supply_fan][:type] = 'CV' fan = air_loop.supplyFan.get.to_FanConstantVolume.get elsif air_loop.supplyFan.get.to_FanVariableVolume.is_initialized air_loop_info[:supply_fan][:type] = 'VV' fan = air_loop.supplyFan.get.to_FanVariableVolume.get end air_loop_info[:supply_fan][:name] = fan.name.get # puts "\n\n\n\n#{fan.name.get}\n\n\n\n" air_loop_info[:supply_fan][:fan_efficiency] = fan.fanEfficiency air_loop_info[:supply_fan][:motor_efficiency] = fan.motorEfficiency air_loop_info[:supply_fan][:pressure_rise] = fan.pressureRise air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] = -1.0 max_air_flow_info = model.sqlFile.get.execAndReturnVectorOfString("SELECT RowName FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' ") max_air_flow_info = validate_optional(max_air_flow_info, model, 'N/A') if max_air_flow_info == 'N/A' error_warning << "max_air_flow_info is nil because the following sql statement returned nil: RowName FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' " else if max_air_flow_info.include? air_loop_info[:supply_fan][:name].to_s.upcase.to_s air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' AND RowName='#{air_loop_info[:supply_fan][:name].upcase}' ").get air_loop_info[:supply_fan][:rated_electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Rated Electric Power' AND Units='W' AND RowName='#{air_loop_info[:supply_fan][:name].upcase}' ") # Version 3.2.0 has renamed rated electric_power to rated electricity rate if air_loop_info[:supply_fan][:rated_electric_power_w].empty? air_loop_info[:supply_fan][:rated_electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Rated Electricity Rate' AND Units='W' AND RowName='#{air_loop_info[:supply_fan][:name].upcase}' ") end air_loop_info[:supply_fan][:rated_electric_power_w] = air_loop_info[:supply_fan][:rated_electric_power_w].get else error_warning << "#{air_loop_info[:supply_fan][:name]} does not exist in sql file WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s'" end end end # economizer air_loop_info[:economizer] = {} air_loop_info[:economizer][:name] = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.name.get unless air_loop.airLoopHVACOutdoorAirSystem.empty? or air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.name.empty? air_loop_info[:economizer][:control_type] = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.getEconomizerControlType unless air_loop.airLoopHVACOutdoorAirSystem.empty? # DX cooling coils air_loop_info[:cooling_coils] = {} air_loop_info[:cooling_coils][:dx_single_speed] = [] air_loop_info[:cooling_coils][:dx_two_speed] = [] air_loop_info[:cooling_coils][:coil_cooling_water] = [] # Heating Coil air_loop_info[:heating_coils] = {} air_loop_info[:heating_coils][:coil_heating_gas] = [] air_loop_info[:heating_coils][:coil_heating_electric] = [] air_loop_info[:heating_coils][:coil_heating_water] = [] # Heat Excahnger air_loop_info[:heat_exchanger] = {} air_loop.supplyComponents.each do |supply_comp| if supply_comp.to_CoilHeatingGas.is_initialized coil = {} air_loop_info[:heating_coils][:coil_heating_gas] << coil gas = supply_comp.to_CoilHeatingGas.get coil[:name] = gas.name.get coil[:type] = 'Gas' coil[:efficency] = gas.gasBurnerEfficiency # coil[:nominal_capacity]= gas.nominalCapacity() coil[:nominal_capacity] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Heating Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].to_s.upcase}'") coil[:nominal_capacity] = validate_optional(coil[:nominal_capacity], model, -1.0) end if supply_comp.to_CoilHeatingElectric.is_initialized coil = {} air_loop_info[:heating_coils][:coil_heating_electric] << coil electric = supply_comp.to_CoilHeatingElectric.get coil[:name] = electric.name.get coil[:type] = 'Electric' coil[:nominal_capacity] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Heating Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].to_s.upcase}'") coil[:nominal_capacity] = validate_optional(coil[:nominal_capacity], model, -1.0) end if supply_comp.to_CoilHeatingWater.is_initialized coil = {} air_loop_info[:heating_coils][:coil_heating_water] << coil water = supply_comp.to_CoilHeatingWater.get coil[:name] = water.name.get coil[:type] = 'Water' coil[:nominal_capacity] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Heating Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].to_s.upcase}'") coil[:nominal_capacity] = validate_optional(coil[:nominal_capacity], model, -1.0) end if supply_comp.to_HeatExchangerAirToAirSensibleAndLatent.is_initialized heatExchanger = supply_comp.to_HeatExchangerAirToAirSensibleAndLatent.get air_loop_info[:heat_exchanger][:name] = heatExchanger.name.get end end # I dont think i need to get the type of heating coil from the sql file, because the coils are differentiated by class, and I have hard coded the information # model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName= 'Heating Coils' AND ColumnName='Type' ").get #padmussen to complete #AND RowName='#{air_loop_info[:heating_coils][:name].upcase}' # Collect all the fans into the the array. air_loop.supplyComponents.each do |supply_comp| if supply_comp.to_CoilCoolingDXSingleSpeed.is_initialized coil = {} air_loop_info[:cooling_coils][:dx_single_speed] << coil single_speed = supply_comp.to_CoilCoolingDXSingleSpeed.get coil[:name] = single_speed.name.get coil[:cop] = single_speed.ratedCOP.to_f coil[:nominal_total_capacity_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].upcase}' ") coil[:nominal_total_capacity_w] = validate_optional(coil[:nominal_total_capacity_w], model, -1.0) end if supply_comp.to_CoilCoolingDXTwoSpeed.is_initialized coil = {} air_loop_info[:cooling_coils][:dx_two_speed] << coil two_speed = supply_comp.to_CoilCoolingDXTwoSpeed.get coil[:name] = two_speed.name.get coil[:cop_low] = two_speed.ratedLowSpeedCOP.to_f coil[:cop_high] = two_speed.ratedHighSpeedCOP.to_f coil[:nominal_total_capacity_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].upcase}' ") coil[:nominal_total_capacity_w] = validate_optional(coil[:nominal_total_capacity_w], model, -1.0) end if supply_comp.to_CoilCoolingWater.is_initialized coil = {} air_loop_info[:cooling_coils][:coil_cooling_water] << coil coil_cooling_water = supply_comp.to_CoilCoolingWater.get coil[:name] = coil_cooling_water.name.get coil[:nominal_total_capacity_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].upcase}' ") coil[:nominal_total_capacity_w] = validate_optional(coil[:nominal_total_capacity_w], model, -1.0) coil[:nominal_sensible_heat_ratio] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Sensible Heat Ratio' AND RowName='#{coil[:name].upcase}' ") coil[:nominal_sensible_heat_ratio] = validate_optional(coil[:nominal_sensible_heat_ratio], model, -1.0) end end qaqc[:air_loops] << air_loop_info end qaqc[:plant_loops] = [] model.getPlantLoops.sort.each do |plant_loop| plant_loop_info = {} qaqc[:plant_loops] << plant_loop_info plant_loop_info[:name] = plant_loop.name.get sizing = plant_loop.sizingPlant plant_loop_info[:design_loop_exit_temperature] = sizing.designLoopExitTemperature plant_loop_info[:loop_design_temperature_difference] = sizing.loopDesignTemperatureDifference # Create Container for plant equipment arrays. plant_loop_info[:pumps] = [] plant_loop_info[:boilers] = [] plant_loop_info[:chiller_electric_eir] = [] plant_loop_info[:cooling_tower_single_speed] = [] plant_loop_info[:water_heater_mixed] = [] plant_loop.supplyComponents.each do |supply_comp| # Collect Constant Speed if supply_comp.to_PumpConstantSpeed.is_initialized pump = supply_comp.to_PumpConstantSpeed.get pump_info = {} plant_loop_info[:pumps] << pump_info pump_info[:name] = pump.name.get pump_info[:type] = 'Pump:ConstantSpeed' pump_info[:head_pa] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Head' AND RowName='#{pump_info[:name].upcase}' ") pump_info[:head_pa] = validate_optional(pump_info[:head_pa], model, -1.0) pump_info[:water_flow_m3_per_s] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Water Flow' AND RowName='#{pump_info[:name].upcase}' ") pump_info[:water_flow_m3_per_s] = validate_optional(pump_info[:water_flow_m3_per_s], model, -1.0) pump_info[:electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Electric Power' AND RowName='#{pump_info[:name].upcase}' ") pump_info[:electric_power_w] = validate_optional(pump_info[:electric_power_w], model, -1.0) pump_info[:motor_efficency] = pump.motorEfficiency end # Collect Variable Speed if supply_comp.to_PumpVariableSpeed.is_initialized pump = supply_comp.to_PumpVariableSpeed.get pump_info = {} plant_loop_info[:pumps] << pump_info pump_info[:name] = pump.name.get pump_info[:type] = 'Pump:VariableSpeed' pump_info[:head_pa] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Head' AND RowName='#{pump_info[:name].upcase}' ") pump_info[:head_pa] = validate_optional(pump_info[:head_pa], model, -1.0) pump_info[:water_flow_m3_per_s] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Water Flow' AND RowName='#{pump_info[:name].upcase}' ") pump_info[:water_flow_m3_per_s] = validate_optional(pump_info[:water_flow_m3_per_s], model, -1.0) pump_info[:electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Electric Power' AND RowName='#{pump_info[:name].upcase}' ") pump_info[:electric_power_w] = validate_optional(pump_info[:electric_power_w], model, -1.0) pump_info[:motor_efficency] = pump.motorEfficiency end # Collect HotWaterBoilers if supply_comp.to_BoilerHotWater.is_initialized boiler = supply_comp.to_BoilerHotWater.get boiler_info = {} plant_loop_info[:boilers] << boiler_info boiler_info[:name] = boiler.name.get boiler_info[:type] = 'Boiler:HotWater' boiler_info[:fueltype] = boiler.fuelType boiler_info[:nominal_capacity] = boiler.nominalCapacity boiler_info[:nominal_capacity] = validate_optional(boiler_info[:nominal_capacity], model, -1.0) end # Collect ChillerElectricEIR if supply_comp.to_ChillerElectricEIR.is_initialized chiller = supply_comp.to_ChillerElectricEIR.get chiller_info = {} plant_loop_info[:chiller_electric_eir] << chiller_info chiller_info[:name] = chiller.name.get chiller_info[:type] = 'Chiller:Electric:EIR' chiller_info[:reference_capacity] = validate_optional(chiller.referenceCapacity, model, -1.0) chiller_info[:reference_leaving_chilled_water_temperature] = chiller.referenceLeavingChilledWaterTemperature end # Collect CoolingTowerSingleSpeed if supply_comp.to_CoolingTowerSingleSpeed.is_initialized coolingTower = supply_comp.to_CoolingTowerSingleSpeed.get coolingTower_info = {} plant_loop_info[:cooling_tower_single_speed] << coolingTower_info coolingTower_info[:name] = coolingTower.name.get coolingTower_info[:type] = 'CoolingTower:SingleSpeed' coolingTower_info[:fan_power_at_design_air_flow_rate] = validate_optional(coolingTower.fanPoweratDesignAirFlowRate, model, -1.0) end # Collect WaterHeaterMixed if supply_comp.to_WaterHeaterMixed.is_initialized waterHeaterMixed = supply_comp.to_WaterHeaterMixed.get waterHeaterMixed_info = {} plant_loop_info[:water_heater_mixed] << waterHeaterMixed_info waterHeaterMixed_info[:name] = waterHeaterMixed.name.get waterHeaterMixed_info[:type] = 'WaterHeater:Mixed' waterHeaterMixed_info[:heater_thermal_efficiency] = waterHeaterMixed.heaterThermalEfficiency.get unless waterHeaterMixed.heaterThermalEfficiency.empty? waterHeaterMixed_info[:heater_fuel_type] = waterHeaterMixed.heaterFuelType end end qaqc[:eplusout_err] = {} warnings = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='0' ") warnings = validate_optional(warnings, model, 'N/A') unless warnings == 'N/A' qaqc[:eplusout_err][:warnings] = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='0' ").get qaqc[:eplusout_err][:fatal] = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='2' ").get qaqc[:eplusout_err][:severe] = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='1' ").get end qaqc[:ruby_warnings] = error_warning end qaqc[:code_metrics] = {} qaqc[:code_metrics]['heating_gj'] = qaqc[:end_uses]['heating_gj'] qaqc[:code_metrics]['cooling_gj'] = qaqc[:end_uses]['cooling_gj'] qaqc[:code_metrics][:ep_conditioned_floor_area_m2] = qaqc[:building][:conditioned_floor_area_m2] qaqc[:code_metrics][:os_conditioned_floor_area_m2] = qaqc[:envelope][:interior_floors_area_m2] + qaqc[:envelope][:outdoor_floors_area_m2] + qaqc[:envelope][:ground_floors_area_m2] # TEDI unless qaqc[:building][:conditioned_floor_area_m2].nil? qaqc[:code_metrics][:building_tedi_gj_per_m2] = (qaqc[:end_uses]['heating_gj'] + qaqc[:end_uses]['cooling_gj'] ) / qaqc[:building][:conditioned_floor_area_m2] # Mech TEDI? qaqc[:code_metrics][:building_medi_gj_per_m2] = (qaqc[:end_uses]['fans_gj'] + qaqc[:end_uses]['pumps_gj'] + qaqc[:end_uses]['heat_rejection_gj'] + qaqc[:end_uses]['humidification_gj'] + qaqc[:end_uses]['heat_recovery_gj'] ) / qaqc[:building][:conditioned_floor_area_m2] end return qaqc end
Create EMS to turn on âAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeedâ in response to a call from the night cycle availability manager of the air loop. It was found that this object doesnât respond properly to this call from the night cycle
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1095 def create_ems_to_turn_on_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_for_night_cycle(multi_speed_heat_pump) model = multi_speed_heat_pump.model avail_manager_name = nil if multi_speed_heat_pump.airLoopHVAC.is_initialized if !multi_speed_heat_pump.airLoopHVAC.get.availabilityManagers.empty? avail_manager_name = multi_speed_heat_pump.airLoopHVAC.get.availabilityManagers[0].name.to_s end end return unless avail_manager_name avail_manager_out_var_name = 'Availability Manager Night Cycle Control Status' avail_manager_out_var = OpenStudio::Model::OutputVariable.new(avail_manager_out_var_name, model) avail_manager_out_var.setKeyValue(avail_manager_name) avail_manager_out_var.setReportingFrequency('Timestep') night_cycle_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, avail_manager_out_var) heat_pump_avail_sch = nil if multi_speed_heat_pump.availabilitySchedule.is_initialized heat_pump_avail_sch = multi_speed_heat_pump.availabilitySchedule.get elsif multi_speed_heat_pump.airLoopHVAC.get.availabilitySchedule.is_initialized heat_pump_avail_sch = multi_speed_heat_pump.airLoopHVAC.get.availabilitySchedule.get else heat_pump_avail_sch = OpenStudio::Model::ScheduleConstant.new(model) heat_pump_avail_sch.setValue(1.0) end heat_pump_avail_sch_var = OpenStudio::Model::OutputVariable.new('Schedule Value', model) heat_pump_avail_sch_var.setKeyValue(heat_pump_avail_sch.name.to_s) heat_pump_avail_sch_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, heat_pump_avail_sch_var) updated_heat_pump_avail_sch = OpenStudio::Model::ScheduleConstant.new(model) multi_speed_heat_pump.setAvailabilitySchedule(updated_heat_pump_avail_sch) # This method will seem like an error in number of args..but this is due to swig voodoo. heat_pump_avail_sch_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(updated_heat_pump_avail_sch, 'Schedule:Constant', 'Schedule Value') heat_pump_avail_sch_prog = OpenStudio::Model::EnergyManagementSystemProgram.new(model) heat_pump_avail_sch_prog.setName("#{ems_friendly_name(multi_speed_heat_pump.name)} Availability Schedule Program by Line") heat_pump_avail_sch_prog_body = <<-EMS IF #{heat_pump_avail_sch_sensor.handle} > 0.0 SET #{heat_pump_avail_sch_actuator.handle} = #{heat_pump_avail_sch_sensor.handle} ELSEIF #{night_cycle_sensor.handle} == 2.0 SET #{heat_pump_avail_sch_actuator.handle} = 1.0 ELSE SET #{heat_pump_avail_sch_actuator.handle} = 0.0 ENDIF EMS heat_pump_avail_sch_prog.setBody(heat_pump_avail_sch_prog_body) pcm = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model) pcm.setName("#{heat_pump_avail_sch_prog.name} Calling Manager") pcm.setCallingPoint('InsideHVACSystemIterationLoop') pcm.addProgram(heat_pump_avail_sch_prog) end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2142 def create_heating_cooling_on_off_availability_schedule(model) # @todo Create a feature to derive start and end heating and cooling seasons from weather file. avail_data = [{ start_month: 1, start_day: 1, end_month: 6, end_day: 30, htg_value: 1, clg_value: 0 }, { start_month: 7, start_day: 1, end_month: 10, end_day: 31, htg_value: 0, clg_value: 1 }, { start_month: 11, start_day: 1, end_month: 12, end_day: 31, htg_value: 1, clg_value: 0 }] # Heating coil availability schedule for tpfc htg_availability_sch = OpenStudio::Model::ScheduleRuleset.new(model) htg_availability_sch.setName('tpfc_htg_availability') # Cooling coil availability schedule for tpfc clg_availability_sch = OpenStudio::Model::ScheduleRuleset.new(model) clg_availability_sch.setName('tpfc_clg_availability') avail_data.each do |data| htg_availability_sch_rule = OpenStudio::Model::ScheduleRule.new(htg_availability_sch) htg_availability_sch_rule.setName('tpfc_htg_availability_sch_rule') htg_availability_sch_rule.setStartDate(model.getYearDescription.makeDate(data[:start_month], data[:start_day])) htg_availability_sch_rule.setEndDate(model.getYearDescription.makeDate(data[:end_month], data[:end_day])) htg_availability_sch_rule.setApplySunday(true) htg_availability_sch_rule.setApplyMonday(true) htg_availability_sch_rule.setApplyTuesday(true) htg_availability_sch_rule.setApplyWednesday(true) htg_availability_sch_rule.setApplyThursday(true) htg_availability_sch_rule.setApplyFriday(true) htg_availability_sch_rule.setApplySaturday(true) day_schedule = htg_availability_sch_rule.daySchedule day_schedule.setName('tpfc_htg_availability_sch_rule_day') day_schedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), data[:htg_value]) clg_availability_sch_rule = OpenStudio::Model::ScheduleRule.new(clg_availability_sch) clg_availability_sch_rule.setName('tpfc_clg_availability_sch_rule') clg_availability_sch_rule.setStartDate(model.getYearDescription.makeDate(data[:start_month], data[:start_day])) clg_availability_sch_rule.setEndDate(model.getYearDescription.makeDate(data[:end_month], data[:end_day])) clg_availability_sch_rule.setApplySunday(true) clg_availability_sch_rule.setApplyMonday(true) clg_availability_sch_rule.setApplyTuesday(true) clg_availability_sch_rule.setApplyWednesday(true) clg_availability_sch_rule.setApplyThursday(true) clg_availability_sch_rule.setApplyFriday(true) clg_availability_sch_rule.setApplySaturday(true) day_schedule = clg_availability_sch_rule.daySchedule day_schedule.setName('tpfc_clg_availability_sch_rule_day') day_schedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), data[:clg_value]) end return clg_availability_sch, htg_availability_sch end
Method will create a hot water loop if systems default fuel and medium sources require it.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 828 def create_hw_loop_if_required(baseboard_type, boiler_fueltype, backup_boiler_fueltype, mau_heating_coil_type, model) # get systems that will be used in the model based on the space types to determine if a hw_loop is required. systems_used = [] model.getSpaces.sort.each do |space| systems_used << get_necb_spacetype_system_selection(space) systems_used.uniq! end # See if we need to create a hot water loop based on fueltype and systems used. hw_loop_needed = false systems_used.each do |system| case system.to_s when '2', '5', '7' hw_loop_needed = true when '1', '6' if (mau_heating_coil_type == 'Hot Water') || (baseboard_type == 'Hot Water') hw_loop_needed = true end when '3', '4' if (mau_heating_coil_type == 'Hot Water') || (baseboard_type == 'Hot Water') hw_loop_needed = true if baseboard_type == 'Hot Water' end end if hw_loop_needed # just need one true condition to need a boiler. break end # each end # create hw_loop as needed.. Assuming one loop per model. if hw_loop_needed @hw_loop = OpenStudio::Model::PlantLoop.new(model) always_on = model.alwaysOnDiscreteSchedule setup_hw_loop_with_components(model, @hw_loop, boiler_fueltype, backup_boiler_fueltype, always_on) end return @hw_loop end
Default method to create a necb system and assign array of zones to be supported by it. It will try to bring zones with similar loads on the same airloops and set control zones where possible for single zone systems and will create monolithic system 6 multizones where possible.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 869 def create_necb_system(baseboard_type:, boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:, zones:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel') # The goal is to minimize the number of system when possible. system_zones_hash = {} zones.each do |zone| system_zones_hash[get_necb_thermal_zone_system_selection(zone)] = [] if system_zones_hash[get_necb_thermal_zone_system_selection(zone)].nil? system_zones_hash[get_necb_thermal_zone_system_selection(zone)] << zone end # puts JSON.pretty_generate(system_zones_hash) # go through each system and zones pairs to system_zones_hash.each_pair do |system, sys_zones| case system when 0, nil # Do nothing no system assigned to zone. Used for Unconditioned spaces when 1 group_similar_zones_together(sys_zones).each do |curr_zones| mau_air_loop = add_sys1_unitary_ac_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: curr_zones, mau_type: mau_type, mau_heating_coil_type: mau_heating_coil_type, baseboard_type: baseboard_type, hw_loop: @hw_loop, multispeed: false) end when 2 group_similar_zones_together(sys_zones).each do |curr_zones| add_sys2_FPFC_sys5_TPFC(model: model, zones: curr_zones, chiller_type: chiller_type, mau_cooling_type: mau_cooling_type, fan_coil_type: 'FPFC', hw_loop: @hw_loop) end when 3 group_similar_zones_together(sys_zones).each do |curr_zones| add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: curr_zones, heating_coil_type: heating_coil_type_sys3, baseboard_type: baseboard_type, hw_loop: @hw_loop, multispeed: false) end when 4 group_similar_zones_together(sys_zones).each do |curr_zones| add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel, zones: curr_zones, heating_coil_type: heating_coil_type_sys4, baseboard_type: baseboard_type, hw_loop: @hw_loop) # add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model, # zones: zones, # heating_coil_type: heating_coil_type_sys4, # baseboard_type: baseboard_type, # hw_loop: @hw_loop, # multispeed: false) end when 5 group_similar_zones_together(sys_zones).each do |curr_zones| add_sys2_FPFC_sys5_TPFC(model: model, zones: curr_zones, chiller_type: chiller_type, mau_cooling_type: mau_cooling_type, fan_coil_type: 'TPFC', hw_loop: @hw_loop) end when 6 if necb_reference_hp add_sys6_multi_zone_reference_hp_with_baseboard_heating(model: model, zones: sys_zones, heating_coil_type: heating_coil_type_sys6, baseboard_type: baseboard_type, hw_loop:@hw_loop, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel) else add_sys6_multi_zone_built_up_system_with_baseboard_heating(model: model, zones: sys_zones, heating_coil_type: heating_coil_type_sys6, baseboard_type: baseboard_type, chiller_type: chiller_type, fan_type: fan_type, hw_loop: @hw_loop) end when 7 group_similar_zones_together(sys_zones).each do |curr_zones| add_sys2_FPFC_sys5_TPFC(model: model, zones: curr_zones, chiller_type: chiller_type, fan_coil_type: 'FPFC', mau_cooling_type: mau_cooling_type, hw_loop: @hw_loop) end end end end
This method will determine the control zone from the last sizing run space loads.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1214 def determine_control_zone(zones) # In this case the control zone is the load with the largest heating loads. This may cause overheating of some zones. # but this is preferred to unmet heating. # Iterate through zones. zone_heating_load_hash = {} zones.each { |zone| zone_heating_load_hash[zone] = stored_zone_heating_load(zone) } return zone_heating_load_hash.max_by(&:last).first end
This model determines the dominant NECB schedule type @param model [OpenStudio::Model::Model] A model object return s.each [String]
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 808 def determine_dominant_necb_schedule_type(model) return determine_dominant_schedule(model.getSpaces) end
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 767 def determine_dominant_schedule(spaces) # lookup necb space type properties space_type_properties = @standards_data['space_types'] # Here is a hash to keep track of the m2 running total of spacetypes for each # sched type. # 2018-04-11: Not sure if this is still used but the list was expanded to incorporate additional existing or potential # future schedules. schedule_hash = Hash[ 'A', 0, 'B', 0, 'C', 0, 'D', 0, 'E', 0, 'F', 0, 'G', 0, 'H', 0, 'I', 0, 'J', 0, 'K', 0, 'L', 0, 'M', 0, 'N', 0, 'O', 0, 'P', 0, 'Q', 0 ] # iterate through spaces in building. spaces.select { |space| !is_an_necb_wildcard_space?(space) && (space.spaceType.get.standardsSpaceType.get != '- undefined -') }.each do |space| # Ensure space floors are multiplied. mult = @space_multiplier_map[space.name.to_s].nil? ? 1.0 : @space_multiplier_map[space.name.to_s] # puts "this #{determine_necb_schedule_type(space)}" schedule_hash[determine_necb_schedule_type(space)] += space.floorArea * mult end # finds max value and returns NECB schedule letter. # determine dominant letter schedule. return schedule_hash.max_by(&:last).first end
This method determines the spacetype schedule type. This will re @author phylroy.lopez@nrcan.gc.ca @param space [String] @return [String]: spacetype
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 816 def determine_necb_schedule_type(space) spacetype_data = @standards_data['space_types'] raise "Spacetype not defined for space #{space.get.name}) if space.spaceType.empty?" if space.spaceType.empty? raise "Undefined standardsSpaceType or StandardsBuildingType for space #{space.spaceType.get.name}) if space.spaceType.empty?" if space.spaceType.get.standardsSpaceType.empty? | space.spaceType.get.standardsBuildingType.empty? space_type_properties = spacetype_data.detect { |st| (st['space_type'] == space.spaceType.get.standardsSpaceType.get) && (st['building_type'] == space.spaceType.get.standardsBuildingType.get) } return space_type_properties['necb_schedule_type'].strip end
this method will determine the vintage of NECB spacetypes the model contains. It will return nil if it canât determine it.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1139 def determine_spacetype_vintage(model) #this code is the list of available vintages space_type_vintage_list = ['NECB2011', 'NECB2015', 'NECB2017', 'NECB2020', 'BTAPPRE1980', 'BTAP1980TO2010'] #this reorders the list to do the current class first. space_type_vintage_list.insert(0, space_type_vintage_list.delete(self.class.name)) # Set the space_type space_type_vintage = nil # get list of space types used in model and a mapped string. model_space_type_names = model.getSpaceTypes.map do |spacetype| [spacetype.standardsBuildingType.get.to_s + '-' + spacetype.standardsSpaceType.get.to_s] end # Now iterate though each vintage space_type_vintage_list.each do |template| # Create the standard object and get a list of all the spacetypes available for that vintage. standard_space_type_list = Standard.build(template).get_all_spacetype_names.map { |spacetype| [spacetype[0].to_s + '-' + spacetype[1].to_s] } # set array to contain unknown spacetypes. unknown_spacetypes = [] # iterate though all space types that the model is using model_space_type_names.each do |space_type_name| # push unknown spacetypes into the array. unknown_spacetypes << space_type_name unless standard_space_type_list.include?(space_type_name) end if unknown_spacetypes.empty? # No unknowns, so return the template and don't bother looking for others. return template end end return space_type_vintage end
Enter in [latitude, longitude] for each loc and this method will return the distance.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 162 def distance(loc1, loc2) rad_per_deg = Math::PI / 180 # PI / 180 rkm = 6371 # Earth radius in kilometers rm = rkm * 1000 # Radius in meters dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg } lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg } a = Math.sin(dlat_rad / 2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2)**2 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) rm * c # Delta in meters end
Arguments: weather_list_url (string): the web address of the json file containing the list of weather files on the repository weather_loc (string): the name of the epw file we are looking for without the .epw extension git_folder (string): the url of the folder containing the weather files. As of 2023-07-07 this this is either the
url of the historical weather data folder or the future weather data folder.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2302 def download_and_save_file(weather_list_url:, weather_loc:, git_folder:) status = false attempt = 1 # Try to download the list of weather files 5 times, waiting 5 seconds between each attempt. until attempt > 5 begin puts "Beginning attempt #{attempt} to download #{weather_list_url}" # Download the list of weather files on the repository URI.open(weather_list_url) do |web_data| # Convert the weather file list to an array if web_data.size <= 100 raise("Could not read #{weather_list_url}!") end weather_files = (JSON.parse(web_data.read)).to_a # Check to see if the requested weather file is on the list zip_name = weather_files.find{ |weather_file| weather_file.match(weather_loc) } # If the weather file is on the list proceed, otherwise report that it could not be found unless zip_name.nil? # Found the weather file on the list # Define the full url of the weather zip file we want to download save_file_url = git_folder + zip_name # Define the local location of where the weather zip file will be saved weather_dir = File.absolute_path(File.join(__FILE__, '..', '..', '..', '..', '..' , '..', "data/weather")) save_file = File.join(weather_dir, zip_name) attemptb = 1 # Try to download the weather file up to 5 times, waiting 5 seconds between each attempt. until attemptb > 5 begin puts "Beginning attempt #{attemptb} to download #{save_file_url}" # Download the weather zip file from the repository URI.open(save_file_url) do |file_url| if file_url.size <= 100 raise("Could not read #{save_file_url}!") end # Save the zip file in the /data/weather folder File.open(save_file, 'wb') { |f| f.write(file_url.read) } puts "Downloaded #{save_file_url} to #{save_file}" # Extract the individual weother files from the zip file status = extract_weather_data(zipped_file: save_file, weather_dir: weather_dir) end attemptb = 10 rescue sleep(30) attemptb += 1 end end end end attempt = 10 rescue sleep(30) attempt += 1 end end return status end
This method extracts data from a zip file. The name implies it is to be used for zip files containing weather data but really it can be used to extract any zip file. Arguments: zipped_file (string): As the name implies, this is the name and path to the zip file we want to expand. weather_dir (string): This is the folder where the zipped files will be extracted to. Donât let the name fool you.
it can be any folder not just a weather directory.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2395 def extract_weather_data(zipped_file:, weather_dir:) # Set a flag to check if the weather data is for future weather. future_file = false # Start expanding the data. Zip::File.open(zipped_file) do |zip_file| puts "Expanding #{zipped_file}" # Cycle through each file in the zip file zip_file.each do |entry| # Define the location of where the file will be saved locally curr_save_file = File.join(weather_dir, entry.name.to_s) puts "Extracting #{entry.name} to #{curr_save_file}" # Check if the file includes a '_ASHRAE.ddy' term. If there is, later on we will rename the '_ASHRAE.ddy' file # to just '.ddy'. This is so the rest of BTAP uses the _ASHRAE.ddy file. entry_name = entry.name.to_s if entry_name.length > 11 future_file = true if entry_name[-11..-1] == '_ASHRAE.ddy' end # entry.extract # This was required before but now it isn't. I'm confused so am saving this comment to # remind me if there are problems later. # Read the data from the file content = entry.get_input_stream.read # Save the data locally File.open(curr_save_file, 'wb') { |save_f| save_f.write(content) } end end if future_file # Rename the non ASHRAE.ddy as '_non_ASHRAE.ddy' and save the '_ASHRAE.ddy' as the regular '.ddy' file. This is # because the ASHRAE .ddy file includes sizing information not included in the regular .ddy file for future # weather data files. Unfortunately, openstudio-standards just looks for the regular .ddy file for sizing # information which is why the switch is done. weather_loc = (File.basename(zipped_file).to_s)[0..-5] puts "Renaming #{weather_loc}.ddy as #{weather_loc}_orig.ddy and #{weather_loc}_ASHRAE.ddy as #{weather_loc}.ddy." puts "This is so that the design weather information in the #{weather_loc}_ASHRAE.ddy file is used." orig_ddy_name = File.join(weather_dir, (weather_loc + ".ddy")) ashrae_ddy_name = File.join(weather_dir, (weather_loc + "_ASHRAE.ddy")) rev_ddy_name = File.join(weather_dir, (weather_loc + "_orig.ddy")) FileUtils.cp(orig_ddy_name, rev_ddy_name) FileUtils.cp(ashrae_ddy_name, orig_ddy_name) end # Return true if everything worked out return true end
Determines the baseline fan impeller efficiency based on the specified fan type.
@return [Double] impeller efficiency (0.0 to 1.0) @todo Add fan type to data model and modify this method
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1330 def fan_baseline_impeller_efficiency(fan) # Assume that the fan efficiency is 65% for normal fans # @todo add fan type to fan data model # and infer impeller efficiency from that? # or do we always assume a certain type of # fan impeller for the baseline system? # @todo check COMNET and T24 ACM and PNNL 90.1 doc fan_impeller_eff = 0.65 return fan_impeller_eff end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1529 def fan_constant_volume_apply_prototype_fan_pressure_rise(fan_constant_volume) fan_constant_volume.setPressureRise(get_standards_constant('fan_constant_volume_pressure_rise_value')) return true end
Determines the minimum fan motor efficiency and nominal size for a given motor bhp. This should be the total brake horsepower with any desired safety factor already included. This method picks the next nominal motor catgory larger than the required brake horsepower, and the efficiency is based on that size. For example, if the bhp = 6.3, the nominal size will be 7.5HP and the efficiency for 90.1-2010 will be 91.7% from Table 10.8B. This method assumes 4-pole, 1800rpm totally-enclosed fan-cooled motors.
@param motor_bhp [Double] motor brake horsepower (hp) @return [Array<Double>] minimum motor efficiency (0.0 to 1.0), nominal horsepower
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1353 def fan_standard_minimum_motor_efficiency_and_size(fan, motor_bhp) fan_motor_eff = 0.85 nominal_hp = motor_bhp # Don't attempt to look up motor efficiency # for zero-hp fans, which may occur when there is no # airflow required for a particular system, typically # heated-only spaces with high internal gains # and no OA requirements such as elevator shafts. return [fan_motor_eff, 0] if motor_bhp == 0.0 # Lookup the minimum motor efficiency motors_table = @standards_data['motors'] # Assuming all fan motors are 4-pole ODP motor_use = 'FAN' motor_type = '' if (fan.class.name == 'OpenStudio::Model::FanConstantVolume') || (fan.class.name == 'OpenStudio::Model::FanOnOff') motor_type = 'CONSTANT' elsif fan.class.name == 'OpenStudio::Model::FanVariableVolume' # Is this a return or supply fan if fan.name.to_s.include?('Supply') motor_type += 'VARIABLE-SUPPLY' elsif fan.name.to_s.include?('Return') motor_type += 'VARIABLE-RETURN' end # 0.909 corrects for 10% over sizing implemented upstream # 0.7457 is to convert from bhp to kW fan_power_kw = 0.909 * 0.7457 * motor_bhp power_vs_flow_curve_name = if fan_power_kw >= 25.0 'VarVolFan-FCInletVanes-NECB2011-FPLR' elsif fan_power_kw >= 7.5 && fan_power_kw < 25 'VarVolFan-AFBIInletVanes-NECB2011-FPLR' else 'VarVolFan-AFBIFanCurve-NECB2011-FPLR' end power_vs_flow_curve = model_add_curve(fan.model, power_vs_flow_curve_name) fan.setFanPowerMinimumFlowRateInputMethod('Fraction') fan.setFanPowerCoefficient5(0.0) fan.setFanPowerMinimumFlowFraction(power_vs_flow_curve.minimumValueofx) fan.setFanPowerCoefficient1(power_vs_flow_curve.coefficient1Constant) fan.setFanPowerCoefficient2(power_vs_flow_curve.coefficient2x) fan.setFanPowerCoefficient3(power_vs_flow_curve.coefficient3xPOW2) fan.setFanPowerCoefficient4(power_vs_flow_curve.coefficient4xPOW3) elsif fan.class.name == 'OpenStudio::Model::FanZoneExhaust' motor_type = 'CONSTANT-RETURN' else raise('') end search_criteria = { 'motor_use' => motor_use, 'motor_type' => motor_type, 'number_of_poles' => 4.0, 'type' => 'Enclosed' } # Exception for small fans, including # zone exhaust, fan coil, and fan powered terminals. # In this case, use the 0.5 HP for the lookup. if fan_small_fan?(fan) nominal_hp = 0.5 else motor_properties = model_find_object(motors_table, search_criteria, motor_bhp) if motor_properties.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{fan.name}, could not find motor properties using search criteria: #{search_criteria}, motor_bhp = #{motor_bhp} hp.") return [fan_motor_eff, nominal_hp] end nominal_hp = motor_properties['maximum_capacity'].to_f.round(1) # If the biggest fan motor size is hit, use the highest category efficiency if nominal_hp == 9999.0 OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Fan', "For #{fan.name}, there is no greater nominal HP. Use the efficiency of the largest motor category.") nominal_hp = motor_bhp end # Round to nearest whole HP for niceness if nominal_hp >= 2 nominal_hp = nominal_hp.round end end # Get the efficiency based on the nominal horsepower # Add 0.01 hp to avoid search errors. motor_properties = model_find_object(motors_table, search_criteria, nominal_hp + 0.01) if motor_properties.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{fan.name}, could not find nominal motor properties using search criteria: #{search_criteria}, motor_hp = #{nominal_hp} hp.") return [fan_motor_eff, nominal_hp] end fan_motor_eff = motor_properties['nominal_full_load_efficiency'] return [fan_motor_eff, nominal_hp] end
Sets the fan pressure rise based on the Prototype buildings inputs which are governed by the flow rate coming through the fan and whether the fan lives inside a unit heater, PTAC, etc.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1546 def fan_variable_volume_apply_prototype_fan_pressure_rise(fan_variable_volume) # 1000 Pa for supply fan and 458.33 Pa for return fan (accounts for efficiency differences between two fans) if fan_variable_volume.name.to_s.include?('Supply') sfan_deltaP = get_standards_constant('supply_fan_variable_volume_pressure_rise_value') fan_variable_volume.setPressureRise(sfan_deltaP) elsif fan_variable_volume.name.to_s.include?('Return') rfan_deltaP = get_standards_constant('return_fan_variable_volume_pressure_rise_value') fan_variable_volume.setPressureRise(rfan_deltaP) end return true end
Determines whether there is a requirement to have a VSD or some other method to reduce fan power at low part load ratios.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1506 def fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume) part_load_control_required = false return part_load_control_required end
This method determines where the mechanical room is in a building. Mechanical rooms are assumed to be conditioned spaces that are not plenums. It goes through all of the spaces in a model ignoring unconditioned spaces and plenums. It then determines the floor of the space, and the centroid of the floor. It accumulates space floor area*floor centroid information and total floor area. It uses this to determine the centroid of the building. It also notes if the space has a space type with âElectrical/Mechanicalâ in it. If it does then it assumes that this is a mechanical room and tracks it separately. Once it has gone through all of the spaces it determines if any mechanical rooms were found. If some were found it finds the lowest one in the building (if more than one) and returns that along with all of the conditioned, non-plenum, spaces. If none were found then it searches for the lowest space in the building closest to the building centroid and returns that as the centroid (along with eth conditioned, non-plenum, spaces).
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 570 def find_mech_room(model) cond_spaces = [] total_peak_flow = 0 mech_rooms = [] check_spaces = nil building_centre = Array.new(3, 0) total_peak_flow = 0 lowest_space = 1000000000000000 sp_func_regex = Regexp.new('Space Function') mech_regex = Regexp.new('Electrical/Mechanical') mech_flag = false index = 0 model.getSpaces.sort.each do |space| spaceType_name = space.spaceType.get.nameString sp_type = spaceType_name[15..-1] # Including regular expressions in the following match for cases where extra characters, which do not belong, are # added to either the space type in the model or the space type reference file. sp_type_info = @standards_data['tables']['space_types']['table'].detect do |data| (Regexp.new(data['space_type'].to_s.upcase).match(sp_type.upcase) || Regexp.new(sp_type.upcase).match(data['space_type'].to_s.upcase) || (data['space_type'].to_s.upcase == sp_type.upcase)) && (data['building_type'].to_s == 'Space Function') end if sp_type_info.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', "The space type called #{sp_type} could not be found. Please check that the schedules.json file is available and that the space types are spelled correctly") next end # Determine if space is heated or cooled via spacetype heating or cooling setpoints also checking if the space is # a plenum by checking if there is a hvac system associtated with it if sp_type_info['heating_setpoint_schedule'].nil? heated = false else heated = true end if sp_type_info['cooling_setpoint_schedule'].nil? cooled = false else cooled = true end if (sp_type_info['necb_hvac_system_selection_type'] == '- undefined -') || /undefined/.match(sp_type_info['necb_hvac_system_selection_type']) not_plenum = false else not_plenum = true end # Determine the bottom surface of the space and calculate it's centroid. Although the mech room is assumed to # be in a space that conditioned and is not a plenum (or attic space) all spaces in the building may not be. # Doing the following to determine the centroid for the entire building (including unconditioned or plenum # spaces). # Get the coordinates of the origin for the space (the coordinates of points in the space are relative to this). xOrigin = space.xOrigin yOrigin = space.yOrigin zOrigin = space.zOrigin # Get the surfaces for the space. space_surfaces = space.surfaces # Find the floor (aka the surface with the lowest centroid). min_surf = space_surfaces.min_by { |sp_surface| sp_surface.centroid.z.to_f } # The following is added to determine the overall floor centroid because some spaces have floors composed of more than one surface. floor_centroid = [0, 0, 0] space_surfaces.each do |sp_surface| if min_surf.centroid.z.to_f == sp_surface.centroid.z.to_f floor_centroid[0] = floor_centroid[0] + sp_surface.centroid.x.to_f * sp_surface.grossArea.to_f floor_centroid[1] = floor_centroid[1] + sp_surface.centroid.y.to_f * sp_surface.grossArea.to_f floor_centroid[2] = floor_centroid[2] + sp_surface.grossArea end end floor_centroid[0] = floor_centroid[0] / floor_centroid[2] floor_centroid[1] = floor_centroid[1] / floor_centroid[2] if lowest_space > (min_surf.centroid.z.to_f + zOrigin) lowest_space = min_surf.centroid.z.to_f + zOrigin end # This part is used to determine the overall x, y centre of the building. This is determined by summing the x # and y components times the floor area and diving by the total floor area. This is only for conditioned spaces. building_centre[0] += (floor_centroid[0] + xOrigin) * floor_centroid[2] building_centre[1] += (floor_centroid[1] + yOrigin) * floor_centroid[2] building_centre[2] += (floor_centroid[2]) # Check if the space is conditioned and not a plenum. If it is then add it to the list of conditioned, non-plenum, # spaces and check if it has a 'Mechanical/Electrical' space type. Note that the mech room is assumed to # be in a space that conditioned and is not a plenum (or attic space). if (heated == true || cooled == true) && (not_plenum == true) if mech_regex.match(spaceType_name) mech_rooms << index end cond_space = { 'space_name' => space.nameString, 'space' => space, 'space_centroid' => [floor_centroid[0] + xOrigin, floor_centroid[1] + yOrigin, min_surf.centroid.z.to_f + zOrigin], 'building_cent_dist' => 0 } cond_spaces << cond_space index += 1 end end return [cond_spaces[mech_rooms[0]], cond_spaces] if mech_rooms.size == 1 if mech_rooms.size > 1 check_spaces = [] lowest_space = 10000000000000 mech_rooms.each do |mech_room| check_spaces << cond_spaces[mech_room] if cond_spaces[mech_room]['space_centroid'][2].to_f < lowest_space lowest_space = cond_spaces[mech_room]['space_centroid'][2].to_f end end else check_spaces = cond_spaces end # If no heated or cooled spaces were found then return false if cond_spaces.empty? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', 'No heated or cooled spaces types could be found which were not also a plenum.') return false end # This is where the average happens building_centre[0] /= building_centre[2] building_centre[1] /= building_centre[2] # Go through each space on the lowest floor of the building and determine the distance between the centroid of the # space's floors and the center of the building I calculated just above. centre_spaces = [] check_spaces.each do |check_space| if check_space['space_centroid'][2] == lowest_space check_space['building_cent_dist'] = Math.sqrt(((check_space['space_centroid'][0] - building_centre[0])**2) + ((check_space['space_centroid'][1] - building_centre[1])**2)) centre_spaces << check_space end end # Determine which of the floor spaces is closest to the centre of the building and that one becomes the location of # the mechanical room. centre_space = centre_spaces.min_by { |dist| dist['building_cent_dist'].round(1) } return [centre_space, cond_spaces] end
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 531 def friction_factor(re_pipe, relative_rough) # This method determines the Darcy-Weisbach friction factor assuming the pipe is circular and filled. if re_pipe <= 2100 # Laminar flow use the Uagen-Poiseuille equation. https://neutrium.net/fluid_flow/pressure-loss-in-pipe # accessed 2018-07-25. f = 64.to_f / re_pipe.to_f elsif re_pipe > 2100 && re_pipe <= 4000 # In the transition flow region I interpolate by Reynolds number between laminar and turbulent regimes. Yeah, that's # crap but if you can come up with something better you are welcome to replace what I have below. flam = 64.to_f / 2100.to_f pipe_rough_fact = relative_rough / 3.7 factor_A = -2 * Math.log10(pipe_rough_fact + (12.to_f / 4000.to_f)) factor_B = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_A) / 4000)) factor_C = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_B) / 4000)) fturb = 1 / ((factor_A - (((factor_B - factor_A)**2) / (factor_C - 2 * factor_B + factor_A)))**2) re_int = (re_pipe - 2100.to_f) / 1900.to_f f = ((fturb - flam) * re_int) + flam elsif re_pipe > 4000 # Turbulent flow use Serghide's Equation which I got from https://neutrium.net/fluid_flow/pressure-loss-in-pipe # accessed 2018-07-25. Apparently it is good for 4000 < Re < 1x10^10 and relative roughness between 1x10-7 and 1. pipe_rough_fact = relative_rough / 3.7 factor_A = -2 * Math.log10(pipe_rough_fact + (12 / re_pipe)) factor_B = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_A) / re_pipe)) factor_C = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_B) / re_pipe)) f = 1 / ((factor_A - (((factor_B - factor_A)**2) / (factor_C - 2 * factor_B + factor_A)))**2) end return f end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 157 def get_all_spacetype_names return @standards_data['space_types'].map { |space_types| [space_types['building_type'], space_types['space_type']] } end
Define ScheduleTypeLimits for Any_Number_ppm @todo (upon other BTAP
tasks) This function can be added to btap/schedules.rb > module StandardScheduleTypeLimits
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1741 def get_any_number_ppm(model) name = 'Any_Number_ppm' any_number_ppm_schedule_type_limits = model.getScheduleTypeLimitsByName(name) return any_number_ppm_schedule_type_limits.get unless any_number_ppm_schedule_type_limits.empty? any_number_ppm_schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) any_number_ppm_schedule_type_limits.setName(name) any_number_ppm_schedule_type_limits.setNumericType('CONTINUOUS') any_number_ppm_schedule_type_limits.setUnitType('Dimensionless') any_number_ppm_schedule_type_limits.setLowerLimitValue(400.0) any_number_ppm_schedule_type_limits.setUpperLimitValue(1000.0) return any_number_ppm_schedule_type_limits end
This model gets the climate zone column index from tables 3.2.2.x @author phylroy.lopez@nrcan.gc.ca @param hdd [Float] @return [Fixnum] climate zone 4-8
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1333 def get_climate_zone_index(hdd) # check for climate zone index from NECB 3.2.2.X case hdd when 0..2999 then return 0 # climate zone 4 when 3000..3999 then return 1 # climate zone 5 when 4000..4999 then return 2 # climate zone 6 when 5000..5999 then return 3 # climate zone 7a when 6000..6999 then return 4 # climate zone 7b when 7000..1000000 then return 5 # climate zone 8 end end
This model gets the climate zone name and returns the climate zone string. @author phylroy.lopez@nrcan.gc.ca @param hdd [Float] @return [Fixnum] climate zone 4-8
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1355 def get_climate_zone_name(hdd) case get_climate_zone_index(hdd) when 0 then return '4' when 1 then return '5' # climate zone 5 when 2 then return '6' # climate zone 6 when 3 then return '7a' # climate zone 7a when 4 then return '7b' # climate zone 7b when 5 then return '8' # climate zone 8 end end
This method is needed to the space_type_apply_internal_loads
space needed for the calculation of atriumsâ LPD when LED lighting is used in atriums. ***END***
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1952 def get_max_space_height_for_space_type(space_type:) # initialize return value to zero. max_space_height_for_space_type = 0.0 # Interate through all spaces in model.. not just ones that have space type defined.. Is this right sara? space_type.spaces.sort.each do |space| # Get only the wall type surfaces and iterate throught them. space.surfaces.sort.select(&:surfaceType == 'Wall').each do |wall_surface| # Find the vertex with the max z value. vertex_with_max_height = wall_surface.vertices.max_by(&:z) # replace max if this surface has something bigger. max_space_height_for_space_type = vertex_with_max_height.z if vertex_with_max_height.z > max_space_height_for_space_type end end return max_space_height_for_space_type end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 178 def get_necb_hdd18(model:, necb_hdd: true) max_distance_tolerance = 500000 min_distance = 100000000000000.0 necb_closest = nil weather_file_path = model.weatherFile.get.path.get.to_s epw_file = model.weatherFile.get.file.get stat_file_path = weather_file_path.gsub('.epw', '.stat') stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) # If necb_hdd is false use the information in the .stat file associated with the.epw file. unless necb_hdd return stat_file.hdd18 end # this extracts the table from the json database. necb_2015_table_c1 = @standards_data['tables']['necb_2015_table_c1']['table'] necb_2015_table_c1.each do |necb| next if necb['lat_long'].nil? # Need this until Tyson cleans up table. dist = distance([epw_file.latitude, epw_file.longitude], necb['lat_long']) if min_distance > dist min_distance = dist necb_closest = necb end end if ((min_distance / 1000.0) > max_distance_tolerance) && !stat_file.hdd18.nil? puts "Could not find close NECB HDD from Table C1 < #{max_distance_tolerance}km. Closest city is #{min_distance / 1000.0}km away. Using epw hdd18 instead." return stat_file.hdd18 else dist_clause = "%.2f % #{(min_distance / 1000.0)}" puts "INFO:NECB HDD18 of #{necb_closest['degree_days_below_18_c'].to_f} at nearest city #{necb_closest['city']},#{necb_closest['province']}, at a distance of " + dist_clause + 'km from epw location. Ref: nbc_2015_table_c1' return necb_closest['degree_days_below_18_c'].to_f end end
Determines what system index number is required for the spaceâs spacetype by NECB rules.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 684 def get_necb_spacetype_system_selection(space) space_type_table = @standards_data['space_types'] space_type_data = model_find_object(space_type_table, 'space_type' => space.spaceType.get.standardsSpaceType.get, 'building_type' => space.spaceType.get.standardsBuildingType.get) if space_type_data.nil? raise("Could not find space_type_data for #{{ 'space_type' => space.spaceType.get.standardsSpaceType.get, 'building_type' => space.spaceType.get.standardsBuildingType.get }} ") end # identify space-system_index and assign the right NECB system type 1-7. necb_hvac_system_selection_table = @standards_data['necb_hvac_system_selection_type'] necb_hvac_system_select = necb_hvac_system_selection_table.detect do |curr_necb_hvac_system_select| curr_necb_hvac_system_select['necb_hvac_system_selection_type'] == space_type_data['necb_hvac_system_selection_type'] && curr_necb_hvac_system_select['min_stories'] <= space.model.getBuilding.standardsNumberOfAboveGroundStories.get && curr_necb_hvac_system_select['max_stories'] >= space.model.getBuilding.standardsNumberOfAboveGroundStories.get && curr_necb_hvac_system_select['min_cooling_capacity_kw'] <= stored_space_cooling_load(space) && curr_necb_hvac_system_select['max_cooling_capacity_kw'] >= stored_space_cooling_load(space) end raise('could not find system for given spacetype') if necb_hvac_system_select.nil? return necb_hvac_system_select['system_type'] end
Determines what system index number is required for the thermal zone based on the spacetypes it contains
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 708 def get_necb_thermal_zone_system_selection(tz) systems = [] tz.spaces.each do |space| systems << get_necb_spacetype_system_selection(space) end systems.uniq! systems.compact! raise('This thermal zone spaces require different systems.') if systems.size > 1 return systems.first end
The below method is for the âmodel_add_daylighting_controlsâ method
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1969 def get_parameters_sidelighting(daylight_space:, floor_surface:, floor_vertices:, floor_area:, primary_sidelighted_area:, area_weighted_vt_handle:, window_area_sum:) daylight_space.surfaces.sort.each do |surface| ##### Get the vertices of each exterior wall of the daylight_space on the floor ##### (these vertices will be used to calculate daylight_space depth in relation to the exterior wall, and ##### the distance of the window to vertical walls on each side of the window) if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'Wall' wall_vertices_x_on_floor = [] wall_vertices_y_on_floor = [] surface_z_min = [surface.vertices[0].z, surface.vertices[1].z, surface.vertices[2].z, surface.vertices[3].z].min surface.vertices.each do |vertex| # puts vertex.z if vertex.z == surface_z_min && surface_z_min == floor_vertices[0][0].z wall_vertices_x_on_floor << vertex.x wall_vertices_y_on_floor << vertex.y end end end if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'Wall' && surface_z_min == floor_vertices[0][0].z ##### Calculate the daylight_space depth in relation to the considered exterior wall. ##### To calculate daylight_space depth, first get the floor vertices which are on the opposite side of the considered exterior wall. floor_vertices_x_wall_opposite = [] floor_vertices_y_wall_opposite = [] floor_vertices[0].each do |floor_vertex| if (floor_vertex.x != wall_vertices_x_on_floor[0] && floor_vertex.y != wall_vertices_y_on_floor[0]) || (floor_vertex.x != wall_vertices_x_on_floor[1] && floor_vertex.y != wall_vertices_y_on_floor[1]) floor_vertices_x_wall_opposite << floor_vertex.x floor_vertices_y_wall_opposite << floor_vertex.y end end ##### To calculate daylight_space depth, second calculate floor length on both sides: (1) exterior wall side, (2) on the opposite side of the exterior wall floor_width_wall_side = Math.sqrt((wall_vertices_x_on_floor[0] - wall_vertices_x_on_floor[1])**2 + (wall_vertices_y_on_floor[0] - wall_vertices_y_on_floor[1])**2) floor_width_wall_opposite = Math.sqrt((floor_vertices_x_wall_opposite[0] - floor_vertices_x_wall_opposite[1])**2 + (floor_vertices_y_wall_opposite[0] - floor_vertices_y_wall_opposite[1])**2) ##### Now, daylight_space depth can be calculated using the floor area and two lengths of the floor (note that these two lengths are in parallel to each other). daylight_space_depth = 2 * floor_area / (floor_width_wall_side + floor_width_wall_opposite) ##### Loop through the windows (including fixed and operable ones) to get window specification (width, height, area, visible transmittance (VT)), and area-weighted VT surface.subSurfaces.sort.each do |subsurface| # puts subsurface.name.to_s if subsurface.subSurfaceType == 'FixedWindow' || subsurface.subSurfaceType == 'OperableWindow' window_vt = subsurface.visibleTransmittance window_vt = window_vt.get window_area = subsurface.netArea window_area_sum += window_area area_weighted_vt_handle += window_area * window_vt window_vertices = subsurface.vertices if window_vertices[0].z.round(2) == window_vertices[1].z.round(2) window_width = Math.sqrt((window_vertices[0].x - window_vertices[1].x)**2.0 + (window_vertices[0].y - window_vertices[1].y)**2.0) else window_width = Math.sqrt((window_vertices[1].x - window_vertices[2].x)**2.0 + (window_vertices[1].y - window_vertices[2].y)**2.0) end window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2) primary_sidelighted_area_depth = [window_head_height, daylight_space_depth].min # as per NECB2011: 4.2.2.9. ##### Calculate the distance of the window to vertical walls on each side of the window (this is used to determine the sidelighted area's width). window_vertices_on_floor = [] window_vertices.each do |vertex| window_vertices_on_floor << floor_surface.plane.project(vertex) end window_wall_distance_side1 = [Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[0].x)**2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[0].y)**2.0), Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[2].x)**2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[2].y)**2.0), 0.6].min # 0.6 m as per NECB2011: 4.2.2.9. window_wall_distance_side2 = [Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[0].x)**2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[0].y)**2.0), Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[2].x)**2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[2].y)**2.0), 0.6].min # 0.6 m as per NECB2011: 4.2.2.9. primary_sidelighted_area_width = window_wall_distance_side1 + window_width + window_wall_distance_side2 primary_sidelighted_area += primary_sidelighted_area_depth * primary_sidelighted_area_width # if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow" end # surface.subSurfaces.each do |subsurface| end # if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" && surface_z_min == floor_vertices[0][0].z end # daylight_space.surfaces.each do |surface| end return primary_sidelighted_area, area_weighted_vt_handle, window_area_sum end
The below method is for the âmodel_add_daylighting_controlsâ method
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2051 def get_parameters_skylight(daylight_space:, skylight_area_weighted_vt_handle:, skylight_area_sum:, daylighted_under_skylight_area:) daylight_space.surfaces.sort.each do |surface| ##### Get roof vertices if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'RoofCeiling' roof_vertices = surface.vertices end ##### Loop through each subsurface to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space surface.subSurfaces.sort.each do |subsurface| if subsurface.subSurfaceType == 'Skylight' skylight_vt = subsurface.visibleTransmittance skylight_vt = skylight_vt.get skylight_area = subsurface.netArea skylight_area_sum += skylight_area skylight_area_weighted_vt_handle += skylight_area * skylight_vt ##### Get skylight vertices skylight_vertices = subsurface.vertices ##### Calculate skylight width and height skylight_width = Math.sqrt((skylight_vertices[0].x - skylight_vertices[1].x)**2.0 + (skylight_vertices[0].y - skylight_vertices[1].y)**2.0) skylight_length = Math.sqrt((skylight_vertices[0].x - skylight_vertices[3].x)**2.0 + (skylight_vertices[0].y - skylight_vertices[3].y)**2.0) ##### Get ceiling height assuming the skylight is flush with the ceiling ceiling_height = skylight_vertices[0].z ##### Calculate roof lengths ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions) roof_length_0 = Math.sqrt((roof_vertices[0].x - roof_vertices[1].x)**2.0 + (roof_vertices[0].y - roof_vertices[1].y)**2.0) roof_length_1 = Math.sqrt((roof_vertices[1].x - roof_vertices[2].x)**2.0 + (roof_vertices[1].y - roof_vertices[2].y)**2.0) roof_length_2 = Math.sqrt((roof_vertices[2].x - roof_vertices[3].x)**2.0 + (roof_vertices[2].y - roof_vertices[3].y)**2.0) roof_length_3 = Math.sqrt((roof_vertices[3].x - roof_vertices[0].x)**2.0 + (roof_vertices[3].y - roof_vertices[0].y)**2.0) ##### Find the skylight point that is the closest one to roof_vertex_0 ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions) roof_vertex_0_skylight_vertex_0 = Math.sqrt((roof_vertices[0].x - skylight_vertices[0].x)**2.0 + (roof_vertices[0].y - skylight_vertices[0].y)**2.0) roof_vertex_0_skylight_vertex_1 = Math.sqrt((roof_vertices[0].x - skylight_vertices[1].x)**2.0 + (roof_vertices[0].y - skylight_vertices[1].y)**2.0) roof_vertex_0_skylight_vertex_2 = Math.sqrt((roof_vertices[0].x - skylight_vertices[2].x)**2.0 + (roof_vertices[0].y - skylight_vertices[2].y)**2.0) roof_vertex_0_skylight_vertex_3 = Math.sqrt((roof_vertices[0].x - skylight_vertices[3].x)**2.0 + (roof_vertices[0].y - skylight_vertices[3].y)**2.0) roof_vertex_0_closest_distance = [roof_vertex_0_skylight_vertex_0, roof_vertex_0_skylight_vertex_1, roof_vertex_0_skylight_vertex_2, roof_vertex_0_skylight_vertex_3].min if roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_0 roof_vertex_0_closest_point = skylight_vertices[0] elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_1 roof_vertex_0_closest_point = skylight_vertices[1] elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_2 roof_vertex_0_closest_point = skylight_vertices[2] elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_3 roof_vertex_0_closest_point = skylight_vertices[3] end ##### Find the skylight point that is the closest one to roof_vertex_2 ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions) roof_vertex_2_skylight_vertex_0 = Math.sqrt((roof_vertices[2].x - skylight_vertices[0].x)**2.0 + (roof_vertices[2].y - skylight_vertices[0].y)**2.0) roof_vertex_2_skylight_vertex_1 = Math.sqrt((roof_vertices[2].x - skylight_vertices[1].x)**2.0 + (roof_vertices[2].y - skylight_vertices[1].y)**2.0) roof_vertex_2_skylight_vertex_2 = Math.sqrt((roof_vertices[2].x - skylight_vertices[2].x)**2.0 + (roof_vertices[2].y - skylight_vertices[2].y)**2.0) roof_vertex_2_skylight_vertex_3 = Math.sqrt((roof_vertices[2].x - skylight_vertices[3].x)**2.0 + (roof_vertices[2].y - skylight_vertices[3].y)**2.0) roof_vertex_2_closest_distance = [roof_vertex_2_skylight_vertex_0, roof_vertex_2_skylight_vertex_1, roof_vertex_2_skylight_vertex_2, roof_vertex_2_skylight_vertex_3].min if roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_0 roof_vertex_2_closest_point = skylight_vertices[0] elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_1 roof_vertex_2_closest_point = skylight_vertices[1] elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_2 roof_vertex_2_closest_point = skylight_vertices[2] elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_3 roof_vertex_2_closest_point = skylight_vertices[3] end ##### Calculate the vertical distance from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2 ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions) ##### For the calculation of each vertical distance: (1) first the area of the triangle is calculated knowing the cooridantes of its three corners; ##### (2) the vertical distance (i.e. triangle height) is calculated knowing the triangle area and the associated roof length. rv_0_triangle_0_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[1].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) - ((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[1].y))).abs rv_0_distance_0 = (2.0 * rv_0_triangle_0_area) / roof_length_0 rv_0_triangle_3_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[3].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) - ((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[3].y))).abs rv_0_distance_3 = (2.0 * rv_0_triangle_3_area) / roof_length_3 rv_2_triangle_1_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[1].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) - ((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[1].y))).abs rv_2_distance_1 = (2.0 * rv_2_triangle_1_area) / roof_length_1 rv_2_triangle_2_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[3].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) - ((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[3].y))).abs rv_2_distance_2 = (2.0 * rv_2_triangle_2_area) / roof_length_2 ##### Set the vertical distances from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2 distance_1 = rv_0_distance_0 distance_2 = rv_0_distance_3 distance_3 = rv_2_distance_1 distance_4 = rv_2_distance_2 ##### Calculate the width and length of the daylighted area under the skylight as per NECB2011: 4.2.2.5. ##### Note: In the below loops, if any exterior walls has window(s), the width and length of the daylighted area under the skylight are re-calculated as per NECB2011: 4.2.2.5. daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4].min daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3].min ##### As noted above, the width and length of the daylighted area under the skylight are re-calculated (as per NECB2011: 4.2.2.5.), if any exterior walls has window(s). ##### To this end, the window_head_height should be calculated, as below: daylight_space.surfaces.sort.each do |curr_surface| if curr_surface.outsideBoundaryCondition == 'Outdoors' && curr_surface.surfaceType == 'Wall' wall_vertices_on_floor_x = [] wall_vertices_on_floor_y = [] wall_vertices = curr_surface.vertices if wall_vertices[0].z == wall_vertices[1].z wall_vertices_on_floor_x << wall_vertices[0].x wall_vertices_on_floor_x << wall_vertices[1].x wall_vertices_on_floor_y << wall_vertices[0].y wall_vertices_on_floor_y << wall_vertices[1].y elsif wall_vertices[0].z == wall_vertices[3].z wall_vertices_on_floor_x << wall_vertices[0].x wall_vertices_on_floor_x << wall_vertices[3].x wall_vertices_on_floor_y << wall_vertices[0].y wall_vertices_on_floor_y << wall_vertices[3].y end window_vertices = subsurface.vertices window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2) ##### Calculate the exterior wall length (on the floor) exterior_wall_length = Math.sqrt((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1])**2.0 + (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1])**2.0) ##### Calculate the vertical distance of skylight_vertices[0] projection onto the roof/floor to the exterior wall skylight_vertex_0_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[0].y)) - ((wall_vertices_on_floor_x[0] - skylight_vertices[0].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs skylight_vertex_0_distance = (2.0 * skylight_vertex_0_triangle_area) / exterior_wall_length ##### Calculate the vertical distance of skylight_vertices[1] projection onto the roof/floor to the exterior wall skylight_vertex_1_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[1].y)) - ((wall_vertices_on_floor_x[0] - skylight_vertices[1].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs skylight_vertex_1_distance = (2.0 * skylight_vertex_1_triangle_area) / exterior_wall_length ##### Calculate the vertical distance of skylight_vertices[3] projection onto the roof/floor to the exterior wall skylight_vertex_3_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[3].y)) - ((wall_vertices_on_floor_x[0] - skylight_vertices[3].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs skylight_vertex_3_distance = (2.0 * skylight_vertex_3_triangle_area) / exterior_wall_length ##### Loop through the subsurfaces that has exterior windows to re-calculate the width and length of the daylighted area under the skylight curr_surface.subSurfaces.sort.each do |curr_subsurface| if curr_subsurface.subSurfaceType == 'FixedWindow' || curr_subsurface.subSurfaceType == 'OperableWindow' if skylight_vertex_0_distance == skylight_vertex_1_distance # skylight_01 is in parellel to the exterior wall if skylight_vertex_0_distance.round(2) == distance_2.round(2) daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2, distance_2 - window_head_height].min + [0.7 * ceiling_height, distance_3].min elsif skylight_vertex_0_distance.round(2) == distance_3.round(2) daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3, distance_3 - window_head_height].min end elsif skylight_vertex_0_distance == skylight_vertex_3_distance # skylight_03 is in parellel to the exterior wall if skylight_vertex_0_distance.round(2) == distance_1.round(2) daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1, distance_1 - window_head_height].min + [0.7 * ceiling_height, distance_4].min elsif skylight_vertex_0_distance.round(2) == distance_4.round(2) daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4, distance_4 - window_head_height].min end # if skylight_vertex_0_distance == skylight_vertex_1_distance end daylighted_under_skylight_area += daylighted_under_skylight_length * daylighted_under_skylight_width # if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow" end # surface.subSurfaces.each do |subsurface| end # if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" end # daylight_space.surfaces.each do |surface| end # if subsurface.subSurfaceType == "Skylight" end # surface.subSurfaces.each do |subsurface| end # daylight_space.surfaces.each do |surface| end return daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 140 def get_qaqc_table(table_name:, search_criteria: nil) return_objects = nil table = @qaqc_data['tables'][table_name] raise("could not find #{table_name} in qaqc table database. ") if table.nil? return table if search_criteria.nil? # removed table beause need to use the object['refs'] rows = table['table'] search_criteria.each do |key, value| rows = rows.select { |row| row[key] == value } end return rows end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 4 def get_sql_table_to_json(model, report_name, report_for_string, table_name) table = [] query_row_names = " SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' AND ReportForString='#{report_for_string}' AND TableName='#{table_name}'" row_names = model.sqlFile.get.execAndReturnVectorOfString(query_row_names).get # get Columns query_col_names = " SELECT DISTINCT ColumnName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' AND ReportForString='#{report_for_string}' AND TableName='#{table_name}'" col_names = model.sqlFile.get.execAndReturnVectorOfString(query_col_names).get # get units query_unit_names = " SELECT DISTINCT Units FROM tabulardatawithstrings WHERE ReportName='#{report_name}' AND ReportForString='#{report_for_string}' AND TableName='#{table_name}'" unit_names = model.sqlFile.get.execAndReturnVectorOfString(query_unit_names).get row_names.each do |row| next if row.nil? || row == '' row_hash = {} row_hash[:name] = row col_names.each do |col| unit_names.each do |unit| query = " SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' AND ReportForString='#{report_for_string}' AND TableName='#{table_name}' AND RowName='#{row}' AND ColumnName='#{col}' AND Units='#{unit}' " column_name = col.to_s.gsub(/\s+/, '_').downcase # If the column name is "additional_fuel" and the file contains a boiler with a FuelOilNo2 fuel type assume # the column name should be "fueloilno2". if column_name.include? 'additional_fuel' model.getPlantLoops.sort.each do |iplantloop| boilers = iplantloop.components.select { |icomponent| icomponent.to_BoilerHotWater.is_initialized } column_name = 'fueloilno2' unless boilers.select { |boiler| boiler.to_BoilerHotWater.get.fuelType.to_s == 'FuelOilNo2' }.empty? end end column_name += "_#{unit}" if unit != '' value = model.sqlFile.get.execAndReturnFirstString(query) next if value.empty? || value.get.nil? value = value.get.strip # check is value is a number if (begin Float(value) rescue StandardError false end) && value.to_f != 0 row_hash[column_name] = value.to_f # Check if value is a date elsif unit == '' && value =~ /\d\d-\D\D\D-\d\d:\d\d/ row_hash[column_name] = DateTime.parse(value) # skip if value in an empty string or a zero value elsif value != '' && value != '0.00' row_hash[column_name] = value end end end if row_hash.size > 1 table << row_hash end end result = { report_name: report_name, report_for_string: report_for_string, table_name: table_name, table: table } return result end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 106 def get_sql_tables_to_json(model) sql_data = [] sql_data << get_sql_table_to_json(model, 'AnnualBuildingUtilityPerformanceSummary', 'Entire Facility', 'End Uses') sql_data << get_sql_table_to_json(model, 'AnnualBuildingUtilityPerformanceSummary', 'Entire Facility', 'Site and Source Energy') # sql_data << get_sql_table_to_json(model, "AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "On-Site Thermal Sources") # sql_data << get_sql_table_to_json(model, "AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "Comfort and Setpoint Not Met Summary") # sql_data << get_sql_table_to_json(model, "InputVerificationandResultsSummary", "Entire Facility", "Window-Wall Ratio") # sql_data << get_sql_table_to_json(model, "InputVerificationandResultsSummary", "Entire Facility", "Conditioned Window-Wall Ratio") # sql_data << get_sql_table_to_json(model, "InputVerificationandResultsSummary", "Entire Facility", "Skylight-Roof Ratio") # sql_data << get_sql_table_to_json(model, "DemandEndUseComponentsSummary", "Entire Facility", "End Uses") # sql_data << get_sql_table_to_json(model, "ComponentSizingSummary", "Entire Facility", "AirLoopHVAC") return sql_data end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 58 def get_standard_constant_value(constant_name:) puts 'do nothing' end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 129 def get_standards_constant(name) object = @standards_data['constants'][name] if object.nil? || object['value'].nil? raise("could not find #{name} in standards constants database. ") end return object['value'] end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 139 def get_standards_formula(name) object = @standards_data['formulas'][name] raise("could not find #{name} in standards formual database. ") if object.nil? || object['value'].nil? return object['value'] end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 50 def get_standards_table(table_name:) if @standards_data['tables'][table_name].nil? message = "Could not find table #{table_name} in database." OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.NECB', message) end @standards_data['tables'][table_name] end
Find the exposed perimeter of a floor surface. For each side of the floor loop through the walls and find the walls that share sides with the floor. Then sum the lengths of the sides of the walls that come in contact with sides of the floor. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 901 def get_surface_exp_per(floor,walls) floor_exp_per = 0.0 vert1 = floor.vertices[0] # loop through the indices of the floor surface for index in 1..floor.vertices.size if index < floor.vertices.size vert2 = floor.vertices[index] else vert2 = floor.vertices[0] end side_length = ((vert2.x-vert1.x)**2+(vert2.y-vert1.y)**2+(vert2.z-vert1.z)**2)**0.5 walls_exp_per = 0.0 walls.each do |wall| vert3 = wall.vertices[0] # loop through the indices of the wall surface for index2 in 1..wall.vertices.size-1 if index2 < wall.vertices.size vert4 = wall.vertices[index2] else vert4 = wall.vertices[0] end vert1_2_3_on_same_line = three_vertices_same_line_and_dir?(vert1,vert2,vert3) if vert1_2_3_on_same_line vert1_2_4_on_same_line = three_vertices_same_line_and_dir?(vert1,vert2,vert4) if vert1_2_4_on_same_line wall_width = ((vert4.x-vert3.x)**2+(vert4.y-vert3.y)**2+(vert4.z-vert3.z)**2)**0.5 walls_exp_per += wall_width end end vert3 = vert4 end end # increment the exposed perimeter of the floor. Limit the length of the walls in contact with the # side of the floor to the length of the side of the floor. floor_exp_per += [walls_exp_per,side_length].min vert1 = vert2 end return floor_exp_per end
This method handles looking for the epw_file in the github.com/canmet-energy/btap_weather repository. It checks for the epw_file in the historical data first. If it is not there then it looks in the future weather data. If it is not there either, it throws an error. epw_file (String
): The name of the epw file. The different weather files all share the same name as the epw file,
only the extension changes.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2272 def get_weather_file_from_repo(epw_file:) # Get just the weather file name without the extension weather_loc = epw_file[0..-5] # Get the url of the file containing the historical weather data file names in the repository and the repository # folder containing the files historic_weather_files_loc = @standards_data['constants']['historic_weather_file_list']['value'].to_s historic_git_folder = @standards_data['constants']['historic_weather_folder_url']['value'].to_s # Get the files from the repository success_flag = download_and_save_file(weather_list_url: historic_weather_files_loc, weather_loc: weather_loc, git_folder: historic_git_folder) return if success_flag # If the file could not be found in the historical data look for it with the future weather data. puts "Could not find #{epw_file} in historical weather data files, looking in future weather data files." future_weather_files_loc = @standards_data['constants']['future_weather_file_list']['value'].to_s future_git_folder = @standards_data['constants']['future_weather_folder_url']['value'].to_s success_flag = download_and_save_file(weather_list_url: future_weather_files_loc, weather_loc: weather_loc, git_folder: future_git_folder) return if success_flag raise("Could not locate the following file in the canmet/btap_weather repository or could not extract the data: #{epw_file}. Please check the spelling of the file or visit https://github.com/canmet-energy/btap_weather to see if the file exists.") end
This method is used to determine if there are single zones that can be grouped with zones of similar loads.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1224 def group_similar_zones_together(zones) total_zones_input = zones.size array_of_array_of_zones = [] accounted_for = [] # Go through other zones to see if there are similar zones with similar loads on the same floor that can be grouped. zones.each do |zone| similar_array_of_zones = [] next if accounted_for.include?(zone.name.to_s) similar_array_of_zones << zone accounted_for << zone.name.to_s zones.each do |zone_target| unless accounted_for.include?(zone_target.name.to_s) if are_zone_loads_similar?(zone_1: zone, zone_2: zone_target) similar_array_of_zones << zone_target accounted_for << zone_target.name.to_s end end end array_of_array_of_zones << similar_array_of_zones end total_zones_output = 0 array_of_array_of_zones.each do |curr_zones| total_zones_output += curr_zones.size end # puts total_zones_output # puts accounted_for.sort # sanity check. if total_zones_output != total_zones_input # puts JSON.pretty_generate(array_of_array_of_zones) # puts JSON.pretty_generate(accounted_for.sort) raise('') end return array_of_array_of_zones end
Sets the minimum effectiveness of the heat exchanger per the standard.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 376 def heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness(heat_exchanger_air_to_air_sensible_and_latent, erv_name = nil) # Assumed to be sensible and latent at all flow # This will now get data of the erv from the json file instead of hardcoding it. Defaults to NECB2011 erv we have been using. erv_name = 'NECB_Default' if erv_name.nil? erv_info = @standards_data['tables']['erv']['table'].detect { |item| item['erv_name'] == erv_name } raise("Could not find #{erv_name} in #{self.class.name} class' erv.json file or it's parents. The available ervs are #{@standards_data['tables']['erv']['table'].map { |item| item['erv_name'] }}") if erv_info.nil? heat_exchanger_air_to_air_sensible_and_latent.setHeatExchangerType(erv_info['HeatExchangerType']) heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(erv_info['SensibleEffectivenessat100HeatingAirFlow']) heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(erv_info['LatentEffectivenessat100HeatingAirFlow']) heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(erv_info['SensibleEffectivenessat100CoolingAirFlow']) heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(erv_info['LatentEffectivenessat100CoolingAirFlow']) if heat_exchanger_air_to_air_sensible_and_latent.model.version < OpenStudio::VersionString.new('3.8.0') heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(erv_info['SensibleEffectivenessat75HeatingAirFlow']) heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(erv_info['LatentEffectivenessat75HeatingAirFlow']) heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(erv_info['SensibleEffectivenessat75CoolingAirFlow']) heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(erv_info['LatentEffectivenessat75CoolingAirFlow']) else heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(erv_info['SensibleEffectivenessat75HeatingAirFlow']) unless erv_info['SensibleEffectivenessat75HeatingAirFlow'].zero? heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(erv_info['LatentEffectivenessat75HeatingAirFlow']) unless erv_info['LatentEffectivenessat75HeatingAirFlow'].zero? heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(erv_info['SensibleEffectivenessat75CoolingAirFlow']) unless erv_info['SensibleEffectivenessat75CoolingAirFlow'].zero? heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(erv_info['LatentEffectivenessat75CoolingAirFlow']) unless erv_info['LatentEffectivenessat75CoolingAirFlow'].zero? end heat_exchanger_air_to_air_sensible_and_latent.setSupplyAirOutletTemperatureControl(erv_info['SupplyAirOutletTemperatureControl']) heat_exchanger_air_to_air_sensible_and_latent.setFrostControlType(erv_info['FrostControlType']) heat_exchanger_air_to_air_sensible_and_latent.setEconomizerLockout(erv_info['EconomizerLockout']) heat_exchanger_air_to_air_sensible_and_latent.setThresholdTemperature(erv_info['ThresholdTemperature']) heat_exchanger_air_to_air_sensible_and_latent.setInitialDefrostTimeFraction(erv_info['InitialDefrostTimeFraction']) update_sys_name(heat_exchanger_air_to_air_sensible_and_latent.airLoopHVAC.get, sys_hr: 'erv') return true end
generates full qaqc.json
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 155 def init_qaqc(model) # load the qaqc.json files # This is currently disabled as most tests are now done using regression and unit tests.. but we may bring this back. # @qaqc_data = self.load_qaqc_database_new() # generate base qaqc hash qaqc = create_base_data(model) # performs the qaqc on the given base qaqc hash # necb_qaqc(qaqc, model) return qaqc end
Check if the space spactype is a dwelling unit as per NECB.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 667 def is_a_necb_dwelling_unit?(space) space_type_table = @standards_data['space_types'] space_type_data = model_find_object(space_type_table, 'template' => self.class.name, 'space_type' => space.spaceType.get.standardsSpaceType.get, 'building_type' => space.spaceType.get.standardsBuildingType.get) necb_hvac_system_selection_table = @standards_data['necb_hvac_system_selection_type'] necb_hvac_system_select = necb_hvac_system_selection_table.detect do |curr_necb_hvac_system_select| curr_necb_hvac_system_select['necb_hvac_system_selection_type'] == space_type_data['necb_hvac_system_selection_type'] && curr_necb_hvac_system_select['min_stories'] <= space.model.getBuilding.standardsNumberOfAboveGroundStories.get && curr_necb_hvac_system_select['max_stories'] >= space.model.getBuilding.standardsNumberOfAboveGroundStories.get end return necb_hvac_system_select['dwelling'] == true end
Check to see if this is a wet space that the NECB does not have a specified schedule or system for. Currently hardcoded to Locker room and washroom.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 661 def is_an_necb_storage_space?(space) # Hack! Should replace this with a proper table lookup. return space.spaceType.get.standardsSpaceType.get.include?('Storage') end
Check to see if this is a wet space that the NECB does not have a specified schedule or system for. Currently hardcoded to Locker room and washroom.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 654 def is_an_necb_wet_space?(space) # Hack! Should replace this with a proper table lookup. return space.spaceType.get.standardsSpaceType.get.include?('Locker room') || space.spaceType.get.standardsSpaceType.get.include?('Washroom') end
Check to see if this is a wildcard space that the NECB does not have a specified schedule or system for.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 641 def is_an_necb_wildcard_space?(space) space_type_table = @standards_data['space_types'] space_type_data = model_find_object(space_type_table, 'template' => self.class.name, 'space_type' => space.spaceType.get.standardsSpaceType.get, 'building_type' => space.spaceType.get.standardsBuildingType.get) raise(space.to_s) if space_type_data.nil? return space_type_data['necb_hvac_system_selection_type'] == 'Wildcard' end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 340 def load_building_type_from_library(building_type:) osm_model_path = File.absolute_path(File.join(__FILE__, '..', '..', '..', "necb/NECB2011/data/geometry/#{building_type}.osm")) model = false if File.file?(osm_model_path) model = BTAP::FileIO.load_osm(osm_model_path) model.getBuilding.setName(building_type) end return model end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 120 def load_qaqc_database_new # Combine the data from the JSON files into a single hash files = Dir.glob("#{File.dirname(__FILE__)}/qaqc_data/*.json").select { |e| File.file? e } @qaqc_data = {} @qaqc_data['tables'] = {} files.each do |file| # puts "loading qaqc data from #{file}" data = JSON.parse(File.read(file)) if !data['tables'].nil? @qaqc_data['tables'] = [*@qaqc_data['tables'], *data['tables']].to_h else @qaqc_data[data.keys.first] = data[data.keys.first] end end # Write test report file. test_result_file = File.join(File.dirname(__FILE__), '..', 'NECB2011_QAQC.json') File.open(test_result_file, 'w') { |f| f.write(JSON.pretty_generate(@qaqc_data)) } return @qaqc_data end
Combine the data from the JSON files into a single hash Load JSON files differently depending on whether loading from the OpenStudio CLI embedded filesystem or from typical gem installation
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 65 def load_standards_database_new @standards_data = {} @standards_data['tables'] = {} if __dir__[0] == ':' # Running from OpenStudio CLI embedded_files_relative('../common', /.*\.json/).each do |file| data = JSON.parse(EmbeddedScripting.getFileAsString(file)) if !data['tables'].nil? @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h else @standards_data[data.keys.first] = data[data.keys.first] end end else path = "#{File.dirname(__FILE__)}/../common/" raise 'Could not find common folder' unless Dir.exist?(path) files = Dir.glob("#{path}/*.json").select { |e| File.file? e } files.each do |file| data = JSON.parse(File.read(file)) if !data['tables'].nil? @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h else @standards_data[data.keys.first] = data[data.keys.first] end end end if __dir__[0] == ':' # Running from OpenStudio CLI embedded_files_relative('data/', /.*\.json/).each do |file| data = JSON.parse(EmbeddedScripting.getFileAsString(file)) if !data['tables'].nil? @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h else @standards_data[data.keys.first] = data[data.keys.first] end end else files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select { |e| File.file? e } files.each do |file| data = JSON.parse(File.read(file)) if !data['tables'].nil? @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h else @standards_data[data.keys.first] = data[data.keys.first] end end end # Write database to file. # File.open(File.join(File.dirname(__FILE__), '..', 'NECB2011.json'), 'w') {|f| f.write(JSON.pretty_generate(@standards_data))} return @standards_data end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1915 def look_up_csv_data(csv_fname, search_criteria) options = { headers: :first_row, converters: [:numeric] } unless File.exist?(csv_fname) raise "File: [#{csv_fname}] Does not exist" end # we'll save the matches here matches = nil # save a copy of the headers headers = nil CSV.open(csv_fname, 'r', **options) do |csv| # Since CSV includes Enumerable we can use 'find_all' # which will return all the elements of the Enumerble for # which the block returns true matches = csv.find_all do |row| match = true search_criteria.keys.each do |key| match &&= (row[key].strip == search_criteria[key].strip) end match end headers = csv.headers end # puts matches raise('More than one match') if matches.size > 1 puts "Zero matches found for [#{search_criteria}]" if matches.empty? # return matches[0] return matches[0] end
@author phylroy.lopez@nrcan.gc.ca @param hdd [Float] @return [Double] a constant float
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 286 def max_fwdr(hdd) # get formula from json database. return eval(get_standards_formula('fdwr_formula')) end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 102 def merge_recursively(a, b) a.merge(b) { |key, a_item, b_item| merge_recursively(a_item, b_item) } end
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 524 def model_add_construction_set_from_osm(model:, construction_set_name: 'BTAP-Mass', osm_path: File.absolute_path(File.join(__FILE__, '..', '..', 'common/construction_defaults.osm'))) # load resources model construction_library = BTAP::FileIO.load_osm(osm_path) if !construction_library.getDefaultConstructionSetByName(construction_set_name.to_s).is_initialized runner.registerError('Did not find the expected construction in library.') return false end selected_construction_set = construction_library.getDefaultConstructionSetByName(construction_set_name.to_s).get new_construction_set = selected_construction_set.clone(model).to_DefaultConstructionSet.get return new_construction_set end
Adds code-minimum constructions based on the building type as defined in the OpenStudio_Standards_construction_sets.json file. Where there is a separate construction set specified for the individual space type, this construction set will be created and applied to this space type, overriding the whole-building construction set.
@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 471 def model_add_constructions(model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying constructions') # Assign construction to adiabatic construction # Assign a material to all internal mass objects assign_contruction_to_adiabatic_surfaces(model) # The constructions lookup table uses a slightly different list of # building types. apply_building_default_constructionset(model) # Make a construction set for each space type, if one is specified # apply_default_constructionsets_to_spacetypes(climate_zone, model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying constructions') return true end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1372 def model_add_daylighting_controls(model:, daylighting_type:) return if daylighting_type == 'none' ##### Find spaces with exterior fenestration including fixed window, operable window, and skylight. daylight_spaces = [] daylight_spaces_target_illuminance_setpoint_hash = {} model.getSpaces.sort.each do |space| space.surfaces.sort.each do |surface| surface.subSurfaces.sort.each do |subsurface| if subsurface.outsideBoundaryCondition == 'Outdoors' && (subsurface.subSurfaceType == 'FixedWindow' || subsurface.subSurfaceType == 'OperableWindow' || subsurface.subSurfaceType == 'Skylight') daylight_spaces << space space_type = space.spaceType.get space_type_name = space.spaceType.get.name.to_s space_type_name = space_type_name.gsub('Space Function', '') # puts "space_type_name is #{space_type_name}" # Gather minimum illuminance level as per NECB lux_spacetype_data = @standards_data['tables']['space_types']['table'] standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil lux_space_type_properties = lux_spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) } if lux_space_type_properties.nil? raise("#{standards_building_type} for #{standards_space_type} was not found please verify the target_illuminance_setpoint database names match the space type names.") end target_illuminance_setpoint = lux_space_type_properties['target_illuminance_setpoint'].to_f daylight_spaces_target_illuminance_setpoint_hash[space.name.to_s] = target_illuminance_setpoint # subsurface.outsideBoundaryCondition == "Outdoors" && (subsurface.subSurfaceType == "FixedWindow" || "OperableWindow") end # surface.subSurfaces.each do |subsurface| end # space.surfaces.each do |surface| end # model.getSpaces.sort.each do |space| end ##### Remove duplicate spaces from the "daylight_spaces" array, as a daylighted space may have various fenestration types. daylight_spaces = daylight_spaces.uniq # puts "daylight_spaces are #{daylight_spaces}" if daylighting_type.nil? || daylighting_type == false || daylighting_type == 'none' || daylighting_type == 'NECB_Default' # puts daylighting sensors in the spaces as per NECB requirements; so some spaces may not have sensors ##### Create hashes for "Primary Sidelighted Areas", "Sidelighting Effective Aperture", "Daylighted Area Under Skylights", ##### and "Skylight Effective Aperture" for the whole model. ##### Each of these hashes will be used later in this function (i.e. model_add_daylighting_controls) ##### to provide a dictionary of daylighted space names and the associated value (i.e. daylighted area or effective aperture). primary_sidelighted_area_hash = {} sidelighting_effective_aperture_hash = {} daylighted_area_under_skylights_hash = {} skylight_effective_aperture_hash = {} ##### Calculate "Primary Sidelighted Areas" AND "Sidelighting Effective Aperture" as per NECB2011. # @todo consider removing overlapped sidelighted area daylight_spaces.sort.each do |daylight_space| primary_sidelighted_area = 0.0 area_weighted_vt_handle = 0.0 area_weighted_vt = 0.0 window_area_sum = 0.0 ##### Calculate floor area of the daylight_space and get floor vertices of the daylight_space (to be used for the calculation of daylight_space depth) floor_surface = nil floor_area = 0.0 floor_vertices = [] daylight_space.surfaces.sort.each do |surface| if surface.surfaceType == 'Floor' floor_surface = surface floor_area += surface.netArea floor_vertices << surface.vertices end end ##### Loop through the surfaces of each daylight_space to calculate primary_sidelighted_area and ##### area-weighted visible transmittance and window_area_sum which are used to calculate sidelighting_effective_aperture primary_sidelighted_area, area_weighted_vt_handle, window_area_sum = get_parameters_sidelighting(daylight_space: daylight_space, floor_surface: floor_surface, floor_vertices: floor_vertices, floor_area: floor_area, primary_sidelighted_area: primary_sidelighted_area, area_weighted_vt_handle: area_weighted_vt_handle, window_area_sum: window_area_sum) primary_sidelighted_area_hash[daylight_space.name.to_s] = primary_sidelighted_area ##### Calculate area-weighted VT of glazing (this is used to calculate sidelighting effective aperture; see NECB2011: 4.2.2.10.). area_weighted_vt = area_weighted_vt_handle / window_area_sum sidelighting_effective_aperture_hash[daylight_space.name.to_s] = window_area_sum * area_weighted_vt / primary_sidelighted_area # daylight_spaces.each do |daylight_space| end ##### Calculate "Daylighted Area Under Skylights" AND "Skylight Effective Aperture" daylight_spaces.sort.each do |daylight_space| # puts daylight_space.name.to_s skylight_area = 0.0 skylight_area_weighted_vt_handle = 0.0 skylight_area_weighted_vt = 0.0 skylight_area_sum = 0.0 daylighted_under_skylight_area = 0.0 ##### Loop through the surfaces of each daylight_space to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum = get_parameters_skylight(daylight_space: daylight_space, skylight_area_weighted_vt_handle: skylight_area_weighted_vt_handle, skylight_area_sum: skylight_area_sum, daylighted_under_skylight_area: daylighted_under_skylight_area) daylighted_area_under_skylights_hash[daylight_space.name.to_s] = daylighted_under_skylight_area ##### Calculate skylight_effective_aperture as per NECB2011: 4.2.2.7. ##### Note that it was assumed that the skylight is flush with the ceiling. Therefore, area-weighted average well factor (WF) was set to 0.9 in the below Equation. skylight_area_weighted_vt = skylight_area_weighted_vt_handle / skylight_area_sum skylight_effective_aperture_hash[daylight_space.name.to_s] = 0.85 * skylight_area_sum * skylight_area_weighted_vt * 0.9 / daylighted_under_skylight_area # daylight_spaces.each do |daylight_space| end # puts "primary_sidelighted_area_hash is #{primary_sidelighted_area_hash}" # puts sidelighting_effective_aperture_hash # puts daylighted_area_under_skylights_hash # puts skylight_effective_aperture_hash ##### Find office spaces >= 25m2 among daylight_spaces offices_larger_25m2 = [] daylight_spaces.sort.each do |daylight_space| ## The following steps are for in case an office has multiple floors at various heights ## 1. Calculate number of floors of each daylight_space ## 2. Find the lowest z among all floors of each daylight_space ## 3. Find lowest floors of each daylight_space (these floors are at the same level) ## 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space ## 1. Calculate number of floors of daylight_space floor_vertices = [] number_floor = 0 daylight_space.surfaces.sort.each do |surface| if surface.surfaceType == 'Floor' floor_vertices << surface.vertices number_floor += 1 end end ## 2. Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space lowest_floor_z = [] highest_floor_z = [] for i in 0..number_floor - 1 if i == 0 lowest_floor_z = floor_vertices[i][0].z highest_floor_z = floor_vertices[i][0].z else if lowest_floor_z > floor_vertices[i][0].z lowest_floor_z = floor_vertices[i][0].z else lowest_floor_z = lowest_floor_z end if highest_floor_z < floor_vertices[i][0].z highest_floor_z = floor_vertices[i][0].z else highest_floor_z = highest_floor_z end end end ## 3 and 4. Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space, ## and gather the vertices of all the lowest floors of daylight_space daylight_space_area = 0 lowest_floors_vertices = [] floor_vertices = [] daylight_space.surfaces.sort.each do |surface| if surface.surfaceType == 'Floor' floor_vertices = surface.vertices if floor_vertices[0].z == lowest_floor_z lowest_floors_vertices << floor_vertices daylight_space_area += surface.netArea end end end if daylight_space.spaceType.get.standardsSpaceType.get.to_s == 'Office - enclosed' && daylight_space_area >= 25.0 offices_larger_25m2 << daylight_space.name.to_s end end ##### find daylight_spaces which do not need daylight sensor controls based on the primary_sidelighted_area as per NECB2011: 4.2.2.8. ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their primary_sidelighted_area <= 100m2), as per NECB2011: 4.2.2.2. daylight_spaces_exception = [] primary_sidelighted_area_hash.sort.each do |key_daylight_space_name, value_primary_sidelighted_area| if value_primary_sidelighted_area <= 100.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false daylight_spaces_exception << key_daylight_space_name end end ##### find daylight_spaces which do not need daylight sensor controls based on the sidelighting_effective_aperture as per NECB2011: 4.2.2.8. ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their sidelighting_effective_aperture <= 10%), as per NECB2011: 4.2.2.2. sidelighting_effective_aperture_hash.sort.each do |key_daylight_space_name, value_sidelighting_effective_aperture| if value_sidelighting_effective_aperture <= 0.1 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false daylight_spaces_exception << key_daylight_space_name end end ##### find daylight_spaces which do not need daylight sensor controls based on the daylighted_area_under_skylights as per NECB2011: 4.2.2.4. ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their daylighted_area_under_skylights <= 400m2), as per NECB2011: 4.2.2.2. daylighted_area_under_skylights_hash.sort.each do |key_daylight_space_name, value_daylighted_area_under_skylights| if value_daylighted_area_under_skylights <= 400.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false daylight_spaces_exception << key_daylight_space_name end end ##### find daylight_spaces which do not need daylight sensor controls based on the skylight_effective_aperture criterion as per NECB2011: 4.2.2.4. ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their skylight_effective_aperture <= 0.6%), as per NECB2011: 4.2.2.2. skylight_effective_aperture_hash.sort.each do |key_daylight_space_name, value_skylight_effective_aperture| if value_skylight_effective_aperture <= 0.006 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false daylight_spaces_exception << key_daylight_space_name end end # puts daylight_spaces_exception ##### Loop through the daylight_spaces and exclude the daylight_spaces that do not meet the criteria (see above) as per NECB2011: 4.2.2.4. and 4.2.2.8. daylight_spaces_exception.sort.each do |daylight_space_exception| daylight_spaces.sort.each do |daylight_space| if daylight_space.name.to_s == daylight_space_exception daylight_spaces.delete(daylight_space) end end end # puts daylight_spaces # elsif daylighting_type == 'add_daylighting_controls' # puts daylighting sensors in all spaces regardless of NECB requirements end #if daylighting_type.nil? || daylighting_type == false || daylighting_type == 'none' || daylighting_type == 'NECB_Default' ##### Create one daylighting sensor and put it at the center of each daylight_space if the space area < 250m2; ##### otherwise, create two daylight sensors, divide the space into two parts and put each of the daylight sensors at the center of each part of the space. daylight_spaces.sort.each do |daylight_space| # puts daylight_space.name.to_s ##### 1. Calculate number of floors of each daylight_space ##### 2. Find the lowest z among all floors of each daylight_space ##### 3. Find lowest floors of each daylight_space (these floors are at the same level) ##### 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space ##### 5. Find min and max of x and y among vertices of all the lowest floors of each daylight_space ##### Calculate number of floors of daylight_space floor_vertices = [] number_floor = 0 daylight_space.surfaces.sort.each do |surface| if surface.surfaceType == 'Floor' floor_vertices << surface.vertices number_floor += 1 end end ##### Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space lowest_floor_z = [] highest_floor_z = [] for i in 0..number_floor - 1 if i == 0 lowest_floor_z = floor_vertices[i][0].z highest_floor_z = floor_vertices[i][0].z else if lowest_floor_z > floor_vertices[i][0].z lowest_floor_z = floor_vertices[i][0].z else lowest_floor_z = lowest_floor_z end if highest_floor_z < floor_vertices[i][0].z highest_floor_z = floor_vertices[i][0].z else highest_floor_z = highest_floor_z end end end # puts lowest_floor_z ##### Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space, ##### and gather the vertices of all the lowest floors of daylight_space daylight_space_area = 0 lowest_floors_vertices = [] floor_vertices = [] daylight_space.surfaces.sort.each do |surface| if surface.surfaceType == 'Floor' floor_vertices = surface.vertices if floor_vertices[0].z == lowest_floor_z lowest_floors_vertices << floor_vertices daylight_space_area += surface.netArea end end end # puts daylight_space.name.to_s # puts number_floor # puts lowest_floors_vertices # puts daylight_space_area ##### Loop through all lowest floors of daylight_space and find the min and max of x and y among their vertices xmin = lowest_floors_vertices[0][0].x ymin = lowest_floors_vertices[0][0].y xmax = lowest_floors_vertices[0][0].x ymax = lowest_floors_vertices[0][0].y zmin = lowest_floor_z for i in 0..lowest_floors_vertices.count - 1 # this loops through each of the lowers floors of daylight_space for j in 0..lowest_floors_vertices[i].count - 1 # this loops through each of vertices of each of the lowers floors of daylight_space if xmin > lowest_floors_vertices[i][j].x xmin = lowest_floors_vertices[i][j].x end if ymin > lowest_floors_vertices[i][j].y ymin = lowest_floors_vertices[i][j].y end if xmax < lowest_floors_vertices[i][j].x xmax = lowest_floors_vertices[i][j].x end if ymax < lowest_floors_vertices[i][j].y ymax = lowest_floors_vertices[i][j].y end end end # puts daylight_space.name.to_s # puts xmin # puts xmax # puts ymin # puts ymax ##### Get the thermal zone of daylight_space (this is used later to assign daylighting sensor) zone = daylight_space.thermalZone # puts "zone name is #{zone}" if !zone.empty? zone = daylight_space.thermalZone.get ##### Get the floor of the daylight_space floors = [] daylight_space.surfaces.sort.each do |surface| if surface.surfaceType == 'Floor' floors << surface end end ##### Set daylighting controls illuminance setpoint and number of stepped control steps number_of_stepped_control_steps = 2 ##### Note that the minimum number of stepped control steps is two steps as per NECB2011. illuminance_setpoint = daylight_spaces_target_illuminance_setpoint_hash.select {|key| key == daylight_space.name.to_s } illuminance_setpoint = illuminance_setpoint[daylight_space.name.to_s] ##### Create daylighting sensor control ##### NOTE: NECB2011 has some requirements on the number of sensors in spaces based on the area of the spaces. ##### However, EnergyPlus/OpenStudio allows to put maximum two built-in sensors in each thermal zone rather than in each space. ##### Since a thermal zone may include several spaces which are not next to each other on the same floor, or ##### a thermal zone may include spaces on different floors, a simplified method has been used to create a daylighting sensor. ##### So, in each thermal zone, only one daylighting sensor has been created even if the area of that thermal zone requires more than one daylighting sensor. ##### Also, it has been assumed that a thermal zone includes spaces which are next to each other and are on the same floor. ##### Furthermore, the one daylighting sensor in each thermal zone (where the thermal zone needs daylighting sensor), ##### the sensor has been put at the intersection of the minimum and maximum x and y of the lowest floor of that thermal zones. sensor = OpenStudio::Model::DaylightingControl.new(daylight_space.model) sensor.setName("#{daylight_space.name} daylighting control") sensor.setSpace(daylight_space) sensor.setIlluminanceSetpoint(illuminance_setpoint) sensor.setLightingControlType('Stepped') sensor.setNumberofSteppedControlSteps(number_of_stepped_control_steps) x_pos = (xmin + xmax) / 2.0 y_pos = (ymin + ymax) / 2.0 z_pos = zmin + 0.8 # put it 0.8 meter above the floor sensor_vertex = OpenStudio::Point3d.new(x_pos, y_pos, z_pos) sensor.setPosition(sensor_vertex) zone.setPrimaryDaylightingControl(sensor) zone.setFractionofZoneControlledbyPrimaryDaylightingControl(1.0) # if !zone.empty? end # daylight_spaces.each do |daylight_space| end # END if daylighting_controls_type.nil? || daylighting_controls_type == false || daylighting_controls_type == 'none' || daylighting_controls_type == 'NECB_Default' end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2 def model_add_hvac(model:) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding HVAC') system_fuel_defaults = get_canadian_system_defaults_by_weatherfile_name(model) necb_autozone_and_autosystem(model: model, runner: nil, use_ideal_air_loads: false, system_fuel_defaults: system_fuel_defaults) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding HVAC') return true end
Adds the loads and associated schedules for each space type as defined in the OpenStudio_Standards_space_types.json file. This includes lights, plug loads, occupants, ventilation rate requirements, infiltration, gas equipment (for kitchens, etc.) and typical schedules for each. Some loads are governed by the standard, others are typical values pulled from sources such as the DOE Reference and DOE Prototype Buildings.
@return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1930 def model_add_loads(model, lights_type, lights_scale) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying space types (loads)') # Loop through all the space types currently in the model, # which are placeholders, and give them appropriate loads and schedules model.getSpaceTypes.sort.each do |space_type| # Rendering color space_type_apply_rendering_color(space_type) # Loads space_type_apply_internal_loads(space_type: space_type, lights_type: lights_type, lights_scale: lights_scale) # Schedules space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying space types (loads)') return true end
Create a schedule from the openstudio standards dataset and add it to the model.
@param schedule_name [String} name of the schedule @return [ScheduleRuleset] the resulting schedule ruleset @todo make return an OptionalScheduleRuleset
Standard#model_add_schedule
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 125 def model_add_schedule(model, schedule_name) super(model, schedule_name) end
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 2 def model_add_swh(model:, swh_fueltype: 'DefaultFuel', shw_scale:) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Service Water Heating') # Get default fuel based on epw location province. if swh_fueltype == 'DefaultFuel' epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) swh_fueltype = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] end # Calculate the tank size and service water pump information shw_sizing = auto_size_shw_capacity(model: model, shw_scale: shw_scale) if shw_sizing['loop_peak_flow_rate_SI'] == 0 # Only add a shw_loop if at least one space calls for shw. If no space calls for shw put out a warning but do not # add a shw loop. OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'No Service Water Heating Added') return true else shw_pump_head = auto_size_shw_pump_head(model, default: false) end # Add the main service water heating loop shw_pump_motor_eff = 0.9 main_swh_loop = OpenstudioStandards::ServiceWaterHeating.create_service_water_heating_loop(model, system_name: 'Main Service Water Loop', service_water_temperature: shw_sizing['max_temp_SI'], service_water_pump_head: shw_pump_head, service_water_pump_motor_efficiency: shw_pump_motor_eff, water_heater_capacity: shw_sizing['tank_capacity_SI'], water_heater_volume: shw_sizing['tank_volume_SI'], water_heater_fuel: swh_fueltype, on_cycle_parasitic_fuel_consumption_rate: shw_sizing['parasitic_loss'], off_cycle_parasitic_fuel_consumption_rate: shw_sizing['parasitic_loss']) # Note that when water use equipment is assigned to spaces then the water used by the equipment is multiplied by # the space (ultimately thermal zone) multiplier. Note that there is a separate water use equipment multiplier # as well which is different than the space (ultimately thermal zone) multiplier. shw_sizing['spaces_w_dhw'].each do |space| OpenstudioStandards::ServiceWaterHeating.create_water_use(model, name: "#{space['shw_spaces'].name.get.capitalize}", flow_rate: space['shw_peakflow_ind_SI'], flow_rate_fraction_schedule: model_add_schedule(model, space['shw_sched']), water_use_temperature: space['shw_temp_SI'], service_water_loop: main_swh_loop, space: space['shw_spaces']) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding Service Water Heating') return true end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1523 def model_apply_sizing_parameters(model) model.getSizingParameters.setHeatingSizingFactor(get_standards_constant('sizing_factor_max_heating')) model.getSizingParameters.setCoolingSizingFactor(get_standards_constant('sizing_factor_max_cooling')) OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set sizing factors to #{get_standards_constant('sizing_factor_max_heating')} for heating and #{get_standards_constant('sizing_factor_max_heating')} for cooling.") end
Created this method so that additional methods can be addded for bulding the prototype model in later code versions without modifying the build_protoype_model method or copying it wholesale for a few changes.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 352 def model_apply_standard(model:, tbd_option: nil, tbd_interpolate: nil, epw_file:, custom_weather_folder: nil, sizing_run_dir: Dir.pwd, necb_reference_hp: false, necb_reference_hp_supp_fuel: 'DefaultFuel', primary_heating_fuel: 'DefaultFuel', swh_fuel: 'DefaultFuel', dcv_type: 'NECB_Default', lights_type: 'NECB_Default', lights_scale: 'NECB_Default', daylighting_type: 'NECB_Default', ecm_system_name: 'NECB_Default', ecm_system_zones_map_option: 'NECB_Default', erv_package: 'NECB_Default', boiler_eff: nil, furnace_eff: nil, unitary_cop: nil, shw_eff: nil, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, door_construction_cond: nil, fixed_window_cond: nil, glass_door_cond: nil, overhead_door_cond: nil, skylight_cond: nil, glass_door_solar_trans: nil, fixed_wind_solar_trans: nil, skylight_solar_trans: nil, fdwr_set: nil, srr_set: nil, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil, nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, nv_delta_temp_in_out: nil, pv_ground_type: nil, pv_ground_total_area_pv_panels_m2: nil, pv_ground_tilt_angle: nil, pv_ground_azimuth_angle: nil, pv_ground_module_description: nil, chiller_type: nil, occupancy_loads_scale: nil, electrical_loads_scale: nil, oa_scale: nil, infiltration_scale: nil, output_variables: nil, shw_scale: nil, output_meters: nil, airloop_economizer_type: nil, baseline_system_zones_map_option: nil, necb_hdd: true, boiler_fuel: nil, boiler_cap_ratio: nil) primary_heating_fuel = validate_primary_heating_fuel(primary_heating_fuel: primary_heating_fuel) self.fuel_type_set = SystemFuels.new() swh_fuel = swh_fuel.nil? ? 'NECB_Default' : swh_fuel.to_s self.fuel_type_set.set_defaults(standards_data: @standards_data, primary_heating_fuel: primary_heating_fuel, swh_fuel: swh_fuel) swh_fuel = self.fuel_type_set.swh_fueltype if swh_fuel.to_s.downcase == 'necb_default' clean_and_scale_model(model: model, rotation_degrees: rotation_degrees, scale_x: scale_x, scale_y: scale_y, scale_z: scale_z) fdwr_set = convert_arg_to_f(variable: fdwr_set, default: -1) srr_set = convert_arg_to_f(variable: srr_set, default: -1) necb_hdd = convert_arg_to_bool(variable: necb_hdd, default: true) boiler_fuel = convert_arg_to_string(variable: boiler_fuel, default: nil) boiler_cap_ratio = convert_arg_to_string(variable: boiler_cap_ratio, default: nil) boiler_cap_ratios = set_boiler_cap_ratios(boiler_cap_ratio: boiler_cap_ratio, boiler_fuel: boiler_fuel) unless boiler_cap_ratio.nil? && boiler_fuel.nil? self.fuel_type_set.set_boiler_fuel(standards_data: @standards_data, boiler_fuel: boiler_fuel, boiler_cap_ratios: boiler_cap_ratios) unless boiler_fuel.nil? # Ensure the volume calculation in all spaces is done automatically model.getSpaces.sort.each do |space| space.autocalculateVolume end apply_weather_data(model: model, epw_file: epw_file, custom_weather_folder: custom_weather_folder) apply_loads(model: model, lights_type: lights_type, lights_scale: lights_scale, occupancy_loads_scale: occupancy_loads_scale, electrical_loads_scale: electrical_loads_scale, oa_scale: oa_scale) apply_envelope(model: model, ext_wall_cond: ext_wall_cond, ext_floor_cond: ext_floor_cond, ext_roof_cond: ext_roof_cond, ground_wall_cond: ground_wall_cond, ground_floor_cond: ground_floor_cond, ground_roof_cond: ground_roof_cond, door_construction_cond: door_construction_cond, fixed_window_cond: fixed_window_cond, glass_door_cond: glass_door_cond, overhead_door_cond: overhead_door_cond, skylight_cond: skylight_cond, glass_door_solar_trans: glass_door_solar_trans, fixed_wind_solar_trans: fixed_wind_solar_trans, skylight_solar_trans: skylight_solar_trans, infiltration_scale: infiltration_scale, necb_hdd: necb_hdd) apply_fdwr_srr_daylighting(model: model, fdwr_set: fdwr_set, srr_set: srr_set, necb_hdd: necb_hdd) apply_thermal_bridging(model: model, tbd_option: tbd_option, tbd_interpolate: tbd_interpolate, wall_U: ext_wall_cond, floor_U: ext_floor_cond, roof_U: ext_roof_cond) apply_auto_zoning(model: model, sizing_run_dir: sizing_run_dir, lights_type: lights_type, lights_scale: lights_scale) apply_kiva_foundation(model) apply_systems_and_efficiencies(model: model, sizing_run_dir: sizing_run_dir, primary_heating_fuel: primary_heating_fuel, swh_fuel: swh_fuel, dcv_type: dcv_type, ecm_system_name: ecm_system_name, ecm_system_zones_map_option: ecm_system_zones_map_option, erv_package: erv_package, boiler_eff: boiler_eff, unitary_cop: unitary_cop, furnace_eff: furnace_eff, shw_eff: shw_eff, daylighting_type: daylighting_type, nv_type: nv_type, nv_opening_fraction: nv_opening_fraction, nv_temp_out_min: nv_temp_out_min, nv_delta_temp_in_out: nv_delta_temp_in_out, pv_ground_type: pv_ground_type, pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2, pv_ground_tilt_angle: pv_ground_tilt_angle, pv_ground_azimuth_angle: pv_ground_azimuth_angle, pv_ground_module_description: pv_ground_module_description, chiller_type: chiller_type, shw_scale: shw_scale, airloop_economizer_type: airloop_economizer_type, baseline_system_zones_map_option: baseline_system_zones_map_option) self.set_output_variables(model: model, output_variables: output_variables) self.set_output_meters(model: model, output_meters: output_meters) return model end
This method is a wrapper to create the 16 archetypes easily. # 55 args
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 212 def model_create_prototype_model(template:, building_type:, epw_file:, custom_weather_folder: nil, debug: false, sizing_run_dir: Dir.pwd, primary_heating_fuel: 'Electricity', swh_fuel: 'NECB_Default', dcv_type: 'NECB_Default', lights_type: 'NECB_Default', lights_scale: 1.0, daylighting_type: 'NECB_Default', ecm_system_name: 'NECB_Default', ecm_system_zones_map_option: 'NECB_Default', erv_package: 'NECB_Default', boiler_eff: nil, unitary_cop: nil, furnace_eff: nil, shw_eff: nil, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, door_construction_cond: nil, fixed_window_cond: nil, glass_door_cond: nil, overhead_door_cond: nil, skylight_cond: nil, glass_door_solar_trans: nil, fixed_wind_solar_trans: nil, skylight_solar_trans: nil, rotation_degrees: nil, fdwr_set: -1.0, srr_set: -1.0, nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, nv_delta_temp_in_out: nil, scale_x: nil, scale_y: nil, scale_z: nil, pv_ground_type: nil, pv_ground_total_area_pv_panels_m2: nil, pv_ground_tilt_angle: nil, pv_ground_azimuth_angle: nil, pv_ground_module_description: nil, chiller_type: nil, occupancy_loads_scale: nil, electrical_loads_scale: nil, oa_scale: nil, infiltration_scale: nil, output_variables: nil, shw_scale: nil, output_meters: nil, airloop_economizer_type: nil, baseline_system_zones_map_option: nil, tbd_option: nil, tbd_interpolate: false, necb_hdd: true, boiler_fuel: nil, boiler_cap_ratio: nil) model = load_building_type_from_library(building_type: building_type) return model_apply_standard(model: model, tbd_option: tbd_option, tbd_interpolate: tbd_interpolate, epw_file: epw_file, custom_weather_folder: custom_weather_folder, sizing_run_dir: sizing_run_dir, primary_heating_fuel: primary_heating_fuel, swh_fuel: swh_fuel, dcv_type: dcv_type, # Four options: (1) 'NECB_Default', (2) 'No_DCV', (3) 'Occupancy_based_DCV' , (4) 'CO2_based_DCV' lights_type: lights_type, # Two options: (1) 'NECB_Default', (2) 'LED' lights_scale: lights_scale, daylighting_type: daylighting_type, # Two options: (1) nil/none/false/'NECB_Default' (Option #1 puts daylighting sensors in the spaces as per NECB requirements; so some spaces may not have sensors), (2) 'add_daylighting_controls' (Option #2 puts daylighting sensors in all spaces regardless of NECB requirements) ecm_system_name: ecm_system_name, ecm_system_zones_map_option: ecm_system_zones_map_option, # (1) 'NECB_Default' (2) 'one_sys_per_floor' (3) 'one_sys_per_bldg' erv_package: erv_package, boiler_eff: boiler_eff, unitary_cop: unitary_cop, furnace_eff: furnace_eff, shw_eff: shw_eff, ext_wall_cond: ext_wall_cond, ext_floor_cond: ext_floor_cond, ext_roof_cond: ext_roof_cond, ground_wall_cond: ground_wall_cond, ground_floor_cond: ground_floor_cond, ground_roof_cond: ground_roof_cond, door_construction_cond: door_construction_cond, fixed_window_cond: fixed_window_cond, glass_door_cond: glass_door_cond, overhead_door_cond: overhead_door_cond, skylight_cond: skylight_cond, glass_door_solar_trans: glass_door_solar_trans, fixed_wind_solar_trans: fixed_wind_solar_trans, skylight_solar_trans: skylight_solar_trans, rotation_degrees: rotation_degrees, fdwr_set: fdwr_set, srr_set: srr_set, nv_type: nv_type, # Two options: (1) nil/none/false/'NECB_Default', (2) 'add_nv' nv_opening_fraction: nv_opening_fraction, # options: (1) nil/none/false (2) 'NECB_Default' (i.e. 0.1), (3) opening fraction of windows, which can be a float number between 0.0 and 1.0 nv_temp_out_min: nv_temp_out_min, # options: (1) nil/none/false(2) 'NECB_Default' (i.e. 13.0 based on inputs from Michel Tardif re a real school in QC), (3) minimum outdoor air temperature (in Celsius) below which natural ventilation is shut down nv_delta_temp_in_out: nv_delta_temp_in_out, # options: (1) nil/none/false (2) 'NECB_Default' (i.e. 1.0 based on inputs from Michel Tardif re a real school in QC), (3) temperature difference (in Celsius) between the indoor and outdoor air temperatures below which ventilation is shut down scale_x: scale_x, scale_y: scale_y, scale_z: scale_z, pv_ground_type: pv_ground_type, # Two options: (1) nil/none/false/'NECB_Default', (2) 'add_pv_ground' pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. building footprint), (3) area value (e.g. 50) pv_ground_tilt_angle: pv_ground_tilt_angle, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. latitude), (3) tilt angle value (e.g. 20) pv_ground_azimuth_angle: pv_ground_azimuth_angle, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. south), (3) azimuth angle value (e.g. 90) pv_ground_module_description: pv_ground_module_description, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. Standard), (3) other options ('Standard', 'Premium', ThinFilm') occupancy_loads_scale: occupancy_loads_scale, electrical_loads_scale: electrical_loads_scale, oa_scale: oa_scale, infiltration_scale: infiltration_scale, chiller_type: chiller_type, # Options: (1) 'NECB_Default'/nil/'none'/false (i.e. do nothing), (2) e.g. 'VSD' output_variables: output_variables, shw_scale: shw_scale, # Options: (1) 'NECB_Default'/nil/'none'/false (i.e. do nothing), (2) a float number larger than 0.0 output_meters: output_meters, airloop_economizer_type: airloop_economizer_type, # (1) 'NECB_Default'/nil/' (2) 'DifferentialEnthalpy' (3) 'DifferentialTemperature' baseline_system_zones_map_option: baseline_system_zones_map_option, # Three options: (1) 'NECB_Default'/'none'/nil (i.e. 'one_sys_per_bldg'), (2) 'one_sys_per_dwelling_unit', (3) 'one_sys_per_bldg' necb_hdd: necb_hdd, boiler_fuel: boiler_fuel, boiler_cap_ratio: boiler_cap_ratio ) end
Creates thermal zones to contain each space, as defined for each building in the system_to_space_map inside the Prototype.building_name e.g. (Prototype.secondary_school.rb) file.
@param (see add_constructions) @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 8 def model_create_thermal_zones(model, space_multiplier_map = nil) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started creating thermal zones') space_multiplier_map = {} if space_multiplier_map.nil? # Remove any Thermal zones assigned model.getThermalZones.each(&:remove) # Create a thermal zone for each space in the self model.getSpaces.sort.each do |space| zone = OpenStudio::Model::ThermalZone.new(model) zone.setName("#{space.name} ZN") unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1) zone.setMultiplier(space_multiplier_map[space.name.to_s]) end space.setThermalZone(zone) # Skip thermostat for spaces with no space type next if space.spaceType.empty? # Add a thermostat space_type_name = space.spaceType.get.name.get thermostat_name = space_type_name + ' Thermostat' thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name) if thermostat.empty? OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}") else thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model) ideal_loads.addToThermalZone(zone) end end OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones') end
Note: Values for dcv_type are: âOccupancy_based_DCVâ, âCO2_based_DCVâ, âNo_DCVâ, âNECB_Defaultâ
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1756 def model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV') return if dcv_type == 'NECB_Defualt' if dcv_type == 'Occupancy_based_DCV' || dcv_type == 'CO2_based_DCV' # @todo IMPORTANT: (upon other BTAP tasks) Set a value for the "Outdoor Air Flow per Person" field of the "OS:DesignSpecification:OutdoorAir" object # Note: The "Outdoor Air Flow per Person" field is required for occupancy-based DCV. # Note: The "Outdoor Air Flow per Person" values should be based on ASHRAE 62.1: Article 6.2.2.1. # Note: The "Outdoor Air Flow per Person" should be entered for "ventilation_per_person" in "lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json" ##### Define indoor CO2 availability schedule (required for CO2-based DCV) ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction ##### Note: the defined schedule here is redundant as the schedule says it is always on AND ##### the "ZoneControl:ContaminantController" object says that "If this field is left blank, the schedule has a value of 1 for all time periods". indoor_co2_availability_schedule = OpenStudio::Model::ScheduleCompact.new(model) indoor_co2_availability_schedule.setName('indoor_co2_availability_schedule') indoor_co2_availability_schedule.setScheduleTypeLimits(BTAP::Resources::Schedules::StandardScheduleTypeLimits.get_fraction(model)) indoor_co2_availability_schedule.to_ScheduleCompact.get # indoor_co2_availability_schedule.setString(1,"indoor_co2_availability_schedule") indoor_co2_availability_schedule.setString(3, 'Through: 12/31') indoor_co2_availability_schedule.setString(4, 'For: Weekdays SummerDesignDay') indoor_co2_availability_schedule.setString(5, 'Until: 07:00') indoor_co2_availability_schedule.setString(6, '0.0') indoor_co2_availability_schedule.setString(7, 'Until: 22:00') indoor_co2_availability_schedule.setString(8, '1.0') indoor_co2_availability_schedule.setString(9, 'Until: 24:00') indoor_co2_availability_schedule.setString(10, '0.0') indoor_co2_availability_schedule.setString(11, 'For: Saturday WinterDesignDay') indoor_co2_availability_schedule.setString(12, 'Until: 07:00') indoor_co2_availability_schedule.setString(13, '0.0') indoor_co2_availability_schedule.setString(14, 'Until: 18:00') indoor_co2_availability_schedule.setString(15, '1.0') indoor_co2_availability_schedule.setString(16, 'Until: 24:00') indoor_co2_availability_schedule.setString(17, '0.0') indoor_co2_availability_schedule.setString(18, 'For: AllOtherDays') indoor_co2_availability_schedule.setString(19, 'Until: 24:00') indoor_co2_availability_schedule.setString(20, '0.0') ##### Define indoor CO2 setpoint schedule (required for CO2-based DCV) ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction indoor_co2_setpoint_schedule = OpenStudio::Model::ScheduleCompact.new(model) indoor_co2_setpoint_schedule.setName('indoor_co2_setpoint_schedule') indoor_co2_setpoint_schedule.setScheduleTypeLimits(get_any_number_ppm(model)) indoor_co2_setpoint_schedule.to_ScheduleCompact.get indoor_co2_setpoint_schedule.setString(3, 'Through: 12/31') indoor_co2_setpoint_schedule.setString(4, 'For: AllDays') indoor_co2_setpoint_schedule.setString(5, 'Until: 24:00') indoor_co2_setpoint_schedule.setString(6, '1000.0') # indoor_co2_setpoint_schedule.setToConstantValue(1000.0) #1000 ppm ##### Define outdoor CO2 schedule (required for CO2-based DCV ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction outdoor_co2_schedule = OpenStudio::Model::ScheduleCompact.new(model) outdoor_co2_schedule.setName('outdoor_co2_schedule') outdoor_co2_schedule.setScheduleTypeLimits(get_any_number_ppm(model)) outdoor_co2_schedule.to_ScheduleCompact.get outdoor_co2_schedule.setString(3, 'Through: 12/31') outdoor_co2_schedule.setString(4, 'For: AllDays') outdoor_co2_schedule.setString(5, 'Until: 24:00') outdoor_co2_schedule.setString(6, '400.0') # outdoor_co2_schedule.setToConstantValue(400.0) #400 ppm ##### Define ZoneAirContaminantBalance (required for CO2-based DCV) zone_air_contaminant_balance = model.getZoneAirContaminantBalance zone_air_contaminant_balance.setCarbonDioxideConcentration(true) zone_air_contaminant_balance.setOutdoorCarbonDioxideSchedule(outdoor_co2_schedule) ##### Set CO2 controller in each space (required for CO2-based DCV) model.getSpaces.sort.each do |space| # puts space.name.to_s zone = space.thermalZone if !zone.empty? zone = space.thermalZone.get end zone_control_co2 = OpenStudio::Model::ZoneControlContaminantController.new(zone.model) zone_control_co2.setName("#{space.name} Zone Control Contaminant Controller") zone_control_co2.setCarbonDioxideControlAvailabilitySchedule(indoor_co2_availability_schedule) zone_control_co2.setCarbonDioxideSetpointSchedule(indoor_co2_setpoint_schedule) zone.setZoneControlContaminantController(zone_control_co2) end # if dcv_type == "Occupancy_based_DCV" || dcv_type == "CO2_based_DCV" end ##### Loop through AirLoopHVACs model.getAirLoopHVACs.sort.each do |air_loop| ##### Loop through AirLoopHVAC's supply nodes to: ##### (1) Find its AirLoopHVAC:OutdoorAirSystem using the supply node; ##### (2) Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem; ##### (3) Get "Controller Mechanical Ventilation" from Controller:OutdoorAir. air_loop.supplyComponents.sort.each do |supply_component| ##### Find AirLoopHVAC:OutdoorAirSystem of AirLoopHVAC using the supply node. hvac_component = supply_component.to_AirLoopHVACOutdoorAirSystem if !hvac_component.empty? ##### Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem. hvac_component = hvac_component.get controller_oa = hvac_component.getControllerOutdoorAir ##### Get "Controller Mechanical Ventilation" from Controller:OutdoorAir. controller_mv = controller_oa.controllerMechanicalVentilation ##### Set "Demand Controlled Ventilation" to "Yes" or "No" in Controller:MechanicalVentilation depending on dcv_type. if (dcv_type == 'CO2_based_DCV') || (dcv_type == 'Occupancy_based_DCV') # Occupancy controller_mv.setDemandControlledVentilation(true) ##### Set the "System Outdoor Air Method" field based on dcv_type in the Controller:MechanicalVentilation object if dcv_type == 'CO2_based_DCV' controller_mv.setSystemOutdoorAirMethod('IndoorAirQualityProcedure') else # dcv_type == 'Occupancy_based_DCV' controller_mv.setSystemOutdoorAirMethod('ZoneSum') end elsif dcv_type == 'No_DCV' controller_mv.setDemandControlledVentilation(false) end # puts controller_mv # if !hvac_component.empty? end # air_loop.supplyComponents.each do |supply_component| end # model.getAirLoopHVACs.each do |air_loop| end end
Helper method to find out which climate zone set contains a specific climate zone. Returns climate zone set name as String
if success, nil if not found.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1653 def model_find_climate_zone_set(model, clim) return 'NECB-CNEB ClimatZone 4-8' end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1470 def necb_design_supply_temp_compliance(qaqc) necb_section_name = get_qaqc_table(table_name: 'design_supply_temp_compliance')['refs'].join(',') qaqc_table = get_qaqc_table(table_name: 'design_supply_temp_compliance') tolerance = 3 qaqc[:thermal_zones].each do |zoneinfo| # skipping undefined schedules if (qaqc_table['exclude']['exclude_string'].any? { |ex_string| zoneinfo[:name].to_s.include? ex_string }) && !qaqc_table['exclude']['exclude_string'].empty? puts "#{zoneinfo[:name]} was skipped in necb_zone_sizing_compliance because it contains #{qaqc_table['exclude']['exclude_string'].join(',')}" next end design_supply_temp_compliance = qaqc_table['table'] design_supply_temp_compliance.each do |compliance| if compliance['var'] == 'heating_design_supply_air_temp' result_value = zoneinfo[:zone_heating_design_supply_air_temperature] elsif compliance['var'] == 'cooling_design_supply_temp' result_value = zoneinfo[:zone_cooling_design_supply_air_temperature] end next if result_value.nil? test_text = "[ZONE][#{zoneinfo[:name]}] #{compliance['var']}" # puts key necb_section_test( qaqc, result_value, compliance['bool_operator'], compliance['expected_value'], necb_section_name, test_text, tolerance ) end end # Design supply temp test # necb_section_name = "NECB2011-?" # round_precision = 3 # qaqc[:thermal_zones].each do |zoneinfo| # # skipping undefined schedules # if zoneinfo[:name].to_s.include?"- undefined -" # next # end # data = {} # #data[:heating_sizing_factor] = [1.3 , zoneinfo[:heating_sizing_factor]] # #data[:cooling_sizing_factor] = [1.1 ,zoneinfo[:cooling_sizing_factor]] # data[:heating_design_supply_air_temp] = [43.0, zoneinfo[:zone_heating_design_supply_air_temperature] ] #unless zoneinfo[:zone_heating_design_supply_air_temperature].nil? # data[:cooling_design_supply_temp] = [13.0, zoneinfo[:zone_cooling_design_supply_air_temperature] ] # data.each do |key,value| # #puts key # necb_section_test( # qaqc, # value[0], # '==', # value[1], # necb_section_name, # "[ZONE][#{zoneinfo[:name]}] #{key}", # round_precision # ) # end # end end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1532 def necb_economizer_compliance(qaqc) # determine correct economizer usage according to section 5.2.2.7 of NECB2011 necb_section_name = get_qaqc_table(table_name: 'economizer_compliance')['refs'].join(',') qaqc_table = get_qaqc_table(table_name: 'economizer_compliance') # stores the full hash of qaqc for economizer_compliance # necb_section_name = "NECB2011-5.2.2.7" qaqc[:air_loops].each do |air_loop_info| capacity = -1.0 if !air_loop_info[:cooling_coils][:dx_single_speed][0].nil? puts 'capacity = air_loop_info[:cooling_coils][:dx_single_speed][0][:nominal_total_capacity_w]' capacity = air_loop_info[:cooling_coils][:dx_single_speed][0][:nominal_total_capacity_w] elsif !air_loop_info[:cooling_coils][:dx_two_speed][0].nil? puts 'capacity = air_loop_info[:cooling_coils][:dx_two_speed][0][:cop_high]' capacity = air_loop_info[:cooling_coils][:dx_two_speed][0][:cop_high] elsif !air_loop_info[:cooling_coils][:coil_cooling_water][0].nil? puts 'capacity = air_loop_info[:cooling_coils][:coil_cooling_water][0][:nominal_total_capacity_w]' capacity = air_loop_info[:cooling_coils][:coil_cooling_water][0][:nominal_total_capacity_w] end puts capacity if capacity == -1.0 # This should not happen qaqc[:errors] << "[necb_economizer_compliance] air_loop_info[:cooling_coils] for #{air_loop_info[:name]} does not have a capacity " else # check for correct economizer usage # puts "air_loop_info[:supply_fan][:max_air_flow_rate]: #{air_loop_info[:supply_fan][:max_air_flow_rate]}" unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 # capacity should be in kW max_air_flow_rate_m3_per_s = air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] necb_section_test( qaqc, eval(qaqc_table['table'][0]['expected_value']), '==', air_loop_info[:economizer][:control_type], necb_section_name, "[AIR LOOP][#{air_loop_info[:name]}][:economizer][:control_type]" ) end end end end
checks envelope compliance fenestration_to_door_and_window_percentage, skylight_to_roof_percentage
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1168 def necb_envelope_compliance(qaqc) # Envelope necb_section_name = 'NECB2011-Section 3.2.1.4' # store hdd in short form hdd = qaqc[:geography][:hdd] # calculate fdwr based on hdd. fdwr = 0 if hdd < 4000 fdwr = 0.40 elsif (hdd >= 4000) && (hdd <= 7000) fdwr = (2000 - 0.2 * hdd) / 3000 elsif hdd > 7000 fdwr = 0.20 end # hardset srr to 0.05 srr = 0.05 # create table of expected values and results. data = {} data[:fenestration_to_door_and_window_percentage] = [fdwr * 100, qaqc[:envelope][:fdwr].round(3)] data[:skylight_to_roof_percentage] = [srr * 100, qaqc[:envelope][:srr].round(3)] # perform test. result must be less than or equal to. data.each do |key, value| necb_section_test( qaqc, value[0], '>=', value[1], necb_section_name, "[ENVELOPE]#{key}", 1 # padmassun added tollerance ) end end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1308 def necb_exterior_fenestration_compliance(qaqc) # Exterior Fenestration necb_section_name = get_qaqc_table(table_name: 'exterior_fenestration_compliance')['refs'].join(',') climate_index = NECB2011.new.get_climate_zone_index(qaqc[:geography][:hdd]) tolerance = 3 # puts "\n\n" # puts "climate_index: #{climate_index}" # puts get_qaqc_table("exterior_fenestration_compliance", {"var" => "ext_window_conductances", "climate_index" => 2}) ['ext_window_conductances', 'ext_door_conductances', 'ext_overhead_door_conductances', 'ext_skylight_conductances'].each do |compliance_var| qaqc_table = get_qaqc_table(table_name: 'exterior_fenestration_compliance', search_criteria: { 'var' => compliance_var, 'climate_index' => climate_index }).first # puts "\n#{qaqc_table}\n" if compliance_var == 'ext_window_conductances' result_value = qaqc[:envelope][:windows_average_conductance_w_per_m2_k] elsif compliance_var == 'ext_door_conductances' result_value = qaqc[:envelope][:doors_average_conductance_w_per_m2_k] elsif compliance_var == 'ext_overhead_door_conductances' result_value = qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k] elsif compliance_var == 'ext_skylight_conductances' result_value = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] end test_text = "[ENVELOPE] #{compliance_var}" next if result_value.nil? necb_section_test( qaqc, result_value, qaqc_table['bool_operator'], qaqc_table['expected_value'], necb_section_name, test_text, tolerance ) end # necb_section_name = "NECB2011-Section 3.2.2.3" # climate_index = BTAP::Compliance::NECB2011::get_climate_zone_index(qaqc[:geography][:hdd]) # result_value_index = 6 # round_precision = 3 # data = {} # data[:ext_window_conductances] = [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:windows_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:windows_average_conductance_w_per_m2_k].nil? # data[:ext_door_conductances] = [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:doors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:doors_average_conductance_w_per_m2_k].nil? # data[:ext_overhead_door_conductances] = [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k].nil? # data[:ext_skylight_conductances] = [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:skylights_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:skylights_average_conductance_w_per_m2_k].nil? # data.each do |key,value| # #puts key # necb_section_test( # qaqc, # value[result_value_index].round(round_precision), # '==', # value[climate_index].round(round_precision), # necb_section_name, # "[ENVELOPE]#{key}", # round_precision # ) # end end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1366 def necb_exterior_ground_surfaces_compliance(qaqc) # Exterior Ground surfaces necb_section_name = get_qaqc_table(table_name: 'exterior_ground_surfaces_compliance')['refs'].join(',') climate_index = NECB2011.new.get_climate_zone_index(qaqc[:geography][:hdd]) tolerance = 3 # puts "\n\n" # puts "climate_index: #{climate_index}" # puts get_qaqc_table("exterior_ground_surfaces_compliance", {"var" => "ground_wall_conductances", "climate_index" => 2}) ['ground_wall_conductances', 'ground_roof_conductances', 'ground_floor_conductances'].each do |compliance_var| qaqc_table = get_qaqc_table(table_name: 'exterior_ground_surfaces_compliance', search_criteria: { 'var' => compliance_var, 'climate_index' => climate_index }).first # puts "\n#{qaqc_table}\n" if compliance_var == 'ground_wall_conductances' result_value = qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] elsif compliance_var == 'ground_roof_conductances' result_value = qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] elsif compliance_var == 'ground_floor_conductances' result_value = qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] end test_text = "[ENVELOPE] #{compliance_var}" next if result_value.nil? necb_section_test( qaqc, result_value, qaqc_table['bool_operator'], qaqc_table['expected_value'], necb_section_name, test_text, tolerance ) end # necb_section_name = "NECB2011-Section 3.2.3.1" # climate_index = BTAP::Compliance::NECB2011::get_climate_zone_index(qaqc[:geography][:hdd]) # result_value_index = 6 # round_precision = 3 # data = {} # data[:ground_wall_conductances] = [ 0.568,0.379,0.284,0.284,0.284,0.210, qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] ] unless qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k].nil? # data[:ground_roof_conductances] = [ 0.568,0.379,0.284,0.284,0.284,0.210, qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] ] unless qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k].nil? # data[:ground_floor_conductances] = [ 0.757,0.757,0.757,0.757,0.757,0.379, qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] ] unless qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k].nil? # data.each {|key,value| necb_section_test( # qaqc, # value[result_value_index], # '==', # value[climate_index], # necb_section_name, # "[ENVELOPE]#{key}", # round_precision # ) # } end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1255 def necb_exterior_opaque_compliance(qaqc) # puts JSON.pretty_generate @qaqc_data # Exterior Opaque necb_section_name = get_qaqc_table(table_name: 'exterior_opaque_compliance')['refs'].join(',') climate_index = NECB2011.new.get_climate_zone_index(qaqc[:geography][:hdd]) puts "HDD #{qaqc[:geography][:hdd]}" tolerance = 3 # puts "\n\n" # puts "climate_index: #{climate_index}" # puts get_qaqc_table("exterior_opaque_compliance", {"var" => "ext_wall_conductances", "climate_index" => 2}) ['ext_wall_conductances', 'ext_roof_conductances', 'ext_floor_conductances'].each do |compliance_var| qaqc_table = get_qaqc_table(table_name: 'exterior_opaque_compliance', search_criteria: { 'var' => compliance_var, 'climate_index' => climate_index }).first # puts "\n#{qaqc_table}\n" if compliance_var == 'ext_wall_conductances' result_value = qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k] elsif compliance_var == 'ext_roof_conductances' result_value = qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k] elsif compliance_var == 'ext_floor_conductances' result_value = qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k] end test_text = "[ENVELOPE] #{compliance_var}" next if result_value.nil? necb_section_test( qaqc, result_value, qaqc_table['bool_operator'], qaqc_table['expected_value'], necb_section_name, test_text, tolerance ) end # result_value_index = 6 # round_precision = 3 # data = {} # data[:ext_wall_conductances] = [0.315,0.278,0.247,0.210,0.210,0.183,qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k].nil? # data[:ext_roof_conductances] = [0.227,0.183,0.183,0.162,0.162,0.142,qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k].nil? # data[:ext_floor_conductances] = [0.227,0.183,0.183,0.162,0.162,0.142,qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k].nil? # data.each {|key,value| necb_section_test( # qaqc, # value[result_value_index], # '==', # value[climate_index], # necb_section_name, # "[ENVELOPE]#{key}", # round_precision # ) # } end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1573 def necb_hrv_compliance(qaqc, model) # HRV check hrv_compliance = get_qaqc_table(table_name: 'hrv_compliance')['table'] necb_section_name = get_qaqc_table(table_name: 'hrv_compliance')['refs'].join(',') qaqc[:air_loops].each do |air_loop_info| hrv_compliance.each do |compliance| data = {} # puts "\nspaceinfo[#{compliance['var']}]" result_value = !air_loop_info[:heat_exchanger].empty? # puts "#{compliance['test_text']}" test_text = "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?" # puts "result_value: #{result_value}" # puts "test_text: #{test_text}\n" # data[:infiltration_method] = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ] # data[:infiltration_flow_per_m2] = [ 0.00025, spaceinfo[:infiltration_flow_per_m2], 5 ] # data.each do |key,value| # puts key outdoor_air_L_per_s = air_loop_info[:outdoor_air_L_per_s] weather_file_path = model.weatherFile.get.path.get.to_s stat_file_path = weather_file_path.gsub('.epw', '.stat') stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) db990 = stat_file.heating_design_info[2] necb_section_test( qaqc, result_value, '==', eval(compliance['expected_value']), necb_section_name, test_text, compliance['tolerance'] ) end end # necb_section_name = "NECB2011-5.2.10.1" # qaqc[:air_loops].each do |air_loop_info| # unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 # weather_file_path = model.weatherFile.get.path.get.to_s # stat_file_path = weather_file_path.gsub('.epw', '.stat') # stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) # db990 = stat_file.heating_design_info[2] # hrv_calc = 0.00123 * air_loop_info[:outdoor_air_L_per_s] * (21 - db990) #=AP46*(21-O$1) # hrv_reqd = hrv_calc > 150 ? true : false # #qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}" # hrv_present = false # unless air_loop_info[:heat_exchanger].empty? # hrv_present = true # end # necb_section_test( # qaqc, # hrv_reqd, # '==', # hrv_present, # necb_section_name, # "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?" # ) # else # qaqc['warnings'] << "[hrv_compliance] air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 for [#{air_loop_info[:name]}]" # end # end end
This methos is not used as part of the QAQC process, because support for MURBS has not been implemented for HRV in NECB 2011 and 2015
This method will run the HRV compliance for a single air loop
@param qaqc [:hash] Hash
that contains the base data with qaqc keys @param model [:OS:Model] OpenStudio Model @param air_loop_info [:hash] single air_loop object from the qaqc hash
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1728 def necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info) # HRV check hrv_compliance = get_qaqc_table('hrv_compliance')['table'] necb_section_name = get_qaqc_table('hrv_compliance')['refs'].join(',') hrv_compliance.each do |compliance| data = {} # puts "\nspaceinfo[#{compliance['var']}]" result_value = !air_loop_info[:heat_exchanger].empty? # puts "#{compliance['test_text']}" test_text = "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?" # puts "result_value: #{result_value}" # puts "test_text: #{test_text}\n" # data[:infiltration_method] = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ] # data[:infiltration_flow_per_m2] = [ 0.00025, spaceinfo[:infiltration_flow_per_m2], 5 ] # data.each do |key,value| # puts key outdoor_air_L_per_s = air_loop_info[:outdoor_air_L_per_s] weather_file_path = model.weatherFile.get.path.get.to_s stat_file_path = weather_file_path.gsub('.epw', '.stat') stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) db990 = stat_file.heating_design_info[2] necb_section_test( qaqc, result_value, '==', eval(compliance['expected_value']), necb_section_name, test_text, compliance['tolerance'] ) end # necb_section_name = "NECB2011-5.2.10.1" # qaqc[:air_loops].each do |air_loop_info| # unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 # weather_file_path = model.weatherFile.get.path.get.to_s # stat_file_path = weather_file_path.gsub('.epw', '.stat') # stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) # db990 = stat_file.heating_design_info[2] # hrv_calc = 0.00123 * air_loop_info[:outdoor_air_L_per_s] * (21 - db990) #=AP46*(21-O$1) # hrv_reqd = hrv_calc > 150 ? true : false # #qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}" # hrv_present = false # unless air_loop_info[:heat_exchanger].empty? # hrv_present = true # end # necb_section_test( # qaqc, # hrv_reqd, # '==', # hrv_present, # necb_section_name, # "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?" # ) # else # qaqc['warnings'] << "[hrv_compliance] air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 for [#{air_loop_info[:name]}]" # end # end end
This methos is not used as part of the QAQC process, because support for MURBS has not been implemented for HRV in NECB 2011 and 2015
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1638 def necb_hrv_compliance_inc_murb(qaqc, model) murb_hrv_compliance = get_qaqc_table('murb_hrv_compliance') hrv_spacetpye_ignore_regex = murb_hrv_compliance['ignored_spacetypes_regex'] hrv_dwelling_unit_spacetpye_regex = murb_hrv_compliance['dwelling_unit_spacetype_regex'] necb_section_name = murb_hrv_compliance['refs'].join(',') model.getAirLoopHVACs.sort.each do |air_loop| air_loop_info = {} qaqc[:air_loops].each do |air_loop_i| next unless air_loop_i[:name] == air_loop.name.get air_loop_info = air_loop_i end zones = air_loop.thermalZones if zones.length == 1 # here the Airloop is serving only one zone # So, next we need to determine if the zone has only # one dwelling unit and no other space types other than stairs, corridor, and lobby zone = zones.first # get the spaces and keep track of the number of spaces and dewlling units num_of_served_spaces = zone.spaces.length contains_dwelling_unit = false if num_of_served_spaces == 0 qaqc[:warnings] << "[necb_murb_hrv_compliance] Thermal Zone [#{zone.name}] does not serve any Spaces" else spaces = zone.spaces() spaces.each do |z_space| spacetype = z_space.spaceType spacetype = validate_optional(spacetype, model, nil) if spacetype.nil? qaqc[:warnings] << "[necb_murb_hrv_compliance] Space [#{z_space.name}] does not have a SpaceType" else # reduce the number of spaces if the space served by the thermal zone is a # stairwell/staircase/lobby/corridor spacetype_name = spacetype.name.to_s ignored_spacetypes_regex = Regexp.new(hrv_spacetpye_ignore_regex, Regexp::IGNORECASE) if ignored_spacetypes_regex =~ spacetype_name num_of_served_spaces -= 1 end # detect is the thermal zone serves a Dwelling Unit dwelling_unit_regex = Regexp.new(hrv_dwelling_unit_spacetpye_regex, Regexp::IGNORECASE) if dwelling_unit_regex =~ spacetype_name contains_dwelling_unit = true end end end if (num_of_served_spaces == 1) && contains_dwelling_unit # here the Thermal zone serves one space that is a dwelling unit # and other space types such as lobby, stairs, or corridors are ignored # So in this case, an HRV is required test_text = "[AIR LOOP][:heat_exchanger] (murb) for [#{air_loop_info[:name]}] is present?" result_value = murb_hrv_compliance['table']['expected_value'] necb_section_test( qaqc, result_value, '==', true, necb_section_name, test_text, nil ) else # Here either the number of served spaces exceed 1 or # does not contain a dwelling unit, So a regular HRV check has to be done for this air loop qaqc[:warnings] << "[necb_murb_hrv_compliance] Regular HRV compliance check for airloop: [#{air_loop.name}], because (it does not serve a single dwelling unit) OR (serves multiple spacetypes)" necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info) end end else # here the Airloop does not serve any zones, or it serves more than one zone # So, a regular hrv compliance must be done for this air loop qaqc[:warnings] << "[necb_murb_hrv_compliance] Regular HRV compliance check for airloop: [#{air_loop.name}], because it serves multiple Thermal zones" necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info) end end end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1202 def necb_infiltration_compliance(qaqc, model) # Infiltration # puts "\n" # puts get_qaqc_table("infiltration_compliance") # puts "\n" # puts "\n" # puts get_qaqc_table("infiltration_compliance", {"var" => ":infiltration_method"} ) # puts "\n" # puts "\n" infiltration_compliance = get_qaqc_table(table_name: 'infiltration_compliance')['table'] necb_section_name = get_qaqc_table(table_name: 'infiltration_compliance')['refs'].join(',') qaqc[:spaces].each do |spaceinfo| model.getSpaces.sort.each do |space| next unless space.name.get == spaceinfo[:name] found = false space.surfaces.each do |surface| next unless surface.outsideBoundaryCondition == 'Outdoors' found = true # peform this infiltration qaqc if and only if the space's surface is in contact with outdoors infiltration_compliance.each do |compliance| # puts "\nspaceinfo[#{compliance['var']}]" eval_string = "spaceinfo[:#{compliance['var']}]" result_value = eval(eval_string) # puts "#{compliance['test_text']}" test_text = "[SPACE][#{spaceinfo[:name]}]-#{compliance['var']}" # puts "result_value: #{result_value}" # puts "test_text: #{test_text}\n" # data[:infiltration_method] = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ] # data[:infiltration_flow_per_m2] = [ 0.00025, spaceinfo[:infiltration_flow_per_m2], 5 ] # data.each do |key,value| # puts key necb_section_test( qaqc, result_value, compliance['bool_operator'], compliance['expected_value'], necb_section_name, test_text, compliance['tolerance'] ) end # peform qaqc only once per space break end if !found qaqc[:warnings] << "necb_infiltration_compliance for SPACE:[#{spaceinfo[:name]}] was skipped because it does not contain surfaces with 'Outside' boundary condition." end end end end
checks the pump power using pressure, and flowrate
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1035 def necb_plantloop_sanity(qaqc) necb_section_name = 'SANITY-??' qaqc[:plant_loops].each do |plant_loop_info| pump_head = plant_loop_info[:pumps][0][:head_pa] flow_rate = plant_loop_info[:pumps][0][:water_flow_m3_per_s] * 1000 hp_check = ((flow_rate * 60 * 60) / 1000 * 1000 * 9.81 * pump_head * 0.000101997) / 3600000 puts "\npump_head #{pump_head}" puts "name: #{qaqc[:building][:name]}" puts "name: #{plant_loop_info[:name]}" puts "flow_rate #{flow_rate}" puts "hp_check #{hp_check}\n" pump_power_hp = plant_loop_info[:pumps][0][:electric_power_w] / 1000 * 0.746 percent_diff = (hp_check - pump_power_hp).to_f.abs / hp_check * 100 if percent_diff.nan? qaqc[:ruby_warnings] << "(hp_check - pump_power_hp).to_f.abs/hp_check * 100 for #{plant_loop_info[:name]} is NaN" next end if pump_power_hp < 1.0 qaqc[:warnings] << "necb_plantloop_sanity [SKIP] [PLANT LOOP][#{plant_loop_info[:name]}][:pumps][0][:electric_power_hp] because pump_power_hp: [#{pump_power_hp}] < 1 hp" next end necb_section_test( qaqc, percent_diff, '<=', 20, # diff of 20% necb_section_name, "[PLANT LOOP][#{plant_loop_info[:name]}][:pumps][0][:electric_power_hp] [#{pump_power_hp}]; NECB value [#{hp_check}]; Percent Diff" ) end end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1841 def necb_qaqc(qaqc, model) puts "\n\nin necb_qaqc 2011 now\n\n" # Now perform basic QA/QC on items for NECB2011 qaqc[:information] = [] qaqc[:warnings] = [] qaqc[:errors] = [] qaqc[:unique_errors] = [] necb_space_compliance(qaqc) necb_envelope_compliance(qaqc) necb_infiltration_compliance(qaqc, model) necb_exterior_opaque_compliance(qaqc) necb_exterior_fenestration_compliance(qaqc) necb_exterior_ground_surfaces_compliance(qaqc) necb_zone_sizing_compliance(qaqc) necb_design_supply_temp_compliance(qaqc) necb_economizer_compliance(qaqc) necb_hrv_compliance(qaqc, model) necb_vav_fan_power_compliance(qaqc) sanity_check(qaqc) necb_plantloop_sanity(qaqc) qaqc[:information] = qaqc[:information].sort qaqc[:warnings] = qaqc[:warnings].sort qaqc[:errors] = qaqc[:errors].sort qaqc[:unique_errors] = qaqc[:unique_errors].sort return qaqc end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1882 def necb_section_test(qaqc, result_value, bool_operator, expected_value, necb_section_name, test_text, tolerance = nil) test = 'eval_failed' command = '' if tolerance.is_a?(Integer) command = "#{result_value}.round(#{tolerance}) #{bool_operator} #{expected_value}.round(#{tolerance})" elsif expected_value.is_a?(String) && result_value.is_a?(String) command = "'#{result_value}' #{bool_operator} '#{expected_value}'" else command = "#{result_value} #{bool_operator} #{expected_value}" end test = eval(command) test_res = nil test_res = true if test.to_s.downcase == 'true' test_res = false if test.to_s.downcase == 'false' raise "Eval command failed #{test}" if test_res.nil? if test_res qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}" else qaqc[:errors] << "[ERROR][TEST-FAIL][#{necb_section_name}]:#{test_text} expected value:#{expected_value} #{bool_operator} result value:#{result_value}" unless (expected_value == -1.0) || (expected_value == 'N/A') qaqc[:unique_errors] << "[ERROR][TEST-FAIL][#{necb_section_name}]:#{test_text} expected value:#{expected_value} #{bool_operator} result value:#{result_value}" end end end
checks space compliance Re: lighting_per_area, occupancy_per_area, occupancy_schedule, electric_equipment_per_area
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1073 def necb_space_compliance(qaqc) # #Padmassun's Code Start # csv_file_name ="#{File.dirname(__FILE__)}/necb_2011_spacetype_info.csv" qaqc[:spaces].each do |space| building_type = '' space_type = '' if space[:space_type_name].include? 'Space Function ' space_type = space[:space_type_name].to_s.rpartition('Space Function ')[2].strip building_type = 'Space Function' elsif space[:space_type_name].include? ' WholeBuilding' space_type = space[:space_type_name].to_s.rpartition(' WholeBuilding')[0].strip building_type = 'WholeBuilding' end ['lighting_per_area_w_per_m2', 'occupancy_per_area_people_per_m2', 'occupancy_schedule', 'electric_equipment_per_area_w_per_m2'].each do |compliance_var| qaqc_table = get_qaqc_table(table_name: 'space_compliance', search_criteria: { 'building_type' => building_type, 'space_type' => space_type }).first puts "\n#{qaqc_table}\n" necb_section_name = get_qaqc_table(table_name: 'space_compliance')['refs'][compliance_var] tolerance = get_qaqc_table(table_name: 'space_compliance')['tolerance'][compliance_var] # puts "\ncompliance_var:#{compliance_var}\n\tnecb_section_name:#{necb_section_name}\n\texp Value:#{qaqc_table[compliance_var]}\n" if compliance_var == 'lighting_per_area_w_per_m2' if space[:lighting_w_per_m2].nil? result_value = 0 else result_value = space[:lighting_w_per_m2] * qaqc_table['lpd_ratio'] end elsif compliance_var == 'occupancy_per_area_people_per_m2' result_value = space[:occ_per_m2] elsif compliance_var == 'occupancy_schedule' result_value = space[:occupancy_schedule] elsif compliance_var == 'electric_equipment_per_area_w_per_m2' result_value = space[:electric_w_per_m2] end test_text = "[SPACE][#{space[:name]}]-[TYPE:][#{space_type}]-#{compliance_var}" next if result_value.nil? necb_section_test( qaqc, result_value, '==', qaqc_table[compliance_var], necb_section_name, test_text, tolerance ) end # row = look_up_csv_data(csv_file_name,{2 => space_type, 1 => building_type}) # if row.nil? # #raise ("space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]") # qaqc[:ruby_warnings] << "space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]" # puts "space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]" # else # #correct the data from the csv file to include a multiplier of 0.9 for specific space types. # reduceLPDSpaces = ["Classroom/lecture/training", "Conf./meet./multi-purpose", "Lounge/recreation", # "Washroom-sch-A", "Washroom-sch-B", "Washroom-sch-C", "Washroom-sch-D", "Washroom-sch-E", # "Washroom-sch-F", "Washroom-sch-G", "Washroom-sch-H", "Washroom-sch-I", "Dress./fitt. - performance arts", # "Locker room", "Retail - dressing/fitting","Locker room-sch-A","Locker room-sch-B","Locker room-sch-C", # "Locker room-sch-D","Locker room-sch-E","Locker room-sch-F","Locker room-sch-G","Locker room-sch-H", # "Locker room-sch-I", "Office - open plan - occsens", "Office - enclosed - occsens", "Storage area - occsens", # "Hospital - medical supply - occsens", "Storage area - refrigerated - occsens"] # if reduceLPDSpaces.include?(space_type) # row[3] = row[3]*0.9 # puts "\n============================\nspace_type: #{space_type}\n============================\n" # end # # Start of Space Compliance # necb_section_name = "NECB2011-Section 8.4.3.6" # data = {} # data[:lighting_per_area] = [ row[3],'==',space[:lighting_w_per_m2] , "Table 4.2.1.6" ,1 ] unless space[:lighting_w_per_m2].nil? # data[:occupancy_per_area] = [ row[4],'==',space[:occ_per_m2] , "Table A-8.4.3.3.1" ,3 ] unless space[:occ_per_m2].nil? # data[:occupancy_schedule] = [ row[5],'==',space[:occupancy_schedule], "Table A-8.4.3.3.1" ,nil ] unless space[:occupancy_schedule].nil? # data[:electric_equipment_per_area] = [ row[6],'==',space[:electric_w_per_m2] , "Table A-8.4.3.3.1" ,1 ] unless space[:electric_w_per_m2].nil? # data.each do |key,value| # #puts key # necb_section_test( # qaqc, # value[0], # value[1], # value[2], # value[3], # "[SPACE][#{space[:name]}]-[TYPE:][#{space_type}]#{key}", # value[4] # ) # end # end#space Compliance end # Padmassun's Code End end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1788 def necb_vav_fan_power_compliance(qaqc) necb_section_name = get_qaqc_table(table_name: 'vav_fan_power_compliance')['refs'].join(',') qaqc_table = get_qaqc_table(table_name: 'vav_fan_power_compliance') # necb_section_name = "NECB2011-5.2.3.3" qaqc[:air_loops].each do |air_loop_info| # necb_clg_cop = air_loop_info[:cooling_coils][:dx_single_speed][:cop] #*assuming that the cop is defined correctly* if air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s].nil? qaqc[:warnings] << '[vav_fan_power_compliance] air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] is nil' next end max_air_flow_rate_m3_per_s = air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] necb_supply_fan_w = -1 if air_loop_info[:name].include? 'PSZ' necb_supply_fan_w = eval(qaqc_table['formulas']['NECB PSZ fan power (W)']).round(2) elsif air_loop_info[:name].include? 'VAV' necb_supply_fan_w = eval(qaqc_table['formulas']['NECB VAV fan power (W)']).round(2) end if air_loop_info[:supply_fan][:rated_electric_power_w].nil? qaqc[:warnings] << '[vav_fan_power_compliance] air_loop_info[:supply_fan][:rated_electric_power_w] is nil' next end supply_fan_w = (air_loop_info[:supply_fan][:rated_electric_power_w]).round(3) absolute_diff = (necb_supply_fan_w - supply_fan_w).to_f.abs if absolute_diff < 10 # This case should ALWAYS PASS necb_section_test( qaqc, 10, '>=', absolute_diff, necb_section_name, "[AIR LOOP][#{air_loop_info[:name]}][:supply_fan][:rated_electric_power_w] [#{supply_fan_w}] Absolute Difference from NECB value [#{necb_supply_fan_w}]" ) next else # The test should pass if and only if the percent difference is less than 10% percent_diff = ((necb_supply_fan_w - supply_fan_w).to_f.abs / necb_supply_fan_w * 100).round(3) necb_section_test( qaqc, 10, '>=', percent_diff, necb_section_name, "[AIR LOOP][#{air_loop_info[:name]}][:supply_fan][:rated_electric_power_w] [#{supply_fan_w}] Percent Diff from NECB value [#{necb_supply_fan_w}]" ) end end end
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1418 def necb_zone_sizing_compliance(qaqc) # Zone Sizing test necb_section_name = get_qaqc_table(table_name: 'zone_sizing_compliance')['refs'].join(',') qaqc_table = get_qaqc_table(table_name: 'zone_sizing_compliance') tolerance = 3 # necb_section_name = "NECB2011-?" # round_precision = 3 qaqc[:thermal_zones].each do |zoneinfo| # skipping undefined schedules if (qaqc_table['exclude']['exclude_string'].any? { |ex_string| zoneinfo[:name].to_s.include? ex_string }) && !qaqc_table['exclude']['exclude_string'].empty? # if zoneinfo[:name].to_s.include?"- undefined -" puts "#{zoneinfo[:name]} was skipped in necb_zone_sizing_compliance because it contains #{qaqc_table['exclude']['exclude_string'].join(',')}" next end zone_sizing_compliance = qaqc_table['table'] zone_sizing_compliance.each do |compliance| eval_string = "zoneinfo[:#{compliance['var']}]" result_value = eval(eval_string) next if result_value.nil? test_text = "[ZONE][#{zoneinfo[:name]}] #{compliance['var']}" # puts key necb_section_test( qaqc, result_value, compliance['bool_operator'], compliance['expected_value'], necb_section_name, test_text, tolerance ) end # data = {} # data[:heating_sizing_factor] = [1.3 , zoneinfo[:heating_sizing_factor]] # data[:cooling_sizing_factor] = [1.1 ,zoneinfo[:cooling_sizing_factor]] # #data[:heating_design_supply_air_temp] = [43.0, zoneinfo[:zone_heating_design_supply_air_temperature] ] #unless zoneinfo[:zone_heating_design_supply_air_temperature].nil? # #data[:cooling_design_supply_temp] = [13.0, zoneinfo[:zone_cooling_design_supply_air_temperature] ] # data.each do |key,value| # #puts key # necb_section_test( # qaqc, # value[0], # '==', # value[1], # necb_section_name, # "[ZONE][#{zoneinfo[:name]}] #{key}", # round_precision # ) # end end end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb, line 163 def new_add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, chiller_type:, fan_type:, hw_loop:) # System Type 6: VAV w/ Reheat # This measure creates: # a single hot water loop with a natural gas or electric boiler or for the building # a single chilled water loop with water cooled chiller for the building # a single condenser water loop for heat rejection from the chiller # a VAV system w/ hot water or electric heating, chilled water cooling, and # hot water or electric reheat for each story of the building # Arguments: # "boiler_fueltype" choices match OS choices for boiler fuel type: # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1" # "heating_coil_type": "Electric" or "Hot Water" # "baseboard_type": "Electric" and "Hot Water" # "chiller_type": "Scroll";"Centrifugal";""Screw";"Reciprocating" # "fan_type": "AF_or_BI_rdg_fancurve";"AF_or_BI_inletvanes";"fc_inletvanes";"var_speed_drive" system_6_data = {} system_6_data[:name] = 'Sys_6_VAV with Reheat' system_6_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0 system_6_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0 system_6_data[:AllOutdoorAirinCooling] = false system_6_data[:AllOutdoorAirinHeating] = false system_6_data[:MinimumSystemAirFlowRatio] = 0.03 # zone data system_6_data[:system_supply_air_temperature] = 13.0 system_6_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_6_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0 system_6_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference' system_6_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0 system_6_data[:ZoneCoolingSizingFactor] = 1.1 system_6_data[:ZoneHeatingSizingFactor] = 1.3 system_6_data[:ZoneVAVMinFlowFactorPerFloorArea] = 0.002 system_6_data[:ZoneVAVMaxReheatTemp] = 43.0 system_6_data[:ZoneVAVDamperAction] = 'Normal' system_data = system_6_data always_on = model.alwaysOnDiscreteSchedule # Chilled Water Plant chw_loop = OpenStudio::Model::PlantLoop.new(model) chiller1, chiller2 = setup_chw_loop_with_components(model, chw_loop, chiller_type) # Condenser System cw_loop = OpenStudio::Model::PlantLoop.new(model) ctower = setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2) # Make a Packaged VAV w/ PFP Boxes for each story of the building model.getBuildingStorys.sort.each do |story| unless (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).empty? air_loop = common_air_loop(model: model, system_data: system_data) air_loop.setName(system_data[:name]) air_loop_sizing = air_loop.sizingSystem air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(system_data[:CentralCoolingDesignSupplyAirTemperature]) air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(system_data[:CentralHeatingDesignSupplyAirTemperature]) air_loop_sizing.setAllOutdoorAirinCooling(system_data[:AllOutdoorAirinCooling]) air_loop_sizing.setAllOutdoorAirinHeating(system_data[:AllOutdoorAirinHeating]) if model.version < OpenStudio::VersionString.new('2.7.0') air_loop_sizing.setMinimumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio]) else air_loop_sizing.setCentralHeatingMaximumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio]) end supply_fan = OpenStudio::Model::FanVariableVolume.new(model, always_on) supply_fan.setName('Sys6 Supply Fan') return_fan = OpenStudio::Model::FanVariableVolume.new(model, always_on) return_fan.setName('Sys6 Return Fan') if heating_coil_type == 'Hot Water' htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) hw_loop.addDemandBranchForComponent(htg_coil) end if heating_coil_type == 'Electric' htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) end clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, always_on) chw_loop.addDemandBranchForComponent(clg_coil) oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model) oa_controller.autosizeMinimumOutdoorAirFlowRate # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be # set explicitly) oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum') oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller) # Add the components to the air loop # in order from closest to zone to furthest from zone supply_inlet_node = air_loop.supplyInletNode supply_outlet_node = air_loop.supplyOutletNode supply_fan.addToNode(supply_inlet_node) htg_coil.addToNode(supply_inlet_node) clg_coil.addToNode(supply_inlet_node) oa_system.addToNode(supply_inlet_node) returnAirNode = oa_system.returnAirModelObject.get.to_Node.get return_fan.addToNode(returnAirNode) # Add a setpoint manager to control the # supply air to a constant temperature sat_sch = OpenStudio::Model::ScheduleRuleset.new(model) sat_sch.setName('Supply Air Temp') sat_sch.defaultDaySchedule.setName('Supply Air Temp Default') sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature]) sat_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch) sat_stpt_manager.addToNode(supply_outlet_node) # Make a VAV terminal with HW reheat for each zone on this story that is in intersection with the zones array. # and hook the reheat coil to the HW loop (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).each do |zone| # Zone sizing parameters sizing_zone = zone.sizingZone sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature]) sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature]) sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor]) sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor]) if heating_coil_type == 'Hot Water' reheat_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on) hw_loop.addDemandBranchForComponent(reheat_coil) elsif heating_coil_type == 'Electric' reheat_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on) end vav_terminal = OpenStudio::Model::AirTerminalSingleDuctVAVReheat.new(model, always_on, reheat_coil) air_loop.addBranchForZone(zone, vav_terminal.to_StraightComponent) # NECB2011 minimum zone airflow setting vav_terminal.setFixedMinimumAirFlowRate(system_data[:ZoneVAVMinFlowFactorPerFloorArea] * zone.floorArea) vav_terminal.setMaximumReheatAirTemperature(system_data[:ZoneVAVMaxReheatTemp]) vav_terminal.setDamperHeatingAction(system_data[:ZoneVAVDamperAction]) # Set zone baseboards add_zone_baseboards(model: model, zone: zone, baseboard_type: baseboard_type, hw_loop: hw_loop) end end # next story end # for debugging # puts "end add_sys6_multi_zone_built_up_with_baseboard_heating" return true end
Math fundtion to determine percent difference.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 721 def percentage_difference(value_1, value_2) return 0.0 if value_1 == value_2 return ((value_1 - value_2).abs / ((value_1 + value_2) / 2) * 100) end
Determines the minimum pump motor efficiency and nominal size for a given motor bhp. This should be the total brake horsepower with any desired safety factor already included. This method picks the next nominal motor catgory larger than the required brake horsepower, and the efficiency is based on that size. For example, if the bhp = 6.3, the nominal size will be 7.5HP and the efficiency for 90.1-2010 will be 91.7% from Table 10.8B. This method assumes 4-pole, 1800rpm totally-enclosed fan-cooled motors.
@param motor_bhp [Double] motor brake horsepower (hp) @return [Array<Double>] minimum motor efficiency (0.0 to 1.0), nominal horsepower
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1459 def pump_standard_minimum_motor_efficiency_and_size(pump, motor_bhp) motor_eff = 0.85 nominal_hp = motor_bhp # Don't attempt to look up motor efficiency # for zero-hp pumps (required for circulation-pump-free # service water heating systems). return [1.0, 0] if motor_bhp == 0.0 # Lookup the minimum motor efficiency motors = @standards_data['motors'] # Assuming all pump motors are 4-pole ODP search_criteria = { 'motor_use' => 'PUMP', 'number_of_poles' => 4.0, 'type' => 'Enclosed' } motor_properties = model_find_object(motors, search_criteria, motor_bhp) if motor_properties.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Pump', "For #{pump.name}, could not find motor properties using search criteria: #{search_criteria}, motor_bhp = #{motor_bhp} hp.") return [motor_eff, nominal_hp] end motor_eff = motor_properties['nominal_full_load_efficiency'] nominal_hp = motor_properties['maximum_capacity'].to_f.round(1) # Round to nearest whole HP for niceness if nominal_hp >= 2 nominal_hp = nominal_hp.round end # Get the efficiency based on the nominal horsepower # Add 0.01 hp to avoid search errors. motor_properties = model_find_object(motors, search_criteria, nominal_hp + 0.01) if motor_properties.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{pump.name}, could not find nominal motor properties using search criteria: #{search_criteria}, motor_hp = #{nominal_hp} hp.") return [motor_eff, nominal_hp] end motor_eff = motor_properties['nominal_full_load_efficiency'] return [motor_eff, nominal_hp] end
Determine and set type of part load control type for heating and chilled water variable speed pumps
@param pump [OpenStudio::Model::PumpVariableSpeed] OpenStudio pump object @return [Boolean] Returns true if applicable, false otherwise
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1539 def pump_variable_speed_control_type(pump) return false end
generates only qaqc component
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 168 def qaqc_only(model) # load the qaqc.json files @qaqc_data = load_qaqc_database_new # generate base qaqc hash qaqc = create_base_data(model) # performs the qaqc on the given base qaqc hash. # using `qaqc.clone` as an argument to pass in a shallow copy, so that the argument passed can stay unmodified. necb_qaqc_with_base = necb_qaqc(qaqc.clone, model) # subract base data from qaqc return (necb_qaqc_with_base.to_a - qaqc.to_a).to_h end
Loop through the layers of the construction of the surface and replace any massless material with a standard one. The material used instead is from the EnergyPlus dataset file âASHRAE_2005_HOF_Materials.idfâ with the name: âInsulation: Expanded polystyrene - extruded (smooth skin surface) (HCFC-142b exp.)â. The thickness of the new material is based on the thermal resistance of the massless material it replaces. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 861 def replace_massless_material_with_std_material(model,surf) std_const_name = "#{surf.construction.get.name.to_s}_std" std_const = model.getLayeredConstructions.select {|const| const.name.to_s == std_const_name} new_const = nil if !std_const.empty? new_const = std_const[0] else new_layers = {} has_massless_mat = false layer_index = 0 surf.construction.get.to_LayeredConstruction.get.layers.each do |layer| if layer.to_MasslessOpaqueMaterial.is_initialized then has_massless_mat = true new_mat = OpenStudio::Model::StandardOpaqueMaterial.new(model) new_mat.setName("Expanded Polystyrene") new_mat.setThermalConductivity(0.029) new_mat.setDensity(29.0) new_mat.setSpecificHeat(1210.0) new_mat.setRoughness('MediumSmooth') new_mat.setThickness(layer.to_MasslessOpaqueMaterial.get.thermalResistance.to_f * new_mat.thermalConductivity.to_f) else new_mat = layer end new_layers[layer_index] = new_mat layer_index += 1 end if has_massless_mat new_const = OpenStudio::Model::Construction.new(model) new_layers.keys.sort.each {|layer_index| new_const.to_LayeredConstruction.get.insertLayer(layer_index,new_layers[layer_index])} new_const.setName("#{surf.construction.get.name.to_s}_std") end end surf.setConstruction(new_const) if !new_const.nil? end
Checks if a space with a proper schedule is conditioned or not
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1002 def sanity_check(qaqc) qaqc[:sanity_check] = {} qaqc[:sanity_check][:fail] = [] qaqc[:sanity_check][:pass] = [] # Padmassun's code for isConditioned start qaqc[:thermal_zones].each do |zoneinfo| zoneinfo[:spaces].each do |space| # skip plenums and undefined spaces/zones if zoneinfo[:name].to_s.include? '- undefined -' next end if zoneinfo[:space_type_name].to_s.include? 'Space Function - undefined -' if zoneinfo[:is_conditioned].to_s == 'No' qaqc[:sanity_check][:pass] << "[TEST-PASS][SANITY_CHECK-PASS] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'No' "] and found as #{zoneinfo[:is_conditioned]}" else qaqc[:sanity_check][:fail] << "[ERROR][SANITY_CHECK-FAIL] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'No' "] but found as #{zoneinfo[:is_conditioned]}" end else if zoneinfo[:is_conditioned].to_s == 'Yes' qaqc[:sanity_check][:pass] << "[TEST-PASS][SANITY_CHECK-PASS] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'Yes' "] and found as #{zoneinfo[:is_conditioned]}" elsif zoneinfo[:name] qaqc[:sanity_check][:fail] << "[ERROR][SANITY_CHECK-FAIL] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'Yes' "] but found as #{zoneinfo[:is_conditioned]}" end end end end qaqc[:sanity_check][:fail] = qaqc[:sanity_check][:fail].sort qaqc[:sanity_check][:pass] = qaqc[:sanity_check][:pass].sort # Padmassun's code for isConditioned end end
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 631 def scale_model_geometry(model, x_scale, y_scale, z_scale) # Identity matrix for setting space origins m = OpenStudio::Matrix.new(4, 4, 0) m[0, 0] = 1.0 / x_scale m[1, 1] = 1.0 / y_scale m[2, 2] = 1.0 / z_scale m[3, 3] = 1.0 t = OpenStudio::Transformation.new(m) model.getPlanarSurfaceGroups.each do |planar_surface| planar_surface.changeTransformation(t) end return model end
This method expects a string with the following pattern: number-number The first number is the percent of the total capacity that the primary boilerâs capacity will be set to. The second number is the percent of the total capacity that the secondary boilerâs capacity will be set to. If no boiler_cap_ratio is provided the the primary boiler will have its capacity set to 75% of the total and the secondary boiler will have its capacity set to 25% of the total. If a boiler_cap_ratio is set to â0_0â then the NECB default capacities are assigned.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2450 def set_boiler_cap_ratios(boiler_cap_ratio:, boiler_fuel:) # Rules if boiler_fuel is defined unless boiler_fuel.nil? # Set the NECB default boiler capacities if the boiler_cap_ratio is set to '0-0' if boiler_cap_ratio == '0-0' boiler_cap_ratios = { primary_ratio: nil, secondary_ratio: nil } return boiler_cap_ratios elsif !boiler_fuel.to_s.downcase.include?('backup') && boiler_cap_ratio.nil? # Set the NECB default boiler capacities if the boiler_cap_ratio in not defined and the boiler fuel type is set # and the primary and secondary fuel types are the same. boiler_cap_ratios = { primary_ratio: nil, secondary_ratio: nil } return boiler_cap_ratios end end # Assuming the above rules do not apply, set the default boiler capacity ratio to 75% for the primary boiler and 25% # for the secondary boiler if boiler_cap_ratio.nil? boiler_cap_ratios = { primary_ratio: 0.75, secondary_ratio: 0.25 } return boiler_cap_ratios end # If you defined the boiler capacity ratios set them accordingly. # Split the capacity ratio using the '-' symbol string_ratios = boiler_cap_ratio.to_s.split('-') # Turn the percentages into fractions primary_ratio = string_ratios[0].to_f/100.0 secondary_ratio = string_ratios[1].to_f/100.0 # Set the hash containg the ratios and return boiler_cap_ratios = { primary_ratio: primary_ratio, secondary_ratio: secondary_ratio } return boiler_cap_ratios end
# File lib/openstudio-standards/standards/necb/NECB2011/lighting.rb, line 146 def set_lighting_per_area(space_type:, definition:, lighting_per_area:, lights_scale:) occ_sens_lpd_frac = 1.0 # NECB2011 space types that require a reduction in the LPD to account for # the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2)) reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation', 'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D', 'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I', 'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B', 'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G', 'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting'] if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get) # Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed" # LPD should only be reduced if their space areas are less than specific area values. # This is checked in a space loop after this function in the calling routine. occ_sens_lpd_frac = 0.9 end definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f * occ_sens_lpd_frac * lights_scale, 'W/ft^2', 'W/m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set LPD to #{lighting_per_area} W/ft^2.") end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1877 def set_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:) # puts "#{space_type.name.to_s} - 'space_height' - #{space_height.to_s}" occ_sens_lpd_frac = 1.0 # NECB2011 space types that require a reduction in the LPD to account for # the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2)) reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation', 'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D', 'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I', 'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B', 'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G', 'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting'] if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get) # Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed" # LPD should only be reduced if their space areas are less than specific area values. # This is checked in a space loop after this function in the calling routine. occ_sens_lpd_frac = 0.9 end # ##### Since Atrium's LPD for LED lighting depends on atrium's height, the height of the atrium (if applicable) should be found. standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil if standards_space_type.include? 'Atrium' # @todo Note that since none of the archetypes has Atrium, this was tested for 'Dining'. #Atrium puts "#{standards_space_type} - has atrium" # space_type.name.to_s # Get the max height for the spacetype. max_space_height_for_spacetype = get_max_space_height_for_space_type(space_type: space_type) if max_space_height_for_spacetype < 12.0 # @todo Note that since none of the archetypes has Atrium, this was tested for 'Dining' with the threshold of 5.0 m for space_height. # @todo Regarding the below equations, identify which version of ASHRAE 90.1 was used in NECB2015. atrium_lpd_eq_smaller_12_intercept = 0 atrium_lpd_eq_smaller_12_slope = 1.06 atrium_lpd_eq_larger_12_intercept = 4.3 atrium_lpd_eq_larger_12_slope = 1.06 lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_smaller_12_intercept + atrium_lpd_eq_smaller_12_slope * 12.0) * 0.092903 # W/ft2 @todo Note that for NECB2011, a constant LPD is used for atrium based on NECB2015's equations. NECB2011's threshold for height is 13.0 m. elsif max_space_height_for_spacetype >= 12.0 && max_space_height_for_spacetype < 13.0 lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 12.5) * 0.092903 # W/ft2 else # i.e. space_height >= 13.0 lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 13.0) * 0.092903 # W/ft2 end puts "#{standards_space_type} - has lighting_per_area_led_lighting_atrium - #{lighting_per_area_led_lighting_atrium}" lighting_per_area_led_lighting = lighting_per_area_led_lighting_atrium end lighting_per_area_led_lighting *= lights_scale definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area_led_lighting.to_f * occ_sens_lpd_frac, 'W/ft^2', 'W/m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set LPD to #{lighting_per_area_led_lighting} W/ft^2.") end
Set all external subsurfaces (doors, windows, skylights) to NECB values. @author phylroy.lopez@nrcan.gc.ca @param subsurface [String] @param hdd [Float]
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 450 def set_necb_external_subsurface_conductance(subsurface, hdd) conductance_value = 0 return unless subsurface.outsideBoundaryCondition.downcase.match('outdoors') case subsurface.subSurfaceType.downcase when /window/ conductance_value = @standards_data['conductances']['Window'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor when /door/ conductance_value = @standards_data['conductances']['Door'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor end subsurface.setRSI(1 / conductance_value) end
Set all external surface conductances to NECB values. @author phylroy.lopez@nrcan.gc.ca @param surface [String] @param hdd [Float] @param is_radiant [Boolian] @param scaling_factor [Float] @return [String] surface as RSI
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 412 def set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0) conductance_value = 0 if surface.outsideBoundaryCondition.casecmp('outdoors').zero? case surface.surfaceType.downcase when 'wall' conductance_value = @standards_data['conductances']['Wall'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor when 'floor' conductance_value = @standards_data['conductances']['Floor'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor when 'roofceiling' conductance_value = @standards_data['conductances']['Roof'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor end if is_radiant conductance_value *= 0.80 end return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value) end return unless surface.outsideBoundaryCondition.downcase =~ /ground/ case surface.surfaceType.downcase when 'wall' conductance_value = @standards_data['conductances']['GroundWall'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor when 'floor' conductance_value = @standards_data['conductances']['GroundFloor'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor when 'roofceiling' conductance_value = @standards_data['conductances']['GroundRoof'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor end if is_radiant conductance_value *= 0.80 end return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value) end
@return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1272 def set_occ_sensor_spacetypes(model, space_type_map) building_type = 'Space Function' space_type_map.each do |space_type_name, space_names| space_names.sort.each do |space_name| space = model.getSpaceByName(space_name) next if space.empty? space = space.get # Check if space type for this space matches NECB2011 specific space type # for occupancy sensor that is area dependent. Note: space.floorArea in m2. # Evaluate the formula in the database. standard_space_type_name = space_type_name floor_area = space.floorArea if eval(@standards_data['formulas']['occupancy_sensors_space_types_formula']['value']) # If there is only one space assigned to this space type, then reassign this stub # to the @@template duplicate with appendage " - occsens", otherwise create a new stub # for this space. Required to use reduced LPD by NECB2011 0.9 factor. space_type_name_occsens = space_type_name + ' - occsens' stub_space_type_occsens = model.getSpaceTypeByName("#{building_type} #{space_type_name_occsens}") if stub_space_type_occsens.empty? # create a new space type just once for space_type_name appended with " - occsens" stub_space_type_occsens = OpenStudio::Model::SpaceType.new(model) stub_space_type_occsens.setStandardsBuildingType(building_type) stub_space_type_occsens.setStandardsSpaceType(space_type_name_occsens) stub_space_type_occsens.setName("#{building_type} #{space_type_name_occsens}") space_type_apply_rendering_color(stub_space_type_occsens) space.setSpaceType(stub_space_type_occsens) else # reassign occsens space type stub already created... stub_space_type_occsens = stub_space_type_occsens.get space.setSpaceType(stub_space_type_occsens) end end end end return true end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2238 def set_output_meters(model:,output_meters:) unless output_meters.nil? or output_meters.empty? # remove existing output meters existing_meters = model.getOutputMeters # OpenStudio doesn't seemt to like two meters of the same name, even if they have different reporting frequencies. output_meters.each do |new_meter| #check if meter already exists result = existing_meters.select { |e_m| e_m.name == new_meter['name'] } puts("More and one output meter named #{new_meter['name']}") if result.size > 1 if result.size >= 1 existing_meter = result[0] puts("A meter named #{new_meter['name']} already exists. One will not be added to the model.") if existing_meter.reportingFrequency != new_meter['frequency'] existing_meter.setReportingFrequency(new_meter['frequency']) puts("Changing reporting frequency of existing meter to #{new_meter['frequency']}.") end end if result.size == 0 meter = OpenStudio::Model::OutputMeter.new(model) meter.setName(new_meter['name']) meter.setReportingFrequency(new_meter['frequency']) puts("Adding meter for #{meter.name} reporting #{new_meter['frequency']}") end end end return model end
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2224 def set_output_variables(model:,output_variables:) unless output_variables.nil? or output_variables.empty? output_variables.each do |output_variable| puts output_variable puts output_variable['frequency'] raise("Frequency is not valid. Must by \"hourly\" or \"timestep\" but got #{output_variable}.") unless ["timestep","hourly",'daily','monthly','annual'].include?(output_variable['frequency']) output = OpenStudio::Model::OutputVariable.new(output_variable['variable'],model) output.setKeyValue(output_variable['key']) output.setReportingFrequency(output_variable['frequency']) end end return model end
This method will create a color object used in SU, 3D Viewer and Floorspace.js
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1263 def set_random_rendering_color(object, random) rendering_color = OpenStudio::Model::RenderingColor.new(object.model) rendering_color.setName(object.name.get) rendering_color.setRenderingRedValue(random.rand(255)) rendering_color.setRenderingGreenValue(random.rand(255)) rendering_color.setRenderingBlueValue(random.rand(255)) return rendering_color end
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 763 def set_wildcard_schedules_to_dominant_building_schedule(model, runner = nil) # Get rid of. end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1591 def set_zones_thermostat_schedule_based_on_space_type_schedules(model, runner = nil) puts 'in set_zones_thermostat_schedule_based_on_space_type_schedules' BTAP.runner_register('DEBUG', 'Start-set_zones_thermostat_schedule_based_on_space_type_schedules', runner) model.getThermalZones.sort.each do |zone| BTAP.runner_register('DEBUG', "Zone = #{zone.name} Spaces =#{zone.spaces.size} ", runner) array = [] zone.spaces.sort.each do |space| schedule_type = determine_necb_schedule_type(space).to_s BTAP.runner_register('DEBUG', "space name/type:#{space.name}/#{schedule_type}", runner) # if wildcard space type, need to get dominant schedule type if '*'.to_s == schedule_type dominant_sched_type = determine_dominant_necb_schedule_type(model) schedule_type = dominant_sched_type end array << schedule_type end array.uniq! if array.size > 1 BTAP.runner_register('Error', "#{zone.name} has spaces with different schedule types. Please ensure that all the spaces are of the same schedule type A to I.", runner) return false end htg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Heating" clg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Cooling" if model.getScheduleRulesetByName(htg_search_string).empty? == false htg_sched = model.getScheduleRulesetByName(htg_search_string).get else BTAP.runner_register('ERROR', "heating_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner) return false end if model.getScheduleRulesetByName(clg_search_string).empty? == false clg_sched = model.getScheduleRulesetByName(clg_search_string).get else BTAP.runner_register('ERROR', "cooling_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner) return false end name = "NECB-#{array[0]}-Thermostat Dual Setpoint Schedule" # If dual setpoint already exists, use that one, else create one ds = if model.getThermostatSetpointDualSetpointByName(name).empty? == false model.getThermostatSetpointDualSetpointByName(name).get else BTAP::Resources::Schedules.create_annual_thermostat_setpoint_dual_setpoint(model, name, htg_sched, clg_sched) end thermostat_clone = ds.clone.to_ThermostatSetpointDualSetpoint.get zone.setThermostatSetpointDualSetpoint(thermostat_clone) BTAP.runner_register('Info', "ThermalZone #{zone.name} set to DualSetpoint Schedule NECB-#{array[0]}", runner) end BTAP.runner_register('DEBUG', 'END-set_zones_thermostat_schedule_based_on_space_type_schedules', runner) return true end
of setup_hw_loop_with_components
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1715 def setup_chw_loop_with_components(model, chw_loop, chiller_type) chw_loop.setName('Chilled Water Loop') sizing_plant = chw_loop.sizingPlant sizing_plant.setLoopType('Cooling') sizing_plant.setDesignLoopExitTemperature(7.0) sizing_plant.setLoopDesignTemperatureDifference(6.0) # Note: pump of 'chilled water loop' has been changed to the variable one as the constant one caused fatal errors for LargeOffice-Yellowknife-NaturalGas for some ECMs and inputs. # Fatal error was: 'CheckForRunawayPlantTemps: Simulation terminated because of run away plant temperatures, too cold' OR '..., too hot' for the PlantLoop of 'Chilled Water Loop'. # Note that the variable speed pump has been already used for 'Hot Water Loop'. chw_pump = OpenStudio::Model::PumpVariableSpeed.new(model) chiller1 = OpenStudio::Model::ChillerElectricEIR.new(model) chiller2 = OpenStudio::Model::ChillerElectricEIR.new(model) chiller1.setCondenserType('WaterCooled') chiller2.setCondenserType('WaterCooled') chiller1_name = "Primary Chiller WaterCooled #{chiller_type}".strip chiller1.setName(chiller1_name) chiller2_name = "Secondary Chiller WaterCooled #{chiller_type}".strip chiller2.setName(chiller2_name) chiller_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) chw_supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) # Add the components to the chilled water loop chw_supply_inlet_node = chw_loop.supplyInletNode chw_supply_outlet_node = chw_loop.supplyOutletNode chw_pump.addToNode(chw_supply_inlet_node) chw_loop.addSupplyBranchForComponent(chiller1) chw_loop.addSupplyBranchForComponent(chiller2) chw_loop.addSupplyBranchForComponent(chiller_bypass_pipe) chw_supply_outlet_pipe.addToNode(chw_supply_outlet_node) # Add a setpoint manager to control the # chilled water to a constant temperature chw_t_c = 7.0 chw_t_sch = BTAP::Resources::Schedules.create_annual_constant_ruleset_schedule(model, 'CHW Temp', 'Temperature', chw_t_c) chw_t_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, chw_t_sch) chw_t_stpt_manager.addToNode(chw_supply_outlet_node) return chiller1, chiller2 end
of setup_chw_loop_with_components
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1761 def setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2) cw_loop.setName('Condenser Water Loop') cw_sizing_plant = cw_loop.sizingPlant cw_sizing_plant.setLoopType('Condenser') cw_sizing_plant.setDesignLoopExitTemperature(29.0) cw_sizing_plant.setLoopDesignTemperatureDifference(6.0) # Note: pump of 'Condenser water loop' has been changed to the variable one as the constant one caused fatal errors for LargeOffice-Montreal-NaturalGas for some ECMs and inputs. # Fatal error was: 'Plant temperatures are getting far too cold, check controls and relative loads and capacities'. # Note that the variable speed pump has been already used for 'Hot Water Loop' and 'Chilled Water Loop'. cw_pump = OpenStudio::Model::PumpVariableSpeed.new(model) clg_tower = OpenStudio::Model::CoolingTowerSingleSpeed.new(model) # TO DO: Need to define and set cooling tower curves clg_tower_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) cw_supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) # Add the components to the condenser water loop cw_supply_inlet_node = cw_loop.supplyInletNode cw_supply_outlet_node = cw_loop.supplyOutletNode cw_pump.addToNode(cw_supply_inlet_node) clg_tower.setDesignInletAirWetBulbTemperature(24.0) clg_tower.setDesignInletAirDryBulbTemperature(35.0) clg_tower.setDesignApproachTemperature(5.0) clg_tower.setDesignRangeTemperature(6.0) cw_loop.addSupplyBranchForComponent(clg_tower) cw_loop.addSupplyBranchForComponent(clg_tower_bypass_pipe) cw_supply_outlet_pipe.addToNode(cw_supply_outlet_node) cw_loop.addDemandBranchForComponent(chiller1) cw_loop.addDemandBranchForComponent(chiller2) # Add a setpoint manager to control the # condenser water to constant temperature cw_t_c = 29.0 cw_t_sch = BTAP::Resources::Schedules.create_annual_constant_ruleset_schedule(model, 'CW Temp', 'Temperature', cw_t_c) cw_t_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, cw_t_sch) cw_t_stpt_manager.addToNode(cw_supply_outlet_node) return clg_tower end
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1657 def setup_hw_loop_with_components(model, hw_loop, boiler_fueltype, backup_boiler_fueltype, pump_flow_sch) hw_loop.setName('Hot Water Loop') sizing_plant = hw_loop.sizingPlant sizing_plant.setLoopType('Heating') sizing_plant.setDesignLoopExitTemperature(82.0) #@todo units sizing_plant.setLoopDesignTemperatureDifference(16.0) # pump (set to variable speed for now till fix to run away plant temperature is found) # pump = OpenStudio::Model::PumpConstantSpeed.new(model) pump = OpenStudio::Model::PumpVariableSpeed.new(model) # @todo the keyword "setPumpFlowRateSchedule" does not seem to work. A message # was sent to NREL to let them know about this. Once there is a fix for this, # use the proper pump schedule depending on whether we have two-pipe or four-pipe # fan coils. # pump.resetPumpFlowRateSchedule() # pump.setPumpFlowRateSchedule(pump_flow_sch) # boiler boiler1 = OpenStudio::Model::BoilerHotWater.new(model) boiler2 = OpenStudio::Model::BoilerHotWater.new(model) boiler1.setFuelType(boiler_fueltype) boiler2.setFuelType(backup_boiler_fueltype) boiler1.setName('Primary Boiler') boiler2.setName('Secondary Boiler') # boiler_bypass_pipe boiler_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) # supply_outlet_pipe supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) # Add the components to the hot water loop hw_supply_inlet_node = hw_loop.supplyInletNode hw_supply_outlet_node = hw_loop.supplyOutletNode pump.addToNode(hw_supply_inlet_node) hw_loop.addSupplyBranchForComponent(boiler1) hw_loop.addSupplyBranchForComponent(boiler2) hw_loop.addSupplyBranchForComponent(boiler_bypass_pipe) supply_outlet_pipe.addToNode(hw_supply_outlet_node) # Add a setpoint manager to control the # hot water based on outdoor temperature hw_oareset_stpt_manager = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model) hw_oareset_stpt_manager.setControlVariable('Temperature') hw_oareset_stpt_manager.setSetpointatOutdoorLowTemperature(82.0) hw_oareset_stpt_manager.setOutdoorLowTemperature(-16.0) hw_oareset_stpt_manager.setSetpointatOutdoorHighTemperature(60.0) hw_oareset_stpt_manager.setOutdoorHighTemperature(0.0) hw_oareset_stpt_manager.addToNode(hw_supply_outlet_node) end
Set the infiltration rate for this space to include the impact of air leakage requirements in the standard.
@return [Double] true if successful, false if not @todo handle doors and vestibules
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1207 def space_apply_infiltration_rate(space) # Remove infiltration rates set at the space type. infiltration_data = @standards_data['infiltration'] unless space.spaceType.empty? space.spaceType.get.spaceInfiltrationDesignFlowRates.each(&:remove) end # Remove infiltration rates set at the space object. space.spaceInfiltrationDesignFlowRates.each(&:remove) exterior_wall_and_roof_and_subsurface_area = OpenstudioStandards::Geometry.space_get_exterior_wall_and_subsurface_and_roof_area(space) # To do # Don't create an object if there is no exterior wall area if exterior_wall_and_roof_and_subsurface_area <= 0.0 OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, no exterior wall area was found, no infiltration will be added.") return true end # Calculate the total infiltration, assuming # that it only occurs through exterior walls and roofs (not floors as # explicit stated in the NECB2011 so overhang/cantilevered floors will # have no effective infiltration) tot_infil_m3_per_s = get_standards_constant('infiltration_rate_m3_per_s_per_m2') * exterior_wall_and_roof_and_subsurface_area # Now spread the total infiltration rate over all # exterior surface area (for the E+ input field) this will include the exterior floor if present. all_ext_infil_m3_per_s_per_m2 = tot_infil_m3_per_s / space.exteriorArea OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Space', "For #{space.name}, adj infil = #{all_ext_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.") # Get any infiltration schedule already assigned to this space or its space type # If not, the always on schedule will be applied. infil_sch = nil unless space.spaceInfiltrationDesignFlowRates.empty? old_infil = space.spaceInfiltrationDesignFlowRates[0] if old_infil.schedule.is_initialized infil_sch = old_infil.schedule.get end end if infil_sch.nil? && space.spaceType.is_initialized space_type = space.spaceType.get unless space_type.spaceInfiltrationDesignFlowRates.empty? old_infil = space_type.spaceInfiltrationDesignFlowRates[0] if old_infil.schedule.is_initialized infil_sch = old_infil.schedule.get end end end if infil_sch.nil? infil_sch = space.model.alwaysOnDiscreteSchedule end # Create an infiltration rate object for this space infiltration = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space.model) infiltration.setName("#{space.name} Infiltration") infiltration.setFlowperExteriorSurfaceArea(all_ext_infil_m3_per_s_per_m2) infiltration.setSchedule(infil_sch) infiltration.setConstantTermCoefficient(get_standards_constant('infiltration_constant_term_coefficient')) infiltration.setTemperatureTermCoefficient(get_standards_constant('infiltration_constant_term_coefficient')) infiltration.setVelocityTermCoefficient(get_standards_constant('infiltration_velocity_term_coefficient')) infiltration.setVelocitySquaredTermCoefficient(get_standards_constant('infiltration_velocity_squared_term_coefficient')) infiltration.setSpace(space) return true end
This method gathers the surface information for the space to determine if spaces are the same.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 586 def space_surface_report(space) surface_report = [] space_floor_area = space.floorArea ['Outdoors', 'Ground'].each do |bc| surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(space.surfaces, [bc]).each do |surface| # sum wall area and subsurface area by direction. This is the old way so excluding top and bottom surfaces. # new way glazings = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(surface.subSurfaces, ['FixedWindow', 'OperableWindow', 'GlassDoor', 'Skylight', 'TubularDaylightDiffuser', 'TubularDaylightDome']) doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(surface.subSurfaces, ['Door', 'OverheadDoor']) azimuth = (surface.azimuth() * 180.0 / Math::PI) tilt = (surface.tilt() * 180.0 / Math::PI) surface_data = surface_report.detect do |curr_surface_data| curr_surface_data[:surface_type] == surface.surfaceType && curr_surface_data[:azimuth] == azimuth && curr_surface_data[:tilt] == tilt && curr_surface_data[:boundary_condition] == bc end if surface_data.nil? surface_data = { surface_type: surface.surfaceType, azimuth: azimuth, tilt: tilt, boundary_condition: bc, surface_area: 0, surface_area_to_floor_ratio: 0, glazed_subsurface_area: 0, glazed_subsurface_area_to_floor_ratio: 0, opaque_subsurface_area: 0, opaque_subsurface_area_to_floor_ratio: 0 } surface_report << surface_data end surface_data[:surface_area] += surface.grossArea.to_i surface_data[:surface_area_to_floor_ratio] += surface.grossArea / space.floorArea surface_data[:glazed_subsurface_area] += glazings.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x }.to_i surface_data[:glazed_subsurface_area_to_floor_ratio] += glazings.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x } / space.floorArea surface_data[:surface_area] += doors.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x }.to_i surface_data[:surface_area_to_floor_ratio] += doors.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x } / space.floorArea end end surface_report.sort! { |a, b| [a[:surface_type], a[:azimuth], a[:tilt], a[:boundary_condition]] <=> [b[:surface_type], b[:azimuth], b[:tilt], b[:boundary_condition]] } return surface_report end
Sets the selected internal loads to standards-based or typical values. For each category that is selected get all load instances. Remove all but the first instance if multiple instances. Add a new instance/definition if no instance exists. Modify the definition for the remaining instance to have the specified values. This method does not alter any loads directly assigned to spaces. This method skips plenums.
@param set_people [Boolean] if true, set the people density. Also, assign reasonable clothing, air velocity, and work efficiency inputs to allow reasonable thermal comfort metrics to be calculated. @param set_lights [Boolean] if true, set the lighting density, lighting fraction to return air, fraction radiant, and fraction visible. @param set_electric_equipment [Boolean] if true, set the electric equipment density @param set_gas_equipment [Boolean] if true, set the gas equipment density @param set_ventilation [Boolean] if true, set the ventilation rates (per-person and per-area) @param set_infiltration [Boolean] if true, set the infiltration rates @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb, line 47 def space_type_apply_internal_loads(space_type:, set_people: true, set_lights: true, set_electric_equipment: true, set_gas_equipment: true, set_ventilation: true, set_infiltration: true, lights_type: 'NECB_Default', lights_scale: 1.0) # Skip plenums # Check if the space type name # contains the word plenum. if space_type.name.get.to_s.downcase.include?('plenum') return false end if space_type.standardsSpaceType.is_initialized if space_type.standardsSpaceType.get.downcase.include?('plenum') return false end end # Get the space Type data from @standards data spacetype_data = @standards_data['tables']['space_types']['table'] standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil space_type_properties = spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) } # Need to add a check, or it'll crash on space_type_properties['occupancy_per_area'].to_f below if space_type_properties.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} was not found in the standards data.") return false end # People people_have_info = false occupancy_per_area = space_type_properties['occupancy_per_area'].to_f people_have_info = true unless occupancy_per_area.zero? if set_people && people_have_info # Remove all but the first instance instances = space_type.people.sort if instances.empty? # Create a new definition and instance definition = OpenStudio::Model::PeopleDefinition.new(space_type.model) definition.setName("#{space_type.name} People Definition") instance = OpenStudio::Model::People.new(definition) instance.setName("#{space_type.name} People") instance.setSpaceType(space_type) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no people, one has been created.") instances << instance elsif instances.size > 1 instances.each_with_index do |inst, i| next if i.zero? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.") inst.remove end end # Modify the definition of the instance space_type.people.sort.each do |inst| definition = inst.peopleDefinition unless occupancy_per_area.zero? definition.setPeopleperSpaceFloorArea(OpenStudio.convert(occupancy_per_area / 1000, 'people/ft^2', 'people/m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set occupancy to #{occupancy_per_area} people/1000 ft^2.") end # set fraction radiant ## definition.setFractionRadiant(0.3) # Clothing schedule for thermal comfort metrics clothing_sch = space_type.model.getScheduleRulesetByName('Clothing Schedule') if clothing_sch.is_initialized clothing_sch = clothing_sch.get else clothing_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model) clothing_sch.setName('Clothing Schedule') clothing_sch.defaultDaySchedule.setName('Clothing Schedule Default Winter Clothes') clothing_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0) sch_rule = OpenStudio::Model::ScheduleRule.new(clothing_sch) sch_rule.daySchedule.setName('Clothing Schedule Summer Clothes') sch_rule.daySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.5) sch_rule.setStartDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(5), 1)) sch_rule.setEndDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(9), 30)) end inst.setClothingInsulationSchedule(clothing_sch) # Air velocity schedule for thermal comfort metrics air_velo_sch = space_type.model.getScheduleRulesetByName('Air Velocity Schedule') if air_velo_sch.is_initialized air_velo_sch = air_velo_sch.get else air_velo_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model) air_velo_sch.setName('Air Velocity Schedule') air_velo_sch.defaultDaySchedule.setName('Air Velocity Schedule Default') air_velo_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.2) end inst.setAirVelocitySchedule(air_velo_sch) # Work efficiency schedule for thermal comfort metrics work_efficiency_sch = space_type.model.getScheduleRulesetByName('Work Efficiency Schedule') if work_efficiency_sch.is_initialized work_efficiency_sch = work_efficiency_sch.get else work_efficiency_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model) work_efficiency_sch.setName('Work Efficiency Schedule') work_efficiency_sch.defaultDaySchedule.setName('Work Efficiency Schedule Default') work_efficiency_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0) end inst.setWorkEfficiencySchedule(work_efficiency_sch) end end # Lights apply_standard_lights(set_lights: set_lights, space_type: space_type, space_type_properties: space_type_properties, lights_type: lights_type, lights_scale: lights_scale) # Electric Equipment elec_equip_have_info = false elec_equip_per_area = space_type_properties['electric_equipment_per_area'].to_f elec_equip_frac_latent = space_type_properties['electric_equipment_fraction_latent'].to_f elec_equip_frac_radiant = space_type_properties['electric_equipment_fraction_radiant'].to_f elec_equip_frac_lost = space_type_properties['electric_equipment_fraction_lost'].to_f elec_equip_have_info = true unless elec_equip_per_area.zero? if set_electric_equipment && elec_equip_have_info # Remove all but the first instance instances = space_type.electricEquipment.sort if instances.empty? definition = OpenStudio::Model::ElectricEquipmentDefinition.new(space_type.model) definition.setName("#{space_type.name} Elec Equip Definition") instance = OpenStudio::Model::ElectricEquipment.new(definition) instance.setName("#{space_type.name} Elec Equip") instance.setSpaceType(space_type) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no electric equipment, one has been created.") instances << instance elsif instances.size > 1 instances.each_with_index do |inst, i| next if i.zero? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.") inst.remove end end # Modify the definition of the instance space_type.electricEquipment.sort.each do |inst| definition = inst.electricEquipmentDefinition unless elec_equip_per_area.zero? definition.setWattsperSpaceFloorArea(OpenStudio.convert(elec_equip_per_area.to_f, 'W/ft^2', 'W/m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set electric EPD to #{elec_equip_per_area} W/ft^2.") end unless elec_equip_frac_latent.zero? definition.setFractionLatent(elec_equip_frac_latent) end unless elec_equip_frac_radiant.zero? definition.setFractionRadiant(elec_equip_frac_radiant) end unless elec_equip_frac_lost.zero? definition.setFractionLost(elec_equip_frac_lost) end end end # Gas Equipment gas_equip_have_info = false gas_equip_per_area = space_type_properties['gas_equipment_per_area'].to_f gas_equip_frac_latent = space_type_properties['gas_equipment_fraction_latent'].to_f gas_equip_frac_radiant = space_type_properties['gas_equipment_fraction_radiant'].to_f gas_equip_frac_lost = space_type_properties['gas_equipment_fraction_lost'].to_f gas_equip_have_info = true unless gas_equip_per_area.zero? if set_gas_equipment && gas_equip_have_info # Remove all but the first instance instances = space_type.gasEquipment.sort if instances.empty? definition = OpenStudio::Model::GasEquipmentDefinition.new(space_type.model) definition.setName("#{space_type.name} Gas Equip Definition") instance = OpenStudio::Model::GasEquipment.new(definition) instance.setName("#{space_type.name} Gas Equip") instance.setSpaceType(space_type) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no gas equipment, one has been created.") instances << instance elsif instances.size > 1 instances.each_with_index do |inst, i| next if i.zero? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.") inst.remove end end # Modify the definition of the instance space_type.gasEquipment.sort.each do |inst| definition = inst.gasEquipmentDefinition unless gas_equip_per_area.zero? definition.setWattsperSpaceFloorArea(OpenStudio.convert(gas_equip_per_area.to_f, 'Btu/hr*ft^2', 'W/m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set gas EPD to #{elec_equip_per_area} Btu/hr*ft^2.") end unless gas_equip_frac_latent.zero? definition.setFractionLatent(gas_equip_frac_latent) end unless gas_equip_frac_radiant.zero? definition.setFractionRadiant(gas_equip_frac_radiant) end unless gas_equip_frac_lost.zero? definition.setFractionLost(gas_equip_frac_lost) end end end # Ventilation ventilation_have_info = false ventilation_per_area = space_type_properties['ventilation_per_area'].to_f ventilation_per_person = space_type_properties['ventilation_per_person'].to_f ventilation_ach = space_type_properties['ventilation_air_changes'].to_f ventilation_occupancy_per_area = space_type_properties['ventilation_occupancy_rate_people_per_1000ft2'].to_f ventilation_have_info = true unless ventilation_per_area.zero? ventilation_have_info = true unless ventilation_per_person.zero? ventilation_have_info = true unless ventilation_ach.zero? # Get the design OA or create a new one if none exists ventilation = space_type.designSpecificationOutdoorAir if ventilation.is_initialized ventilation = ventilation.get else ventilation = OpenStudio::Model::DesignSpecificationOutdoorAir.new(space_type.model) ventilation.setName("#{space_type.name} Ventilation") space_type.setDesignSpecificationOutdoorAir(ventilation) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no ventilation specification, one has been created.") end if set_ventilation && ventilation_have_info # Modify the ventilation properties ventilation.setOutdoorAirMethod('Sum') unless ventilation_per_area.zero? ventilation.setOutdoorAirFlowperFloorArea(OpenStudio.convert(ventilation_per_area.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation per area to #{ventilation_per_area} cfm/ft^2.") end unless ventilation_per_person.zero? # For BTAP we often use an occupancy per area rate for ventilation which is different from the one used for # everything else. The mod_ventilation_per_person rate adjusts the per person ventilation rate so that the # proper ventilation rate is calculated when using the general occupant per area rate. mod_ventilation_per_person = ventilation_per_person * ventilation_occupancy_per_area / occupancy_per_area ventilation.setOutdoorAirFlowperPerson(OpenStudio.convert(mod_ventilation_per_person.to_f, 'ft^3/min*person', 'm^3/s*person').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation per person to #{mod_ventilation_per_person} cfm/person.") end unless ventilation_ach.zero? ventilation.setOutdoorAirFlowAirChangesperHour(ventilation_ach) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation to #{ventilation_ach} ACH.") end elsif set_ventilation && !ventilation_have_info # All space types must have a design spec OA # object for ventilation controls to work correctly, # even if the values are all zero. ventilation.setOutdoorAirFlowperFloorArea(0) ventilation.setOutdoorAirFlowperPerson(0) ventilation.setOutdoorAirFlowAirChangesperHour(0) end # Infiltration infiltration_have_info = false infiltration_per_area_ext = space_type_properties['infiltration_per_exterior_area'].to_f infiltration_per_area_ext_wall = space_type_properties['infiltration_per_exterior_wall_area'].to_f infiltration_ach = space_type_properties['infiltration_air_changes'].to_f unless infiltration_per_area_ext.zero? && infiltration_per_area_ext_wall.zero? && infiltration_ach.zero? infiltration_have_info = true end return unless set_infiltration && infiltration_have_info # Remove all but the first instance instances = space_type.spaceInfiltrationDesignFlowRates.sort if instances.empty? instance = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space_type.model) instance.setName("#{space_type.name} Infiltration") instance.setSpaceType(space_type) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no infiltration objects, one has been created.") instances << instance elsif instances.size > 1 instances.each_with_index do |inst, i| next if i.zero? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.") inst.remove end end # Modify each instance space_type.spaceInfiltrationDesignFlowRates.sort.each do |inst| unless infiltration_per_area_ext.zero? inst.setFlowperExteriorSurfaceArea(OpenStudio.convert(infiltration_per_area_ext.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{ventilation_ach} per ft^2 exterior surface area.") end unless infiltration_per_area_ext_wall.zero? inst.setFlowperExteriorWallArea(OpenStudio.convert(infiltration_per_area_ext_wall.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{infiltration_per_area_ext_wall} per ft^2 exterior wall area.") end unless infiltration_ach.zero? inst.setAirChangesperHour(infiltration_ach) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{ventilation_ach} ACH.") end end end
Method to store space sizing loads. This is needed because later when the zones are destroyed this information will be lost.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 193 def store_space_sizing_loads(model) @stored_space_heating_sizing_loads = {} @stored_space_cooling_sizing_loads = {} model.getSpaces.sort.each do |space| space_type = space.spaceType.get.standardsSpaceType.get # error if zone design load methods are not available if space.model.version < OpenStudio::VersionString.new('3.6.0') OpenStudio.logFree(OpenStudio::Error, 'openstudio.autozone', "Required ThermalZone methods .autosizedHeatingDesignLoad and .autosizedCoolingDesignLoad are not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.") end @stored_space_heating_sizing_loads[space] = space_type == '- undefined -' ? 0.0 : space.thermalZone.get.autosizedHeatingDesignLoad.get / space.floorArea @stored_space_cooling_sizing_loads[space] = space_type == '- undefined -' ? 0.0 : space.thermalZone.get.autosizedCoolingDesignLoad.get / space.floorArea end end
Returns the cooling load per area for space after sizing runs has been done.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 222 def stored_space_cooling_load(space) if @stored_space_cooling_sizing_loads.nil? # do a sizing run. raise('autorun sizing run failed!') if model_run_sizing_run(space.model, "#{Dir.pwd}/autozone") == false # collect sizing information on each space. store_space_sizing_loads(space.model) end @stored_space_cooling_sizing_loads[space] end
Returns heating load per area for space after sizing run has been done.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 210 def stored_space_heating_load(space) if @stored_space_heating_sizing_loads.nil? # do a sizing run. raise('autorun sizing run failed!') if model_run_sizing_run(space.model, "#{Dir.pwd}/autozone") == false # collect sizing information on each space. store_space_sizing_loads(space.model) end @stored_space_heating_sizing_loads[space] end
Returns the cooling load per area for zone after sizing runs has been done.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 243 def stored_zone_cooling_load(zone) total = 0.0 zone.spaces.each do |space| total += stored_space_cooling_load(space) end return total end
Returns the heating load per area for zone after sizing runs has been done.
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 234 def stored_zone_heating_load(zone) total = 0.0 zone.spaces.each do |space| total += stored_space_heating_load(space) end return total end
check if two surfaces are in contact. For every two consecutive vertices on surface 1, loop through two consecutive vertices of surface two. Then check whether the vertices of surfaces 2 are on the same line as the vertices from surface 1. If the two vectors defined by the two vertices on surface 1 and those on surface 2 overlap, then the two surfaces are in contact. If a side from surface 2 is in contact with a side from surface 1, the length of the side from surface 2 is limited to the length of the side from surface 1. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 820 def surfaces_are_in_contact?(surf1,surf2) surfaces_in_contact = false vert1 = surf1.vertices[0] for index1 in 1..surf1.vertices.size if index1 < surf1.vertices.size vert2 = surf1.vertices[index1] else vert2 = surf1.vertices[0] end seg12_length = ((vert2.x-vert1.x)**2+(vert2.y-vert1.y)**2+(vert2.z-vert1.z)**2)**0.5 surf2_seg_length = 0.0 vert3 = surf2.vertices[0] for index2 in 1..surf2.vertices.size if index2 < surf2.vertices.size vert4 = surf2.vertices[index2] else vert4 = surf2.vertices[0] end vert1_2_3_same_line_and_dir = three_vertices_same_line_and_dir?(vert1,vert2,vert3) if vert1_2_3_same_line_and_dir vert1_2_4_same_line_and_dir = three_vertices_same_line_and_dir?(vert1,vert2,vert4) if vert1_2_4_same_line_and_dir surfaces_in_contact = true seg34_length = ((vert4.x-vert3.x)**2+(vert4.y-vert3.y)**2+(vert4.z-vert3.z)**2)**0.5 surf2_seg_length += seg34_length raise("Surface #{surf2.name.to_s} has sides in contact with surface #{surf1.name.to_s} but with a length greater than the max.") if surf2_seg_length > seg12_length end end vert3 = vert4 end vert1 = vert2 end return surfaces_in_contact end
Determine if demand control ventilation (DCV) is required for this zone based on area and occupant density. Does not account for System requirements like ERV, economizer, etc. Those are accounted for in the AirLoopHVAC method of the same name.
@return [Boolean] Returns true if required, false if not. @todo Add exception logic for 90.1-2013
for cells, sickrooms, labs, barbers, salons, and bowling alleys
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1519 def thermal_zone_demand_control_ventilation_required?(thermal_zone, climate_zone) return false end
This method cycles through the spaces in a thermal zone and then sorts them by story. The method then cycles through the spaces on a story and then calculates the centroid of the spaces in the thermal zone on that floor. The method returns an array of hashes, one for each story. Each hash has the following structure:
{ story_name: Name of a given story. spaces: Array containing all of the spaces in the thermal zone on the story in story_name. centroid: Array containing the x, y, and z coordinates of the centroid of the ceilings of the spaces listed in 'spaces:' above. ceiling_area: Total area of the ceilings of the spaces in 'spaces:' above. }
Only spaces which are conditioned (heated or cooled) and are not plenums are included.
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1816 def thermal_zone_get_centroid_per_floor(thermal_zone) stories = [] thermal_zone.spaces.sort.each do |space| spaceType_name = space.spaceType.get.nameString sp_type = spaceType_name[15..-1] # Including regular expressions in the following match for cases where extra characters, which do not belong, are # added to either the space type in the model or the space type reference file. sp_type_info = @standards_data['space_types'].detect do |data| (Regexp.new(data['space_type'].to_s.upcase).match(sp_type.upcase) || Regexp.new(sp_type.upcase).match(data['space_type'].to_s.upcase) || (data['space_type'].to_s.upcase == sp_type.upcase)) && (data['building_type'].to_s == 'Space Function') end if sp_type_info.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.thermal_zone_get_centroid_per_floor', "The space type called #{sp_type} could not be found. Please check that the schedules.json file is available and that the space types are spelled correctly") next end # Determine if space is heated or cooled via spacetype heating or cooling setpoints also checking if the space is # a plenum by checking if there is a hvac system associtated with it if sp_type_info['heating_setpoint_schedule'].nil? heated = false else heated = true end if sp_type_info['cooling_setpoint_schedule'].nil? cooled = false else cooled = true end if (sp_type_info['necb_hvac_system_selection_type'] == '- undefined -') || /undefined/.match(sp_type_info['necb_hvac_system_selection_type']) not_plenum = false else not_plenum = true end # If the spaces are heated or cooled and are not a plenum then continue if (heated || cooled) && not_plenum # Get the story name and sit it to none if there is no story name story_name = space.buildingStory.get.nameString story_name = 'none' if story_name.nil? # If this is the first story in the arry then add a new one. if stories.empty? stories << { story_name: story_name, spaces: [space], centroid: [0, 0, 0], ceiling_area: 0 } next else # If this is not the first story in the array check if the story already is in the array. i = nil stories.each_with_index do |storycheck, index| if storycheck[:story_name] == story_name i = index end end # If the story is not in the array then add it. if i.nil? stories << { story_name: story_name, spaces: [space], centroid: [0, 0, 0], ceiling_area: 0 } else # If the story is already in the arry then add the space to the array of spaces for that story stories[i][:spaces] << space end end end end # Go through each story in the array above stories.each do |story| tz_centre = [0, 0, 0, 0] # Go through each space in a given story story[:spaces].each do |space| # Determine the top surface of the space and calculate it's centroid. # Get the coordinates of the origin for the space (the coordinates of points in the space are relative to this). xOrigin = space.xOrigin yOrigin = space.yOrigin zOrigin = space.zOrigin # Go through each surface in the space and find ceilings by determining which is called 'RoofCeiing'. Find the # overall centroid of all the ceilings in the spaces. Find centroid by multiplying the centroid of the surfaces # multiplied by the area of the surface and add them all up. Then divide this by the overall area. This is the # area weighted average of the centroid coordinates. ceiling_centroid = [0, 0, 0, 0] space.surfaces.each do |sp_surface| if sp_surface.surfaceType.to_s.upcase == 'ROOFCEILING' ceiling_centroid[0] = ceiling_centroid[0] + sp_surface.centroid.x.to_f * sp_surface.grossArea.to_f ceiling_centroid[1] = ceiling_centroid[1] + sp_surface.centroid.y.to_f * sp_surface.grossArea.to_f ceiling_centroid[2] = ceiling_centroid[2] + sp_surface.centroid.z.to_f * sp_surface.grossArea.to_f ceiling_centroid[3] = ceiling_centroid[3] + sp_surface.grossArea end end ceiling_centroid[0] = ceiling_centroid[0] / ceiling_centroid[3] ceiling_centroid[1] = ceiling_centroid[1] / ceiling_centroid[3] ceiling_centroid[2] = ceiling_centroid[2] / ceiling_centroid[3] # This part is used to determine the overall x, y centre of the thermal zone. This is determined by summing the # x and y components times the ceiling area and diving by the total ceiling area. I also added z since the # ceilings may not be all have the same height. tz_centre[0] += (ceiling_centroid[0] + xOrigin) * ceiling_centroid[3] tz_centre[1] += (ceiling_centroid[1] + yOrigin) * ceiling_centroid[3] tz_centre[2] += (ceiling_centroid[2] + zOrigin) * ceiling_centroid[3] tz_centre[3] += (ceiling_centroid[3]) end tz_centre[0] /= tz_centre[3] tz_centre[1] /= tz_centre[3] tz_centre[2] /= tz_centre[3] # Update the :centroid and :ceiling_area hashes for the story to reflect the x, y, and z coordinates of the # overall centroid of spaces on that floor. story[:centroid] = tz_centre[0..2] story[:ceiling_area] = tz_centre[3] end return stories end
check that three vertices are on the same line. Also check that the vectors from vert1 and vert2 and from vert1 and vert3 are in the same direction. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 945 def three_vertices_same_line_and_dir?(vert1,vert2,vert3) tol = 1.0e-5 vec12x,vec12y,vec12z = -vert1.x+vert2.x,-vert1.y+vert2.y,-vert1.z+vert2.z # x,y,z of vector 12 vec12x = 0.0 if vec12x.abs < tol vec12y = 0.0 if vec12y.abs < tol vec12z = 0.0 if vec12z.abs < tol vec13x,vec13y,vec13z = -vert1.x+vert3.x,-vert1.y+vert3.y,-vert1.z+vert3.z # x,y,z of vector 13 vec13x = 0.0 if vec13x.abs < tol vec13y = 0.0 if vec13y.abs < tol vec13z = 0.0 if vec13z.abs < tol # x,y,z of the cross product of the vectors 12 and 13 cross_12_13_x = vec12y*vec13z-vec12z*vec13y cross_12_13_y = vec12z*vec13x-vec12x*vec13z cross_12_13_z = vec12x*vec13y-vec12y*vec13x # vectors are in parallel when x,y,z of cross product are 0.0 vertices_on_same_line = false vertices_on_same_line = true if (cross_12_13_x == 0.0) && (cross_12_13_y == 0.0) && (cross_12_13_z == 0.0) vectors_same_direction = false if vertices_on_same_line vec12_13_x_factor = vec13x*vec12x vec12_13_y_factor = vec13y*vec12y vec12_13_z_factor = vec13z*vec12z vectors_same_direction = true if (vec12_13_x_factor >= 0.0) && (vec12_13_y_factor >= 0.0) && (vec12_13_z_factor >= 0.0) end same_line_same_dir = vertices_on_same_line && vectors_same_direction return same_line_same_dir end
Method to update the base system name based on the inputs provided. Only the parts of the name with string inputs are updated
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2298 def update_sys_name(airloop, sys_abbr: nil, sys_oa: nil, sys_hr: nil, sys_htg: nil, sys_clg: nil, sys_sf: nil, zone_htg: nil, zone_clg: nil, sys_rf: nil) name_parts = airloop.name.to_s.split('|').reject(&:empty?) if sys_abbr.is_a? String then name_parts[0] = sys_abbr end if sys_oa.is_a? String then name_parts[1] = sys_oa end for i in 0..name_parts.size - 1 if (name_parts[i].include? 'shr>') && (sys_hr.is_a? String) name_parts[i] = "shr>#{sys_hr}" elsif (name_parts[i].include? 'sh>') && (sys_htg.is_a? String) name_parts[i] = "sh>#{sys_htg}" elsif (name_parts[i].include? 'sc>') && (sys_clg.is_a? String) name_parts[i] = "sc>#{sys_clg}" elsif (name_parts[i].include? 'ssf') && (sys_sf.is_a? String) name_parts[i] = "ssf>#{sys_sf}" elsif (name_parts[i].include? 'zh>') && (zone_htg.is_a? String) name_parts[i] = "zh>#{zone_htg}" elsif (name_parts[i].include? 'zc>') && (zone_clg.is_a? String) name_parts[i] = "zc>#{zone_clg}" elsif (name_parts[i].include? 'srf>') && (sys_rf.is_a? String) name_parts[i] = "srf>#{sys_rf}" end end sys_name = '' name_parts.each { |part| sys_name += "#{part}|" } # Check if the last part of the system name is an integer. If it is, then remove the last part from the system name. check_int = begin Integer(name_parts.last.strip) rescue StandardError nil end sys_name = sys_name.chop unless check_int.nil? airloop.setName(sys_name) end
This method will validate that the space types in the model are indeed the correct NECB spacetypes names.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1170 def validate_and_upate_space_types(model) space_type_vintage = determine_spacetype_vintage(model) if space_type_vintage.nil? message = "These some of the spacetypes in the model are not part of any necb standard.\n Please ensure all spacetype in model are correct." puts "Error: #{message}" OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.NECB', message) return false elsif space_type_vintage == self.class.name # the spacetype in the model match the version we are trying to create. # no translation neccesary. return true else # Need to translate to current vintage. no_errors = true st_model_vintage_string = "#{space_type_vintage}_space_type" bt_model_vintage_string = "#{space_type_vintage}_building_type" st_target_vintage_string = "#{self.class.name}_space_type" bt_target_vintage_string = "#{self.class.name}_building_type" space_type_upgrade_map = @standards_data['space_type_upgrade_map'] model.getSpaceTypes.sort.each do |st| space_type_map = space_type_upgrade_map.detect { |row| (row[st_model_vintage_string] == st.standardsSpaceType.get.to_s) && (row[bt_model_vintage_string] == st.standardsBuildingType.get.to_s) } st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip) raise('could not set buildingtype') unless st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip) raise('could not set this') unless st.setStandardsSpaceType(space_type_map[st_target_vintage_string].to_s.strip) # Set name of spacetype to new name. st.setName("#{st.standardsBuildingType.get} #{st.standardsSpaceType.get}") end return no_errors end end
This method is defined and used by the vintage classes to address he issue with the heat pump fuel types. This method does nothing when creating NECB reference buildings.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2440 def validate_primary_heating_fuel(primary_heating_fuel:) return primary_heating_fuel end
Applies the standard efficiency ratings and typical losses and paraisitic loads to this object. Efficiency and skin loss coefficient (UA) Per PNNL www.energycodes.gov/sites/default/files/documents/PrototypeModelEnhancements_2014_0.pdf Appendix A: Service Water Heating
@return [Boolean] true if successful, false if not
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 60 def water_heater_mixed_apply_efficiency(water_heater_mixed) # Get the capacity of the water heater # @todo add capability to pull autosized water heater capacity # if the Sizing:WaterHeater object is ever implemented in OpenStudio. capacity_w = water_heater_mixed.heaterMaximumCapacity if capacity_w.empty? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find capacity, standard will not be applied.") return false else capacity_w = capacity_w.get end capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get # Get the volume of the water heater # @todo add capability to pull autosized water heater volume # if the Sizing:WaterHeater object is ever implemented in OpenStudio. volume_m3 = water_heater_mixed.tankVolume if volume_m3.empty? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find volume, standard will not be applied.") return false else volume_m3 = volume_m3.get end volume_gal = OpenStudio.convert(volume_m3, 'm^3', 'gal').get # Get the heater fuel type fuel_type = water_heater_mixed.heaterFuelType unless fuel_type == 'NaturalGas' || fuel_type == 'Electricity' || fuel_type == 'FuelOilNo2' OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, fuel type of #{fuel_type} is not yet supported, standard will not be applied.") end # Calculate the water heater efficiency and # skin loss coefficient (UA) # Calculate the energy factor (EF) # From PNNL http://www.energycodes.gov/sites/default/files/documents/PrototypeModelEnhancements_2014_0.pdf # Appendix A: Service Water Heating water_heater_eff = nil ua_btu_per_hr_per_f = nil sl_btu_per_hr = nil case fuel_type when 'Electricity' volume_l_per_s = volume_m3 * 1000 if capacity_btu_per_hr <= OpenStudio.convert(12, 'kW', 'Btu/hr').get # Fixed water heater efficiency per PNNL water_heater_eff = 1 # Calculate the max allowable standby loss (SL) sl_w = if volume_l_per_s < 270 40 + 0.2 * volume_l_per_s # assume bottom inlet else 0.472 * volume_l_per_s - 33.5 # assume bottom inlet end sl_btu_per_hr = OpenStudio.convert(sl_w, 'W', 'Btu/hr').get else # Fixed water heater efficiency per PNNL water_heater_eff = 1 # Calculate the max allowable standby loss (SL) # use this - NECB does not give SL calculation for cap > 12 kW sl_btu_per_hr = 20 + (35 * Math.sqrt(volume_gal)) end # Calculate the skin loss coefficient (UA) ua_btu_per_hr_per_f = sl_btu_per_hr / 70 when 'NaturalGas' if capacity_btu_per_hr <= 75_000 # Fixed water heater thermal efficiency per PNNL water_heater_eff = 0.82 # Calculate the minimum Energy Factor (EF) base_ef = 0.67 vol_drt = 0.0019 ef = base_ef - (vol_drt * volume_gal) # Calculate the Recovery Efficiency (RE) # based on a fixed capacity of 75,000 Btu/hr # and a fixed volume of 40 gallons by solving # this system of equations: # ua = (1/.95-1/re)/(67.5*(24/41094-1/(re*cap))) # 0.82 = (ua*67.5+cap*re)/cap cap = 75_000.0 re = (Math.sqrt(6724 * ef**2 * cap**2 + 40_409_100 * ef**2 * cap - 28_080_900 * ef * cap + 29_318_000_625 * ef**2 - 58_636_001_250 * ef + 29_318_000_625) + 82 * ef * cap + 171_225 * ef - 171_225) / (200 * ef * cap) # Calculate the skin loss coefficient (UA) # based on the actual capacity. ua_btu_per_hr_per_f = (water_heater_eff - re) * capacity_btu_per_hr / 67.5 else # Thermal efficiency requirement from 90.1 et = 0.8 # Calculate the max allowable standby loss (SL) cap_adj = 800 vol_drt = 110 sl_btu_per_hr = (capacity_btu_per_hr / cap_adj + vol_drt * Math.sqrt(volume_gal)) # Calculate the skin loss coefficient (UA) ua_btu_per_hr_per_f = (sl_btu_per_hr * et) / 70 # Calculate water heater efficiency water_heater_eff = (ua_btu_per_hr_per_f * 70 + capacity_btu_per_hr * et) / capacity_btu_per_hr end when 'FuelOilNo2' if capacity_btu_per_hr <= 75_000 # Fixed water heater thermal efficiency per PNNL water_heater_eff = 0.82 # Calculate the minimum Energy Factor (EF) base_ef = 0.67 vol_drt = 0.0019 ef = base_ef - (vol_drt * volume_gal) # Calculate the Recovery Efficiency (RE) # based on a fixed capacity of 75,000 Btu/hr # and a fixed volume of 40 gallons by solving # this system of equations: # ua = (1/.95-1/re)/(67.5*(24/41094-1/(re*cap))) # 0.82 = (ua*67.5+cap*re)/cap cap = 75_000.0 re = (Math.sqrt(6724 * ef**2 * cap**2 + 40_409_100 * ef**2 * cap - 28_080_900 * ef * cap + 29_318_000_625 * ef**2 - 58_636_001_250 * ef + 29_318_000_625) + 82 * ef * cap + 171_225 * ef - 171_225) / (200 * ef * cap) # Calculate the skin loss coefficient (UA) # based on the actual capacity. ua_btu_per_hr_per_f = (water_heater_eff - re) * capacity_btu_per_hr / 67.5 else # Thermal efficiency requirement from 90.1 et = 0.8 # Calculate the max allowable standby loss (SL) cap_adj = 800 vol_drt = 110 sl_btu_per_hr = (capacity_btu_per_hr / cap_adj + vol_drt * Math.sqrt(volume_gal)) # Calculate the skin loss coefficient (UA) ua_btu_per_hr_per_f = (sl_btu_per_hr * et) / 70 # Calculate water heater efficiency water_heater_eff = (ua_btu_per_hr_per_f * 70 + capacity_btu_per_hr * et) / capacity_btu_per_hr end end # Convert to SI ua_btu_per_hr_per_c = OpenStudio.convert(ua_btu_per_hr_per_f, 'Btu/hr*R', 'W/K').get # Set the water heater properties # Efficiency water_heater_mixed.setHeaterThermalEfficiency(water_heater_eff) # Skin loss water_heater_mixed.setOffCycleLossCoefficienttoAmbientTemperature(ua_btu_per_hr_per_c) water_heater_mixed.setOnCycleLossCoefficienttoAmbientTemperature(ua_btu_per_hr_per_c) # @todo Parasitic loss (pilot light) # PNNL document says pilot lights were removed, but IDFs # still have the on/off cycle parasitic fuel consumptions filled in water_heater_mixed.setOnCycleParasiticFuelType(fuel_type) # self.setOffCycleParasiticFuelConsumptionRate(??) water_heater_mixed.setOnCycleParasiticHeatFractiontoTank(0) water_heater_mixed.setOffCycleParasiticFuelType(fuel_type) # self.setOffCycleParasiticFuelConsumptionRate(??) water_heater_mixed.setOffCycleParasiticHeatFractiontoTank(0.8) # set part-load performance curve if (fuel_type == 'NaturalGas') || (fuel_type == 'FuelOilNo2') plf_vs_plr_curve = model_add_curve(water_heater_mixed.model, 'SWH-EFFFPLR-NECB2011') water_heater_mixed.setPartLoadFactorCurve(plf_vs_plr_curve) end # Append the name with standards information water_heater_mixed.setName("#{water_heater_mixed.name} #{water_heater_eff.round(3)} Therm Eff") OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.WaterHeaterMixed', "For #{template}: #{water_heater_mixed.name}; thermal efficiency = #{water_heater_eff.round(3)}, skin-loss UA = #{ua_btu_per_hr_per_f.round}Btu/hr-R") return true end
do not apply zone hvac ventilation control
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1125 def zone_hvac_component_occupancy_ventilation_control(zone_hvac_component) return false end