# Custom changes for the TallBuilding prototype. # These are changes that are inconsistent with other prototype # building types. module TallBuilding def model_custom_hvac_tweaks(building_type, climate_zone, prototype_input, model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started building type specific adjustments') # TODO: make additional parameters mutable by the user # the number of floors for each function type is defined in additional_params additional_params = { num_of_floor_retail: 2, num_of_floor_office: 18, num_of_floor_residential: 9, num_of_floor_hotel: 9 } # add tall building elevators to the elevator machine room add_elevator_system_loads(model, additional_params) # for tall and super tall buildings, add main (multiple) and booster swh here instead of model_add_swh add_swh_tall_bldg(model, prototype_input, additional_params) # # update the infiltration coefficients of tall buildings based on Lisa Ng's research (from NIST) # # The set of coefficients are not quite appropriate for tall buildings, leading to super high infiltration rate # # TODO: further infiltration research is needed # update_infil_coeff(model) # apply vertical weather variations to tall buildings apply_vertical_weather_variation(model) # add thermostat to highriseapartment corridors add_thermostat_to_corridor(model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished building type specific adjustments') return true end # Add elevators to elevator machine room # schedules: # Large Office BLDG ELEVATORS, OfficeLarge ELEV_LIGHT_FAN_SCH_ADD_DF # HotelLarge BLDG_ELEVATORS, HotelLarge ELEV_LIGHT_FAN_SCH_ADD_DF # ApartmentMidRise BLDG_ELEVATORS, ApartmentMidRise ELEV_LIGHT_FAN_SCH_24_7 # Retail elevator schedule developed def add_elevator_system_loads(model, additional_params) # get the elevator machine room space from the model if model.getSpaceTypeByName('Elevator Machine Room').empty? OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'No Elevator Machine Room spacetype was found.') return false else elev_mc_rooms = model.getSpaceTypeByName('Elevator Machine Room').get.spaces if elev_mc_rooms.size > 1 OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'More than one elevator machine room in the model.') return false elsif elev_mc_rooms.empty? OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'No elevator machine room in the model.') return false else elev_mc_room = elev_mc_rooms[0] end end # calculate the number of elevators needed for each function type num_retail_flr = additional_params[:num_of_floor_retail].to_i num_office_flr = additional_params[:num_of_floor_office].to_i num_resi_flr = additional_params[:num_of_floor_residential].to_i num_hotel_flr = additional_params[:num_of_floor_hotel].to_i area_per_flr = 20000 # 20000 ft2 per floor motor_power_per_elev = 28300 # See scorecard Tall Building fan_light_power_per_elev = 161.9 # See scorecard Tall Building num_elev_retail = (num_retail_flr * area_per_flr / 45000.0).ceil num_elev_office = (num_office_flr * area_per_flr / 45000.0).ceil num_elev_resi = (num_resi_flr * area_per_flr / 45000.0).ceil num_elev_hotel = (num_hotel_flr * area_per_flr / 45000.0).ceil # create the equipment object for elevator motor and fan/lights separately, for each function type # Elevator lift motor add_elevator_equip(model, elev_mc_room, num_elev_retail, 'Retail', motor_power_per_elev, fan_light_power_per_elev, 'RetailStandalone BLDG_ELEVATORS', 'RetailStandalone ELEV_LIGHT_FAN_SCH_24_7') add_elevator_equip(model, elev_mc_room, num_elev_office, 'Office', motor_power_per_elev, fan_light_power_per_elev, 'OfficeLarge BLDG_ELEVATORS', 'OfficeLarge ELEV_LIGHT_FAN_SCH_24_7') add_elevator_equip(model, elev_mc_room, num_elev_resi, 'Apartment', motor_power_per_elev, fan_light_power_per_elev, 'ApartmentMidRise BLDG_ELEVATORS', 'ApartmentHighRise ELEV_LIGHT_FAN_SCH_24_7') add_elevator_equip(model, elev_mc_room, num_elev_hotel, 'Hotel', motor_power_per_elev, fan_light_power_per_elev, 'HotelLarge BLDG_ELEVATORS', 'HotelLarge ELEV_LIGHT_FAN_SCH_24_7') end def add_elevator_equip(model, space, num_of_elev, function_type, motor_power_per_elev, fan_light_power_per_elev, elev_power_sch_name, elev_fan_light_sch_name) motor_equip_frac_loss = 0.85 motor_equip_frac_radiant = 0.05 fan_light_equip_frac_radiant = 0.5 elevator_definition = OpenStudio::Model::ElectricEquipmentDefinition.new(model) elevator_definition.setName('Elevator Motor') elevator_definition.setDesignLevel(motor_power_per_elev * num_of_elev) elevator_definition.setFractionLost(motor_equip_frac_loss) elevator_definition.setFractionRadiant(motor_equip_frac_radiant) elevator_equipment = OpenStudio::Model::ElectricEquipment.new(elevator_definition) elevator_equipment.setName("#{num_of_elev} Elevator Motors for #{function_type}") elevator_equipment.setEndUseSubcategory('Elevators') elevator_sch = model_add_schedule(model, elev_power_sch_name) elevator_equipment.setSchedule(elevator_sch) elevator_equipment.setSpace(space) # Elevator fan and lights elevator_fan_definition = OpenStudio::Model::ElectricEquipmentDefinition.new(model) elevator_fan_definition.setName('Elevator Fan') elevator_fan_definition.setDesignLevel(fan_light_power_per_elev * num_of_elev) elevator_fan_definition.setFractionRadiant(fan_light_equip_frac_radiant) elevator_fan_equipment = OpenStudio::Model::ElectricEquipment.new(elevator_fan_definition) elevator_fan_equipment.setName("#{num_of_elev} Elevator Fans for #{function_type}") elevator_fan_equipment.setEndUseSubcategory('Elevators') elevator_fan_sch = model_add_schedule(model, elev_fan_light_sch_name) elevator_fan_equipment.setSchedule(elevator_fan_sch) elevator_fan_equipment.setSpace(space) end # for tall and super tall buildings, add main (multiple) and booster swh in model_custom_hvac_tweaks def add_swh_tall_bldg(model, prototype_input, additional_params) # get all building stories and rank based on Z-origin story_info = {} model.getBuildingStorys.sort.each do |story| next if story.name.to_s.include? 'ElevatorMachineRm' story_info[story.name.to_s] = {} story_info[story.name.to_s]['z_coordinate'] = story.nominalZCoordinate.get.to_f story_info[story.name.to_s]['multiplier'] = story.spaces[0].multiplier end stories_ranked = story_info.sort_by { |story_name, story| story['z_coordinate'] } # combine stories that add up to no more than 12 floors swh_system_stories = [] num_of_stories = 0 # initial hotel_swh_loop = nil stories_ranked.each_with_index do |story_pair, index| story_multiplier = story_pair[1]['multiplier'] combined_num_of_story = num_of_stories + story_multiplier # if the top story (last one), combine into the last swh loop if combined_num_of_story <= 12 || (index == stories_ranked.size - 1) # 12 is based on large office prototype model swh_system_stories.push(story_pair[0]) num_of_stories = combined_num_of_story end # if the top story (last one), create swh loop if combined_num_of_story > 12 || (index == stories_ranked.size - 1) # when combined stories reaches limitation, create the SWH system swh_fueltype = prototype_input['main_water_heater_fuel'] # Add the main service water loop if swh_system_stories.size == 1 swh_loop_name = "#{swh_system_stories[0].split(' story')[0]}} Service Water Loop" elsif swh_system_stories.size > 1 swh_loop_name = "#{swh_system_stories[0].split(' story')[0]} to #{swh_system_stories[-1].split(' story')[0]} Service Water Loop" else OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'No story info in the SWH loop.') return false end main_swh_loop = model_add_swh_loop(model, swh_loop_name, nil, OpenStudio.convert(prototype_input['main_service_water_temperature'], 'F', 'C').get, prototype_input['main_service_water_pump_head'].to_f, prototype_input['main_service_water_pump_motor_efficiency'], OpenStudio.convert(prototype_input['main_water_heater_capacity'], 'Btu/hr', 'W').get, OpenStudio.convert(prototype_input['main_water_heater_volume'], 'gal', 'm^3').get, swh_fueltype, OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get) # Attach the end uses based on floor function type # Office and retail: add to mechanical room only # Hotel and apartment: add to each space swh_system_stories.each do |story_name| OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Model', 'Adding shw by story spaces for Tall Building') hotel_swh_loop = main_swh_loop if story_name.include? 'Hotel_top' # locate the swh loop that supplies hotel top floor, where kitchen is. For booster # Log how many water fixtures are added water_fixtures = [] story = model.getBuildingStoryByName(story_name).get story.spaces.each do |space| next if space.name.to_s.downcase.include? 'plenum' search_criteria = { 'template' => template, 'building_type' => space.spaceType.get.standardsBuildingType.get, 'space_type' => space.spaceType.get.standardsSpaceType.get } data = standards_lookup_table_first(table_name: 'space_types', search_criteria: search_criteria) # Skip space types with no data next if data.nil? # Skip space types with no water use, unless it is a NECB archetype (these do not have peak flow rates defined) next if data['service_water_heating_peak_flow_rate'].to_f == 0.0 && data['service_water_heating_peak_flow_per_area'].to_f == 0.0 # Add a service water use for each space space_multiplier = space.multiplier water_fixture = model_add_swh_end_uses_by_space(model, main_swh_loop, space, space_multiplier) unless water_fixture.nil? water_fixtures << water_fixture end end OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{water_fixtures.size} water fixtures to SWH loop #{swh_loop_name}") end # reset to swh_system_stories = [story_pair[0]] num_of_stories = story_multiplier end end # Add the booster water loop if there is any hotel floor if additional_params[:num_of_floor_hotel].to_i > 0 swh_booster_loop = model_add_swh_booster(model, hotel_swh_loop, OpenStudio.convert(prototype_input['booster_water_heater_capacity'], 'Btu/hr', 'W').get, OpenStudio.convert(prototype_input['booster_water_heater_volume'], 'gal', 'm^3').get, prototype_input['booster_water_heater_fuel'], OpenStudio.convert(prototype_input['booster_water_temperature'], 'F', 'C').get, 0, nil) # Attach the end uses model_add_booster_swh_end_uses(model, swh_booster_loop, OpenStudio.convert(prototype_input['booster_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get, prototype_input['booster_service_water_flowrate_schedule'], OpenStudio.convert(prototype_input['booster_water_use_temperature'], 'F', 'C').get) end # for tall and super tall buildings, there is laundry only if hotel has more than 1 floors # hotel_bot has laundry, if only one floor, doesn't have hotel_bot if additional_params[:num_of_floor_hotel].to_i > 1 # Add the laundry service water heating loop laundry_swh_loop = model_add_swh_loop(model, 'Laundry Service Water Loop', nil, OpenStudio.convert(prototype_input['laundry_service_water_temperature'], 'F', 'C').get, prototype_input['laundry_service_water_pump_head'].to_f, prototype_input['laundry_service_water_pump_motor_efficiency'], OpenStudio.convert(prototype_input['laundry_water_heater_capacity'], 'Btu/hr', 'W').get, OpenStudio.convert(prototype_input['laundry_water_heater_volume'], 'gal', 'm^3').get, prototype_input['laundry_water_heater_fuel'], OpenStudio.convert(prototype_input['laundry_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get) # Attach the end uses if specified in prototype inputs model_add_swh_end_uses(model, 'Laundry', laundry_swh_loop, OpenStudio.convert(prototype_input['laundry_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get, prototype_input['laundry_service_water_flowrate_schedule'], OpenStudio.convert(prototype_input['laundry_water_use_temperature'], 'F', 'C').get, nil) end end # update the infiltration coefficients of tall buildings based on Lisa Ng's research (from NIST) def update_infil_coeff(model) # System On | System Off # A: -0.03973203 | 0 # B: 0.02282265 | 0.031045705 # D: 0.083767524 | 0.016210069 # Step1: replace the original infiltration schedule to airloop availability schedule, change the coeff to System On set # Step2: add new infiltration obj, assign with HVAC off schedule, assign the coeff to System Off set. # hotel and apartment HVAC are always on, so just do Step1, no Step2 coeff_a_on, coeff_b_on, coeff_d_on = -0.03973203, 0.02282265, 0.083767524 coeff_a_off, coeff_b_off, coeff_d_off = 0.0, 0.031045705, 0.016210069 office_hvac_sch = model_add_schedule(model, 'OfficeLarge HVACOperationSchd') office_hvac_off_sch = model_add_schedule(model, 'OfficeLarge HVACOperationOFFSchd') retail_hvac_sch = model_add_schedule(model, 'RetailStandalone HVACOperationSchd') retail_hvac_off_sch = model_add_schedule(model, 'RetailStandalone HVACOperationOFFSchd') resi_hvac_sch = model_add_schedule(model, 'Always On') hotel_hvac_sch = model_add_schedule(model, 'HotelLarge HVACOperationSchd') model.getSpaceInfiltrationDesignFlowRates.sort.each do |infiltration| orin_infil_name = infiltration.name.to_s hvac_sch = nil hvac_off_sch = nil space_name = infiltration.space.get.name.to_s space_name = space_name.split(' ')[-1] if space_name.start_with? 'Office' hvac_sch = office_hvac_sch hvac_off_sch = office_hvac_off_sch elsif space_name.start_with? 'Retail' hvac_sch = retail_hvac_sch hvac_off_sch = retail_hvac_off_sch elsif space_name.start_with? 'Resi' hvac_sch = resi_hvac_sch elsif space_name.start_with? 'Hotel' hvac_sch = hotel_hvac_sch end unless hvac_sch.nil? infiltration.setName(orin_infil_name + ' HVAC On') infiltration.setSchedule(hvac_sch) end # coeff will be updated anyway infiltration.setConstantTermCoefficient(coeff_a_on) infiltration.setTemperatureTermCoefficient(coeff_b_on) infiltration.setVelocityTermCoefficient(0) infiltration.setVelocitySquaredTermCoefficient(coeff_d_on) unless hvac_off_sch.nil? infiltration_hvac_off = infiltration.clone(model).to_SpaceInfiltrationDesignFlowRate.get infiltration_hvac_off.setName(orin_infil_name + ' HVAC Off') infiltration_hvac_off.setSchedule(hvac_off_sch) infiltration_hvac_off.setConstantTermCoefficient(coeff_a_off) infiltration_hvac_off.setTemperatureTermCoefficient(coeff_b_off) infiltration_hvac_off.setVelocityTermCoefficient(0) infiltration_hvac_off.setVelocitySquaredTermCoefficient(coeff_d_off) end end end # apply vertical weather variations to tall buildings # current method is using the E+ default variation trend by specifying the height of outdoor air nodes def apply_vertical_weather_variation(model) # OA node height is not implemented OpenStudio yet. # Temporary fix to be done via adding EnergyPlus measure. # model.getAirLoopHVACOutdoorAirSystems.each do |oa_system| # # get the outdoor air system outdoor air node # oa_node = oa_system.outdoorAirModelObject.get.to_Node.get # # get the height of the plenum if any, assign to outdoor air node # # end end # HighriseApartment doesn't apply thermostat to corridor spaces def add_thermostat_to_corridor(model) thermostat = OpenStudio::Model::ThermostatSetpointDualSetpoint.new(model) thermostat.setName('HighriseApartment Corridor Thermostat') thermostat.setHeatingSetpointTemperatureSchedule(model_add_schedule(model, 'ApartmentHighRise HTGSETP_APT_SCH')) thermostat.setCoolingSetpointTemperatureSchedule(model_add_schedule(model, 'ApartmentHighRise CLGSETP_APT_SCH')) model.getSpaceTypes.each do |space_type| unless space_type.standardsBuildingType.empty? || space_type.standardsSpaceType.empty? if space_type.standardsBuildingType.get == 'HighriseApartment' && space_type.standardsSpaceType.get == 'Corridor' space_type.spaces.each do |space| thermostat_clone = thermostat.clone(model).to_ThermostatSetpointDualSetpoint.get space.thermalZone.get.setThermostatSetpointDualSetpoint(thermostat_clone) end end end end end def model_custom_swh_tweaks(model, building_type, climate_zone, prototype_input) # customized swh system is not added here, but in model_custom_hvac_tweaks instead # because model_custom_swh_tweaks is performed in Prototype.Model after efficiency assignment. If swh added here, efficiency can't be updated. # this can't be moved upwards in Prototype.Model as it will affect other building types (e.g. SmallOfficeDetailed, LargeOfficeDetailed). return true end def model_custom_geometry_tweaks(building_type, climate_zone, prototype_input, model) # TODO: make additional parameters mutable by the user # the number of floors for each function type is defined in additional_params additional_params = { num_of_floor_retail: 2, num_of_floor_office: 18, num_of_floor_residential: 9, num_of_floor_hotel: 9 } # get the number of floors for each function if additional_params.nil? num_retail_flr, num_office_flr, num_resi_flr, num_hotel_flr = 2, 18, 9, 9 elsif additional_params.is_a?(Hash) keys = [:num_of_floor_retail, :num_of_floor_office, :num_of_floor_residential, :num_of_floor_hotel] if (additional_params.keys & keys).any? # if any function type is assigned with number of floor if additional_params.key?(:num_of_floor_retail) && additional_params[:num_of_floor_retail].is_a?(Numeric) num_retail_flr = additional_params[:num_of_floor_retail].to_i else num_retail_flr = 0 end if additional_params.key?(:num_of_floor_office) && additional_params[:num_of_floor_office].is_a?(Numeric) num_office_flr = additional_params[:num_of_floor_office].to_i else num_office_flr = 0 end if additional_params.key?(:num_of_floor_residential) && additional_params[:num_of_floor_residential].is_a?(Numeric) num_resi_flr = additional_params[:num_of_floor_residential].to_i else num_resi_flr = 0 end if additional_params.key?(:num_of_floor_hotel) && additional_params[:num_of_floor_hotel].is_a?(Numeric) num_hotel_flr = additional_params[:num_of_floor_hotel].to_i else num_hotel_flr = 0 end if num_retail_flr == 0 && num_office_flr == 0 && num_resi_flr == 0 && num_hotel_flr == 0 num_retail_flr, num_office_flr, num_resi_flr, num_hotel_flr = 2, 18, 9, 9 elsif num_retail_flr + num_office_flr + num_resi_flr + num_hotel_flr < 20 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'The building is not eligible as a tall building because the total number of floors is less than 20') return false elsif num_retail_flr + num_office_flr + num_resi_flr + num_hotel_flr >= 75 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', "The building has #{num_retail_flr + num_office_flr + num_resi_flr + num_hotel_flr} floors, which should "\ 'be classified as super tall building. Please select SuperTall Building as the building type instead.') return false end else # if no number of floor is given for any function type num_retail_flr, num_office_flr, num_resi_flr, num_hotel_flr = 2, 18, 9, 9 end else OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'additional_params is not a Hash') return false end # Validate number of floors values, can't be negative if num_retail_flr < 0 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Number of floors for Retail is negative.') return false elsif num_office_flr < 0 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Number of floors for Office is negative.') return false elsif num_resi_flr < 0 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Number of floors for Apartment is negative.') return false elsif num_hotel_flr < 0 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Number of floors for Hotel is negative.') return false elsif num_retail_flr == 0 && num_office_flr == 0 && num_resi_flr == 0 && num_hotel_flr == 0 OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Number of floors for all function types are all zero.') return false end # update the number of floors in additional_params additional_params[:num_of_floor_retail] = num_retail_flr additional_params[:num_of_floor_office] = num_office_flr additional_params[:num_of_floor_residential] = num_resi_flr additional_params[:num_of_floor_hotel] = num_hotel_flr puts '*' * 150 puts "num_retail_flr = #{num_retail_flr}" puts "num_office_flr = #{num_office_flr}" puts "num_resi_flr = #{num_resi_flr}" puts "num_hotel_flr = #{num_hotel_flr}" f_to_f_height_retail = 4.8768 f_to_c_height_retail = 4.2672 f_to_f_height_non_retail = 3.5052 f_to_c_height_non_retail = 2.7432 # Steps: # 1. Determine multiplier for each basic floor (as long as not bottom/top floor, don't need to separate floor) # 2. Modify name and Z origins for each floor as needed # 3. Fix surface boundary condition, change to adiabatic for floors using multiplier, # and the floors below (plenum ceiling) and above (floor) the floors with multiplier # 4. construct hvac system map json # Check sum of num_of_flr below and above the current floor (e.g. For office, check retail num_of_flr, and check apartment + hotel num_of_flr) # if below is 0, separate one floor as the ground floor # if above is 0, separate one floor as the top floor # For example, when number of hotel floors is 0, # if num_resi_flr >=3, # the residential mid story (n>=2) should separate one story out as top floor. # else if num_resi_flr == 2, the resi_mid story works as the top floor # else if num_resi_flr == 1, the resi_bot story works as the top floor # similar for office and retail total_num_of_flr = num_retail_flr + num_office_flr + num_resi_flr + num_hotel_flr model.getBuilding.setStandardsNumberOfAboveGroundStories(total_num_of_flr) model.getBuilding.setStandardsNumberOfStories(total_num_of_flr + 1) # one basement story current_story = 1 current_height = 0 # Retail retail_f1_story_orin = model.getBuildingStoryByName('Retail_F1 story').get retail_f2_story_orin = model.getBuildingStoryByName('Retail_F2 story').get if num_retail_flr == 0 [retail_f1_story_orin, retail_f2_story_orin].each do |story| story.spaces.each do |space| space.thermalZone.get.remove space.remove end story.remove end else # num_retail_flr >= 1 # deal with retail_f1_story retail_f1_story_orin.setNominalZCoordinate(current_height) retail_f1_story_orin.setNominalFloortoFloorHeight(f_to_f_height_retail) retail_f1_story_orin.spaces.each do |space| space.setName("F#{current_story} " + space.name.to_s) end current_height += f_to_f_height_retail current_story += 1 # deal with retail_f2_story, deep copy as needed if num_retail_flr > 1 multiplier_list = get_multiplier_list(num_retail_flr - 1) if multiplier_list.is_a? Numeric multiplier = multiplier_list z_origin = current_height + f_to_f_height_retail * (multiplier / 2.0 - 0.5) if multiplier == 1 && num_office_flr >= 2 deep_copy_story(model, retail_f2_story_orin, 1, z_origin, f_to_c_height_retail, f_to_f_height_retail, current_story, if_ground_story_plenum_adiabatic: true) else deep_copy_story(model, retail_f2_story_orin, multiplier, z_origin, f_to_c_height_retail, f_to_f_height_retail, current_story) end # update the story # and height # current_story += multiplier current_height += f_to_f_height_retail * multiplier elsif multiplier_list.is_a? Array multiplier_list.each do |mpl| z_origin = current_height + f_to_f_height_retail * (mpl / 2.0 - 0.5) deep_copy_story(model, retail_f2_story_orin, mpl, z_origin, f_to_c_height_retail, f_to_f_height_retail, current_story) # update the story # and height # current_story += mpl current_height += f_to_f_height_retail * mpl end end end # remove the original RetailF2 floor retail_f2_story_orin.spaces.each do |space| space.thermalZone.get.remove space.remove end retail_f2_story_orin.remove end # Office office_story_orin = model.getBuildingStoryByName('Office story').get if num_office_flr == 0 office_story_orin.spaces.each do |space| space.thermalZone.get.remove space.remove end office_story_orin.remove elsif num_office_flr == 1 # only update the z origin and name office_story_orin.setNominalZCoordinate(current_height) office_story_orin.setNominalFloortoFloorHeight(f_to_f_height_non_retail) office_story_orin.spaces.each do |space| space.setName("F#{current_story} " + space.name.to_s) if space.name.to_s.include? 'Plenum' space.setZOrigin(current_height + f_to_c_height_non_retail) else space.setZOrigin(current_height) end end office_story_orin.setName("F#{current_story} Office story") # update the story # and height # current_height += f_to_f_height_non_retail current_story += 1 else num_office_flr_w_mult = num_office_flr # If no retail, separate one floor as ground floor if num_retail_flr == 0 if_ground_story_plenum_adiabatic = false z_origin = current_height num_office_flr_w_mult -= 1 if num_office_flr_w_mult > 1 if_ground_story_plenum_adiabatic = true end deep_copy_story(model, office_story_orin, 1, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story, if_ground_story_plenum_adiabatic: if_ground_story_plenum_adiabatic) # update the story # and height # current_story += 1 current_height += f_to_f_height_non_retail end # if no residential and hotel, separate one floor as top floor if num_resi_flr + num_hotel_flr == 0 if_top_story_floor_adiabatic = false z_origin = num_retail_flr * f_to_f_height_retail + (num_office_flr - 1) * f_to_f_height_non_retail top_story_num = num_retail_flr + num_office_flr num_office_flr_w_mult -= 1 if num_office_flr_w_mult > 1 if_top_story_floor_adiabatic = true end deep_copy_story(model, office_story_orin, 1, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, top_story_num, if_top_story_floor_adiabatic: if_top_story_floor_adiabatic) end if num_office_flr_w_mult >= 1 multiplier_list = get_multiplier_list(num_office_flr_w_mult) if multiplier_list.is_a? Numeric multiplier = multiplier_list z_origin = current_height + f_to_f_height_non_retail * (multiplier / 2.0 - 0.5) deep_copy_story(model, office_story_orin, multiplier, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story) # update the story # and height # current_story += multiplier current_height += f_to_f_height_non_retail * multiplier elsif multiplier_list.is_a? Array multiplier_list.each do |mpl| z_origin = current_height + f_to_f_height_non_retail * (mpl / 2.0 - 0.5) deep_copy_story(model, office_story_orin, mpl, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story) # update the story # and height # current_story += mpl current_height += f_to_f_height_non_retail * mpl end end end # remove the original office floor office_story_orin.spaces.each do |space| space.thermalZone.get.remove space.remove end office_story_orin.remove end # Apartment resi_bot_story_orin = model.getBuildingStoryByName('Resi_bot story').get resi_mid_story_orin = model.getBuildingStoryByName('Resi_mid story').get if num_resi_flr == 0 [resi_bot_story_orin, resi_mid_story_orin].each do |story| story.spaces.each do |space| space.thermalZone.get.remove space.remove end story.remove end else # num_resi_flr >= 1 # deal with resi_bot story resi_bot_story_orin.setNominalZCoordinate(current_height) resi_bot_story_orin.setNominalFloortoFloorHeight(f_to_f_height_non_retail) resi_bot_story_orin.setName("F#{current_story} " + resi_bot_story_orin.name.to_s) resi_bot_story_orin.spaces.each do |space| space.setName("F#{current_story} " + space.name.to_s) if space.name.to_s.include? 'Plenum' space.setZOrigin(current_height + f_to_c_height_non_retail) else space.setZOrigin(current_height) end end current_height += f_to_f_height_non_retail current_story += 1 # deal with resi_mid story, deep copy if needed if num_resi_flr > 1 num_resi_mid_flr_w_mult = num_resi_flr - 1 # if no hotel, separate one floor from resi_mid story as top floor if num_hotel_flr == 0 if_top_story_floor_adiabatic = false z_origin = num_retail_flr * f_to_f_height_retail + (num_office_flr + num_resi_flr - 1) * f_to_f_height_non_retail top_story_num = num_retail_flr + num_office_flr + num_resi_flr num_resi_mid_flr_w_mult -= 1 if num_resi_mid_flr_w_mult > 1 if_top_story_floor_adiabatic = true end deep_copy_story(model, resi_mid_story_orin, 1, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, top_story_num, if_top_story_floor_adiabatic: if_top_story_floor_adiabatic) end if num_resi_mid_flr_w_mult >= 1 multiplier_list = get_multiplier_list(num_resi_mid_flr_w_mult) if multiplier_list.is_a? Numeric multiplier = multiplier_list z_origin = current_height + f_to_f_height_non_retail * (multiplier / 2.0 - 0.5) deep_copy_story(model, resi_mid_story_orin, multiplier, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story) current_story += multiplier current_height += f_to_f_height_non_retail * multiplier elsif multiplier_list.is_a? Array multiplier_list.each do |mpl| z_origin = current_height + f_to_f_height_non_retail * (mpl / 2.0 - 0.5) deep_copy_story(model, resi_mid_story_orin, mpl, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story) # update the story # and height # current_story += mpl current_height += f_to_f_height_non_retail * mpl end end end end # remove the original resi_mid story resi_mid_story_orin.spaces.each do |space| space.thermalZone.get.remove space.remove end resi_mid_story_orin.remove end # Hotel hotel_bot_story_orin = model.getBuildingStoryByName('Hotel_bot story').get hotel_mid_story_orin = model.getBuildingStoryByName('Hotel_mid story').get hotel_top_story_orin = model.getBuildingStoryByName('Hotel_top story').get if num_hotel_flr == 0 [hotel_bot_story_orin, hotel_mid_story_orin, hotel_top_story_orin].each do |story| story.spaces.each do |space| space.thermalZone.get.remove space.remove end story.remove end else # num_hotel_flr >= 1 # deal with hotel_top_story hotel_top_origin = num_retail_flr * f_to_f_height_retail + (num_office_flr + num_resi_flr + num_hotel_flr - 1) * f_to_f_height_non_retail hotel_top_story_orin.setNominalZCoordinate(hotel_top_origin) hotel_top_story_orin.setNominalFloortoFloorHeight(f_to_f_height_non_retail) hotel_top_story_orin.setName("F#{total_num_of_flr} " + hotel_top_story_orin.name.to_s) hotel_top_story_orin.spaces.each do |space| space.setName("F#{total_num_of_flr} " + space.name.to_s) if space.name.to_s.include? 'Plenum' space.setZOrigin(hotel_top_origin + f_to_c_height_non_retail) else space.setZOrigin(hotel_top_origin) end end # deal with hotel_bot and hotel_mid stories, deep copy as needed if num_hotel_flr > 1 # deal with hotel_bot_story hotel_bot_story_orin.setNominalZCoordinate(current_height) hotel_bot_story_orin.setNominalFloortoFloorHeight(f_to_f_height_non_retail) hotel_bot_story_orin.setName("F#{current_story} " + hotel_bot_story_orin.name.to_s) hotel_bot_story_orin.spaces.each do |space| space.setName("F#{current_story} " + space.name.to_s) if space.name.to_s.include? 'Plenum' space.setZOrigin(current_height + f_to_c_height_non_retail) else space.setZOrigin(current_height) end end current_story += 1 current_height += f_to_f_height_non_retail if num_hotel_flr >= 3 multiplier_list = get_multiplier_list(num_hotel_flr - 2) if multiplier_list.is_a? Numeric multiplier = multiplier_list z_origin = current_height + f_to_f_height_non_retail * (multiplier / 2.0 - 0.5) deep_copy_story(model, hotel_mid_story_orin, multiplier, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story) elsif multiplier_list.is_a? Array multiplier_list.each do |mpl| z_origin = current_height + f_to_f_height_non_retail * (mpl / 2.0 - 0.5) deep_copy_story(model, hotel_mid_story_orin, mpl, z_origin, f_to_c_height_non_retail, f_to_f_height_non_retail, current_story) # update the story # and height # current_story += mpl current_height += f_to_f_height_non_retail * mpl end end end else # num_hotel_flr == 1 hotel_bot_story_orin.spaces.each do |space| space.thermalZone.get.remove space.remove end hotel_bot_story_orin.remove end # remove the original hotel_mid floor hotel_mid_story_orin.spaces.each do |space| space.thermalZone.get.remove space.remove end hotel_mid_story_orin.remove end # Relocate the ElevatorMachineRm story building_height = num_retail_flr * f_to_f_height_retail + (num_office_flr + num_resi_flr + num_hotel_flr) * f_to_f_height_non_retail top_elevatorMachineRm_story = model.getBuildingStoryByName('ElevatorMachineRm story').get top_elevatorMachineRm_story.setNominalZCoordinate(building_height) top_elevatorMachineRm_story.spaces.each do |space| space.setZOrigin(building_height) end # connect the surfaces spaces = OpenStudio::Model::SpaceVector.new model.getSpaces.sort.each do |space| spaces << space end OpenStudio::Model.intersectSurfaces(spaces) OpenStudio::Model.matchSurfaces(spaces) # Update the hvac system map based on updated geometry new_hvac_map_str = generate_new_json(model, additional_params) @system_to_space_map = JSON.parse(new_hvac_map_str) return true end def generate_new_json(model, additional_params) new_json = [] # get the number of floors for each function num_retail_flr = additional_params[:num_of_floor_retail].to_i num_office_flr = additional_params[:num_of_floor_office].to_i num_resi_flr = additional_params[:num_of_floor_residential].to_i num_hotel_flr = additional_params[:num_of_floor_hotel].to_i # one chiller per 13 floors, +1 is basement. Ref: large office prototype model num_chillers = ((num_retail_flr + num_office_flr + num_resi_flr + num_hotel_flr + 1) / 13.0).ceil hotel_common_spaces = [] hotel_top_plenum_space = nil model.getBuildingStorys.each do |story| story_name = story.name.to_s space_names = [] plenum_space = '' story.spaces.sort.each do |space| if space.name.to_s.include? 'Plenum' plenum_space = space.name.to_s elsif (story_name.include? 'Hotel') && (!space.name.to_s.downcase.include? 'guest') hotel_common_spaces.push(space.name.to_s) else space_names.push(space.name.to_s) end end if story_name.include? 'Office' hvac_obj = { "type": 'VAV', "name": story_name + ' VAV WITH REHEAT', "return_plenum": plenum_space, "operation_schedule": 'OfficeLarge HVACOperationSchd', "oa_damper_schedule": 'OfficeLarge MinOA_MotorizedDamper_Sched', "chw_pumping_type": 'const_pri_var_sec', "chiller_cooling_type": 'WaterCooled', "chiller_condenser_type": nil, "chiller_compressor_type": 'Centrifugal', "chw_number_chillers": num_chillers, "number_cooling_towers": num_chillers, "space_names": space_names } elsif story_name.include? 'Retail' hvac_obj = { "type": 'VAV', "name": story_name + ' VAV WITH REHEAT', "return_plenum": plenum_space, "operation_schedule": 'RetailStandalone HVACOperationSchd', "oa_damper_schedule": 'RetailStandalone MinOA_MotorizedDamper_Sched', "chw_pumping_type": 'const_pri_var_sec', "chiller_cooling_type": 'WaterCooled', "chiller_condenser_type": nil, "chiller_compressor_type": 'Centrifugal', "chw_number_chillers": num_chillers, "number_cooling_towers": num_chillers, "space_names": space_names } elsif story_name.include? 'Hotel' hvac_obj = { "type": 'DOAS Cold Supply', "name": story_name + ' DOAS', "return_plenum": plenum_space, "operation_schedule": 'HotelLarge HVACOperationSchd', "oa_damper_schedule": 'HotelLarge MinOA_MotorizedDamper_Sched', "chw_pumping_type": 'const_pri_var_sec', "chiller_cooling_type": 'WaterCooled', "chiller_condenser_type": nil, "chiller_compressor_type": 'Centrifugal', "chw_number_chillers": num_chillers, "number_cooling_towers": num_chillers, "economizer_control_method": 'DifferentialDryBulb', "space_names": space_names } # get the top floor plenum for hotel common areas' VAV system hotel_top_plenum_space = plenum_space if story_name.include? 'top' elsif story_name.include? 'Resi' hvac_obj = { "type": 'DOAS Cold Supply', "name": story_name + ' DOAS', "return_plenum": plenum_space, "operation_schedule": 'Always On', "oa_damper_schedule": 'Always On', "chw_pumping_type": 'const_pri_var_sec', "chiller_cooling_type": 'WaterCooled', "chiller_condenser_type": nil, "chiller_compressor_type": 'Centrifugal', "chw_number_chillers": num_chillers, "number_cooling_towers": num_chillers, "economizer_control_method": 'DifferentialDryBulb', "space_names": space_names } elsif story_name.include? 'Basement' hvac_obj = { "type": 'CAV', "name": 'CAV_bas', "operation_schedule": 'OfficeLarge HVACOperationSchd', "oa_damper_schedule": 'OfficeLarge MinOA_MotorizedDamper_Sched', "chw_pumping_type": 'const_pri_var_sec', "chiller_cooling_type": 'WaterCooled', "chiller_condenser_type": nil, "chiller_compressor_type": 'Centrifugal', "chw_number_chillers": num_chillers, "number_cooling_towers": num_chillers, "space_names": space_names } elsif story_name.include? 'ElevatorMachineRm' hvac_obj = { "type": 'PSZ-AC', "name": story_name + ' PSZ-AC', "operation_schedule": 'Always On', "oa_damper_schedule": 'Always On', "cooling_type": 'Single Speed DX AC', "heating_type": 'Electricity', "fan_type": 'ConstantVolume', "space_names": space_names } end new_json.push(hvac_obj) end # add VAV system for all hotel common area spaces if num_hotel_flr >= 1 hotel_common_hvac_obj = { "type": 'VAV', "name": 'Hotel Common Areas VAV WITH REHEAT', "return_plenum": hotel_top_plenum_space, "operation_schedule": 'HotelLarge HVACOperationSchd', "oa_damper_schedule": 'HotelLarge MinOA_MotorizedDamper_Sched', "chw_pumping_type": 'const_pri_var_sec', "chiller_cooling_type": 'WaterCooled', "chiller_condenser_type": nil, "chiller_compressor_type": 'Centrifugal', "chw_number_chillers": num_chillers, "number_cooling_towers": num_chillers, "space_names": hotel_common_spaces } new_json.push(hotel_common_hvac_obj) end return JSON.pretty_generate(new_json) end def get_multiplier_list(num_floors) a = (num_floors.to_f / 10).ceil # a = (25.0/10).ceil => 3 return num_floors if a == 1 multiplier_list = [] multiplier = (num_floors.to_f / a).ceil # multiplier = (25.0/3).ceil = 9 multiplier_rep_times = num_floors / multiplier # multiplier_rep_times = 25/9 = 2 if num_floors % multiplier == 0 multiplier_list.fill(multiplier, multiplier_list.size, multiplier_rep_times) # [multiplier,multiplier,multiplier...] else spare_multiplier = num_floors % multiplier # spare_multiplier = 25 % 9 = 7 i = 0 while i < multiplier_rep_times multiplier_list << multiplier i += 1 end multiplier_list << spare_multiplier # [multiplier, multiplier, spare_multiplier] end return multiplier_list end def update_space_outside_boundary_to_adiabatic(space, if_top_story_floor_adiabatic: false, if_ground_story_plenum_adiabatic: false) if (space.name.to_s.include? 'Plenum') && !if_top_story_floor_adiabatic space.surfaces.each do |surface| if surface.surfaceType.to_s == 'RoofCeiling' if surface.outsideBoundaryCondition.to_s == 'Surface' if !surface.adjacentSurface.empty? adj_surface = surface.adjacentSurface.get adj_surface.setOutsideBoundaryCondition('Adiabatic') end end surface.setOutsideBoundaryCondition('Adiabatic') end end else # for ground floor plenum adiabatic scenario, skip floors unless if_ground_story_plenum_adiabatic space.surfaces.each do |surface| if surface.surfaceType.to_s == 'Floor' if surface.outsideBoundaryCondition.to_s == 'Surface' if !surface.adjacentSurface.empty? adj_surface = surface.adjacentSurface.get adj_surface.setOutsideBoundaryCondition('Adiabatic') end end surface.setOutsideBoundaryCondition('Adiabatic') end end end end end def deep_copy_story(model, original_story, multiplier, new_z_origin, f_to_c_height, f_to_f_height, current_story, if_top_story_floor_adiabatic: false, if_ground_story_plenum_adiabatic: false) # clone the story new_story = original_story.clone(model).to_BuildingStory.get new_story.setNominalZCoordinate(new_z_origin) new_story.setNominalFloortoFloorHeight(f_to_f_height) new_story.setNominalFloortoCeilingHeight(f_to_c_height) if multiplier == 1 new_story.setName("F#{current_story} " + original_story.name.to_s) else new_story.setName("F#{current_story}-" + "F#{current_story + multiplier - 1} " + original_story.name.to_s) end # clone the spaces on the story original_story.spaces.each do |space| old_name = space.name.to_s if multiplier == 1 new_name = "F#{current_story} " + old_name else new_name = "F#{current_story}-" + "F#{current_story + multiplier - 1} " + old_name end # clone space new_space = space.clone(model).to_Space.get new_space.setName(new_name) # assign new Z Origin if old_name.include? 'Plenum' new_space.setZOrigin(new_z_origin + f_to_c_height) else new_space.setZOrigin(new_z_origin) end # clone thermal zone and assign new_t_zone = space.thermalZone.get.clone(model).to_ThermalZone.get new_t_zone.setName('TZ-' + new_name) new_t_zone.setMultiplier(multiplier * space.thermalZone.get.multiplier) # story multiplier and original thermal zone multiplier new_space.setThermalZone(new_t_zone) # assign new building story new_space.setBuildingStory(new_story) # update boundary condition to adiabatic as needed # for top story, when the story below has a multiplier # set the surface boundary condition as adiabatic for its floors (otherwise they will end up being "ground") if multiplier > 1 || (multiplier == 1 && if_top_story_floor_adiabatic) || (multiplier == 1 && if_ground_story_plenum_adiabatic) update_space_outside_boundary_to_adiabatic(new_space, if_top_story_floor_adiabatic: if_top_story_floor_adiabatic, if_ground_story_plenum_adiabatic: if_ground_story_plenum_adiabatic) end end end end