diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 10271e10aa..a7b682aebd 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -162,6 +162,7 @@ set(model_resources_src model/offset_tests.osm model/ParkUnder_Retail_Office_C2.osm model/ASHRAECourthouse.osm + model/A205ExampleChiller.RS0001.a205.cbor ) diff --git a/resources/energyplus/ProposedEnergy+.idd b/resources/energyplus/ProposedEnergy+.idd index 00a56b4725..a7aedd13f3 100644 --- a/resources/energyplus/ProposedEnergy+.idd +++ b/resources/energyplus/ProposedEnergy+.idd @@ -42357,6 +42357,127 @@ Boiler:Steam, \retaincase \default General +Chiller:Electric:ASHRAE205, + \min-fields 11 + \memo This chiller model utilizes ASHRAE Standard 205 compliant representations + \memo for chillers (Representation Specification RS0001). + A1, \field Name + \type alpha + \reference Chillers + \required-field + \reference-class-name validPlantEquipmentTypes + \reference validPlantEquipmentNames + \reference-class-name validBranchEquipmentTypes + \reference validBranchEquipmentNames + A2, \field Representation File Name + \note The name of the ASHRAE205 RS0001 (chiller) representation file + \type alpha + \retaincase + \required-field + A3, \field Performance Interpolation Method + \type choice + \key Linear + \key Cubic + \default Linear + N1, \field Rated Capacity + \note Not yet implemented / reserved for future use. Full load capacity at AHRI 550/590 test conditions. + \note Used to scale representation data. + \type real + \units W + \minimum> 0.0 + \autosizable + \default autosize + N2, \field Sizing Factor + \note Multiplies the autosized flow rates. + \type real + \minimum> 0.0 + \default 1.0 + A4 , \field Ambient Temperature Indicator + \note Used to determine standby losses + \required-field + \type choice + \key Schedule + \key Zone + \key Outdoors + A5 , \field Ambient Temperature Schedule Name + \type object-list + \object-list ScheduleNames + A6, \field Ambient Temperature Zone Name + \note Any energy imbalance on the chiller results in heat added to this zone. + \type object-list + \object-list ZoneNames + A7, \field Ambient Temperature Outdoor Air Node Name + \type node + \note required for Ambient Temperature Indicator=Outdoors + A8, \field Chilled Water Inlet Node Name + \type node + \required-field + A9, \field Chilled Water Outlet Node Name + \type node + \required-field + A10, \field Chilled Water Maximum Requested Flow Rate + \type real + \units m3/s + \minimum> 0 + \autosizable + \ip-units gal/min + \default autosize + A11, \field Condenser Inlet Node Name + \type node + A12, \field Condenser Outlet Node Name + \type node + A13, \field Condenser Maximum Requested Flow Rate + \type real + \units m3/s + \minimum> 0 + \autosizable + \ip-units gal/min + \default autosize + A14, \field Chiller Flow Mode + \note Select operating mode for fluid flow through the chiller. "NotModulated" is for + \note either variable or constant pumping with flow controlled by the external plant system. + \note "ConstantFlow" is for constant pumping with flow controlled by chiller to operate at + \note full design flow rate. "LeavingSetpointModulated" is for variable pumping with flow + \note controlled by chiller to vary flow to target a leaving temperature setpoint. + \type choice + \key ConstantFlow + \key LeavingSetpointModulated + \key NotModulated + \default NotModulated + A15, \field Oil Cooler Inlet Node Name + \note Use if the oil cooler uses an external cooling loop, otherwise the oil cooler will add + \note heat to the ambient conditions (i.e., it is air cooled). + \type node + A16, \field Oil Cooler Outlet Node Name + \type node + A17, \field Oil Cooler Design Flow Rate + \type real + \units m3/s + \minimum> 0 + \ip-units gal/min + A18, \field Auxiliary Inlet Node Name + \note Use if the auxiliary components of the chiller use an external cooling loop, otherwise + \note the auxiliary components will add heat to the ambient conditions (i.e., they are air cooled). + \type node + A19, \field Auxiliary Outlet Node Name + \type node + A20, \field Auxiliary Cooling Design Flow Rate + \type real + \units m3/s + \minimum> 0 + \ip-units gal/min + A21, \field Heat Recovery Inlet Node Name + \note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205. + \type node + A21, \field Heat Recovery Outlet Node Name + \note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205. + \type node + A23; \field End-Use Subcategory + \note Any text may be used here to categorize the end-uses in the ABUPS End Uses by Subcategory table. + \type alpha + \retaincase + \default General + Chiller:Electric:EIR, \min-fields 23 \memo This chiller model is the empirical model from the DOE-2 building Energy diff --git a/resources/model/A205ExampleChiller.RS0001.a205.cbor b/resources/model/A205ExampleChiller.RS0001.a205.cbor new file mode 100644 index 0000000000..7f8e55453f Binary files /dev/null and b/resources/model/A205ExampleChiller.RS0001.a205.cbor differ diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd index 0629c730b7..e10cb6c024 100644 --- a/resources/model/OpenStudio.idd +++ b/resources/model/OpenStudio.idd @@ -13813,6 +13813,137 @@ OS:Chiller:Electric:ReformulatedEIR, \retaincase \default General +OS:Chiller:Electric:ASHRAE205, + \min-fields 12 + \memo This chiller model utilizes ASHRAE Standard 205 compliant representations + \memo for chillers (Representation Specification RS0001). + A1, \field Handle + \type handle + \required-field + A2, \field Name + \type alpha + \reference Chillers + \required-field + \reference ConnectionObject + A3, \field Representation File Name + \note The name of the ASHRAE205 RS0001 (chiller) representation file + \type object-list + \object-list ExternalFileNames + \required-field + A4, \field Performance Interpolation Method + \type choice + \key Linear + \key Cubic + \required-field + N1, \field Rated Capacity + \note Not yet implemented / reserved for future use. Full load capacity at AHRI 550/590 test conditions. + \note Used to scale representation data. + \type real + \units W + \minimum> 0.0 + \autosizable + \required-field + N2, \field Sizing Factor + \note Multiplies the autosized flow rates. + \type real + \minimum> 0.0 + \required-field + A5 , \field Ambient Temperature Indicator + \note Used to determine standby losses + \required-field + \type choice + \key Schedule + \key Zone + \key Outdoors + A6 , \field Ambient Temperature Schedule Name + \type object-list + \object-list ScheduleNames + A7, \field Ambient Temperature Zone Name + \note Any energy imbalance on the chiller results in heat added to this zone. + \type object-list + \object-list ThermalZoneNames + A8, \field Ambient Temperature Outdoor Air Node Name + \type alpha + \note required for Ambient Temperature Indicator=Outdoors + A9, \field Chilled Water Inlet Node Name + \type object-list + \object-list ConnectionNames + \required-field + A10, \field Chilled Water Outlet Node Name + \type object-list + \object-list ConnectionNames + \required-field + A11, \field Chilled Water Maximum Requested Flow Rate + \type real + \units m3/s + \minimum> 0 + \autosizable + \ip-units gal/min + \required-field + A12, \field Condenser Inlet Node Name + \type object-list + \object-list ConnectionNames + A13, \field Condenser Outlet Node Name + \type object-list + \object-list ConnectionNames + A14, \field Condenser Maximum Requested Flow Rate + \type real + \units m3/s + \minimum> 0 + \autosizable + \ip-units gal/min + \required-field + A15, \field Chiller Flow Mode + \note Select operating mode for fluid flow through the chiller. "NotModulated" is for + \note either variable or constant pumping with flow controlled by the external plant system. + \note "ConstantFlow" is for constant pumping with flow controlled by chiller to operate at + \note full design flow rate. "LeavingSetpointModulated" is for variable pumping with flow + \note controlled by chiller to vary flow to target a leaving temperature setpoint. + \type choice + \key ConstantFlow + \key LeavingSetpointModulated + \key NotModulated + \required-field + A16, \field Oil Cooler Inlet Node Name + \note Use if the oil cooler uses an external cooling loop, otherwise the oil cooler will add + \note heat to the ambient conditions (i.e., it is air cooled). + \type object-list + \object-list ConnectionNames + A17, \field Oil Cooler Outlet Node Name + \type object-list + \object-list ConnectionNames + A18, \field Oil Cooler Design Flow Rate + \type real + \units m3/s + \minimum> 0 + \ip-units gal/min + A19, \field Auxiliary Inlet Node Name + \note Use if the auxiliary components of the chiller use an external cooling loop, otherwise + \note the auxiliary components will add heat to the ambient conditions (i.e., they are air cooled). + \type object-list + \object-list ConnectionNames + A20, \field Auxiliary Outlet Node Name + \type object-list + \object-list ConnectionNames + A21, \field Auxiliary Cooling Design Flow Rate + \type real + \units m3/s + \minimum> 0 + \ip-units gal/min + A22, \field Heat Recovery Inlet Node Name + \note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205. + \type object-list + \object-list ConnectionNames + A22, \field Heat Recovery Outlet Node Name + \note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205. + \type object-list + \object-list ConnectionNames + A24; \field End-Use Subcategory + \note Any text may be used here to categorize the end-uses in the ABUPS End Uses by Subcategory table. + \type alpha + \retaincase + \default General + OS:CentralHeatPumpSystem, A1, \field Handle \type handle diff --git a/src/energyplus/CMakeLists.txt b/src/energyplus/CMakeLists.txt index 39f2dd9788..76eb248ddc 100644 --- a/src/energyplus/CMakeLists.txt +++ b/src/energyplus/CMakeLists.txt @@ -62,6 +62,7 @@ set(${target_name}_src ForwardTranslator/ForwardTranslateCFactorUndergroundWallConstruction.cpp ForwardTranslator/ForwardTranslateChillerAbsorption.cpp ForwardTranslator/ForwardTranslateChillerAbsorptionIndirect.cpp + ForwardTranslator/ForwardTranslateChillerElectricASHRAE205.cpp ForwardTranslator/ForwardTranslateChillerElectricEIR.cpp ForwardTranslator/ForwardTranslateChillerElectricReformulatedEIR.cpp ForwardTranslator/ForwardTranslateChillerHeaterPerformanceElectricEIR.cpp @@ -457,6 +458,7 @@ set(${target_name}_src ReverseTranslator/ReverseTranslateBuilding.cpp ReverseTranslator/ReverseTranslateBuildingSurfaceDetailed.cpp ReverseTranslator/ReverseTranslateControllerOutdoorAir.cpp + ReverseTranslator/ReverseTranslateChillerElectricASHRAE205.cpp ReverseTranslator/ReverseTranslateCoilCoolingDX.cpp ReverseTranslator/ReverseTranslateCoilCoolingDXCurveFitPerformance.cpp ReverseTranslator/ReverseTranslateCoilCoolingDXCurveFitOperatingMode.cpp @@ -659,6 +661,7 @@ set(${target_name}_test_src Test/AvailabilityManagerNightCycle_GTest.cpp Test/AvailabilityManagerHybridVentilation_GTest.cpp Test/Building_GTest.cpp + Test/ChillerElectricASHRAE205_GTest.cpp Test/CentralHeatPumpSystem_GTest.cpp Test/CoilCoolingDX_GTest.cpp Test/CoilCoolingDXCurveFitPerformance_GTest.cpp diff --git a/src/energyplus/ForwardTranslator.cpp b/src/energyplus/ForwardTranslator.cpp index 3c4855de9b..bdbce99d3b 100644 --- a/src/energyplus/ForwardTranslator.cpp +++ b/src/energyplus/ForwardTranslator.cpp @@ -937,6 +937,11 @@ namespace energyplus { retVal = translateChillerAbsorptionIndirect(mo); break; } + case openstudio::IddObjectType::OS_Chiller_Electric_ASHRAE205: { + auto mo = modelObject.cast(); + retVal = translateChillerElectricASHRAE205(mo); + break; + } case openstudio::IddObjectType::OS_Chiller_Electric_EIR: { model::ChillerElectricEIR chiller = modelObject.cast(); retVal = translateChillerElectricEIR(chiller); diff --git a/src/energyplus/ForwardTranslator.hpp b/src/energyplus/ForwardTranslator.hpp index 0ad5dc5302..9e574f5c24 100644 --- a/src/energyplus/ForwardTranslator.hpp +++ b/src/energyplus/ForwardTranslator.hpp @@ -121,6 +121,7 @@ namespace model { class CFactorUndergroundWallConstruction; class ChillerAbsorption; class ChillerAbsorptionIndirect; + class ChillerElectricASHRAE205; class ChillerElectricEIR; class ChillerElectricReformulatedEIR; class ChillerHeaterPerformanceElectricEIR; @@ -747,6 +748,8 @@ namespace energyplus { boost::optional translateChillerAbsorptionIndirect(model::ChillerAbsorptionIndirect& modelObject); + boost::optional translateChillerElectricASHRAE205(model::ChillerElectricASHRAE205& modelObject); + boost::optional translateChillerElectricEIR(model::ChillerElectricEIR& modelObject); boost::optional translateChillerElectricReformulatedEIR(model::ChillerElectricReformulatedEIR& modelObject); diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateChillerElectricASHRAE205.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateChillerElectricASHRAE205.cpp new file mode 100644 index 0000000000..aedd456216 --- /dev/null +++ b/src/energyplus/ForwardTranslator/ForwardTranslateChillerElectricASHRAE205.cpp @@ -0,0 +1,218 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#include "../ForwardTranslator.hpp" +#include "../../model/Model.hpp" + +#include "../../model/ChillerElectricASHRAE205.hpp" + +#include "../../model/Schedule.hpp" +#include "../../model/ExternalFile.hpp" +#include "../../model/ThermalZone.hpp" + +#include "../../model/Node.hpp" +#include "../../model/Node_Impl.hpp" + +#include +// #include "../../utilities/idd/IddEnums.hpp" +#include + +using namespace openstudio::model; + +namespace openstudio { + +namespace energyplus { + + boost::optional ForwardTranslator::translateChillerElectricASHRAE205(model::ChillerElectricASHRAE205& modelObject) { + + path filePath = modelObject.representationFile().filePath(); + if (!openstudio::filesystem::exists(filePath)) { + LOG(Warn, modelObject.briefDescription() << "will not be translated, cannot find the representation file '" << filePath << "'"); + return boost::none; + } + + // make the path correct for this system + filePath = openstudio::filesystem::system_complete(filePath); + + if (!modelObject.chilledWaterLoop()) { + LOG(Warn, modelObject.briefDescription() << "will not be translated, it not on a Chilled Water Loop."); + return boost::none; + } + + // Instantiate an IdfObject of the class to store the values + IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Chiller_Electric_ASHRAE205, modelObject); + + // Representation File Name: Required String + idfObject.setString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName, openstudio::toString(filePath)); + + // Performance Interpolation Method + std::string performanceInterpolationMethod = modelObject.performanceInterpolationMethod(); + idfObject.setString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod, performanceInterpolationMethod); + + if (modelObject.isRatedCapacityAutosized()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::RatedCapacity, "Autosize"); + } else { + idfObject.setDouble(Chiller_Electric_ASHRAE205Fields::RatedCapacity, modelObject.ratedCapacity().get()); + } + + // Sizing Factor: Optional Double + idfObject.setDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor, modelObject.sizingFactor()); + + // Ambient Temperature Indicator: Required String + std::string ambientTemperatureIndicator = modelObject.ambientTemperatureIndicator(); + bool ambientTempOk = false; + + if (openstudio::istringEqual("Schedule", ambientTemperatureIndicator)) { + // Ambient Temperature Schedule Name: Optional Object + if (boost::optional _ambientTemperatureSchedule = modelObject.ambientTemperatureSchedule()) { + if (boost::optional _owo = translateAndMapModelObject(_ambientTemperatureSchedule.get())) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName, _owo->nameString()); + ambientTempOk = true; + } + } + } else if (openstudio::istringEqual("Zone", ambientTemperatureIndicator)) { + // Ambient Temperature Zone Name: Optional Object + if (boost::optional _ambientTemperatureZone = modelObject.ambientTemperatureZone()) { + if (boost::optional _owo = translateAndMapModelObject(_ambientTemperatureZone.get())) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName, _owo->nameString()); + ambientTempOk = true; + } + } + } else if (openstudio::istringEqual("Outdoors", ambientTemperatureIndicator)) { + ambientTempOk = true; + } + if (!ambientTempOk) { + LOG(Warn, "For " << modelObject.briefDescription() << " the Ambient Temperature Indicator is " << ambientTemperatureIndicator + << " but the required objects don't match. Falling back to 'Outdoors'"); + ambientTemperatureIndicator = "Outdoors"; + } + + idfObject.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator, ambientTemperatureIndicator); + + if (openstudio::istringEqual("Outdoors", ambientTemperatureIndicator)) { + std::string oaNodeName = modelObject.nameString() + " OA Node"; + if (boost::optional s_ = modelObject.ambientTemperatureOutdoorAirNodeName()) { + oaNodeName = s_.get(); + } + + idfObject.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, oaNodeName); + + IdfObject _oaNodeList(openstudio::IddObjectType::OutdoorAir_NodeList); + _oaNodeList.setString(0, oaNodeName); + m_idfObjects.push_back(_oaNodeList); + } + + // Chilled Water Node Names: Required Nodes + if (auto node_ = modelObject.chilledWaterInletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName, node_->nameString()); + } + + if (auto node_ = modelObject.chilledWaterOutletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName, node_->nameString()); + } + + // Optional Nodes + + // Condenser + if (auto node_ = modelObject.condenserInletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName, node_->nameString()); + } + + if (auto node_ = modelObject.condenserOutletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName, node_->nameString()); + } + + // Heat Recovery + if (auto node_ = modelObject.heatRecoveryInletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName, node_->nameString()); + } + + if (auto node_ = modelObject.heatRecoveryOutletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName, node_->nameString()); + } + + // Oil Cooler + if (auto node_ = modelObject.oilCoolerInletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName, node_->nameString()); + } + + if (auto node_ = modelObject.oilCoolerOutletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName, node_->nameString()); + } + + // Auxiliary + if (auto node_ = modelObject.auxiliaryInletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName, node_->nameString()); + } + + if (auto node_ = modelObject.auxiliaryOutletNode()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName, node_->nameString()); + } + + if (modelObject.isChilledWaterMaximumRequestedFlowRateAutosized()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, "Autosize"); + } else { + // ChilledWater Maximum Requested Flow Rate: boost::optional + if (boost::optional _chilledWaterMaximumRequestedFlowRate = modelObject.chilledWaterMaximumRequestedFlowRate()) { + idfObject.setDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, _chilledWaterMaximumRequestedFlowRate.get()); + } + } + + if (modelObject.isCondenserMaximumRequestedFlowRateAutosized()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, "Autosize"); + } else { + // Condenser Maximum Requested Flow Rate: boost::optional + if (boost::optional _condenserMaximumRequestedFlowRate = modelObject.condenserMaximumRequestedFlowRate()) { + idfObject.setDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, _condenserMaximumRequestedFlowRate.get()); + } + } + + // Chiller Flow Mode: Optional String + idfObject.setString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode, modelObject.chillerFlowMode()); + + // Oil Cooler Design Flow Rate: boost::optional + if (boost::optional _oilCoolerDesignFlowRate = modelObject.oilCoolerDesignFlowRate()) { + idfObject.setDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, _oilCoolerDesignFlowRate.get()); + } + + // Auxiliary Cooling Design Flow Rate: boost::optional + if (boost::optional _auxiliaryCoolingDesignFlowRate = modelObject.auxiliaryCoolingDesignFlowRate()) { + idfObject.setDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, _auxiliaryCoolingDesignFlowRate.get()); + } + + // End-Use Subcategory: Optional String + if (!modelObject.isEndUseSubcategoryDefaulted()) { + idfObject.setString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, modelObject.endUseSubcategory()); + } + + return idfObject; + } // End of translate function + +} // end namespace energyplus +} // end namespace openstudio diff --git a/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp b/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp index 92fbac6415..7852a789cd 100644 --- a/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp +++ b/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp @@ -43,6 +43,8 @@ #include "../../model/CentralHeatPumpSystemModule.hpp" #include "../../model/ChillerHeaterPerformanceElectricEIR.hpp" +#include "../../model/ChillerElectricASHRAE205.hpp" +#include "../../model/ChillerElectricASHRAE205_Impl.hpp" #include "../../model/ChillerElectricEIR.hpp" #include "../../model/ChillerElectricEIR_Impl.hpp" #include "../../model/ChillerElectricReformulatedEIR.hpp" @@ -204,8 +206,14 @@ namespace energyplus { // Probably check if all modules have a ChillerHeaterPerformanceComponent // that has a flow rate hardsized, and multiple by the number of performance comp // Better to add a method to centralheatpumpsystem, for eg "totalDesignFlowRate" - // Problem is that you need to know which loop is requesting this since there are three - // "Design XXX Water Flow Rate" (XXX= Chilled Water, Condenser Water, or Hot Water) + // Problem is that you need to know which loop is requesting this since there are two supply loops with + // "Design XXX Water Flow Rate" (XXX= Chilled Water, or Hot Water) + break; + } + + case openstudio::IddObjectType::OS_Chiller_Electric_ASHRAE205: { + auto chiller = component.cast(); + result = chiller.chilledWaterMaximumRequestedFlowRate(); break; } case openstudio::IddObjectType::OS_Chiller_Electric_EIR: { @@ -483,6 +491,9 @@ namespace energyplus { // As a result, this is handled in coolingComponents() and heatingComponents() directly return ComponentType::NONE; } + case openstudio::IddObjectType::OS_Chiller_Electric_ASHRAE205: { + return ComponentType::COOLING; + } case openstudio::IddObjectType::OS_Chiller_Electric_EIR: { return ComponentType::COOLING; } diff --git a/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp b/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp index 9712c31f76..e2cd3bb2ee 100644 --- a/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp +++ b/src/energyplus/ForwardTranslator/ForwardTranslatePlantLoop.cpp @@ -54,6 +54,8 @@ #include "../../model/BoilerHotWater_Impl.hpp" #include "../../model/CentralHeatPumpSystem.hpp" #include "../../model/CentralHeatPumpSystem_Impl.hpp" +#include "../../model/ChillerElectricASHRAE205.hpp" +#include "../../model/ChillerElectricASHRAE205_Impl.hpp" #include "../../model/ChillerElectricEIR.hpp" #include "../../model/ChillerElectricEIR_Impl.hpp" #include "../../model/ChillerElectricReformulatedEIR.hpp" @@ -152,6 +154,8 @@ namespace energyplus { if (modelObjects.size() > 0) { int i = 0; + boost::optional prevNode_; + for (auto& modelObject : modelObjects) { boost::optional inletNode; boost::optional outletNode; @@ -167,6 +171,7 @@ namespace energyplus { if (modelObject.optionalCast()) { // Skip nodes we don't want them showing up on branches + prevNode_ = modelObject.cast(); continue; } @@ -334,21 +339,45 @@ namespace energyplus { auto sourceLoop = central_hp->sourcePlantLoop(); // supply = cooling Loop - if (loop.handle() == coolingLoop->handle()) { + if (coolingLoop && loop.handle() == coolingLoop->handle()) { inletNode = waterToWaterComponent->supplyInletModelObject()->optionalCast(); outletNode = waterToWaterComponent->supplyOutletModelObject()->optionalCast(); // tertiary = heating loop - } else if (loop.handle() == heatingLoop->handle()) { + } else if (heatingLoop && loop.handle() == heatingLoop->handle()) { inletNode = waterToWaterComponent->tertiaryInletModelObject()->optionalCast(); outletNode = waterToWaterComponent->tertiaryOutletModelObject()->optionalCast(); // demand = source loop - } else if (loop.handle() == sourceLoop->handle()) { + } else if (sourceLoop && loop.handle() == sourceLoop->handle()) { inletNode = waterToWaterComponent->demandInletModelObject()->optionalCast(); outletNode = waterToWaterComponent->demandOutletModelObject()->optionalCast(); } + } else if (auto ch = modelObject.optionalCast()) { + // This one has **FIVE** loops and can be potentially several time on the same loop (demand side, eg: Condenser + Oil Cooler + Auxiliary) + + // Doing this: `if (ch->chilledWaterLoop() && loop.handle() == ch->chilledWaterLoop()->handle())` isn't enough because we could be on the + // same loop, so we rely on prevNode_ instead + OS_ASSERT(prevNode_); + + if (ch->chilledWaterInletNode() && ch->chilledWaterInletNode()->handle() == prevNode_->handle()) { + inletNode = ch->chilledWaterInletNode(); + outletNode = ch->chilledWaterOutletNode(); + } else if (ch->condenserInletNode() && ch->condenserInletNode()->handle() == prevNode_->handle()) { + inletNode = ch->condenserInletNode(); + outletNode = ch->condenserOutletNode(); + } else if (ch->heatRecoveryInletNode() && ch->heatRecoveryInletNode()->handle() == prevNode_->handle()) { + inletNode = ch->heatRecoveryInletNode(); + outletNode = ch->heatRecoveryOutletNode(); + } else if (ch->oilCoolerInletNode() && ch->oilCoolerInletNode()->handle() == prevNode_->handle()) { + inletNode = ch->oilCoolerInletNode(); + outletNode = ch->oilCoolerOutletNode(); + } else if (ch->auxiliaryInletNode() && ch->auxiliaryInletNode()->handle() == prevNode_->handle()) { + inletNode = ch->auxiliaryInletNode(); + outletNode = ch->auxiliaryOutletNode(); + } + // Regular case } else if (isSupplyBranch) { inletNode = waterToWaterComponent->supplyInletModelObject()->optionalCast(); diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateSizingPlant.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateSizingPlant.cpp index 88c8399b6b..2700fe9de6 100644 --- a/src/energyplus/ForwardTranslator/ForwardTranslateSizingPlant.cpp +++ b/src/energyplus/ForwardTranslator/ForwardTranslateSizingPlant.cpp @@ -68,6 +68,9 @@ namespace energyplus { case openstudio::IddObjectType::OS_DistrictHeating: { return PlantSizingType::HOTWATER; } + case openstudio::IddObjectType::OS_Chiller_Electric_ASHRAE205: { + return PlantSizingType::CHILLEDWATER; + } case openstudio::IddObjectType::OS_Chiller_Electric_EIR: { return PlantSizingType::CHILLEDWATER; } diff --git a/src/energyplus/ReverseTranslator.cpp b/src/energyplus/ReverseTranslator.cpp index 9d3ef0e899..45028fa46d 100644 --- a/src/energyplus/ReverseTranslator.cpp +++ b/src/energyplus/ReverseTranslator.cpp @@ -338,6 +338,10 @@ namespace energyplus { modelObject = translateBuilding(workspaceObject); break; } + case openstudio::IddObjectType::Chiller_Electric_ASHRAE205: { + modelObject = translateChillerElectricASHRAE205(workspaceObject); + break; + } case openstudio::IddObjectType::Coil_Heating_Fuel: { //modelObject = translateCoilHeatingGas(workspaceObject ); break; diff --git a/src/energyplus/ReverseTranslator.hpp b/src/energyplus/ReverseTranslator.hpp index 73bbda6606..24cdf0f6b6 100644 --- a/src/energyplus/ReverseTranslator.hpp +++ b/src/energyplus/ReverseTranslator.hpp @@ -95,6 +95,8 @@ namespace energyplus { boost::optional translateBuildingSurfaceDetailed(const WorkspaceObject& workspaceObject); + boost::optional translateChillerElectricASHRAE205(const WorkspaceObject& workspaceObject); + boost::optional translateControllerOutdoorAir(const WorkspaceObject& workspaceObject); boost::optional translateCoilHeatingGas(const WorkspaceObject& workspaceObject); diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateChillerElectricASHRAE205.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateChillerElectricASHRAE205.cpp new file mode 100644 index 0000000000..cc17074003 --- /dev/null +++ b/src/energyplus/ReverseTranslator/ReverseTranslateChillerElectricASHRAE205.cpp @@ -0,0 +1,183 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#include "../ReverseTranslator.hpp" + +#include "../../model/ChillerElectricASHRAE205.hpp" + +#include "../../model/Schedule.hpp" +#include "../../model/Schedule_Impl.hpp" + +#include "../../model/ThermalZone.hpp" +#include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" + +#include "../../model/ExternalFile.hpp" + +#include +#include + +using namespace openstudio::model; + +namespace openstudio { + +namespace energyplus { + + boost::optional ReverseTranslator::translateChillerElectricASHRAE205(const WorkspaceObject& workspaceObject) { + + std::string fileName = workspaceObject.getString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName).get(); + boost::optional extfile = ExternalFile::getExternalFile(m_model, fileName); + + if (!extfile) { + LOG(Error, "Could not translate " << workspaceObject.briefDescription() << ", cannot find Representation File \"" << fileName << "\""); + return boost::none; + } + + boost::optional ambientTemperatureIndicator_ = + workspaceObject.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator); + if (!ambientTemperatureIndicator_) { + LOG(Error, "For " << workspaceObject.briefDescription() << ", cannot find required property 'Ambient Temperature Indicator'"); + return boost::none; + } + + openstudio::model::ChillerElectricASHRAE205 modelObject(extfile.get()); + + // Name + if (boost::optional _name = workspaceObject.name()) { + modelObject.setName(_name.get()); + } + + // Performance Interpolation Method: Optional String + if (boost::optional _performanceInterpolationMethod = + workspaceObject.getString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod)) { + modelObject.setPerformanceInterpolationMethod(_performanceInterpolationMethod.get()); + } + + // Rated Capacity: Optional Double + if (boost::optional _ratedCapacity = workspaceObject.getDouble(Chiller_Electric_ASHRAE205Fields::RatedCapacity)) { + modelObject.setRatedCapacity(_ratedCapacity.get()); + } + + // Sizing Factor: Optional Double + if (boost::optional _sizingFactor = workspaceObject.getDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor)) { + modelObject.setSizingFactor(_sizingFactor.get()); + } + + // Ambient Temperature Indicator: Required String + if (openstudio::istringEqual("Schedule", ambientTemperatureIndicator_.get())) { + + // Ambient Temperature Schedule Name: Optional Object + if (auto wo_sch_ = workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName)) { + if (auto mo_sch_ = translateAndMapWorkspaceObject(wo_sch_.get())) { + if (boost::optional sch_ = mo_sch_->optionalCast()) { + modelObject.setAmbientTemperatureSchedule(sch_.get()); + } else { + LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Ambient Temperature Schedule Name'"); + } + } + } else { + LOG(Warn, workspaceObject.briefDescription() + << " has an Ambient Temperature Indicator of type 'Schedule', but no Schedule assigned. Falling back to 'Outdoors'"); + } + } else if (openstudio::istringEqual("Zone", ambientTemperatureIndicator_.get())) { + + // Ambient Temperature Zone Name: Optional Object + if (auto wo_z_ = workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName)) { + if (auto mo_ = translateAndMapWorkspaceObject(wo_z_.get())) { + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = mo_->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + modelObject.setAmbientTemperatureZone(z_.get()); + } + } else { + LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Ambient Temperature Zone Name'"); + } + } + } else { + LOG(Warn, workspaceObject.briefDescription() + << " has an Ambient Temperature Indicator of type 'Zone', but no Zone assigned. Falling back to 'Outdoors'"); + } + } else { + // Ambient Temperature Outdoor Air Node Name: Optional Node + if (boost::optional s_ = workspaceObject.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName)) { + modelObject.setAmbientTemperatureOutdoorAirNodeName(s_.get()); + } + } + + // Chilled Water Maximum Requested Flow Rate: Optional Double + if (boost::optional _chilledWaterMaximumRequestedFlowRate = + workspaceObject.getDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate)) { + modelObject.setChilledWaterMaximumRequestedFlowRate(_chilledWaterMaximumRequestedFlowRate.get()); + } + + // Condenser Maximum Requested Flow Rate: Optional Double + if (boost::optional _condenserMaximumRequestedFlowRate = + workspaceObject.getDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate)) { + modelObject.setCondenserMaximumRequestedFlowRate(_condenserMaximumRequestedFlowRate.get()); + } + + // Chiller Flow Mode: Optional String + if (boost::optional _chillerFlowMode = workspaceObject.getString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode)) { + modelObject.setChillerFlowMode(_chillerFlowMode.get()); + } + + if (boost::optional _oilCoolerDesignFlowRate = workspaceObject.getDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate)) { + modelObject.setOilCoolerDesignFlowRate(_oilCoolerDesignFlowRate.get()); + } + + // Auxiliary Cooling Design Flow Rate: Optional Double + if (boost::optional _auxiliaryCoolingDesignFlowRate = + workspaceObject.getDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate)) { + modelObject.setAuxiliaryCoolingDesignFlowRate(_auxiliaryCoolingDesignFlowRate.get()); + } + + // End-Use Subcategory: Optional String + if (boost::optional _endUseSubcategory = workspaceObject.getString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory)) { + modelObject.setEndUseSubcategory(_endUseSubcategory.get()); + } + + if (workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName) + || workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName)) { + LOG(Warn, "For " << workspaceObject.briefDescription() << " Loop Connections are NOT ReverseTranslated."); + } + + return modelObject; + } // End of translate function + +} // end namespace energyplus +} // end namespace openstudio diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateCoilCoolingDX.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateCoilCoolingDX.cpp index 1c37e6a1ed..76a4b06920 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateCoilCoolingDX.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateCoilCoolingDX.cpp @@ -38,6 +38,8 @@ #include "../../model/Schedule_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include "../../model/CoilCoolingDXCurveFitPerformance.hpp" #include "../../model/CoilCoolingDXCurveFitPerformance_Impl.hpp" @@ -90,10 +92,14 @@ namespace energyplus { } if ((target = workspaceObject.getTarget(openstudio::Coil_Cooling_DXFields::CondenserZoneName))) { - OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target); - if (modelObject) { - if (auto optZ = modelObject->optionalCast()) { - dx.setCondenserZone(optZ.get()); + if (auto mo_ = translateAndMapWorkspaceObject(*target)) { + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = mo_->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + dx.setCondenserZone(z_.get()); + } + } else { + LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Condenser Zone Name'"); } } } diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageConverter.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageConverter.cpp index 190d87fb17..6aa2e3c63f 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageConverter.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageConverter.cpp @@ -36,6 +36,8 @@ #include "../../model/Schedule_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include "../../model/Curve.hpp" #include "../../model/Curve_Impl.hpp" @@ -53,7 +55,7 @@ namespace energyplus { OptionalModelObject ReverseTranslator::translateElectricLoadCenterStorageConverter(const WorkspaceObject& workspaceObject) { - OptionalModelObject result, omo; + OptionalModelObject omo; OptionalDouble optD; boost::optional owo; OptionalString optS; @@ -116,9 +118,12 @@ namespace energyplus { // ZoneName if ((owo = workspaceObject.getTarget(ElectricLoadCenter_Storage_ConverterFields::ZoneName))) { - if ((omo = translateAndMapWorkspaceObject(owo.get()))) { - if (boost::optional thermalZone = omo->optionalCast()) { - elcConv.setThermalZone(thermalZone.get()); + if (boost::optional mo = translateAndMapWorkspaceObject(owo.get())) { + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = mo->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + elcConv.setThermalZone(z_.get()); + } } } } @@ -129,8 +134,7 @@ namespace energyplus { elcConv.setRadiativeFraction(*optD); } - result = elcConv; - return result; + return elcConv; } } // namespace energyplus diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageLiIonNMCBattery.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageLiIonNMCBattery.cpp index cc449caa71..33a399ee4b 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageLiIonNMCBattery.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageLiIonNMCBattery.cpp @@ -36,6 +36,8 @@ #include "../../model/Schedule_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include @@ -50,7 +52,6 @@ namespace energyplus { OptionalModelObject ReverseTranslator::translateElectricLoadCenterStorageLiIonNMCBattery(const WorkspaceObject& workspaceObject) { - OptionalModelObject result, temp; OptionalDouble d; boost::optional owo; OptionalString optS; @@ -105,8 +106,11 @@ namespace energyplus { // ZoneName if ((owo = workspaceObject.getTarget(ElectricLoadCenter_Storage_LiIonNMCBatteryFields::ZoneName))) { if (boost::optional mo = translateAndMapWorkspaceObject(owo.get())) { - if (boost::optional thermalZone = mo->optionalCast()) { - elcStorLiIonNMCBattery.setThermalZone(thermalZone.get()); + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = mo->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + elcStorLiIonNMCBattery.setThermalZone(z_.get()); + } } } } @@ -201,7 +205,7 @@ namespace energyplus { elcStorLiIonNMCBattery.setBatteryCellInternalElectricalResistance(*d); } - return result; + return elcStorLiIonNMCBattery; } } // namespace energyplus diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageSimple.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageSimple.cpp index eedf6bd632..81ae5ca5b4 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageSimple.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateElectricLoadCenterStorageSimple.cpp @@ -36,6 +36,8 @@ #include "../../model/Schedule_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include @@ -50,7 +52,6 @@ namespace energyplus { OptionalModelObject ReverseTranslator::translateElectricLoadCenterStorageSimple(const WorkspaceObject& workspaceObject) { - OptionalModelObject result, temp; OptionalDouble d; boost::optional owo; OptionalString optS; @@ -74,9 +75,14 @@ namespace energyplus { // ZoneName if ((owo = workspaceObject.getTarget(ElectricLoadCenter_Storage_SimpleFields::ZoneName))) { - if (boost::optional mo = translateAndMapWorkspaceObject(owo.get())) { - if (boost::optional thermalZone = mo->optionalCast()) { - elcStorSimple.setThermalZone(thermalZone.get()); + if (boost::optional mo_ = translateAndMapWorkspaceObject(owo.get())) { + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = mo_->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + elcStorSimple.setThermalZone(z_.get()); + } + } else { + LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Zone Name'"); } } } @@ -129,8 +135,7 @@ namespace energyplus { elcStorSimple.setInitialStateofCharge(*d); } - result = elcStorSimple; - return result; + return elcStorSimple; } } // namespace energyplus diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateFanSystemModel.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateFanSystemModel.cpp index 9116a155ba..011fc6937b 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateFanSystemModel.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateFanSystemModel.cpp @@ -39,6 +39,8 @@ #include "../../model/Curve_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include "../../utilities/idf/IdfExtensibleGroup.hpp" @@ -170,13 +172,17 @@ namespace energyplus { // Motor Loss Zone Name: Optional Object if ((_wo = workspaceObject.getTarget(Fan_SystemModelFields::MotorLossZoneName))) { if ((_mo = translateAndMapWorkspaceObject(_wo.get()))) { - if (boost::optional _motorLossZone = _mo->optionalCast()) { - modelObject.setMotorLossZone(_motorLossZone.get()); + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = _mo->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + modelObject.setMotorLossZone(z_.get()); + } } else { LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Motor Loss Zone Name'"); } } } + // Motor Loss Radiative Fraction: Optional Double if (boost::optional _motorLossRadiativeFraction = workspaceObject.getDouble(Fan_SystemModelFields::MotorLossRadiativeFraction)) { modelObject.setMotorLossRadiativeFraction(_motorLossRadiativeFraction.get()); diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateRefrigerationCase.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateRefrigerationCase.cpp index 30856e8a33..bb94d8ffea 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateRefrigerationCase.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateRefrigerationCase.cpp @@ -36,6 +36,9 @@ #include "../../model/ThermalZone_Impl.hpp" #include "../../model/CurveCubic.hpp" #include "../../model/CurveCubic_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" + #include #include "../../utilities/idd/IddEnums.hpp" #include @@ -88,12 +91,18 @@ namespace energyplus { } // ZoneName if ((wo = workspaceObject.getTarget(Refrigeration_CaseFields::ZoneName))) { - if (boost::optional mo = translateAndMapWorkspaceObject(wo.get())) { - if (boost::optional thermalZone = mo->optionalCast()) { - refrigerationCase->setThermalZone(thermalZone.get()); + if (boost::optional mo_ = translateAndMapWorkspaceObject(wo.get())) { + // Zone is translated, and a Space is returned instead + if (boost::optional space_ = mo_->optionalCast()) { + if (auto z_ = space_->thermalZone()) { + refrigerationCase->setThermalZone(z_.get()); + } + } else { + LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Zone Name'"); } } } + // RatedAmbientTemperature value = workspaceObject.getDouble(Refrigeration_CaseFields::RatedAmbientTemperature); if (value) { diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp index a1ffe2e8ea..49e88815ab 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp @@ -33,6 +33,8 @@ #include "../../model/ZoneMixing_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include "../../model/Schedule.hpp" #include "../../model/Schedule_Impl.hpp" @@ -57,9 +59,15 @@ namespace energyplus { OptionalWorkspaceObject target = workspaceObject.getTarget(openstudio::ZoneCrossMixingFields::ZoneorSpaceName); OptionalThermalZone zone; if (target) { - OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target); - if (modelObject) { - zone = modelObject->optionalCast(); + if (OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target)) { + if (target->iddObject().type() == IddObjectType::Zone) { + // Zone maps to Space in RT + if (auto s_ = modelObject->optionalCast()) { + zone = s_->thermalZone(); + } + } else { + // It's a space, but we don't allow mapping to a space in model SDK for now, and we don't have a Space RT... so we'll never get here + } } } @@ -71,8 +79,13 @@ namespace energyplus { OptionalThermalZone sourceZone; if (target) { OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target); - if (modelObject) { - sourceZone = modelObject->optionalCast(); + if (target->iddObject().type() == IddObjectType::Zone) { + // Zone maps to Space in RT + if (auto s_ = modelObject->optionalCast()) { + sourceZone = s_->thermalZone(); + } + } else { + // It's a space, but we don't allow mapping to a space in model SDK for now, and we don't have a Space RT... so we'll never get here } } diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp index d2c47866e9..94467e6a71 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp @@ -33,6 +33,8 @@ #include "../../model/ZoneMixing_Impl.hpp" #include "../../model/ThermalZone.hpp" #include "../../model/ThermalZone_Impl.hpp" +#include "../../model/Space.hpp" +#include "../../model/Space_Impl.hpp" #include "../../model/Schedule.hpp" #include "../../model/Schedule_Impl.hpp" @@ -57,9 +59,15 @@ namespace energyplus { OptionalWorkspaceObject target = workspaceObject.getTarget(openstudio::ZoneMixingFields::ZoneorSpaceName); OptionalThermalZone zone; if (target) { - OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target); - if (modelObject) { - zone = modelObject->optionalCast(); + if (OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target)) { + if (target->iddObject().type() == IddObjectType::Zone) { + // Zone maps to Space in RT + if (auto s_ = modelObject->optionalCast()) { + zone = s_->thermalZone(); + } + } else { + // It's a space, but we don't allow mapping to a space in model SDK for now, and we don't have a Space RT... so we'll never get here + } } } @@ -67,7 +75,26 @@ namespace energyplus { return boost::none; } + target = workspaceObject.getTarget(openstudio::ZoneMixingFields::SourceZoneorSpaceName); + OptionalThermalZone sourceZone; + if (target) { + OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target); + if (target->iddObject().type() == IddObjectType::Zone) { + // Zone maps to Space in RT + if (auto s_ = modelObject->optionalCast()) { + sourceZone = s_->thermalZone(); + } + } else { + // It's a space, but we don't allow mapping to a space in model SDK for now, and we don't have a Space RT... so we'll never get here + } + } + + if (!sourceZone) { + return boost::none; + } + openstudio::model::ZoneMixing mixing(*zone); + mixing.setSourceZone(sourceZone.get()); OptionalString s = workspaceObject.name(); if (s) { @@ -120,16 +147,6 @@ namespace energyplus { LOG(Error, "Unknown DesignFlowRateCalculationMethod value for workspace object" << workspaceObject); } - target = workspaceObject.getTarget(openstudio::ZoneMixingFields::SourceZoneorSpaceName); - if (target) { - OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target); - if (modelObject) { - if (modelObject->optionalCast()) { - mixing.setSourceZone(modelObject->cast()); - } - } - } - d = workspaceObject.getDouble(openstudio::ZoneMixingFields::DeltaTemperature); if (d) { mixing.setDeltaTemperature(*d); diff --git a/src/energyplus/Test/ChillerElectricASHRAE205_GTest.cpp b/src/energyplus/Test/ChillerElectricASHRAE205_GTest.cpp new file mode 100644 index 0000000000..a5d8772f98 --- /dev/null +++ b/src/energyplus/Test/ChillerElectricASHRAE205_GTest.cpp @@ -0,0 +1,408 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#include +#include "EnergyPlusFixture.hpp" + +#include "../ErrorFile.hpp" +#include "../ForwardTranslator.hpp" +#include "../ReverseTranslator.hpp" + +#include "../../model/ChillerElectricASHRAE205.hpp" +#include "../../model/ChillerElectricASHRAE205_Impl.hpp" + +#include "../../model/ExternalFile.hpp" +#include "../../model/ExternalFile_Impl.hpp" + +#include "../../model/ThermalZone.hpp" +#include "../../model/Space.hpp" +#include "../../model/PlantLoop.hpp" +#include "../../model/PlantLoop_Impl.hpp" +#include "../../model/Node.hpp" +#include "../../model/Node_Impl.hpp" +#include "../../model/Schedule.hpp" +#include "../../model/ScheduleConstant.hpp" +#include "../../model/Mixer.hpp" +#include "../../model/Splitter.hpp" + +#include "../../utilities/geometry/Point3d.hpp" + +#include "../../utilities/idf/IdfObject.hpp" +#include "../../utilities/idf/WorkspaceObject.hpp" +#include "../../utilities/idf/IdfExtensibleGroup.hpp" +#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp" + +// E+ FieldEnums +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "../../utilities/core/PathHelpers.hpp" +#include +#include + +using namespace openstudio::energyplus; +using namespace openstudio::model; +using namespace openstudio; + +/* Ensures that the nodes that translated correctly + * that means correct node names in the ChillerElectricASHRAE205 but also + * on the Branches + */ +TEST_F(EnergyPlusFixture, ForwardTranslator_ChillerElectricASHRAE205) { + + ForwardTranslator ft; + + Model m; + + auto createLoop = [&m](const std::string& prefix) { + PlantLoop p(m); + static constexpr std::array compNames = { + "Supply Inlet", "Supply Splitter", "Supply Connection Node", "Supply Mixer", "Supply Outlet", + "Demand Inlet", "Demand Splitter", "Demand Connection Node", "Demand Mixer", "Demand Outlet", + }; + p.setName(prefix); + for (size_t i = 0; auto& comp : p.components()) { + comp.setName(prefix + " " + std::string{compNames[i++]}); + } + return p; + }; + + openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); + EXPECT_TRUE(exists(p)); + + boost::optional representationFile = ExternalFile::getExternalFile(m, openstudio::toString(p)); + ASSERT_TRUE(representationFile); + + ChillerElectricASHRAE205 ch(representationFile.get()); + + EXPECT_TRUE(ch.setPerformanceInterpolationMethod("Cubic")); + ch.autosizeRatedCapacity(); + EXPECT_TRUE(ch.setSizingFactor(1.1)); + + ThermalZone z(m); + z.setName("Basement"); + { + std::vector sPoints{{0, 0, 0}, {0, 1, 0}, {1, 1, 0}, {1, 0, 0}}; + auto space_ = Space::fromFloorPrint(sPoints, 3.0, m); + ASSERT_TRUE(space_); + EXPECT_TRUE(space_->setThermalZone(z)); + } + + EXPECT_TRUE(ch.setAmbientTemperatureZone(z)); + + EXPECT_TRUE(ch.setChilledWaterMaximumRequestedFlowRate(0.0428)); + + auto chwLoop = createLoop("chwLoop"); + EXPECT_TRUE(chwLoop.addSupplyBranchForComponent(ch)); + + auto cndLoop = createLoop("cndLoop"); + EXPECT_TRUE(cndLoop.addDemandBranchForComponent(ch)); + EXPECT_TRUE(ch.setCondenserMaximumRequestedFlowRate(0.0552)); + + EXPECT_TRUE(ch.setChillerFlowMode("ConstantFlow")); + + auto ocLoop = createLoop("ocLoop"); + EXPECT_TRUE(ch.addDemandBranchOnOilCoolerLoop(ocLoop)); + EXPECT_TRUE(ch.setOilCoolerDesignFlowRate(0.001)); + + auto auxLoop = createLoop("auxLoop"); + EXPECT_TRUE(ch.addDemandBranchOnAuxiliaryLoop(auxLoop)); + EXPECT_TRUE(ch.setAuxiliaryCoolingDesignFlowRate(0.002)); + + EXPECT_TRUE(ch.setEndUseSubcategory("Chiller")); + + ch.chilledWaterInletNode()->setName("ChilledWater Inlet Node"); + ch.chilledWaterOutletNode()->setName("ChilledWater Outlet Node"); + ch.condenserInletNode()->setName("Condenser Inlet Node"); + ch.condenserOutletNode()->setName("Condenser Outlet Node"); + + ch.oilCoolerInletNode()->setName("OilCooler Inlet Node"); + ch.oilCoolerOutletNode()->setName("OilCooler Outlet Node"); + ch.auxiliaryInletNode()->setName("Auxiliary Inlet Node"); + ch.auxiliaryOutletNode()->setName("Auxiliary Outlet Node"); + + if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { + auto hrLoop = createLoop("hrLoop"); + EXPECT_TRUE(hrLoop.addDemandBranchForComponent(ch, true)); + ch.heatRecoveryInletNode()->setName("HeatRecovery Inlet Node"); + ch.heatRecoveryOutletNode()->setName("HeatRecovery Outlet Node"); + } + + { + Workspace w = ft.translateModel(m); + + std::vector woChs = w.getObjectsByType(IddObjectType::Chiller_Electric_ASHRAE205); + ASSERT_EQ(1, woChs.size()); + auto& woCh = woChs.front(); + + EXPECT_EQ(ch.nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::Name).get()); + EXPECT_EQ(representationFile->filePath(), woCh.getString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName).get()); + EXPECT_EQ("Cubic", woCh.getString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod).get()); + EXPECT_EQ("Autosize", woCh.getString(Chiller_Electric_ASHRAE205Fields::RatedCapacity).get()); + EXPECT_EQ(1.1, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor).get()); + + EXPECT_EQ("Zone", woCh.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator).get()); + EXPECT_TRUE(woCh.isEmpty(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName)); + EXPECT_EQ("Basement", woCh.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName).get()); + EXPECT_TRUE(woCh.isEmpty(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName)); + + EXPECT_EQ(0.0428, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate).get()); + + EXPECT_EQ(0.0552, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate).get()); + + EXPECT_EQ("ConstantFlow", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode).get()); + + EXPECT_EQ(0.001, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate).get()); + + EXPECT_EQ(0.002, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate).get()); + + EXPECT_EQ("Chiller", woCh.getString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory).get()); + + EXPECT_EQ("ChilledWater Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName).get()); + EXPECT_EQ("ChilledWater Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName).get()); + EXPECT_EQ(ch.chilledWaterInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName).get()); + EXPECT_EQ(ch.chilledWaterOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName).get()); + + EXPECT_EQ("Condenser Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName).get()); + EXPECT_EQ("Condenser Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName).get()); + EXPECT_EQ(ch.condenserInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName).get()); + EXPECT_EQ(ch.condenserOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName).get()); + + EXPECT_EQ("OilCooler Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName).get()); + EXPECT_EQ("OilCooler Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName).get()); + EXPECT_EQ(ch.oilCoolerInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName).get()); + EXPECT_EQ(ch.oilCoolerOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName).get()); + + EXPECT_EQ("Auxiliary Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName).get()); + EXPECT_EQ("Auxiliary Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName).get()); + EXPECT_EQ(ch.auxiliaryInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName).get()); + EXPECT_EQ(ch.auxiliaryOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName).get()); + + if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { + EXPECT_EQ("HeatRecovery Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName).get()); + EXPECT_EQ("HeatRecovery Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName).get()); + EXPECT_EQ(ch.heatRecoveryInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName).get()); + EXPECT_EQ(ch.heatRecoveryOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName).get()); + } + + // Check node names on supply/demand branches + // Checks that the special case implemented in ForwardTranslatePlantLoop::populateBranch does the right job + + struct Expected + { + bool isSupply = true; + std::string plantName; + std::string inletNodeName; + std::string outletNodeName; + }; + + std::vector expecteds = { + {true, ch.chilledWaterLoop()->nameString(), ch.chilledWaterInletNode()->nameString(), ch.chilledWaterOutletNode()->nameString()}, + {false, ch.condenserWaterLoop()->nameString(), ch.condenserInletNode()->nameString(), ch.condenserOutletNode()->nameString()}, + {false, ch.oilCoolerLoop()->nameString(), ch.oilCoolerInletNode()->nameString(), ch.oilCoolerOutletNode()->nameString()}, + {false, ch.auxiliaryLoop()->nameString(), ch.auxiliaryInletNode()->nameString(), ch.auxiliaryOutletNode()->nameString()}, + }; + + if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { + expecteds.emplace_back(false, ch.heatRecoveryLoop()->nameString(), ch.heatRecoveryInletNode()->nameString(), + ch.heatRecoveryOutletNode()->nameString()); + } + + for (const auto& e : expecteds) { + auto p_ = w.getObjectByTypeAndName(IddObjectType::PlantLoop, e.plantName); + ASSERT_TRUE(p_.is_initialized()) << "Cannot find PlantLoop named " << e.plantName; + WorkspaceObject idf_plant = p_.get(); + unsigned index = e.isSupply ? PlantLoopFields::PlantSideBranchListName : PlantLoopFields::DemandSideBranchListName; + WorkspaceObject idf_brlist = idf_plant.getTarget(index).get(); + + // Should have at least three branches: supply inlet, the one with the Chiller, supply outlet. + // On the demand side, there's also a bypass branch that is added by the FT by default + ASSERT_EQ(e.isSupply ? 3 : 4, idf_brlist.extensibleGroups().size()) << "Failed for " << e.plantName; + // Get the Chiller one + auto w_eg = idf_brlist.extensibleGroups()[1].cast(); + WorkspaceObject idf_branch = w_eg.getTarget(BranchListExtensibleFields::BranchName).get(); + + // There should be only one equipment on the branch + ASSERT_EQ(1, idf_branch.extensibleGroups().size()); + auto w_eg2 = idf_branch.extensibleGroups()[0].cast(); + + ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentName).get(), ch.nameString()); + ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentInletNodeName).get(), e.inletNodeName); + ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentOutletNodeName).get(), e.outletNodeName); + + WorkspaceObject idf_plant_op = p_->getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get(); + if (e.isSupply) { + // Should have created a Cooling Load one only + ASSERT_EQ(1, idf_plant_op.extensibleGroups().size()); + auto w_eg = idf_plant_op.extensibleGroups()[0].cast(); + ASSERT_EQ("PlantEquipmentOperation:CoolingLoad", + w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get()); + + // Get the Operation Scheme + auto op_scheme_ = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName); + ASSERT_TRUE(op_scheme_); + + // Get the Plant Equipment List of this CoolingLoad scheme + // There should only be one Load Range + ASSERT_EQ(1u, op_scheme_->extensibleGroups().size()); + + // Load range 1 + w_eg = op_scheme_->extensibleGroups()[0].cast(); + auto peq_list_ = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName); + ASSERT_TRUE(peq_list_); + + // Should have one equipment on it: CentralHeatPumpSystem + auto peqs = peq_list_->extensibleGroups(); + ASSERT_EQ(1, peqs.size()); + ASSERT_EQ("Chiller:Electric:ASHRAE205", peqs.front().getString(PlantEquipmentListExtensibleFields::EquipmentObjectType).get()); + ASSERT_EQ(ch.nameString(), peqs.front().getString(PlantEquipmentListExtensibleFields::EquipmentName).get()); + + } else { + EXPECT_EQ(0, idf_plant_op.extensibleGroups().size()); + } + } + } + + // Not assigned to a Chilled Water Loop? not translated, it's required + ch.removeFromPlantLoop(); + { + // Assigned to a Surface + Workspace w = ft.translateModel(m); + + EXPECT_EQ(0, w.getObjectsByType(IddObjectType::Chiller_Electric_ASHRAE205).size()); + } +} + +TEST_F(EnergyPlusFixture, ReverseTranslator_ChillerElectricASHRAE205) { + + ReverseTranslator rt; + + Workspace w(StrictnessLevel::Minimal, IddFileType::EnergyPlus); + + auto wo_zone = w.addObject(IdfObject(IddObjectType::Zone)).get(); + wo_zone.setName("Basement"); + + auto woCh = w.addObject(IdfObject(IddObjectType::Chiller_Electric_ASHRAE205)).get(); + woCh.setName("Chiller ASHRAE205"); + + openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); + EXPECT_TRUE(exists(p)); + + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName, openstudio::toString(p))); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod, "Cubic")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::RatedCapacity, "")); // Defaults to Autosize + EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor, 1.1)); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator, "Zone")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName, "")); + EXPECT_TRUE(woCh.setPointer(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName, wo_zone.handle())); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, "")); + EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, 0.0428)); + EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, 0.0552)); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode, "ConstantFlow")); + EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, 0.001)); + EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, 0.002)); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, "Chiller")); + + // Nodes not RT'ed + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName, "ChilledWater Inlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName, "ChilledWater Outlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName, "Condenser Inlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName, "Condenser Outlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName, "OilCooler Inlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName, "OilCooler Outlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName, "Auxiliary Inlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName, "Auxiliary Outlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName, "HeatRecovery Inlet Node")); + EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName, "HeatRecovery Outlet Node")); + + Model m = rt.translateWorkspace(w); + + auto chs = m.getConcreteModelObjects(); + ASSERT_EQ(1, chs.size()); + auto& ch = chs.front(); + + ASSERT_TRUE(ch.name()); + EXPECT_EQ("Chiller ASHRAE205", ch.nameString()); + EXPECT_EQ("A205ExampleChiller.RS0001.a205.cbor", ch.representationFile().fileName()); + EXPECT_EQ("Cubic", ch.performanceInterpolationMethod()); + EXPECT_TRUE(ch.isRatedCapacityAutosized()); + EXPECT_EQ(1.1, ch.sizingFactor()); + EXPECT_EQ("Zone", ch.ambientTemperatureIndicator()); + EXPECT_FALSE(ch.ambientTemperatureSchedule()); + ASSERT_TRUE(ch.ambientTemperatureZone()); + EXPECT_EQ("Basement", ch.ambientTemperatureZone()->nameString()); + EXPECT_FALSE(ch.ambientTemperatureOutdoorAirNodeName()); + + EXPECT_FALSE(ch.isChilledWaterMaximumRequestedFlowRateAutosized()); + ASSERT_TRUE(ch.chilledWaterMaximumRequestedFlowRate()); + EXPECT_EQ(0.0428, ch.chilledWaterMaximumRequestedFlowRate().get()); + + EXPECT_FALSE(ch.isCondenserMaximumRequestedFlowRateAutosized()); + ASSERT_TRUE(ch.condenserMaximumRequestedFlowRate()); + EXPECT_EQ(0.0552, ch.condenserMaximumRequestedFlowRate().get()); + + EXPECT_EQ("ConstantFlow", ch.chillerFlowMode()); + + ASSERT_TRUE(ch.oilCoolerDesignFlowRate()); + EXPECT_EQ(0.001, ch.oilCoolerDesignFlowRate().get()); + ASSERT_TRUE(ch.auxiliaryCoolingDesignFlowRate()); + EXPECT_EQ(0.002, ch.auxiliaryCoolingDesignFlowRate().get()); + + EXPECT_EQ("Chiller", ch.endUseSubcategory()); + + EXPECT_FALSE(ch.chilledWaterLoop()); + EXPECT_FALSE(ch.condenserWaterLoop()); + EXPECT_FALSE(ch.heatRecoveryLoop()); + EXPECT_FALSE(ch.oilCoolerLoop()); + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.chilledWaterInletNode()); + EXPECT_FALSE(ch.chilledWaterOutletNode()); + EXPECT_FALSE(ch.condenserInletNode()); + EXPECT_FALSE(ch.condenserOutletNode()); + EXPECT_FALSE(ch.heatRecoveryInletNode()); + EXPECT_FALSE(ch.heatRecoveryOutletNode()); + EXPECT_FALSE(ch.oilCoolerInletNode()); + EXPECT_FALSE(ch.oilCoolerOutletNode()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); +} diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt index 30e15e8c4b..c73cb7d6b8 100644 --- a/src/model/CMakeLists.txt +++ b/src/model/CMakeLists.txt @@ -281,6 +281,9 @@ set(${target_name}_src ChillerAbsorptionIndirect.hpp ChillerAbsorptionIndirect_Impl.hpp ChillerAbsorptionIndirect.cpp + ChillerElectricASHRAE205.hpp + ChillerElectricASHRAE205_Impl.hpp + ChillerElectricASHRAE205.cpp ChillerElectricEIR.hpp ChillerElectricEIR_Impl.hpp ChillerElectricEIR.cpp @@ -1891,6 +1894,7 @@ set(${target_name}_test_src # test/CentralHeatPumpSystemModule_GTest.cpp => Included in the above test/ChillerAbsorption_GTest.cpp test/ChillerAbsorptionIndirect_GTest.cpp + test/ChillerElectricASHRAE205_GTest.cpp test/ChillerElectricEIR_GTest.cpp test/ChillerElectricReformulatedEIR_GTest.cpp test/ChillerHeaterPerformanceElectricEIR_GTest.cpp diff --git a/src/model/ChillerElectricASHRAE205.cpp b/src/model/ChillerElectricASHRAE205.cpp new file mode 100644 index 0000000000..18a33a96c4 --- /dev/null +++ b/src/model/ChillerElectricASHRAE205.cpp @@ -0,0 +1,1179 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#include "ChillerElectricASHRAE205.hpp" +#include "ChillerElectricASHRAE205_Impl.hpp" + +#include "ExternalFile.hpp" +#include "ExternalFile_Impl.hpp" +#include "Schedule.hpp" +#include "Schedule_Impl.hpp" +#include "Node.hpp" +#include "Node_Impl.hpp" +#include "PlantLoop.hpp" +#include "ThermalZone.hpp" +#include "ThermalZone_Impl.hpp" +#include "Model.hpp" +#include "Mixer.hpp" +#include "Splitter.hpp" + +#include "ScheduleTypeLimits.hpp" +#include "ScheduleTypeRegistry.hpp" + +#include +#include +#include + +#include "../utilities/units/Unit.hpp" + +#include "../utilities/core/Assert.hpp" + +namespace openstudio { +namespace model { + + namespace detail { + + ChillerElectricASHRAE205_Impl::ChillerElectricASHRAE205_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle) + : WaterToWaterComponent_Impl(idfObject, model, keepHandle) { + OS_ASSERT(idfObject.iddObject().type() == ChillerElectricASHRAE205::iddObjectType()); + } + + ChillerElectricASHRAE205_Impl::ChillerElectricASHRAE205_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, + bool keepHandle) + : WaterToWaterComponent_Impl(other, model, keepHandle) { + OS_ASSERT(other.iddObject().type() == ChillerElectricASHRAE205::iddObjectType()); + } + + ChillerElectricASHRAE205_Impl::ChillerElectricASHRAE205_Impl(const ChillerElectricASHRAE205_Impl& other, Model_Impl* model, bool keepHandle) + : WaterToWaterComponent_Impl(other, model, keepHandle) {} + + const std::vector& ChillerElectricASHRAE205_Impl::outputVariableNames() const { + static std::vector result{"Chiller Part Load Ratio", + "Chiller Cycling Ratio", + "Minimum Part Load Ratio", + "Chiller Electricity Rate", + "Chiller Electricity Energy", + "Chiller Evaporator Cooling Rate", + "Chiller Evaporator Cooling Energy", + "Chiller Evaporator Inlet Temperature", + "Chiller Evaporator Outlet Temperature", + "Chiller Evaporator Mass Flow Rate", + "Chiller Condenser Heat Transfer Rate", + "Chiller Condenser Heat Transfer Energy", + "Chiller COP", + "Chiller Condenser Inlet Temperature", + "Chiller Condenser Outlet Temperature", + "Chiller Condenser Mass Flow Rate", + "Chiller Effective Heat Rejection Temperature", + "Chiller Zone Heat Gain Rate", + "Chiller Zone Heat Gain Energy", + "Oil Cooler Heat Transfer Rate", + "Oil Cooler Heat Transfer Energy", + "Auxiliary Heat Transfer Rate", + "Auxiliary Heat Transfer Energy"}; + return result; + } + + IddObjectType ChillerElectricASHRAE205_Impl::iddObjectType() const { + return ChillerElectricASHRAE205::iddObjectType(); + } + + std::vector ChillerElectricASHRAE205_Impl::getScheduleTypeKeys(const Schedule& schedule) const { + std::vector result; + UnsignedVector fieldIndices = getSourceIndices(schedule.handle()); + UnsignedVector::const_iterator b(fieldIndices.begin()); + UnsignedVector::const_iterator e(fieldIndices.end()); + if (std::find(b, e, OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName) != e) { + result.emplace_back("ChillerElectricASHRAE205", "Ambient Temperature"); + } + return result; + } + + boost::optional ChillerElectricASHRAE205_Impl::optionalRepresentationFile() const { + return getObject().getModelObjectTarget(OS_Chiller_Electric_ASHRAE205Fields::RepresentationFileName); + } + + ExternalFile ChillerElectricASHRAE205_Impl::representationFile() const { + boost::optional value = optionalRepresentationFile(); + if (!value) { + LOG_AND_THROW(briefDescription() << " does not have a Representation File attached."); + } + return value.get(); + } + + bool ChillerElectricASHRAE205_Impl::setRepresentationFile(const ExternalFile& externalFile) { + bool result = setPointer(OS_Chiller_Electric_ASHRAE205Fields::RepresentationFileName, externalFile.handle()); + return result; + } + + std::string ChillerElectricASHRAE205_Impl::performanceInterpolationMethod() const { + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod, true); + OS_ASSERT(value); + return value.get(); + } + + bool ChillerElectricASHRAE205_Impl::setPerformanceInterpolationMethod(const std::string& performanceInterpolationMethod) { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod, performanceInterpolationMethod); + return result; + } + + boost::optional ChillerElectricASHRAE205_Impl::ratedCapacity() const { + return getDouble(OS_Chiller_Electric_ASHRAE205Fields::RatedCapacity, true); + } + + bool ChillerElectricASHRAE205_Impl::isRatedCapacityAutosized() const { + bool result = false; + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::RatedCapacity, true); + if (value) { + result = openstudio::istringEqual(value.get(), "Autosize"); + } + return result; + } + + bool ChillerElectricASHRAE205_Impl::setRatedCapacity(double ratedCapacity) { + bool result = setDouble(OS_Chiller_Electric_ASHRAE205Fields::RatedCapacity, ratedCapacity); + return result; + } + + void ChillerElectricASHRAE205_Impl::autosizeRatedCapacity() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::RatedCapacity, "Autosize"); + OS_ASSERT(result); + } + + double ChillerElectricASHRAE205_Impl::sizingFactor() const { + boost::optional value = getDouble(OS_Chiller_Electric_ASHRAE205Fields::SizingFactor, true); + OS_ASSERT(value); + return value.get(); + } + + bool ChillerElectricASHRAE205_Impl::setSizingFactor(double sizingFactor) { + bool result = setDouble(OS_Chiller_Electric_ASHRAE205Fields::SizingFactor, sizingFactor); + return result; + } + + std::string ChillerElectricASHRAE205_Impl::ambientTemperatureIndicator() const { + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator, true); + OS_ASSERT(value); + return value.get(); + } + + bool ChillerElectricASHRAE205_Impl::setAmbientTemperatureIndicator(const std::string& ambientTemperatureIndicator) { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator, ambientTemperatureIndicator); + return result; + } + + boost::optional ChillerElectricASHRAE205_Impl::ambientTemperatureSchedule() const { + return getObject().getModelObjectTarget(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName); + } + + bool ChillerElectricASHRAE205_Impl::setAmbientTemperatureSchedule(Schedule& schedule) { + bool result = + setSchedule(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName, "ChillerElectricASHRAE205", "Ambient Temperature", schedule); + if (result) { + bool ok = setAmbientTemperatureIndicator("Schedule"); + OS_ASSERT(ok); + } + return result; + } + + void ChillerElectricASHRAE205_Impl::resetAmbientTemperatureSchedule() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName, ""); + OS_ASSERT(result); + if (openstudio::istringEqual("Schedule", ambientTemperatureIndicator())) { + result = setAmbientTemperatureIndicator("Outdoors"); + OS_ASSERT(result); + } + } + + boost::optional ChillerElectricASHRAE205_Impl::ambientTemperatureZone() const { + return getObject().getModelObjectTarget(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName); + } + + bool ChillerElectricASHRAE205_Impl::setAmbientTemperatureZone(const ThermalZone& thermalZone) { + bool result = setPointer(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName, thermalZone.handle()); + if (result) { + bool ok = setAmbientTemperatureIndicator("Zone"); + OS_ASSERT(ok); + } + return result; + } + + void ChillerElectricASHRAE205_Impl::resetAmbientTemperatureZone() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName, ""); + OS_ASSERT(result); + if (openstudio::istringEqual("Zone", ambientTemperatureIndicator())) { + result = setAmbientTemperatureIndicator("Outdoors"); + OS_ASSERT(result); + } + } + + boost::optional ChillerElectricASHRAE205_Impl::ambientTemperatureOutdoorAirNodeName() const { + return getString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, false, true); + } + + bool ChillerElectricASHRAE205_Impl::setAmbientTemperatureOutdoorAirNodeName(const std::string& ambientTemperatureOutdoorAirNodeName) { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, ambientTemperatureOutdoorAirNodeName); + if (result) { + bool ok = setAmbientTemperatureIndicator("Outdoors"); + OS_ASSERT(ok); + } + return result; + } + + void ChillerElectricASHRAE205_Impl::resetAmbientTemperatureOutdoorAirNodeName() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, ""); + OS_ASSERT(result); + } + + boost::optional ChillerElectricASHRAE205_Impl::chilledWaterMaximumRequestedFlowRate() const { + return getDouble(OS_Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, true); + } + + bool ChillerElectricASHRAE205_Impl::isChilledWaterMaximumRequestedFlowRateAutosized() const { + bool result = false; + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, true); + if (value) { + result = openstudio::istringEqual(value.get(), "Autosize"); + } + return result; + } + + bool ChillerElectricASHRAE205_Impl::setChilledWaterMaximumRequestedFlowRate(double chilledWaterMaximumRequestedFlowRate) { + bool result = setDouble(OS_Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, chilledWaterMaximumRequestedFlowRate); + return result; + } + + void ChillerElectricASHRAE205_Impl::autosizeChilledWaterMaximumRequestedFlowRate() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, "Autosize"); + OS_ASSERT(result); + } + + boost::optional ChillerElectricASHRAE205_Impl::condenserMaximumRequestedFlowRate() const { + return getDouble(OS_Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, true); + } + + bool ChillerElectricASHRAE205_Impl::isCondenserMaximumRequestedFlowRateAutosized() const { + bool result = false; + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, true); + if (value) { + result = openstudio::istringEqual(value.get(), "Autosize"); + } + return result; + } + + bool ChillerElectricASHRAE205_Impl::setCondenserMaximumRequestedFlowRate(double condenserMaximumRequestedFlowRate) { + bool result = setDouble(OS_Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, condenserMaximumRequestedFlowRate); + return result; + } + + void ChillerElectricASHRAE205_Impl::autosizeCondenserMaximumRequestedFlowRate() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, "Autosize"); + OS_ASSERT(result); + } + + std::string ChillerElectricASHRAE205_Impl::chillerFlowMode() const { + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::ChillerFlowMode, true); + OS_ASSERT(value); + return value.get(); + } + + bool ChillerElectricASHRAE205_Impl::setChillerFlowMode(const std::string& chillerFlowMode) { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::ChillerFlowMode, chillerFlowMode); + return result; + } + + boost::optional ChillerElectricASHRAE205_Impl::oilCoolerDesignFlowRate() const { + return getDouble(OS_Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, true); + } + + bool ChillerElectricASHRAE205_Impl::setOilCoolerDesignFlowRate(double oilCoolerDesignFlowRate) { + bool result = setDouble(OS_Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, oilCoolerDesignFlowRate); + return result; + } + + void ChillerElectricASHRAE205_Impl::resetOilCoolerDesignFlowRate() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, ""); + OS_ASSERT(result); + } + + boost::optional ChillerElectricASHRAE205_Impl::auxiliaryCoolingDesignFlowRate() const { + return getDouble(OS_Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, true); + } + + bool ChillerElectricASHRAE205_Impl::setAuxiliaryCoolingDesignFlowRate(double auxiliaryCoolingDesignFlowRate) { + bool result = setDouble(OS_Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, auxiliaryCoolingDesignFlowRate); + return result; + } + + void ChillerElectricASHRAE205_Impl::resetAuxiliaryCoolingDesignFlowRate() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, ""); + OS_ASSERT(result); + } + + std::string ChillerElectricASHRAE205_Impl::endUseSubcategory() const { + boost::optional value = getString(OS_Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, true); + OS_ASSERT(value); + return value.get(); + } + + bool ChillerElectricASHRAE205_Impl::isEndUseSubcategoryDefaulted() const { + return isEmpty(OS_Chiller_Electric_ASHRAE205Fields::EndUseSubcategory); + } + + bool ChillerElectricASHRAE205_Impl::setEndUseSubcategory(const std::string& endUseSubcategory) { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, endUseSubcategory); + OS_ASSERT(result); + return result; + } + + void ChillerElectricASHRAE205_Impl::resetEndUseSubcategory() { + bool result = setString(OS_Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, ""); + OS_ASSERT(result); + } + + boost::optional ChillerElectricASHRAE205_Impl::autosizedRatedCapacity() { + return getAutosizedValue("Design Size Rated Capacity", "W"); + } + + boost::optional ChillerElectricASHRAE205_Impl::autosizedChilledWaterMaximumRequestedFlowRate() { + return getAutosizedValue("Design Size Chilled Water Maximum Requested Flow Rate", "m3/s"); + } + + boost::optional ChillerElectricASHRAE205_Impl::autosizedCondenserMaximumRequestedFlowRate() { + return getAutosizedValue("Design Size Condenser Maximum Requested Flow Rate", "m3/s"); + } + + void ChillerElectricASHRAE205_Impl::autosize() { + autosizeRatedCapacity(); + autosizeChilledWaterMaximumRequestedFlowRate(); + autosizeCondenserMaximumRequestedFlowRate(); + } + + void ChillerElectricASHRAE205_Impl::applySizingValues() { + boost::optional val; + val = autosizedRatedCapacity(); + if (val) { + setRatedCapacity(val.get()); + } + + val = autosizedChilledWaterMaximumRequestedFlowRate(); + if (val) { + setChilledWaterMaximumRequestedFlowRate(val.get()); + } + + val = autosizedCondenserMaximumRequestedFlowRate(); + if (val) { + setCondenserMaximumRequestedFlowRate(val.get()); + } + } + + // Primary: Chiller Water + unsigned ChillerElectricASHRAE205_Impl::supplyInletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName; + } + + unsigned ChillerElectricASHRAE205_Impl::supplyOutletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName; + } + + boost::optional ChillerElectricASHRAE205_Impl::chilledWaterInletNode() const { + if (auto mo_ = supplyInletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::chilledWaterOutletNode() const { + if (auto mo_ = supplyOutletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + // Secondary: Condenser Loop + + unsigned ChillerElectricASHRAE205_Impl::demandInletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName; + } + + unsigned ChillerElectricASHRAE205_Impl::demandOutletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName; + } + + boost::optional ChillerElectricASHRAE205_Impl::condenserInletNode() const { + if (auto mo_ = demandInletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::condenserOutletNode() const { + if (auto mo_ = demandOutletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + // Tertiary: Heat Recovery + unsigned ChillerElectricASHRAE205_Impl::tertiaryInletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName; + } + + unsigned ChillerElectricASHRAE205_Impl::tertiaryOutletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName; + } + + boost::optional ChillerElectricASHRAE205_Impl::heatRecoveryInletNode() const { + if (auto mo_ = tertiaryInletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::heatRecoveryOutletNode() const { + if (auto mo_ = tertiaryOutletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + bool ChillerElectricASHRAE205_Impl::addToNode(Node& node) { + boost::optional t_plantLoop = node.plantLoop(); + + // If trying to add to a node that is on the demand side of a plant loop + if (t_plantLoop) { + if (t_plantLoop->demandComponent(node.handle())) { + // If there is already a another loop where it's on the demand side (condenser Plant Loop) + boost::optional cndLoop_ = this->condenserWaterLoop(); + if (cndLoop_) { + // And it's not the same as the node's loop + if (t_plantLoop.get() != cndLoop_.get()) { + // And if there is no Heat Recovery (tertiary) + if (!this->heatRecoveryLoop().is_initialized()) { + // Then try to add it to the tertiary one + LOG(Warn, "Calling addToTertiaryNode to connect it to the tertiary (=Heat Recovery) loop for " << briefDescription()); + return this->addToTertiaryNode(node); + } + } + } + } + } + + // All other cases, call the base class implementation + return WaterToWaterComponent_Impl::addToNode(node); + } + + bool ChillerElectricASHRAE205_Impl::addToTertiaryNode(Node& node) { + + if constexpr (!ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { + LOG(Warn, "For " << briefDescription() << ", Heat Recovery isn't implemented by EnergyPlus yet, so tertiary connection is disabled."); + return false; + } + + auto t_plantLoop = node.plantLoop(); + + // Only accept adding to a node that is on a demand side of a plant loop + // Since tertiary here = heat recovery loop (heating) + if (t_plantLoop) { + if (t_plantLoop->demandComponent(node.handle())) { + // Call base class method which accepts both supply and demand + return WaterToWaterComponent_Impl::addToTertiaryNode(node); + } else { + LOG(Info, + "Tertiary Loop (Heat Recovery Loop) connections can only be placed on the Demand side (of a Heating Loop), for " << briefDescription()); + } + } + return false; + } + + std::vector ChillerElectricASHRAE205_Impl::edges(const boost::optional& prev) { + // This handles supply, demand, and tertiary connections + std::vector edges = WaterToWaterComponent_Impl::edges(prev); + + auto pushOilCoolerOutletModelObject = [&]() { + if (auto edgeModelObject = oilCoolerOutletModelObject()) { + auto edgeHVACComponent = edgeModelObject->optionalCast(); + OS_ASSERT(edgeHVACComponent); + edges.push_back(edgeHVACComponent.get()); + } + }; + + auto pushAuxiliaryOutletModelObject = [&]() { + if (auto edgeModelObject = auxiliaryOutletModelObject()) { + auto edgeHVACComponent = edgeModelObject->optionalCast(); + OS_ASSERT(edgeHVACComponent); + edges.push_back(edgeHVACComponent.get()); + } + }; + + if (prev) { + if (auto inletModelObject = oilCoolerInletModelObject()) { + if (prev.get() == inletModelObject.get()) { + pushOilCoolerOutletModelObject(); + return edges; + } + } + if (auto inletModelObject = auxiliaryInletModelObject()) { + if (prev.get() == inletModelObject.get()) { + pushAuxiliaryOutletModelObject(); + return edges; + } + } + } else { + pushOilCoolerOutletModelObject(); + pushAuxiliaryOutletModelObject(); + return edges; + } + + return edges; + } + + boost::optional ChillerElectricASHRAE205_Impl::plantLoop() const { + if (boost::optional mo_ = supplyOutletModelObject()) { + if (boost::optional n_ = mo_->optionalCast()) { + return n_->plantLoop(); + } + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::secondaryPlantLoop() const { + if (boost::optional mo_ = demandOutletModelObject()) { + if (boost::optional n_ = mo_->optionalCast()) { + return n_->plantLoop(); + } + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::tertiaryPlantLoop() const { + if (boost::optional mo_ = tertiaryOutletModelObject()) { + if (boost::optional n_ = mo_->optionalCast()) { + return n_->plantLoop(); + } + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::chilledWaterLoop() const { + return plantLoop(); + } + + boost::optional ChillerElectricASHRAE205_Impl::condenserWaterLoop() const { + return secondaryPlantLoop(); + } + boost::optional ChillerElectricASHRAE205_Impl::heatRecoveryLoop() const { + return tertiaryPlantLoop(); + } + + // Extra loops + + /// Oil Cooler Loop + + unsigned ChillerElectricASHRAE205_Impl::oilCoolerInletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName; + } + + OptionalModelObject ChillerElectricASHRAE205_Impl::oilCoolerInletModelObject() const { + return connectedObject(ChillerElectricASHRAE205_Impl::oilCoolerInletPort()); + } + + boost::optional ChillerElectricASHRAE205_Impl::oilCoolerInletNode() const { + if (auto mo_ = oilCoolerInletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + unsigned ChillerElectricASHRAE205_Impl::oilCoolerOutletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName; + } + + OptionalModelObject ChillerElectricASHRAE205_Impl::oilCoolerOutletModelObject() const { + return connectedObject(ChillerElectricASHRAE205_Impl::oilCoolerOutletPort()); + } + + boost::optional ChillerElectricASHRAE205_Impl::oilCoolerOutletNode() const { + if (auto mo_ = oilCoolerOutletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::oilCoolerLoop() const { + if (boost::optional n_ = oilCoolerOutletNode()) { + return n_->plantLoop(); + } + return boost::none; + } + + bool ChillerElectricASHRAE205_Impl::removeFromOilCoolerLoop() { + if (auto plant = oilCoolerLoop()) { + return HVACComponent_Impl::removeFromLoop(plant->demandInletNode(), plant->demandOutletNode(), oilCoolerInletPort(), oilCoolerOutletPort()); + } + + return false; + } + + bool ChillerElectricASHRAE205_Impl::addDemandBranchOnOilCoolerLoop(PlantLoop& plantLoop) { + Model _model = this->model(); + + if (plantLoop.model() != _model) { + return false; + } + + Splitter splitter = plantLoop.demandSplitter(); + Mixer mixer = plantLoop.demandMixer(); + + if (splitter.outletModelObjects().size() == 1u) { + if (boost::optional mo = splitter.lastOutletModelObject()) { + if (boost::optional node = mo->optionalCast()) { + if ((node->outletModelObject().get() == mixer) && (node->inletModelObject().get() == splitter)) { + + return addToOilCoolerLoopNode(node.get()); + } + } + } + } + + unsigned nextOutletPort = splitter.nextOutletPort(); + unsigned nextInletPort = mixer.nextInletPort(); + + Node node(_model); + + _model.connect(splitter, nextOutletPort, node, node.inletPort()); + _model.connect(node, node.outletPort(), mixer, nextInletPort); + + if (addToOilCoolerLoopNode(node)) { + return true; + } + + _model.disconnect(node, node.outletPort()); + _model.disconnect(node, node.inletPort()); + node.remove(); + + return false; + } + + bool ChillerElectricASHRAE205_Impl::addToOilCoolerLoopNode(Node& node) { + + // One of the example files actually has the Oil Coiler and Auxiliary nodes in series on the same branch... + // But this would require a bunch of changes to allow a component to be twice on the same branch, so I'm not allowing it + if (node.getImpl()->isConnected(getObject())) { + // if ((oilCoolerInletNode() && (node.handle() == oilCoolerInletNode()->handle())) + // || (oilCoolerOutletNode() && (node.handle() == oilCoolerOutletNode()->handle()))) { + return false; + } + + auto plantLoop_ = node.plantLoop(); + if (!plantLoop_) { + LOG(Warn, "For " << briefDescription() << ", Oil Cooler Loop can only be connected to a PlantLoop"); + return false; + } + + if (!plantLoop_->demandComponent(node.handle())) { + LOG(Warn, "For " << briefDescription() << ", Oil Cooler Loop can only be connected to a PlantLoop on the demand side"); + return false; + } + + HVACComponent systemStartComponent = plantLoop_->demandInletNode(); + HVACComponent systemEndComponent = plantLoop_->demandOutletNode(); + + removeFromOilCoolerLoop(); + + return HVACComponent_Impl::addToNode(node, systemStartComponent, systemEndComponent, oilCoolerInletPort(), oilCoolerOutletPort()); + } + + /// Auxiliary Loop + + unsigned ChillerElectricASHRAE205_Impl::auxiliaryInletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName; + } + + OptionalModelObject ChillerElectricASHRAE205_Impl::auxiliaryInletModelObject() const { + return connectedObject(ChillerElectricASHRAE205_Impl::auxiliaryInletPort()); + } + + boost::optional ChillerElectricASHRAE205_Impl::auxiliaryInletNode() const { + if (auto mo_ = auxiliaryInletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + unsigned ChillerElectricASHRAE205_Impl::auxiliaryOutletPort() const { + return OS_Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName; + } + + OptionalModelObject ChillerElectricASHRAE205_Impl::auxiliaryOutletModelObject() const { + return connectedObject(ChillerElectricASHRAE205_Impl::auxiliaryOutletPort()); + } + + boost::optional ChillerElectricASHRAE205_Impl::auxiliaryOutletNode() const { + if (auto mo_ = auxiliaryOutletModelObject()) { + return mo_->optionalCast(); + } + return boost::none; + } + + boost::optional ChillerElectricASHRAE205_Impl::auxiliaryLoop() const { + if (boost::optional n_ = auxiliaryOutletNode()) { + return n_->plantLoop(); + } + return boost::none; + } + + bool ChillerElectricASHRAE205_Impl::removeFromAuxiliaryLoop() { + if (auto plant = auxiliaryLoop()) { + return HVACComponent_Impl::removeFromLoop(plant->demandInletNode(), plant->demandOutletNode(), auxiliaryInletPort(), auxiliaryOutletPort()); + } + + return false; + } + + bool ChillerElectricASHRAE205_Impl::addDemandBranchOnAuxiliaryLoop(PlantLoop& plantLoop) { + Model _model = this->model(); + + if (plantLoop.model() != _model) { + return false; + } + + Splitter splitter = plantLoop.demandSplitter(); + Mixer mixer = plantLoop.demandMixer(); + + if (splitter.outletModelObjects().size() == 1u) { + if (boost::optional mo = splitter.lastOutletModelObject()) { + if (boost::optional node = mo->optionalCast()) { + if ((node->outletModelObject().get() == mixer) && (node->inletModelObject().get() == splitter)) { + + return addToAuxiliaryLoopNode(node.get()); + } + } + } + } + + unsigned nextOutletPort = splitter.nextOutletPort(); + unsigned nextInletPort = mixer.nextInletPort(); + + Node node(_model); + + _model.connect(splitter, nextOutletPort, node, node.inletPort()); + _model.connect(node, node.outletPort(), mixer, nextInletPort); + + if (addToAuxiliaryLoopNode(node)) { + return true; + } + + _model.disconnect(node, node.outletPort()); + _model.disconnect(node, node.inletPort()); + node.remove(); + + return false; + } + + bool ChillerElectricASHRAE205_Impl::addToAuxiliaryLoopNode(Node& node) { + + // One of the example files actually has the Oil Coiler and Auxiliary nodes in series on the same branch... + // But this would require a bunch of changes to allow a component to be twice on the same branch, so I'm not allowing it + if (node.getImpl()->isConnected(getObject())) { + // if ((auxiliaryInletNode() && (node.handle() == auxiliaryInletNode()->handle())) + // || (auxiliaryOutletNode() && (node.handle() == auxiliaryOutletNode()->handle()))) { + return false; + } + + auto plantLoop_ = node.plantLoop(); + if (!plantLoop_) { + LOG(Warn, "For " << briefDescription() << ", Oil Cooler Loop can only be connected to a PlantLoop"); + return false; + } + + if (!plantLoop_->demandComponent(node.handle())) { + LOG(Warn, "For " << briefDescription() << ", Oil Cooler Loop can only be connected to a PlantLoop on the demand side"); + return false; + } + + HVACComponent systemStartComponent = plantLoop_->demandInletNode(); + HVACComponent systemEndComponent = plantLoop_->demandOutletNode(); + + removeFromAuxiliaryLoop(); + + return HVACComponent_Impl::addToNode(node, systemStartComponent, systemEndComponent, auxiliaryInletPort(), auxiliaryOutletPort()); + } + + ModelObject ChillerElectricASHRAE205_Impl::clone(Model model) const { + // WaterToWaterComponent resets the supply, demand and tertiary ports + auto mo = WaterToWaterComponent_Impl::clone(model); + + mo.setString(oilCoolerInletPort(), ""); + mo.setString(oilCoolerOutletPort(), ""); + mo.setString(auxiliaryInletPort(), ""); + mo.setString(auxiliaryOutletPort(), ""); + return mo; + } + + } // namespace detail + + ChillerElectricASHRAE205::ChillerElectricASHRAE205(const ExternalFile& representationFile) + : WaterToWaterComponent(ChillerElectricASHRAE205::iddObjectType(), representationFile.model()) { + OS_ASSERT(getImpl()); + + auto filePath = representationFile.filePath(); + bool ok = (toString(filePath.extension()) == ".cbor"); + if (!ok) { + remove(); + LOG_AND_THROW("External file must have a .cbor extension, got externalfile='" << filePath << "'."); + } + ok = setPointer(OS_Chiller_Electric_ASHRAE205Fields::RepresentationFileName, representationFile.handle()); + OS_ASSERT(ok); + + setPerformanceInterpolationMethod("Linear"); + autosizeRatedCapacity(); + setSizingFactor(1.0); + getImpl()->setAmbientTemperatureIndicator("Outdoors"); + autosizeChilledWaterMaximumRequestedFlowRate(); + autosizeCondenserMaximumRequestedFlowRate(); + setChillerFlowMode("NotModulated"); + setEndUseSubcategory("General"); + } + + IddObjectType ChillerElectricASHRAE205::iddObjectType() { + return {IddObjectType::OS_Chiller_Electric_ASHRAE205}; + } + + std::vector ChillerElectricASHRAE205::performanceInterpolationMethodValues() { + return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), + OS_Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod); + } + + std::vector ChillerElectricASHRAE205::ambientTemperatureIndicatorValues() { + return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator); + } + + std::vector ChillerElectricASHRAE205::chillerFlowModeValues() { + return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Chiller_Electric_ASHRAE205Fields::ChillerFlowMode); + } + + ExternalFile ChillerElectricASHRAE205::representationFile() const { + return getImpl()->representationFile(); + } + + std::string ChillerElectricASHRAE205::performanceInterpolationMethod() const { + return getImpl()->performanceInterpolationMethod(); + } + + boost::optional ChillerElectricASHRAE205::ratedCapacity() const { + return getImpl()->ratedCapacity(); + } + + bool ChillerElectricASHRAE205::isRatedCapacityAutosized() const { + return getImpl()->isRatedCapacityAutosized(); + } + + boost::optional ChillerElectricASHRAE205::autosizedRatedCapacity() { + return getImpl()->autosizedRatedCapacity(); + } + + double ChillerElectricASHRAE205::sizingFactor() const { + return getImpl()->sizingFactor(); + } + + std::string ChillerElectricASHRAE205::ambientTemperatureIndicator() const { + return getImpl()->ambientTemperatureIndicator(); + } + + boost::optional ChillerElectricASHRAE205::ambientTemperatureSchedule() const { + return getImpl()->ambientTemperatureSchedule(); + } + + boost::optional ChillerElectricASHRAE205::ambientTemperatureZone() const { + return getImpl()->ambientTemperatureZone(); + } + + boost::optional ChillerElectricASHRAE205::ambientTemperatureOutdoorAirNodeName() const { + return getImpl()->ambientTemperatureOutdoorAirNodeName(); + } + + boost::optional ChillerElectricASHRAE205::chilledWaterMaximumRequestedFlowRate() const { + return getImpl()->chilledWaterMaximumRequestedFlowRate(); + } + + bool ChillerElectricASHRAE205::isChilledWaterMaximumRequestedFlowRateAutosized() const { + return getImpl()->isChilledWaterMaximumRequestedFlowRateAutosized(); + } + + boost::optional ChillerElectricASHRAE205::autosizedChilledWaterMaximumRequestedFlowRate() { + return getImpl()->autosizedChilledWaterMaximumRequestedFlowRate(); + } + + boost::optional ChillerElectricASHRAE205::condenserMaximumRequestedFlowRate() const { + return getImpl()->condenserMaximumRequestedFlowRate(); + } + + bool ChillerElectricASHRAE205::isCondenserMaximumRequestedFlowRateAutosized() const { + return getImpl()->isCondenserMaximumRequestedFlowRateAutosized(); + } + + boost::optional ChillerElectricASHRAE205::autosizedCondenserMaximumRequestedFlowRate() { + return getImpl()->autosizedCondenserMaximumRequestedFlowRate(); + } + + std::string ChillerElectricASHRAE205::chillerFlowMode() const { + return getImpl()->chillerFlowMode(); + } + + boost::optional ChillerElectricASHRAE205::oilCoolerDesignFlowRate() const { + return getImpl()->oilCoolerDesignFlowRate(); + } + + boost::optional ChillerElectricASHRAE205::auxiliaryCoolingDesignFlowRate() const { + return getImpl()->auxiliaryCoolingDesignFlowRate(); + } + + std::string ChillerElectricASHRAE205::endUseSubcategory() const { + return getImpl()->endUseSubcategory(); + } + + bool ChillerElectricASHRAE205::isEndUseSubcategoryDefaulted() const { + return getImpl()->isEndUseSubcategoryDefaulted(); + } + + bool ChillerElectricASHRAE205::setRepresentationFile(const ExternalFile& externalFile) { + return getImpl()->setRepresentationFile(externalFile); + } + + bool ChillerElectricASHRAE205::setPerformanceInterpolationMethod(const std::string& performanceInterpolationMethod) { + return getImpl()->setPerformanceInterpolationMethod(performanceInterpolationMethod); + } + + bool ChillerElectricASHRAE205::setRatedCapacity(double ratedCapacity) { + return getImpl()->setRatedCapacity(ratedCapacity); + } + + void ChillerElectricASHRAE205::autosizeRatedCapacity() { + getImpl()->autosizeRatedCapacity(); + } + + bool ChillerElectricASHRAE205::setSizingFactor(double sizingFactor) { + return getImpl()->setSizingFactor(sizingFactor); + } + + // Done via the other setAmbientXXX + // bool ChillerElectricASHRAE205::setAmbientTemperatureIndicator(const std::string& ambientTemperatureIndicator) { + // return getImpl()->setAmbientTemperatureIndicator(ambientTemperatureIndicator); + // } + + bool ChillerElectricASHRAE205::setAmbientTemperatureSchedule(Schedule& schedule) { + return getImpl()->setAmbientTemperatureSchedule(schedule); + } + + void ChillerElectricASHRAE205::resetAmbientTemperatureSchedule() { + getImpl()->resetAmbientTemperatureSchedule(); + } + + bool ChillerElectricASHRAE205::setAmbientTemperatureZone(const ThermalZone& thermalZone) { + return getImpl()->setAmbientTemperatureZone(thermalZone); + } + + void ChillerElectricASHRAE205::resetAmbientTemperatureZone() { + getImpl()->resetAmbientTemperatureZone(); + } + + bool ChillerElectricASHRAE205::setAmbientTemperatureOutdoorAirNodeName(const std::string& ambientTemperatureOutdoorAirNodeName) { + return getImpl()->setAmbientTemperatureOutdoorAirNodeName(ambientTemperatureOutdoorAirNodeName); + } + + void ChillerElectricASHRAE205::resetAmbientTemperatureOutdoorAirNodeName() { + getImpl()->resetAmbientTemperatureOutdoorAirNodeName(); + } + + bool ChillerElectricASHRAE205::setChilledWaterMaximumRequestedFlowRate(double chilledWaterMaximumRequestedFlowRate) { + return getImpl()->setChilledWaterMaximumRequestedFlowRate(chilledWaterMaximumRequestedFlowRate); + } + + void ChillerElectricASHRAE205::autosizeChilledWaterMaximumRequestedFlowRate() { + getImpl()->autosizeChilledWaterMaximumRequestedFlowRate(); + } + + bool ChillerElectricASHRAE205::setCondenserMaximumRequestedFlowRate(double condenserMaximumRequestedFlowRate) { + return getImpl()->setCondenserMaximumRequestedFlowRate(condenserMaximumRequestedFlowRate); + } + + void ChillerElectricASHRAE205::autosizeCondenserMaximumRequestedFlowRate() { + getImpl()->autosizeCondenserMaximumRequestedFlowRate(); + } + + bool ChillerElectricASHRAE205::setChillerFlowMode(const std::string& chillerFlowMode) { + return getImpl()->setChillerFlowMode(chillerFlowMode); + } + + bool ChillerElectricASHRAE205::setOilCoolerDesignFlowRate(double oilCoolerDesignFlowRate) { + return getImpl()->setOilCoolerDesignFlowRate(oilCoolerDesignFlowRate); + } + + void ChillerElectricASHRAE205::resetOilCoolerDesignFlowRate() { + getImpl()->resetOilCoolerDesignFlowRate(); + } + + bool ChillerElectricASHRAE205::setAuxiliaryCoolingDesignFlowRate(double auxiliaryCoolingDesignFlowRate) { + return getImpl()->setAuxiliaryCoolingDesignFlowRate(auxiliaryCoolingDesignFlowRate); + } + + void ChillerElectricASHRAE205::resetAuxiliaryCoolingDesignFlowRate() { + getImpl()->resetAuxiliaryCoolingDesignFlowRate(); + } + + bool ChillerElectricASHRAE205::setEndUseSubcategory(const std::string& endUseSubcategory) { + return getImpl()->setEndUseSubcategory(endUseSubcategory); + } + + void ChillerElectricASHRAE205::resetEndUseSubcategory() { + getImpl()->resetEndUseSubcategory(); + } + + // Convenience getters + + boost::optional ChillerElectricASHRAE205::chilledWaterLoop() const { + return getImpl()->chilledWaterLoop(); + } + + boost::optional ChillerElectricASHRAE205::chilledWaterInletNode() const { + return getImpl()->chilledWaterInletNode(); + } + + boost::optional ChillerElectricASHRAE205::chilledWaterOutletNode() const { + return getImpl()->chilledWaterOutletNode(); + } + + boost::optional ChillerElectricASHRAE205::condenserWaterLoop() const { + return getImpl()->condenserWaterLoop(); + } + + boost::optional ChillerElectricASHRAE205::condenserInletNode() const { + return getImpl()->condenserInletNode(); + } + + boost::optional ChillerElectricASHRAE205::condenserOutletNode() const { + return getImpl()->condenserOutletNode(); + } + + boost::optional ChillerElectricASHRAE205::heatRecoveryLoop() const { + return getImpl()->heatRecoveryLoop(); + } + + boost::optional ChillerElectricASHRAE205::heatRecoveryInletNode() const { + return getImpl()->heatRecoveryInletNode(); + } + + boost::optional ChillerElectricASHRAE205::heatRecoveryOutletNode() const { + return getImpl()->heatRecoveryOutletNode(); + } + + // Oil Cooler Loop + + unsigned ChillerElectricASHRAE205::oilCoolerInletPort() const { + return getImpl()->oilCoolerInletPort(); + } + boost::optional ChillerElectricASHRAE205::oilCoolerInletModelObject() const { + return getImpl()->oilCoolerInletModelObject(); + } + boost::optional ChillerElectricASHRAE205::oilCoolerInletNode() const { + return getImpl()->oilCoolerInletNode(); + } + + unsigned ChillerElectricASHRAE205::oilCoolerOutletPort() const { + return getImpl()->oilCoolerOutletPort(); + } + boost::optional ChillerElectricASHRAE205::oilCoolerOutletModelObject() const { + return getImpl()->oilCoolerOutletModelObject(); + } + boost::optional ChillerElectricASHRAE205::oilCoolerOutletNode() const { + return getImpl()->oilCoolerOutletNode(); + } + + boost::optional ChillerElectricASHRAE205::oilCoolerLoop() const { + return getImpl()->oilCoolerLoop(); + } + + bool ChillerElectricASHRAE205::addDemandBranchOnOilCoolerLoop(PlantLoop& plantLoop) { + return getImpl()->addDemandBranchOnOilCoolerLoop(plantLoop); + } + + bool ChillerElectricASHRAE205::addToOilCoolerLoopNode(Node& node) { + return getImpl()->addToOilCoolerLoopNode(node); + } + + bool ChillerElectricASHRAE205::removeFromOilCoolerLoop() { + return getImpl()->removeFromOilCoolerLoop(); + } + + // Auxiliary Loop + + unsigned ChillerElectricASHRAE205::auxiliaryInletPort() const { + return getImpl()->auxiliaryInletPort(); + } + boost::optional ChillerElectricASHRAE205::auxiliaryInletModelObject() const { + return getImpl()->auxiliaryInletModelObject(); + } + boost::optional ChillerElectricASHRAE205::auxiliaryInletNode() const { + return getImpl()->auxiliaryInletNode(); + } + + unsigned ChillerElectricASHRAE205::auxiliaryOutletPort() const { + return getImpl()->auxiliaryOutletPort(); + } + boost::optional ChillerElectricASHRAE205::auxiliaryOutletModelObject() const { + return getImpl()->auxiliaryOutletModelObject(); + } + boost::optional ChillerElectricASHRAE205::auxiliaryOutletNode() const { + return getImpl()->auxiliaryOutletNode(); + } + + boost::optional ChillerElectricASHRAE205::auxiliaryLoop() const { + return getImpl()->auxiliaryLoop(); + } + + bool ChillerElectricASHRAE205::addDemandBranchOnAuxiliaryLoop(PlantLoop& plantLoop) { + return getImpl()->addDemandBranchOnAuxiliaryLoop(plantLoop); + } + + bool ChillerElectricASHRAE205::addToAuxiliaryLoopNode(Node& node) { + return getImpl()->addToAuxiliaryLoopNode(node); + } + + bool ChillerElectricASHRAE205::removeFromAuxiliaryLoop() { + return getImpl()->removeFromAuxiliaryLoop(); + } + + /// @cond + ChillerElectricASHRAE205::ChillerElectricASHRAE205(std::shared_ptr impl) + : WaterToWaterComponent(std::move(impl)) {} + /// @endcond + +} // namespace model +} // namespace openstudio diff --git a/src/model/ChillerElectricASHRAE205.hpp b/src/model/ChillerElectricASHRAE205.hpp new file mode 100644 index 0000000000..396f376c9d --- /dev/null +++ b/src/model/ChillerElectricASHRAE205.hpp @@ -0,0 +1,231 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#ifndef MODEL_CHILLERELECTRICASHRAE205_HPP +#define MODEL_CHILLERELECTRICASHRAE205_HPP + +#include +#include "WaterToWaterComponent.hpp" + +namespace openstudio { + +namespace model { + + class ExternalFile; + class Node; + class ThermalZone; + class Schedule; + + namespace detail { + + class ChillerElectricASHRAE205_Impl; + + } // namespace detail + + /** ChillerElectricASHRAE205 is a WaterToWaterComponent that wraps the OpenStudio IDD object 'OS:Chiller:Electric:ASHRAE205'. */ + class MODEL_API ChillerElectricASHRAE205 : public WaterToWaterComponent + { + public: + /** @name Constructors and Destructors */ + //@{ + + explicit ChillerElectricASHRAE205(const ExternalFile& representationFile); + + virtual ~ChillerElectricASHRAE205() = default; + + // TODO: once supported by E+, re-enable + static constexpr bool isHeatRecoverySupportedByEnergyplus = false; + + //@} + + static IddObjectType iddObjectType(); + + static std::vector performanceInterpolationMethodValues(); + + static std::vector ambientTemperatureIndicatorValues(); + + static std::vector chillerFlowModeValues(); + + /** @name Getters */ + //@{ + + ExternalFile representationFile() const; + + std::string performanceInterpolationMethod() const; + + boost::optional ratedCapacity() const; + bool isRatedCapacityAutosized() const; + + double sizingFactor() const; + + std::string ambientTemperatureIndicator() const; + + boost::optional ambientTemperatureSchedule() const; + + boost::optional ambientTemperatureZone() const; + + boost::optional ambientTemperatureOutdoorAirNodeName() const; + + boost::optional chilledWaterMaximumRequestedFlowRate() const; + bool isChilledWaterMaximumRequestedFlowRateAutosized() const; + + boost::optional condenserMaximumRequestedFlowRate() const; + bool isCondenserMaximumRequestedFlowRateAutosized() const; + + std::string chillerFlowMode() const; + + boost::optional oilCoolerDesignFlowRate() const; + + boost::optional auxiliaryCoolingDesignFlowRate() const; + + std::string endUseSubcategory() const; + bool isEndUseSubcategoryDefaulted() const; + + //@} + /** @name Setters */ + //@{ + + bool setRepresentationFile(const ExternalFile& externalFile); + + bool setPerformanceInterpolationMethod(const std::string& performanceInterpolationMethod); + + bool setRatedCapacity(double ratedCapacity); + void autosizeRatedCapacity(); + + bool setSizingFactor(double sizingFactor); + + // This is done via the setAmbientXXX methods + // bool setAmbientTemperatureIndicator(const std::string& ambientTemperatureIndicator); + + /** This will make the Ambient Temperature Indicator = 'Schedule' */ + bool setAmbientTemperatureSchedule(Schedule& schedule); + /** This will reset the Ambient Temperature Indicator = 'Outdoors' */ + void resetAmbientTemperatureSchedule(); + + /** This will make the Ambient Temperature Indicator = 'Zone' */ + bool setAmbientTemperatureZone(const ThermalZone& thermalZone); + /** This will reset the Ambient Temperature Indicator = 'Outdoors' */ + void resetAmbientTemperatureZone(); + + /** This will make the Ambient Temperature Indicator = 'Outdoors' */ + bool setAmbientTemperatureOutdoorAirNodeName(const std::string& ambientTemperatureOutdoorAirNodeName); + /** This will reset the Ambient Temperature Indicator = 'Outdoors'. A default Outdoor Air Node Name will be used in the ForwardTranslator */ + void resetAmbientTemperatureOutdoorAirNodeName(); + + bool setChilledWaterMaximumRequestedFlowRate(double chilledWaterMaximumRequestedFlowRate); + void autosizeChilledWaterMaximumRequestedFlowRate(); + + bool setCondenserMaximumRequestedFlowRate(double condenserMaximumRequestedFlowRate); + void autosizeCondenserMaximumRequestedFlowRate(); + + bool setChillerFlowMode(const std::string& chillerFlowMode); + + bool setOilCoolerDesignFlowRate(double oilCoolerDesignFlowRate); + void resetOilCoolerDesignFlowRate(); + + bool setAuxiliaryCoolingDesignFlowRate(double auxiliaryCoolingDesignFlowRate); + void resetAuxiliaryCoolingDesignFlowRate(); + + bool setEndUseSubcategory(const std::string& endUseSubcategory); + void resetEndUseSubcategory(); + + //@} + /** @name Other */ + //@{ + + boost::optional autosizedRatedCapacity(); + + boost::optional autosizedChilledWaterMaximumRequestedFlowRate(); + + boost::optional autosizedCondenserMaximumRequestedFlowRate(); + + /** Convenience Function to return the Chilled Water Loop (chiller on supply) **/ + boost::optional chilledWaterLoop() const; + // Same as supplyInletModelObject, but cast to a Node + boost::optional chilledWaterInletNode() const; + boost::optional chilledWaterOutletNode() const; + + /** Convenience Function to return the Condenser Water Loop (chiller on demand side) **/ + boost::optional condenserWaterLoop() const; + boost::optional condenserInletNode() const; + boost::optional condenserOutletNode() const; + + // Not implemented in E+ 22.2.0, but reserved as a Tertiary Loop for now + boost::optional heatRecoveryLoop() const; + boost::optional heatRecoveryInletNode() const; + boost::optional heatRecoveryOutletNode() const; + + unsigned oilCoolerInletPort() const; + boost::optional oilCoolerInletModelObject() const; + boost::optional oilCoolerInletNode() const; + unsigned oilCoolerOutletPort() const; + boost::optional oilCoolerOutletModelObject() const; + boost::optional oilCoolerOutletNode() const; + boost::optional oilCoolerLoop() const; + bool addDemandBranchOnOilCoolerLoop(PlantLoop& plantLoop); + bool addToOilCoolerLoopNode(Node& node); + bool removeFromOilCoolerLoop(); + + unsigned auxiliaryInletPort() const; + boost::optional auxiliaryInletModelObject() const; + boost::optional auxiliaryInletNode() const; + unsigned auxiliaryOutletPort() const; + boost::optional auxiliaryOutletModelObject() const; + boost::optional auxiliaryOutletNode() const; + bool addDemandBranchOnAuxiliaryLoop(PlantLoop& plantLoop); + bool addToAuxiliaryLoopNode(Node& node); + bool removeFromAuxiliaryLoop(); + boost::optional auxiliaryLoop() const; + + //@} + protected: + /// @cond + using ImplType = detail::ChillerElectricASHRAE205_Impl; + + explicit ChillerElectricASHRAE205(std::shared_ptr impl); + + friend class detail::ChillerElectricASHRAE205_Impl; + friend class Model; + friend class IdfObject; + friend class openstudio::detail::IdfObject_Impl; + /// @endcond + private: + REGISTER_LOGGER("openstudio.model.ChillerElectricASHRAE205"); + }; + + /** \relates ChillerElectricASHRAE205*/ + using OptionalChillerElectricASHRAE205 = boost::optional; + + /** \relates ChillerElectricASHRAE205*/ + using ChillerElectricASHRAE205Vector = std::vector; + +} // namespace model +} // namespace openstudio + +#endif // MODEL_CHILLERELECTRICASHRAE205_HPP diff --git a/src/model/ChillerElectricASHRAE205_Impl.hpp b/src/model/ChillerElectricASHRAE205_Impl.hpp new file mode 100644 index 0000000000..002ba8d49d --- /dev/null +++ b/src/model/ChillerElectricASHRAE205_Impl.hpp @@ -0,0 +1,249 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#ifndef MODEL_CHILLERELECTRICASHRAE205_IMPL_HPP +#define MODEL_CHILLERELECTRICASHRAE205_IMPL_HPP + +#include +#include "WaterToWaterComponent_Impl.hpp" + +namespace openstudio { +namespace model { + + class ExternalFile; + class Node; + class ThermalZone; + class Schedule; + + namespace detail { + + /** ChillerElectricASHRAE205_Impl is a WaterToWaterComponent_Impl that is the implementation class for ChillerElectricASHRAE205.*/ + class MODEL_API ChillerElectricASHRAE205_Impl : public WaterToWaterComponent_Impl + { + public: + /** @name Constructors and Destructors */ + //@{ + + ChillerElectricASHRAE205_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle); + + ChillerElectricASHRAE205_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle); + + ChillerElectricASHRAE205_Impl(const ChillerElectricASHRAE205_Impl& other, Model_Impl* model, bool keepHandle); + + virtual ~ChillerElectricASHRAE205_Impl() = default; + + //@} + /** @name Virtual Methods */ + //@{ + + virtual const std::vector& outputVariableNames() const override; + + virtual IddObjectType iddObjectType() const override; + + virtual std::vector getScheduleTypeKeys(const Schedule& schedule) const override; + + // chilledWaterLoop + virtual boost::optional plantLoop() const override; + virtual unsigned supplyInletPort() const override; + virtual unsigned supplyOutletPort() const override; + + // condenserWaterLoop + virtual boost::optional secondaryPlantLoop() const override; + virtual unsigned demandInletPort() const override; + virtual unsigned demandOutletPort() const override; + + // heatRecoveryLoop + virtual boost::optional tertiaryPlantLoop() const override; + virtual unsigned tertiaryInletPort() const override; + virtual unsigned tertiaryOutletPort() const override; + + virtual std::vector edges(const boost::optional& prev) override; + + /** This function will perform a check if trying to add it to a node that is on the demand side of a plant loop. + * If: + * - the node is on the demand side of a loop + * - the node isn't on the current condenser water loop itself + * - the chiller doesn't already have a heat recovery (tertiary) loop, + * then it tries to add it to the Tertiary loop. + * In all other cases, it will call the base class' method WaterToWaterComponent_Impl::addToNode() + * If this is connecting to the demand side of a loop (not tertiary), will set the chiller condenserType to WaterCooled + */ + virtual bool addToNode(Node& node) override; + + /* Restricts addToTertiaryNode to a node that is on the demand side of a plant loop (tertiary = Heat Recovery Loop) */ + virtual bool addToTertiaryNode(Node& node) override; + + virtual ModelObject clone(Model model) const override; + + virtual void autosize() override; + + virtual void applySizingValues() override; + + //@} + /** @name Getters */ + //@{ + + ExternalFile representationFile() const; + + std::string performanceInterpolationMethod() const; + + boost::optional ratedCapacity() const; + bool isRatedCapacityAutosized() const; + + double sizingFactor() const; + + std::string ambientTemperatureIndicator() const; + + boost::optional ambientTemperatureSchedule() const; + + boost::optional ambientTemperatureZone() const; + + boost::optional ambientTemperatureOutdoorAirNodeName() const; + + boost::optional chilledWaterMaximumRequestedFlowRate() const; + bool isChilledWaterMaximumRequestedFlowRateAutosized() const; + + boost::optional condenserMaximumRequestedFlowRate() const; + bool isCondenserMaximumRequestedFlowRateAutosized() const; + + std::string chillerFlowMode() const; + + boost::optional oilCoolerDesignFlowRate() const; + + boost::optional auxiliaryCoolingDesignFlowRate() const; + + std::string endUseSubcategory() const; + bool isEndUseSubcategoryDefaulted() const; + + //@} + /** @name Setters */ + //@{ + + bool setRepresentationFile(const ExternalFile& externalFile); + + bool setPerformanceInterpolationMethod(const std::string& performanceInterpolationMethod); + + bool setRatedCapacity(double ratedCapacity); + void autosizeRatedCapacity(); + + bool setSizingFactor(double sizingFactor); + + bool setAmbientTemperatureIndicator(const std::string& ambientTemperatureIndicator); + + bool setAmbientTemperatureSchedule(Schedule& schedule); + void resetAmbientTemperatureSchedule(); + + bool setAmbientTemperatureZone(const ThermalZone& thermalZone); + void resetAmbientTemperatureZone(); + + bool setAmbientTemperatureOutdoorAirNodeName(const std::string& ambientTemperatureOutdoorAirNodeName); + void resetAmbientTemperatureOutdoorAirNodeName(); + + bool setChilledWaterMaximumRequestedFlowRate(double chilledWaterMaximumRequestedFlowRate); + void autosizeChilledWaterMaximumRequestedFlowRate(); + + bool setCondenserMaximumRequestedFlowRate(double condenserMaximumRequestedFlowRate); + void autosizeCondenserMaximumRequestedFlowRate(); + + bool setChillerFlowMode(const std::string& chillerFlowMode); + + bool setOilCoolerDesignFlowRate(double oilCoolerDesignFlowRate); + void resetOilCoolerDesignFlowRate(); + + bool setAuxiliaryCoolingDesignFlowRate(double auxiliaryCoolingDesignFlowRate); + void resetAuxiliaryCoolingDesignFlowRate(); + + bool setEndUseSubcategory(const std::string& endUseSubcategory); + void resetEndUseSubcategory(); + + //@} + /** @name Other */ + //@{ + + boost::optional autosizedRatedCapacity(); + + boost::optional autosizedChilledWaterMaximumRequestedFlowRate(); + + boost::optional autosizedCondenserMaximumRequestedFlowRate(); + + /** Convenience Function to return the Chilled Water Loop (chiller on supply) **/ + boost::optional chilledWaterLoop() const; + // Same as supplyInletModelObject, but cast to a Node + boost::optional chilledWaterInletNode() const; + boost::optional chilledWaterOutletNode() const; + + /** Convenience Function to return the Condenser Water Loop (chiller on demand side) **/ + boost::optional condenserWaterLoop() const; + boost::optional condenserInletNode() const; + boost::optional condenserOutletNode() const; + + // Not implemented in E+ 22.2.0, but reserved as a Tertiary Loop for now + boost::optional heatRecoveryLoop() const; + boost::optional heatRecoveryInletNode() const; + boost::optional heatRecoveryOutletNode() const; + + /*static*/ unsigned oilCoolerInletPort() const; + boost::optional oilCoolerInletModelObject() const; + /*static*/ unsigned oilCoolerOutletPort() const; + boost::optional oilCoolerOutletModelObject() const; + boost::optional oilCoolerLoop() const; + bool addDemandBranchOnOilCoolerLoop(PlantLoop& plantLoop); + bool addToOilCoolerLoopNode(Node& node); + bool removeFromOilCoolerLoop(); + boost::optional oilCoolerInletNode() const; + boost::optional oilCoolerOutletNode() const; + + /*static*/ unsigned auxiliaryInletPort() const; + boost::optional auxiliaryInletModelObject() const; + /*static*/ unsigned auxiliaryOutletPort() const; + boost::optional auxiliaryOutletModelObject() const; + bool addDemandBranchOnAuxiliaryLoop(PlantLoop& plantLoop); + bool addToAuxiliaryLoopNode(Node& node); + bool removeFromAuxiliaryLoop(); + boost::optional auxiliaryLoop() const; + boost::optional auxiliaryInletNode() const; + boost::optional auxiliaryOutletNode() const; + + //@} + protected: + private: + REGISTER_LOGGER("openstudio.model.ChillerElectricASHRAE205"); + + // Optional getters for use by methods like children() so can remove() if the constructor fails. + // There are other ways for the public versions of these getters to fail--perhaps all required + // objects should be returned as boost::optionals + boost::optional optionalRepresentationFile() const; + }; + + } // namespace detail + +} // namespace model +} // namespace openstudio + +#endif // MODEL_CHILLERELECTRICASHRAE205_IMPL_HPP diff --git a/src/model/ConcreteModelObjects.hpp b/src/model/ConcreteModelObjects.hpp index b3e0264348..ee8f6ce1fd 100644 --- a/src/model/ConcreteModelObjects.hpp +++ b/src/model/ConcreteModelObjects.hpp @@ -106,6 +106,7 @@ #include "ClimateZones.hpp" #include "ChillerAbsorption.hpp" #include "ChillerAbsorptionIndirect.hpp" +#include "ChillerElectricASHRAE205.hpp" #include "ChillerElectricEIR.hpp" #include "ChillerElectricReformulatedEIR.hpp" #include "ChillerHeaterPerformanceElectricEIR.hpp" @@ -639,6 +640,7 @@ #include "CFactorUndergroundWallConstruction_Impl.hpp" #include "ChillerAbsorption_Impl.hpp" #include "ChillerAbsorptionIndirect_Impl.hpp" +#include "ChillerElectricASHRAE205_Impl.hpp" #include "ChillerElectricEIR_Impl.hpp" #include "ChillerElectricReformulatedEIR_Impl.hpp" #include "ChillerHeaterPerformanceElectricEIR_Impl.hpp" diff --git a/src/model/ExternalFile.cpp b/src/model/ExternalFile.cpp index a9af885da4..9d3b4d4d4a 100644 --- a/src/model/ExternalFile.cpp +++ b/src/model/ExternalFile.cpp @@ -33,6 +33,9 @@ #include "Model.hpp" #include "ScheduleFile.hpp" #include "PythonPluginInstance.hpp" +#include "PythonPluginInstance_Impl.hpp" +#include "ChillerElectricASHRAE205.hpp" +#include "ChillerElectricASHRAE205_Impl.hpp" #include #include @@ -45,7 +48,6 @@ #include #include "ScheduleFile_Impl.hpp" -#include "PythonPluginInstance_Impl.hpp" namespace openstudio { namespace model { @@ -114,6 +116,13 @@ namespace model { tmp.insert(tmp.end(), tmp2.begin(), tmp2.end()); } + // ChillerElectricASHRAE205 + std::vector chs = chillerElectricASHRAE205s(); + for (auto& ch : chs) { + std::vector tmp2 = ch.remove(); + tmp.insert(tmp.end(), tmp2.begin(), tmp2.end()); + } + std::vector idfObjects = ModelObject_Impl::remove(); idfObjects.insert(idfObjects.end(), tmp.begin(), tmp.end()); @@ -192,6 +201,11 @@ namespace model { return result; } + std::vector ExternalFile_Impl::chillerElectricASHRAE205s() const { + std::vector result = getObject().getModelObjectSources(); + return result; + } + } // namespace detail boost::optional ExternalFile::getExternalFile(const Model& model, const std::string& filename) { @@ -313,8 +327,12 @@ namespace model { return getImpl()->pythonPluginInstances(); } + std::vector ExternalFile::chillerElectricASHRAE205s() const { + return getImpl()->chillerElectricASHRAE205s(); + } + /// @cond - ExternalFile::ExternalFile(std::shared_ptr impl) : ResourceObject(impl) {} + ExternalFile::ExternalFile(std::shared_ptr impl) : ResourceObject(std::move(impl)) {} /// @endcond } // namespace model diff --git a/src/model/ExternalFile.hpp b/src/model/ExternalFile.hpp index 8f6e525a6b..399928aaeb 100644 --- a/src/model/ExternalFile.hpp +++ b/src/model/ExternalFile.hpp @@ -41,6 +41,7 @@ namespace model { class ScheduleFile; class PythonPluginInstance; + class ChillerElectricASHRAE205; namespace detail { @@ -94,6 +95,8 @@ namespace model { std::vector pythonPluginInstances() const; + std::vector chillerElectricASHRAE205s() const; + //@} protected: /// @cond diff --git a/src/model/ExternalFile_Impl.hpp b/src/model/ExternalFile_Impl.hpp index cfcf53cada..3f12981e6e 100644 --- a/src/model/ExternalFile_Impl.hpp +++ b/src/model/ExternalFile_Impl.hpp @@ -40,6 +40,7 @@ namespace model { class ExternalFile; class ScheduleFile; class PythonPluginInstance; + class ChillerElectricASHRAE205; namespace detail { @@ -101,6 +102,8 @@ namespace model { std::vector pythonPluginInstances() const; + std::vector chillerElectricASHRAE205s() const; + //@} protected: bool setFileName(const std::string& fileName); diff --git a/src/model/Loop.cpp b/src/model/Loop.cpp index 576d7ff195..d9287f57bd 100644 --- a/src/model/Loop.cpp +++ b/src/model/Loop.cpp @@ -39,6 +39,8 @@ #include "Splitter_Impl.hpp" #include "Mixer.hpp" #include "Mixer_Impl.hpp" +#include "HVACComponent.hpp" +#include "HVACComponent_Impl.hpp" #include "StraightComponent.hpp" #include "StraightComponent_Impl.hpp" #include "WaterToAirComponent.hpp" @@ -106,7 +108,9 @@ namespace model { } boost::optional prev; - if (visited.size() >= 2u) prev = visited.rbegin()[1]; + if (visited.size() >= 2u) { + prev = visited.rbegin()[1]; + } std::vector nodes = hvacComponent.getImpl()->edges(prev); @@ -200,7 +204,9 @@ namespace model { void findModelObjects(const HVACComponent& sink, std::vector& visited, std::vector& paths, bool isDemandComponents) { boost::optional prev; - if (visited.size() >= 2u) prev = visited.rbegin()[1]; + if (visited.size() >= 2u) { + prev = visited.rbegin()[1]; + } std::vector nodes = visited.back().getImpl()->edges(prev); diff --git a/src/model/Model.cpp b/src/model/Model.cpp index db96379c9c..6df76e2d05 100644 --- a/src/model/Model.cpp +++ b/src/model/Model.cpp @@ -3873,6 +3873,7 @@ namespace model { REGISTER_CONSTRUCTOR(CFactorUndergroundWallConstruction); REGISTER_CONSTRUCTOR(ChillerAbsorption); REGISTER_CONSTRUCTOR(ChillerAbsorptionIndirect); + REGISTER_CONSTRUCTOR(ChillerElectricASHRAE205); REGISTER_CONSTRUCTOR(ChillerElectricEIR); REGISTER_CONSTRUCTOR(ChillerElectricReformulatedEIR); REGISTER_CONSTRUCTOR(ChillerHeaterPerformanceElectricEIR); @@ -4428,6 +4429,7 @@ namespace model { REGISTER_COPYCONSTRUCTORS(ClimateZones); REGISTER_COPYCONSTRUCTORS(ChillerAbsorption); REGISTER_COPYCONSTRUCTORS(ChillerAbsorptionIndirect); + REGISTER_COPYCONSTRUCTORS(ChillerElectricASHRAE205); REGISTER_COPYCONSTRUCTORS(ChillerElectricEIR); REGISTER_COPYCONSTRUCTORS(ChillerElectricReformulatedEIR); REGISTER_COPYCONSTRUCTORS(ChillerHeaterPerformanceElectricEIR); diff --git a/src/model/ModelHVAC.i b/src/model/ModelHVAC.i index 91590c33dd..bb855885ff 100644 --- a/src/model/ModelHVAC.i +++ b/src/model/ModelHVAC.i @@ -164,6 +164,7 @@ MODELOBJECT_TEMPLATES(AirTerminalDualDuctVAVOutdoorAir); MODELOBJECT_TEMPLATES(CentralHeatPumpSystem); MODELOBJECT_TEMPLATES(CentralHeatPumpSystemModule); MODELOBJECT_TEMPLATES(ChillerHeaterPerformanceElectricEIR); +MODELOBJECT_TEMPLATES(ChillerElectricASHRAE205); MODELOBJECT_TEMPLATES(ChillerElectricEIR); MODELOBJECT_TEMPLATES(ChillerElectricReformulatedEIR); MODELOBJECT_TEMPLATES(CoilCoolingDXMultiSpeedStageData); @@ -285,6 +286,7 @@ SWIG_MODELOBJECT(AirTerminalDualDuctVAVOutdoorAir, 1); SWIG_MODELOBJECT(CentralHeatPumpSystem, 1); SWIG_MODELOBJECT(CentralHeatPumpSystemModule, 1); SWIG_MODELOBJECT(ChillerHeaterPerformanceElectricEIR, 1); +SWIG_MODELOBJECT(ChillerElectricASHRAE205, 1); SWIG_MODELOBJECT(ChillerElectricEIR, 1); SWIG_MODELOBJECT(ChillerElectricReformulatedEIR, 1); SWIG_MODELOBJECT(CoilCoolingDXMultiSpeedStageData, 1); diff --git a/src/model/ScheduleTypeRegistry.cpp b/src/model/ScheduleTypeRegistry.cpp index c53bd048a0..9796e72845 100644 --- a/src/model/ScheduleTypeRegistry.cpp +++ b/src/model/ScheduleTypeRegistry.cpp @@ -171,6 +171,7 @@ namespace model { {"AvailabilityManagerScheduledOff", "Availability Manager Scheduled Off", "schedule", false, "Availability", 0.0, 1.0}, {"CentralHeatPumpSystem", "Ancillary Operation", "ancillaryOperationSchedule", false, "Availability", 0.0, 1.0}, {"CentralHeatPumpSystemModule", "Chiller Heater Modules Control", "chillerHeaterModulesControlSchedule", false, "Availability", 0.0, 1.0}, + {"ChillerElectricASHRAE205", "Ambient Temperature", "ambientTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()}, {"ChillerElectricEIR", "Basin Heater Operating", "basinHeaterSchedule", false, "Availability", 0.0, 1.0}, {"ChillerElectricEIR", "Heat Recovery Inlet High Temperature Limit", "heatRecoveryInletHighTemperatureLimitSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()}, diff --git a/src/model/WaterToWaterComponent.cpp b/src/model/WaterToWaterComponent.cpp index f0a8b6969a..3aaba52581 100644 --- a/src/model/WaterToWaterComponent.cpp +++ b/src/model/WaterToWaterComponent.cpp @@ -256,8 +256,8 @@ namespace model { if (m_secondaryPlantLoop) { return m_secondaryPlantLoop; } else { - // JM: Same comment as for plantLoop() above, though as of 2018-01-03 I can't think of an actual object - // that could be on the demand side of two plant loops + // JM: Same comment as for plantLoop() above + // Any WaterToWaterComponent that has an Heat Recovery loop could be on the demand side of two plant loops boost::optional tertiaryPlantLoop = this->tertiaryPlantLoop(); std::vector plantLoops = this->model().getConcreteModelObjects(); diff --git a/src/model/WaterToWaterComponent_Impl.hpp b/src/model/WaterToWaterComponent_Impl.hpp index a643d93e84..461e75afa6 100644 --- a/src/model/WaterToWaterComponent_Impl.hpp +++ b/src/model/WaterToWaterComponent_Impl.hpp @@ -109,7 +109,7 @@ namespace model { * This method checks for presence of the WaterToWaterComponent on either the supply or demand side of plantLoops * and does an extra check for actual node: the tertiaryOutletModelObject has to be on the plant loop too */ - boost::optional tertiaryPlantLoop() const; + virtual boost::optional tertiaryPlantLoop() const; virtual bool removeFromTertiaryPlantLoop(); diff --git a/src/model/test/ChillerElectricASHRAE205_GTest.cpp b/src/model/test/ChillerElectricASHRAE205_GTest.cpp new file mode 100644 index 0000000000..81b0fb768a --- /dev/null +++ b/src/model/test/ChillerElectricASHRAE205_GTest.cpp @@ -0,0 +1,720 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. 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. +***********************************************************************************************************************/ + +#include "ModelFixture.hpp" + +#include "../ChillerElectricASHRAE205.hpp" +#include "../ChillerElectricASHRAE205_Impl.hpp" + +#include "../ExternalFile.hpp" +#include "../ExternalFile_Impl.hpp" + +#include "../ThermalZone.hpp" +#include "../ThermalZone_Impl.hpp" +#include "../PlantLoop.hpp" +#include "../PlantLoop_Impl.hpp" +#include "../Node.hpp" +#include "../Node_Impl.hpp" +#include "../Schedule.hpp" +#include "../ScheduleConstant.hpp" +#include "../Mixer.hpp" +#include "../Splitter.hpp" + +#include "../../utilities/core/PathHelpers.hpp" +#include +#include + +using namespace openstudio; +using namespace openstudio::model; + +TEST_F(ModelFixture, ChillerElectricASHRAE205_GettersSetters) { + Model m; + + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + + openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); + EXPECT_TRUE(exists(p)); + + openstudio::path expectedDestDir; + std::vector absoluteFilePaths = m.workflowJSON().absoluteFilePaths(); + if (absoluteFilePaths.empty()) { + expectedDestDir = m.workflowJSON().absoluteRootDir(); + } else { + expectedDestDir = absoluteFilePaths[0]; + } + + if (exists(expectedDestDir)) { + removeDirectory(expectedDestDir); + } + ASSERT_FALSE(exists(expectedDestDir)); + EXPECT_TRUE(exists(p)); + + boost::optional representationFile = ExternalFile::getExternalFile(m, openstudio::toString(p)); + ASSERT_TRUE(representationFile); + + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(0, representationFile->chillerElectricASHRAE205s().size()); + EXPECT_EQ(openstudio::toString(p.filename()), representationFile->fileName()); + EXPECT_TRUE(equivalent(expectedDestDir / representationFile->fileName(), representationFile->filePath())); + EXPECT_TRUE(exists(representationFile->filePath())); + EXPECT_NE(p, representationFile->filePath()); + + ChillerElectricASHRAE205 ch(representationFile.get()); + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(1, representationFile->chillerElectricASHRAE205s().size()); + EXPECT_EQ(representationFile->handle(), ch.representationFile().handle()); + + // Representation File Name: Required Object + // ExternalFile obj(m); + // EXPECT_TRUE(ch.setRepresentationFile(obj)); + // EXPECT_EQ(obj, ch.representationFile()); + + // Performance Interpolation Method: Required String + // Ctor default + EXPECT_EQ("Linear", ch.performanceInterpolationMethod()); + EXPECT_TRUE(ch.setPerformanceInterpolationMethod("Cubic")); + EXPECT_EQ("Cubic", ch.performanceInterpolationMethod()); + // Bad Value + EXPECT_FALSE(ch.setPerformanceInterpolationMethod("BADENUM")); + EXPECT_EQ("Cubic", ch.performanceInterpolationMethod()); + + // Rated Capacity: Required Double + // Autosize + ch.autosizeRatedCapacity(); + EXPECT_TRUE(ch.isRatedCapacityAutosized()); + // Set + EXPECT_TRUE(ch.setRatedCapacity(0.5)); + ASSERT_TRUE(ch.ratedCapacity()); + EXPECT_EQ(0.5, ch.ratedCapacity().get()); + EXPECT_FALSE(ch.isRatedCapacityAutosized()); + // Bad Value + EXPECT_FALSE(ch.setRatedCapacity(-10.0)); + ASSERT_TRUE(ch.ratedCapacity()); + EXPECT_EQ(0.5, ch.ratedCapacity().get()); + EXPECT_FALSE(ch.isRatedCapacityAutosized()); + + // Sizing Factor: Required Double + // Ctor default + EXPECT_EQ(1.0, ch.sizingFactor()); + EXPECT_TRUE(ch.setSizingFactor(0.6)); + EXPECT_EQ(0.6, ch.sizingFactor()); + // Bad Value + EXPECT_FALSE(ch.setSizingFactor(-10.0)); + EXPECT_EQ(0.6, ch.sizingFactor()); + + // Ambient Temperature Indicator: Required String + EXPECT_EQ("Outdoors", ch.ambientTemperatureIndicator()); + + // EXPECT_TRUE(ch.setAmbientTemperatureIndicator("Schedule")); + // EXPECT_EQ("Schedule", ch.ambientTemperatureIndicator()); + // // Bad Value + // EXPECT_FALSE(ch.setAmbientTemperatureIndicator("BADENUM")); + // EXPECT_EQ("Schedule", ch.ambientTemperatureIndicator()); + + // Ambient Temperature Schedule Name: Optional Object + ScheduleConstant tempSch(m); + EXPECT_TRUE(ch.setAmbientTemperatureSchedule(tempSch)); + ASSERT_TRUE(ch.ambientTemperatureSchedule()); + EXPECT_EQ(tempSch, ch.ambientTemperatureSchedule().get()); + EXPECT_EQ("Schedule", ch.ambientTemperatureIndicator()); + ch.resetAmbientTemperatureOutdoorAirNodeName(); + EXPECT_EQ("Schedule", ch.ambientTemperatureIndicator()); + ch.resetAmbientTemperatureSchedule(); + EXPECT_EQ("Outdoors", ch.ambientTemperatureIndicator()); + + // Ambient Temperature Zone Name: Optional Object + ThermalZone z(m); + EXPECT_TRUE(ch.setAmbientTemperatureZone(z)); + ASSERT_TRUE(ch.ambientTemperatureZone()); + EXPECT_EQ(z, ch.ambientTemperatureZone().get()); + EXPECT_EQ("Zone", ch.ambientTemperatureIndicator()); + ch.resetAmbientTemperatureSchedule(); + EXPECT_EQ("Zone", ch.ambientTemperatureIndicator()); + ch.resetAmbientTemperatureZone(); + EXPECT_EQ("Outdoors", ch.ambientTemperatureIndicator()); + + EXPECT_TRUE(ch.setAmbientTemperatureZone(z)); + EXPECT_EQ("Zone", ch.ambientTemperatureIndicator()); + ch.resetAmbientTemperatureOutdoorAirNodeName(); + EXPECT_EQ("Zone", ch.ambientTemperatureIndicator()); + + // Ambient Temperature Outdoor Air Node Name: Optional Object + EXPECT_TRUE(ch.setAmbientTemperatureOutdoorAirNodeName("My Outdoor Air Node Name")); + ASSERT_TRUE(ch.ambientTemperatureOutdoorAirNodeName()); + EXPECT_EQ("My Outdoor Air Node Name", ch.ambientTemperatureOutdoorAirNodeName().get()); + EXPECT_EQ("Outdoors", ch.ambientTemperatureIndicator()); + + // Chilled Water Maximum Requested Flow Rate: Required Double + // Ctor default + EXPECT_TRUE(ch.isChilledWaterMaximumRequestedFlowRateAutosized()); + // Set + EXPECT_TRUE(ch.setChilledWaterMaximumRequestedFlowRate(1.3)); + ASSERT_TRUE(ch.chilledWaterMaximumRequestedFlowRate()); + EXPECT_EQ(1.3, ch.chilledWaterMaximumRequestedFlowRate().get()); + // Bad Value + EXPECT_FALSE(ch.setChilledWaterMaximumRequestedFlowRate(-10.0)); + ASSERT_TRUE(ch.chilledWaterMaximumRequestedFlowRate()); + EXPECT_EQ(1.3, ch.chilledWaterMaximumRequestedFlowRate().get()); + EXPECT_FALSE(ch.isChilledWaterMaximumRequestedFlowRateAutosized()); + // Autosize + ch.autosizeChilledWaterMaximumRequestedFlowRate(); + EXPECT_TRUE(ch.isChilledWaterMaximumRequestedFlowRateAutosized()); + + // Condenser Maximum Requested Flow Rate: Required Double + // Ctor default + EXPECT_TRUE(ch.isCondenserMaximumRequestedFlowRateAutosized()); + // Set + EXPECT_TRUE(ch.setCondenserMaximumRequestedFlowRate(1.6)); + ASSERT_TRUE(ch.condenserMaximumRequestedFlowRate()); + EXPECT_EQ(1.6, ch.condenserMaximumRequestedFlowRate().get()); + // Bad Value + EXPECT_FALSE(ch.setCondenserMaximumRequestedFlowRate(-10.0)); + ASSERT_TRUE(ch.condenserMaximumRequestedFlowRate()); + EXPECT_EQ(1.6, ch.condenserMaximumRequestedFlowRate().get()); + EXPECT_FALSE(ch.isCondenserMaximumRequestedFlowRateAutosized()); + // Autosize + ch.autosizeCondenserMaximumRequestedFlowRate(); + EXPECT_TRUE(ch.isCondenserMaximumRequestedFlowRateAutosized()); + + // Chiller Flow Mode: Required String + EXPECT_TRUE(ch.setChillerFlowMode("ConstantFlow")); + EXPECT_EQ("ConstantFlow", ch.chillerFlowMode()); + // Bad Value + EXPECT_FALSE(ch.setChillerFlowMode("BADENUM")); + EXPECT_EQ("ConstantFlow", ch.chillerFlowMode()); + + // Oil Cooler Design Flow Rate: Optional Double + EXPECT_TRUE(ch.setOilCoolerDesignFlowRate(2.0)); + ASSERT_TRUE(ch.oilCoolerDesignFlowRate()); + EXPECT_EQ(2.0, ch.oilCoolerDesignFlowRate().get()); + // Bad Value + EXPECT_FALSE(ch.setOilCoolerDesignFlowRate(-10.0)); + ASSERT_TRUE(ch.oilCoolerDesignFlowRate()); + EXPECT_EQ(2.0, ch.oilCoolerDesignFlowRate().get()); + + // Auxiliary Cooling Design Flow Rate: Optional Double + EXPECT_TRUE(ch.setAuxiliaryCoolingDesignFlowRate(2.3)); + ASSERT_TRUE(ch.auxiliaryCoolingDesignFlowRate()); + EXPECT_EQ(2.3, ch.auxiliaryCoolingDesignFlowRate().get()); + // Bad Value + EXPECT_FALSE(ch.setAuxiliaryCoolingDesignFlowRate(-10.0)); + ASSERT_TRUE(ch.auxiliaryCoolingDesignFlowRate()); + EXPECT_EQ(2.3, ch.auxiliaryCoolingDesignFlowRate().get()); + + // End-Use Subcategory: Optional String + // Default value from IDD + EXPECT_EQ("General", ch.endUseSubcategory()); + // Set + EXPECT_TRUE(ch.setEndUseSubcategory("Chiller")); + EXPECT_EQ("Chiller", ch.endUseSubcategory()); + EXPECT_FALSE(ch.isEndUseSubcategoryDefaulted()); + // Reset + ch.resetEndUseSubcategory(); + EXPECT_TRUE(ch.isEndUseSubcategoryDefaulted()); + EXPECT_EQ("General", ch.endUseSubcategory()); + + // Removing the ExternalFile removes the Chiller + openstudio::path dstPath = representationFile->filePath(); + EXPECT_TRUE(exists(p)); + EXPECT_TRUE(exists(dstPath)); + + representationFile->remove(); + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + + EXPECT_TRUE(exists(p)); + EXPECT_FALSE(exists(dstPath)); +} + +TEST_F(ModelFixture, ChillerElectricASHRAE205_Loops) { + Model m; + + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + + openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); + EXPECT_TRUE(exists(p)); + + openstudio::path expectedDestDir; + std::vector absoluteFilePaths = m.workflowJSON().absoluteFilePaths(); + if (absoluteFilePaths.empty()) { + expectedDestDir = m.workflowJSON().absoluteRootDir(); + } else { + expectedDestDir = absoluteFilePaths[0]; + } + + if (exists(expectedDestDir)) { + removeDirectory(expectedDestDir); + } + ASSERT_FALSE(exists(expectedDestDir)); + EXPECT_TRUE(exists(p)); + + boost::optional representationFile = ExternalFile::getExternalFile(m, openstudio::toString(p)); + ASSERT_TRUE(representationFile); + + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(0, representationFile->chillerElectricASHRAE205s().size()); + EXPECT_EQ(openstudio::toString(p.filename()), representationFile->fileName()); + EXPECT_TRUE(equivalent(expectedDestDir / representationFile->fileName(), representationFile->filePath())); + EXPECT_TRUE(exists(representationFile->filePath())); + EXPECT_NE(p, representationFile->filePath()); + + ChillerElectricASHRAE205 ch(representationFile.get()); + EXPECT_FALSE(ch.chilledWaterLoop()); + EXPECT_FALSE(ch.chilledWaterInletNode()); + EXPECT_FALSE(ch.chilledWaterOutletNode()); + + EXPECT_FALSE(ch.condenserWaterLoop()); + EXPECT_FALSE(ch.condenserInletNode()); + EXPECT_FALSE(ch.condenserOutletNode()); + + EXPECT_FALSE(ch.heatRecoveryLoop()); + EXPECT_FALSE(ch.heatRecoveryInletNode()); + EXPECT_FALSE(ch.heatRecoveryOutletNode()); + + EXPECT_FALSE(ch.oilCoolerLoop()); + EXPECT_FALSE(ch.oilCoolerInletNode()); + EXPECT_FALSE(ch.oilCoolerOutletNode()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + + auto createLoop = [&m](const std::string& prefix) { + PlantLoop p(m); + static constexpr std::array compNames = { + "Supply Inlet", "Supply Splitter", "Supply Connection Node", "Supply Mixer", "Supply Outlet", + "Demand Inlet", "Demand Splitter", "Demand Connection Node", "Demand Mixer", "Demand Outlet", + }; + p.setName(prefix); + for (size_t i = 0; auto& comp : p.components()) { + comp.setName(prefix + " " + std::string{compNames[i++]}); + } + return p; + }; + + auto chwLoop = createLoop("chwLoop"); + auto cndLoop = createLoop("cndLoop"); + auto hrLoop = createLoop("hrLoop"); + auto ocLoop = createLoop("ocLoop"); + auto auxLoop = createLoop("auxLoop"); + + // Chilled Water + { + EXPECT_TRUE(chwLoop.addSupplyBranchForComponent(ch)); + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + + EXPECT_FALSE(ch.condenserWaterLoop()); + EXPECT_FALSE(ch.condenserInletNode()); + EXPECT_FALSE(ch.condenserOutletNode()); + + EXPECT_FALSE(ch.heatRecoveryLoop()); + EXPECT_FALSE(ch.heatRecoveryInletNode()); + EXPECT_FALSE(ch.heatRecoveryOutletNode()); + + EXPECT_FALSE(ch.oilCoolerLoop()); + EXPECT_FALSE(ch.oilCoolerInletNode()); + EXPECT_FALSE(ch.oilCoolerOutletNode()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + } + + // Condenser + { + EXPECT_TRUE(cndLoop.addDemandBranchForComponent(ch)); + ASSERT_TRUE(ch.chilledWaterLoop()); + EXPECT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + EXPECT_FALSE(ch.heatRecoveryLoop()); + EXPECT_FALSE(ch.heatRecoveryInletNode()); + EXPECT_FALSE(ch.heatRecoveryOutletNode()); + + EXPECT_FALSE(ch.oilCoolerLoop()); + EXPECT_FALSE(ch.oilCoolerInletNode()); + EXPECT_FALSE(ch.oilCoolerOutletNode()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + } + + auto checkHeatRecoveryLoop = [&ch, &hrLoop]() { + if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { + + ASSERT_TRUE(ch.heatRecoveryLoop()); + EXPECT_EQ(hrLoop, ch.heatRecoveryLoop().get()); + ASSERT_TRUE(ch.heatRecoveryInletNode()); + EXPECT_EQ(ch.tertiaryInletModelObject()->handle(), ch.heatRecoveryInletNode()->handle()); + ASSERT_TRUE(ch.heatRecoveryOutletNode()); + EXPECT_EQ(ch.tertiaryOutletModelObject()->handle(), ch.heatRecoveryOutletNode()->handle()); + } else { + EXPECT_FALSE(ch.heatRecoveryLoop()); + EXPECT_FALSE(ch.heatRecoveryInletNode()); + EXPECT_FALSE(ch.heatRecoveryOutletNode()); + } + }; + + // Heat Recovery + { + if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { + EXPECT_TRUE(hrLoop.addDemandBranchForComponent(ch)); + } else { + EXPECT_FALSE(hrLoop.addDemandBranchForComponent(ch)); + } + + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + EXPECT_FALSE(ch.oilCoolerLoop()); + EXPECT_FALSE(ch.oilCoolerInletNode()); + EXPECT_FALSE(ch.oilCoolerOutletNode()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + } + + // Oil Cooler Loop + { + auto ocDemandConnectionNodes = ocLoop.demandComponents(ocLoop.demandSplitter(), ocLoop.demandMixer(), IddObjectType::OS_Node); + + // addToNode + { + ASSERT_EQ(1, ocDemandConnectionNodes.size()); + auto ocDemandConnectionNode = ocDemandConnectionNodes[0].cast(); + EXPECT_EQ(5, ocLoop.demandComponents().size()); + EXPECT_TRUE(ch.addToOilCoolerLoopNode(ocDemandConnectionNode)); + EXPECT_EQ(7, ocLoop.demandComponents().size()); + + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + ASSERT_TRUE(ch.oilCoolerLoop()); + EXPECT_EQ(ocLoop, ch.oilCoolerLoop().get()); + ASSERT_TRUE(ch.oilCoolerInletNode()); + EXPECT_EQ(ch.oilCoolerInletModelObject()->handle(), ch.oilCoolerInletNode()->handle()); + ASSERT_TRUE(ch.oilCoolerOutletNode()); + EXPECT_EQ(ch.oilCoolerOutletModelObject()->handle(), ch.oilCoolerOutletNode()->handle()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + } + + // Try removeFromLoop + { + ch.removeFromOilCoolerLoop(); + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + EXPECT_FALSE(ch.oilCoolerLoop()); + EXPECT_FALSE(ch.oilCoolerInletNode()); + EXPECT_FALSE(ch.oilCoolerOutletNode()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + + EXPECT_EQ(5, ocLoop.demandComponents().size()); + } + // Try addDemandBranchOnLoop + { + + EXPECT_TRUE(ch.addDemandBranchOnOilCoolerLoop(ocLoop)); + EXPECT_EQ(7, ocLoop.demandComponents().size()); + + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + ASSERT_TRUE(ch.oilCoolerLoop()); + EXPECT_EQ(ocLoop, ch.oilCoolerLoop().get()); + ASSERT_TRUE(ch.oilCoolerInletNode()); + EXPECT_EQ(ch.oilCoolerInletModelObject()->handle(), ch.oilCoolerInletNode()->handle()); + ASSERT_TRUE(ch.oilCoolerOutletNode()); + EXPECT_EQ(ch.oilCoolerOutletModelObject()->handle(), ch.oilCoolerOutletNode()->handle()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + } + } + + // Auxiliary Loop + { + // AddToNode + { + auto auxDemandConnectionNodes = auxLoop.demandComponents(auxLoop.demandSplitter(), auxLoop.demandMixer(), IddObjectType::OS_Node); + ASSERT_EQ(1, auxDemandConnectionNodes.size()); + auto auxDemandConnectionNode = auxDemandConnectionNodes[0].cast(); + EXPECT_EQ(5, auxLoop.demandComponents().size()); + EXPECT_TRUE(ch.addToAuxiliaryLoopNode(auxDemandConnectionNode)); + EXPECT_EQ(7, auxLoop.demandComponents().size()); + + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + ASSERT_TRUE(ch.plantLoop()); + ASSERT_EQ(chwLoop, ch.plantLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.secondaryPlantLoop()); + ASSERT_EQ(cndLoop, ch.secondaryPlantLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + ASSERT_TRUE(ch.oilCoolerLoop()); + EXPECT_EQ(ocLoop, ch.oilCoolerLoop().get()); + ASSERT_TRUE(ch.oilCoolerInletNode()); + EXPECT_EQ(ch.oilCoolerInletModelObject()->handle(), ch.oilCoolerInletNode()->handle()); + ASSERT_TRUE(ch.oilCoolerOutletNode()); + EXPECT_EQ(ch.oilCoolerOutletModelObject()->handle(), ch.oilCoolerOutletNode()->handle()); + } + { + // Try removeFromLoop + ch.removeFromAuxiliaryLoop(); + EXPECT_EQ(5, auxLoop.demandComponents().size()); + + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + ASSERT_TRUE(ch.oilCoolerLoop()); + EXPECT_EQ(ocLoop, ch.oilCoolerLoop().get()); + ASSERT_TRUE(ch.oilCoolerInletNode()); + EXPECT_EQ(ch.oilCoolerInletModelObject()->handle(), ch.oilCoolerInletNode()->handle()); + ASSERT_TRUE(ch.oilCoolerOutletNode()); + EXPECT_EQ(ch.oilCoolerOutletModelObject()->handle(), ch.oilCoolerOutletNode()->handle()); + + EXPECT_FALSE(ch.auxiliaryLoop()); + EXPECT_FALSE(ch.auxiliaryInletNode()); + EXPECT_FALSE(ch.auxiliaryOutletNode()); + EXPECT_EQ(5, auxLoop.demandComponents().size()); + } + + { + // Try addDemandBranchOnLoop + EXPECT_TRUE(ch.addDemandBranchOnAuxiliaryLoop(auxLoop)); + EXPECT_EQ(7, auxLoop.demandComponents().size()); + + ASSERT_TRUE(ch.chilledWaterLoop()); + ASSERT_EQ(chwLoop, ch.chilledWaterLoop().get()); + ASSERT_TRUE(ch.plantLoop()); + ASSERT_EQ(chwLoop, ch.plantLoop().get()); + EXPECT_TRUE(ch.chilledWaterInletNode()); + EXPECT_EQ(ch.supplyInletModelObject()->handle(), ch.chilledWaterInletNode()->handle()); + EXPECT_TRUE(ch.chilledWaterOutletNode()); + EXPECT_EQ(ch.supplyOutletModelObject()->handle(), ch.chilledWaterOutletNode()->handle()); + + ASSERT_TRUE(ch.condenserWaterLoop()); + EXPECT_EQ(cndLoop, ch.condenserWaterLoop().get()); + ASSERT_TRUE(ch.secondaryPlantLoop()); + ASSERT_EQ(cndLoop, ch.secondaryPlantLoop().get()); + ASSERT_TRUE(ch.condenserInletNode()); + EXPECT_EQ(ch.demandInletModelObject()->handle(), ch.condenserInletNode()->handle()); + ASSERT_TRUE(ch.condenserOutletNode()); + EXPECT_EQ(ch.demandOutletModelObject()->handle(), ch.condenserOutletNode()->handle()); + + checkHeatRecoveryLoop(); + + ASSERT_TRUE(ch.oilCoolerLoop()); + EXPECT_EQ(ocLoop, ch.oilCoolerLoop().get()); + ASSERT_TRUE(ch.oilCoolerInletNode()); + EXPECT_EQ(ch.oilCoolerInletModelObject()->handle(), ch.oilCoolerInletNode()->handle()); + ASSERT_TRUE(ch.oilCoolerOutletNode()); + EXPECT_EQ(ch.oilCoolerOutletModelObject()->handle(), ch.oilCoolerOutletNode()->handle()); + } + } + + // Removing the Chiller doesn't remove the ExternalFile + openstudio::path dstPath = representationFile->filePath(); + EXPECT_TRUE(exists(p)); + EXPECT_TRUE(exists(dstPath)); + + ch.remove(); + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(0, m.getConcreteModelObjects().size()); + + EXPECT_TRUE(exists(p)); + EXPECT_TRUE(exists(dstPath)); +} + +TEST_F(ModelFixture, ChillerElectricASHRAE205_NotCBORFile) { + Model model; + + openstudio::path p = resourcesPath() / toPath("model/7-7_Windows_Complete.osm"); + EXPECT_TRUE(exists(p)); + + boost::optional representationFile = ExternalFile::getExternalFile(model, openstudio::toString(p)); + ASSERT_TRUE(representationFile); + + ASSERT_THROW(ChillerElectricASHRAE205{representationFile.get()}, openstudio::Exception); +} + +TEST_F(ModelFixture, ChillerElectricASHRAE205_Clone) { + Model m; + + openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); + EXPECT_TRUE(exists(p)); + + openstudio::path expectedDestDir; + std::vector absoluteFilePaths = m.workflowJSON().absoluteFilePaths(); + if (absoluteFilePaths.empty()) { + expectedDestDir = m.workflowJSON().absoluteRootDir(); + } else { + expectedDestDir = absoluteFilePaths[0]; + } + + if (exists(expectedDestDir)) { + removeDirectory(expectedDestDir); + } + ASSERT_FALSE(exists(expectedDestDir)); + EXPECT_TRUE(exists(p)); + + boost::optional representationFile = ExternalFile::getExternalFile(m, openstudio::toString(p)); + ASSERT_TRUE(representationFile); + + ChillerElectricASHRAE205 ch(representationFile.get()); + + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(representationFile->handle(), ch.representationFile().handle()); + EXPECT_EQ(1, representationFile->chillerElectricASHRAE205s().size()); + + { + // Another model first, so we don't mess the original one + Model m2; + auto ch2 = ch.clone(m2).cast(); + + ASSERT_EQ(1, m2.getConcreteModelObjects().size()); + auto representationFile2 = m2.getConcreteModelObjects().front(); + EXPECT_EQ(1, m2.getConcreteModelObjects().size()); + EXPECT_EQ(representationFile2.handle(), ch2.representationFile().handle()); + EXPECT_NE(representationFile->handle(), ch2.representationFile().handle()); + EXPECT_EQ(1, representationFile2.chillerElectricASHRAE205s().size()); + } + + { + // Same Model, should point to the same ExternaFile + auto ch2 = ch.clone(m).cast(); + EXPECT_EQ(1, m.getConcreteModelObjects().size()); + EXPECT_EQ(2, m.getConcreteModelObjects().size()); + EXPECT_EQ(representationFile->handle(), ch.representationFile().handle()); + EXPECT_EQ(representationFile->handle(), ch2.representationFile().handle()); + EXPECT_EQ(2, representationFile->chillerElectricASHRAE205s().size()); + } +}