lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.2.12 vs lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.2.13.rc3
- old
+ new
@@ -148,10 +148,11 @@
# Apply the baseline system temperatures
model.getPlantLoops.sort.each do |plant_loop|
# Skip the SWH loops
next if plant_loop_swh_loop?(plant_loop)
+
plant_loop_apply_prm_baseline_temperatures(plant_loop)
end
# Set the heating and cooling sizing parameters
model_apply_prm_sizing_parameters(model)
@@ -177,19 +178,21 @@
# Set the baseline number of boilers and chillers
model.getPlantLoops.sort.each do |plant_loop|
# Skip the SWH loops
next if plant_loop_swh_loop?(plant_loop)
+
plant_loop_apply_prm_number_of_boilers(plant_loop)
plant_loop_apply_prm_number_of_chillers(plant_loop)
end
# Set the baseline number of cooling towers
# Must be done after all chillers are added
model.getPlantLoops.sort.each do |plant_loop|
# Skip the SWH loops
next if plant_loop_swh_loop?(plant_loop)
+
plant_loop_apply_prm_number_of_cooling_towers(plant_loop)
end
# Run sizing run with the new chillers, boilers, and cooling towers to determine capacities
if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false
@@ -199,10 +202,11 @@
# Set the pumping control strategy and power
# Must be done after sizing components
model.getPlantLoops.sort.each do |plant_loop|
# Skip the SWH loops
next if plant_loop_swh_loop?(plant_loop)
+
plant_loop_apply_prm_baseline_pump_power(plant_loop)
plant_loop_apply_prm_baseline_pumping_type(plant_loop)
end
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Prescriptive HVAC Controls and Equipment Efficiencies ***')
@@ -270,10 +274,11 @@
stories = []
zones.each do |zone|
zone.spaces.each do |space|
story = space.buildingStory
next if story.empty?
+
stories << story.get
end
end
# Reduce down to the unique set of stories
@@ -736,14 +741,14 @@
end
# Add a hot water PTAC to each zone
model_add_ptac(model,
zones,
- cooling_type: "Single Speed DX AC",
- heating_type: "Water",
+ cooling_type: 'Single Speed DX AC',
+ heating_type: 'Water',
hot_water_loop: hot_water_loop,
- fan_type: "ConstantVolume")
+ fan_type: 'ConstantVolume')
end
when 'PTHP' # System 2
unless zones.empty?
# add an air-source packaged terminal heat pump with electric supplemental heat to each zone.
@@ -784,11 +789,11 @@
model_add_psz_ac(model,
zones,
cooling_type: cooling_type,
chilled_water_loop: chilled_water_loop,
heating_type: heating_type,
- supplemental_heating_type: "Gas",
+ supplemental_heating_type: 'Gas',
hot_water_loop: hot_water_loop,
fan_location: 'DrawThrough',
fan_type: 'ConstantVolume')
end
@@ -848,11 +853,11 @@
# Add a PVAV with Reheat for the primary zones
stories = []
story_group[0].spaces.each do |space|
stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)]
end
- story_name = stories.sort_by { |nm, z| z }[0][0]
+ story_name = stories.min_by { |nm, z| z }[0]
system_name = "#{story_name} PVAV_Reheat (Sys5)"
# If and only if there are primary zones to attach to the loop
# counter example: floor with only one elevator machine room that get classified as sec_zones
unless pri_zones.empty?
@@ -899,11 +904,11 @@
# Add an VAV for the primary zones
stories = []
story_group[0].spaces.each do |space|
stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)]
end
- story_name = stories.sort_by { |nm, z| z }[0][0]
+ story_name = stories.min_by { |nm, z| z }[0]
system_name = "#{story_name} PVAV_PFP_Boxes (Sys6)"
# If and only if there are primary zones to attach to the loop
unless pri_zones.empty?
model_add_pvav_pfp_boxes(model,
pri_zones,
@@ -977,11 +982,11 @@
# Add a VAV for the primary zones
stories = []
story_group[0].spaces.each do |space|
stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)]
end
- story_name = stories.sort_by { |nm, z| z }[0][0]
+ story_name = stories.min_by { |nm, z| z }[0]
system_name = "#{story_name} VAV_Reheat (Sys7)"
# If and only if there are primary zones to attach to the loop
# counter example: floor with only one elevator machine room that get classified as sec_zones
unless pri_zones.empty?
@@ -1044,19 +1049,19 @@
# Add an VAV for the primary zones
stories = []
story_group[0].spaces.each do |space|
stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)]
end
- story_name = stories.sort_by { |nm, z| z }[0][0]
+ story_name = stories.min_by { |nm, z| z }[0]
system_name = "#{story_name} VAV_PFP_Boxes (Sys8)"
# If and only if there are primary zones to attach to the loop
unless pri_zones.empty?
model_add_vav_pfp_boxes(model,
pri_zones,
system_name: system_name,
chilled_water_loop: chilled_water_loop,
- fan_efficiency:0.62,
+ fan_efficiency: 0.62,
fan_motor_efficiency: 0.9,
fan_pressure_rise: 4.0)
end
# Add a PSZ_HP for each secondary zone
unless sec_zones.empty?
@@ -1292,10 +1297,11 @@
# Get the fractional lighting schedule
lights_sch = lights.schedule
full_load_hrs = 0.0
# Skip lights with no schedule
next if lights_sch.empty?
+
lights_sch = lights_sch.get
if lights_sch.to_ScheduleRuleset.is_initialized
lights_sch = lights_sch.to_ScheduleRuleset.get
full_load_hrs = schedule_ruleset_annual_equivalent_full_load_hrs(lights_sch)
if full_load_hrs > 0
@@ -1402,10 +1408,11 @@
if all_zones_on_story.include?(zone)
# Skip zones that were already assigned to a story.
# This can happen if a zone has multiple spaces on multiple stories.
# Stairwells and atriums are typical scenarios.
next if zones_already_assigned.include?(zone)
+
zones_on_story << zone
zones_already_assigned << zone
end
end
@@ -1463,18 +1470,18 @@
# @note This must be performed before the sizing run because it impacts component sizes, which in turn impact efficiencies.
def model_apply_multizone_vav_outdoor_air_sizing(model)
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying multizone vav OA sizing.')
# Multi-zone VAV outdoor air sizing
- model.getAirLoopHVACs.sort.each {|obj| air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(obj)}
+ model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(obj) }
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying multizone vav OA sizing.')
end
# Applies the HVAC parts of the template to all objects in the model using the the template specified in the model.
- def model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: true)
- sql_db_vars_map = {}
+ def model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: true, sql_db_vars_map: nil)
+ sql_db_vars_map = {} if sql_db_vars_map.nil?
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Started applying HVAC efficiency standards for #{template} template.")
# Air Loop Controls
if apply_controls.nil? || apply_controls == true
@@ -1533,24 +1540,24 @@
model.getCoolingTowerSingleSpeeds.sort.each { |obj| cooling_tower_single_speed_apply_efficiency_and_curves(obj) }
model.getCoolingTowerTwoSpeeds.sort.each { |obj| cooling_tower_two_speed_apply_efficiency_and_curves(obj) }
model.getCoolingTowerVariableSpeeds.sort.each { |obj| cooling_tower_variable_speed_apply_efficiency_and_curves(obj) }
# Fluid Coolers
- # TODO: enable when evaportive fluid cooler methods and data are available
- # model.getFluidCoolerSingleSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj) }
- # model.getFluidCoolerTwoSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj) }
- # model.getEvaporativeFluidCoolerSingleSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj) }
- # model.getEvaporativeFluidCoolerTwoSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj) }
+ model.getFluidCoolerSingleSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj, equipment_type: 'Dry Cooler') }
+ model.getFluidCoolerTwoSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj, equipment_type: 'Dry Cooler') }
+ model.getEvaporativeFluidCoolerSingleSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj, equipment_type: 'Closed Cooling Tower') }
+ model.getEvaporativeFluidCoolerTwoSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj, equipment_type: 'Closed Cooling Tower') }
# ERVs
model.getHeatExchangerAirToAirSensibleAndLatents.each { |obj| heat_exchanger_air_to_air_sensible_and_latent_apply_efficiency(obj) }
# Gas Heaters
model.getCoilHeatingGass.sort.each { |obj| coil_heating_gas_apply_efficiency_and_curves(obj) }
- model.getCoilHeatingGasMultiStages.each {|obj| coil_heating_gas_multi_stage_apply_efficiency_and_curves(obj)}
+ model.getCoilHeatingGasMultiStages.each { |obj| coil_heating_gas_multi_stage_apply_efficiency_and_curves(obj) }
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Finished applying HVAC efficiency standards for #{template} template.")
+ return true
end
# Applies daylighting controls to each space in the model per the standard.
def model_add_daylighting_controls(model)
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding daylighting controls.')
@@ -1584,11 +1591,10 @@
end
return true
end
-
# Method to search through a hash for the objects that meets the desired search criteria, as passed via a hash.
# Returns an Array (empty if nothing found) of matching objects.
#
# @param hash_of_objects [Hash] hash of objects to search through
# @param search_criteria [Hash] hash of search criteria
@@ -1606,32 +1612,34 @@
# if rules.size.zero?
# OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.")
# return false
# end
def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil)
-
matching_objects = []
if hash_of_objects.is_a?(Hash) && hash_of_objects.key?('table')
hash_of_objects = hash_of_objects['table']
end
# Compare each of the objects against the search criteria
raise("This is not a table #{hash_of_objects}") unless hash_of_objects.respond_to?(:each)
+
hash_of_objects.each do |object|
meets_all_search_criteria = true
search_criteria.each do |key, value|
# Don't check non-existent search criteria
next unless object.key?(key)
+
# Stop as soon as one of the search criteria is not met
# 'Any' is a special key that matches anything
unless object[key] == value || object[key] == 'Any'
meets_all_search_criteria = false
break
end
end
# Skip objects that don't meet all search criteria
next unless meets_all_search_criteria
+
# If made it here, object matches all search criteria
matching_objects << object
end
# If capacity was specified, narrow down the matching objects
@@ -1723,11 +1731,10 @@
# 'number_of_poles' => 4.0,
# 'type' => 'Enclosed',
# }
# motor_properties = self.model.find_object(motors, search_criteria, capacity: 2.5)
def model_find_object(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil)
-
matching_objects = model_find_objects(hash_of_objects, search_criteria, capacity, date, area, num_floors)
# Check the number of matching objects found
if matching_objects.size.zero?
desired_object = nil
@@ -1740,14 +1747,10 @@
end
return desired_object
end
-
-
-
-
# Method to search through a hash for the objects that meets the desired search criteria, as passed via a hash.
# Returns an Array (empty if nothing found) of matching objects.
#
# @param table_name [Hash] name of table in standards database.
# @param search_criteria [Hash] hash of search criteria
@@ -1764,17 +1767,17 @@
# rules = model_find_objects(standards_data['schedules'], 'name' => schedule_name)
# if rules.size.zero?
# OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.")
# return false
# end
- def standards_lookup_table_many(table_name: , search_criteria: {} , capacity: nil, date: nil, area: nil, num_floors: nil)
+ def standards_lookup_table_many(table_name:, search_criteria: {}, capacity: nil, date: nil, area: nil, num_floors: nil)
desired_object = nil
search_criteria_matching_objects = []
matching_objects = []
- hash_of_objects= @standards_data[table_name]
-
- #needed for NRCan data structure compatibility. We keep all tables in a 'tables' hash in @standards_data and the table
+ hash_of_objects = @standards_data[table_name]
+
+ # needed for NRCan data structure compatibility. We keep all tables in a 'tables' hash in @standards_data and the table
# itself is in the 'table' hash index.
if hash_of_objects.nil?
if @standards_data['tables'].nil?
# Format of @standards_data is not NRCan-style and table simply doesn't exist.
return matching_objects
@@ -1788,19 +1791,21 @@
hash_of_objects.each do |object|
meets_all_search_criteria = true
search_criteria.each do |key, value|
# Don't check non-existent search criteria
next unless object.key?(key)
+
# Stop as soon as one of the search criteria is not met
# 'Any' is a special key that matches anything
unless object[key] == value || object[key] == 'Any'
meets_all_search_criteria = false
break
end
end
# Skip objects that don't meet all search criteria
next unless meets_all_search_criteria
+
# If made it here, object matches all search criteria
matching_objects << object
end
# If capacity was specified, narrow down the matching objects
@@ -1828,10 +1833,11 @@
next if object['minimum_capacity'].nil? || object['maximum_capacity'].nil?
# Skip objects whose the minimum capacity is below the specified capacity
next if capacity <= object['minimum_capacity'].to_f
# Skip objects whose max
next if capacity > object['maximum_capacity'].to_f
+
# Found a matching object
matching_objects << object
end
end
# If date was specified, narrow down the matching objects
@@ -1842,10 +1848,11 @@
next if !object.key?('start_date') || !object.key?('end_date')
# Skip objects whose the start date is earlier than the specified date
next if date <= Date.parse(object['start_date'])
# Skip objects whose end date is beyond the specified date
next if date > Date.parse(object['end_date'])
+
# Found a matching object
date_matching_objects << object
end
matching_objects = date_matching_objects
end
@@ -1903,15 +1910,15 @@
# 'number_of_poles' => 4.0,
# 'type' => 'Enclosed',
# }
# motor_properties = self.model.find_object(motors, search_criteria, 2.5)
def standards_lookup_table_first(table_name:, search_criteria: {}, capacity: nil, date: nil)
- #run the many version of the look up code...DRY.
+ # run the many version of the look up code...DRY.
matching_objects = standards_lookup_table_many(table_name: table_name,
- search_criteria: search_criteria,
- capacity: capacity,
- date: date)
+ search_criteria: search_criteria,
+ capacity: capacity,
+ date: date)
# Check the number of matching objects found
if matching_objects.size.zero?
desired_object = nil
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Find object search criteria returned no results. Search criteria: #{search_criteria}. Called from #{caller(0)[1]}")
@@ -1981,11 +1988,11 @@
numeric_type: nil,
unit_type: nil)
if standard_sch_type_limit.nil?
if lower_limit_value.nil? || upper_limit_value.nil? || numeric_type.nil? || unit_type.nil?
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "If calling model_add_schedule_type_limits without a standard_sch_type_limit, you must specify all properties of ScheduleTypeLimits.")
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'If calling model_add_schedule_type_limits without a standard_sch_type_limit, you must specify all properties of ScheduleTypeLimits.')
return false
end
schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
schedule_type_limits.setName(name) if !name.nil?
schedule_type_limits.setLowerLimitValue(lower_limit_value)
@@ -2041,12 +2048,12 @@
schedule_type_limits.setName('Activity')
schedule_type_limits.setLowerLimitValue(70.0)
schedule_type_limits.setUpperLimitValue(1000.0)
schedule_type_limits.setNumericType('Continuous')
schedule_type_limits.setUnitType('ActivityLevel')
- else
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Invalid standard_sch_type_limit for method model_add_schedule_type_limits.")
+ else
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'Invalid standard_sch_type_limit for method model_add_schedule_type_limits.')
end
end
end
return schedule_type_limits
end
@@ -2057,10 +2064,11 @@
# @param schedule_name [String} name of the schedule
# @return [ScheduleRuleset] the resulting schedule ruleset
# @todo make return an OptionalScheduleRuleset
def model_add_schedule(model, schedule_name)
return nil if schedule_name.nil? || schedule_name == ''
+
# First check model and return schedule if it already exists
model.getSchedules.sort.each do |schedule|
if schedule.name.get.to_s == schedule_name
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added schedule: #{schedule_name}")
return schedule
@@ -2328,11 +2336,11 @@
construction_set_glazing_u_value(construction, target_u_value_ip.to_f, data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
construction_set_glazing_shgc(construction, target_shgc.to_f)
else # if !data['intended_surface_type'] == 'ExteriorWindow' && !data['intended_surface_type'] == 'Skylight'
# Set the U-Value
construction_set_u_value(construction, target_u_value_ip.to_f, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
- # else
+ # else
# OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Not modifying U-value for #{data['intended_surface_type']} u_val #{target_u_value_ip} f_fac #{target_f_factor_ip} c_fac #{target_c_factor_ip}")
end
elsif target_f_factor_ip && data['intended_surface_type'] == 'GroundContactFloor'
@@ -2371,19 +2379,20 @@
if frame_data
frame_width_in = frame_data['frame_width'].to_f
frame_with_m = OpenStudio.convert(frame_width_in, 'in', 'm').get
frame_resistance_ip = frame_data['resistance'].to_f
frame_resistance_si = OpenStudio.convert(frame_resistance_ip, 'hr*ft^2*R/Btu', 'm^2*K/W').get
- frame_conductance_si = 1.0/frame_resistance_si
+ frame_conductance_si = 1.0 / frame_resistance_si
frame = OpenStudio::Model::WindowPropertyFrameAndDivider.new(model)
frame.setName("Skylight frame R-#{frame_resistance_ip.round(2)} #{frame_width_in.round(1)} in. wide")
frame.setFrameWidth(frame_with_m)
frame.setFrameConductance(frame_conductance_si)
skylights_frame_added = 0
model.getSubSurfaces.each do |sub_surface|
next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && sub_surface.subSurfaceType == 'Skylight'
- # todo enable proper window frame setting after https://github.com/NREL/OpenStudio/issues/2895 is fixed
+
+ # TODO: enable proper window frame setting after https://github.com/NREL/OpenStudio/issues/2895 is fixed
sub_surface.setString(8, frame.name.get.to_s)
skylights_frame_added += 1
# if sub_surface.allowWindowPropertyFrameAndDivider
# sub_surface.setWindowPropertyFrameAndDivider(frame)
# skylights_frame_added += 1
@@ -2615,11 +2624,11 @@
data['exterior_door_building_category']))
end
if data['exterior_glass_door_standards_construction_type'] && data['exterior_glass_door_building_category']
exterior_subsurfaces.setGlassDoorConstruction(model_find_and_add_construction(model,
climate_zone_set,
- 'GlassDoor',
+ 'GlassDoor',
data['exterior_glass_door_standards_construction_type'],
data['exterior_glass_door_building_category']))
end
if data['exterior_overhead_door_standards_construction_type'] && data['exterior_overhead_door_building_category']
exterior_subsurfaces.setOverheadDoorConstruction(model_find_and_add_construction(model,
@@ -2696,11 +2705,11 @@
# OpenStudio::logFree(OpenStudio::Info, "openstudio.prototype.addCurve", "Adding curve '#{curve_name}' to the model.")
# Find curve data
data = model_find_object(standards_data['curves'], 'name' => curve_name)
if data.nil?
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.Model.Model", "Could not find a curve called '#{curve_name}' in the standards.")
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "Could not find a curve called '#{curve_name}' in the standards.")
return nil
end
# Make the correct type of curve
case data['form']
@@ -2786,11 +2795,11 @@
curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output']
curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output']
return curve
when 'MultiVariableLookupTable'
num_ind_var = data['number_independent_variables'].to_i
- table = OpenStudio::Model::TableMultiVariableLookup.new(model,num_ind_var)
+ table = OpenStudio::Model::TableMultiVariableLookup.new(model, num_ind_var)
table.setName(data['name'])
table.setInterpolationMethod(data['interpolation_method'])
table.setNumberofInterpolationPoints(data['number_of_interpolation_points'])
table.setCurveType(data['curve_type'])
table.setTableDataFormat('SingleLineIndependentVariableWithMatrix')
@@ -2802,21 +2811,21 @@
if num_ind_var == 2
table.setMinimumValueofX2(data['minimum_independent_variable_2'].to_f)
table.setMaximumValueofX2(data['maximum_independent_variable_2'].to_f)
table.setInputUnitTypeforX2(data['input_unit_type_x2'])
end
- data_points = data.each.select {|key,value| key.include? "data_point"}
- data_points.each do |key,value|
+ data_points = data.each.select { |key, value| key.include? 'data_point' }
+ data_points.each do |key, value|
if num_ind_var == 1
- table.addPoint(value.split(',')[0].to_f,value.split(',')[1].to_f)
+ table.addPoint(value.split(',')[0].to_f, value.split(',')[1].to_f)
elsif num_ind_var == 2
- table.addPoint(value.split(',')[0].to_f,value.split(',')[1].to_f,value.split(',')[2].to_f)
+ table.addPoint(value.split(',')[0].to_f, value.split(',')[1].to_f, value.split(',')[2].to_f)
end
end
return table
else
- OpenStudio::logFree(OpenStudio::Error, "openstudio.Model.Model", "#{curve_name}' has an invalid form: #{data['form']}', cannot create this curve.")
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "#{curve_name}' has an invalid form: #{data['form']}', cannot create this curve.")
return nil
end
end
# Get the full path to the weather file that is specified in the model
@@ -2874,18 +2883,18 @@
temp = File.read("#{standards_data_dir}/test_performance_expected_dd_results.csv")
else
temp = File.read("#{standards_data_dir}/legacy_idf_results.csv")
end
end
- legacy_idf_csv = CSV.new(temp, :headers => true, :converters => :all)
- legacy_idf_results = legacy_idf_csv.to_a.map {|row| row.to_hash }
+ legacy_idf_csv = CSV.new(temp, headers: true, converters: :all)
+ legacy_idf_results = legacy_idf_csv.to_a.map(&:to_hash)
# Get the results for this building
search_criteria = {
- 'Building Type' => building_type,
- 'Template' => template,
- 'Climate Zone' => climate_zone
+ 'Building Type' => building_type,
+ 'Template' => template,
+ 'Climate Zone' => climate_zone
}
energy_values = model_find_object(legacy_idf_results, search_criteria)
if energy_values.nil?
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find legacy simulation results for #{search_criteria}")
return {}
@@ -2916,16 +2925,17 @@
# List of all fuel types
fuel_types = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
# List of all end uses
- end_uses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps', 'Heat Rejection','Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
+ end_uses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps', 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
# Sum the legacy results up by fuel and by end use
fuel_types.each do |fuel_type|
end_uses.each do |end_use|
next if end_use == 'Exterior Equipment'
+
legacy_val = legacy_values["#{end_use}|#{fuel_type}"]
# Combine the exterior lighting and exterior equipment
if end_use == 'Exterior Lighting'
legacy_exterior_equipment = legacy_values["Exterior Equipment|#{fuel_type}"]
@@ -2956,14 +2966,13 @@
if legacy_results_hash['total_energy_by_end_use'][end_use]
legacy_results_hash['total_energy_by_end_use'][end_use] += legacy_val # add to existing counter
else
legacy_results_hash['total_energy_by_end_use'][end_use] = legacy_val # start new counter
end
-
end
- end # Next end use
- end # Next fuel type
+ end
+ end
return legacy_results_hash
end
# Keep track of floor area for prototype buildings.
@@ -2981,18 +2990,19 @@
result = 11_345
elsif building_type == 'LargeOffice' # 498,600 ft^2
result = 46_320
elsif building_type == 'MediumOffice' # 53,600 ft^2
result = 4982
- elsif building_type == 'LargeOfficeDetailed' # 498,600 ft^2
+ elsif building_type == 'LargeOfficeDetailed' # 498,600 ft^2
result = 46_320
elsif building_type == 'MediumOfficeDetailed' # 53,600 ft^2
result = 4982
elsif building_type == 'MidriseApartment' # 33,700 ft^2
result = 3135
elsif building_type == 'Office'
- result = nil # TODO: - there shouldn't be a prototype building for this
+ result = nil
+ # TODO: there shouldn't be a prototype building for this
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'Measures calling this should choose between SmallOffice, MediumOffice, and LargeOffice')
elsif building_type == 'Outpatient' # 40.950 ft^2
result = 3804
elsif building_type == 'PrimarySchool' # 73,960 ft^2
result = 6871
@@ -3012,13 +3022,13 @@
result = 2090
elsif building_type == 'SuperMarket' # 45,002 ft2 (from legacy reference idf file)
result = 4181
elsif building_type == 'Warehouse' # 49,495 ft^2 (legacy ref shows 52,045, but I wil calc using 49,495)
result = 4595
- elsif building_type == 'SmallDataCenterLowITE' or building_type == 'SmallDataCenterHighITE' # 600 ft^2
+ elsif building_type == 'SmallDataCenterLowITE' || building_type == 'SmallDataCenterHighITE' # 600 ft^2
result = 56
- elsif building_type == 'LargeDataCenterLowITE' or building_type == 'LargeDataCenterHighITE' # 6000 ft^2
+ elsif building_type == 'LargeDataCenterLowITE' || building_type == 'LargeDataCenterHighITE' # 6000 ft^2
result = 557
elsif building_type == 'Laboratory' # 90000 ft^2
result = 8361
else
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Didn't find expected building type. As a result can't determine floor prototype floor area")
@@ -3251,10 +3261,11 @@
end
# Hard-assigned surfaces
model.getSurfaces.sort.each do |surf|
next unless surf.outsideBoundaryCondition == boundary_condition
+
surf_type = surf.surfaceType
if surf_type == 'Floor' || surf_type == 'Wall'
next unless type.include?(surf_type)
elsif surf_type == 'RoofCeiling'
next unless type.include?('Roof') || type.include?('Ceiling')
@@ -3263,10 +3274,11 @@
end
# Hard-assigned subsurfaces
model.getSubSurfaces.sort.each do |surf|
next unless surf.outsideBoundaryCondition == boundary_condition
+
surf_type = surf.subSurfaceType
if surf_type == 'FixedWindow' || surf_type == 'OperableWindow'
next unless type == 'ExteriorWindow'
elsif surf_type == 'Door'
next unless type.include?('Door')
@@ -3278,10 +3290,11 @@
# Throw out the empty constructions
all_constructions = []
constructions.uniq.each do |const|
next if const.empty?
+
all_constructions << const.get
end
# Only return the unique list (should already be uniq)
all_constructions = all_constructions.uniq
@@ -3422,17 +3435,19 @@
types_to_modify.each do |boundary_condition, surface_type|
# Surfaces
model.getSurfaces.sort.each do |surf|
next unless surf.outsideBoundaryCondition == boundary_condition
next unless surf.surfaceType == surface_type
+
surfaces_to_modify << surf
end
# SubSurfaces
model.getSubSurfaces.sort.each do |surf|
next unless surf.outsideBoundaryCondition == boundary_condition
next unless surf.subSurfaceType == surface_type
+
surfaces_to_modify << surf
end
end
# Modify these surfaces
@@ -3458,12 +3473,11 @@
# @param intended_surface_type [string] the surface type
# @param standards_construction_type [string] the type of construction
# @param building_category [string] the type of building
# @param climate_zone [string] the building's climate zone
# @return [hash] hash of construction properties
- def model_get_construction_properties(model, intended_surface_type, standards_construction_type, building_category, climate_zone=nil)
-
+ def model_get_construction_properties(model, intended_surface_type, standards_construction_type, building_category, climate_zone = nil)
# get climate_zone_set
climate_zone = model_get_building_climate_zone_and_building_type(model)['climate_zone'] if climate_zone.nil?
climate_zone_set = model_find_climate_zone_set(model, climate_zone)
# populate search hash
@@ -3485,18 +3499,18 @@
#
# @param building_category [string] the type of building
# @param space_type [string] space type within the building type. Typically nil.
# @return [hash] hash of construction set data
def model_get_construction_set(building_type, space_type = nil)
- #populate search hash
+ # populate search hash
search_criteria = {
- 'template' => template,
- 'building_type' => building_type,
- 'space_type' => space_type
+ 'template' => template,
+ 'building_type' => building_type,
+ 'space_type' => space_type
}
- #Search construction sets table for the exterior wall building category and construction type
+ # Search construction sets table for the exterior wall building category and construction type
construction_set_data = model_find_object(standards_data['construction_sets'], search_criteria)
return construction_set_data
end
@@ -3531,15 +3545,17 @@
space.surfaces.sort.each do |surface|
# Skip non-outdoor surfaces
next unless surface.outsideBoundaryCondition == 'Outdoors'
# Skip non-walls
next unless surface.surfaceType.casecmp('wall').zero?
+
# This wall's gross area (including window area)
wall_area_m2 += surface.grossArea * space.multiplier
# Subsurfaces in this surface
surface.subSurfaces.sort.each do |ss|
next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow'
+
wind_area_m2 += ss.netArea * space.multiplier
end
end
# Determine the space category
@@ -3630,28 +3646,33 @@
case cat
when 'Unconditioned'
next # Skip unconditioned spaces
when 'NonResConditioned'
next unless red_nr
+
mult = mult_nr_red
when 'ResConditioned'
next unless red_res
+
mult = mult_res_red
when 'Semiheated'
next unless red_sh
+
mult = mult_sh_red
end
# Loop through all surfaces in this space
space.surfaces.sort.each do |surface|
# Skip non-outdoor surfaces
next unless surface.outsideBoundaryCondition == 'Outdoors'
# Skip non-walls
next unless surface.surfaceType.casecmp('wall').zero?
+
# Subsurfaces in this surface
surface.subSurfaces.sort.each do |ss|
next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow'
+
# Reduce the size of the window
# If a vertical rectangle, raise sill height to avoid
# impacting daylighting areas, otherwise
# reduce toward centroid.
red = 1.0 - mult
@@ -3691,15 +3712,17 @@
space.surfaces.sort.each do |surface|
# Skip non-outdoor surfaces
next unless surface.outsideBoundaryCondition == 'Outdoors'
# Skip non-walls
next unless surface.surfaceType == 'RoofCeiling'
+
# This wall's gross area (including skylight area)
wall_area_m2 += surface.grossArea * space.multiplier
# Subsurfaces in this surface
surface.subSurfaces.sort.each do |ss|
next unless ss.subSurfaceType == 'Skylight'
+
sky_area_m2 += ss.netArea * space.multiplier
end
end
# Determine the space category
@@ -3765,13 +3788,15 @@
# Skip spaces whose skylights don't need to be reduced
case cat
when 'NonRes'
next unless red_nr
+
mult = mult_nr_red
when 'Res'
next unless red_res
+
mult = mult_res_red
when 'Semiheated'
next unless red_sh
# mult = mult_sh_red
end
@@ -3780,13 +3805,15 @@
space.surfaces.sort.each do |surface|
# Skip non-outdoor surfaces
next unless surface.outsideBoundaryCondition == 'Outdoors'
# Skip non-walls
next unless surface.surfaceType == 'RoofCeiling'
+
# Subsurfaces in this surface
surface.subSurfaces.sort.each do |ss|
next unless ss.subSurfaceType == 'Skylight'
+
# Reduce the size of the skylight
red = 1.0 - mult
sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
end
end
@@ -3810,20 +3837,22 @@
def model_remove_prm_hvac(model)
# Plant loops
model.getPlantLoops.sort.each do |loop|
# Don't remove service water heating loops
next if plant_loop_swh_loop?(loop)
+
loop.remove
end
# Air loops
model.getAirLoopHVACs.each(&:remove)
# Zone equipment
model.getThermalZones.sort.each do |zone|
zone.equipment.each do |zone_equipment|
next if zone_equipment.to_FanZoneExhaust.is_initialized
+
zone_equipment.remove
end
end
# Outdoor VRF units (not in zone, not in loops)
@@ -3834,22 +3863,22 @@
# Remove EMS objects that may be orphaned from removing HVAC
#
# @return [Bool] true if successful, false if not
def model_remove_prm_ems_objects(model)
- model.getEnergyManagementSystemActuators.each { |x| x.remove }
- model.getEnergyManagementSystemConstructionIndexVariables.each { |x| x.remove }
- model.getEnergyManagementSystemCurveOrTableIndexVariables.each { |x| x.remove }
- model.getEnergyManagementSystemGlobalVariables.each { |x| x.remove }
- model.getEnergyManagementSystemInternalVariables.each { |x| x.remove }
- model.getEnergyManagementSystemMeteredOutputVariables.each { |x| x.remove }
- model.getEnergyManagementSystemOutputVariables.each { |x| x.remove }
- model.getEnergyManagementSystemPrograms.each { |x| x.remove }
- model.getEnergyManagementSystemProgramCallingManagers.each { |x| x.remove }
- model.getEnergyManagementSystemSensors.each { |x| x.remove }
- model.getEnergyManagementSystemSubroutines.each { |x| x.remove }
- model.getEnergyManagementSystemTrendVariables.each { |x| x.remove }
+ model.getEnergyManagementSystemActuators.each(&:remove)
+ model.getEnergyManagementSystemConstructionIndexVariables.each(&:remove)
+ model.getEnergyManagementSystemCurveOrTableIndexVariables.each(&:remove)
+ model.getEnergyManagementSystemGlobalVariables.each(&:remove)
+ model.getEnergyManagementSystemInternalVariables.each(&:remove)
+ model.getEnergyManagementSystemMeteredOutputVariables.each(&:remove)
+ model.getEnergyManagementSystemOutputVariables.each(&:remove)
+ model.getEnergyManagementSystemPrograms.each(&:remove)
+ model.getEnergyManagementSystemProgramCallingManagers.each(&:remove)
+ model.getEnergyManagementSystemSensors.each(&:remove)
+ model.getEnergyManagementSystemSubroutines.each(&:remove)
+ model.getEnergyManagementSystemTrendVariables.each(&:remove)
return true
end
# Remove external shading devices. Site shading will not be impacted.
@@ -3857,10 +3886,11 @@
def model_remove_external_shading_devices(model)
shading_surfaces_removed = 0
model.getShadingSurfaceGroups.sort.each do |shade_group|
# Skip Site shading
next if shade_group.shadingSurfaceType == 'Site'
+
# Space shading surfaces should be removed
shading_surfaces_removed += shade_group.shadingSurfaces.size
shade_group.remove
end
@@ -3934,11 +3964,11 @@
result << { units: 'unit', block: 20, max_hourly: 12.0, max_daily: 80.0, avg_day_unit: 42.0 }
result << { units: 'unit', block: 50, max_hourly: 10.0, max_daily: 73.0, avg_day_unit: 40.0 }
result << { units: 'unit', block: 75, max_hourly: 8.5, max_daily: 66.0, avg_day_unit: 38.0 }
result << { units: 'unit', block: 100, max_hourly: 7.0, max_daily: 60.0, avg_day_unit: 37.0 }
result << { units: 'unit', block: 200, max_hourly: 5.0, max_daily: 50.0, avg_day_unit: 35.0 }
- elsif ['Office', 'LargeOffice', 'MediumOffice', 'SmallOffice','LargeOfficeDetailed', 'MediumOfficeDetailed', 'SmallOfficeDetailed'].include? building_type
+ elsif ['Office', 'LargeOffice', 'MediumOffice', 'SmallOffice', 'LargeOfficeDetailed', 'MediumOfficeDetailed', 'SmallOfficeDetailed'].include? building_type
result << { units: 'person', block: nil, max_hourly: 0.4, max_daily: 2.0, avg_day_unit: 1.0 }
elsif building_type == 'Outpatient'
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "No SWH rules of thumbs for #{building_type}.")
elsif building_type == 'PrimarySchool'
result << { units: 'student', block: nil, max_hourly: 0.6, max_daily: 1.5, avg_day_unit: 0.6 }
@@ -4113,11 +4143,11 @@
# Determine which climate zone to use.
# Defaults to the least specific climate zone set.
# For example, 2A and 2 both contain 2A, so use 2.
def model_get_climate_zone_set_from_list(model, possible_climate_zone_sets)
- climate_zone_set = possible_climate_zone_sets.sort.first
+ climate_zone_set = possible_climate_zone_sets.min
return climate_zone_set
end
# This method ensures that all spaces with spacetypes defined contain at least a standardSpaceType appropriate for the template.
# So, if any space with a space type defined does not have a Stnadard spacetype, or is undefined, an error will stop
@@ -4198,10 +4228,11 @@
end
# update count of ground wall areas
next if surface.surfaceType != 'Wall'
next if surface.outsideBoundaryCondition != 'Ground' # TODO: - make more flexible for slab/basement model.modeling
+
story_ground_wall_area += surface.grossArea
end
# skip if surface had no vertices
next if z_points.empty?
@@ -4296,10 +4327,11 @@
# determine num_parking_spots in another method
# loop through spaces to get mis values
space_type.spaces.sort.each do |space|
next unless space.partofTotalFloorArea
+
effective_num_spaces += space.multiplier
floor_area += space.floorArea * space.multiplier
num_people += space.numberOfPeople * space.multiplier
end
@@ -4371,24 +4403,25 @@
end
return space_type_hash.sort.to_h
end
-
# This method will apply the a FDWR to a model. It will remove any existing windows and doors and use the
# Default contruction to set to apply the window construction. Sill height is in meters
def apply_max_fdwr(model, runner, sillHeight_si, wwr)
empty_const_warning = false
model.getSpaces.sort.each do |space|
space.surfaces.sort.each do |surface|
zone = surface.space.get.thermalZone
zone_multiplier = nil
next if zone.empty?
- if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == "Wall"
- surface.subSurfaces.each {|ss| ss.remove}
+
+ if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == 'Wall')
+ surface.subSurfaces.each(&:remove)
new_window = surface.setWindowToWallRatio(wwr, sillHeight_si, true)
raise "#{surface.name.get} did not get set to #{wwr}. The size of the surface is #{surface.grossArea}" unless surface.windowToWallRatio.round(3) == wwr.round(3)
+
if new_window.empty?
runner.registerWarning("The requested window to wall ratio for surface '#{surface.name}' was too large. Fenestration was not altered for this surface.")
else
windows_added = true
# warn user if resulting window doesn't have a construction, as it will result in failed simulation. In the future may use logic from starting windows to apply construction to new window.
@@ -4404,29 +4437,29 @@
# This method will apply the a SRR to a model. It will remove any existing skylights and use the
# Default contruction to set to apply the skylight construction. A default skylight square area of 0.25^2 is used.
def apply_max_srr(model, runner, srr, skylight_area = 0.25 * 0.25)
spaces = []
- surface_type = "RoofCeiling"
+ surface_type = 'RoofCeiling'
model.getSpaces.sort.each do |space|
space.surfaces.sort.each do |surface|
- if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == surface_type
+ if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == surface_type)
spaces << space
break
end
end
end
pattern = OpenStudio::Model.generateSkylightPattern(spaces, spaces[0].directionofRelativeNorth, srr, Math.sqrt(skylight_area), Math.sqrt(skylight_area)) # ratio, x value, y value
# applying skylight pattern
skylights = OpenStudio::Model.applySkylightPattern(pattern, spaces, OpenStudio::Model::OptionalConstructionBase.new)
- spacenames = spaces.map {|space| space.name.get}
+ spacenames = spaces.map { |space| space.name.get }
runner.registerInfo("Adding #{skylights.size} skylights to #{spacenames}")
end
# This method will limit the subsurface of a given surface_type ("Wall" or "RoofCeiling") to the ratio for the building.
# This method only reduces subsurface sizes at most.
- def apply_limit_to_subsurface_ratio(model, ratio, surface_type = "Wall")
+ def apply_limit_to_subsurface_ratio(model, ratio, surface_type = 'Wall')
fdwr = get_outdoor_subsurface_ratio(model, surface_type)
if fdwr <= ratio
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Building FDWR of #{fdwr} is already lower than limit of #{ratio.round}%.")
return true
end
@@ -4439,10 +4472,11 @@
space.surfaces.sort.each do |surface|
# Skip non-outdoor surfaces
next unless surface.outsideBoundaryCondition == 'Outdoors'
# Skip non-walls
next unless surface.surfaceType == surface_type
+
# Subsurfaces in this surface
surface.subSurfaces.sort.each do |ss|
# Reduce the size of the window
red = 1.0 - mult
sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
@@ -4463,17 +4497,19 @@
def model_standards_climate_zone(model)
climate_zone = ''
model.getClimateZones.climateZones.each do |cz|
if cz.institution == 'ASHRAE'
next if cz.value == '' # Skip blank ASHRAE climate zones put in by OpenStudio Application
+
climate_zone = if cz.value == '7' || cz.value == '8'
"ASHRAE 169-2013-#{cz.value}A"
else
"ASHRAE 169-2013-#{cz.value}"
end
elsif cz.institution == 'CEC'
next if cz.value == '' # Skip blank ASHRAE climate zones put in by OpenStudio Application
+
climate_zone = "CEC T24-CEC#{cz.value}"
end
end
return climate_zone
end
@@ -4500,24 +4536,24 @@
end
return true
end
-
# This method return the building ratio of subsurface_area / surface_type_area where surface_type can be "Wall" or "RoofCeiling"
- def get_outdoor_subsurface_ratio(model, surface_type = "Wall")
+ def get_outdoor_subsurface_ratio(model, surface_type = 'Wall')
surface_area = 0.0
sub_surface_area = 0
all_surfaces = []
all_sub_surfaces = []
model.getSpaces.sort.each do |space|
zone = space.thermalZone
zone_multiplier = nil
next if zone.empty?
+
zone_multiplier = zone.get.multiplier
space.surfaces.sort.each do |surface|
- if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == surface_type
+ if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == surface_type)
surface_area += surface.grossArea * zone_multiplier
surface.subSurfaces.sort.each do |sub_surface|
sub_surface_area += sub_surface.grossArea * sub_surface.multiplier * zone_multiplier
end
end
@@ -4532,10 +4568,11 @@
def load_initial_osm(osm_file)
# Load the geometry .osm
unless File.exist?(osm_file)
raise("The initial osm path: #{osm_file} does not exist.")
end
+
osm_model_path = OpenStudio::Path.new(osm_file.to_s)
# Upgrade version if required.
version_translator = OpenStudio::OSVersion::VersionTranslator.new
model = version_translator.loadModel(osm_model_path).get
validate_initial_model(model)
@@ -4543,39 +4580,39 @@
end
def validate_initial_model(model)
is_valid = true
if model.getBuildingStorys.empty?
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign Spaces to BuildingStorys the geometry model.")
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Please assign Spaces to BuildingStorys the geometry model.')
is_valid = false
end
if model.getThermalZones.empty?
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign Spaces to ThermalZones the geometry model.")
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Please assign Spaces to ThermalZones the geometry model.')
is_valid = false
end
if model.getBuilding.standardsNumberOfStories.empty?
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please define Building.standardsNumberOfStories the geometry model.")
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Please define Building.standardsNumberOfStories the geometry model.')
is_valid = false
end
if model.getBuilding.standardsNumberOfAboveGroundStories.empty?
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please define Building.standardsNumberOfAboveStories in the geometry model.")
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Please define Building.standardsNumberOfAboveStories in the geometry model.')
is_valid = false
end
if @space_type_map.nil? || @space_type_map.empty?
@space_type_map = get_space_type_maps_from_model(model)
if @space_type_map.nil? || @space_type_map.empty?
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign SpaceTypes in the geometry model or in standards database #{@space_type_map}.")
is_valid = false
else
@space_type_map = @space_type_map.sort.to_h
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Loaded space type map from model")
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Loaded space type map from model')
end
end
# ensure that model is intersected correctly.
- model.getSpaces.each {|space1| model.getSpaces.each {|space2| space1.intersectSurfaces(space2)}}
+ model.getSpaces.each { |space1| model.getSpaces.each { |space2| space1.intersectSurfaces(space2) } }
# Get multipliers from TZ in model. Need this for HVAC contruction.
@space_multiplier_map = {}
model.getSpaces.sort.each do |space|
@space_multiplier_map[space.name.get] = space.multiplier if space.multiplier > 1
end
@@ -4593,11 +4630,11 @@
# @param model [OpenStudio::Model::Model] the model
# @return [String] the ventilation method, either Sum or Maximum
def model_ventilation_method(model)
building_data = model_get_building_climate_zone_and_building_type(model)
building_type = building_data['building_type']
- if building_type != 'Laboratory' # Laboratory has multiple criteria on ventilation, pick the greatest
+ if building_type != 'Laboratory' # Laboratory has multiple criteria on ventilation, pick the greatest
ventilation_method = 'Sum'
else
ventilation_method = 'Maximum'
end
@@ -4610,16 +4647,16 @@
# @return [Bool] returns true if successful, false if not
def model_remove_unused_resource_objects(model)
start_size = model.objects.size
model.getResourceObjects.sort.each do |obj|
if obj.directUseCount.zero?
- OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{obj.name} is unused; it will be removed.")
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{obj.name} is unused; it will be removed.")
model.removeObject(obj.handle)
end
end
end_size = model.objects.size
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.Model', "The model started with #{start_size} objects and finished with #{end_size} objects after removing unused resource objects.")
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The model started with #{start_size} objects and finished with #{end_size} objects after removing unused resource objects.")
return true
end
# This method looks at occupancy profiles for the building as a whole and generates an hours of operation default
# schedule for the building. It also clears out any higher level hours of operation schedule assignments.
@@ -4635,82 +4672,84 @@
# @param fraction_of_daily_occ_range [Double] fraction above/below daily min range required to start and end hours of operation
# @param invert_res [Bool] if true will reverse hours of operation for residential space types
# @param gen_occ_profile [Bool] if true creates a merged occupancy schedule for diagnostic purposes. This schedule is added to the model but no specifically returned by this method
# @return [ScheduleRuleset] schedule that is assigned to the building as default hours of operation
def model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: 0.25, invert_res: true, gen_occ_profile: false)
-
# create an array of non-residential and residential spaces
res_spaces = []
non_res_spaces = []
res_people_design = 0
non_res_people_design = 0
- model.getSpaces.each do |space|
+ model.getSpaces.sort.each do |space|
if space_residential?(space)
res_spaces << space
res_people_design += space.numberOfPeople * space.multiplier
else
non_res_spaces << space
non_res_people_design += space.numberOfPeople * space.multiplier
end
end
- OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.Model", "Model has design level of #{non_res_people_design} people in non residential spaces and #{res_people_design} people in residential spaces.")
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "Model has design level of #{non_res_people_design} people in non residential spaces and #{res_people_design} people in residential spaces.")
# create merged schedule for prevalent type (not used but can be generated for diagnostics)
if gen_occ_profile
res_prevalent = false
if res_people_design > non_res_people_design
- occ_merged = spaces_get_occupancy_schedule(res_spaces, sch_name: "Calculated Occupancy Fraction Residential Merged")
+ occ_merged = spaces_get_occupancy_schedule(res_spaces, sch_name: 'Calculated Occupancy Fraction Residential Merged')
res_prevalent = true
else
- occ_merged = spaces_get_occupancy_schedule(non_res_spaces, sch_name: "Calculated Occupancy Fraction NonResidential Merged")
+ occ_merged = spaces_get_occupancy_schedule(non_res_spaces, sch_name: 'Calculated Occupancy Fraction NonResidential Merged')
end
end
# re-run spaces_get_occupancy_schedule with x above min occupancy to create on/off schedule
if res_people_design > non_res_people_design
hours_of_operation = spaces_get_occupancy_schedule(res_spaces,
- sch_name: "Building Hours of Operation Residential",
- occupied_percentage_threshold: fraction_of_daily_occ_range,
- threshold_calc_method: "normalized_daily_range")
+ sch_name: 'Building Hours of Operation Residential',
+ occupied_percentage_threshold: fraction_of_daily_occ_range,
+ threshold_calc_method: 'normalized_daily_range')
res_prevalent = true
else
hours_of_operation = spaces_get_occupancy_schedule(non_res_spaces,
- sch_name: "Building Hours of Operation NonResidential",
- occupied_percentage_threshold: fraction_of_daily_occ_range,
- threshold_calc_method: "normalized_daily_range")
+ sch_name: 'Building Hours of Operation NonResidential',
+ occupied_percentage_threshold: fraction_of_daily_occ_range,
+ threshold_calc_method: 'normalized_daily_range')
end
# remove gaps resulting in multiple on off cycles for each rule in schedule so it will be valid hours of operation
profiles = []
profiles << hours_of_operation.defaultDaySchedule
hours_of_operation.scheduleRules.each do |rule|
profiles << rule.daySchedule
end
- profiles.each do |profile|
+ profiles.sort.each do |profile|
times = profile.times
values = profile.values
next if times.size <= 3 # length of 1-3 should produce valid hours_of_operation profiles
+
# Find the latest time where the value == 1
latest_time = nil
times.zip(values).each do |time, value|
if value > 0
latest_time = time
end
end
# Skip profiles that are zero all the time
next if latest_time.nil?
+
# Calculate the duration from this point to midnight
wrap_dur_left_hr = 0
if values.first == 0 && values.last == 0
wrap_dur_left_hr = 24.0 - latest_time.totalHours
end
occ_gap_hash = {}
prev_time = 0
prev_val = nil
- times.each_with_index do |time,i|
+ times.each_with_index do |time, i|
next if time.totalHours == 0.0 # should not see this
next if values[i] == prev_val # check if two 0 until time next to each other
+
if values[i] == 0 # only store vacant segments
if time.totalHours == 24
occ_gap_hash[prev_time] = time.totalHours - prev_time + wrap_dur_left_hr
else
occ_gap_hash[prev_time] = time.totalHours - prev_time
@@ -4732,48 +4771,46 @@
# time for gap end
target_end_hr = max_occ_gap_end_hr.truncate
target_end_min = ((max_occ_gap_end_hr - target_end_hr) * 60.0).truncate
max_occ_gap_end = OpenStudio::Time.new(0, target_end_hr, target_end_min, 0)
- profile.addValue(max_occ_gap_start,1)
- profile.addValue(max_occ_gap_end,0)
+ profile.addValue(max_occ_gap_start, 1)
+ profile.addValue(max_occ_gap_end, 0)
os_time_24 = OpenStudio::Time.new(0, 24, 0, 0)
if max_occ_gap_start > max_occ_gap_end
- profile.addValue(os_time_24,0)
+ profile.addValue(os_time_24, 0)
else
- profile.addValue(os_time_24,1)
+ profile.addValue(os_time_24, 1)
end
end
# reverse 1 and 0 values for res_prevalent building
# currently spaces_get_occupancy_schedule doesn't use defaultDayProflie, so only inspecting rules for now.
if invert_res && res_prevalent
- OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.Model", "Per argument passed in hours of operation are being inverted for buildings with more people in residential versus non-residential spaces.")
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', 'Per argument passed in hours of operation are being inverted for buildings with more people in residential versus non-residential spaces.')
hours_of_operation.scheduleRules.each do |rule|
profile = rule.daySchedule
times = profile.times
values = profile.values
profile.clearValues
- times.each_with_index do |time,i|
+ times.each_with_index do |time, i|
orig_val = values[i]
new_value = nil
if orig_val == 0 then new_value = 1 end
if orig_val == 1 then new_value = 0 end
- profile.addValue(time,new_value)
+ profile.addValue(time, new_value)
end
end
end
# set hours of operation for building level hours of operation
- model.getDefaultScheduleSets.each do |sch_set|
- sch_set.resetHoursofOperationSchedule
- end
+ model.getDefaultScheduleSets.each(&:resetHoursofOperationSchedule)
if model.getBuilding.defaultScheduleSet.is_initialized
default_sch_set = model.getBuilding.defaultScheduleSet.get
else
default_sch_set = OpenStudio::Model::DefaultScheduleSet.new(model)
- default_sch_set.setName("Building Default Schedule Set")
+ default_sch_set.setName('Building Default Schedule Set')
model.getBuilding.setDefaultScheduleSet(default_sch_set)
end
default_sch_set.setHoursofOperationSchedule(hours_of_operation)
return hours_of_operation
@@ -4786,116 +4823,117 @@
#
# @author David Goldwasser
# @param model [Model]
# @param step_ramp_logic [String]
# @param infer_hoo_for_non_assigned_objects [Bool] # attempt to get hoo for objects like swh with and exterior lighting
+ # @param gather_data_only: false (stops method before changes made if true)
+ # @param [hoo_var_method] accepts hours and fractional. Any other value value will result in hoo variables not being applied
# @return [Hash] schedule is key, value is hash of number of objects
- def model_setup_parametric_schedules(model, step_ramp_logic: nil, infer_hoo_for_non_assigned_objects: true,gather_data_only: false)
-
+ def model_setup_parametric_schedules(model, step_ramp_logic: nil, infer_hoo_for_non_assigned_objects: true, gather_data_only: false, hoo_var_method: 'hours')
parametric_inputs = {}
default_sch_type = OpenStudio::Model::DefaultScheduleType.new('HoursofOperationSchedule')
# thermal zones, air loops, plant loops will require some logic if they refer to more than one hours of operaiton schedule.
# for initial use case while have same horus of operaiton so this can be pretty simple, but will have to re-visit it sometime
# possible solution A: choose hoo that contributes the largest fraction of floor area
# possible solution B: expand the hours of operation for a given day to include combined range of hoo objects
# whatever approach is used for gathering parametric inputs for existing ruleset schedules should also be used for model_apply_parametric_schedules
# loop through spaces (trace hours of operation back to space)
- gather_inputs_parametric_space_space_type_schedules(model.getSpaces,parametric_inputs,gather_data_only)
+ gather_inputs_parametric_space_space_type_schedules(model.getSpaces, parametric_inputs, gather_data_only)
# loop through space types (trace hours of operation back to space type).
- gather_inputs_parametric_space_space_type_schedules(model.getSpaceTypes,parametric_inputs,gather_data_only)
+ gather_inputs_parametric_space_space_type_schedules(model.getSpaceTypes, parametric_inputs, gather_data_only)
# loop through thermal zones (trace hours of operation back to spaces in thermal zone)
thermal_zone_hash = {} # key is zone and hash is hours of operation
- model.getThermalZones.each do |zone|
+ model.getThermalZones.sort.each do |zone|
# identify hours of operation
hours_of_operation = spaces_hours_of_operation(zone.spaces)
thermal_zone_hash[zone] = hours_of_operation
# get thermostat setpoint schedules
if zone.thermostatSetpointDualSetpoint.is_initialized
thermostat = zone.thermostatSetpointDualSetpoint.get
if thermostat.heatingSetpointTemperatureSchedule.is_initialized && thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized
schedule = thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get
- gather_inputs_parametric_schedules(schedule,thermostat,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, thermostat, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
end
- if thermostat.coolingSetpointTemperatureSchedule.is_initialized&& thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized
+ if thermostat.coolingSetpointTemperatureSchedule.is_initialized && thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized
schedule = thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get
- gather_inputs_parametric_schedules(schedule,thermostat,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, thermostat, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
end
end
end
# loop through air loops (trace hours of operation back through spaces served by air loops)
air_loop_hash = {} # key is zone and hash is hours of operation
- model.getAirLoopHVACs.each do |air_loop|
+ model.getAirLoopHVACs.sort.each do |air_loop|
# identify hours of operation
air_loop_spaces = []
- air_loop.thermalZones.each do |zone|
- air_loop_spaces = air_loop_spaces + zone.spaces
- air_loop_spaces = air_loop_spaces + zone.spaces
+ air_loop.thermalZones.sort.each do |zone|
+ air_loop_spaces += zone.spaces
+ air_loop_spaces += zone.spaces
end
hours_of_operation = spaces_hours_of_operation(air_loop_spaces)
air_loop_hash[air_loop] = hours_of_operation
if air_loop.availabilitySchedule.to_ScheduleRuleset.is_initialized
schedule = air_loop.availabilitySchedule.to_ScheduleRuleset.get
- gather_inputs_parametric_schedules(schedule,air_loop,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, air_loop, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
end
avail_mgrs = air_loop.availabilityManagers
- avail_mgrs.each do |avail_mgr|
- # todo - I'm finding availability mangers, but not any resources for them, even if I use OpenStudio::Model.getRecursiveChildren(avail_mgr)
+ avail_mgrs.sort.each do |avail_mgr|
+ # TODO: - I'm finding availability mangers, but not any resources for them, even if I use OpenStudio::Model.getRecursiveChildren(avail_mgr)
resources = avail_mgr.resources
resources = OpenStudio::Model.getRecursiveResources(avail_mgr)
- resources.each do |resource|
+ resources.sort.each do |resource|
if resource.to_ScheduleRuleset.is_initialized
schedule = resource.to_ScheduleRuleset.get
- gather_inputs_parametric_schedules(schedule,avail_mgr,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, avail_mgr, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
end
end
end
end
# look through all model HVAC components find scheduleRuleset objects, resources, that use them and zone or air loop for hours of operation
hvac_components = model.getHVACComponents
- hvac_components.each do |component|
+ hvac_components.sort.each do |component|
# identify zone, or air loop it refers to, some may refer to plant loop, OA or other component
thermal_zone = nil
air_loop = nil
plant_loop = nil
schedules = []
if component.to_ZoneHVACComponent.is_initialized && component.to_ZoneHVACComponent.get.thermalZone.is_initialized
thermal_zone = component.to_ZoneHVACComponent.get.thermalZone.get
end
if component.airLoopHVAC.is_initialized
air_loop = component.airLoopHVAC.get
- else
end
if component.plantLoop.is_initialized
plant_loop = component.plantLoop.get
end
- component.resources.each do |resource|
+ component.resources.sort.each do |resource|
if resource.to_ThermalZone.is_initialized
thermal_zone = resource.to_ThermalZone.get
elsif resource.to_ScheduleRuleset.is_initialized
schedules << resource.to_ScheduleRuleset.get
end
end
# inspect resources for children of objects found in thermal zone or plant loop
# get objects like OA controllers and unitary object components
next if thermal_zone.nil? && air_loop.nil?
+
children = OpenStudio::Model.getRecursiveChildren(component)
- children.each do |child|
- child.resources.each do |sub_resource|
+ children.sort.each do |child|
+ child.resources.sort.each do |sub_resource|
if sub_resource.to_ScheduleRuleset.is_initialized
schedules << sub_resource.to_ScheduleRuleset.get
end
end
end
# process schedules found for this component
- schedules.each do |schedule|
+ schedules.sort.each do |schedule|
hours_of_operation = nil
if !thermal_zone.nil?
hours_of_operation = thermal_zone_hash[thermal_zone]
elsif !air_loop.nil?
hours_of_operation = air_loop_hash[air_loop]
@@ -4904,41 +4942,39 @@
next
else
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Cannot identify where #{component.name.get} is in system. Will not gather parametric inputs for #{schedule.name.get}")
next
end
- gather_inputs_parametric_schedules(schedule,component,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, component, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
end
-
end
- # todo - Service Water Heating supply side (may or may not be associated with a space)
+ # TODO: - Service Water Heating supply side (may or may not be associated with a space)
# todo - water use equipment definitions (temperature, sensible, latent) may be in multiple spaces, need to identify hoo, but typically constant schedules
# water use equipment (flow rate fraction)
# todo - address common schedules used across multiple instances
- model.getWaterUseEquipments.each do |water_use_equipment|
-
+ model.getWaterUseEquipments.sort.each do |water_use_equipment|
if water_use_equipment.flowRateFractionSchedule.is_initialized && water_use_equipment.flowRateFractionSchedule.get.to_ScheduleRuleset.is_initialized
schedule = water_use_equipment.flowRateFractionSchedule.get.to_ScheduleRuleset.get
- next if parametric_inputs.has_key?(schedule)
+ next if parametric_inputs.key?(schedule)
opt_space = water_use_equipment.space
if opt_space.is_initialized
space = space.get
hours_of_operation = space_hours_of_operation(space)
- gather_inputs_parametric_schedules(schedule,water_use_equipment,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, water_use_equipment, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
else
hours_of_operation = spaces_hours_of_operation(model.getSpaces)
if !hours_of_operation.nil?
- gather_inputs_parametric_schedules(schedule,water_use_equipment,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(schedule, water_use_equipment, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method)
end
end
end
end
- # todo - Refrigeration (will be associated with thermal zone)
+ # TODO: - Refrigeration (will be associated with thermal zone)
# todo - exterior lights (will be astronomical, but like AEDG's may have reduction later at night)
return parametric_inputs
end
@@ -4953,44 +4989,42 @@
# @param ramp_frequency [Double] ramp frequency in minutes. If nil method will match simulation timestep
# @param infer_hoo_for_non_assigned_objects [Bool] # attempt to get hoo for objects like swh with and exterior lighting
# @param error_on_out_of_order [Bool] true will error if applying formula creates out of order values
# @return [Array] of modified ScheduleRuleset objects
def model_apply_parametric_schedules(model, ramp_frequency: nil, infer_hoo_for_non_assigned_objects: true, error_on_out_of_order: true)
-
# get ramp frequency (fractional hour) from timestep
if ramp_frequency.nil?
steps_per_hour = if model.getSimulationControl.timestep.is_initialized
model.getSimulationControl.timestep.get.numberOfTimestepsPerHour
else
6 # default OpenStudio timestep if none specified
end
- ramp_frequency = 1.0/steps_per_hour.to_f
+ ramp_frequency = 1.0 / steps_per_hour.to_f
end
# Go through model and create parametric formulas for all schedules
- parametric_inputs = model_setup_parametric_schedules(model,gather_data_only: true)
+ parametric_inputs = model_setup_parametric_schedules(model, gather_data_only: true)
parametric_schedules = []
model.getScheduleRulesets.sort.each do |sch|
- if !sch.hasAdditionalProperties or !sch.additionalProperties.hasFeature("param_sch_ver")
+ if !sch.hasAdditionalProperties || !sch.additionalProperties.hasFeature('param_sch_ver')
# for now don't look at schedules without targets, in future can alter these by looking at building level hours of operation
- next if not sch.directUseCount > 0 # won't catch if used for space type load instance, but that space type isn't used
- # todo - address schedules that fall into this category, if they are used in the model
+ next if sch.directUseCount <= 0 # won't catch if used for space type load instance, but that space type isn't used
+
+ # TODO: - address schedules that fall into this category, if they are used in the model
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "For #{sch.sources.first.name}, #{sch.name} is not setup as parametric schedule. It has #{sch.sources.size} sources.")
next
end
# apply parametric inputs
- schedule_apply_parametric_inputs(sch,ramp_frequency,infer_hoo_for_non_assigned_objects,error_on_out_of_order,parametric_inputs)
+ schedule_apply_parametric_inputs(sch, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order, parametric_inputs)
# add schedule to array
parametric_schedules << sch
-
end
return parametric_schedules
-
end
private
# Helper method to fill in hourly values
@@ -4998,10 +5032,11 @@
if sch_type == 'Constant'
day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), values[0])
elsif sch_type == 'Hourly'
(0..23).each do |i|
next if values[i] == values[i + 1]
+
day_sch.addValue(OpenStudio::Time.new(0, i + 1, 0, 0), values[i])
end
else
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Schedule type: #{sch_type} is not recognized. Valid choices are 'Constant' and 'Hourly'.")
end
@@ -5069,10 +5104,11 @@
# to electric resistance if it's electric
else
if electric
plant_loop.supplyComponents.each do |component|
next unless component.to_WaterHeaterMixed.is_initialized
+
water_heater = component.to_WaterHeaterMixed.get
# G3.1.11.b: If electric, WaterHeater:Mixed with electric resistance
water_heater.setHeaterFuelType('Electricity')
water_heater.setHeaterThermalEfficiency(1.0)
end
@@ -5109,12 +5145,10 @@
end
return true
end
-
-
def load_user_geometry_osm(osm_model_path:)
version_translator = OpenStudio::OSVersion::VersionTranslator.new
model = version_translator.loadModel(osm_model_path)
# Check that the model loaded successfully
@@ -5148,15 +5182,10 @@
end
end
return model
end
-
-
-
-
-
# Loads a osm as a starting point.
#
# @param osm_file [String] path to the .osm file, relative to the /data folder
# @return [Bool] returns true if successful, false if not
def load_geometry_osm(osm_file)
@@ -5205,63 +5234,62 @@
@space_type_map = @space_type_map.sort.to_h
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Loaded space type map from osm file: #{osm_model_path}")
end
end
return model
-
end
# pass array of space types or spaces
#
# @author David Goldwasser
# @param array of spaces or space types
# @return hash
- def gather_inputs_parametric_space_space_type_schedules(space_space_types,parametric_inputs,gather_data_only)
-
+ def gather_inputs_parametric_space_space_type_schedules(space_space_types, parametric_inputs, gather_data_only)
space_space_types.each do |space_type|
# get hours of operation for space type once
- next if space_type.class == "OpenStudio::Model::SpaceTypes" && space_type.floorArea == 0
+ next if space_type.class == 'OpenStudio::Model::SpaceTypes' && space_type.floorArea == 0
+
hours_of_operation = space_hours_of_operation(space_type)
if hours_of_operation.nil?
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.Standards.Model", "Can't evaluate schedules for #{space_type.name}, doesn't have hours of operation.")
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.Model', "Can't evaluate schedules for #{space_type.name}, doesn't have hours of operation.")
next
end
# loop through internal load instances
space_type.lights.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.luminaires.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.electricEquipment.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.gasEquipment.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.steamEquipment.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.otherEquipment.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.people.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
if load_inst.activityLevelSchedule.is_initialized && load_inst.activityLevelSchedule.get.to_ScheduleRuleset.is_initialized
act_sch = load_inst.activityLevelSchedule.get.to_ScheduleRuleset.get
- gather_inputs_parametric_schedules(act_sch,load_inst,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(act_sch, load_inst, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: 'hours')
end
end
space_type.spaceInfiltrationDesignFlowRates.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
space_type.spaceInfiltrationEffectiveLeakageAreas.each do |load_inst|
- gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
end
dsgn_spec_oa = space_type.designSpecificationOutdoorAir
if dsgn_spec_oa.is_initialized
- gather_inputs_parametric_load_inst_schedules(dsgn_spec_oa.get,parametric_inputs,hours_of_operation,gather_data_only)
+ gather_inputs_parametric_load_inst_schedules(dsgn_spec_oa.get, parametric_inputs, hours_of_operation, gather_data_only)
end
end
return parametric_inputs
end
@@ -5269,54 +5297,54 @@
# method to process load instance schedules for model_setup_parametric_schedules
#
# @author David Goldwasser
# @param opt_sch
# @return hash
- def gather_inputs_parametric_load_inst_schedules(load_inst,parametric_inputs,hours_of_operation,gather_data_only)
- if load_inst.class.to_s == "OpenStudio::Model::People"
+ def gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only)
+ if load_inst.class.to_s == 'OpenStudio::Model::People'
opt_sch = load_inst.numberofPeopleSchedule
- elsif load_inst.class.to_s == "OpenStudio::Model::DesignSpecificationOutdoorAir"
+ elsif load_inst.class.to_s == 'OpenStudio::Model::DesignSpecificationOutdoorAir'
opt_sch = load_inst.outdoorAirFlowRateFractionSchedule
else
opt_sch = load_inst.schedule
end
if !opt_sch.is_initialized || !opt_sch.get.to_ScheduleRuleset.is_initialized
return nil
end
- gather_inputs_parametric_schedules(opt_sch.get.to_ScheduleRuleset.get,load_inst,parametric_inputs,hours_of_operation,gather_data_only: gather_data_only)
+ gather_inputs_parametric_schedules(opt_sch.get.to_ScheduleRuleset.get, load_inst, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: 'hours')
+
return parametric_inputs
end
# method to process load instance schedules for model_setup_parametric_schedules
#
# @author David Goldwasser
# @param [sch]
# @param [hoo_var_Method] accepts hours and fractional. Any other value value will result in hoo variables not being applied
# @return [hash]
- def gather_inputs_parametric_schedules(sch,load_inst,parametric_inputs,hours_of_operation,ramp: true,min_ramp_dur_hr: 2.0,gather_data_only: false,hoo_var_method: "hours")
-
- if parametric_inputs.has_key?(sch)
+ def gather_inputs_parametric_schedules(sch, load_inst, parametric_inputs, hours_of_operation, ramp: true, min_ramp_dur_hr: 2.0, gather_data_only: false, hoo_var_method: 'hours')
+ if parametric_inputs.key?(sch)
if hours_of_operation != parametric_inputs[sch][:hoo_inputs] # don't warn if the hours of operation between old and new schedule are equivalent
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.Standards.Model", "#{load_inst.name} uses #{sch.name} but parametric inputs have already been setup based on hours of operation for #{parametric_inputs[sch][:target].name.to_s}.")
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.Model', "#{load_inst.name} uses #{sch.name} but parametric inputs have already been setup based on hours of operation for #{parametric_inputs[sch][:target].name}.")
return nil
end
end
# gather and store data for scheduleRuleset
min_max = schedule_ruleset_annual_min_max_value(sch)
- ruleset_hash = {floor: min_max['min'], ceiling: min_max['max'], target: load_inst, hoo_inputs: hours_of_operation}
+ ruleset_hash = { floor: min_max['min'], ceiling: min_max['max'], target: load_inst, hoo_inputs: hours_of_operation }
parametric_inputs[sch] = ruleset_hash
# stop here if only gathering information otherwise will continue and generate additional parametric properties for schedules and rules
if gather_data_only then return parametric_inputs end
# set scheduleRuleset properties
props = sch.additionalProperties
- props.setFeature("param_sch_ver","0.0.1") # this is needed to see if formulas are in sync with version of standards that processes them also used to flag schedule as parametric
- props.setFeature("param_sch_floor",min_max['min'])
- props.setFeature("param_sch_ceiling",min_max['max'])
+ props.setFeature('param_sch_ver', '0.0.1') # this is needed to see if formulas are in sync with version of standards that processes them also used to flag schedule as parametric
+ props.setFeature('param_sch_floor', min_max['min'])
+ props.setFeature('param_sch_ceiling', min_max['max'])
# cleanup existing profiles
schedule_ruleset_cleanup_profiles(sch)
# gather profiles
@@ -5335,22 +5363,21 @@
year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year)
year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year)
indices_vector = sch.getActiveRuleIndices(year_start_date, year_end_date)
# step through profiles and add additional properties to describe profiles
- schedule_days.each_with_index do |(schedule_day,current_rule_index),i|
-
+ schedule_days.each_with_index do |(schedule_day, current_rule_index), i|
# loop through indices looking of rule in hoo that contains days in the rule
hoo_target_index = nil
days_used = []
- indices_vector.each_with_index do |profile_index,i|
- if profile_index == current_rule_index then days_used << i+1 end
+ indices_vector.each_with_index do |profile_index, i|
+ if profile_index == current_rule_index then days_used << i + 1 end
end
# find days_used in hoo profiles that contains all days used from this profile
hoo_profile_match_hash = {}
best_fit_check = {}
- hours_of_operation.each do |profile_index,value|
+ hours_of_operation.each do |profile_index, value|
days_for_rule_not_in_hoo_profile = days_used - value[:days_used]
hoo_profile_match_hash[profile_index] = days_for_rule_not_in_hoo_profile
best_fit_check[profile_index] = days_for_rule_not_in_hoo_profile.size
if days_for_rule_not_in_hoo_profile.empty?
hoo_target_index = profile_index
@@ -5360,11 +5387,11 @@
if hoo_target_index.nil?
hoo_start = nil
hoo_end = nil
occ = nil
vac = nil
- # todo - issue warning when this happens on any profile that isn't a constant value
+ # TODO: - issue warning when this happens on any profile that isn't a constant value
else
# get hours of operation for this specific profile
hoo_start = hours_of_operation[hoo_target_index][:hoo_start]
hoo_end = hours_of_operation[hoo_target_index][:hoo_end]
occ = hours_of_operation[hoo_target_index][:hoo_hours]
@@ -5373,31 +5400,30 @@
props = schedule_day.additionalProperties
par_val_time_hash = {} # time is key, value is value in and optional value out as a one or two object array
times = schedule_day.times
values = schedule_day.values
- values.each_with_index do |value,j|
-
+ values.each_with_index do |value, j|
# don't add value until 24 if it is the same as first value for non constant profiles
if values.size > 1 && j == values.size - 1 && value == values.first
next
end
current_time = times[j].totalHours
# if step height goes floor to ceiling then do not ramp.
- if !ramp or values.uniq.size < 3
+ if !ramp || (values.uniq.size < 3)
# this will result in steps like old profiles, update to ramp in most cases
if j == values.size - 1
- par_val_time_hash[current_time] = [value,values.first]
+ par_val_time_hash[current_time] = [value, values.first]
else
- par_val_time_hash[current_time] = [value,values[j+1]]
+ par_val_time_hash[current_time] = [value, values[j + 1]]
end
else
if j == 0
prev_time = times.last.totalHours - 24 # e.g. 24 would show as until 0
else
- prev_time = times[j-1].totalHours
+ prev_time = times[j - 1].totalHours
end
if j == values.size - 1
next_time = times.first.totalHours + 24 # e.g. 6 would show as until 30
next_value = values.first
@@ -5405,19 +5431,19 @@
if value == next_value
next
end
else
- next_time = times[j+1].totalHours
- next_value = values[j+1]
+ next_time = times[j + 1].totalHours
+ next_value = values[j + 1]
end
# delta time is min min_ramp_dur_hr, half of previous dur, half of next dur
# todo - would be nice to change to 0.25 for vally less than 2 hours
multiplier = 0.5
- delta = [min_ramp_dur_hr,(current_time - prev_time)*multiplier,(next_time - current_time)*multiplier].min
+ delta = [min_ramp_dur_hr, (current_time - prev_time) * multiplier, (next_time - current_time) * multiplier].min
# add value to left if not already added
- if !par_val_time_hash.has_key?(current_time - delta)
+ if !par_val_time_hash.key?(current_time - delta)
time_left = current_time - delta
if time_left < 0.0 then time_left += 24.0 end
par_val_time_hash[time_left] = [value]
end
# add value to right
@@ -5430,32 +5456,31 @@
# sort hash by keys
par_val_time_hash.sort.to_h
# calculate estimated value (not including any secondary logic)
est_daily_flh = 0.0
- prev_time = par_val_time_hash.keys.sort.last - 24.0
+ prev_time = par_val_time_hash.keys.max - 24.0
prev_value = par_val_time_hash.values.last.last # last value in last optional pair of values
- par_val_time_hash.sort.each do |time,value_array|
+ par_val_time_hash.sort.each do |time, value_array|
segment_length = time - prev_time
- avg_value = (value_array.first + prev_value)*0.5
+ avg_value = (value_array.first + prev_value) * 0.5
est_daily_flh += segment_length * avg_value
prev_time = time
prev_value = value_array.last
end
# test expected value against estimated value
daily_flh = day_schedule_equivalent_full_load_hrs(schedule_day)
- percent_change = ((daily_flh - est_daily_flh)/daily_flh) * 100.0
+ percent_change = ((daily_flh - est_daily_flh) / daily_flh) * 100.0
if percent_change.abs > 0.05
- # todo - this estimation can have flaws. Fix or remove it, make sure to update for secondary logic (if we implement that here)
+ # TODO: - this estimation can have flaws. Fix or remove it, make sure to update for secondary logic (if we implement that here)
# post application checks compares against actual instead of estimated values
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "For day schedule #{schedule_day.name} in #{sch.name} there was a #{percent_change.round(4)}% change. Expected full load hours is #{daily_flh.round(4)}, but estimated value is #{est_daily_flh.round(4)}")
end
raw_string = []
- par_val_time_hash.sort.each do |time,value_array|
-
+ par_val_time_hash.sort.each do |time, value_array|
# add in value variables
# not currently using range, only using min max for constant schedules or schedules with just two values
value_array_var = []
value_array.each do |val|
if val == min_max['min'] && values.uniq.size < 3
@@ -5474,106 +5499,104 @@
# includes code to identify delta for wrap around of 24
formula_identifier = {}
start_delta_array = [hoo_start - time, hoo_start - time + 24, hoo_start - time - 24]
start_delta_array_abs = [(hoo_start - time).abs, (hoo_start - time + 24).abs, (hoo_start - time - 24).abs]
start_delta_h = start_delta_array[start_delta_array_abs.index(start_delta_array_abs.min)]
- formula_identifier["start"] = start_delta_h
+ formula_identifier['start'] = start_delta_h
mid_calc = hoo_start + occ * 0.5
mid_delta_array = [mid_calc - time, mid_calc - time + 24, mid_calc - time - 24]
mid_delta_array_abs = [(mid_calc - time).abs, (mid_calc - time + 24).abs, (mid_calc - time - 24).abs]
mid_delta_h = mid_delta_array[mid_delta_array_abs.index(mid_delta_array_abs.min)]
- formula_identifier["mid"] = mid_delta_h
+ formula_identifier['mid'] = mid_delta_h
end_delta_array = [hoo_end - time, hoo_end - time + 24, hoo_end - time - 24]
end_delta_array_abs = [(hoo_end - time).abs, (hoo_end - time + 24).abs, (hoo_end - time - 24).abs]
end_delta_h = end_delta_array[end_delta_array_abs.index(end_delta_array_abs.min)]
- formula_identifier["end"] = end_delta_h
+ formula_identifier['end'] = end_delta_h
# need to store min absolute value to pick the best fit
formula_identifier_min_abs = {}
- formula_identifier.each do |k,v|
+ formula_identifier.each do |k, v|
formula_identifier_min_abs[k] = v.abs
end
# pick from possible formula approaches for any datapoint where x is hour value
min_key = formula_identifier_min_abs.key(formula_identifier_min_abs.values.min)
min_value = formula_identifier[min_key]
- if hoo_var_method == "hours"
+ if hoo_var_method == 'hours'
# minimize x, which should be no greater than 12, see if rounding to 2 decimal places works
min_value = min_value.round(2)
- if min_key == "start"
+ if min_key == 'start'
if min_value == 0
- time = "hoo_start"
+ time = 'hoo_start'
elsif min_value < 0
time = "hoo_start + #{min_value.abs}"
else # greater than 0
time = "hoo_start - #{min_value}"
end
- elsif min_key == "mid"
+ elsif min_key == 'mid'
if min_value == 0
- time = "mid"
+ time = 'mid'
# converted to variable for simplicity but could also be described like this
# time = "hoo_start + occ * 0.5"
elsif min_value < 0
time = "mid + #{min_value.abs}"
else # greater than 0
time = "mid - #{min_value}"
end
else # min_key == "end"
if min_value == 0
- time = "hoo_end"
+ time = 'hoo_end'
elsif min_value < 0
time = "hoo_end + #{min_value.abs}"
else # greater than 0
time = "hoo_end - #{min_value}"
end
end
- elsif hoo_var_method == "fractional"
+ elsif hoo_var_method == 'fractional'
# minimize x(hour before converted to fraction), which should be no greater than 0.5 as fraction, see if rounding to 3 decimal places works
if occ > 0
- min_value_occ_fract = min_value.abs/occ
+ min_value_occ_fract = min_value.abs / occ
else
min_value_occ_fract = 0.0
end
if vac > 0
- min_value_vac_fract = min_value.abs/vac
+ min_value_vac_fract = min_value.abs / vac
else
min_value_vac_fract = 0.0
end
- if min_key == "start"
+ if min_key == 'start'
if min_value == 0
- time = "hoo_start"
+ time = 'hoo_start'
elsif min_value < 0
time = "hoo_start + occ * #{min_value_occ_fract.round(3)}"
else # greater than 0
time = "hoo_start - vac * #{min_value_vac_fract.round(3)}"
end
- elsif min_key == "mid"
- # todo - see what is going wrong with after mid in formula
+ elsif min_key == 'mid'
+ # TODO: - see what is going wrong with after mid in formula
if min_value == 0
- time = "mid"
+ time = 'mid'
# converted to variable for simplicity but could also be described like this
# time = "hoo_start + occ * 0.5"
elsif min_value < 0
time = "mid + occ * #{min_value_occ_fract.round(3)}"
else # greater than 0
time = "mid - occ * #{min_value_occ_fract.round(3)}"
end
else # min_key == "end"
if min_value == 0
- time = "hoo_end"
+ time = 'hoo_end'
elsif min_value < 0
time = "hoo_end + vac * #{min_value_vac_fract.round(3)}"
else # greater than 0
time = "hoo_end - occ * #{min_value_occ_fract.round(3)}"
end
end
- else # "none"
- # do not add in hoo variables
end
end
# populate string
@@ -5583,36 +5606,42 @@
raw_string << "#{time} ~ #{value_array_var.first} ~ #{value_array_var.last}"
end
end
# store profile formula with hoo and value variables
- props.setFeature("param_day_profile",raw_string.join(" | "))
+ props.setFeature('param_day_profile', raw_string.join(' | '))
- # todo - not used yet, but will add methods described below and others
+ # TODO: - not used yet, but will add methods described below and others
# todo - lower infiltration based on air loop hours of operation if air loop has outdoor air object
# todo - lower lighting or plug loads based on occupancy at given time steps in a space
# todo - set elevator fraction based multiple factors such as trips, occupants per trip, and elevator type to determine floor consumption when not in use.
- props.setFeature("param_day_secondary_logic","") # secondary logic method such as occupancy impacting schedule values
- props.setFeature("param_day_secondary_logic_arg_val","") # optional argument used for some secondary logic applied to values
+ props.setFeature('param_day_secondary_logic', '') # secondary logic method such as occupancy impacting schedule values
+ props.setFeature('param_day_secondary_logic_arg_val', '') # optional argument used for some secondary logic applied to values
# tag profile type
# may be useful for parametric changes to tag typical, medium, minimal, or same ones with off_peak prefix
# todo - I would like to use these same tags for hours of operation and have parametric tags then ignore the days of week and date range from the rule object
# tagging min/max makes sense in fractional schedules but not temperature schedules like thermostats (specifically cooling setpoints)
# todo - I think these tags should come from occpancy schedule for space(s) schedule. That way all schedules in a space will refer to same profile from hours of operation
# todo - add school specific logic hear or in post processing, currently default profile for school may not be most prevalent one
if current_rule_index == -1
- props.setFeature("param_day_tag","typical_operation")
+ props.setFeature('param_day_tag', 'typical_operation')
elsif daily_flh == daily_flhs.min
- props.setFeature("param_day_tag","minimal_operation")
+ props.setFeature('param_day_tag', 'minimal_operation')
elsif daily_flh == daily_flhs.max
- props.setFeature("param_day_tag","maximum_operation") # normally this should not be used as typical should be the most active day
+ props.setFeature('param_day_tag', 'maximum_operation') # normally this should not be used as typical should be the most active day
else
- props.setFeature("param_day_tag","medium_operation") # not min max or typical
+ props.setFeature('param_day_tag', 'medium_operation') # not min max or typical
end
-
end
return parametric_inputs
end
+ # Default SAT reset type
+ #
+ # @param air_loop_hvac [OpenStudio::model::AirLoopHVAC] Airloop
+ # @return [String] Returns type of SAT reset
+ def air_loop_hvac_supply_air_temperature_reset_type(air_loop_hvac)
+ return 'warmest_zone'
+ end
end