Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

include ruleset functions #1823

Draft
wants to merge 2 commits into
base: AppendixG_Dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion lib/openstudio-standards/standards/Standards.Model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ def model_create_prm_any_baseline_building(user_model, building_type, climate_zo

# Check proposed model unmet load hours
if unmet_load_hours_check
# Set proposed model export data in json format
OpenStudioStandards::RulesetChecking.export_json_output(proposed_model)

# Run user model; need annual simulation to get unmet load hours
if model_run_simulation_and_log_errors(proposed_model, run_dir = "#{sizing_run_dir}/PROP")
umlh = OpenstudioStandards::SqlFile.model_get_annual_occupied_unmet_hours(proposed_model)
Expand All @@ -128,8 +131,17 @@ def model_create_prm_any_baseline_building(user_model, building_type, climate_zo
proposed_model.save(OpenStudio::Path.new("#{sizing_run_dir}/proposed_final.osm"), true)
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
idf = forward_translator.translateModel(proposed_model)

proposed_model.getSpaces.sort.each do |space|
space_cond_type = space_conditioning_category(space)
next if space_cond_type == 'Unconditioned'
OpenStudioStandards::RulesetChecking.tag_spaces(idf, space)
end
idf_path = OpenStudio::Path.new("#{sizing_run_dir}/proposed_final.idf")
idf.save(idf_path, true)

# export to epjson
OpenStudioStandards::RulesetChecking.export_epjson(proposed_model, sizing_run_dir, 'proposed_final')
end

# Define different orientation from original orientation
Expand Down Expand Up @@ -494,15 +506,21 @@ def model_create_prm_any_baseline_building(user_model, building_type, climate_zo
# @todo: turn off self shading
# Set Solar Distribution to MinimalShadowing... problem is when you also have detached shading such as surrounding buildings etc
# It won't be taken into account, while it should: only self shading from the building itself should be turned off but to my knowledge there isn't a way to do this in E+

model_status = degs > 0 ? "baseline_final_#{degs}" : 'baseline_final'
OpenStudioStandards::RulesetChecking.export_json_output(model)
model.save(OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.osm"), true)

# Translate to IDF and save for debugging
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
idf = forward_translator.translateModel(model)
model.getSpaces.sort.each do |space|
space_cond_type = space_conditioning_category(space)
next if space_cond_type == 'Unconditioned'
OpenStudioStandards::RulesetChecking.tag_spaces(idf, space)
end
idf_path = OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.idf")
idf.save(idf_path, true)
OpenStudioStandards::RulesetChecking.export_epjson(model, sizing_run_dir, "#{model_status}")

# Check unmet load hours
if unmet_load_hours_check
Expand Down
243 changes: 125 additions & 118 deletions lib/openstudio-standards/utilities/ruleset_check.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,88 @@
module OpenStudioStandards
module RulesetChecking
# building type hash map
BUILDING_MAP = {
'All others' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'ALL_OTHERS'
},
'Grocery store' => {
'lighting' => 'SALES_AREA',
'ventilation' => 'RETAIL_SUPERMARKET',
'shw' => 'ALL_OTHERS'
},
'Healthcare (outpatient)' => {
'lighting' => 'HEALTHCARE_FACILITY_PATIENT_ROOM',
'ventilation' => 'OUTPATIENT_HEALTH_CARE_FACILITIES_URGENT_CARE_EXAMINATION_ROOM',
'shw' => 'HEALTH_CARE_CLINIC'
},
'Hospital' => {
'lighting' => 'HEALTHCARE_FACILITY_PATIENT_ROOM',
'ventilation' => 'OUTPATIENT_HEALTH_CARE_FACILITIES_GENERAL_EXAMINATION_ROOM',
'shw' => 'HOSPITAL_AND_OUTPATIENT_SURGERY'
},
'Hotel/motel <= 75 rooms' => {
'lighting' => 'GUEST_ROOM',
'ventilation' => 'HOTELS_MOTELS_RESORTS_DORMITORIES_BEDROOM_LIVING_ROOM',
'shw' => 'HOTEL'
},
'Hotel/motel > 75 rooms' => {
'lighting' => 'GUEST_ROOM',
'ventilation' => 'HOTELS_MOTELS_RESORTS_DORMITORIES_BEDROOM_LIVING_ROOM',
'shw' => 'HOTEL'
},
'Office 5,000 to 50,000 sq ft' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'OFFICE'
},
'Office <= 5,000 sq ft' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'OFFICE'
},
'Office > 50,000 sq ft' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'OFFICE'
},
'Restaurant (full service)' => {
'lighting' => 'DINING_AREA_ALL_OTHERS',
'ventilation' => 'FOOD_AND_BEVERAGE_SERVICE_RESTAURANT_DINING_ROOMS',
'shw' => 'DINING_FAMILY'
},
'Restaurant (quick service)' => {
'lighting' => 'DINING_AREA_CAFETERIA_OR_FAST_FOOD_DINING',
'ventilation' => 'FOOD_AND_BEVERAGE_SERVICE_CAFETERIA_FAST_FOOD_DINING',
'shw' => 'DINING_BAR_LOUNGE_LEISURE'
},
'Retail (stand alone)' => {
'lighting' => 'RETAIL_FACILITIES_DRESSING_FITTING_ROOM',
'ventilation' => 'RETAIL_SALES_EXCEPT_OTHER_SPECIFIC_RETAIL',
'shw' => 'shw_type_retail_stand_alone'
},
'Retail (strip mall)' => {
'lighting' => 'RETAIL_FACILITIES_MALL_CONCOURSE',
'ventilation' => 'RETAIL_MALL_COMMON_AREAS',
'shw' => 'RETAIL'
},
'School (primary)' => {
'lighting' => 'CLASSROOM_LECTURE_HALL_TRAINING_ROOM_SCHOOL',
'ventilation' => 'EDUCATIONAL_FACILITIES_CLASSROOMS_AGES_5_TO_8',
'shw' => 'SCHOOL_UNIVERSITY'
},
'School (secondary and university)' => {
'lighting' => 'CLASSROOM_LECTURE_HALL_TRAINING_ROOM_SCHOOL',
'ventilation' => 'EDUCATIONAL_FACILITIES_CLASSROOMS_AGE_9_PLUS',
'shw' => 'SCHOOL_UNIVERSITY'
},
'Warehouse (nonrefrigerated)' => {
'lighting' => 'WAREHOUSE_STORAGE_AREA_MEDIUM_TO_BULKY_PALLETIZED_ITEMS',
'ventilation' => 'MISCELLANEOUS_SPACES_WAREHOUSES',
'shw' => 'WAREHOUSE'
}
}
# Export OpenStudio model to epJson file.
#
# @param model [OpenStudio::Model::Model]
Expand All @@ -11,7 +94,7 @@ def self.export_epjson(model, save_dir, model_name)
workspace = forward_translator.translateModel(model)
workspace_epjson_str = OpenStudio::EPJSON::toJSONString(workspace)
begin
output_path = "#{save_dir}/#{model_name}.json"
output_path = "#{save_dir}/#{model_name}.epjson"
epjson_model = File.open(output_path, 'w')
epjson_model.puts(workspace_epjson_str)
rescue StandardError => e
Expand All @@ -28,108 +111,21 @@ def self.export_epjson(model, save_dir, model_name)
# Which requires the import of IDF model and OpenStudio model
#
# @param idf_model [OpenStudio::IDF]
# @param model [OpenStudio::Model::Model]
# @param space [OpenStudio::Model::Space]
# @return [Boolean]
def self.tag_spaces(idf_model, model)
building_map = {
'All others' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'ALL_OTHERS'
},
'Grocery store' => {
'lighting' => 'SALES_AREA',
'ventilation' => 'RETAIL_SUPERMARKET',
'shw' => 'ALL_OTHERS'
},
'Healthcare (outpatient)' => {
'lighting' => 'HEALTHCARE_FACILITY_PATIENT_ROOM',
'ventilation' => 'OUTPATIENT_HEALTH_CARE_FACILITIES_URGENT_CARE_EXAMINATION_ROOM',
'shw' => 'HEALTH_CARE_CLINIC'
},
'Hospital' => {
'lighting' => 'HEALTHCARE_FACILITY_PATIENT_ROOM',
'ventilation' => 'OUTPATIENT_HEALTH_CARE_FACILITIES_GENERAL_EXAMINATION_ROOM',
'shw' => 'HOSPITAL_AND_OUTPATIENT_SURGERY'
},
'Hotel/motel <= 75 rooms' => {
'lighting' => 'GUEST_ROOM',
'ventilation' => 'HOTELS_MOTELS_RESORTS_DORMITORIES_BEDROOM_LIVING_ROOM',
'shw' => 'HOTEL'
},
'Hotel/motel > 75 rooms' => {
'lighting' => 'GUEST_ROOM',
'ventilation' => 'HOTELS_MOTELS_RESORTS_DORMITORIES_BEDROOM_LIVING_ROOM',
'shw' => 'HOTEL'
},
'Office 5,000 to 50,000 sq ft' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'OFFICE'
},
'Office <= 5,000 sq ft' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'OFFICE'
},
'Office > 50,000 sq ft' => {
'lighting' => 'OFFICE_OPEN_PLAN',
'ventilation' => 'OFFICE_BUILDINGS_OFFICE_SPACE',
'shw' => 'OFFICE'
},
'Restaurant (full service)' => {
'lighting' => 'DINING_AREA_ALL_OTHERS',
'ventilation' => 'FOOD_AND_BEVERAGE_SERVICE_RESTAURANT_DINING_ROOMS',
'shw' => 'DINING_FAMILY'
},
'Restaurant (quick service)' => {
'lighting' => 'DINING_AREA_CAFETERIA_OR_FAST_FOOD_DINING',
'ventilation' => 'FOOD_AND_BEVERAGE_SERVICE_CAFETERIA_FAST_FOOD_DINING',
'shw' => 'DINING_BAR_LOUNGE_LEISURE'
},
'Retail (stand alone)' => {
'lighting' => 'RETAIL_FACILITIES_DRESSING_FITTING_ROOM',
'ventilation' => 'RETAIL_SALES_EXCEPT_OTHER_SPECIFIC_RETAIL',
'shw' => 'shw_type_retail_stand_alone'
},
'Retail (strip mall)' => {
'lighting' => 'RETAIL_FACILITIES_MALL_CONCOURSE',
'ventilation' => 'RETAIL_MALL_COMMON_AREAS',
'shw' => 'RETAIL'
},
'School (primary)' => {
'lighting' => 'CLASSROOM_LECTURE_HALL_TRAINING_ROOM_SCHOOL',
'ventilation' => 'EDUCATIONAL_FACILITIES_CLASSROOMS_AGES_5_TO_8',
'shw' => 'SCHOOL_UNIVERSITY'
},
'School (secondary and university)' => {
'lighting' => 'CLASSROOM_LECTURE_HALL_TRAINING_ROOM_SCHOOL',
'ventilation' => 'EDUCATIONAL_FACILITIES_CLASSROOMS_AGE_9_PLUS',
'shw' => 'SCHOOL_UNIVERSITY'
},
'Warehouse (nonrefrigerated)' => {
'lighting' => 'WAREHOUSE_STORAGE_AREA_MEDIUM_TO_BULKY_PALLETIZED_ITEMS',
'ventilation' => 'MISCELLANEOUS_SPACES_WAREHOUSES',
'shw' => 'WAREHOUSE'
}
}
model.getSpaces.sort.each do |space|
space_cond_type = space_conditioning_category(space)
next if space_cond_type == 'Unconditioned'
def self.tag_spaces(idf_model, space)
# all spaces from PRMs shall have the additional properties of building_type_for_wwr.
# In this implementation, we will use this data to populate the rest of space types.
bldg_type_wwr = get_additional_property_as_string(space, 'building_type_for_wwr', 'All others')
bldg_types = BUILDING_MAP[bldg_type_wwr]
idf_space = idf_model.getObjectsByName(space.name.to_s)[0]
space_tag_index = OpenStudioStandards::RulesetChecking::find_index_of_string(idf_space, 'Space Type')
tag_1_index = space_tag_index + 1
tag_2_index = space_tag_index + 2

# all spaces from PRMs shall have the additional properties of building_type_for_wwr.
# In this implementation, we will use this data to populate the rest of space types.
bldg_type_wwr = get_additional_property_as_string(space, 'building_type_for_wwr', 'All others')
bldg_types = building_map[bldg_type_wwr]
idf_space = idf_model.getObjectsByName(space.name.to_s)[0]
space_tag_index = find_index_of_string(idf_space, 'Space Type')
tag_1_index = space_tag_index + 1
tag_2_index = space_tag_index + 2

idf_space.setString(space_tag_index, bldg_types['lighting'])
idf_space.setString(tag_1_index, bldg_types['ventilation'])
idf_space.setString(tag_2_index, bldg_types['shw'])
end
idf_space.setString(space_tag_index, bldg_types['lighting'])
idf_space.setString(tag_1_index, bldg_types['ventilation'])
idf_space.setString(tag_2_index, bldg_types['shw'])
end

# Revise the output variables to prepare data export for RPD generation
Expand Down Expand Up @@ -170,28 +166,39 @@ def self.export_json_output(model)
output_variable.setReportingFrequency('hourly')
output_variable.setKeyValue('*')
end
end

# Find the index of the E+ object which has a commented line with the search string
# (e.g. find the index of "!- Fan Inlet Node Name")
# @param idf_object
# @param search_string str
# @return index if found
def self.find_index_of_string(idf_object, search_string)
# Count the number of lines of a idf object and return the last index
# (e.g. find the index of "!- Fan Inlet Node Name")
# @param idf_object
# @param search_string str
# @return index if found
def self.find_last_index_of_idf_object(idf_object)
split_object = idf_object.to_s.split("\n")
# remove the object title.
split_object.length - 1
end

# Find the index of the E+ object which has a commented line with the search string
# If the index is not found, this function returns the last index of the object + 1
# (e.g. find the index of "!- Fan Inlet Node Name")
# @param idf_object
# @param search_string str
# @return index if found
def self.find_index_of_string(idf_object, search_string)

split_object = idf_object.to_s.split("\n")
split_object = idf_object.to_s.split("\n")

index_counter = 0
index_counter = 0

split_object.each do |line|
if line.include? search_string
# subtract 1 because the first line is the object type, not a index
return index_counter - 1
else
index_counter += 1
split_object.each do |line|
if line.include? search_string
# subtract 1 because the first line is the object type, not a index
return index_counter - 1
else
index_counter += 1
end
end
find_last_index_of_idf_object(idf_object)
end

raise "Search string: '#{search_string}' not found in #{idf_object.to_s}"
end
end