diff --git a/src/EnergyPlus/ChillerElectricEIR.cc b/src/EnergyPlus/ChillerElectricEIR.cc index 6f29289ef82..c6877552d98 100644 --- a/src/EnergyPlus/ChillerElectricEIR.cc +++ b/src/EnergyPlus/ChillerElectricEIR.cc @@ -1145,6 +1145,8 @@ void ElectricEIRChillerSpecs::oneTimeInit(EnergyPlusData &state) if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // reset flow priority DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + // this chiller sets its own evaporator flow rate to hold the leaving setpoint + DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).ModulatedFlow = true; // check if setpoint on outlet node if ((state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) && (state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPointHi == Node::SensedNodeFlagValue)) { diff --git a/src/EnergyPlus/ChillerIndirectAbsorption.cc b/src/EnergyPlus/ChillerIndirectAbsorption.cc index a8ad4a2af2e..cb382caef37 100644 --- a/src/EnergyPlus/ChillerIndirectAbsorption.cc +++ b/src/EnergyPlus/ChillerIndirectAbsorption.cc @@ -833,6 +833,8 @@ void IndirectAbsorberSpecs::oneTimeInit(EnergyPlusData &state) if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // reset flow priority this->CWPlantLoc.comp->FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + // this chiller sets its own evaporator flow rate to hold the leaving setpoint + this->CWPlantLoc.comp->ModulatedFlow = true; if ((state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) && (state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPointHi == Node::SensedNodeFlagValue)) { diff --git a/src/EnergyPlus/ChillerReformulatedEIR.cc b/src/EnergyPlus/ChillerReformulatedEIR.cc index 7fa87f39daa..ebb3ade802b 100644 --- a/src/EnergyPlus/ChillerReformulatedEIR.cc +++ b/src/EnergyPlus/ChillerReformulatedEIR.cc @@ -936,6 +936,8 @@ void ReformulatedEIRChillerSpecs::oneTimeInit(EnergyPlusData &state) if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // reset flow priority DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + // this chiller sets its own evaporator flow rate to hold the leaving setpoint + DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).ModulatedFlow = true; // check if setpoint on outlet node if ((state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) && (state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPointHi == Node::SensedNodeFlagValue)) { diff --git a/src/EnergyPlus/Plant/Component.hh b/src/EnergyPlus/Plant/Component.hh index fc1abec2f88..19b848ac2d1 100644 --- a/src/EnergyPlus/Plant/Component.hh +++ b/src/EnergyPlus/Plant/Component.hh @@ -309,6 +309,7 @@ namespace DataPlant { DataPlant::HowMet HowLoadServed; // nature of component in terms of how it can meet load Real64 MinOutletTemp; // Component exit lower limit temperature Real64 MaxOutletTemp; // Component exit upper limit temperature + bool ModulatedFlow; // true if this component modulates its own flow to hold a leaving setpoint bool FreeCoolCntrlShutDown; // true if component was shut down because of free cooling Real64 FreeCoolCntrlMinCntrlTemp; // current control temp value for free cooling controls DataPlant::FreeCoolControlMode FreeCoolCntrlMode; // type of sensor used for free cooling controls @@ -325,7 +326,7 @@ namespace DataPlant { FlowPriority(LoopFlowStatus::Invalid), ON(false), Available(false), NodeNumIn(0), NodeNumOut(0), MyLoad(0.0), MaxLoad(0.0), MinLoad(0.0), OptLoad(0.0), SizFac(0.0), CurOpSchemeType(DataPlant::OpScheme::Invalid), NumOpSchemes(0), CurCompLevelOpNum(0), EquipDemand(0.0), EMSLoadOverrideOn(false), EMSLoadOverrideValue(0.0), HowLoadServed(DataPlant::HowMet::Invalid), MinOutletTemp(0.0), - MaxOutletTemp(0.0), FreeCoolCntrlShutDown(false), FreeCoolCntrlMinCntrlTemp(0.0), + MaxOutletTemp(0.0), ModulatedFlow(false), FreeCoolCntrlShutDown(false), FreeCoolCntrlMinCntrlTemp(0.0), FreeCoolCntrlMode(DataPlant::FreeCoolControlMode::Invalid), FreeCoolCntrlNodeNum(0), IndexInLoopSidePumps(0), TempDesCondIn(0.0), TempDesEvapOut(0.0), compPtr(nullptr) { diff --git a/src/EnergyPlus/PlantChillers.cc b/src/EnergyPlus/PlantChillers.cc index e3238352f95..65817574ee5 100644 --- a/src/EnergyPlus/PlantChillers.cc +++ b/src/EnergyPlus/PlantChillers.cc @@ -2082,6 +2082,8 @@ namespace PlantChillers { if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // reset flow priority DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + // this chiller sets its own evaporator flow rate to hold the leaving setpoint + DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).ModulatedFlow = true; // check if setpoint on outlet node if ((state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) && @@ -4145,6 +4147,8 @@ namespace PlantChillers { if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // reset flow priority DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + // this chiller sets its own evaporator flow rate to hold the leaving setpoint + DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).ModulatedFlow = true; // check if setpoint on outlet node if ((state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) && (state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPointHi == Node::SensedNodeFlagValue)) { @@ -6106,6 +6110,8 @@ namespace PlantChillers { if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // reset flow priority DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + // this chiller sets its own evaporator flow rate to hold the leaving setpoint + DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).ModulatedFlow = true; // check if setpoint on outlet node if ((state.dataLoopNodes->Node(this->EvapOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) && diff --git a/src/EnergyPlus/PlantCondLoopOperation.cc b/src/EnergyPlus/PlantCondLoopOperation.cc index 7aa3d5134ca..0b6bb1d6996 100644 --- a/src/EnergyPlus/PlantCondLoopOperation.cc +++ b/src/EnergyPlus/PlantCondLoopOperation.cc @@ -3604,6 +3604,12 @@ void AdjustChangeInLoadByHowServed(EnergyPlusData &state, //- Retrieve data from the plant loop data structure CurMassFlowRate = state.dataLoopNodes->Node(this_component.NodeNumIn).MassFlowRate; + // A component in a modulated flow mode controls its own flow rate to hold the leaving setpoint, so it can raise + // its flow up to whatever the branch can deliver. Judging its capacity by the current flow, which may still be + // sitting at the branch pump minimum, understates it and spills load onto the next machine in the list. + if (this_component.ModulatedFlow) { + CurMassFlowRate = max(CurMassFlowRate, state.dataLoopNodes->Node(this_component.NodeNumIn).MassFlowRateMaxAvail); + } ToutLowLimit = this_component.MinOutletTemp; Tinlet = state.dataLoopNodes->Node(this_component.NodeNumIn).Temp; CurSpecHeat = plantLoc.loop->glycol->getSpecificHeat(state, Tinlet, RoutineName); @@ -3688,11 +3694,13 @@ void AdjustChangeInLoadByHowServed(EnergyPlusData &state, this_component.Available = true; this_component.FreeCoolCntrlShutDown = false; CurMassFlowRate = state.dataLoopNodes->Node(this_component.NodeNumIn).MassFlowRate; + if (this_component.ModulatedFlow) { + CurMassFlowRate = max(CurMassFlowRate, state.dataLoopNodes->Node(this_component.NodeNumIn).MassFlowRateMaxAvail); + } ToutLowLimit = this_component.MinOutletTemp; Tinlet = state.dataLoopNodes->Node(this_component.NodeNumIn).Temp; CurSpecHeat = plantLoc.loop->glycol->getSpecificHeat(state, Tinlet, RoutineName); QdotTmp = CurMassFlowRate * CurSpecHeat * (Tinlet - ToutLowLimit); - // !- Don't correct if Q is zero, as this could indicate a component which this hasn't been implemented or not yet turned // on if (CurMassFlowRate > 0.0) { diff --git a/tst/EnergyPlus/unit/PlantCondLoopOperation.unit.cc b/tst/EnergyPlus/unit/PlantCondLoopOperation.unit.cc index d8ad601c067..7e798d09782 100644 --- a/tst/EnergyPlus/unit/PlantCondLoopOperation.unit.cc +++ b/tst/EnergyPlus/unit/PlantCondLoopOperation.unit.cc @@ -55,6 +55,8 @@ #include #include #include +#include +#include #include #include #include @@ -1445,3 +1447,43 @@ TEST_F(EnergyPlusFixture, OperationSchemePriority) EXPECT_EQ(coolingTower(1).CurCompLevelOpNum, 1); EXPECT_EQ(coolingTower(1).CurOpSchemeType, DataPlant::OpScheme::Uncontrolled); } + +TEST_F(EnergyPlusFixture, AdjustChangeInLoadByHowServed_ModulatedFlowChiller) +{ + // A chiller in LeavingSetpointModulated mode sets its own flow rate, so its capacity must not be truncated using the current (branch pump + // minimum) flow rate. + + state->dataPlnt->PlantLoop.allocate(1); + auto &thisLoop = state->dataPlnt->PlantLoop(1); + thisLoop.glycol = Fluid::GetWater(*state); + thisLoop.LoopSide(DataPlant::LoopSideLocation::Supply).Branch.allocate(1); + thisLoop.LoopSide(DataPlant::LoopSideLocation::Supply).Branch(1).Comp.allocate(1); + + state->dataLoopNodes->Node.allocate(1); + state->dataLoopNodes->Node(1).Temp = 8.0; // chiller inlet, low load return temp + state->dataLoopNodes->Node(1).MassFlowRate = 1.0; // currently at the pump minimum + state->dataLoopNodes->Node(1).MassFlowRateMaxAvail = 10.0; // what the chiller could pull + + auto &thisComp = thisLoop.LoopSide(DataPlant::LoopSideLocation::Supply).Branch(1).Comp(1); + thisComp.HowLoadServed = DataPlant::HowMet::ByNominalCapLowOutLimit; + thisComp.NodeNumIn = 1; + thisComp.MinOutletTemp = 6.7; // same as the loop setpoint, as in the defect file + + PlantLocation plantLoc(1, DataPlant::LoopSideLocation::Supply, 1, 1); + PlantUtilities::SetPlantLocationLinks(*state, plantLoc); + + Real64 const Cp = thisLoop.glycol->getSpecificHeat(*state, 8.0, "AdjustChangeInLoadByHowServed_ModulatedFlowChiller"); + Real64 constexpr dispatchedLoad = 20000.0; + + // Constant flow chiller: capacity is limited by the flow it actually has + thisComp.ModulatedFlow = false; + Real64 changeInLoad = dispatchedLoad; + PlantCondLoopOperation::AdjustChangeInLoadByHowServed(*state, plantLoc, changeInLoad); + EXPECT_NEAR(changeInLoad, 1.0 * Cp * (8.0 - 6.7), 0.001); + + // Variable flow chiller: it can raise its own flow, so the full dispatched load stands + thisComp.ModulatedFlow = true; + changeInLoad = dispatchedLoad; + PlantCondLoopOperation::AdjustChangeInLoadByHowServed(*state, plantLoc, changeInLoad); + EXPECT_NEAR(changeInLoad, dispatchedLoad, 0.001); +}