From 9178b72a45cff7a9c924508d4b7124277fa64024 Mon Sep 17 00:00:00 2001 From: kevin-moos Date: Tue, 16 Jun 2026 14:01:20 -0500 Subject: [PATCH 1/3] #11568 - Find the best available ventilation-only setting instead of defaulting to standby --- src/EnergyPlus/HybridEvapCoolingModel.cc | 79 ++++++++++++------------ 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/src/EnergyPlus/HybridEvapCoolingModel.cc b/src/EnergyPlus/HybridEvapCoolingModel.cc index cb3c8f72ca3..ef47cf36772 100644 --- a/src/EnergyPlus/HybridEvapCoolingModel.cc +++ b/src/EnergyPlus/HybridEvapCoolingModel.cc @@ -1078,22 +1078,21 @@ namespace HybridEvapCoolingModel { Real64 PLHumidRatio, PLDehumidRatio, PLVentRatio, PLSensibleCoolingRatio, PLSensibleHeatingRatio, PartRuntimeFraction; PLHumidRatio = PLDehumidRatio = PLVentRatio = PLSensibleCoolingRatio = PLSensibleHeatingRatio = 0; - if (Mvent > 0) { + if (VentilationRequested && Mvent > 0) { PLVentRatio = MinOA_Msa / Mvent; } PartRuntimeFraction = PLVentRatio; - if (SensibleRoomORZone > 0) { + if (CoolingRequested && SensibleRoomORZone > 0) { PLSensibleCoolingRatio = std::abs(RequestedCoolingLoad) / std::abs(SensibleRoomORZone); } if (PLSensibleCoolingRatio > PartRuntimeFraction) { PartRuntimeFraction = PLSensibleCoolingRatio; } - if (SensibleRoomORZone < 0) { + if (HeatingRequested && SensibleRoomORZone < 0) { PLSensibleHeatingRatio = std::abs(RequestedHeatingLoad) / std::abs(SensibleRoomORZone); } - if (PLSensibleHeatingRatio > PartRuntimeFraction) { PartRuntimeFraction = PLSensibleHeatingRatio; } @@ -1101,7 +1100,6 @@ namespace HybridEvapCoolingModel { if (RequestedDehumidificationLoad > 0) { PLDehumidRatio = std::abs(RequestedDehumidificationLoad) / std::abs(LatentRoomORZone); } - if (PLDehumidRatio > PartRuntimeFraction) { PartRuntimeFraction = PLDehumidRatio; } @@ -1193,13 +1191,15 @@ namespace HybridEvapCoolingModel { bool DidWeMeetLoad = false; bool DidWeMeetHumidification = false; bool DidWePartlyMeetLoad = false; + bool DidWeMeetVentilation = false; Real64 OptimalSetting_RunFractionTotalFuel = IMPLAUSIBLE_POWER; Real64 Tma; Real64 Wma; Real64 Hsa; Real64 Hma; - Real64 PreviousMaxiumConditioningOutput = 0; - Real64 PreviousMaxiumHumidOrDehumidOutput = 0; + Real64 PreviousMaximumConditioningOutput = 0; + Real64 PreviousMaximumHumidOrDehumidOutput = 0; + Real64 PreviousMaximumSupplyFanElectricPower = IMPLAUSIBLE_POWER; std::string ObjectID = Name.c_str(); if (StepIns.RHosa > 1) { ShowSevereError( @@ -1328,15 +1328,6 @@ namespace HybridEvapCoolingModel { CandidateSetting.Unscaled_Supply_Air_Mass_Flow_Rate = UnscaledMsa; CandidateSetting.ScaledSupply_Air_Mass_Flow_Rate = ScaledMsa; - // If no load is requested but ventilation is required, set the supply air mass flow rate to the minimum of the - // required ventilation flow rate and the maximum supply air flow rate - if (!CoolingRequested && !HeatingRequested && !DehumidificationRequested && !HumidificationRequested) { - CandidateSetting.ScaledSupply_Air_Mass_Flow_Rate = - min(MinOA_Msa, CandidateSetting.ScaledSupply_Air_Mass_Flow_Rate); - // add fan heat if not included in lookup tables for supply air stream - Tsa = StepIns.Tosa + FanHeatTemp; - } - CandidateSetting.ScaledSupply_Air_Ventilation_Volume = CandidateSetting.ScaledSupply_Air_Mass_Flow_Rate / state.dataEnvrn->StdRhoAir; CandidateSetting.oMode = Mode; @@ -1453,33 +1444,32 @@ namespace HybridEvapCoolingModel { thisSetting.oMode.CalculateCurveVal(state, StepIns.Tosa, Wosa, StepIns.Tra, Wra, UnscaledMsa, OSAF, WATER_USE); // Calculate partload fraction required to meet all requirements - // Fraction can be above 1 meaning its not able to do it completely in a time step - Real64 PartRuntimeFraction = CalculatePartRuntimeFraction(MinOA_Msa, - thisSetting.Supply_Air_Ventilation_Volume * state.dataEnvrn->StdRhoAir, - StepIns.RequestedCoolingLoad, - StepIns.RequestedHeatingLoad, - SensibleRoomORZone, - StepIns.ZoneDehumidificationLoad, - StepIns.ZoneMoistureLoad, - latentRoomORZone); + thisSetting.Runtime_Fraction = CalculatePartRuntimeFraction(MinOA_Msa, + thisSetting.Supply_Air_Ventilation_Volume * state.dataEnvrn->StdRhoAir, + StepIns.RequestedCoolingLoad, + StepIns.RequestedHeatingLoad, + SensibleRoomORZone, + StepIns.ZoneDehumidificationLoad, + StepIns.ZoneMoistureLoad, + latentRoomORZone); Real64 RunFractionTotalFuel; switch (ObjectiveFunction) { default: case ObjectiveFunctionType::ElectricityUse: - RunFractionTotalFuel = thisSetting.ElectricalPower * PartRuntimeFraction; + RunFractionTotalFuel = (thisSetting.ElectricalPower * thisSetting.Runtime_Fraction) + + (thisSetting.SupplyFanElectricPower * thisSetting.Runtime_Fraction); break; case ObjectiveFunctionType::SecondFuelUse: - RunFractionTotalFuel = thisSetting.SecondaryFuelConsumptionRate * PartRuntimeFraction; + RunFractionTotalFuel = thisSetting.SecondaryFuelConsumptionRate * thisSetting.Runtime_Fraction; break; case ObjectiveFunctionType::ThirdFuelUse: - RunFractionTotalFuel = thisSetting.ThirdFuelConsumptionRate * PartRuntimeFraction; + RunFractionTotalFuel = thisSetting.ThirdFuelConsumptionRate * thisSetting.Runtime_Fraction; break; case ObjectiveFunctionType::WaterUse: - RunFractionTotalFuel = thisSetting.WaterConsumptionRate * PartRuntimeFraction; + RunFractionTotalFuel = thisSetting.WaterConsumptionRate * thisSetting.Runtime_Fraction; break; } - thisSetting.Runtime_Fraction = PartRuntimeFraction; // if (Conditioning_load_met && Humidification_load_met) { // store best performing mode @@ -1495,24 +1485,33 @@ namespace HybridEvapCoolingModel { if (Conditioning_load_met) { DidWeMeetLoad = true; - if (HumidificationRequested && (latentRoomORZone < PreviousMaxiumHumidOrDehumidOutput)) { + if (HumidificationRequested && (latentRoomORZone < PreviousMaximumHumidOrDehumidOutput)) { store_best_attempt = true; } - if (DehumidificationRequested && (latentRoomORZone > PreviousMaxiumHumidOrDehumidOutput)) { + if (DehumidificationRequested && (latentRoomORZone > PreviousMaximumHumidOrDehumidOutput)) { store_best_attempt = true; } if (store_best_attempt) { - PreviousMaxiumHumidOrDehumidOutput = latentRoomORZone; + PreviousMaximumHumidOrDehumidOutput = latentRoomORZone; } } else { - if (CoolingRequested && (SensibleRoomORZone > PreviousMaxiumConditioningOutput)) { + if (CoolingRequested && (SensibleRoomORZone > PreviousMaximumConditioningOutput)) { store_best_attempt = true; } - if (HeatingRequested && (SensibleRoomORZone < PreviousMaxiumConditioningOutput)) { + if (HeatingRequested && (SensibleRoomORZone < PreviousMaximumConditioningOutput)) { store_best_attempt = true; } if (store_best_attempt) { - PreviousMaxiumConditioningOutput = SensibleRoomORZone; + PreviousMaximumConditioningOutput = SensibleRoomORZone; + } else if (VentilationRequested && !DidWePartlyMeetLoad) { + // If no load is requested but ventilation is required, find the setting that uses the least supply fan power. + // Note that settings that meet partial load still take priority, so use logic separate from store_best_attempt. + Real64 RunFractionSupplyFanElectricPower = thisSetting.SupplyFanElectricPower * thisSetting.Runtime_Fraction; + if (RunFractionSupplyFanElectricPower < PreviousMaximumSupplyFanElectricPower) { + PreviousMaximumSupplyFanElectricPower = RunFractionSupplyFanElectricPower; + OptimalSetting = thisSetting; + DidWeMeetVentilation = true; + } } } if (store_best_attempt) { @@ -1542,6 +1541,7 @@ namespace HybridEvapCoolingModel { ErrorCode = 0; // save the optimal setting in the CurrentOperatingSettings[0] = OptimalSetting; + PrimaryMode = OptimalSetting.Mode; PrimaryModeRuntimeFraction = OptimalSetting.Runtime_Fraction; oStandBy.Runtime_Fraction = (1 - PrimaryModeRuntimeFraction); if (oStandBy.Runtime_Fraction < 0) { @@ -1549,18 +1549,17 @@ namespace HybridEvapCoolingModel { } CurrentOperatingSettings[1] = oStandBy; } else { - // if we partly met the load then do the best we can and run full out in that optimal setting. - if (DidWePartlyMeetLoad) { + // if we partly met the load or met the ventilation then do the best we can + if (DidWePartlyMeetLoad || DidWeMeetVentilation) { ErrorCode = 0; count_DidWeNotMeetLoad++; if (OptimalSetting.ElectricalPower == IMPLAUSIBLE_POWER) { ShowWarningError(state, "Model was not able to provide cooling for a time step, called in HybridEvapCooling:dostep"); OptimalSetting.ElectricalPower = 0; } - OptimalSetting.Runtime_Fraction = 1; CurrentOperatingSettings[0] = OptimalSetting; PrimaryMode = OptimalSetting.Mode; - PrimaryModeRuntimeFraction = 1; + PrimaryModeRuntimeFraction = OptimalSetting.Runtime_Fraction; } // if we didn't even partially meet the load make sure the operational settings are just the standby mode. else { From a6eaf1bc0af57539b3ac0a1cf9b9065371d42b74 Mon Sep 17 00:00:00 2001 From: kevin-moos Date: Wed, 17 Jun 2026 08:48:30 -0500 Subject: [PATCH 2/3] Make adjustments to unit tests --- src/EnergyPlus/HybridEvapCoolingModel.cc | 6 ++++++ .../unit/Resources/UnitaryHybridUnitTest_DOSA.idf | 2 +- tst/EnergyPlus/unit/UnitaryHybridAirConditioner.unit.cc | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/EnergyPlus/HybridEvapCoolingModel.cc b/src/EnergyPlus/HybridEvapCoolingModel.cc index ef47cf36772..3063f6e1b3e 100644 --- a/src/EnergyPlus/HybridEvapCoolingModel.cc +++ b/src/EnergyPlus/HybridEvapCoolingModel.cc @@ -1328,6 +1328,12 @@ namespace HybridEvapCoolingModel { CandidateSetting.Unscaled_Supply_Air_Mass_Flow_Rate = UnscaledMsa; CandidateSetting.ScaledSupply_Air_Mass_Flow_Rate = ScaledMsa; + if (!CoolingRequested && !HeatingRequested && !DehumidificationRequested && !HumidificationRequested) { + // If no load is requested but ventilation is required, set the supply air temp to the outside air temp + // add fan heat if not included in lookup tables for supply air stream + Tsa = StepIns.Tosa + FanHeatTemp; + } + CandidateSetting.ScaledSupply_Air_Ventilation_Volume = CandidateSetting.ScaledSupply_Air_Mass_Flow_Rate / state.dataEnvrn->StdRhoAir; CandidateSetting.oMode = Mode; diff --git a/tst/EnergyPlus/unit/Resources/UnitaryHybridUnitTest_DOSA.idf b/tst/EnergyPlus/unit/Resources/UnitaryHybridUnitTest_DOSA.idf index 59a1345d52a..c365af9a7bd 100644 --- a/tst/EnergyPlus/unit/Resources/UnitaryHybridUnitTest_DOSA.idf +++ b/tst/EnergyPlus/unit/Resources/UnitaryHybridUnitTest_DOSA.idf @@ -748,7 +748,7 @@ 1, !- Mode 4 Minimum Outdoor Air Fraction 1, !- Mode 4 Maximum Outdoor Air Fraction 0.7039, !- Mode 4 Minimum Supply Air Mass Flow Rate Ratio - 0.957, !- Mode 4 Maximum Supply Air Mass Flow Rate Ratio + 1, !- Mode 4 Maximum Supply Air Mass Flow Rate Ratio Mode5_Heat, !- Mode 5 Name Mode5_TSA_lookup, !- Mode 5 Supply Air Temperature Lookup Table Name Mode5_wSA_lookup, !- Mode 5 Supply Air Humidity Ratio Lookup Table Name diff --git a/tst/EnergyPlus/unit/UnitaryHybridAirConditioner.unit.cc b/tst/EnergyPlus/unit/UnitaryHybridAirConditioner.unit.cc index 0fe226fd821..9e865d9d92f 100644 --- a/tst/EnergyPlus/unit/UnitaryHybridAirConditioner.unit.cc +++ b/tst/EnergyPlus/unit/UnitaryHybridAirConditioner.unit.cc @@ -313,8 +313,8 @@ TEST_F(EnergyPlusFixture, Test_UnitaryHybridAirConditioner_Unittest) EXPECT_EQ(modenumber, 4); // Ventilation Mode EXPECT_NEAR(Tsa, Tosa, 1.0); EXPECT_NEAR(Msa, DesignMinVR, 0.001); - EXPECT_GT(Electricpower, 4000 / NormalizationDivisor * MinFlowFraction); - EXPECT_LT(Electricpower, 5000 / NormalizationDivisor); + EXPECT_GT(Electricpower, 3000 / NormalizationDivisor * MinFlowFraction); + EXPECT_LT(Electricpower, 4000 / NormalizationDivisor); // check fan heat calculation in supply air stream if not included in lookup tables thisUnitary.FanHeatGain = true; From 4aa67c0c4a1a1cfd4e4c1627d1e78f8c56270f2d Mon Sep 17 00:00:00 2001 From: kevin-moos Date: Wed, 17 Jun 2026 14:53:18 -0500 Subject: [PATCH 3/3] Add standby for partial setting --- src/EnergyPlus/HybridEvapCoolingModel.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/EnergyPlus/HybridEvapCoolingModel.cc b/src/EnergyPlus/HybridEvapCoolingModel.cc index 3063f6e1b3e..dd9d5610bc3 100644 --- a/src/EnergyPlus/HybridEvapCoolingModel.cc +++ b/src/EnergyPlus/HybridEvapCoolingModel.cc @@ -1566,6 +1566,11 @@ namespace HybridEvapCoolingModel { CurrentOperatingSettings[0] = OptimalSetting; PrimaryMode = OptimalSetting.Mode; PrimaryModeRuntimeFraction = OptimalSetting.Runtime_Fraction; + oStandBy.Runtime_Fraction = (1 - PrimaryModeRuntimeFraction); + if (oStandBy.Runtime_Fraction < 0) { + oStandBy.Runtime_Fraction = 0; + } + CurrentOperatingSettings[1] = oStandBy; } // if we didn't even partially meet the load make sure the operational settings are just the standby mode. else {