Skip to content

Commit

Permalink
Merge pull request #10303 from NREL/10102InconsistentPoolFlowRatesTake2
Browse files Browse the repository at this point in the history
Correction of Inconsistent Flow Rates from Swimming Pools
  • Loading branch information
Myoldmopar authored Nov 21, 2023
2 parents 5737a7d + 1834f50 commit 685c7f2
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 89 deletions.
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;
}
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
}

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) {
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);
}

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;

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);
}

4 comments on commit 685c7f2

@nrel-bot-2
Copy link

Choose a reason for hiding this comment

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

develop (Myoldmopar) - x86_64-Linux-Ubuntu-22.04-gcc-11.4: OK (2778 of 2778 tests passed, 0 test warnings)

Build Badge Test Badge

@nrel-bot-3
Copy link

Choose a reason for hiding this comment

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

develop (Myoldmopar) - x86_64-MacOS-10.17-clang-14.0.0: OK (2757 of 2757 tests passed, 0 test warnings)

Build Badge Test Badge

@nrel-bot-2b
Copy link

Choose a reason for hiding this comment

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

develop (Myoldmopar) - x86_64-Linux-Ubuntu-22.04-gcc-11.4-UnitTestsCoverage-Debug: OK (1969 of 1969 tests passed, 0 test warnings)

Build Badge Test Badge Coverage Badge

@nrel-bot-2
Copy link

Choose a reason for hiding this comment

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

develop (Myoldmopar) - x86_64-Linux-Ubuntu-22.04-gcc-11.4-IntegrationCoverage-Debug: OK (790 of 790 tests passed, 0 test warnings)

Build Badge Test Badge Coverage Badge

Please sign in to comment.