Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correction of Inconsistent Flow Rates from Swimming Pools #10303

Merged
merged 6 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/EnergyPlus/Plant/PlantManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
#include <EnergyPlus/SetPointManager.hh>
#include <EnergyPlus/SolarCollectors.hh>
#include <EnergyPlus/SurfaceGroundHeatExchanger.hh>
#include <EnergyPlus/SwimmingPool.hh>
#include <EnergyPlus/SystemAvailabilityManager.hh>
#include <EnergyPlus/UserDefinedComponents.hh>
#include <EnergyPlus/UtilityRoutines.hh>
Expand Down Expand Up @@ -1314,8 +1315,12 @@ void GetPlantInput(EnergyPlusData &state)
// now deal with demand components of the ZoneHVAC type served by ControlCompOutput
break;
}
case PlantEquipmentType::SwimmingPool_Indoor: {
this_comp.CurOpSchemeType = OpScheme::Demand;
this_comp.compPtr = SwimmingPool::SwimmingPoolData::factory(state, CompNames(CompNum));
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks exactly right. I think there should be one more spot in PlantManager where we need to switch on equipment type. Down where we assign the flow request priority. If that's the only change needed here, I can add that while I'm doing final testing.

case PlantEquipmentType::PackagedTESCoolingCoil:
case PlantEquipmentType::SwimmingPool_Indoor:
case PlantEquipmentType::CoilWaterCooling:
case PlantEquipmentType::CoilWaterDetailedFlatCooling:
case PlantEquipmentType::CoilWaterSimpleHeating:
Expand Down
148 changes: 69 additions & 79 deletions src/EnergyPlus/SwimmingPool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,29 +105,23 @@ namespace EnergyPlus::SwimmingPool {
// 4. Smith, C., R. Jones, and G. Lof (1993). Energy Requirements and Potential Savings for Heated
// Indoor Swimming Pools. ASHRAE Transactions 99(2), p.864-874.

void SimSwimmingPool(EnergyPlusData &state, bool FirstHVACIteration)
SwimmingPoolData *SwimmingPoolData::factory(EnergyPlusData &state, std::string const &objectName)
{
// Process the input data if it hasn't been done already
if (state.dataSwimmingPools->getSwimmingPoolInput) {
GetSwimmingPool(state);
state.dataSwimmingPools->getSwimmingPoolInput = false;
}

// System wide (for all pools) inits
state.dataHeatBalFanSys->SumConvPool = 0.0;
state.dataHeatBalFanSys->SumLatentPool = 0.0;

PlantLocation A(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
Real64 CurLoad = 0.0;
bool RunFlag = true;

for (auto &thisPool : state.dataSwimmingPools->Pool) {
thisPool.simulate(state, A, FirstHVACIteration, CurLoad, RunFlag);
// Now look for this particular swimming pool in the list
for (auto &pool : state.dataSwimmingPools->Pool) {
if (pool.Name == objectName) {
return &pool;
}
}

if (state.dataSwimmingPools->NumSwimmingPools > 0) HeatBalanceSurfaceManager::CalcHeatBalanceInsideSurf(state);

ReportSwimmingPool(state);
// If we didn't find it, fatal
ShowFatalError(state,
format("LocalSwimmingPoolFactory: Error getting inputs or index for swimming pool named: {}", objectName)); // LCOV_EXCL_LINE
// Shut up the compiler
return nullptr; // LCOV_EXCL_LINE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, looks like a typical plant factory. I like that it returns a SwimmingPoolData* instead of a PlantComponent*. That makes things easier.

}

void SwimmingPoolData::simulate(EnergyPlusData &state,
Expand All @@ -136,11 +130,21 @@ void SwimmingPoolData::simulate(EnergyPlusData &state,
[[maybe_unused]] Real64 &CurLoad,
[[maybe_unused]] bool RunFlag)
{
state.dataHeatBalFanSys->SumConvPool(this->ZonePtr) = 0.0;
state.dataHeatBalFanSys->SumLatentPool(this->ZonePtr) = 0.0;

CurLoad = 0.0;
RunFlag = true;

this->initialize(state, FirstHVACIteration);

this->calculate(state);

this->update(state);

if (state.dataSwimmingPools->NumSwimmingPools > 0) HeatBalanceSurfaceManager::CalcHeatBalanceInsideSurf(state);

this->report(state);
}

void GetSwimmingPool(EnergyPlusData &state)
Expand Down Expand Up @@ -1011,13 +1015,61 @@ void SwimmingPoolData::update(EnergyPlusData &state)
Real64 WaterMassFlow = state.dataLoopNodes->Node(this->WaterInletNode).MassFlowRate; // water mass flow rate
if (WaterMassFlow > 0.0) state.dataLoopNodes->Node(this->WaterOutletNode).Temp = this->PoolWaterTemp;
}

void SwimmingPoolData::oneTimeInit_new([[maybe_unused]] EnergyPlusData &state)
{
}

void SwimmingPoolData::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
{
}

void SwimmingPoolData::report(EnergyPlusData &state)
{
// SUBROUTINE INFORMATION:
// AUTHOR Rick Strand, Ho-Sung Kim
// DATE WRITTEN October 2014

// PURPOSE OF THIS SUBROUTINE:
// This subroutine simply produces output for the swimming pool model.

// SUBROUTINE PARAMETER DEFINITIONS:
static constexpr std::string_view RoutineName("SwimmingPoolData::report");
Real64 constexpr MinDensity = 1.0; // to avoid a divide by zero

int SurfNum = this->SurfacePtr; // surface number index

// First transfer the surface inside temperature data to the current pool water temperature
this->PoolWaterTemp = state.dataHeatBalSurf->SurfInsideTempHist(1)(SurfNum);

// Next calculate the amount of heating done by the plant loop
Real64 Cp = FluidProperties::GetSpecificHeatGlycol(state, "WATER", this->PoolWaterTemp, this->GlycolIndex,
RoutineName); // specific heat of water
this->HeatPower = this->WaterMassFlowRate * Cp * (this->WaterInletTemp - this->PoolWaterTemp);

// Now the power consumption of miscellaneous equipment
Real64 Density = FluidProperties::GetDensityGlycol(state, "WATER", this->PoolWaterTemp, this->GlycolIndex,
RoutineName); // density of water
if (Density > MinDensity) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I'm not sure GetDensityGlycol will ever give back zero density, but this check is fine I guess.

this->MiscEquipPower = this->MiscPowerFactor * this->WaterMassFlowRate / Density;
} else {
this->MiscEquipPower = 0.0;
}

// Also the radiant exchange converted to convection by the pool cover
this->RadConvertToConvectRep = this->RadConvertToConvect * state.dataSurface->Surface(SurfNum).Area;

// Finally calculate the summed up report variables
Real64 thisTimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
this->MiscEquipEnergy = this->MiscEquipPower * thisTimeStepSysSec;
this->HeatEnergy = this->HeatPower * thisTimeStepSysSec;
this->MakeUpWaterMass = this->MakeUpWaterMassFlowRate * thisTimeStepSysSec;
this->EvapEnergyLoss = this->EvapHeatLossRate * thisTimeStepSysSec;

this->MakeUpWaterVolFlowRate = MakeUpWaterVolFlowFunct(this->MakeUpWaterMassFlowRate, Density);
this->MakeUpWaterVol = MakeUpWaterVolFunct(this->MakeUpWaterMass, Density);
}

void UpdatePoolSourceValAvg(EnergyPlusData &state, bool &SwimmingPoolOn) // .TRUE. if the swimming pool "runs" this zone time step
{
// SUBROUTINE INFORMATION:
Expand Down Expand Up @@ -1090,68 +1142,6 @@ void UpdatePoolSourceValAvg(EnergyPlusData &state, bool &SwimmingPoolOn) // .TRU
}
}

void ReportSwimmingPool(EnergyPlusData &state)
{
// SUBROUTINE INFORMATION:
// AUTHOR Rick Strand, Ho-Sung Kim
// DATE WRITTEN October 2014

// PURPOSE OF THIS SUBROUTINE:
// This subroutine simply produces output for the swimming pool model.

// SUBROUTINE PARAMETER DEFINITIONS:
static constexpr std::string_view RoutineName("ReportSwimmingPool");
Real64 constexpr MinDensity = 1.0; // to avoid a divide by zero

for (int PoolNum = 1; PoolNum <= state.dataSwimmingPools->NumSwimmingPools; ++PoolNum) {

int SurfNum = state.dataSwimmingPools->Pool(PoolNum).SurfacePtr; // surface number index

// First transfer the surface inside temperature data to the current pool water temperature
state.dataSwimmingPools->Pool(PoolNum).PoolWaterTemp = state.dataHeatBalSurf->SurfInsideTempHist(1)(SurfNum);

// Next calculate the amount of heating done by the plant loop
Real64 Cp = FluidProperties::GetSpecificHeatGlycol(state,
"WATER",
state.dataSwimmingPools->Pool(PoolNum).PoolWaterTemp,
state.dataSwimmingPools->Pool(PoolNum).GlycolIndex,
RoutineName); // specific heat of water
state.dataSwimmingPools->Pool(PoolNum).HeatPower =
state.dataSwimmingPools->Pool(PoolNum).WaterMassFlowRate * Cp *
(state.dataSwimmingPools->Pool(PoolNum).WaterInletTemp - state.dataSwimmingPools->Pool(PoolNum).PoolWaterTemp);

// Now the power consumption of miscellaneous equipment
Real64 Density = FluidProperties::GetDensityGlycol(state,
"WATER",
state.dataSwimmingPools->Pool(PoolNum).PoolWaterTemp,
state.dataSwimmingPools->Pool(PoolNum).GlycolIndex,
RoutineName); // density of water
if (Density > MinDensity) {
state.dataSwimmingPools->Pool(PoolNum).MiscEquipPower =
state.dataSwimmingPools->Pool(PoolNum).MiscPowerFactor * state.dataSwimmingPools->Pool(PoolNum).WaterMassFlowRate / Density;
} else {
state.dataSwimmingPools->Pool(PoolNum).MiscEquipPower = 0.0;
}

// Also the radiant exchange converted to convection by the pool cover
state.dataSwimmingPools->Pool(PoolNum).RadConvertToConvectRep =
state.dataSwimmingPools->Pool(PoolNum).RadConvertToConvect * state.dataSurface->Surface(SurfNum).Area;

// Finally calculate the summed up report variables
state.dataSwimmingPools->Pool(PoolNum).MiscEquipEnergy =
state.dataSwimmingPools->Pool(PoolNum).MiscEquipPower * state.dataHVACGlobal->TimeStepSysSec;
state.dataSwimmingPools->Pool(PoolNum).HeatEnergy = state.dataSwimmingPools->Pool(PoolNum).HeatPower * state.dataHVACGlobal->TimeStepSysSec;
state.dataSwimmingPools->Pool(PoolNum).MakeUpWaterMass =
state.dataSwimmingPools->Pool(PoolNum).MakeUpWaterMassFlowRate * state.dataHVACGlobal->TimeStepSysSec;
state.dataSwimmingPools->Pool(PoolNum).EvapEnergyLoss =
state.dataSwimmingPools->Pool(PoolNum).EvapHeatLossRate * state.dataHVACGlobal->TimeStepSysSec;

state.dataSwimmingPools->Pool(PoolNum).MakeUpWaterVolFlowRate =
MakeUpWaterVolFlowFunct(state.dataSwimmingPools->Pool(PoolNum).MakeUpWaterMassFlowRate, Density);
state.dataSwimmingPools->Pool(PoolNum).MakeUpWaterVol = MakeUpWaterVolFunct(state.dataSwimmingPools->Pool(PoolNum).MakeUpWaterMass, Density);
}
}

Real64 MakeUpWaterVolFlowFunct(Real64 MakeUpWaterMassFlowRate, Real64 Density)
{
return MakeUpWaterMassFlowRate / Density;
Expand Down
8 changes: 4 additions & 4 deletions src/EnergyPlus/SwimmingPool.hh
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ namespace SwimmingPool {
{
}

static SwimmingPoolData *factory(EnergyPlusData &state, std::string const &objectName);

void simulate([[maybe_unused]] EnergyPlusData &state,
const PlantLocation &calledFromLocation,
bool FirstHVACIteration,
Expand Down Expand Up @@ -187,16 +189,14 @@ namespace SwimmingPool {
void oneTimeInit(EnergyPlusData &state) override;

void oneTimeInit_new(EnergyPlusData &state) override;

void report(EnergyPlusData &state);
};

void GetSwimmingPool(EnergyPlusData &state);

void SimSwimmingPool(EnergyPlusData &state, bool FirstHVACIteration);

void UpdatePoolSourceValAvg(EnergyPlusData &state, bool &SwimmingPoolOn); // .TRUE. if the swimming pool has "run" this zone time step

void ReportSwimmingPool(EnergyPlusData &state);

Real64 MakeUpWaterVolFlowFunct(Real64 MakeUpWaterMassFlowRate, Real64 Density);

Real64 MakeUpWaterVolFunct(Real64 MakeUpWaterMass, Real64 Density);
Expand Down
5 changes: 0 additions & 5 deletions src/EnergyPlus/ZoneEquipmentManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@
#include <EnergyPlus/ScheduleManager.hh>
#include <EnergyPlus/SplitterComponent.hh>
#include <EnergyPlus/SteamBaseboardRadiator.hh>
#include <EnergyPlus/SwimmingPool.hh>
#include <EnergyPlus/SystemAvailabilityManager.hh>
#include <EnergyPlus/ThermalChimney.hh>
#include <EnergyPlus/UnitHeater.hh>
Expand Down Expand Up @@ -3099,10 +3098,6 @@ void SimZoneEquipment(EnergyPlusData &state, bool const FirstHVACIteration, bool

FirstCall = false;

// Simulate all of the pools. These have a potential impact on surface heat balances, zone air heat balances, and moisture balances.
// These should be simulated first so that any systems or zone equipment devices deal with the effects of the pool properly.
SwimmingPool::SimSwimmingPool(state, FirstHVACIteration);

// Loop over all the primary air loop; simulate their components (equipment)
// and controllers
if (state.dataHeatBal->ZoneAirMassFlow.EnforceZoneMassBalance) {
Expand Down
81 changes: 81 additions & 0 deletions tst/EnergyPlus/unit/SwimmingPool.unit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include <EnergyPlus/Data/EnergyPlusData.hh>
#include <EnergyPlus/DataEnvironment.hh>
#include <EnergyPlus/DataHeatBalFanSys.hh>
#include <EnergyPlus/DataHeatBalSurface.hh>
#include <EnergyPlus/DataSizing.hh>
#include <EnergyPlus/DataSurfaces.hh>
#include <EnergyPlus/Plant/DataPlant.hh>
Expand Down Expand Up @@ -460,3 +461,83 @@ TEST_F(EnergyPlusFixture, SwimmingPool_MultiplePoolUpdatePoolSourceValAvgTest)
EXPECT_NEAR(HBFanData->PoolHeatTransCoefs(1), Pool1Data.HeatTransCoefsAvg, closeEnough);
EXPECT_NEAR(HBFanData->PoolHeatTransCoefs(2), Pool2Data.HeatTransCoefsAvg, closeEnough);
}

TEST_F(EnergyPlusFixture, SwimmingPool_factoryTest)
{
// Test of new factory routine as part of the move to make swimming pools a plant loop object called
// from the plant loop and not zone equipment.
state->dataSwimmingPools->getSwimmingPoolInput = false;
state->dataSwimmingPools->NumSwimmingPools = 4;
state->dataSwimmingPools->Pool.allocate(state->dataSwimmingPools->NumSwimmingPools);

auto &poolData = state->dataSwimmingPools->Pool;
poolData(1).Name = "Schwimmbad Nummer Eins";
poolData(2).Name = "Schwimmbad Nummer Zwei";
poolData(3).Name = "Schwimmbad Nummer Drei";
poolData(4).Name = "Schwimmbad Nummer Vier";

// Test 1: First Pool
SwimmingPoolData *factoryResult = SwimmingPoolData::factory(*state, poolData(1).Name);
EXPECT_NE(factoryResult, nullptr);

// Test 2: First Pool
factoryResult = SwimmingPoolData::factory(*state, poolData(2).Name);
EXPECT_NE(factoryResult, nullptr);

// Test 3: First Pool
factoryResult = SwimmingPoolData::factory(*state, poolData(3).Name);
EXPECT_NE(factoryResult, nullptr);

// Test 4: First Pool
factoryResult = SwimmingPoolData::factory(*state, poolData(4).Name);
EXPECT_NE(factoryResult, nullptr);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auf allen steht „First Pool“, aber ansonsten sieht der test gut aus.


TEST_F(EnergyPlusFixture, SwimmingPool_reportTest)
{
// Test of modified report routine as part of the move to make swimming pools a plant loop object called
// from the plant loop and not zone equipment. Report routine gets called for each pool separately rather
// than reporting for everything all at once.
Real64 constexpr closeEnough = 0.000001;
SwimmingPoolData myPool;

// Test Data
myPool.Name = "This Pool";
myPool.SurfacePtr = 1;
state->dataHeatBalSurf->SurfInsideTempHist.allocate(1);
state->dataHeatBalSurf->SurfInsideTempHist(1).dimension(1, 0);
state->dataHeatBalSurf->SurfInsideTempHist(1)(1) = 10.0;
myPool.WaterMassFlowRate = 0.001;
myPool.WaterInletTemp = 40.0;
myPool.MiscPowerFactor = 1000.0;
myPool.RadConvertToConvect = 0.5;
state->dataSurface->Surface.allocate(1);
state->dataSurface->Surface(1).Area = 5.0;
state->dataHVACGlobal->TimeStepSysSec = 60.0;
myPool.MiscEquipPower = 0.12;
myPool.MakeUpWaterMassFlowRate = 0.1;
myPool.EvapHeatLossRate = 0.016;

// Test of .report routine
myPool.report(*state);
Real64 expectedHeatPower = 125.73;
Real64 expectedMiscEquipPower = 0.0010003;
Real64 expectedRadConvertToConvectRep = 2.5;
Real64 expectedMiscEquipEnergy = 0.060018;
Real64 expectedHeatEnergy = 7543.8;
Real64 expectedMakeUpWaterMass = 6.0;
Real64 expectedEvapEnergyLoss = 0.96;
Real64 expectedMakeUpWaterVolFlowRate = 0.0001003;
Real64 expectedMakeUpWaterVol = 0.0060018;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these based on a specific controlled calculation?


EXPECT_NEAR(state->dataHeatBalSurf->SurfInsideTempHist(1)(1), myPool.PoolWaterTemp, closeEnough);
EXPECT_NEAR(expectedHeatPower, myPool.HeatPower, closeEnough);
EXPECT_NEAR(expectedMiscEquipPower, myPool.MiscEquipPower, closeEnough);
EXPECT_NEAR(expectedRadConvertToConvectRep, myPool.RadConvertToConvectRep, closeEnough);
EXPECT_NEAR(expectedMiscEquipEnergy, myPool.MiscEquipEnergy, closeEnough);
EXPECT_NEAR(expectedHeatEnergy, myPool.HeatEnergy, closeEnough);
EXPECT_NEAR(expectedMakeUpWaterMass, myPool.MakeUpWaterMass, closeEnough);
EXPECT_NEAR(expectedEvapEnergyLoss, myPool.EvapEnergyLoss, closeEnough);
EXPECT_NEAR(expectedMakeUpWaterVolFlowRate, myPool.MakeUpWaterVolFlowRate, closeEnough);
EXPECT_NEAR(expectedMakeUpWaterVol, myPool.MakeUpWaterVol, closeEnough);
}
Loading