Fix #11181 - Wrong Sunlit fractions for Detached Shading Surfaces when Sun is Down#11237
Fix #11181 - Wrong Sunlit fractions for Detached Shading Surfaces when Sun is Down#11237
Conversation
s_surf->SurfaceWindow was not allocated in this test.
Previously SolarShading.cc was doing
```c++
for (auto &e : s_surf->SurfaceWindow) {
std::fill(e.OutProjSLFracMult.begin(), e.OutProjSLFracMult.end(), 1.0);
std::fill(e.InOutProjSLFracMult.begin(), e.InOutProjSLFracMult.end(), 1.0);
}
```
And I changed this to an index based loop, so it fails
|
Running the Shoebox_v25.1.idf from DevSupport. before fix, see #11181 (comment) After fix:
|
|
|
|
| for (int zoneNum = 1; zoneNum <= state.dataGlobal->NumOfZones; ++zoneNum) { | ||
| for (int spaceNum : state.dataHeatBal->Zone(zoneNum).spaceIndexes) { | ||
| auto &thisSpace = state.dataHeatBal->space(spaceNum); | ||
| int firstSurf = thisSpace.OpaqOrIntMassSurfaceFirst; | ||
| int lastSurf = thisSpace.OpaqOrIntMassSurfaceLast; |
There was a problem hiding this comment.
This was only doing it for attached building surfaces
| // Array1D and 1D-ish | ||
| for (int surfNum = 1; surfNum <= state.dataSurface->TotSurfaces; ++surfNum) { | ||
| s_surf->SurfOpaqAO(surfNum) = 0.0; | ||
| state.dataSolarShading->SurfSunCosTheta(surfNum) = 0.0; | ||
|
|
||
| for (int hour = 1; hour <= 24; ++hour) { | ||
| s_surf->SurfaceWindow(surfNum).OutProjSLFracMult[hour] = 1.0; | ||
| s_surf->SurfaceWindow(surfNum).InOutProjSLFracMult[hour] = 1.0; | ||
| } | ||
| } | ||
| } else { | ||
| for (int zoneNum = 1; zoneNum <= state.dataGlobal->NumOfZones; ++zoneNum) { | ||
| for (int spaceNum : state.dataHeatBal->Zone(zoneNum).spaceIndexes) { | ||
| auto &thisSpace = state.dataHeatBal->space(spaceNum); | ||
| int const firstSurf = thisSpace.HTSurfaceFirst; | ||
| int const lastSurf = thisSpace.HTSurfaceLast; | ||
| for (int surfNum = firstSurf; surfNum <= lastSurf; ++surfNum) { | ||
| state.dataSolarShading->SurfSunCosTheta(surfNum) = 0.0; | ||
| s_surf->SurfOpaqAO(surfNum) = 0.0; | ||
| state.dataHeatBal->SurfSunlitFrac(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfSunlitFracWithoutReveal(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfSunlitFracHR(state.dataGlobal->HourOfDay, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfCosIncAngHR(state.dataGlobal->HourOfDay, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfCosIncAng(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, surfNum) = 0.0; | ||
|
|
||
| // Array2D | ||
| for (int hour = 1; hour <= 24; ++hour) { | ||
| for (int surfNum = 1; surfNum <= state.dataSurface->TotSurfaces; ++surfNum) { | ||
| state.dataHeatBal->SurfSunlitFracHR(hour, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfCosIncAngHR(hour, surfNum) = 0.0; | ||
| } | ||
| } | ||
|
|
||
| // Array3D | ||
| for (int hour = 1; hour <= 24; ++hour) { | ||
| for (int timestep = 1; timestep <= state.dataGlobal->TimeStepsInHour; ++timestep) { | ||
| for (int surfNum = 1; surfNum <= state.dataSurface->TotSurfaces; ++surfNum) { | ||
| state.dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfCosIncAng(hour, timestep, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfSunlitFracWithoutReveal(hour, timestep, surfNum) = 0.0; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Array4D | ||
| for (int hour = 1; hour <= 24; ++hour) { | ||
| for (int timestep = 1; timestep <= state.dataGlobal->TimeStepsInHour; ++timestep) { | ||
| for (int backSurfNum = 1; backSurfNum <= state.dataBSDFWindow->MaxBkSurf; ++backSurfNum) { | ||
| for (int surfNum = firstSurf; surfNum <= lastSurf; ++surfNum) { | ||
| state.dataHeatBal->SurfWinBackSurfaces(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, backSurfNum, surfNum) = 0; | ||
| state.dataHeatBal->SurfWinOverlapAreas(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, backSurfNum, surfNum) = 0.0; | ||
| for (int surfNum = 1; surfNum <= state.dataSurface->TotSurfaces; ++surfNum) { | ||
| state.dataHeatBal->SurfWinBackSurfaces(hour, timestep, backSurfNum, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfWinOverlapAreas(hour, timestep, backSurfNum, surfNum) = 0.0; |
There was a problem hiding this comment.
Do it for all arrays, keeping the explicit init instead of relying (like pre v9.6.0) on ObjexxFCL assign functors
| for (int surfNum = 1; surfNum <= state.dataSurface->TotSurfaces; ++surfNum) { | ||
| state.dataSolarShading->SurfSunCosTheta(surfNum) = 0.0; | ||
| s_surf->SurfOpaqAO(surfNum) = 0.0; | ||
|
|
||
| s_surf->SurfaceWindow(surfNum).OutProjSLFracMult[state.dataGlobal->HourOfDay] = 1.0; | ||
| s_surf->SurfaceWindow(surfNum).InOutProjSLFracMult[state.dataGlobal->HourOfDay] = 1.0; | ||
|
|
||
| // Array2D | ||
| state.dataHeatBal->SurfSunlitFracHR(state.dataGlobal->HourOfDay, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfCosIncAngHR(state.dataGlobal->HourOfDay, surfNum) = 0.0; | ||
|
|
||
| // Array3D | ||
| state.dataHeatBal->SurfSunlitFrac(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfSunlitFracWithoutReveal(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, surfNum) = 0.0; | ||
| state.dataHeatBal->SurfCosIncAng(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, surfNum) = 0.0; | ||
| } | ||
|
|
||
| // Array4D | ||
| for (int backSurfNum = 1; backSurfNum <= state.dataBSDFWindow->MaxBkSurf; ++backSurfNum) { | ||
| for (int surfNum = 1; surfNum <= state.dataSurface->TotSurfaces; ++surfNum) { | ||
| state.dataHeatBal->SurfWinBackSurfaces(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, backSurfNum, surfNum) = 0; | ||
| state.dataHeatBal->SurfWinOverlapAreas(state.dataGlobal->HourOfDay, state.dataGlobal->TimeStep, backSurfNum, surfNum) = 0.0; | ||
| } |
There was a problem hiding this comment.
Same idea when DetailedSolarTimestepIntegration is on
| TEST_F(EnergyPlusFixture, SolarShadingTest_CalcPerSolarBeamTestResetsFrac) | ||
| { | ||
| // Test for #11181 - Test inits for integrated and non-integrated shading calcs, including for DETACHED shading surfaces | ||
| // We are going to mimic the case where we have two surfaces: | ||
| // 1) A surface part a zone | ||
| // 2) A detached or building shading surface, not part of any zone | ||
| // | ||
| // We also assume we are in a case where we're starting a new environment, and the SurfSunlitFrac is already filled with numbers | ||
| // The issue with #11181 was that the arrays were NOT being cleared for the detached shading surfaces, so the previous values were retained when | ||
| // the Sun was down, because FigureSolarBeamAtTimestep returns early in that case |
| // Prefill with some values | ||
| state->dataHeatBal->SurfSunlitFrac = 0.5; | ||
| state->dataHeatBal->SurfSunlitFracHR = 0.5; | ||
| // Test non-integrated option first, CalcPerSolarBeam should set OutProjSLFracMult and InOutProjSLFracMult to 1.0 for all hours | ||
| for (int SurfNum = 1; SurfNum <= state->dataSurface->TotSurfaces; ++SurfNum) { | ||
| for (int Hour = 1; Hour <= Constant::iHoursInDay; ++Hour) { | ||
| state->dataSurface->SurfaceWindow(SurfNum).OutProjSLFracMult[Hour] = 999.0; | ||
| state->dataSurface->SurfaceWindow(SurfNum).InOutProjSLFracMult[Hour] = 888.0; | ||
| } | ||
| } | ||
|
|
||
| SolarShading::DetermineShadowingCombinations(*state); | ||
|
|
||
| state->dataSysVars->DetailedSolarTimestepIntegration = false; | ||
| CalcPerSolarBeam(*state, AvgEqOfTime, AvgSinSolarDeclin, AvgCosSolarDeclin); | ||
|
|
||
| int constexpr sunRiseHour = 8; | ||
| int constexpr sunRiseTimeStep = 1; | ||
| int constexpr sunSetHour = 17; | ||
| int constexpr sunSetTimeStep = 1; | ||
|
|
||
| auto isSunUp = [=](int hour, int timestep) { | ||
| if ((hour < sunRiseHour || (hour == sunRiseHour && timestep < sunRiseTimeStep))) { | ||
| return false; | ||
| } | ||
| if (hour > sunSetHour || (hour == sunSetHour && timestep > sunSetTimeStep)) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| }; | ||
|
|
||
| // Ensure we have sun is Up working properly | ||
| for (int hour = 1; hour <= Constant::iHoursInDay; ++hour) { | ||
| for (int timestep = 1; timestep <= state->dataGlobal->TimeStepsInHour; ++timestep) { | ||
| if (isSunUp(hour, timestep)) { | ||
| EXPECT_GT(state->dataBSDFWindow->SUNCOSTS(timestep, hour).z, 0.0); | ||
| } else { | ||
| EXPECT_LT(state->dataBSDFWindow->SUNCOSTS(timestep, hour).z, 0.0); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
We prefill the arrays with some values (0.5 all around).
Then we call CalcPerSolarBeam.
I create a lambda that checks if the sun is up, and ensure it works like I expect it to. It does.
| // Check that the fraction Sunlit is properly set to 1.0 when the sun is Up and ZERO otherwis: it does NOT remember previously set values | ||
| for (int surfNum = 1; surfNum <= state->dataSurface->TotSurfaces; ++surfNum) { | ||
| SCOPED_TRACE(fmt::format("Surface {}='{}'", surfNum, state->dataSurface->Surface(surfNum).Name)); | ||
| for (int hour = 1; hour <= Constant::iHoursInDay; ++hour) { | ||
| SCOPED_TRACE(fmt::format("Hour={}", hour)); | ||
| if (isSunUp(hour, state->dataGlobal->TimeStepsInHour)) { | ||
| EXPECT_DOUBLE_EQ(1.0, state->dataHeatBal->SurfSunlitFracHR(hour, surfNum)) << "Failed when Sun is Up"; | ||
| } else { | ||
| EXPECT_DOUBLE_EQ(0.0, state->dataHeatBal->SurfSunlitFracHR(hour, surfNum)) << "Failed when Sun is Down"; | ||
| } | ||
| for (int timestep = 1; timestep <= state->dataGlobal->TimeStepsInHour; ++timestep) { | ||
| SCOPED_TRACE(fmt::format("Timestep={}", timestep)); | ||
| if (isSunUp(hour, timestep)) { | ||
| EXPECT_DOUBLE_EQ(1.0, state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)) << "Failed when Sun is Up"; | ||
| } else { | ||
| EXPECT_DOUBLE_EQ(0.0, state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)) << "Failed when Sun is Down"; | ||
| } | ||
| } | ||
|
|
||
| EXPECT_EQ(1.0, state->dataSurface->SurfaceWindow(surfNum).OutProjSLFracMult[hour]); | ||
| EXPECT_EQ(1.0, state->dataSurface->SurfaceWindow(surfNum).InOutProjSLFracMult[hour]); | ||
| } | ||
| } |
There was a problem hiding this comment.
It will fail a bunch on develop, when the SunIsDown, and only for the DETACHED shading surface.
[ RUN ] EnergyPlusFixture.SolarShadingTest_CalcPerSolarBeamTestResetsFrac
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:319: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFracHR(hour, surfNum)
Which is: 0.5
Failed when Sun is Down
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:315: Hour=1
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:313: Surface 2='SHADING'
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:326: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)
Which is: 0.5
Failed when Sun is Down
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:322: Timestep=1
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:315: Hour=1
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:313: Surface 2='SHADING'
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:326: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)
Which is: 0.5
Failed when Sun is Down
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:322: Timestep=2
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:315: Hour=1
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:313: Surface 2='SHADING'
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:326: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)
Which is: 0.5
Failed when Sun is Down
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:322: Timestep=3
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:315: Hour=1
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:313: Surface 2='SHADING'
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:326: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)
Which is: 0.5
Failed when Sun is Down
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:322: Timestep=4
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:315: Hour=1
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:313: Surface 2='SHADING'
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:319: Failure
[...]
| // Test integrated option, CalcPerSolarBeam should set OutProjSLFracMult and InOutProjSLFracMult to 1.0 only for the specified hour | ||
| // Re-initialize to new values | ||
| for (int surfNum = 1; surfNum <= state->dataSurface->TotSurfaces; ++surfNum) { | ||
| for (int hour = 1; hour <= Constant::iHoursInDay; ++hour) { | ||
| state->dataSurface->SurfaceWindow(surfNum).OutProjSLFracMult[hour] = 555.0; | ||
| state->dataSurface->SurfaceWindow(surfNum).InOutProjSLFracMult[hour] = 444.0; | ||
| } | ||
| } | ||
| state->dataHeatBal->SurfSunlitFrac = 0.5; | ||
| state->dataHeatBal->SurfSunlitFracHR = 0.5; | ||
|
|
||
| state->dataSysVars->DetailedSolarTimestepIntegration = true; | ||
| state->dataGlobal->HourOfDay = 23; | ||
| state->dataGlobal->TimeStep = 3; | ||
| CalcPerSolarBeam(*state, AvgEqOfTime, AvgSinSolarDeclin, AvgCosSolarDeclin); | ||
|
|
||
| for (int surfNum = 1; surfNum <= state->dataSurface->TotSurfaces; ++surfNum) { | ||
| SCOPED_TRACE(fmt::format("Surface {}='{}'", surfNum, state->dataSurface->Surface(surfNum).Name)); | ||
| for (int hour = 1; hour <= Constant::iHoursInDay; ++hour) { | ||
| SCOPED_TRACE(fmt::format("Hour={}", hour)); | ||
| if (hour == state->dataGlobal->HourOfDay) { | ||
| EXPECT_EQ(1.0, state->dataSurface->SurfaceWindow(surfNum).OutProjSLFracMult[hour]); | ||
| EXPECT_EQ(1.0, state->dataSurface->SurfaceWindow(surfNum).InOutProjSLFracMult[hour]); | ||
| EXPECT_DOUBLE_EQ(0.0, state->dataHeatBal->SurfSunlitFracHR(hour, surfNum)); | ||
| } else { | ||
| EXPECT_EQ(555.0, state->dataSurface->SurfaceWindow(surfNum).OutProjSLFracMult[hour]); | ||
| EXPECT_EQ(444.0, state->dataSurface->SurfaceWindow(surfNum).InOutProjSLFracMult[hour]); | ||
| EXPECT_DOUBLE_EQ(0.5, state->dataHeatBal->SurfSunlitFracHR(hour, surfNum)); | ||
| } | ||
| for (int timestep = 1; timestep <= state->dataGlobal->TimeStepsInHour; ++timestep) { | ||
| SCOPED_TRACE(fmt::format("Timestep={}", timestep)); | ||
| if (hour == state->dataGlobal->HourOfDay && timestep == state->dataGlobal->TimeStep) { | ||
| EXPECT_DOUBLE_EQ(0.0, state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)); | ||
| } else { | ||
| EXPECT_DOUBLE_EQ(0.5, state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Another pass with DetailedTimeStepIntegration. It fails as well for the shading surface on develop
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:358: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFracHR(hour, surfNum)
Which is: 0.5
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:354: Hour=23
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:352: Surface 2='SHADING'
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:367: Failure
Expected equality of these values:
0.0
Which is: 0
state->dataHeatBal->SurfSunlitFrac(hour, timestep, surfNum)
Which is: 0.5
Google Test trace:
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:365: Timestep=3
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:354: Hour=23
/home/julien/Software/Others/EnergyPlus2/tst/EnergyPlus/unit/SolarShading.unit.cc:352: Surface 2='SHADING'
[ FAILED ] EnergyPlusFixture.SolarShadingTest_CalcPerSolarBeamTestResetsFrac (1065 ms)
|
mitchute
left a comment
There was a problem hiding this comment.
This all looks reasonable.




Pull request overview
Some solar shading arrays are not cleared.
Description of the purpose of this PR
The issue with #11181 was that the arrays were NOT being cleared for the detached shading surfaces, so the previous values were retained when the Sun was down, because FigureSolarBeamAtTimestep returns early in that case
Original regression appeared in v9.6.0, and is due to #8820
Specifically the commit, the change in SolarShading.cc c10a756#diff-18fbb8a5b1334b62513de2441bdb4bb4ef1fbe5fbec24d5dd0effcd019dfc33dR4795
Before, the ObjexxFCL assignment was used to reset everything this commit changed the initialization to explicit loop variables, BUT it does it only for heat transfer surfaces linked to a zone/space, and no longer does it for detached shading surfaces.
Pull Request Author
Reviewer