-
Notifications
You must be signed in to change notification settings - Fork 392
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
Fix #10574 - Deal with BIPVT for WaterHeater sizing and throw error if Total Collector area found is zero #10577
Conversation
``` [ RUN ] EnergyPlusFixture.WH_Sizing /Users/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc:5690: Failure Expected equality of these values: 5.00 Which is: 5 Tank.Sizing.TotalSolarCollectorArea Which is: 0 /Users/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc:5692: Failure Expected equality of these values: 1.0 Which is: 1 Tank.Volume Which is: 0 /Users/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc:5711: Failure Value of: std::isnan(Tank.AmbientZoneGain) Actual: true Expected: false /Users/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc:5712: Failure Expected equality of these values: 0.0 Which is: 0 Tank.AmbientZoneGain Which is: nan ```
…f Total Collector area found is zero
…eed to check if we need to trigger the getinput for them
7567778
to
4ee8bcb
Compare
for (int CollectorNum = 1; CollectorNum <= state.dataPhotovoltaicThermalCollector->NumPVT; ++CollectorNum) { | ||
auto const &collector = state.dataPhotovoltaicThermalCollector->PVT(CollectorNum); | ||
this->Sizing.TotalSolarCollectorArea += collector.AreaCol; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also check the BIPVT collectors
if (this->VolumeWasAutoSized) { | ||
if (this->Sizing.TotalSolarCollectorArea > 0) { | ||
tmpTankVolume = this->Sizing.TotalSolarCollectorArea * this->Sizing.TankCapacityPerCollectorArea; | ||
} else { | ||
ShowFatalError(state, | ||
format("{}: Tank=\"{}\", requested sizing for volume with PerSolarCollectorArea but total found " | ||
"area of Collectors is zero.", | ||
RoutineName, | ||
this->Name)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If VolumeWasAutosized, and the collector area found is 0, we throw.
Otherwise we'd end up with the same issue, the volume is 0, the Mass of tank fluid (kg) m
is therefore 0 when CalcTankTemp is called, and you end up with a divide by zero.
EnergyPlus/src/EnergyPlus/WaterThermalTanks.cc
Line 7553 in 3c8844f
a = Q / (m * Cp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
@@ -11201,9 +11202,6 @@ void WaterThermalTankData::SizeTankForSupplySide(EnergyPlusData &state) | |||
|
|||
static constexpr std::string_view RoutineName("SizeTankForSupplySide"); | |||
|
|||
Real64 Tstart = 14.44; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- TODO: should this return early?
if (!this->VolumeWasAutoSized && !this->MaxCapacityWasAutoSized) {
return;
}
SizeStandAloneWaterHeater does avoid doing work
EnergyPlus/src/EnergyPlus/WaterThermalTanks.cc
Line 11555 in 3c8844f
if (this->VolumeWasAutoSized || this->MaxCapacityWasAutoSized) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, did you check if it triggered any failures or diffs when you return early? If it causes any issues, then I say we push that to a later PR. If it is a no diff/issue and we just gain a little efficiency, then that's great.
auto const &collector = state.dataSolarCollectors->Collector(CollectorNum); | ||
this->Sizing.TotalSolarCollectorArea += state.dataSurface->Surface(collector.Surface).Area; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- TODO: Should we make this should only count the collectors that serve this WH?!
const int thisPlantNum = this->SrcSidePlantLoc.loopNum;
const int plantNum = collector.plantLoc.loopNum;
if (plantNum == thisPlantNum) {
this->Sizing.TotalSolarCollectorArea += state.dataSurface->Surface(collector.Surface).Area;
}
The documentation does say that it uses all of the collectors in the model, but I think that is weird.
PerSolarCollectorArea
This design method scales tank volume based on the collector area for a solar hot water collector. The collector area is summed for all the collectors in the model and each tank is sized for the total. The collector area is determined from input for Solar Collectors defined elsewhere in the input file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an interesting idea. Are there any corner cases we would need to consider further? Multiple WH on the same plant loop or something? I don't think any corner cases would cause problems, but it may just continue to use non-ideal sizing values or something. Either way, I'm not sure we should worry about that in this scope.
@@ -5427,3 +5435,279 @@ TEST_F(EnergyPlusFixture, setBackupElementCapacityTest) | |||
expectedAnswer = -456.0; | |||
EXPECT_NEAR(DSup.BackupElementCapacity, expectedAnswer, allowedTolerance); | |||
} | |||
|
|||
TEST_F(EnergyPlusFixture, MixedTank_PVT_Per_VolumeSizing_PerSolarCollectorArea) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New unit test.
A PlantLoop with a WHMixed on the supply, a BIPVT collector on the demand.
WHMixed was Autosized Volume, Sizing is PerCollectorArea
state->dataPlnt->PlantFirstSizesOkayToFinalize = true; | ||
state->dataPlnt->PlantFinalSizesOkayToReport = true; | ||
Tank.onInitLoopEquip(*state, plantLocSupply); | ||
EXPECT_ENUM_EQ(DataPlant::LoopSideLocation::Supply, Tank.SrcSidePlantLoc.loopSideNum); | ||
EXPECT_EQ(1, Tank.SrcSidePlantLoc.loopNum); | ||
|
||
// compare_eio_stream(delimited_string({ | ||
// "Water Heater Information,WaterHeater:Mixed,SOLAR LOOP WATER HEATER,-99998.9999,0.0,0.000,0.0000", | ||
// "! <Component Sizing Information>, Component Type, Component Name, Input Field Description, Value", | ||
// " Component Sizing Information, WaterHeater:Mixed, SOLAR LOOP WATER HEATER, Tank Volume [m3], 1.00000", | ||
// })); | ||
EXPECT_DOUBLE_EQ(5.00, Tank.Sizing.TotalSolarCollectorArea); | ||
EXPECT_DOUBLE_EQ(0.2, Tank.Sizing.TankCapacityPerCollectorArea); | ||
EXPECT_DOUBLE_EQ(1.0, Tank.Volume); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Autosizing works with BIPVT
state->dataGlobal->HourOfDay = 0; | ||
state->dataGlobal->TimeStep = 1; | ||
state->dataGlobal->TimeStepZone = 1.0 / 60.0; // one-minute system time step | ||
state->dataHVACGlobal->TimeStepSys = state->dataGlobal->TimeStepZone; | ||
state->dataHVACGlobal->TimeStepSysSec = state->dataHVACGlobal->TimeStepSys * Constant::SecInHour; | ||
Tank.SavedTankTemp = 60.0; | ||
Tank.TankTemp = 60.0; | ||
Tank.AmbientTempZone = 20.0; | ||
Tank.AmbientTemp = 20.0; | ||
Tank.UseInletTemp = 20.0; | ||
Tank.SetPointTemp = 55.0; | ||
Tank.SetPointTemp2 = Tank.SetPointTemp; | ||
Tank.TimeElapsed = 0.0; | ||
|
||
Tank.SourceMassFlowRate = 0.0; | ||
|
||
Tank.CalcWaterThermalTankMixed(*state); | ||
EXPECT_FALSE(std::isnan(Tank.AmbientZoneGain)); | ||
EXPECT_DOUBLE_EQ(0.0, Tank.AmbientZoneGain); // Didn't define on/off cycle losses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specifically test for the NaN encountered.
There are still several TODO's so I'm putting this as waiting on developer for now. |
@Myoldmopar These TODOs are meant for discussion with you/the reviewer. it's stuff I noticed, that is extra, and that maybe should be addressed, maybe here (in the case of early returns), maybe into another (counting per zone) |
I see, I should have looked closer. Back when the PR list was like 60 I was looking quickly for PRs that appeared 'not done', and the TODO's were a red flag to me. :) I'll check it out in more detail now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My feeling on the TODO's is to just wait until a later PR. This seems good to me. Anyone have any issues?
auto const &collector = state.dataSolarCollectors->Collector(CollectorNum); | ||
this->Sizing.TotalSolarCollectorArea += state.dataSurface->Surface(collector.Surface).Area; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an interesting idea. Are there any corner cases we would need to consider further? Multiple WH on the same plant loop or something? I don't think any corner cases would cause problems, but it may just continue to use non-ideal sizing values or something. Either way, I'm not sure we should worry about that in this scope.
@@ -11201,9 +11202,6 @@ void WaterThermalTankData::SizeTankForSupplySide(EnergyPlusData &state) | |||
|
|||
static constexpr std::string_view RoutineName("SizeTankForSupplySide"); | |||
|
|||
Real64 Tstart = 14.44; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, did you check if it triggered any failures or diffs when you return early? If it causes any issues, then I say we push that to a later PR. If it is a no diff/issue and we just gain a little efficiency, then that's great.
if (this->VolumeWasAutoSized) { | ||
if (this->Sizing.TotalSolarCollectorArea > 0) { | ||
tmpTankVolume = this->Sizing.TotalSolarCollectorArea * this->Sizing.TankCapacityPerCollectorArea; | ||
} else { | ||
ShowFatalError(state, | ||
format("{}: Tank=\"{}\", requested sizing for volume with PerSolarCollectorArea but total found " | ||
"area of Collectors is zero.", | ||
RoutineName, | ||
this->Name)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
this->Sizing.TotalSolarCollectorArea += state.dataSurface->Surface(state.dataSolarCollectors->Collector(CollectorNum).Surface).Area; | ||
auto const &collector = state.dataSolarCollectors->Collector(CollectorNum); | ||
this->Sizing.TotalSolarCollectorArea += state.dataSurface->Surface(collector.Surface).Area; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm so tempted to say things like "range-based loop" and "std::accumulate", both of which could help remove the 1-based array dependence, but I won't for now. https://godbolt.org/z/35jEcv1E5
This is still fine, just let me pull develop and test locally. |
Yep, still good here. Merging, thanks @jmarrec |
Pull request overview
The issue is specific to the Solar Loop Water Heater. It's really a very specific combination of factors that lead to the issue:
As a result the collector area is found to be 0.0, there are zero warnings/errors reported.
The tank volume becomes zero, so the mass of the fluid is also 0, and in CalcTankTemp, you end up with a NaN due to a divide by zero.
This tank has "Ambient Temperature Indicator" set to "Zone", and that NaN propagates to the zone internal gains, and you end up with a DualSetPointWithDeadBand error due to that.
Tested that I can reproduce the crash with the Defect File (I put it on DevSupport) and it runs to completion after fix
Pull Request Author
Add to this list or remove from it as applicable. This is a simple templated set of guidelines.
Reviewer
This will not be exhaustively relevant to every PR.