Module: OsLib_QAQC
- Included in:
- GenericQAQC
- Defined in:
- lib/measures/generic_qaqc/resources/check_fan_pwr.rb,
lib/measures/generic_qaqc/resources/check_cond_zns.rb,
lib/measures/generic_qaqc/resources/check_pump_pwr.rb,
lib/measures/generic_qaqc/resources/check_plant_cap.rb,
lib/measures/generic_qaqc/resources/check_sch_coord.rb,
lib/measures/generic_qaqc/resources/check_schedules.rb,
lib/measures/generic_qaqc/resources/check_part_loads.rb,
lib/measures/generic_qaqc/resources/check_calibration.rb,
lib/measures/generic_qaqc/resources/check_placeholder.rb,
lib/measures/generic_qaqc/resources/check_plant_temps.rb,
lib/measures/generic_qaqc/resources/check_plenum_loads.rb,
lib/measures/generic_qaqc/resources/check_air_sys_temps.rb,
lib/measures/generic_qaqc/resources/check_mech_sys_type.rb,
lib/measures/generic_qaqc/resources/check_weather_files.rb,
lib/measures/generic_qaqc/resources/check_eui_by_end_use.rb,
lib/measures/generic_qaqc/resources/check_internal_loads.rb,
lib/measures/generic_qaqc/resources/check_mech_sys_capacity.rb,
lib/measures/generic_qaqc/resources/check_domestic_hot_water.rb,
lib/measures/generic_qaqc/resources/check_eui_reasonableness.rb,
lib/measures/generic_qaqc/resources/check_mech_sys_efficiency.rb,
lib/measures/generic_qaqc/resources/check_envelope_conductance.rb,
lib/measures/generic_qaqc/resources/check_mech_sys_part_load_eff.rb,
lib/measures/generic_qaqc/resources/check_simultaneous_heating_and_cooling.rb,
lib/measures/generic_qaqc/resources/check_supply_air_and_thermostat_temp_difference.rb
Overview
******************************************************************************* OpenStudio®, Copyright © Alliance for Sustainable Energy, LLC. See also openstudio.net/license *******************************************************************************
Instance Method Summary collapse
-
#bin_part_loads_by_ten_pcts(hrly_plrs) ⇒ Object
Bin the hourly part load ratios into 10% bins.
-
#check_air_sys_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false) ⇒ Object
Check the air loop and zone operational vs.
-
#check_calibration(category, target_standard, max_nmbe, max_cvrmse, name_only = false) ⇒ Object
Check the calibration against utility bills.
-
#check_cond_zns(category, target_standard, name_only = false) ⇒ Object
Check that all zones with people are conditioned (have a thermostat with setpoints).
-
#check_domestic_hot_water(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_envelope_conductance(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model todo - do I need unique tolerance ranges for conductance, reflectance, and shgc.
-
#check_eui_by_end_use(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_eui_reasonableness(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_fan_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false) ⇒ Object
Check the fan power (W/cfm) for each air loop fan in the model to identify unrealistically sized fans.
-
#check_internal_loads(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_mech_sys_capacity(category, options, target_standard, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_mech_sys_efficiency(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_mech_sys_part_load_eff(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_mech_sys_type(category, target_standard, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_part_loads(category, target_standard, max_pct_delta = 0.1, name_only = false) ⇒ Object
Check primary heating and cooling equipment part load ratios to find equipment that is significantly oversized or undersized.
-
#check_placeholder(category, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_plant_cap(category, target_standard, max_pct_delta = 0.1, name_only = false) ⇒ Object
Check primary plant loop heating and cooling equipment capacity against coil loads to find equipment that is significantly oversized or undersized.
-
#check_plant_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false) ⇒ Object
Check the plant loop operational vs.
-
#check_plenum_loads(category, target_standard, name_only = false) ⇒ Object
Check that there are no people or lights in plenums.
-
#check_pump_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false) ⇒ Object
Check the pumping power (W/gpm) for each pump in the model to identify unrealistically sized pumps.
-
#check_sch_coord(category, target_standard, max_hrs, name_only = false) ⇒ Object
Check that the lighting, equipment, and HVAC setpoint schedules coordinate with the occupancy schedules.
-
#check_schedules(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_simultaneous_heating_and_cooling(category, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_supply_air_and_thermostat_temp_difference(category, target_standard, max_delta, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#check_weather_files(category, options, name_only = false) ⇒ Object
checks the number of unmet hours in the model.
-
#generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) ⇒ Object
code for each load instance for different load types will pass through here will return nill or a single attribute.
-
#get_start_and_end_times(schedule_ruleset) ⇒ Object
Determine the hour when the schedule first exceeds the starting value and when it goes back down to the ending value at the end of the day.
- #map_sub_surfaces_props(sub_surface, check_elems, defaulted_const_type) ⇒ Object
-
#map_surface_props(surface, check_elems, defaulted_const_type) ⇒ Object
common methods.
Instance Method Details
#bin_part_loads_by_ten_pcts(hrly_plrs) ⇒ Object
Bin the hourly part load ratios into 10% bins
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/measures/generic_qaqc/resources/check_part_loads.rb', line 8 def bin_part_loads_by_ten_pcts(hrly_plrs) bins = Array.new(10, 0) op_hrs = 0.0 hrly_plrs.each do |plr| op_hrs += 1.0 if plr > 0 if plr <= 0.1 # add below-zero % PLRs to final bin bins[0] += 1 elsif plr > 0.1 && plr <= 0.2 bins[1] += 1 elsif plr > 0.2 && plr <= 0.3 bins[2] += 1 elsif plr > 0.3 && plr <= 0.4 bins[3] += 1 elsif plr > 0.4 && plr <= 0.5 bins[4] += 1 elsif plr > 0.5 && plr <= 0.6 bins[5] += 1 elsif plr > 0.6 && plr <= 0.7 bins[6] += 1 elsif plr > 0.7 && plr <= 0.8 bins[7] += 1 elsif plr > 0.8 && plr <= 0.9 bins[8] += 1 elsif plr > 0.9 # add over-100% PLRs to final bin bins[9] += 1 end end # Convert bins from hour counts to % of operating hours. bins.each_with_index do |bin, i| bins[i] = bins[i] / op_hrs end return bins end |
#check_air_sys_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false) ⇒ Object
Check the air loop and zone operational vs. sizing temperatures and make sure everything is coordinated. This identifies problems caused by sizing to one set of conditions and operating at a different set.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/measures/generic_qaqc/resources/check_air_sys_temps.rb', line 10 def check_air_sys_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Air System Temperatures') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that air system sizing and operation temperatures are coordinated.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check each air loop in the model @model.getAirLoopHVACs.sort.each do |airloop| loop_name = airloop.name.to_s # Get the central heating and cooling SAT for sizing sizing_system = airloop.sizingSystem loop_siz_htg_f = OpenStudio.convert(sizing_system.centralHeatingDesignSupplyAirTemperature, 'C', 'F').get loop_siz_clg_f = OpenStudio.convert(sizing_system.centralCoolingDesignSupplyAirTemperature, 'C', 'F').get # Compare air loop to zone sizing temperatures airloop.thermalZones.each do |zone| # If this zone has a reheat terminal, get the reheat temp for comparison reheat_op_f = nil reheat_zone = false zone.equipment.each do |equip| obj_type = equip.iddObjectType.valueName.to_s case obj_type when 'OS_AirTerminal_SingleDuct_ConstantVolume_Reheat' term = equip.to_AirTerminalSingleDuctConstantVolumeReheat.get reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get reheat_zone = true when 'OS_AirTerminal_SingleDuct_VAV_HeatAndCool_Reheat' term = equip.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.get reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get reheat_zone = true when 'OS_AirTerminal_SingleDuct_VAV_Reheat' term = equip.to_AirTerminalSingleDuctVAVReheat.get reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get reheat_zone = true when 'OS_AirTerminal_SingleDuct_ParallelPIU_Reheat' term = equip.to_AirTerminalSingleDuctParallelPIUReheat.get # reheat_op_f = # Not an OpenStudio input reheat_zone = true when 'OS_AirTerminal_SingleDuct_SeriesPIU_Reheat' term = equip.to_AirTerminalSingleDuctSeriesPIUReheat.get # reheat_op_f = # Not an OpenStudio input reheat_zone = true end end # Get the zone heating and cooling SAT for sizing sizing_zone = zone.sizingZone zone_siz_htg_f = OpenStudio.convert(sizing_zone.zoneHeatingDesignSupplyAirTemperature, 'C', 'F').get zone_siz_clg_f = OpenStudio.convert(sizing_zone.zoneCoolingDesignSupplyAirTemperature, 'C', 'F').get # Check cooling temperatures if ((loop_siz_clg_f - zone_siz_clg_f) / loop_siz_clg_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the sizing for the air loop is done with a cooling supply air temp of #{loop_siz_clg_f.round(2)}F, but the sizing for the zone is done with a cooling supply air temp of #{zone_siz_clg_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end # Check heating temperatures if reheat_zone && reheat_op_f if ((reheat_op_f - zone_siz_htg_f) / reheat_op_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the reheat air temp is set to #{reheat_op_f.round(2)}F, but the sizing for the zone is done with a heating supply air temp of #{zone_siz_htg_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end elsif reheat_zone && !reheat_op_f # Don't perform the check if it is a reheat zone but the reheat temperature # is not available from the model inputs else if ((loop_siz_htg_f - zone_siz_htg_f) / loop_siz_htg_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the sizing for the air loop is done with a heating supply air temp of #{loop_siz_htg_f.round(2)}F, but the sizing for the zone is done with a heating supply air temp of #{zone_siz_htg_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end end end # Determine the min and max operational temperatures loop_op_min_f = nil loop_op_max_f = nil airloop.supplyOutletNode.setpointManagers.each do |spm| obj_type = spm.iddObjectType.valueName.to_s case obj_type when 'OS_SetpointManager_Scheduled' sch = spm.to_SetpointManagerScheduled.get.schedule if sch.to_ScheduleRuleset.is_initialized min_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(sch.to_ScheduleRuleset.get)['min'] max_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(sch.to_ScheduleRuleset.get)['max'] elsif sch.to_ScheduleConstant.is_initialized min_c = std.schedule_constant_annual_min_max_value(sch.to_ScheduleConstant.get)['min'] max_c = std.schedule_constant_annual_min_max_value(sch.to_ScheduleConstant.get)['max'] else next end loop_op_min_f = OpenStudio.convert(min_c, 'C', 'F').get loop_op_max_f = OpenStudio.convert(max_c, 'C', 'F').get when 'OS_SetpointManager_SingleZoneReheat' spm = spm.to_SetpointManagerSingleZoneReheat.get loop_op_min_f = OpenStudio.convert(spm.minimumSupplyAirTemperature, 'C', 'F').get loop_op_max_f = OpenStudio.convert(spm.maximumSupplyAirTemperature, 'C', 'F').get when 'OS_SetpointManager_Warmest' spm = spm.to_SetpointManagerSingleZoneReheat.get loop_op_min_f = OpenStudio.convert(spm.minimumSetpointTemperature, 'C', 'F').get loop_op_max_f = OpenStudio.convert(spm.maximumSetpointTemperature, 'C', 'F').get when 'OS_SetpointManager_WarmestTemperatureFlow' spm = spm.to_SetpointManagerSingleZoneReheat.get loop_op_min_f = OpenStudio.convert(spm.minimumSetpointTemperature, 'C', 'F').get loop_op_max_f = OpenStudio.convert(spm.maximumSetpointTemperature, 'C', 'F').get else next # Only check the commonly used setpoint managers end end # Compare air loop sizing temperatures to operational temperatures # Cooling if loop_op_min_f if ((loop_op_min_f - loop_siz_clg_f) / loop_op_min_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{airloop.name}, the sizing is done with a cooling supply air temp of #{loop_siz_clg_f.round(2)}F, but the setpoint manager controlling the loop operates down to #{loop_op_min_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end end # Heating if loop_op_max_f if ((loop_op_max_f - loop_siz_htg_f) / loop_op_max_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{airloop.name}, the sizing is done with a heating supply air temp of #{loop_siz_htg_f.round(2)}F, but the setpoint manager controlling the loop operates up to #{loop_op_max_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_calibration(category, target_standard, max_nmbe, max_cvrmse, name_only = false) ⇒ Object
Check the calibration against utility bills.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/measures/generic_qaqc/resources/check_calibration.rb', line 8 def check_calibration(category, target_standard, max_nmbe, max_cvrmse, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Calibration') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that the model is calibrated to the utility bills.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check that there are utility bills in the model if @model.getUtilityBills.empty? check_elems << OpenStudio::Attribute.new('flag', 'Model contains no utility bills, cannot check calibration.') end # Check the calibration for each utility bill @model.getUtilityBills.each do |bill| bill_name = bill.name.get fuel = bill.fuelType.valueDescription # Consumption # NMBE if bill.NMBE.is_initialized nmbe = bill.NMBE.get if nmbe > max_nmbe || nmbe < -1.0 * max_nmbe check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the consumption NMBE of #{nmbe.round(1)}% is outside the limit of +/- #{max_nmbe}%, so the model is not calibrated.") end end # CVRMSE if bill.CVRMSE.is_initialized cvrmse = bill.CVRMSE.get if cvrmse > max_cvrmse check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the consumption CVRMSE of #{cvrmse.round(1)}% is above the limit of #{max_cvrmse}%, so the model is not calibrated.") end end # Peak Demand (for some fuels) if bill.peakDemandUnitConversionFactor.is_initialized peak_conversion = bill.peakDemandUnitConversionFactor.get # Get modeled and actual values actual_vals = [] modeled_vals = [] bill.billingPeriods.each do |billing_period| actual_peak = billing_period.peakDemand if actual_peak.is_initialized actual_vals << actual_peak.get end modeled_peak = billing_period.modelPeakDemand if modeled_peak.is_initialized modeled_vals << modeled_peak.get end end # Check that both arrays are the same size unless actual_vals.size == modeled_vals.size check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, cannot get the same number of modeled and actual peak demand values, cannot check peak demand calibration.") end # NMBE and CMRMSE ysum = 0 sum_err = 0 squared_err = 0 n = actual_vals.size actual_vals.each_with_index do |actual, i| modeled = modeled_vals[i] actual *= peak_conversion # Convert actual demand to model units ysum += actual sum_err += (actual - modeled) squared_err += (actual - modeled)**2 end if n > 1 = ysum / n # NMBE demand_nmbe = 100.0 * (sum_err / (n - 1)) / if demand_nmbe > max_nmbe || demand_nmbe < -1.0 * max_nmbe check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the peak demand NMBE of #{demand_nmbe.round(1)}% is outside the limit of +/- #{max_nmbe}%, so the model is not calibrated.") end # CVRMSE demand_cvrmse = 100.0 * (squared_err / (n - 1))**0.5 / if demand_cvrmse > max_cvrmse check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the peak demand CVRMSE of #{demand_cvrmse.round(1)}% is above the limit of #{max_cvrmse}%, so the model is not calibrated.") end end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_cond_zns(category, target_standard, name_only = false) ⇒ Object
Check that all zones with people are conditioned (have a thermostat with setpoints)
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/measures/generic_qaqc/resources/check_cond_zns.rb', line 8 def check_cond_zns(category, target_standard, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Conditioned Zones') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that all zones with people have thermostats.') check_elems << OpenStudio::Attribute.new('min_pass', "#{(min_pass * 100).round(0)}") check_elems << OpenStudio::Attribute.new('max_pass', "#{(max_pass * 100).round(0)}") # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin @model.getThermalZones.each do |zone| # Only check zones that have people num_ppl = zone.numberOfPeople next unless zone.numberOfPeople > 0 # Check that the zone is heated (at a minimum) # by checking that the heating setpoint is at least 41F. # Sometimes people include thermostats but set the setpoints # such that the system never comes on. This check attempts to catch that. unless std.thermal_zone_heated?(zone) check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} has #{num_ppl} people but is not heated. Zones containing people are expected to be conditioned, heated-only at a minimum. Heating setpoint must be at least 41F to be considered heated.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_domestic_hot_water(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/measures/generic_qaqc/resources/check_domestic_hot_water.rb', line 10 def check_domestic_hot_water(category, target_standard, min_pass, max_pass, name_only = false) # TODO: - could expose meal turnover and people per unit for res and hotel into arguments # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Domestic Hot Water') check_elems << OpenStudio::Attribute.new('category', category) if target_standard == 'ICC IECC 2015' check_elems << OpenStudio::Attribute.new('description', 'Check service water heating consumption against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.') else check_elems << OpenStudio::Attribute.new('description', 'Check against the 2011 ASHRAE Handbook - HVAC Applications, Table 7 section 50.14.') end check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # loop through water_use_equipment service_water_consumption_daily_avg_gal = 0.0 @model.getWaterUseEquipments.each do |water_use_equipment| # get peak flow rate from def peak_flow_rate_si = water_use_equipment.waterUseEquipmentDefinition.peakFlowRate source_units = 'm^3/s' target_units = 'gal/min' peak_flow_rate_ip = OpenStudio.convert(peak_flow_rate_si, source_units, target_units).get # get value from flow rate schedule if water_use_equipment.flowRateFractionSchedule.is_initialized # get annual equiv for model schedule schedule_inst = water_use_equipment.flowRateFractionSchedule.get if schedule_inst.to_ScheduleRuleset.is_initialized if use_old_gem_code annual_equiv_flow_rate = schedule_inst.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_inst.to_ScheduleRuleset.get) end elsif schedule_inst.to_ScheduleConstant.is_initialized if use_old_gem_code annual_equiv_flow_rate = schedule_inst.to_ScheduleConstant.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = OpenstudioStandards::Schedules.schedule_constant_get_equivalent_full_load_hours(schedule_inst.to_ScheduleConstant.get) end else check_elems << OpenStudio::Attribute.new('flag', "#{schedule_inst.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") next end else # issue flag check_elems << OpenStudio::Attribute.new('flag', "#{water_use_equipment.name} doesn't have a schedule. Can't identify hot water consumption.") next end # add to global service water consumpiton value service_water_consumption_daily_avg_gal += 60.0 * peak_flow_rate_ip * annual_equiv_flow_rate / 365.0 end if target_standard == 'ICC IECC 2015' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Apartment' # currently only supports midrise apt space type space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # lookup target gal/day for the building bedrooms_per_unit = 2.0 # assumption num_units = num_people / 2.5 # Avg 2.5 units per person. if use_old_gem_code target_consumption = @model.find_icc_iecc_2015_hot_water_demand(num_units, bedrooms_per_unit) else target_consumption = std.model_find_icc_iecc_2015_hot_water_demand(@model, num_units, bedrooms_per_unit) end else # only other path for now is 90.1-2013 # get building type building_type = '' if @model.getBuilding.standardsBuildingType.is_initialized building_type = @model.getBuilding.standardsBuildingType.get end # lookup data from standards if use_old_gem_code ashrae_hot_water_demand = @model.find_ashrae_hot_water_demand else ashrae_hot_water_demand = std.model_find_ashrae_hot_water_demand(@model) end # building type specific logic for water consumption # todo - update test to exercise various building types if !ashrae_hot_water_demand.empty? if building_type == 'FullServiceRestaurant' num_people_hours = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Dining' space_type_floor_area = space_type.floorArea space_type_num_people_hours = 0.0 # loop through peole instances space_type.people.each do |inst| inst_num_people = inst.getNumberOfPeople(space_type_floor_area) inst_schedule = inst.numberofPeopleSchedule.get # sim will fail prior to this if doesn't have it if inst_schedule.to_ScheduleRuleset.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(inst_schedule.to_ScheduleRuleset.get) end elsif inst_schedule.to_ScheduleConstant.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleConstant.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleConstant.get) end else check_elems << OpenStudio::Attribute.new('flag', "#{inst_schedule.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") annual_equiv_flow_rate = 0.0 end inst_num_people_horus = annual_equiv_flow_rate * inst_num_people space_type_num_people_hours += inst_num_people_horus end num_people_hours += space_type_num_people_hours end num_meals = num_people_hours / (365.0 * 1.5) # 90 minute meal target_consumption = num_meals * ashrae_hot_water_demand.first[:avg_day_unit] elsif ['LargeHotel', 'SmallHotel'].include? building_type num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'GuestRoom' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # find best fit from returned results num_units = num_people / 2.0 # 2 people per room design load, not typical occupancy avg_day_unit = nil fit = nil ashrae_hot_water_demand.each do |block| if fit.nil? avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs elsif (avg_day_unit - block[:block]).abs - fit avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs end end target_consumption = num_units * avg_day_unit elsif building_type == 'MidriseApartment' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Apartment' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # find best fit from returned results num_units = num_people / 2.5 # Avg 2.5 units per person. avg_day_unit = nil fit = nil ashrae_hot_water_demand.each do |block| if fit.nil? avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs elsif (avg_day_unit - block[:block]).abs - fit avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs end end target_consumption = num_units * avg_day_unit elsif ['Office', 'LargeOffice', 'MediumOffice', 'SmallOffice'].include? building_type num_people = @model.getBuilding.numberOfPeople target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit] elsif building_type == 'PrimarySchool' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Classroom' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit] elsif building_type == 'QuickServiceRestaurant' num_people_hours = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Dining' space_type_floor_area = space_type.floorArea space_type_num_people_hours = 0.0 # loop through peole instances space_type.peoples.each do |inst| inst_num_people = inst.getNumberOfPeople(space_type_floor_area) inst_schedule = inst.numberofPeopleSchedule.get # sim will fail prior to this if doesn't have it if inst_schedule.to_ScheduleRuleset.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(inst_schedule.to_ScheduleRuleset.get) end elsif inst_schedule.to_ScheduleConstant.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleConstant.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleConstant.get) end else check_elems << OpenStudio::Attribute.new('flag', "#{inst_schedule.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") annual_equiv_flow_rate = 0.0 end inst_num_people_horus = annual_equiv_flow_rate * inst_num_people space_type_num_people_hours += inst_num_people_horus end num_people_hours += space_type_num_people_hours end num_meals = num_people_hours / (365.0 * 0.5) # 30 minute leal # todo - add logic to address drive through traffic target_consumption = num_meals * ashrae_hot_water_demand.first[:avg_day_unit] elsif building_type == 'SecondarySchool' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Classroom' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit] else check_elems << OpenStudio::Attribute.new('flag', "No rule of thumb values exist for #{building_type}. Hot water consumption was not checked.") end else check_elems << OpenStudio::Attribute.new('flag', "No rule of thumb values exist for #{building_type}. Hot water consumption was not checked.") end end # check actual against target if !target_consumption.nil? if service_water_consumption_daily_avg_gal < target_consumption * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Annual average of #{service_water_consumption_daily_avg_gal.round} gallons per day of hot water is more than #{min_pass * 100} % below the value of #{target_consumption.round} gallons per day.") elsif service_water_consumption_daily_avg_gal > target_consumption * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Annual average of #{service_water_consumption_daily_avg_gal.round} gallons per day of hot water is more than #{max_pass * 100} % above the value of #{target_consumption.round} gallons per day.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_envelope_conductance(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model todo - do I need unique tolerance ranges for conductance, reflectance, and shgc
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'lib/measures/generic_qaqc/resources/check_envelope_conductance.rb', line 91 def check_envelope_conductance(category, target_standard, min_pass, max_pass, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Envelope R-Value') check_elems << OpenStudio::Attribute.new('category', category) if target_standard == 'ICC IECC 2015' dislay_standard = target_standard check_elems << OpenStudio::Attribute.new('description', "Check envelope against Table R402.1.2 and R402.1.4 in #{dislay_standard} Residential Provisions.") elsif target_standard.include?('90.1-2013') display_standard = "ASHRAE #{target_standard}" check_elems << OpenStudio::Attribute.new('description', "Check envelope against #{display_standard} Table 5.5.2, Table G2.1.5 b,c,d,e, Section 5.5.3.1.1a. Roof reflectance of 55%, wall relfectance of 30%.") else # TODO: - could add more elsifs if want to dsiplay tables and sections for additional 90.1 standards if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end check_elems << OpenStudio::Attribute.new('description', "Check envelope against #{display_standard}. Roof reflectance of 55%, wall relfectance of 30%.") end check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end begin # setup standard std = Standard.build(target_standard) # gather building type for summary if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') bt_cz = std.model_get_building_properties(@model) else bt_cz = std.model_get_building_climate_zone_and_building_type(@model) end building_type = bt_cz['building_type'] climate_zone = bt_cz['climate_zone'] prototype_prefix = "#{target_standard} #{building_type} #{climate_zone}" # if building type, or climate zone are empty don't run this section if [building_type, climate_zone].include?("") check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target Envelope performance. Make sure model has climate zone and building type defined.") check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem end # make array of construction details for surfaces surface_details = [] missing_surface_constructions = [] sub_surface_details = [] missing_sub_surface_constructions = [] construction_type_array = [] space_type_const_properties = {} defaulted_const_type = [] data_not_returned_for = [] # loop through all space types used in the model @model.getSpaceTypes.each do |space_type| next if space_type.floorArea <= 0 # loop through spaces space_type.spaces.each do |space| space.surfaces.each do |surface| next if surface.outsideBoundaryCondition != 'Outdoors' if surface.construction.is_initialized surf_props = self.map_surface_props(surface,check_elems,defaulted_const_type) ext_surf_type = surf_props[:ext_surf_type] const_type = surf_props[:const_type] construction = surf_props[:construction] # todo - need to get and add the building_category for this space/space type and add to surface_details. If can't identify then issue warning and assume it is nonresidential data = std.space_type_get_construction_properties(space_type, ext_surf_type, const_type) if !data.nil? const_bldg_cat = data['building_category'] surface_details << { boundary_condition: surface.outsideBoundaryCondition, surface_type: ext_surf_type, construction: construction, construction_type: const_type, const_bldg_cat: const_bldg_cat } if !construction_type_array.include? [ext_surf_type,const_type,const_bldg_cat] construction_type_array << [ext_surf_type,const_type,const_bldg_cat] end else if !data_not_returned_for.include?([space_type,ext_surf_type,const_type]) check_elems << OpenStudio::Attribute.new('flag', "Data not returned for #{space_type.name} on #{const_type} for #{ext_surf_type}.") data_not_returned_for << [space_type,ext_surf_type,const_type] end end else missing_constructions << surface.name.get end # make array of construction details for sub_surfaces surface.subSurfaces.each do |sub_surface| if sub_surface.construction.is_initialized sub_surf_props = self.map_sub_surfaces_props(sub_surface,check_elems,defaulted_const_type) ext_sub_surf_type = sub_surf_props[:ext_sub_surf_type] sub_const_type = sub_surf_props[:sub_const_type] construction = sub_surf_props[:construction] data = std.space_type_get_construction_properties(space_type, ext_sub_surf_type, sub_const_type) if !data.nil? const_bldg_cat = data['building_category'] sub_surface_details << {boundary_condition: sub_surface.outsideBoundaryCondition, surface_type: ext_sub_surf_type, construction: sub_surface.construction.get, construction_type: sub_const_type, const_bldg_cat: const_bldg_cat} if !construction_type_array.include? [ext_sub_surf_type,sub_const_type,const_bldg_cat] construction_type_array << [ext_sub_surf_type,sub_const_type,const_bldg_cat] end else if !data_not_returned_for.include?([space_type,ext_sub_surf_type,sub_const_type]) check_elems << OpenStudio::Attribute.new('flag', "Data not returned for #{space_type.name} on #{sub_const_type} for #{ext_sub_surf_type}.") data_not_returned_for << [space_type,ext_sub_surf_type,sub_const_type] end end else missing_constructions << sub_surface.name.get end end end end if !missing_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_constructions.size} surfaces are missing constructions in #{space_type.name}.") end if !missing_sub_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_constructions.size} sub surfaces are missing constructions in #{space_type.name}.") end construction_type_array.each do |const_attributes| # gather data for exterior wall intended_surface_type = const_attributes[0] standards_construction_type = const_attributes[1] if !space_type_const_properties.key?(intended_surface_type) space_type_const_properties[intended_surface_type] = {} end if !space_type_const_properties[intended_surface_type].key?(standards_construction_type) space_type_const_properties[intended_surface_type][standards_construction_type] = {} end data = std.space_type_get_construction_properties(space_type, intended_surface_type, standards_construction_type) if data.nil? check_elems << OpenStudio::Attribute.new('flag', "Didn't find target construction values for #{target_standard} #{standards_construction_type} #{intended_surface_type} for #{space_type.name}.") elsif ['ExteriorWall','ExteriorDoor'].include? intended_surface_type const_bldg_cat = data['building_category'] if !space_type_const_properties[intended_surface_type][standards_construction_type].key?(const_bldg_cat) space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat] = {} end space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['u_value'] = data['assembly_maximum_u_value'] space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['reflectance'] = 0.30 # hard coded value elsif intended_surface_type.include? 'ExteriorFloor' const_bldg_cat = data['building_category'] if !space_type_const_properties[intended_surface_type][standards_construction_type].key?(const_bldg_cat) space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat] = {} end space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['u_value'] = data['assembly_maximum_u_value'] elsif intended_surface_type.include? 'ExteriorRoof' const_bldg_cat = data['building_category'] if !space_type_const_properties[intended_surface_type][standards_construction_type].key?(const_bldg_cat) space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat] = {} end space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['u_value'] = data['assembly_maximum_u_value'] space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['reflectance'] = 0.55 # hard coded value else # glazing const_bldg_cat = data['building_category'] if !space_type_const_properties[intended_surface_type][standards_construction_type].key?(const_bldg_cat) space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat] = {} end space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['u_value'] = data['assembly_maximum_u_value'] space_type_const_properties[intended_surface_type][standards_construction_type][const_bldg_cat]['shgc'] = data['assembly_maximum_solar_heat_gain_coefficient'] end end end # loop through unique construction arary combinations surface_details.uniq.each do |surface_detail| if surface_detail[:construction].thermalConductance.is_initialized # don't use intened surface type of construction, look map based on surface type and boundary condition boundary_condition = surface_detail[:boundary_condition] intended_surface_type = surface_detail[:surface_type] construction_type = surface_detail[:construction_type] next if boundary_condition.to_s != 'Outdoors' film_coefficients_r_value = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, includes_int_film = true, includes_ext_film = true) thermal_conductance = surface_detail[:construction].thermalConductance.get r_value_with_film = 1 / thermal_conductance + film_coefficients_r_value source_units = 'm^2*K/W' target_units = 'ft^2*h*R/Btu' r_value_ip = OpenStudio.convert(r_value_with_film, source_units, target_units).get solar_reflectance = surface_detail[:construction].to_LayeredConstruction.get.layers[0].to_OpaqueMaterial.get.solarReflectance .get # TODO: - check optional first does what happens with ext. air wall const_bldg_cat = surface_detail[:const_bldg_cat] # lookup target_r_value_ip target_r_value_ip = 1.0 / space_type_const_properties[intended_surface_type][construction_type][const_bldg_cat]['u_value'].to_f # stop if didn't find values (0 or infinity) next if construction_type == 0.0 next if construction_type == Float::INFINITY # check r avlues if r_value_ip < target_r_value_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{min_pass * 100} % below the value of #{target_r_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{surface_detail[:construction_type]}.") elsif r_value_ip > target_r_value_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{max_pass * 100} % above the value of #{target_r_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{surface_detail[:construction_type]}.") end # lookup target_reflectance target_reflectance = space_type_const_properties[intended_surface_type][construction_type][const_bldg_cat]['reflectance'].to_f # check solar reflectance next if intended_surface_type == 'ExteriorFloor' # do not check reflectance exterior floors (overhang) if (solar_reflectance < target_reflectance * (1.0 - min_pass)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{min_pass * 100} % below the value of #{(target_reflectance * 100).round} %.") elsif (solar_reflectance > target_reflectance * (1.0 + max_pass)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{max_pass * 100} % above the value of #{(target_reflectance * 100).round} %.") end else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate R value for #{surface_detail[:construction].name}.") end end # loop through unique construction arary combinations sub_surface_details.uniq.each do |sub_surface_detail| if sub_surface_detail[:surface_type] == 'ExteriorWindow' || sub_surface_detail[:surface_type] == 'Skylight' # check for non opaque sub surfaces source_units = 'W/m^2*K' target_units = 'Btu/ft^2*h*R' surface_construction = sub_surface_detail[:construction].to_LayeredConstruction.get u_factor_si = OpenstudioStandards::Constructions.construction_get_conductance(surface_construction) u_factor_ip = OpenStudio.convert(u_factor_si, source_units, target_units).get shgc = OpenstudioStandards::Constructions.construction_get_solar_transmittance(sub_surface_detail[:construction].to_LayeredConstruction.get) intended_surface_type = sub_surface_detail[:surface_type] construction_type = sub_surface_detail[:construction_type] const_bldg_cat = sub_surface_detail[:const_bldg_cat] boundary_condition = sub_surface_detail[:boundary_condition] next if boundary_condition.to_s != 'Outdoors' # lookup target_u_value_ip target_u_value_ip = space_type_const_properties[intended_surface_type][construction_type][const_bldg_cat]['u_value'].to_f # stop if didn't find values (0 or infinity) next if target_u_value_ip == 0.0 next if target_u_value_ip == Float::INFINITY # check u avlues if u_factor_ip < target_u_value_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "U value of #{u_factor_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{min_pass * 100} % below the value of #{target_u_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{sub_surface_detail[:construction_type]}.") elsif u_factor_ip > target_u_value_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "U value of #{u_factor_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{max_pass * 100} % above the value of #{target_u_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{sub_surface_detail[:construction_type]}.") end # lookup target_shgc target_shgc = space_type_const_properties[intended_surface_type][construction_type][const_bldg_cat]['shgc'].to_f # check shgc if shgc < target_shgc * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "SHGC of #{shgc.round(2)} % for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{min_pass * 100} % below the value of #{target_shgc.round(2)} %.") elsif shgc > target_shgc * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "SHGC of #{shgc.round(2)} % for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{max_pass * 100} % above the value of #{target_shgc.round(2)} %.") end else # check for opaque sub surfaces if sub_surface_detail[:construction].thermalConductance.is_initialized # don't use intened surface type of construction, look map based on surface type and boundary condition boundary_condition = sub_surface_detail[:boundary_condition] intended_surface_type = sub_surface_detail[:surface_type] construction_type = sub_surface_detail[:construction_type] next if boundary_condition.to_s != 'Outdoors' film_coefficients_r_value = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, includes_int_film = true, includes_ext_film = true) thermal_conductance = sub_surface_detail[:construction].thermalConductance.get r_value_with_film = 1 / thermal_conductance + film_coefficients_r_value source_units = 'm^2*K/W' target_units = 'ft^2*h*R/Btu' r_value_ip = OpenStudio.convert(r_value_with_film, source_units, target_units).get solar_reflectance = sub_surface_detail[:construction].to_LayeredConstruction.get.layers[0].to_OpaqueMaterial.get.solarReflectance .get # TODO: - check optional first does what happens with ext. air wall const_bldg_cat = sub_surface_detail[:const_bldg_cat] # lookup target_r_value_ip target_r_value_ip = 1.0 / space_type_const_properties[intended_surface_type][construction_type][const_bldg_cat]['u_value'].to_f # stop if didn't find values (0 or infinity) next if target_r_value_ip == 0.0 next if target_r_value_ip == Float::INFINITY # check r avlues if r_value_ip < target_r_value_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{min_pass * 100} % below the value of #{target_r_value_ip.round(2)} % (#{target_units}) for #{prototype_prefix} #{sub_surface_detail[:construction_type]} #{sub_surface_detail[:surface_type]}.") elsif r_value_ip > target_r_value_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{max_pass * 100} % above the value of #{target_r_value_ip.round(2)} % (#{target_units}) for #{prototype_prefix} #{sub_surface_detail[:construction_type]} #{sub_surface_detail[:surface_type]}.") end # lookup target_reflectance target_reflectance = space_type_const_properties[intended_surface_type][construction_type][const_bldg_cat]['reflectance'].to_f # check solar reflectance if (solar_reflectance < target_reflectance* (1.0 - min_pass)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{min_pass * 100} % below the value of #{(target_reflectance * 100).round} % for #{prototype_prefix} #{sub_surface_detail[:construction_type]} #{sub_surface_detail[:surface_type]}.") elsif (solar_reflectance > target_reflectance * (1.0 + max_pass)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{sub_surface_detail[:construction].name} in #{const_bldg_cat} space type is more than #{max_pass * 100} % above the value of #{(target_reflectance * 100).round} % for #{prototype_prefix} #{sub_surface_detail[:construction_type]} #{sub_surface_detail[:surface_type]}.") end else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate R value for #{sub_surface_detail[:construction].name}.") end end end # check spaces without space types against Nonresidential for this climate zone @model.getSpaces.each do |space| if !space.spaceType.is_initialized # make array of construction details for surfaces surface_details = [] missing_surface_constructions = [] sub_surface_details = [] missing_sub_surface_constructions = [] const_bldg_cat = 'Nonresidential' check_elems << OpenStudio::Attribute.new('flag', "Treating surfaces and sub-surfaces in space #{space.name} as Nonresidential since no space type is assigned.") space.surfaces.each do |surface| next if surface.outsideBoundaryCondition != 'Outdoors' if surface.construction.is_initialized surf_props = self.map_surface_props(surface,check_elems,defaulted_const_type) ext_surf_type = surf_props[:ext_surf_type] const_type = surf_props[:const_type] construction = surf_props[:construction] surface_details << { boundary_condition: surface.outsideBoundaryCondition, surface_type: ext_surf_type, construction: surface.construction.get,construction_type: const_type, const_bldg_cat: const_bldg_cat} else missing_constructions << surface.name.get end # make array of construction details for sub_surfaces surface.subSurfaces.each do |sub_surface| if sub_surface.construction.is_initialized sub_surf_props = self.map_sub_surfaces_props(sub_surface,check_elems,defaulted_const_type) ext_sub_surf_type = sub_surf_props[:ext_sub_surf_type] sub_const_type = sub_surf_props[:sub_const_type] construction = sub_surf_props[:construction] sub_surface_details << {boundary_condition: sub_surface.outsideBoundaryCondition, surface_type: ext_sub_surf_type, construction: sub_surface.construction.get, construction_type: sub_const_type, const_bldg_cat: const_bldg_cat} else missing_constructions << sub_surface.name.get end end end if !missing_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_constructions.size} surfaces are missing constructions in #{space_type.name}. Spaces and can't be checked.") end if !missing_sub_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_constructions.size} sub surfaces are missing constructions in #{space_type.name}. Spaces and can't be checked.") end surface_details.uniq.each do |surface_detail| if surface_detail[:construction].thermalConductance.is_initialized # don't use intened surface type of construction, look map based on surface type and boundary condition boundary_condition = surface_detail[:boundary_condition] intended_surface_type = surface_detail[:surface_type] construction_type = surface_detail[:construction_type] film_coefficients_r_value = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, includes_int_film = true, includes_ext_film = true) thermal_conductance = surface_detail[:construction].thermalConductance.get r_value_with_film = 1 / thermal_conductance + film_coefficients_r_value source_units = 'm^2*K/W' target_units = 'ft^2*h*R/Btu' r_value_ip = OpenStudio.convert(r_value_with_film, source_units, target_units).get solar_reflectance = surface_detail[:construction].to_LayeredConstruction.get.layers[0].to_OpaqueMaterial.get.solarReflectance .get # TODO: - check optional first does what happens with ext. air wall # calculate target_r_value_ip target_reflectance = nil data = std.model_get_construction_properties(@model, intended_surface_type, construction_type, const_bldg_cat) if data.nil? check_elems << OpenStudio::Attribute.new('flag', "Didn't find construction for #{construction_type} #{intended_surface_type} for #{space.name}.") next elsif ['ExteriorWall','ExteriorDoor'].include? intended_surface_type assembly_maximum_u_value = data['assembly_maximum_u_value'] target_reflectance = 0.30 elsif intended_surface_type.include? 'ExteriorFloor' assembly_maximum_u_value = data['assembly_maximum_u_value'] elsif intended_surface_type.include? 'ExteriorRoof' assembly_maximum_u_value = data['assembly_maximum_u_value'] target_reflectance = 0.55 else assembly_maximum_u_value = data['assembly_maximum_u_value'] assembly_maximum_solar_heat_gain_coefficient = data['assembly_maximum_solar_heat_gain_coefficient'] end assembly_maximum_r_value_ip = 1 / assembly_maximum_u_value # stop if didn't find values (0 or infinity) next if assembly_maximum_r_value_ip == 0.0 next if assembly_maximum_r_value_ip == Float::INFINITY # check r avlues if r_value_ip < assembly_maximum_r_value_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{space.name} is more than #{min_pass * 100} % below the value of #{assembly_maximum_r_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{surface_detail[:construction_type]} #{surface_detail[:const_bldg_cat]}.") elsif r_value_ip > assembly_maximum_r_value_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_r_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{surface_detail[:construction_type]} #{surface_detail[:const_bldg_cat]}") elsif r_value_ip > assembly_maximum_r_value_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{space.name} is more than #{max_pass * 100} % above the value of #{assembly_maximum_r_value_ip.round(2)} (#{target_units}) for #{prototype_prefix} #{surface_detail[:construction_type]} #{surface_detail[:building_type_category]}.") end # check solar reflectance next if intended_surface_type == 'ExteriorFloor' # do not check reflectance exterior floors (overhang) if (solar_reflectance < target_reflectance * (1.0 - min_pass)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{space.name} is more than #{min_pass * 100} % below the value of #{(target_reflectance * 100).round} %.") elsif (solar_reflectance > target_reflectance * (1.0 + max_pass)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{space.name} is more than #{max_pass * 100} % above the value of #{(target_reflectance * 100).round} %.") end else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate R value for #{surface_detail[:construction].name}.") end end sub_surface_details.uniq.each do |sub_surface_detail| # TODO: update this so it works for doors and windows check_elems << OpenStudio::Attribute.new('flag', "Not setup to check sub-surfaces of spaces without space types. Can't check properties for #{sub_surface_detail[:construction].name}.") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_eui_by_end_use(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/measures/generic_qaqc/resources/check_eui_by_end_use.rb', line 10 def check_eui_by_end_use(category, target_standard, min_pass, max_pass, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'End Use by Category') check_elems << OpenStudio::Attribute.new('category', category) # update display sttandard if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems << OpenStudio::Attribute.new('description', "Check model consumption by end use against #{target_standard} DOE prototype building.") check_elems.each do |elem| results << elem.valueAsString end return results end begin # setup standard std = Standard.build(target_standard) target_eui = std.model_find_target_eui(@model) # gather building type for summary if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') bt_cz = std.model_get_building_properties(@model) else bt_cz = std.model_get_building_climate_zone_and_building_type(@model) end building_type = bt_cz['building_type'] # mapping to obuilding type to match space types if building_type.include?("Office") then building_type = "Office" end climate_zone = bt_cz['climate_zone'] prototype_prefix = "#{display_standard} #{building_type} #{climate_zone}" check_elems << OpenStudio::Attribute.new('description', "Check model consumption by end use against #{prototype_prefix} DOE prototype building.") check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # total building area query = 'SELECT Value FROM tabulardatawithstrings WHERE ' query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and " query << "ReportForString='Entire Facility' and " query << "TableName='Building Area' and " query << "RowName='Total Building Area' and " query << "ColumnName='Area' and " query << "Units='m2';" query_results = @sql.execAndReturnFirstDouble(query) if query_results.empty? check_elems << OpenStudio::Attribute.new('flag', "Can't calculate EUI, SQL query for building area failed.") return OpenStudio::Attribute.new('check', check_elems) else energy_plus_area = query_results.get end # temp code to check OS vs. E+ area open_studio_area = @model.getBuilding.floorArea if (energy_plus_area - open_studio_area).abs >= 0.1 check_elems << OpenStudio::Attribute.new('flag', "EnergyPlus reported area is #{energy_plus_area} (m^2). OpenStudio reported area is #{@model.getBuilding.floorArea} (m^2).") end # loop through end uses and gather consumption, normalized by floor area actual_eui_by_end_use = {} OpenStudio::EndUseCategoryType.getValues.each do |end_use| # get end uses end_use = OpenStudio::EndUseCategoryType.new(end_use).valueDescription total_end_use = 0 OpenStudio::EndUseFuelType.getValues.each do |fuel_type| # convert integer to string fuel_name = OpenStudio::EndUseFuelType.new(fuel_type).valueDescription next if fuel_name == 'Water' query_fuel = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= '#{fuel_name}'" results_fuel = @sql.execAndReturnFirstDouble(query_fuel).get total_end_use += results_fuel end # populate hash for actual end use normalized by area actual_eui_by_end_use[end_use] = total_end_use / energy_plus_area end # check if all spaces types used the building type defined in the model (percentage calculation doesn't check if all area is inclued in building floor area) if building_type != '' primary_type_floor_area = 0.0 non_pri_area = 0.0 non_pri_types = [] @model.getSpaceTypes.each do |space_type| st_bt = space_type.standardsBuildingType if st_bt.is_initialized st_bt = st_bt.get.to_s if st_bt.include?("Office") then st_bt = "Office" end if st_bt.to_s == building_type.to_s primary_type_floor_area += space_type.floorArea else non_pri_area += space_type.floorArea if !non_pri_types.include?(st_bt) then non_pri_types << st_bt end end else non_pri_area += space_type.floorArea if !non_pri_types.include?(st_bt) then non_pri_types << st_bt end end end if non_pri_area > 0.0 check_elems << OpenStudio::Attribute.new('flag', "The primary building type, #{building_type}, only represents #{(100 * primary_type_floor_area / (primary_type_floor_area + non_pri_area)).round}% of the total building area. Other standads building types included are #{non_pri_types.sort.join(",")}. While a comparison to the #{building_type} prototype consumption by end use is provided, it would not be unexpected for the building consumption by end use to be significantly different than the prototype.") end end # gather target end uses for given standard as hash std = Standard.build(target_standard) target_eui_by_end_use = std.model_find_target_eui_by_end_use(@model) # units for flag display text and unit conversion source_units = 'GJ/m^2' target_units = 'kBtu/ft^2' # check acutal vs. target against tolerance if !target_eui_by_end_use.nil? actual_eui_by_end_use.each do |end_use, value| # this should have value of 0 in model. This page change in the future. It doesn't exist in target lookup next if end_use == 'Exterior Equipment' # perform check and issue flags as needed target_value = target_eui_by_end_use[end_use] eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(value, source_units, target_units).get, 5, true) target_eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_value, source_units, target_units).get, 1, true) # add in use case specific logic to skip checks when near 0 actual and target skip = false if (end_use == 'Heat Recovery') && (value < 0.05) && (target_value < 0.05) then skip = true end if (end_use == 'Pumps') && (value < 0.05) && (target_value < 0.05) then skip = true end if (value < target_value * (1.0 - min_pass)) && !skip check_elems << OpenStudio::Attribute.new('flag', "#{end_use} EUI of #{eui_ip_neat} (#{target_units}) is more than #{min_pass * 100} % below the #{prototype_prefix} prototype #{end_use} EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.") elsif (value > target_value * (1.0 + max_pass)) && !skip check_elems << OpenStudio::Attribute.new('flag', "#{end_use} EUI of #{eui_ip_neat} (#{target_units}) is more than #{max_pass * 100} % above the #{prototype_prefix} prototype #{end_use} EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.") end end else if ['90.1-2016','90.1-2019'].include?(target_standard) || target_standard.include?("ComStock") check_elems << OpenStudio::Attribute.new('flag', "target EUI end use comparison is not supported yet for #{target_standard}.") else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target end use EUIs. Make sure model has expected climate zone and building type.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_eui_reasonableness(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/measures/generic_qaqc/resources/check_eui_reasonableness.rb', line 10 def check_eui_reasonableness(category, target_standard, min_pass, max_pass, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'EUI Reasonableness') check_elems << OpenStudio::Attribute.new('category', category) # update display sttandard if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end # stop here if only name is requested this is used to populate display name for arguments if name_only == true check_elems << OpenStudio::Attribute.new('description', "Check model EUI against #{display_standard} DOE prototype building.") results = [] check_elems.each do |elem| results << elem.valueAsString end return results end begin # setup standard std = Standard.build(target_standard) target_eui = std.model_find_target_eui(@model) # gather building type for summary if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') bt_cz = std.model_get_building_properties(@model) else bt_cz = std.model_get_building_climate_zone_and_building_type(@model) end building_type = bt_cz['building_type'] climate_zone = bt_cz['climate_zone'] prototype_prefix = "#{display_standard} #{building_type} #{climate_zone}" # mapping to obuilding type to match space types if building_type.include?("Office") then building_type = "Office" end # last part of summary table check_elems << OpenStudio::Attribute.new('description', "Check model EUI against #{prototype_prefix} DOE prototype building.") check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # total building area query = 'SELECT Value FROM tabulardatawithstrings WHERE ' query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and " query << "ReportForString='Entire Facility' and " query << "TableName='Building Area' and " query << "RowName='Total Building Area' and " query << "ColumnName='Area' and " query << "Units='m2';" query_results = @sql.execAndReturnFirstDouble(query) if query_results.empty? check_elems << OpenStudio::Attribute.new('flag', "Can't calculate EUI, SQL query for building area failed.") return OpenStudio::Attribute.new('check', check_elems) else energy_plus_area = query_results.get end # temp code to check OS vs. E+ area open_studio_area = @model.getBuilding.floorArea if (energy_plus_area - open_studio_area).abs >= 0.1 check_elems << OpenStudio::Attribute.new('flag', "EnergyPlus reported area is #{energy_plus_area} (m^2). OpenStudio reported area is #{@model.getBuilding.floorArea} (m^2).") end # EUI source_units = 'GJ/m^2' target_units = 'kBtu/ft^2' if energy_plus_area > 0.0 # don't calculate EUI if building doesn't have any area # todo - netSiteEnergy deducts for renewable. May want to update this to show gross consumption vs. net consumption eui = @sql.netSiteEnergy.get / energy_plus_area else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate model EUI, building doesn't have any floor area.") return OpenStudio::Attribute.new('check', check_elems) end # check if all spaces types used the building type defined in the model (percentage calculation doesn't check if all area is inclued in building floor area) if building_type != '' primary_type_floor_area = 0.0 non_pri_area = 0.0 non_pri_types = [] @model.getSpaceTypes.each do |space_type| st_bt = space_type.standardsBuildingType if st_bt.is_initialized st_bt = st_bt.get.to_s if st_bt.include?("Office") then st_bt = "Office" end if st_bt.to_s == building_type.to_s primary_type_floor_area += space_type.floorArea else non_pri_area += space_type.floorArea if !non_pri_types.include?(st_bt) then non_pri_types << st_bt end end else non_pri_area += space_type.floorArea if !non_pri_types.include?(st_bt) then non_pri_types << st_bt end end end if non_pri_area > 0.0 check_elems << OpenStudio::Attribute.new('flag', "The primary building type, #{building_type}, only represents #{(100 * primary_type_floor_area / (primary_type_floor_area + non_pri_area)).round}% of the total building area. Other standads building types included are #{non_pri_types.sort.join(",")}. While a comparison to the #{building_type} prototype EUI is provided, it would not be unexpected for the building EUI to be significantly different than the prototype.") end end # check model vs. target for user specified tolerance. if !target_eui.nil? eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(eui, source_units, target_units).get, 1, true) target_eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_eui, source_units, target_units).get, 1, true) if eui < target_eui * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Model EUI of #{eui_ip_neat} (#{target_units}) is more than #{min_pass * 100} % below the #{prototype_prefix} prototype EUI of #{target_eui_ip_neat} (#{target_units}).") elsif eui > target_eui * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Model EUI of #{eui_ip_neat} (#{target_units}) is more than #{max_pass * 100} % above the #{prototype_prefix} prototype EUI of #{target_eui_ip_neat} (#{target_units}).") end else if ['90.1-2016','90.1-2019'].include?(target_standard) || target_standard.include?("ComStock") check_elems << OpenStudio::Attribute.new('flag', "target EUI comparison is not supported yet for #{target_standard}.") else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target EUI. Make sure model has expected climate zone and building type.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_fan_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false) ⇒ Object
Check the fan power (W/cfm) for each air loop fan in the model to identify unrealistically sized fans.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/measures/generic_qaqc/resources/check_fan_pwr.rb', line 9 def check_fan_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Fan Power') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that fan power vs flow makes sense.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check each air loop @model.getAirLoopHVACs.each do |plant_loop| # Set the expected W/cfm expected_w_per_cfm = 1.1 # Check the W/cfm for each fan on each air loop plant_loop.supplyComponents.each do |sc| # Get the W/cfm for the fan obj_type = sc.iddObjectType.valueName.to_s case obj_type when 'OS_Fan_ConstantVolume' actual_w_per_cfm = std.fan_rated_w_per_cfm(sc.to_FanConstantVolume.get) when 'OS_Fan_OnOff' actual_w_per_cfm = std.fan_rated_w_per_cfm(sc.to_FanOnOff.get) when 'OS_Fan_VariableVolume' actual_w_per_cfm = std.fan_rated_w_per_cfm(sc.to_FanVariableVolume.get) else next # Skip non-fan objects end # Compare W/cfm to expected/typical values if ((expected_w_per_cfm - actual_w_per_cfm) / actual_w_per_cfm).abs > max_pwr_delta check_elems << OpenStudio::Attribute.new('flag', "For #{sc.name} on #{plant_loop.name}, the actual fan power of #{actual_w_per_cfm.round(1)} W/cfm is more than #{(max_pwr_delta * 100.0).round(2)}% different from the expected #{expected_w_per_cfm} W/cfm.") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_internal_loads(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/measures/generic_qaqc/resources/check_internal_loads.rb', line 10 def check_internal_loads(category, target_standard, min_pass, max_pass, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Internal Loads') check_elems << OpenStudio::Attribute.new('category', category) # update display sttandard if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] if target_standard == 'ICC IECC 2015' check_elems << OpenStudio::Attribute.new('description', 'Check internal loads against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.') else check_elems << OpenStudio::Attribute.new('description', "Check LPD, ventilation rates, occupant density, plug loads, and equipment loads against #{display_standard} DOE Prototype buildings.") end check_elems.each do |elem| results << elem.valueAsString end return results end begin # setup standard std = Standard.build(target_standard) # gather building type for summary if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') bt_cz = std.model_get_building_properties(@model) else bt_cz = std.model_get_building_climate_zone_and_building_type(@model) end building_type = bt_cz['building_type'] # mapping to obuilding type to match space types if building_type.include?("Office") then building_type = "Office" end climate_zone = bt_cz['climate_zone'] prototype_prefix = "#{display_standard} #{building_type} #{climate_zone}" if target_standard == 'ICC IECC 2015' check_elems << OpenStudio::Attribute.new('description', 'Check internal loads against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.') else check_elems << OpenStudio::Attribute.new('description', "Check LPD, ventilation rates, occupant density, plug loads, and equipment loads against #{prototype_prefix} DOE Prototype building.") end check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) if target_standard == 'ICC IECC 2015' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Apartment' # currently only supports midrise apt space type space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # lookup iecc internal loads for the building bedrooms_per_unit = 2.0 # assumption num_units = num_people / 2.5 # Avg 2.5 units per person. target_loads_hash = std.model_find_icc_iecc_2015_internal_loads(@model, num_units, bedrooms_per_unit) # get model internal gains for lights, elec equipment, and gas equipment model_internal_gains_si = 0.0 query_eleint_lights = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Lighting' and ColumnName= 'Electricity'" query_elec_equip = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Equipment' and ColumnName= 'Electricity'" query_gas_equip = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Equipment' and ColumnName= 'Natural Gas'" model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_eleint_lights).get model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_elec_equip).get model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_gas_equip).get model_internal_gains_si_kbtu_per_day = OpenStudio.convert(model_internal_gains_si, 'GJ', 'kBtu').get / 365.0 # assumes annual run # get target internal loads target_igain_btu_per_day = target_loads_hash['igain_btu_per_day'] target_igain_kbtu_per_day = OpenStudio.convert(target_igain_btu_per_day, 'Btu', 'kBtu').get # check internal loads if model_internal_gains_si_kbtu_per_day < target_igain_kbtu_per_day * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The model average of #{OpenStudio.toNeatString(model_internal_gains_si_kbtu_per_day, 2, true)} (kBtu/day) is more than #{min_pass * 100} % below the expected value of #{OpenStudio.toNeatString(target_igain_kbtu_per_day, 2, true)} (kBtu/day) for #{target_standard}.") elsif model_internal_gains_si_kbtu_per_day > target_igain_kbtu_per_day * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The model average of #{OpenStudio.toNeatString(model_internal_gains_si_kbtu_per_day, 2, true)} (kBtu/day) is more than #{max_pass * 100} % above the expected value of #{OpenStudio.toNeatString(target_igain_kbtu_per_day, 2, true)} k(Btu/day) for #{target_standard}.") end # get target mech vent target_mech_vent_cfm = target_loads_hash['mech_vent_cfm'] # get model mech vent model_mech_vent_si = 0 @model.getSpaceTypes.each do |space_type| next if space_type.floorArea <= 0 # get necessary space type information floor_area = space_type.floorArea num_people = space_type.getNumberOfPeople(floor_area) # get volume for space type for use with ventilation and infiltration space_type_volume = 0.0 space_type_exterior_area = 0.0 space_type_exterior_wall_area = 0.0 space_type.spaces.each do |space| space_type_volume += space.volume * space.multiplier space_type_exterior_area = space.exteriorArea * space.multiplier space_type_exterior_wall_area = space.exteriorWallArea * space.multiplier end # get design spec OA object if space_type.designSpecificationOutdoorAir.is_initialized oa = space_type.designSpecificationOutdoorAir.get oa_method = oa.outdoorAirMethod oa_per_person = oa.outdoorAirFlowperPerson * num_people oa_ach = oa.outdoorAirFlowAirChangesperHour * space_type_volume oa_per_area = oa.outdoorAirFlowperFloorArea * floor_area oa_flow_rate = oa.outdoorAirFlowRate oa_space_type_total = oa_per_person + oa_ach + oa_per_area + oa_flow_rate value_count = 0 if oa_per_person > 0 then value_count += 1 end if oa_ach > 0 then value_count += 1 end if oa_per_area > 0 then value_count += 1 end if oa_flow_rate > 0 then value_count += 1 end if (oa_method != 'Sum') && (value_count > 1) check_elems << OpenStudio::Attribute.new('flag', "Outdoor Air Method for #{space_type.name} was #{oa_method}. Expected value was Sum.") end else oa_space_type_total = 0.0 end # add to building total oa model_mech_vent_si += oa_space_type_total end # check oa model_mech_vent_cfm = OpenStudio.convert(model_mech_vent_si, 'm^3/s', 'cfm').get if model_mech_vent_cfm < target_mech_vent_cfm * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The model mechanical ventilation of #{OpenStudio.toNeatString(model_mech_vent_cfm, 2, true)} cfm is more than #{min_pass * 100} % below the expected value of #{OpenStudio.toNeatString(target_mech_vent_cfm, 2, true)} cfm for #{target_standard}.") elsif model_mech_vent_cfm > target_mech_vent_cfm * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The model mechanical ventilation of #{OpenStudio.toNeatString(model_mech_vent_cfm, 2, true)} cfm is more than #{max_pass * 100} % above the expected value of #{OpenStudio.toNeatString(target_mech_vent_cfm, 2, true)} cfm for #{target_standard}.") end else # gather all non statandard space types so can be listed in single flag non_tagged_space_types = [] # loop through all space types used in the model @model.getSpaceTypes.each do |space_type| next if space_type.floorArea <= 0 # get necessary space type information floor_area = space_type.floorArea num_people = space_type.getNumberOfPeople(floor_area) # load in standard info for this space type data = std.space_type_get_standards_data(space_type) if data.nil? || data.empty? # skip if all spaces using this space type are plenums all_spaces_plenums = true space_type.spaces.each do |space| if !OpenstudioStandards::Space.space_plenum?(space) all_spaces_plenums = false next end end if !all_spaces_plenums non_tagged_space_types << space_type.floorArea end next end # check lpd for space type model_lights_si = space_type.getLightingPowerPerFloorArea(floor_area, num_people) data['lighting_per_area'].nil? ? (target_lights_ip = 0.0) : (target_lights_ip = data['lighting_per_area']) source_units = 'W/m^2' target_units = 'W/ft^2' load_type = 'Lighting Power Density' model_ip = OpenStudio.convert(model_lights_si, source_units, target_units).get target_ip = target_lights_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) if model_ip < target_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") elsif model_ip > target_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") end # check electric equipment model_elec_si = space_type.getElectricEquipmentPowerPerFloorArea(floor_area, num_people) data['electric_equipment_per_area'].nil? ? (target_elec_ip = 0.0) : (target_elec_ip = data['electric_equipment_per_area']) source_units = 'W/m^2' target_units = 'W/ft^2' load_type = 'Electric Power Density' model_ip = OpenStudio.convert(model_elec_si, source_units, target_units).get target_ip = target_elec_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) if model_ip < target_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") elsif model_ip > target_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") end # check gas equipment model_gas_si = space_type.getGasEquipmentPowerPerFloorArea(floor_area, num_people) data['gas_equipment_per_area'].nil? ? (target_gas_ip = 0.0) : (target_gas_ip = data['gas_equipment_per_area']) source_units = 'W/m^2' target_units = 'Btu/hr*ft^2' load_type = 'Gas Power Density' model_ip = OpenStudio.convert(model_gas_si, source_units, target_units).get target_ip = target_gas_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) if model_ip < target_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") elsif model_ip > target_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") end # check people model_occ_si = space_type.getPeoplePerFloorArea(floor_area) data['occupancy_per_area'].nil? ? (target_occ_ip = 0.0) : (target_occ_ip = data['occupancy_per_area']) source_units = '1/m^2' # people/m^2 target_units = '1/ft^2' # people per ft^2 (can't add *1000) to the bottom, need to do later load_type = 'Occupancy per Area' model_ip = OpenStudio.convert(model_occ_si, source_units, target_units).get * 1000.0 target_ip = target_occ_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) # for people need to update target units just for display. Can't be used for converstion. target_units = 'People/1000 ft^2' if model_ip < target_ip * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") elsif model_ip > target_ip * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") end # get volume for space type for use with ventilation and infiltration space_type_volume = 0.0 space_type_exterior_area = 0.0 space_type_exterior_wall_area = 0.0 space_type.spaces.each do |space| space_type_volume += space.volume * space.multiplier space_type_exterior_area = space.exteriorArea * space.multiplier space_type_exterior_wall_area = space.exteriorWallArea * space.multiplier end # get design spec OA object if space_type.designSpecificationOutdoorAir.is_initialized oa = space_type.designSpecificationOutdoorAir.get oa_method = oa.outdoorAirMethod oa_per_person = oa.outdoorAirFlowperPerson oa_other = 0.0 oa_ach = oa.outdoorAirFlowAirChangesperHour * space_type_volume oa_per_area = oa.outdoorAirFlowperFloorArea * floor_area oa_flow_rate = oa.outdoorAirFlowRate oa_total = oa_ach + oa_per_area + oa_flow_rate value_count = 0 if oa_per_person > 0 then value_count += 1 end if oa_ach > 0 then value_count += 1 end if oa_per_area > 0 then value_count += 1 end if oa_flow_rate > 0 then value_count += 1 end if (oa_method != 'Sum') && (value_count > 1) check_elems << OpenStudio::Attribute.new('flag', "Outdoor Air Method for #{space_type.name} was #{oa_method}. Expected value was Sum.") end else oa_per_person = 0.0 oa_other_total = 0.0 end # get target values for OA target_oa_per_person_ip = data['ventilation_per_person'].to_f # ft^3/min*person target_oa_ach_ip = data['ventilation_air_changes'].to_f # ach target_oa_per_area_ip = data['ventilation_per_area'].to_f # ft^3/min*ft^2 if target_oa_per_person_ip.nil? target_oa_per_person_si = 0.0 else target_oa_per_person_si = OpenStudio.convert(target_oa_per_person_ip, 'cfm', 'm^3/s').get end if target_oa_ach_ip.nil? target_oa_ach_si = 0.0 else target_oa_ach_si = target_oa_ach_ip * space_type_volume end if target_oa_per_area_ip.nil? target_oa_per_area_si = 0.0 else target_oa_per_area_si = OpenStudio.convert(target_oa_per_area_ip, 'cfm/ft^2', 'm^3/s*m^2').get * floor_area end target_oa_total = target_oa_ach_si + target_oa_per_area_si # check oa per person source_units = 'm^3/s' target_units = 'cfm' load_type = 'Outdoor Air Per Person' model_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(oa_per_person, source_units, target_units).get, 2, true) target_ip_neat = OpenStudio.toNeatString(target_oa_per_person_ip, 2, true) if oa_per_person < target_oa_per_person_si * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") elsif oa_per_person > target_oa_per_person_si * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") end # check other oa source_units = 'm^3/s' target_units = 'cfm' load_type = 'Outdoor Air (Excluding per Person Value)' model_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(oa_total, source_units, target_units).get, 2, true) target_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_oa_total, source_units, target_units).get, 2, true) if oa_total < target_oa_total * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") elsif oa_total > target_oa_total * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the value of #{target_ip_neat} (#{target_units}) for #{prototype_prefix} Prototype model.") end end # report about non standard space types if non_tagged_space_types.size > 0 impacted_floor_area = non_tagged_space_types.sum building_area = @model.getBuilding.floorArea check_elems << OpenStudio::Attribute.new('flag', "Unexpected standard building/space types found for #{non_tagged_space_types.size} space types covering #{(100.0 * impacted_floor_area/building_area).round}% of floor area, can't provide comparisons for internal loads for those space types.") end # warn if there are spaces in model that don't use space type unless they appear to be plenums @model.getSpaces.each do |space| next if OpenstudioStandards::Space.space_plenum?(space) if !space.spaceType.is_initialized check_elems << OpenStudio::Attribute.new('flag', "#{space.name} doesn't have a space type assigned, can't validate internal loads.") end end # TODO: - need to address internal loads where fuel is variable like cooking and laundry # todo - For now we are not going to loop through spaces looking for loads beyond what comes from space type # todo - space infiltration end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_mech_sys_capacity(category, options, target_standard, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/measures/generic_qaqc/resources/check_mech_sys_capacity.rb', line 10 def check_mech_sys_capacity(category, , target_standard, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Capacity') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check HVAC capacity against ASHRAE rules of thumb for chiller max flow rate, air loop max flow rate, air loop cooling capciaty, and zone heating capcaity. Zone heating check will skip thermal zones without any exterior exposure, and thermal zones that are not conditioned.') check_elems << OpenStudio::Attribute.new('min_pass', "Var") check_elems << OpenStudio::Attribute.new('max_pass', "Var") # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # check max flow rate of chillers in model @model.getPlantLoops.sort.each do |plant_loop| # next if no chiller on plant loop chillers = [] plant_loop.supplyComponents.each do |sc| if sc.to_ChillerElectricEIR.is_initialized chillers << sc.to_ChillerElectricEIR.get end end next if chillers.empty? # gather targets for chiller capacity chiller_max_flow_rate_target = ['chiller_max_flow_rate']['target'] chiller_max_flow_rate_fraction_min = ['chiller_max_flow_rate']['min'] chiller_max_flow_rate_fraction_max = ['chiller_max_flow_rate']['max'] chiller_max_flow_rate_units_ip = ['chiller_max_flow_rate']['units'] # gal/ton*min # string above or display only, for converstion 12000 Btu/h per ton # get capacity of loop (not individual chiller but entire loop) if use_old_gem_code total_cooling_capacity_w = plant_loop.total_cooling_capacity else total_cooling_capacity_w = std.plant_loop_total_cooling_capacity(plant_loop) end total_cooling_capacity_ton = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/h').get / 12000.0 # get the max flow rate (through plant, not specific chiller) if use_old_gem_code maximum_loop_flow_rate = plant_loop.find_maximum_loop_flow_rate else maximum_loop_flow_rate = std.plant_loop_find_maximum_loop_flow_rate(plant_loop) end maximum_loop_flow_rate_ip = OpenStudio.convert(maximum_loop_flow_rate, 'm^3/s', 'gal/min').get # calculate the flow per tons of cooling model_flow_rate_per_ton_cooling_ip = maximum_loop_flow_rate_ip / total_cooling_capacity_ton # check flow rate per capacity if model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_target * (1.0 - chiller_max_flow_rate_fraction_min) check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is more than #{chiller_max_flow_rate_fraction_min * 100} % below the typical value of #{chiller_max_flow_rate_target.round(2)} #{chiller_max_flow_rate_units_ip}.") elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_target * (1.0 + chiller_max_flow_rate_fraction_max) check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is more than #{chiller_max_flow_rate_fraction_max * 100} % above the typical value of #{chiller_max_flow_rate_target.round(2)} #{chiller_max_flow_rate_units_ip}.") end end # loop through air loops to get max flor rate and cooling capacity. @model.getAirLoopHVACs.sort.each do |air_loop| # TODO: - check if DOAS, don't check airflow or cooling capacity if it is (why not check OA for DOAS? would it be different target) # gather argument options for air_loop_max_flow_rate checks air_loop_max_flow_rate_target = ['air_loop_max_flow_rate']['target'] air_loop_max_flow_rate_fraction_min = ['air_loop_max_flow_rate']['min'] air_loop_max_flow_rate_fraction_max = ['air_loop_max_flow_rate']['max'] air_loop_max_flow_rate_units_ip = ['air_loop_max_flow_rate']['units'] air_loop_max_flow_rate_units_si = 'm^3/m^2*s' # get values from model for air loop checks if use_old_gem_code floor_area_served = air_loop.floor_area_served # m^2 else floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) # m^2 end if use_old_gem_code design_supply_air_flow_rate = air_loop.find_design_supply_air_flow_rate # m^3/s else design_supply_air_flow_rate = std.air_loop_hvac_find_design_supply_air_flow_rate(air_loop) # m^3/s end # check max flow rate of air loops in the model model_normalized_flow_rate_si = design_supply_air_flow_rate / floor_area_served model_normalized_flow_rate_ip = OpenStudio.convert(model_normalized_flow_rate_si, air_loop_max_flow_rate_units_si, air_loop_max_flow_rate_units_ip).get if model_normalized_flow_rate_ip < air_loop_max_flow_rate_target * (1.0 - air_loop_max_flow_rate_fraction_min) check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is more than #{air_loop_max_flow_rate_fraction_min * 100} % below the typical value of #{air_loop_max_flow_rate_target.round(2)} #{air_loop_max_flow_rate_units_ip}.") elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_target * (1.0 + air_loop_max_flow_rate_fraction_max) check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is more than #{air_loop_max_flow_rate_fraction_max * 100} % above the typical value of #{air_loop_max_flow_rate_target.round(2)} #{air_loop_max_flow_rate_units_ip}.") end end # loop through air loops to get max flor rate and cooling capacity. @model.getAirLoopHVACs.sort.each do |air_loop| # check if DOAS, don't check airflow or cooling capacity if it is sizing_system = air_loop.sizingSystem next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement' # gather argument options for air_loop_cooling_capacity checks air_loop_cooling_capacity_target = ['air_loop_cooling_capacity']['target'] air_loop_cooling_capacity_fraction_min = ['air_loop_cooling_capacity']['min'] air_loop_cooling_capacity_fraction_max = ['air_loop_cooling_capacity']['max'] air_loop_cooling_capacity_units_ip = ['air_loop_cooling_capacity']['units'] # tons/ft^2 # string above or display only, for converstion 12000 Btu/h per ton air_loop_cooling_capacity_units_si = 'W/m^2' # get values from model for air loop checks if use_old_gem_code floor_area_served = air_loop.floor_area_served # m^2 else floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) # m^2 end if use_old_gem_code capacity = air_loop.total_cooling_capacity # W else capacity = std.air_loop_hvac_total_cooling_capacity(air_loop) # W end # check cooling capacity of air loops in the model model_normalized_capacity_si = capacity / floor_area_served model_normalized_capacity_ip = OpenStudio.convert(model_normalized_capacity_si, air_loop_cooling_capacity_units_si, 'Btu/ft^2*h').get / 12000.0 # hard coded to get tons from Btu/h # want to display in tons/ft^2 so invert number and display for checks model_tons_per_area_ip = 1.0 / model_normalized_capacity_ip target_tons_per_area_ip = 1.0 / air_loop_cooling_capacity_target inverted_units = 'ft^2/ton' if model_tons_per_area_ip < target_tons_per_area_ip * (1.0 - air_loop_cooling_capacity_fraction_max) check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{inverted_units} for #{air_loop.name.get} is more than #{air_loop_cooling_capacity_fraction_max * 100} % below the typical value of #{target_tons_per_area_ip.round} #{inverted_units}.") elsif model_tons_per_area_ip > target_tons_per_area_ip * (1.0 + air_loop_cooling_capacity_fraction_min) check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{inverted_units} for #{air_loop.name.get} is more than #{air_loop_cooling_capacity_fraction_min * 100} % above the typical value of #{target_tons_per_area_ip.round} #{inverted_units}.") end end # check heating capacity of thermal zones in the model with exterior exposure report_name = 'HVACSizingSummary' table_name = 'Zone Sensible Heating' column_name = 'User Design Load per Area' target = ['zone_heating_capacity']['target'] fraction_min = ['zone_heating_capacity']['min'] fraction_max = ['zone_heating_capacity']['max'] units_ip = ['zone_heating_capacity']['units'] units_si = 'W/m^2' @model.getThermalZones.sort.each do |thermal_zone| next if thermal_zone.canBePlenum next if thermal_zone.exteriorSurfaceArea == 0.0 query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{thermal_zone.name.get.upcase}' and ColumnName= '#{column_name}'" results = @sql.execAndReturnFirstDouble(query) # W/m^2 model_zone_heating_capacity_ip = OpenStudio.convert(results.to_f, units_si, units_ip).get # check actual against target if model_zone_heating_capacity_ip < target * (1.0 - fraction_min) check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is more than #{fraction_min * 100} % below the typical value of #{target.round(2)} Btu/ft^2*h.") elsif model_zone_heating_capacity_ip > target * (1.0 + fraction_max) check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is more than #{fraction_max * 100} % above the typical value of #{target.round(2)} Btu/ft^2*h.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_mech_sys_efficiency(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/measures/generic_qaqc/resources/check_mech_sys_efficiency.rb', line 10 def check_mech_sys_efficiency(category, target_standard, min_pass, max_pass, name_only = false) component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed', 'BoilerHotWater', 'FanConstantVolume', 'FanVariableVolume', 'PumpConstantSpeed', 'PumpVariableSpeed'] # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Efficiency') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) if target_standard.include?('90.1-2013') display_standard = "ASHRAE #{target_standard}" check_elems << OpenStudio::Attribute.new('description', "Check against #{display_standard} Tables 6.8.1 A-K for the following component types: #{component_type_array.join(', ')}.") else # TODO: - could add more elsifs if want to dsiplay tables and sections for additional 90.1 standards if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end check_elems << OpenStudio::Attribute.new('description', "Check against #{display_standard} for the following component types: #{component_type_array.join(', ')}.") end # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # check ChillerElectricEIR objects (will also have curve check in different script) @model.getChillerElectricEIRs.each do |component| # eff values from model reference_COP = component.referenceCOP # get eff values from standards (if name doesn't have expected strings find object returns first object of multiple) if use_old_gem_code standard_minimum_full_load_efficiency = component.standard_minimum_full_load_efficiency(target_standard) else standard_minimum_full_load_efficiency = std.chiller_electric_eir_standard_minimum_full_load_efficiency(component) end # check actual against target if standard_minimum_full_load_efficiency.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target full load efficiency for #{component.name}.") elsif reference_COP < standard_minimum_full_load_efficiency * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_full_load_efficiency.round(2)}.") elsif reference_COP > standard_minimum_full_load_efficiency * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_full_load_efficiency.round(2)}.") end end # check CoilCoolingDXSingleSpeed objects (will also have curve check in different script) @model.getCoilCoolingDXSingleSpeeds.each do |component| # eff values from model rated_COP = component.ratedCOP.to_f # get eff values from standards if use_old_gem_code standard_minimum_cop = component.standard_minimum_cop(target_standard) else standard_minimum_cop = std.coil_cooling_dx_single_speed_standard_minimum_cop(component) end # check actual against target if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_COP < standard_minimum_cop * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") elsif rated_COP > standard_minimum_cop * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") end end # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script) @model.getCoilCoolingDXTwoSpeeds.each do |component| # eff values from model rated_high_speed_COP = component.ratedHighSpeedCOP.to_f rated_low_speed_COP = component.ratedLowSpeedCOP.to_f # get eff values from standards if use_old_gem_code standard_minimum_cop = component.standard_minimum_cop(target_standard) else standard_minimum_cop = std.coil_cooling_dx_two_speed_standard_minimum_cop(component) end # check actual against target if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_high_speed_COP < standard_minimum_cop * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") elsif rated_high_speed_COP > standard_minimum_cop * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") end if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_low_speed_COP < standard_minimum_cop * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") elsif rated_low_speed_COP > standard_minimum_cop * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") end end # check CoilHeatingDXSingleSpeed objects # todo - need to test this once json file populated for this data @model.getCoilHeatingDXSingleSpeeds.each do |component| # eff values from model rated_COP = component.ratedCOP # get eff values from standards if use_old_gem_code standard_minimum_cop = component.standard_minimum_cop(target_standard) else standard_minimum_cop = std.coil_heating_dx_single_speed_standard_minimum_cop(component) end # check actual against target if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_COP < standard_minimum_cop * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_cop.round(2)} for #{display_standard}.") elsif rated_COP > standard_minimum_cop * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_cop.round(2)}. for #{display_standard}") end end # check BoilerHotWater @model.getBoilerHotWaters.each do |component| # eff values from model nominal_thermal_efficiency = component.nominalThermalEfficiency # get eff values from standards if use_old_gem_code standard_minimum_thermal_efficiency = component.standard_minimum_thermal_efficiency(target_standard) else standard_minimum_thermal_efficiency = std.boiler_hot_water_standard_minimum_thermal_efficiency(component) end # check actual against target if component.autosizedNominalCapacity.get == 0 check_elems << OpenStudio::Attribute.new('flag', "Boiler named #{component.name} has a capacity of 0.") elsif standard_minimum_thermal_efficiency.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target thermal efficiency for #{component.name}.") elsif nominal_thermal_efficiency < standard_minimum_thermal_efficiency * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_thermal_efficiency.round(2)} for #{display_standard}.") elsif nominal_thermal_efficiency > standard_minimum_thermal_efficiency * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_thermal_efficiency.round(2)} for #{display_standard}.") end end # check FanConstantVolume @model.getFanConstantVolumes.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards if use_old_gem_code motor_bhp = component.brake_horsepower else motor_bhp = std.fan_brake_horsepower(component) end if use_old_gem_code standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0] else standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] end # check actual against target if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") end end # check FanVariableVolume @model.getFanVariableVolumes.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards if use_old_gem_code motor_bhp = component.brake_horsepower else motor_bhp = std.fan_brake_horsepower(component) end if use_old_gem_code standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0] else standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] end # check actual against target if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") end end # check PumpConstantSpeed @model.getPumpConstantSpeeds.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards if use_old_gem_code motor_bhp = component.brake_horsepower else motor_bhp = std.pump_brake_horsepower(component) end next if motor_bhp == 0.0 if use_old_gem_code standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0] else standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] end # check actual against target if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") end end # check PumpVariableSpeed @model.getPumpVariableSpeeds.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards if use_old_gem_code motor_bhp = component.brake_horsepower else motor_bhp = std.pump_brake_horsepower(component) end next if motor_bhp == 0.0 if use_old_gem_code standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0] else standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] end # check actual against target if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.") end end # TODO: - should I throw flag if any other component types are in the model # BasicOfficeTest_Mueller.osm test model current exercises the following component types # (CoilCoolingDXTwoSpeed,FanVariableVolume,PumpConstantSpeed) # BasicOfficeTest_Mueller_altHVAC_a checks these component types # (ChillerElectricEIR,CoilCoolingDXSingleSpeed,CoilHeatingDXSingleSpeed,BoilerHotWater,FanConstantVolume,PumpVariableSpeed) rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_mech_sys_part_load_eff(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/measures/generic_qaqc/resources/check_mech_sys_part_load_eff.rb', line 12 def check_mech_sys_part_load_eff(category, target_standard, min_pass, max_pass, name_only = false) if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed'] # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Part Load Efficiency') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', "Check 40% and 80% part load efficency against #{display_standard} for the following compenent types: #{component_type_array.join(', ')}. Checking EIR Function of Part Load Ratio curve for chiller and EIR Function of Flow Fraction for DX coils.") check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # TODO: - add in check for VAV fan # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # TODO: - in future would be nice to dynamically genrate list of possible options from standards json chiller_air_cooled_condenser_types = ['WithCondenser', 'WithoutCondenser'] chiller_water_cooled_compressor_types = ['Reciprocating', 'Scroll', 'Rotary Screw', 'Centrifugal'] absorption_types = ['Single Effect', 'Double Effect Indirect Fired', 'Double Effect Direct Fired'] # check getChillerElectricEIRs objects (will also have curve check in different script) @model.getChillerElectricEIRs.each do |component| # get curve and evaluate electric_input_to_cooling_output_ratio_function_of_PLR = component.electricInputToCoolingOutputRatioFunctionOfPLR curve_40_pct = electric_input_to_cooling_output_ratio_function_of_PLR.evaluate(0.4) curve_80_pct = electric_input_to_cooling_output_ratio_function_of_PLR.evaluate(0.8) # find ac properties if use_old_gem_code search_criteria = component.find_search_criteria(target_standard) else search_criteria = std.chiller_electric_eir_find_search_criteria(component) end # extend search_criteria for absorption_type absorption_types.each do |absorption_type| if component.name.to_s.include?(absorption_type) search_criteria['absorption_type'] = absorption_type next end end # extend search_criteria for condenser type or compressor type if search_criteria['cooling_type'] == 'AirCooled' chiller_air_cooled_condenser_types.each do |condenser_type| if component.name.to_s.include?(condenser_type) search_criteria['condenser_type'] = condenser_type next end end # if no match and also no absorption_type then issue warning if !search_criteria.key?('condenser_type') || search_criteria['condenser_type'].nil? if !search_criteria.key?('absorption_type') || search_criteria['absorption_type'].nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find unique search criteria for #{component.name}. #{search_criteria}") next # don't go past here end end elsif search_criteria['cooling_type'] == 'WaterCooled' chiller_air_cooled_condenser_types.each do |compressor_type| if component.name.to_s.include?(compressor_type) search_criteria['compressor_type'] = compressor_type next end end # if no match and also no absorption_type then issue warning if !search_criteria.key?('compressor_type') || search_criteria['compressor_type'].nil? if !search_criteria.key?('absorption_type') || search_criteria['absorption_type'].nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find unique search criteria for #{component.name}. #{search_criteria}") next # don't go past here end end end # lookup chiller if use_old_gem_code capacity_w = component.find_capacity else capacity_w = std.chiller_electric_eir_find_capacity(component) end capacity_tons = OpenStudio.convert(capacity_w, 'W', 'ton').get if use_old_gem_code chlr_props = component.model.find_object($os_standards['chillers'], search_criteria, capacity_tons, Date.today) else chlr_props = std.model_find_object(std.standards_data['chillers'], search_criteria, capacity_tons, Date.today) end if chlr_props.nil? check_elems << OpenStudio::Attribute.new('flag', "Didn't find chiller for #{component.name}. #{search_criteria}") next # don't go past here in loop if can't find curve end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve target_curve_name = chlr_props['eirfplr'] if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target eirfplr curve for #{component.name}") next # don't go past here in loop if can't find curve end if use_old_gem_code temp_curve = model_temp.add_curve(target_curve_name) else temp_curve = std.model_add_curve(model_temp, target_curve_name) end target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") end end # check getCoilCoolingDXSingleSpeeds objects (will also have curve check in different script) @model.getCoilCoolingDXSingleSpeeds.each do |component| # get curve and evaluate eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionOfFlowFractionCurve curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4) curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8) # find ac properties if use_old_gem_code search_criteria = component.find_search_criteria(target_standard) else search_criteria = std.coil_dx_find_search_criteria(component) end if use_old_gem_code capacity_w = component.find_capacity else capacity_w = std.coil_cooling_dx_single_speed_find_capacity(component) end capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get if use_old_gem_code if component.heat_pump? ac_props = component.model.find_object($os_standards['heat_pumps'], search_criteria, capacity_btu_per_hr, Date.today) else ac_props = component.model.find_object($os_standards['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) end else if std.coil_dx_heat_pump?(component) ac_props = std.model_find_object(std.standards_data['heat_pumps'], search_criteria, capacity_btu_per_hr, Date.today) else ac_props = std.model_find_object(std.standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) end end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve target_curve_name = ac_props['cool_eir_fflow'] if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target cool_eir_fflow curve for #{component.name}") next # don't go past here in loop if can't find curve end if use_old_gem_code temp_curve = model_temp.add_curve(target_curve_name) else temp_curve = std.model_add_curve(model_temp, target_curve_name) end target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") end end # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script) @model.getCoilCoolingDXTwoSpeeds.each do |component| # get curve and evaluate eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionOfFlowFractionCurve curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4) curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8) # find ac properties if use_old_gem_code search_criteria = component.find_search_criteria(target_standard) else search_criteria = std.coil_dx_find_search_criteria(component) end if use_old_gem_code capacity_w = component.find_capacity else capacity_w = std.coil_cooling_dx_two_speed_find_capacity(component) end capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get if use_old_gem_code ac_props = component.model.find_object($os_standards['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) else ac_props = std.model_find_object(std.standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve target_curve_name = ac_props['cool_eir_fflow'] if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target cool_eir_flow curve for #{component.name}") next # don't go past here in loop if can't find curve end if use_old_gem_code temp_curve = model_temp.add_curve(target_curve_name) else temp_curve = std.model_add_curve(model_temp, target_curve_name) end target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") end end # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script) @model.getCoilHeatingDXSingleSpeeds.each do |component| # get curve and evaluate eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionofFlowFractionCurve # why lowercase of here but not in CoilCoolingDX objects curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4) curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8) # find ac properties if use_old_gem_code search_criteria = component.find_search_criteria(target_standard) else search_criteria = std.coil_dx_find_search_criteria(component) end if use_old_gem_code capacity_w = component.find_capacity else capacity_w = std.coil_heating_dx_single_speed_find_capacity(component) end capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get if use_old_gem_code ac_props = component.model.find_object($os_standards['heat_pumps_heating'], search_criteria, capacity_btu_per_hr, Date.today) else ac_props = std.model_find_object(std.standards_data['heat_pumps_heating'], search_criteria, capacity_btu_per_hr, Date.today) end if ac_props.nil? target_curve_name = nil else target_curve_name = ac_props['heat_eir_fflow'] end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target curve for #{component.name}") next # don't go past here in loop if can't find curve end if use_old_gem_code temp_curve = model_temp.add_curve(target_curve_name) else temp_curve = std.model_add_curve(model_temp, target_curve_name) end # Ensure that the curve was found in standards before attempting to evaluate if temp_curve.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find coefficients of curve called #{target_curve_name} for #{component.name}, cannot check part-load performance.") next end target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") end end # check @model.getFanVariableVolumes.each do |component| # skip if not on multi-zone system. if component.airLoopHVAC.is_initialized airloop = component.airLoopHVAC.get next unless airloop.thermalZones.size > 1.0 end # skip of brake horsepower is 0 if use_old_gem_code next if component.brake_horsepower == 0.0 else next if std.fan_brake_horsepower(component) == 0.0 end # temp model for use by temp model and target curve model_temp = OpenStudio::Model::Model.new # get coeficents for fan model_fan_coefs = [] model_fan_coefs << component.fanPowerCoefficient1.get model_fan_coefs << component.fanPowerCoefficient2.get model_fan_coefs << component.fanPowerCoefficient3.get model_fan_coefs << component.fanPowerCoefficient4.get model_fan_coefs << component.fanPowerCoefficient5.get # make model curve model_curve = OpenStudio::Model::CurveQuartic.new(model_temp) model_curve.setCoefficient1Constant(model_fan_coefs[0]) model_curve.setCoefficient2x(model_fan_coefs[1]) model_curve.setCoefficient3xPOW2(model_fan_coefs[2]) model_curve.setCoefficient4xPOW3(model_fan_coefs[3]) model_curve.setCoefficient5xPOW4(model_fan_coefs[4]) curve_40_pct = model_curve.evaluate(0.4) curve_80_pct = model_curve.evaluate(0.8) # get target coefs target_fan = OpenStudio::Model::FanVariableVolume.new(model_temp) if use_old_gem_code target_fan.set_control_type('Multi Zone VAV with Static Pressure Reset') else std.fan_variable_volume_set_control_type(target_fan, 'Multi Zone VAV with VSD and Static Pressure Reset') end # get coeficents for fan target_fan_coefs = [] target_fan_coefs << target_fan.fanPowerCoefficient1.get target_fan_coefs << target_fan.fanPowerCoefficient2.get target_fan_coefs << target_fan.fanPowerCoefficient3.get target_fan_coefs << target_fan.fanPowerCoefficient4.get target_fan_coefs << target_fan.fanPowerCoefficient5.get # make model curve target_curve = OpenStudio::Model::CurveQuartic.new(model_temp) target_curve.setCoefficient1Constant(target_fan_coefs[0]) target_curve.setCoefficient2x(target_fan_coefs[1]) target_curve.setCoefficient3xPOW2(target_fan_coefs[2]) target_curve.setCoefficient4xPOW3(target_fan_coefs[3]) target_curve.setCoefficient5xPOW4(target_fan_coefs[4]) target_curve_40_pct = target_curve.evaluate(0.4) target_curve_80_pct = target_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{display_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{display_standard}.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_mech_sys_type(category, target_standard, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/measures/generic_qaqc/resources/check_mech_sys_type.rb', line 10 def check_mech_sys_type(category, target_standard, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Baseline Mechanical System Type') check_elems << OpenStudio::Attribute.new('category', category) # add ASHRAE to display of target standard if includes with 90.1 if target_standard.include?('90.1 2013') check_elems << OpenStudio::Attribute.new('description', 'Check against ASHRAE 90.1 2013 Tables G3.1.1 A-B. Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.') else check_elems << OpenStudio::Attribute.new('description', 'Check against ASHRAE 90.1. Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.') end # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # Get the actual system type for all zones in the model act_zone_to_sys_type = {} @model.getThermalZones.each do |zone| if use_old_gem_code act_zone_to_sys_type[zone] = zone.infer_system_type else act_zone_to_sys_type[zone] = std.thermal_zone_infer_system_type(zone) end end # Get the baseline system type for all zones in the model if use_old_gem_code climate_zone = @model.get_building_climate_zone_and_building_type['climate_zone'] else if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') climate_zone = std.model_get_building_properties(@model)['climate_zone'] else climate_zone = std.model_get_building_climate_zone_and_building_type(@model)['climate_zone'] end end if use_old_gem_code req_zone_to_sys_type = @model.get_baseline_system_type_by_zone(target_standard, climate_zone) else req_zone_to_sys_type = std.model_get_baseline_system_type_by_zone(@model, climate_zone) end # Compare the actual to the correct @model.getThermalZones.each do |zone| # TODO: - skip if plenum is_plenum = false zone.spaces.each do |space| if use_old_gem_code if space.plenum? is_plenum = true end else if OpenstudioStandards::Space.space_plenum?(space) is_plenum = true end end end next if is_plenum req_sys_type = req_zone_to_sys_type[zone] act_sys_type = act_zone_to_sys_type[zone] if act_sys_type == req_sys_type puts "#{zone.name} system type = #{act_sys_type}" else if req_sys_type == '' then req_sys_type = 'Unknown' end puts "#{zone.name} baseline system type is incorrect. Supposed to be #{req_sys_type}, but was #{act_sys_type} instead." check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the baseline system type would be #{req_sys_type}; the current system type is #{act_sys_type}.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_part_loads(category, target_standard, max_pct_delta = 0.1, name_only = false) ⇒ Object
Check primary heating and cooling equipment part load ratios to find equipment that is significantly oversized or undersized.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/measures/generic_qaqc/resources/check_part_loads.rb', line 46 def check_part_loads(category, target_standard, max_pct_delta = 0.1, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Part Load') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that equipment operates at reasonable part load ranges.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Establish limits for % of operating hrs expected above 90% part load expected_pct_hrs_above_90 = 0.1 # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod') ann_env_pd = env_pd break end end end # only try to get the annual timeseries if an annual simulation was run if ann_env_pd.nil? check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.') return check_elem end # Boilers @model.getBoilerHotWaters.each do |equip| # Get the timeseries part load ratio data key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Boiler Part Load Ratio' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # Chillers @model.getChillerElectricEIRs.each do |equip| # Get the timeseries part load ratio data key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Chiller Part Load Ratio' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # Cooling Towers (Single Speed) @model.getCoolingTowerSingleSpeeds.each do |equip| # Get the design fan power if equip.fanPoweratDesignAirFlowRate.is_initialized dsn_pwr = equip.fanPoweratDesignAirFlowRate.get elsif equip.autosizedFanPoweratDesignAirFlowRate.is_initialized dsn_pwr = equip.autosizedFanPoweratDesignAirFlowRate.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries fan power key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Cooling Tower Fan Electric Power' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # Cooling Towers (Two Speed) @model.getCoolingTowerTwoSpeeds.each do |equip| # Get the design fan power if equip.highFanSpeedFanPower.is_initialized dsn_pwr = equip.highFanSpeedFanPower.get elsif equip.autosizedHighFanSpeedFanPower.is_initialized dsn_pwr = equip.autosizedHighFanSpeedFanPower.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries fan power key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Cooling Tower Fan Electric Power' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # Cooling Towers (Variable Speed) @model.getCoolingTowerVariableSpeeds.each do |equip| # Get the design fan power if equip.designFanPower.is_initialized dsn_pwr = equip.designFanPower.get elsif equip.autosizedDesignFanPower.is_initialized dsn_pwr = equip.autosizedDesignFanPower.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries fan power key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Cooling Tower Fan Electric Power' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # DX Cooling Coils (Single Speed) @model.getCoilCoolingDXSingleSpeeds.each do |equip| # Get the design coil capacity if equip.ratedTotalCoolingCapacity.is_initialized dsn_pwr = equip.ratedTotalCoolingCapacity.get elsif equip.autosizedRatedTotalCoolingCapacity.is_initialized dsn_pwr = equip.autosizedRatedTotalCoolingCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries coil capacity key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Cooling Coil Total Cooling Rate' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # DX Cooling Coils (Two Speed) @model.getCoilCoolingDXTwoSpeeds.each do |equip| # Get the design coil capacity if equip.ratedHighSpeedTotalCoolingCapacity.is_initialized dsn_pwr = equip.ratedHighSpeedTotalCoolingCapacity.get elsif equip.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized dsn_pwr = equip.autosizedRatedHighSpeedTotalCoolingCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries coil capacity key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Cooling Coil Total Cooling Rate' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # DX Cooling Coils (Variable Speed) @model.getCoilCoolingDXVariableSpeeds.each do |equip| # Get the design coil capacity if equip.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.is_initialized dsn_pwr = equip.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.get elsif equip.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.is_initialized dsn_pwr = equip.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries coil capacity key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Cooling Coil Total Cooling Rate' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end # Gas Heating Coils @model.getCoilHeatingGass.each do |equip| # Get the design coil capacity if equip.nominalCapacity.is_initialized dsn_pwr = equip.nominalCapacity.get elsif equip.autosizedNominalCapacity.is_initialized dsn_pwr = equip.autosizedNominalCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{equip.name}, cannot check part load ratios.") next end # Get the timeseries coil capacity key_value = equip.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' variable_name = 'Heating Coil Air Heating Rate' ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? check_elems << OpenStudio::Attribute.new('flag', "#{variable_name} Timeseries not found for #{key_value}.") next end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << ts[i] / dsn_pwr.to_f end # Bin part load ratios pct_hrs_above_90 = bin_part_loads_by_ten_pcts(plrs)[9] # Check top-end part load ratio bins if ((pct_hrs_above_90 - expected_pct_hrs_above_90) / pct_hrs_above_90).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{equip.name}, the actual hrs above 90% part load of #{(pct_hrs_above_90 * 100).round(2)}% is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{(expected_pct_hrs_above_90 * 100).round(2)}% of hrs above 90% part load. This could indicate significantly oversized or undersized equipment.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_placeholder(category, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/measures/generic_qaqc/resources/check_placeholder.rb', line 10 def check_placeholder(category, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Place Holder Check') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'This does nothing, it will just throw a flag until I add real check code to the method.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end begin # TODO: - implement QAQC check code here # remove this once code is written to do real checks check_elems << OpenStudio::Attribute.new('flag', 'Code has not been implemented yet for this QAQC check') rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_plant_cap(category, target_standard, max_pct_delta = 0.1, name_only = false) ⇒ Object
Check primary plant loop heating and cooling equipment capacity against coil loads to find equipment that is significantly oversized or undersized.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/measures/generic_qaqc/resources/check_plant_cap.rb', line 9 def check_plant_cap(category, target_standard, max_pct_delta = 0.1, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Plant Capacity') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that plant equipment capacity matches loads.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check the heating and cooling capacity of the plant loops against their coil loads @model.getPlantLoops.each do |plant_loop| # Heating capacity htg_cap_w = std.plant_loop_total_heating_capacity(plant_loop) # Cooling capacity clg_cap_w = std.plant_loop_total_cooling_capacity(plant_loop) # Heating and cooling loads plant_loop.demandComponents.each do |dc| # Get the load for each coil htg_load_w = 0.0 clg_load_w = 0.0 obj_type = sc.iddObjectType.valueName.to_s case obj_type when 'OS_Coil_Heating_Water' coil = sc.to_CoilHeatingWater.get if coil.ratedCapacity.is_initialized clg_load_w += coil.ratedCapacity.get elsif coil.autosizedRatedCapacity.is_initialized clg_load_w += coil.autosizedRatedCapacity.get end when 'OS_Coil_Cooling_Water' coil = sc.to_CoilCoolingWater.get if coil.autosizedDesignCoilLoad.is_initialized clg_load_w += coil.autosizedDesignCoilLoad.get end end end # Don't check loops with no loads. These are probably # SWH or non-typical loops that can't be checked by simple methods. # Heating if htg_load_w > 0 htg_cap_kbtu_per_hr = OpenStudio.convert(htg_cap_w, 'W', 'kBtu/hr').get.round(1) htg_load_kbtu_per_hr = OpenStudio.convert(htg_load_w, 'W', 'kBtu/hr').get.round(1) if ((htg_cap_w - htg_load_w) / htg_cap_w).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the total heating capacity of #{htg_cap_kbtu_per_hr} kBtu/hr is more than #{(max_pct_delta * 100.0).round(2)}% different from the combined coil load of #{htg_load_kbtu_per_hr} kBtu/hr. This could indicate significantly oversized or undersized equipment.") end end # Cooling if clg_load_w > 0 clg_cap_tons = OpenStudio.convert(clg_cap_w, 'W', 'ton').get.round(1) clg_load_tons = OpenStudio.convert(clg_load_w, 'W', 'ton').get.round(1) if ((clg_cap_w - clg_load_w) / clg_cap_w).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the total cooling capacity of #{clg_cap_kbtu_per_hr} tons is more than #{(max_pct_delta * 100.0).round(2)}% different from the combined coil load of #{clg_load_kbtu_per_hr} tons. This could indicate significantly oversized or undersized equipment.") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_plant_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false) ⇒ Object
Check the plant loop operational vs. sizing temperatures and make sure everything is coordinated. This identifies problems caused by sizing to one set of conditions and operating at a different set.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/measures/generic_qaqc/resources/check_plant_temps.rb', line 10 def check_plant_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Plant Loop Temperatures') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that plant loop sizing and operation temperatures are coordinated.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check each plant loop in the model @model.getPlantLoops.sort.each do |plant_loop| loop_name = plant_loop.name.to_s # Get the central heating and cooling SAT for sizing sizing_plant = plant_loop.sizingPlant loop_siz_f = OpenStudio.convert(sizing_plant.designLoopExitTemperature, 'C', 'F').get # Determine the min and max operational temperatures loop_op_min_f = nil loop_op_max_f = nil plant_loop.supplyOutletNode.setpointManagers.each do |spm| obj_type = spm.iddObjectType.valueName.to_s case obj_type when 'OS_SetpointManager_Scheduled' sch = spm.to_SetpointManagerScheduled.get.schedule if sch.to_ScheduleRuleset.is_initialized min_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(sch.to_ScheduleRuleset.get)['min'] max_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(sch.to_ScheduleRuleset.get)['max'] elsif sch.to_ScheduleConstant.is_initialized min_c = std.schedule_constant_annual_min_max_value(sch.to_ScheduleConstant.get)['min'] max_c = std.schedule_constant_annual_min_max_value(sch.to_ScheduleConstant.get)['max'] else next end loop_op_min_f = OpenStudio.convert(min_c, 'C', 'F').get loop_op_max_f = OpenStudio.convert(max_c, 'C', 'F').get when 'OS_SetpointManager_Scheduled_DualSetpoint' spm = spm.to_SetpointManagerSingleZoneReheat.get # Lowest setpoint is minimum of low schedule low_sch = spm.to_SetpointManagerScheduled.get.lowSetpointSchedule next if low_sch.empty? low_sch = low_sch.get if low_sch.to_ScheduleRuleset.is_initialized min_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(low_sch.to_ScheduleRuleset.get)['min'] max_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(low_sch.to_ScheduleRuleset.get)['max'] elsif low_sch.to_ScheduleConstant.is_initialized min_c = std.schedule_constant_annual_min_max_value(low_sch.to_ScheduleConstant.get)['min'] max_c = std.schedule_constant_annual_min_max_value(low_sch.to_ScheduleConstant.get)['max'] else next end loop_op_min_f = OpenStudio.convert(min_c, 'C', 'F').get # Highest setpoint it maximum of high schedule high_sch = spm.to_SetpointManagerScheduled.get.highSetpointSchedule next if high_sch.empty? high_sch = high_sch.get if high_sch.to_ScheduleRuleset.is_initialized min_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(high_sch.to_ScheduleRuleset.get)['min'] max_c = openstudiostandards::schedules.schedule_ruleset_annual_min_max_value(high_sch.to_ScheduleRuleset.get)['max'] elsif high_sch.to_ScheduleConstant.is_initialized min_c = std.schedule_constant_annual_min_max_value(high_sch.to_ScheduleConstant.get)['min'] max_c = std.schedule_constant_annual_min_max_value(high_sch.to_ScheduleConstant.get)['max'] else next end loop_op_max_f = OpenStudio.convert(max_c, 'C', 'F').get when 'OS_SetpointManager_OutdoorAirReset' spm = spm.to_SetpointManagerOutdoorAirReset.get temp_1_f = OpenStudio.convert(spm.setpointatOutdoorHighTemperature, 'C', 'F').get temp_2_f = OpenStudio.convert(spm.setpointatOutdoorLowTemperature, 'C', 'F').get loop_op_min_f = [temp_1_f, temp_2_f].min loop_op_max_f = [temp_1_f, temp_2_f].max else next # Only check the commonly used setpoint managers end end # Compare plant loop sizing temperatures to operational temperatures case sizing_plant.loopType when 'Heating' if loop_op_max_f if ((loop_op_max_f - loop_siz_f) / loop_op_max_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the sizing is done with a supply water temp of #{loop_siz_f.round(2)}F, but the setpoint manager controlling the loop operates up to #{loop_op_max_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end end when 'Cooling' if loop_op_min_f if ((loop_op_min_f - loop_siz_f) / loop_op_min_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the sizing is done with a supply water temp of #{loop_siz_f.round(2)}F, but the setpoint manager controlling the loop operates down to #{loop_op_min_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.") end end when 'Condenser' # Not checking sizing of condenser loops end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_plenum_loads(category, target_standard, name_only = false) ⇒ Object
Check that there are no people or lights in plenums.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/measures/generic_qaqc/resources/check_plenum_loads.rb', line 8 def check_plenum_loads(category, target_standard, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Plenum Loads') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that the plenums do not have people or lights.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin @model.getThermalZones.each do |zone| # Only check plenums next unless std.thermal_zone_plenum?(zone) # People num_people = zone.numberOfPeople if num_people > 0 check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} is a plenum, but has #{num_people.round(1)} people. Plenums should not contain people.") end # Lights lights_w = zone.lightingPower if lights_w > 0 check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} is a plenum, but has #{lights_w.round(1)} W of lights. Plenums should not contain lights.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_pump_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false) ⇒ Object
Check the pumping power (W/gpm) for each pump in the model to identify unrealistically sized pumps.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/measures/generic_qaqc/resources/check_pump_pwr.rb', line 9 def check_pump_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Pump Power') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that pump power vs flow makes sense.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check each plant loop @model.getPlantLoops.each do |plant_loop| # Set the expected/typical W/gpm loop_type = plant_loop.sizingPlant.loopType case loop_type when 'Heating' expected_w_per_gpm = 19.0 when 'Cooling' expected_w_per_gpm = 22.0 when 'Condenser' expected_w_per_gpm = 19.0 end # Check the W/gpm for each pump on each plant loop plant_loop.supplyComponents.each do |sc| # Get the W/gpm for the pump obj_type = sc.iddObjectType.valueName.to_s case obj_type when 'OS_Pump_ConstantSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(sc.to_PumpConstantSpeed.get) when 'OS_Pump_VariableSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(sc.to_PumpVariableSpeed.get) when 'OS_HeaderedPumps_ConstantSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(sc.to_HeaderedPumpsConstantSpeed.get) when 'OS_HeaderedPumps_VariableSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(sc.to_HeaderedPumpsVariableSpeed.get) else next # Skip non-pump objects end # Compare W/gpm to expected/typical values if ((expected_w_per_gpm - actual_w_per_gpm) / actual_w_per_gpm).abs > max_pwr_delta check_elems << OpenStudio::Attribute.new('flag', "For #{sc.name} on #{plant_loop.name}, the actual pumping power of #{actual_w_per_gpm.round(1)} W/gpm is more than #{(max_pwr_delta * 100.0).round(2)}% different from the expected #{expected_w_per_gpm} W/gpm for a #{loop_type} plant loop.") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_sch_coord(category, target_standard, max_hrs, name_only = false) ⇒ Object
Check that the lighting, equipment, and HVAC setpoint schedules coordinate with the occupancy schedules. This is defined as having start and end times within the specified number of hours away from the occupancy schedule.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/measures/generic_qaqc/resources/check_sch_coord.rb', line 101 def check_sch_coord(category, target_standard, max_hrs, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Conditioned Zones') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that lighting, equipment, and HVAC schedules coordinate with occupancy.') # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Convert max hr limit to OpenStudio Time max_hrs = OpenStudio::Time.new(0, max_hrs, 0, 0) # Check schedules in each space @model.getSpaces.each do |space| # Occupancy, Lighting, and Equipment Schedules coord_schs = [] occ_schs = [] # Get the space type (optional) space_type = space.spaceType # Occupancy occs = [] occs += space.people # From space directly occs += space_type.get.people if space_type.is_initialized # Inherited from space type occs.each do |occ| occ_schs << occ.numberofPeopleSchedule.get if occ.numberofPeopleSchedule.is_initialized end # Lights lts = [] lts += space.lights # From space directly lts += space_type.get.lights if space_type.is_initialized # Inherited from space type lts.each do |lt| coord_schs << lt.schedule.get if lt.schedule.is_initialized end # Equip plugs = [] plugs += space.electricEquipment # From space directly plugs += space_type.get.electricEquipment if space_type.is_initialized # Inherited from space type plugs.each do |plug| coord_schs << plug.schedule.get if plug.schedule.is_initialized end # HVAC Schedule (airloop-served zones only) if space.thermalZone.is_initialized zone = space.thermalZone.get if zone.airLoopHVAC.is_initialized coord_schs << zone.airLoopHVAC.get.availabilitySchedule end end # Cannot check spaces with no occupancy schedule to compare against next if occ_schs.empty? # Get start and end occupancy times from the first occupancy schedule occ_start_time, occ_end_time = get_start_and_end_times(occ_schs[0]) # Cannot check a space where the occupancy start time or end time cannot be determined next if occ_start_time.nil? || occ_end_time.nil? # Check all schedules against occupancy # Lights should have a start and end within X hrs of the occupancy start and end coord_schs.each do |coord_sch| # Get start and end time of load/HVAC schedule start_time, end_time = get_start_and_end_times(coord_sch) if start_time.nil? check_elems << OpenStudio::Attribute.new('flag', "Could not determine start time of a schedule called #{coord_sch.name}, cannot determine if schedule coordinates with occupancy schedule.") next elsif end_time.nil? check_elems << OpenStudio::Attribute.new('flag', "Could not determine end time of a schedule called #{coord_sch.name}, cannot determine if schedule coordinates with occupancy schedule.") next end # Check start time if (occ_start_time - start_time) > max_hrs || (start_time - occ_start_time) > max_hrs check_elems << OpenStudio::Attribute.new('flag', "The start time of #{coord_sch.name} is #{start_time}, which is more than #{max_hrs} away from the occupancy schedule start time of #{occ_start_time} for #{occ_schs[0].name} in #{space.name}. Schedules do not coordinate.") end # Check end time if (occ_end_time - end_time) > max_hrs || (end_time - occ_end_time) > max_hrs check_elems << OpenStudio::Attribute.new('flag', "The end time of #{coord_sch.name} is #{end_time}, which is more than #{max_hrs} away from the occupancy schedule end time of #{occ_end_time} for #{occ_schs[0].name} in #{space.name}. Schedules do not coordinate.") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_schedules(category, target_standard, min_pass, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/measures/generic_qaqc/resources/check_schedules.rb', line 10 def check_schedules(category, target_standard, min_pass, max_pass, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Schedules') check_elems << OpenStudio::Attribute.new('category', category) # update display sttandard if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems << OpenStudio::Attribute.new('description', 'Check schedules for lighting, ventilation, occupant density, plug loads, and equipment based on DOE reference building schedules in terms of full load hours per year.') check_elems.each do |elem| results << elem.valueAsString end return results end begin # setup standard std = Standard.build(target_standard) # gather building type for summary if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') bt_cz = std.model_get_building_properties(@model) else bt_cz = std.model_get_building_climate_zone_and_building_type(@model) end building_type = bt_cz['building_type'] # mapping to obuilding type to match space types if building_type.include?("Office") then building_type = "Office" end climate_zone = bt_cz['climate_zone'] prototype_prefix = "#{display_standard} #{building_type} #{climate_zone}" check_elems << OpenStudio::Attribute.new('description', "Check schedules for lighting, ventilation, occupant density, plug loads, and equipment based on #{prototype_prefix} DOE reference building schedules in terms of full load hours per year.") check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # gather all non statandard space types so can be listed in single flag non_tagged_space_types = [] # loop through all space types used in the model @model.getSpaceTypes.each do |space_type| next if space_type.floorArea <= 0 # load in standard info for this space type data = std.space_type_get_standards_data(space_type) if data.nil? || data.empty? # skip if all spaces using this space type are plenums all_spaces_plenums = true space_type.spaces.each do |space| if !OpenstudioStandards::Space.space_plenum?(space) all_spaces_plenums = false next end end if !all_spaces_plenums non_tagged_space_types << space_type.floorArea end next end # temp model to hold schedules to check model_temp = OpenStudio::Model::Model.new # check lighting schedules data['lighting_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['lighting_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['lighting_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['lighting_schedule']} in standards json.") else # loop through and test individual load instances target_hrs = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target.to_ScheduleRuleset.get) space_type.lights.each do |load_inst| inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) if inst_sch_check then check_elems << inst_sch_check end end end end # check electric equipment schedules data['electric_equipment_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['electric_equipment_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['electric_equipment_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['electric_equipment_schedule']} in standards json.") else # loop through and test individual load instances target_hrs = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target.to_ScheduleRuleset.get) space_type.electricEquipment.each do |load_inst| inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) if inst_sch_check then check_elems << inst_sch_check end end end end # check gas equipment schedules # todo - update measure test to with space type to check this data['gas_equipment_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['gas_equipment_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['gas_equipment_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['gas_equipment_schedule']} in standards json.") else # loop through and test individual load instances target_hrs = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target.to_ScheduleRuleset.get) space_type.gasEquipment.each do |load_inst| inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) if inst_sch_check then check_elems << inst_sch_check end end end end # check occupancy schedules data['occupancy_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['occupancy_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['occupancy_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['occupancy_schedule']} in standards json.") else # loop through and test individual load instances target_hrs = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target.to_ScheduleRuleset.get) space_type.people.each do |load_inst| inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) if inst_sch_check then check_elems << inst_sch_check end end end end # TODO: - check ventilation schedules # if objects are in the model should they just be always on schedule, or have a 8760 annual equiv value # oa_schedule should not exist, or if it does shoudl be always on or have 8760 annual equiv value if space_type.designSpecificationOutdoorAir.is_initialized oa = space_type.designSpecificationOutdoorAir.get if oa.outdoorAirFlowRateFractionSchedule.is_initialized # TODO: - update measure test to check this target_hrs = 8760 inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, oa, space_type, check_elems, min_pass, max_pass, target_standard) if inst_sch_check then check_elems << inst_sch_check end end end # notes # current logic only looks at 8760 values and not design days # when multiple instances of a type currently check every schedule by itself. In future could do weighted avg. merge # not looking at infiltration schedules # not looking at luminaires # not looking at space loads, only loads at space type # only checking schedules where standard shows non zero load value # model load for space type where standards doesn't have one wont throw flag about mis-matched schedules end # report about non standard space types if non_tagged_space_types.size > 0 impacted_floor_area = non_tagged_space_types.sum building_area = @model.getBuilding.floorArea check_elems << OpenStudio::Attribute.new('flag', "Unexpected standard building/space types found for #{non_tagged_space_types.size} space types covering #{(100 * impacted_floor_area/building_area).round}% of floor area, can't provide comparisons for schedules for those space types.") end # warn if there are spaces in model that don't use space type unless they appear to be plenums @model.getSpaces.each do |space| next if OpenstudioStandards::Space.space_plenum?(space) if !space.spaceType.is_initialized check_elems << OpenStudio::Attribute.new('flag', "#{space.name} doesn't have a space type assigned, can't validate schedules.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_simultaneous_heating_and_cooling(category, max_pass, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/measures/generic_qaqc/resources/check_simultaneous_heating_and_cooling.rb', line 10 def check_simultaneous_heating_and_cooling(category, max_pass, name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Simultaneous Heating and Cooling') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check for simultaneous heating and cooling by looping through all Single Duct VAV Reheat Air Terminals and analyzing hourly data when there is a cooling load. ') check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end begin # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod') ann_env_pd = env_pd break end end end # only try to get the annual timeseries if an annual simulation was run if ann_env_pd.nil? check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot determine simultaneous heating and cooling.') return check_elem end # For each VAV reheat terminal, calculate # the annual total % reheat hours. @model.getAirTerminalSingleDuctVAVReheats.each do |term| # Reheat coil heating rate rht_coil = term.reheatCoil key_value = rht_coil.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep" variable_name = 'Heating Coil Heating Rate' variable_name_alt = 'Heating Coil Air Heating Rate' rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it. # try and alternate variable name if rht_rate_ts.empty? rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name_alt, key_value) # key value would go at the end if we used it. end if rht_rate_ts.empty? check_elems << OpenStudio::Attribute.new('flag', "Heating Coil (Air) Heating Rate Timeseries not found for #{key_value}.") else rht_rate_ts = rht_rate_ts.get.values # Put timeseries into array rht_rate_vals = [] for i in 0..(rht_rate_ts.size - 1) rht_rate_vals << rht_rate_ts[i] end # Zone Air Terminal Sensible Heating Rate key_value = "ADU #{term.name.get.to_s.upcase}" # must be in all caps. time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep" variable_name = 'Zone Air Terminal Sensible Cooling Rate' clg_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it. if clg_rate_ts.empty? check_elems << OpenStudio::Attribute.new('flag', "Zone Air Terminal Sensible Cooling Rate Timeseries not found for #{key_value}.") else clg_rate_ts = clg_rate_ts.get.values # Put timeseries into array clg_rate_vals = [] for i in 0..(clg_rate_ts.size - 1) clg_rate_vals << clg_rate_ts[i] end # Loop through each timestep and calculate the hourly # % reheat value. ann_rht_hrs = 0 ann_clg_hrs = 0 ann_pcts = [] rht_rate_vals.zip(clg_rate_vals).each do |rht_w, clg_w| # Skip hours with no cooling (in heating mode) next if clg_w == 0 pct_overcool_rht = rht_w / (rht_w + clg_w) ann_rht_hrs += pct_overcool_rht # implied * 1hr b/c hrly results ann_clg_hrs += 1 ann_pcts << pct_overcool_rht.round(3) end # Calculate annual % reheat hours ann_pct_reheat = ((ann_rht_hrs / ann_clg_hrs) * 100).round(1) # Compare to limit if ann_pct_reheat > max_pass * 100.0 check_elems << OpenStudio::Attribute.new('flag', "#{term.name} has #{ann_pct_reheat}% overcool-reheat, which is greater than the limit of #{max_pass * 100.0}%. This terminal is in cooling mode for #{ann_clg_hrs} hours of the year.") end end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_supply_air_and_thermostat_temp_difference(category, target_standard, max_delta, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/measures/generic_qaqc/resources/check_supply_air_and_thermostat_temp_difference.rb', line 10 def check_supply_air_and_thermostat_temp_difference(category, target_standard, max_delta, name_only = false) # G3.1.2.9 requires a 20 degree F delta between supply air temperature and zone temperature. target_clg_delta = 20.0 # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Supply and Zone Air Temperature') check_elems << OpenStudio::Attribute.new('category', category) if @utility_name.nil? check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold of #{target_clg_delta}F plus the selected tolerance.") else check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold set by #{@utility_name}.") end check_elems << OpenStudio::Attribute.new('min_pass', max_delta) # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # loop through thermal zones @model.getThermalZones.sort.each do |thermal_zone| model_clg_min = nil # populate thermostat ranges if thermal_zone.thermostatSetpointDualSetpoint.is_initialized thermostat = thermal_zone.thermostatSetpointDualSetpoint.get if thermostat.coolingSetpointTemperatureSchedule.is_initialized clg_sch = thermostat.coolingSetpointTemperatureSchedule.get schedule_values = nil if clg_sch.to_ScheduleRuleset.is_initialized if use_old_gem_code schedule_values = clg_sch.to_ScheduleRuleset.get.annual_min_max_value else schedule_values = OpenstudioStandards::Schedules.schedule_get_min_max(clg_sch) end elsif clg_sch.to_ScheduleConstant.is_initialized if use_old_gem_code schedule_values = clg_sch.to_ScheduleConstant.get.annual_min_max_value else schedule_values = OpenstudioStandards::Schedules.schedule_get_min_max(clg_sch) end end unless schedule_values.nil? puts "hello1" puts schedule_values puts "hello2" model_clg_min = schedule_values['min'] end end else # go to next zone if not conditioned next end # flag if there is setpoint schedule can't be inspected (isn't ruleset) if model_clg_min.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't inspect thermostat schedules for #{thermal_zone.name}") else # get supply air temps from thermal zone sizing sizing_zone = thermal_zone.sizingZone clg_supply_air_temp = sizing_zone.zoneCoolingDesignSupplyAirTemperature # convert model values to IP model_clg_min_ip = OpenStudio.convert(model_clg_min, 'C', 'F').get clg_supply_air_temp_ip = OpenStudio.convert(clg_supply_air_temp, 'C', 'F').get # check supply air against zone temperature (only check against min setpoint, assume max is night setback) if model_clg_min_ip - clg_supply_air_temp_ip > target_clg_delta + max_delta check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) larger than the expected delta of #{target_clg_delta} (F)") elsif model_clg_min_ip - clg_supply_air_temp_ip < target_clg_delta - max_delta check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) smaller than the expected delta of #{target_clg_delta} (F)") end end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#check_weather_files(category, options, name_only = false) ⇒ Object
checks the number of unmet hours in the model
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/measures/generic_qaqc/resources/check_weather_files.rb', line 10 def check_weather_files(category, , name_only = false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Weather Files') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', "Check weather file, design days, and climate zone against #{@utility_name} list of allowable options.") # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| results << elem.valueAsString end return results end begin # get weather file model_epw = nil if @model.getWeatherFile.url.is_initialized raw_epw = @model.getWeatherFile.url.get end_path_index = raw_epw.rindex('/') model_epw = raw_epw.slice!(end_path_index + 1, raw_epw.length) # everything right of last forward slash end # check design days (model must have one or more of the required summer and winter design days) # get design days names from model model_summer_dd_names = [] model_winter_dd_names = [] @model.getDesignDays.each do |design_day| if design_day.dayType == 'SummerDesignDay' model_summer_dd_names << design_day.name.to_s elsif design_day.dayType == 'WinterDesignDay' model_winter_dd_names << design_day.name.to_s else puts "unexpected day type of #{design_day.dayType} wont' be included in check" end end # find matching weather file from options, as well as design days and climate zone if .key?(model_epw) required_summer_dd = [model_epw]['summer'] required_winter_dd = [model_epw]['winter'] valid_climate_zones = [[model_epw]['climate_zone']] # check for intersection betwen model valid design days summer_intersection = (required_summer_dd & model_summer_dd_names) winter_intersection = (required_winter_dd & model_winter_dd_names) if summer_intersection.empty? && !required_summer_dd.empty? check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected summer design days for #{model_epw}") end if winter_intersection.empty? && !required_winter_dd.empty? check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected winter design days for #{model_epw}") end else check_elems << OpenStudio::Attribute.new('flag', "#{model_epw} is not a an expected weather file.") check_elems << OpenStudio::Attribute.new('flag', "Model doesn't have expected epw file, as a result can't validate design days.") valid_climate_zones = [] .each do |lookup_epw, value| valid_climate_zones << value['climate_zone'] end end # get ashrae climate zone from model model_climate_zone = nil climateZones = @model.getClimateZones climateZones.climateZones.each do |climateZone| if climateZone.institution == 'ASHRAE' model_climate_zone = climateZone.value next end end if model_climate_zone == '' check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone has not been defined. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.") elsif !valid_climate_zones.include?(model_climate_zone) check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone was #{model_climate_zone}. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.") end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end |
#generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) ⇒ Object
code for each load instance for different load types will pass through here will return nill or a single attribute
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/measures/generic_qaqc/resources/check_schedules.rb', line 207 def generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass, target_standard) schedule_inst = nil inst_hrs = nil # setup standard std = Standard.build(target_standard) # gather building type for summary if Gem::Version.new(OpenstudioStandards::VERSION) > Gem::Version.new('0.2.16') bt_cz = std.model_get_building_properties(@model) else bt_cz = std.model_get_building_climate_zone_and_building_type(@model) end building_type = bt_cz['building_type'] climate_zone = bt_cz['climate_zone'] prototype_prefix = "#{target_standard} #{building_type} #{climate_zone}" # get schedule if (load_inst.class.to_s == 'OpenStudio::Model::People') && load_inst.numberofPeopleSchedule.is_initialized schedule_inst = load_inst.numberofPeopleSchedule.get elsif (load_inst.class.to_s == 'OpenStudio::Model::DesignSpecificationOutdoorAir') && load_inst.outdoorAirFlowRateFractionSchedule.is_initialized schedule_inst = load_inst.outdoorAirFlowRateFractionSchedule .get elsif load_inst.schedule.is_initialized schedule_inst = load_inst.schedule.get else return OpenStudio::Attribute.new('flag', "#{load_inst.name} in #{space_type.name} doesn't have a schedule assigned.") end # get annual equiv for model schedule if schedule_inst.to_ScheduleRuleset.is_initialized inst_hrs = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_inst.to_ScheduleRuleset.get) elsif schedule_inst.to_ScheduleConstant.is_initialized inst_hrs = std.schedule_constant_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleConstant.get) else return OpenStudio::Attribute.new('flag', "#{schedule_inst.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") end # check instance against target if inst_hrs < target_hrs * (1.0 - min_pass) return OpenStudio::Attribute.new('flag', "#{inst_hrs.round} annual equivalent full load hours for #{schedule_inst.name} in #{space_type.name} is more than #{min_pass * 100} (%) below the value of #{target_hrs.round} hours from the #{prototype_prefix} DOE Prototype building.") elsif inst_hrs > target_hrs * (1.0 + max_pass) return OpenStudio::Attribute.new('flag', "#{inst_hrs.round} annual equivalent full load hours for #{schedule_inst.name} in #{space_type.name} is more than #{max_pass * 100} (%) above the value of #{target_hrs.round} hours from the #{prototype_prefix} DOE Prototype building.") end # will get to this if no flag was thrown return false end |
#get_start_and_end_times(schedule_ruleset) ⇒ Object
Determine the hour when the schedule first exceeds the starting value and when it goes back down to the ending value at the end of the day. This method only works for ScheduleRuleset schedules.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/measures/generic_qaqc/resources/check_sch_coord.rb', line 10 def get_start_and_end_times(schedule_ruleset) # Ensure that this is a ScheduleRuleset schedule_ruleset = schedule_ruleset.to_ScheduleRuleset return [nil, nil] if schedule_ruleset.empty? schedule_ruleset = schedule_ruleset.get # Define the start and end date year_start_date = nil year_end_date = nil if schedule_ruleset.model.yearDescription.is_initialized year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) else year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) end # Get the ordered list of all the day schedules that are used by this schedule ruleset day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) # Get a 365-value array of which schedule is used on each day of the year, day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) # Create a map that shows how many days each schedule is used day_sch_freq = day_schs_used_each_day.group_by { |n| n } day_sch_freq = day_sch_freq.sort_by { |freq| freq[1].size } common_day_freq = day_sch_freq.last # Build a hash that maps schedule day index to schedule day schedule_index_to_day = {} day_schs.each_with_index do |day_sch, i| schedule_index_to_day[day_schs_used_each_day[i]] = day_sch end # Get the most common day schedule sch_index = common_day_freq[0] number_of_days_sch_used = common_day_freq[1].size # Get the day schedule at this index day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) schedule_ruleset.defaultDaySchedule else schedule_index_to_day[sch_index] end # Determine the full load hours for just one day values = [] times = [] day_sch.times.each_with_index do |time, i| times << day_sch.times[i] values << day_sch.values[i] end # Get the minimum value start_val = values.first end_val = values.last # Get the start time (first time value goes above minimum) start_time = nil values.each_with_index do |val, i| break if i == values.size - 1 # Stop if we reach end of array if val == start_val && values[i + 1] > start_val start_time = times[i + 1] break end end # Get the end time (first time value goes back down to minimum) end_time = nil values.each_with_index do |val, i| if i < values.size - 1 if val > end_val && values[i + 1] == end_val end_time = times[i] break end else if val > end_val && values[0] == start_val # Check first hour of day for schedules that end at midnight end_time = OpenStudio::Time.new(0, 24, 0, 0) break end end end return [start_time, end_time] end |
#map_sub_surfaces_props(sub_surface, check_elems, defaulted_const_type) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/measures/generic_qaqc/resources/check_envelope_conductance.rb', line 47 def map_sub_surfaces_props(sub_surface, check_elems, defaulted_const_type) construction = sub_surface.construction.get const_standards = construction.standardsInformation if const_standards.standardsConstructionType.is_initialized sub_const_type = const_standards.standardsConstructionType.get if sub_surface.subSurfaceType == "Door" || sub_surface.subSurfaceType == "OverheadDoor" ext_sub_surf_type = "ExteriorDoor" elsif sub_surface.subSurfaceType == "FixedWindow" || sub_surface.subSurfaceType == "OperableWindow" ext_sub_surf_type = "ExteriorWindow" elsif sub_surface.subSurfaceType == "Skylight" ext_sub_surf_type = "Skylight" else # todo - add message about constructions not being checked ext_sub_surf_type = sub_surface.surfaceType # address and test GlassDoor end else if sub_surface.subSurfaceType == "Door" ext_sub_surf_type = 'ExteriorDoor' sub_const_type = 'Swinging' elsif sub_surface.subSurfaceType == "OverheadDoor" ext_sub_surf_type = 'ExteriorDoor' sub_const_type = 'NonSwinging' elsif sub_surface.subSurfaceType == "FixedWindow" || sub_surface.subSurfaceType == "OperableWindow" ext_sub_surf_type = 'ExteriorWindow' sub_const_type = 'Metal framing (all other)' elsif sub_surface.subSurfaceType == "Skylight" ext_sub_surf_type = 'Skylight' sub_const_type = 'Glass with Curb' else check_elems << OpenStudio::Attribute.new('flag', "#{construction.name} is not associated with a standards construction type, this measure does not have default target mapping for #{sub_surface.surfaceType} sub-surface types.") end if !defaulted_const_type.include?(construction) check_elems << OpenStudio::Attribute.new('flag', "#{construction.name} is not associated with a standards construction type, checking based on #{sub_const_type} for #{ext_sub_surf_type}.") defaulted_const_type << construction end end return {ext_sub_surf_type: ext_sub_surf_type, sub_const_type: sub_const_type, construction: construction} end |
#map_surface_props(surface, check_elems, defaulted_const_type) ⇒ Object
common methods
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/measures/generic_qaqc/resources/check_envelope_conductance.rb', line 10 def map_surface_props(surface, check_elems, defaulted_const_type) # see of standards info to get standard construction type if set construction = surface.construction.get const_standards = construction.standardsInformation if const_standards.standardsConstructionType.is_initialized const_type = const_standards.standardsConstructionType.get if surface.surfaceType == "Wall" ext_surf_type = "ExteriorWall" elsif surface.surfaceType == "RoofCeiling" ext_surf_type = "ExteriorRoof" elsif surface.surfaceType == "Floor" ext_surf_type = "ExteriorFloor" else ext_surf_type = nil # should not hit this end else if surface.surfaceType == "Wall" ext_surf_type = 'ExteriorWall' const_type = 'SteelFramed' elsif surface.surfaceType == "RoofCeiling" ext_surf_type = 'ExteriorRoof' const_type = 'IEAD' elsif surface.surfaceType == "Floor" ext_surf_type = 'ExteriorFloor' const_type = 'Mass' end if !defaulted_const_type.include?(construction) check_elems << OpenStudio::Attribute.new('flag', "#{construction.name} is not associated with a standards construction type, checking based on #{const_type} for #{ext_surf_type}.") defaulted_const_type << construction end end return {ext_surf_type: ext_surf_type, const_type: const_type, construction: construction} end |