Skip to content

Commit

Permalink
Merge pull request #10230 from NREL/9878_ChillerHeaterAbsorptionDoubl…
Browse files Browse the repository at this point in the history
…eEffect

Fix #9878 - ChillerHeater:Absorption:DoubleEffect should calculate Specific heat of Water and Air at the right temperatures
  • Loading branch information
Myoldmopar authored Sep 19, 2023
2 parents 25fac22 + 6ee584b commit 944be33
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 83 deletions.
112 changes: 45 additions & 67 deletions src/EnergyPlus/ChillerExhaustAbsorption.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1874,86 +1874,64 @@ void ExhaustAbsorberSpecs::calcHeater(EnergyPlusData &state, Real64 &MyLoad, boo
// all variables that are local copies of data structure
// variables are prefaced with an "l" for local.
// Local copies of ExhaustAbsorberReportVars Type
Real64 lHotWaterReturnTemp(0.0);
Real64 lHeatingLoad(0.0); // heating load on the chiller
Real64 lHeatThermalEnergyUseRate(0.0); // instantaneous use of thermal energy for period for heating
Real64 lHeatElectricPower(0.0); // parasitic electric power used for heating
Real64 lHotWaterSupplyTemp(0.0); // reporting: hot water supply (outlet) temperature
Real64 lHotWaterMassFlowRate(0.0); // reporting: hot water mass flow rate
Real64 lHeatPartLoadRatio(0.0); // operating part load ratio (load/capacity for heating)
Real64 lAvailableHeatingCapacity(0.0); // current heating capacity
Real64 lFractionOfPeriodRunning(0.0);
Real64 lExhaustInTemp(0.0); // Exhaust inlet temperature
Real64 lExhaustInFlow(0.0); // Exhaust inlet flow rate
Real64 lExhHeatRecPotentialHeat(0.0); // Exhaust heat recovery potential
Real64 lExhaustAirHumRat(0.0);
// other local variables
Real64 HeatSupplySetPointTemp(0.0);

// set node values to data structure values for nodes

int lHeatReturnNodeNum = this->HeatReturnNodeNum;
int lHeatSupplyNodeNum = this->HeatSupplyNodeNum;
int lExhaustAirInletNodeNum = this->ExhaustAirInletNodeNum;

// set local copies of data from rest of input structure

Real64 lNomCoolingCap = this->NomCoolingCap; // W - design nominal capacity of Absorber
Real64 lNomHeatCoolRatio = this->NomHeatCoolRatio; // ratio of heating to cooling capacity
Real64 lThermalEnergyHeatRatio = this->ThermalEnergyHeatRatio; // ratio of ThermalEnergy input to heating output
Real64 lElecHeatRatio = this->ElecHeatRatio; // ratio of electricity input to heating output
Real64 lMinPartLoadRat = this->MinPartLoadRat; // min allowed operating frac full load
Real64 lMaxPartLoadRat = this->MaxPartLoadRat; // max allowed operating frac full load
int lHeatCapFCoolCurve = this->HeatCapFCoolCurve; // Heating Capacity Function of Cooling Capacity Curve
int lThermalEnergyHeatFHPLRCurve = this->ThermalEnergyHeatFHPLRCurve; // ThermalEnergy Input to heat output ratio during heating only function
int LoopNum = this->HWPlantLoc.loopNum;
DataPlant::LoopSideLocation LoopSideNum = this->HWPlantLoc.loopSideNum;

Real64 Cp_HW = FluidProperties::GetSpecificHeatGlycol(
state, state.dataPlnt->PlantLoop(LoopNum).FluidName, lHotWaterReturnTemp, state.dataPlnt->PlantLoop(LoopNum).FluidIndex, RoutineName);

Real64 lCoolElectricPower = this->CoolElectricPower; // parasitic electric power used for cooling
Real64 lCoolThermalEnergyUseRate = this->CoolThermalEnergyUseRate; // instantaneous use of thermal energy for period for cooling
Real64 lCoolPartLoadRatio = this->CoolPartLoadRatio;

// initialize entering conditions
lHotWaterReturnTemp = state.dataLoopNodes->Node(lHeatReturnNodeNum).Temp;
lHotWaterMassFlowRate = state.dataLoopNodes->Node(lHeatReturnNodeNum).MassFlowRate;
switch (state.dataPlnt->PlantLoop(LoopNum).LoopDemandCalcScheme) {
case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
HeatSupplySetPointTemp = state.dataLoopNodes->Node(lHeatSupplyNodeNum).TempSetPoint;
} break;
case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
HeatSupplySetPointTemp = state.dataLoopNodes->Node(lHeatSupplyNodeNum).TempSetPointLo;
} break;
default: {
assert(false);
} break;
}
Real64 HeatDeltaTemp = std::abs(lHotWaterReturnTemp - HeatSupplySetPointTemp);
auto &hwPlantLoop = state.dataPlnt->PlantLoop(this->HWPlantLoc.loopNum);
const Real64 HeatSupplySetPointTemp = [this, &hwPlantLoop, &state]() {
switch (hwPlantLoop.LoopDemandCalcScheme) {
case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
return state.dataLoopNodes->Node(this->HeatSupplyNodeNum).TempSetPoint;
}
case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
return state.dataLoopNodes->Node(this->HeatSupplyNodeNum).TempSetPointLo;
}
default: {
assert(false);
return 0.0;
}
}
}();

auto const &heatReturnNode = state.dataLoopNodes->Node(this->HeatReturnNodeNum);
Real64 const HeatDeltaTemp = std::abs(heatReturnNode.Temp - HeatSupplySetPointTemp);
// reporting: hot water mass flow rate
Real64 lHotWaterMassFlowRate = heatReturnNode.MassFlowRate;

// If no loop demand or Absorber OFF, return
// will need to modify when absorber can act as a boiler
if (MyLoad <= 0 || !RunFlag) {
// set node temperatures
lHotWaterSupplyTemp = lHotWaterReturnTemp;
lFractionOfPeriodRunning = min(1.0, max(lHeatPartLoadRatio, lCoolPartLoadRatio) / lMinPartLoadRat);
lHotWaterSupplyTemp = heatReturnNode.Temp;
lFractionOfPeriodRunning = min(1.0, max(lHeatPartLoadRatio, this->CoolPartLoadRatio) / this->MinPartLoadRat);
} else {

Real64 const Cp_HW =
FluidProperties::GetSpecificHeatGlycol(state, hwPlantLoop.FluidName, heatReturnNode.Temp, hwPlantLoop.FluidIndex, RoutineName);

// Determine available heating capacity using the current cooling load
lAvailableHeatingCapacity =
this->NomHeatCoolRatio * this->NomCoolingCap * Curve::CurveValue(state, lHeatCapFCoolCurve, (this->CoolingLoad / this->NomCoolingCap));
lAvailableHeatingCapacity = this->NomHeatCoolRatio * this->NomCoolingCap *
Curve::CurveValue(state, this->HeatCapFCoolCurve, (this->CoolingLoad / this->NomCoolingCap));

// Calculate current load for heating
MyLoad = sign(max(std::abs(MyLoad), this->HeatingCapacity * lMinPartLoadRat), MyLoad);
MyLoad = sign(min(std::abs(MyLoad), this->HeatingCapacity * lMaxPartLoadRat), MyLoad);
MyLoad = sign(max(std::abs(MyLoad), this->HeatingCapacity * this->MinPartLoadRat), MyLoad);
MyLoad = sign(min(std::abs(MyLoad), this->HeatingCapacity * this->MaxPartLoadRat), MyLoad);

// Determine the following variables depending on if the flow has been set in
// the nodes (flowlock=1 to 2) or if the amount of load is still be determined (flowlock=0)
// chilled water flow,
// cooling load taken by the chiller, and
// supply temperature
switch (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).FlowLock) {
switch (hwPlantLoop.LoopSide(this->HWPlantLoc.loopSideNum).FlowLock) {
case DataPlant::FlowLock::Unlocked: { // mass flow rates may be changed by loop components
lHeatingLoad = std::abs(MyLoad);
if (HeatDeltaTemp != 0) {
Expand Down Expand Up @@ -1989,25 +1967,25 @@ void ExhaustAbsorberSpecs::calcHeater(EnergyPlusData &state, Real64 &MyLoad, boo

// Calculate ThermalEnergy consumption for heating
// ThermalEnergy used for heating availCap * HIR * HIR-FT * HIR-FPLR

lHeatThermalEnergyUseRate =
lAvailableHeatingCapacity * lThermalEnergyHeatRatio * Curve::CurveValue(state, lThermalEnergyHeatFHPLRCurve, lHeatPartLoadRatio);
lHeatThermalEnergyUseRate = lAvailableHeatingCapacity * this->ThermalEnergyHeatRatio *
Curve::CurveValue(state, this->ThermalEnergyHeatFHPLRCurve, lHeatPartLoadRatio);

// calculate the fraction of the time period that the chiller would be running
// use maximum from heating and cooling sides
lFractionOfPeriodRunning = min(1.0, max(lHeatPartLoadRatio, lCoolPartLoadRatio) / lMinPartLoadRat);
lFractionOfPeriodRunning = min(1.0, max(lHeatPartLoadRatio, this->CoolPartLoadRatio) / this->MinPartLoadRat);

// Calculate electric parasitics used
// for heating based on nominal capacity not available capacity
lHeatElectricPower = lNomCoolingCap * lNomHeatCoolRatio * lElecHeatRatio * lFractionOfPeriodRunning;
lHeatElectricPower = this->NomCoolingCap * this->NomHeatCoolRatio * this->ElecHeatRatio * lFractionOfPeriodRunning;
// Coodinate electric parasitics for heating and cooling to avoid double counting
// Total electric is the max of heating electric or cooling electric
// If heating electric is greater, leave cooling electric and subtract if off of heating elec
// If cooling electric is greater, set heating electric to zero

lExhaustInTemp = state.dataLoopNodes->Node(lExhaustAirInletNodeNum).Temp;
lExhaustInFlow = state.dataLoopNodes->Node(lExhaustAirInletNodeNum).MassFlowRate;
Real64 CpAir = Psychrometrics::PsyCpAirFnW(lExhaustAirHumRat);
lExhaustInTemp = state.dataLoopNodes->Node(this->ExhaustAirInletNodeNum).Temp;
lExhaustInFlow = state.dataLoopNodes->Node(this->ExhaustAirInletNodeNum).MassFlowRate;
Real64 const lExhaustAirHumRat = state.dataLoopNodes->Node(this->ExhaustAirInletNodeNum).HumRat;
Real64 const CpAir = Psychrometrics::PsyCpAirFnW(lExhaustAirHumRat);
lExhHeatRecPotentialHeat = lExhaustInFlow * CpAir * (lExhaustInTemp - AbsLeavingTemp);
if (lExhHeatRecPotentialHeat < lHeatThermalEnergyUseRate) {
if (this->ExhTempLTAbsLeavingHeatingTempIndex == 0) {
Expand All @@ -2032,31 +2010,31 @@ void ExhaustAbsorberSpecs::calcHeater(EnergyPlusData &state, Real64 &MyLoad, boo
// If exhaust is not available, it means the avilable thermal energy is 0.0 and Chiller is not available
lHeatThermalEnergyUseRate = 0.0;
lHeatElectricPower = 0.0;
lHotWaterSupplyTemp = lHotWaterReturnTemp;
lFractionOfPeriodRunning = min(1.0, max(lHeatPartLoadRatio, lCoolPartLoadRatio) / lMinPartLoadRat);
lHotWaterSupplyTemp = heatReturnNode.Temp;
lFractionOfPeriodRunning = min(1.0, max(lHeatPartLoadRatio, this->CoolPartLoadRatio) / this->MinPartLoadRat);
}

if (lHeatElectricPower <= lCoolElectricPower) {
if (lHeatElectricPower <= this->CoolElectricPower) {
lHeatElectricPower = 0.0;
} else {
lHeatElectricPower -= lCoolElectricPower;
lHeatElectricPower -= this->CoolElectricPower;
}

} // IF(MyLoad==0 .OR. .NOT. RunFlag)
// Write into the Report Variables except for nodes
this->HeatingLoad = lHeatingLoad;
this->HeatThermalEnergyUseRate = lHeatThermalEnergyUseRate;
this->HeatElectricPower = lHeatElectricPower;
this->HotWaterReturnTemp = lHotWaterReturnTemp;
this->HotWaterReturnTemp = heatReturnNode.Temp;
this->HotWaterSupplyTemp = lHotWaterSupplyTemp;
this->HotWaterFlowRate = lHotWaterMassFlowRate;
this->HeatPartLoadRatio = lHeatPartLoadRatio;
this->HeatingCapacity = lAvailableHeatingCapacity;
this->FractionOfPeriodRunning = lFractionOfPeriodRunning;

// write the combined heating and cooling ThermalEnergy used and electric used
this->ThermalEnergyUseRate = lCoolThermalEnergyUseRate + lHeatThermalEnergyUseRate;
this->ElectricPower = lCoolElectricPower + lHeatElectricPower;
this->ThermalEnergyUseRate = this->CoolThermalEnergyUseRate + lHeatThermalEnergyUseRate;
this->ElectricPower = this->CoolElectricPower + lHeatElectricPower;
this->ExhaustInTemp = lExhaustInTemp;
this->ExhaustInFlow = lExhaustInFlow;
this->ExhHeatRecPotentialHeat = lExhHeatRecPotentialHeat;
Expand Down
68 changes: 52 additions & 16 deletions tst/EnergyPlus/unit/ChillerExhaustAbsorption.unit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
#include <EnergyPlus/ChillerExhaustAbsorption.hh>
#include <EnergyPlus/Data/EnergyPlusData.hh>
#include <EnergyPlus/DataErrorTracking.hh>
// #include <EnergyPlus/DataLoopNode.hh>
#include <EnergyPlus/DataLoopNode.hh>
#include <EnergyPlus/FluidProperties.hh>
#include <EnergyPlus/Plant/DataPlant.hh>

#include "Fixtures/EnergyPlusFixture.hh"
Expand Down Expand Up @@ -637,29 +638,60 @@ TEST_F(EnergyPlusFixture, ExhAbsorption_calcHeater_Fix_Test)

auto &thisChillerHeater = state->dataChillerExhaustAbsorption->ExhaustAbsorber(1);

Real64 loadinput = 5000.0;
bool runflaginput = true;

thisChillerHeater.CoolingLoad = 100000.0;
thisChillerHeater.CoolingLoad = 100'000.0;
thisChillerHeater.CoolPartLoadRatio = 1.0;
state->dataPlnt->TotNumLoops = 1;
state->dataPlnt->PlantLoop.allocate(state->dataPlnt->TotNumLoops);

thisChillerHeater.HWPlantLoc.loopNum = 1;
thisChillerHeater.HWPlantLoc.loopSideNum = DataPlant::LoopSideLocation::Demand;
state->dataPlnt->PlantLoop(1).FluidName = "WATER";
state->dataPlnt->PlantLoop(1).FluidIndex = 1;
state->dataPlnt->PlantLoop(1).LoopDemandCalcScheme = DataPlant::LoopDemandCalcScheme::SingleSetPoint;
state->dataPlnt->PlantLoop(1).LoopSide(DataPlant::LoopSideLocation::Demand).FlowLock = DataPlant::FlowLock::Locked;
state->dataLoopNodes->Node(3).Temp = 60.0;
state->dataLoopNodes->Node(3).MassFlowRate = 0.5;
state->dataLoopNodes->Node(4).TempSetPoint = 70.0;
state->dataLoopNodes->Node(7).Temp = 350.0;
state->dataLoopNodes->Node(7).MassFlowRate = 0.5;
auto &hwPlantLoop = state->dataPlnt->PlantLoop(1);
hwPlantLoop.FluidName = "WATER";
hwPlantLoop.FluidIndex = 1;
hwPlantLoop.LoopDemandCalcScheme = DataPlant::LoopDemandCalcScheme::SingleSetPoint;
hwPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).FlowLock = DataPlant::FlowLock::Locked;

EXPECT_EQ(1, thisChillerHeater.ChillReturnNodeNum);
EXPECT_EQ("EXH CHILLER INLET NODE", state->dataLoopNodes->NodeID(1));
EXPECT_EQ(2, thisChillerHeater.ChillSupplyNodeNum);
EXPECT_EQ("EXH CHILLER OUTLET NODE", state->dataLoopNodes->NodeID(2));
EXPECT_EQ(3, thisChillerHeater.HeatReturnNodeNum);
EXPECT_EQ("EXH CHILLER HEATING INLET NODE", state->dataLoopNodes->NodeID(3));
EXPECT_EQ(4, thisChillerHeater.HeatSupplyNodeNum);
EXPECT_EQ("EXH CHILLER HEATING OUTLET NODE", state->dataLoopNodes->NodeID(4));
EXPECT_EQ(5, thisChillerHeater.CondReturnNodeNum);
EXPECT_EQ("EXH CHILLER CONDENSER INLET NODE", state->dataLoopNodes->NodeID(5));
EXPECT_EQ("CAPSTONE C65 COMBUSTION AIR INLET NODE", state->dataLoopNodes->NodeID(6));
EXPECT_EQ(7, thisChillerHeater.ExhaustAirInletNodeNum);
EXPECT_EQ("CAPSTONE C65 COMBUSTION AIR OUTLET NODE", state->dataLoopNodes->NodeID(7));

constexpr Real64 hwSupplySetpoint = 70.0;
constexpr Real64 hwReturnTemp = 60.0;
constexpr Real64 hwMassFlow = 0.5;
state->dataLoopNodes->Node(thisChillerHeater.HeatReturnNodeNum).Temp = hwReturnTemp;
state->dataLoopNodes->Node(thisChillerHeater.HeatReturnNodeNum).MassFlowRate = hwMassFlow;
state->dataLoopNodes->Node(thisChillerHeater.HeatSupplyNodeNum).TempSetPoint = hwSupplySetpoint;

// Minimum temperature leaving the Chiller absorber is 176.6 C (350 F)
constexpr Real64 exhaustInTemp = 350.0;
constexpr Real64 absLeavingTemp = 176.667;
constexpr Real64 exhaustInMassFlowRate = 0.5;
constexpr Real64 exhaustInHumRate = 0.005;
state->dataLoopNodes->Node(thisChillerHeater.ExhaustAirInletNodeNum).Temp = exhaustInTemp;
state->dataLoopNodes->Node(thisChillerHeater.ExhaustAirInletNodeNum).MassFlowRate = exhaustInMassFlowRate;
state->dataLoopNodes->Node(thisChillerHeater.ExhaustAirInletNodeNum).HumRat = exhaustInHumRate;

Real64 loadinput = 5000.0;
bool const runflaginput = true;
thisChillerHeater.calcHeater(*state, loadinput, runflaginput);

EXPECT_NEAR(thisChillerHeater.HeatingLoad, 21085.0, 1e-6);
const Real64 CpHW = FluidProperties::GetSpecificHeatGlycol(*state, hwPlantLoop.FluidName, hwReturnTemp, hwPlantLoop.FluidIndex, "UnitTest");
EXPECT_EQ(4185.0, CpHW);
const Real64 expectedHeatingLoad = (hwSupplySetpoint - hwReturnTemp) * hwMassFlow * CpHW;

EXPECT_NEAR(20925.0, expectedHeatingLoad, 1e-6);

EXPECT_NEAR(thisChillerHeater.HeatingLoad, expectedHeatingLoad, 1e-6);
EXPECT_NEAR(thisChillerHeater.HeatElectricPower, 400.0, 1e-6);
EXPECT_NEAR(thisChillerHeater.HotWaterReturnTemp, 60.0, 1e-6);
EXPECT_NEAR(thisChillerHeater.HotWaterSupplyTemp, 70.0, 1e-6);
Expand All @@ -669,7 +701,11 @@ TEST_F(EnergyPlusFixture, ExhAbsorption_calcHeater_Fix_Test)
EXPECT_NEAR(thisChillerHeater.ElectricPower, 400.0, 1e-6);
EXPECT_NEAR(thisChillerHeater.ExhaustInTemp, 350.0, 1e-6);
EXPECT_NEAR(thisChillerHeater.ExhaustInFlow, 0.5, 1e-6);
EXPECT_NEAR(thisChillerHeater.ExhHeatRecPotentialHeat, 87087.5769469, 1e-6);

Real64 const CpAir = Psychrometrics::PsyCpAirFnW(exhaustInHumRate);
Real64 const expectedExhHeatRecPotentialHeat = exhaustInMassFlowRate * CpAir * (exhaustInTemp - absLeavingTemp);
EXPECT_NEAR(87891.51, expectedExhHeatRecPotentialHeat, 0.01);
EXPECT_NEAR(expectedExhHeatRecPotentialHeat, thisChillerHeater.ExhHeatRecPotentialHeat, 0.01);
}

TEST_F(EnergyPlusFixture, ExhAbsorption_GetInput_Multiple_Objects_Test)
Expand Down

0 comments on commit 944be33

Please sign in to comment.