diff --git a/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp b/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp index 256a26b663..ec0667931b 100644 --- a/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp +++ b/src/energyplus/ForwardTranslator/ForwardTranslatePlantEquipmentOperationSchemes.cpp @@ -174,13 +174,17 @@ namespace energyplus { boost::optional inletNode(const PlantLoop& plantLoop, const HVACComponent& component) { auto comps = plantLoop.supplyComponents(plantLoop.supplyInletNode(), component); - OS_ASSERT(comps.size() >= 2u); + if (comps.size() < 2u) { + return boost::none; + } return (comps.end() - 2u)->optionalCast(); } boost::optional outletNode(const PlantLoop& plantLoop, const HVACComponent& component) { auto comps = plantLoop.supplyComponents(component, plantLoop.supplyOutletNode()); - OS_ASSERT(comps.size() >= 2u); + if (comps.size() < 2u) { + return boost::none; + } return comps[1].optionalCast(); } @@ -437,7 +441,9 @@ namespace energyplus { for (const auto& comp : subsetCastVector(plantLoop.supplyComponents())) { if (!comp.optionalCast()) { if (_isSetpointComponent(plantLoop, comp)) { - result.push_back(operationSchemeComponent(comp)); + // Keep comp as-is (not operationSchemeComponent(comp)): inletNode/outletNode/flowrate + // below need the component that's actually on the supply branch. + result.push_back(comp); } } } @@ -543,16 +549,21 @@ namespace energyplus { } for (auto& setpointComponent : t_setpointComponents) { + // translationComponent may be a containing component (eg: HeatPumpAirToWater), used only + // for translation/naming below; setpointComponent stays the one on the supply branch for + // the inletNode/outletNode/flowrate/componentType lookups. + auto translationComponent = operationSchemeComponent(setpointComponent); + // TODO: @kbenne, Find the right way to deal with this // For now, "dirty" (?) fix for Generator:MicroTurbine boost::optional _idfObject; - if (boost::optional mchpHR = setpointComponent.optionalCast()) { + if (boost::optional mchpHR = translationComponent.optionalCast()) { GeneratorMicroTurbine mchp = mchpHR->generatorMicroTurbine(); _idfObject = translateAndMapModelObject(mchp); } else { - _idfObject = translateAndMapModelObject(setpointComponent); + _idfObject = translateAndMapModelObject(translationComponent); } OS_ASSERT(_idfObject); diff --git a/src/energyplus/Test/PlantEquipmentOperationSchemes_GTest.cpp b/src/energyplus/Test/PlantEquipmentOperationSchemes_GTest.cpp index 98e47fee5e..564a03f468 100644 --- a/src/energyplus/Test/PlantEquipmentOperationSchemes_GTest.cpp +++ b/src/energyplus/Test/PlantEquipmentOperationSchemes_GTest.cpp @@ -40,6 +40,8 @@ #include "../../model/GroundHeatExchangerHorizontalTrench.hpp" #include "../../model/GroundHeatExchangerVertical.hpp" #include "../../model/HeatExchangerFluidToFluid.hpp" +#include "../../model/HeatPumpAirToWater.hpp" +#include "../../model/HeatPumpAirToWaterHeating.hpp" #include "../../model/HeatPumpAirToWaterFuelFiredHeating.hpp" #include "../../model/HeatPumpAirToWaterFuelFiredCooling.hpp" #include "../../model/HeatPumpWaterToWaterEquationFitCooling.hpp" @@ -522,6 +524,53 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_PlantEquipmentOperationSchemes_Both_ EXPECT_EQ("Dual", w_eg.getString(PlantEquipmentOperation_ComponentSetpointExtensibleFields::OperationType).get()); } +TEST_F(EnergyPlusFixture, ForwardTranslator_PlantEquipmentOperationSchemes_HeatPumpAirToWaterHeating_ComponentSetpoint) { + // Test for #5665 - segfault when a SetpointManager is on the outlet node of a + // HeatPumpAirToWaterHeating, since it isn't itself on the plant loop (its containing + // HeatPumpAirToWater is translated/named instead, but node lookups need the actual component) + + Model m; + + HeatPumpAirToWater awhp(m); + HeatPumpAirToWaterHeating awhp_hc(m); + ASSERT_TRUE(awhp.setHeatingOperationMode(awhp_hc)); + + PlantLoop hwLoop(m); + ASSERT_TRUE(hwLoop.addSupplyBranchForComponent(awhp_hc)); + + ScheduleConstant sch(m); + SetpointManagerScheduled spm(m, sch); + ASSERT_TRUE(awhp_hc.outletModelObject()); + Node outletNode = awhp_hc.outletModelObject()->cast(); + ASSERT_TRUE(spm.addToNode(outletNode)); + + ForwardTranslator ft; + Workspace w = ft.translateModel(m); + + boost::optional _wo = w.getObjectByTypeAndName(IddObjectType::PlantLoop, hwLoop.name().get()); + ASSERT_TRUE(_wo.is_initialized()); + WorkspaceObject idf_loop = _wo.get(); + WorkspaceObject idf_plant_op = idf_loop.getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get(); + + // HeatPumpAirToWaterHeating is Heating type, so it also lands on the default HeatingLoad scheme + // in addition to the ComponentSetpoint one created because of the SetpointManager + ASSERT_EQ(2u, idf_plant_op.extensibleGroups().size()); + auto w_eg = idf_plant_op.extensibleGroups()[1].cast(); + ASSERT_EQ("PlantEquipmentOperation:ComponentSetpoint", + w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get()); + + _wo = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName); + ASSERT_TRUE(_wo.is_initialized()); + WorkspaceObject idf_op_scheme = _wo.get(); + + ASSERT_EQ(1u, idf_op_scheme.extensibleGroups().size()); + w_eg = idf_op_scheme.extensibleGroups()[0].cast(); + EXPECT_EQ("HeatPump:AirToWater", w_eg.getString(PlantEquipmentOperation_ComponentSetpointExtensibleFields::EquipmentObjectType).get()); + EXPECT_EQ(awhp.nameString(), w_eg.getString(PlantEquipmentOperation_ComponentSetpointExtensibleFields::EquipmentName).get()); + EXPECT_TRUE(w_eg.getString(PlantEquipmentOperation_ComponentSetpointExtensibleFields::DemandCalculationNodeName)); + EXPECT_TRUE(w_eg.getString(PlantEquipmentOperation_ComponentSetpointExtensibleFields::SetpointNodeName)); +} + TEST_F(EnergyPlusFixture, ForwardTranslator_PlantEquipmentOperationSchemes_HeatPumpPlantLoopEIR) { Model m;