# frozen_string_literal: true # ******************************************************************************* # OpenStudio(R), Copyright (c) 2008-2022, 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://openstudio.nrel.gov/openstudio-measure-writing-guide # see the URL below for information on using life cycle cost objects in OpenStudio # http://openstudio.nrel.gov/openstudio-life-cycle-examples # see the URL below for access to C++ documentation on model objects (click on "model" in the main window to view model objects) # http://openstudio.nrel.gov/sites/openstudio.nrel.gov/files/nv_data/cpp_documentation_it/model/html/namespaces.html # start the measure class GLHEProGFunctionImport < OpenStudio::Measure::ModelMeasure # define the name that a user will see, this method may be deprecated as # the display name in PAT comes from the name field in measure.xml def name return 'GLHEProGFunctionImport' end # define the arguments that the user will input def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # make an argument for location of G Function .idf file g_function_path = OpenStudio::Measure::OSArgument.makeStringArgument('g_function_path', true) g_function_path.setDisplayName('G Function File Path (C:/g_function.idf)') args << g_function_path # Find the names of all plant loops in the model that contain both a # district heating and district cooling object loop_names = OpenStudio::StringVector.new loop_handles = OpenStudio::StringVector.new model.getPlantLoops.each do |loop| dist_htg_name = nil dist_clg_name = nil loop.supplyComponents.each do |sc| if sc.to_DistrictHeating.is_initialized dist_htg_name = sc.name.get elsif sc.to_DistrictCooling.is_initialized dist_clg_name = sc.name.get end end if dist_htg_name && dist_clg_name loop_names << loop.name.get loop_handles << loop.handle.to_s end end # make an argument for plant loops object = OpenStudio::Measure::OSArgument.makeChoiceArgument('object', loop_handles, loop_names, true) object.setDisplayName('Select plant loop to add GLHX to') args << object 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 g_function_path = runner.getStringArgumentValue('g_function_path', user_arguments) object = runner.getOptionalWorkspaceObjectChoiceValue('object', user_arguments, model) # Check to make sure the g function file exists if !File.exist?(g_function_path) runner.registerError("The G Function file '#{g_function_path}' could not be found.") return false end # Check the loop selection loop = nil if object.empty? handle = runner.getDoubleArgumentValue('object', user_arguments) if handle.empty? runner.registerError('No loop was chosen.') else runner.registerError("The selected loop with handle '#{handle}' was not found in the model. It may have been removed by another measure.") end return false else if object.get.to_PlantLoop.is_initialized loop = object.get.to_PlantLoop.get else runner.registerError('Script Error - argument not showing up as loop.') return false end end # Check the location of the GFunction if !File.exist?(g_function_path) runner.registerError("Coulnd't find the G Function file. Check file path and try again: '#{g_function_path}'.") end # Remove the district heating and district cooling objects from the loop loop.supplyComponents.each do |sc| if sc.to_DistrictHeating.is_initialized || sc.to_DistrictCooling.is_initialized sc.remove runner.registerInfo("Removed #{sc.name} from #{loop.name}.") end end # Fix up the IDF file (GLHEPro exports slightly malformed IDF) idf_text = nil File.open(g_function_path, 'r') { |f| idf_text = f.read } idf_text = idf_text.gsub('GROUND HEAT EXCHANGER:VERTICAL,', 'GROUNDHEATEXCHANGER:VERTICAL,') idf_text = idf_text.gsub(' :,', ',') File.open(g_function_path, 'w') { |f2| f2.puts idf_text } # Remove the setpointmanager from the supply outlet node # TODO: this might be necessary? # Add a GLHX to the loop glhx = OpenStudio::Model::GroundHeatExchangerVertical.new(model) glhx.setName("GLHX for #{loop.name}") loop.addSupplyBranchForComponent(glhx) runner.registerInfo("Added GLHX to #{loop.name}.") # Read the input parameters from the G Function .idf file g_function_file = OpenStudio::Workspace.load(g_function_path).get glhx_idf = g_function_file.getObjectsByType('GROUNDHEATEXCHANGER:VERTICAL'.to_IddObjectType)[0] max_flow = glhx_idf.getDouble(3).get num_boreholes = glhx_idf.getInt(4).get borehole_length = glhx_idf.getDouble(5).get borehole_radius = glhx_idf.getDouble(6).get ground_cond = glhx_idf.getDouble(7).get ground_ht_cap = glhx_idf.getDouble(8).get specific_heat = glhx_idf.getDouble(9).get t_ground = glhx_idf.getDouble(10).get vol_flowrate = glhx_idf.getDouble(11).get grout_cond = glhx_idf.getDouble(12).get pipe_cond = glhx_idf.getDouble(13).get fluid_cond = glhx_idf.getDouble(14).get # runner.registerInfo("fluid_cond = #{fluid_cond}") fluid_density = glhx_idf.getDouble(15).get # runner.registerInfo("fluid_density = #{fluid_density}") fluid_visc = glhx_idf.getDouble(16).get # runner.registerInfo("fluid_visc = #{fluid_visc}") pipe_diam = glhx_idf.getDouble(17).get # runner.registerInfo("pipe_diam = #{pipe_diam}") u_tube_sep = glhx_idf.getDouble(18).get # runner.registerInfo("u_tube_sep = #{u_tube_sep}") pipe_wall_thick = glhx_idf.getDouble(19).get # runner.registerInfo("pipe_wall_thick = #{pipe_wall_thick}") max_sim = glhx_idf.getInt(20).get # runner.registerInfo("max_sim = #{max_sim}") num_data_pairs = glhx_idf.getInt(21).get # runner.registerInfo("num_data_pairs = #{num_data_pairs}") reference_ratio = borehole_radius / borehole_length # Set the input parameters of the GLHE glhx.setMaximumFlowRate(max_flow) glhx.setNumberofBoreHoles(num_boreholes) glhx.setBoreHoleLength(borehole_length) glhx.setBoreHoleRadius(borehole_radius) glhx.setGroundThermalConductivity(ground_cond) glhx.setGroundThermalHeatCapacity(ground_ht_cap) glhx.setGroundTemperature(t_ground) glhx.setDesignFlowRate(vol_flowrate) glhx.setGroutThermalConductivity(grout_cond) glhx.setPipeThermalConductivity(pipe_cond) glhx.setPipeOutDiameter(pipe_diam) glhx.setUTubeDistance(u_tube_sep) glhx.setPipeThickness(pipe_wall_thick) glhx.setMaximumLengthofSimulation(max_sim) glhx.setGFunctionReferenceRatio(reference_ratio) # Add the G Function pairs after removing all old ones glhx.removeAllGFunctions pair_range = 22..(76 * 2 + 22 - 2) # Pairs start on field 22 pair_range.step(2) do |i| lntts = glhx_idf.getDouble(i) gfnc = glhx_idf.getDouble(i + 1) if lntts.empty? || gfnc.empty? runner.registerWarning("Pair in fields #{i} and #{i + 1} missing.") next else lntts = lntts.get gfnc = gfnc.get end # runner.registerInfo("G Function pair: #{lntts} : #{gfnc}") glhx.addGFunction(lntts, gfnc) end return true end end # this allows the measure to be use by the application GLHEProGFunctionImport.new.registerWithApplication