# ******************************************************************************* # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC. # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # (1) Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # (2) Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # (3) Neither the name of the copyright holder nor the names of any contributors # may be used to endorse or promote products derived from this software without # specific prior written permission from the respective party. # # (4) Other than as required in clauses (1) and (2), distributions in any form # of modifications or other derivative works may not use the "OpenStudio" # trademark, "OS", "os", or any other confusingly similar designation without # specific prior written permission from Alliance for Sustainable Energy, LLC. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ******************************************************************************* # see the URL below for information on how to write OpenStudio measures # http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/ begin # load OpenStudio measure libraries from common location require 'measure_resources/os_lib_model_simplification' rescue LoadError # common location unavailable, load from local resources require_relative 'resources/os_lib_model_simplification' end # start the measure class BlendedSpaceTypeFromModel < OpenStudio::Measure::ModelMeasure # contains code to blend space types include OsLib_ModelSimplification # human readable name def name return 'Blended Space Type from Model' end # human readable description def description return 'This measure will remove all space type assignemnts and hard assigned internal loads from spaces that are included in the building floor area. Spaces such as plenums and attics will be left alone. A blended space type will be created from the original internal loads and assigned at the building level. Thermostats, Service Water Heating, and HVAC systems will not be altered. Any constructions associated with space types will be hard assigned prior to the space type assignemnt being removed.' end # human readable description of modeling approach def modeler_description return 'The goal of this measure is to create a single space type that represents the loads and schedules of a collection of space types in a model. When possible the measure will create mulitple load instances of a specific type in the resulting blended space type. This allows the original schedules to be used, and can allow for down stream EE measures on specific internal loads. Design Ventilation Outdoor Air objects will have to be merged into a single object. Will try to maintain the load design type (power, per area, per person) when possible. Need to account for zone multipliers when createding blended internal loads. Also address what happens to daylighting control objets. Original space types will be left in the model, some may still be assigned to spaces not included in the building area.' end # define the arguments that the user will input def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # make choice argument for blend_method choices = OpenStudio::StringVector.new choices << 'Building Type' choices << 'Building Story' choices << 'Building' blend_method = OpenStudio::Measure::OSArgument.makeChoiceArgument('blend_method', choices, true) blend_method.setDisplayName('Blend Space Types that are part of the same') blend_method.setDefaultValue('Building') args << blend_method # any of these in the end will be an array of spaces. # if choose building types, spaces without space types or without standards space type info are ignored (or go to their own blend) # if choose building story spaces not on building story will be ignored or combined to its own space type. # Think about how to handle plenums and attics (this is for building type or building story) return args end # define what happens when the measure is run def run(model, runner, user_arguments) super(model, runner, user_arguments) # use the built-in error checking if !runner.validateUserArguments(arguments(model), user_arguments) return false end # assign the user inputs to variables blend_method = runner.getStringArgumentValue('blend_method', user_arguments) # divide the building up based on blend method space_type_hash = {} if blend_method == 'Building Type' # loop through spaces and organize by building type model.getSpaceTypes.each do |space_type| # skip if not used next if space_type.floorArea == 0 # skip if space_type isn't associated with standards building type if !space_type.standardsBuildingType.is_initialized runner.registerWarning("#{space_type.name} does not have a building type associated with it. Spaces using this space type will be ignored") next end # add space to array within space_type_hash building_type = space_type.standardsBuildingType.get if !space_type_hash.key?(building_type) space_type_hash[building_type] = [] end space_type_hash[building_type] << space_type end elsif blend_method == 'Building Story' # loop through building stories model.getBuildingStorys.sort.each do |story| # array of space types for this building story story_space_types = {} # key original space type, value is cloned space type # loop through spaces in story and gather and clone space types as needed story.spaces.each do |space| # skip if space isn't assigned to a space type if !space.spaceType.is_initialized runner.registerWarning("#{space.name} is not assigned to a space type, it will be ignored") next else space_type = space.spaceType.get end # skip if space_type isn't associated with standards building type if !space_type.standardsBuildingType.is_initialized runner.registerWarning("#{space_type.name} does not have a building type associated with it. #{space} which uses that space type will be ignored") next end # clone space first time used for each story if !story_space_types.include?(space_type) orig_name = space_type.name new_space_type = space_type.clone(model).to_SpaceType.get new_space_type.setName("#{orig_name} #{story.name}") story_space_types[space_type] = new_space_type end # re-assign space to clone of space type space.setSpaceType(story_space_types[space_type]) end # populate space_type_hash space_type_hash[story.name] = story_space_types.values end # remove unused space_types (which includes original spaces types that were cloned for building stories as needed) model.getSpaceTypes.each do |space_type| if space_type.floorArea == 0 space_type.remove end end # warn if spaces in model that are not on building story (re-assign to cloned space type) model.getSpaces.each do |space| next if space.buildingStory.is_initialized runner.registerWarning("#{space.name} is not assigned to a building story. It will be ignored.") end else space_type_hash[blend_method] = model.getSpaceTypes.sort end # report initial condition of model initial_cond_space_type_hash = {} model.getSpaceTypes.sort.each do |space_type| next if space_type.floorArea == 0 floor_area_si = 0 # loop through spaces so I can skip if not included in floor area space_type.spaces.each do |space| next if !space.partofTotalFloorArea floor_area_si += space.floorArea * space.multiplier end next if floor_area_si == 0 initial_cond_space_type_hash[space_type] = floor_area_si end runner.registerInitialCondition("The initial building uses #{initial_cond_space_type_hash.size} spaces types.") # blend space types blend_space_type_collections(runner, model, space_type_hash) # report final condition of model # re-run same same code used for initial condition final_cond_space_type_hash = {} model.getSpaceTypes.sort.each do |space_type| next if space_type.floorArea == 0 floor_area_si = 0 # loop through spaces so I can skip if not included in floor area space_type.spaces.each do |space| next if !space.partofTotalFloorArea floor_area_si += space.floorArea * space.multiplier end next if floor_area_si == 0 final_cond_space_type_hash[space_type] = floor_area_si end runner.registerFinalCondition("The final building uses #{final_cond_space_type_hash.size} spaces types.") return true end end # register the measure to be used by the application BlendedSpaceTypeFromModel.new.registerWithApplication