-
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 overlapping shadow calculations for shading surface transmittance >0 and <1 #10040
Conversation
Looking back in the archives, this same issue was addressed long ago. Found 2 relevant commits.
https://github.com/NREL/EnergyPlusArchive/commit/56e08a151128f55417e888b47dbf6b0ed4a9af29 added some more logic:
This almost matches what's currently in
The logic was rearranged slightly in da5f9fd#diff-18fbb8a5b1334b62513de2441bdb4bb4ef1fbe5fbec24d5dd0effcd019dfc33dL3216. So, the question becomes, what's wrong with this logic? |
src/EnergyPlus/SolarShading.cc
Outdated
Real64 const HCT_1 = state.dataSolarShading->HCT(NS1); | ||
Real64 const HCT_2 = state.dataSolarShading->HCT(NS2); | ||
Real64 HCT_3 = HCT_1 * HCT_2; | ||
if (HCT_2 != 1.0 && HCT_1 != 1.0) { |
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.
Isn't this the same as if (HCT_3 != 1.0)
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.
Hmm, at HCT_1 and HCT_2 = 1, HCT_3 = (HCT_1 * HCT_2) = 1
and HCT_3 = (HCT_1 + HCT_2) - (HCT_1 * HCT_2) = 1
. And at all other transmittances, HCT_3 = (HCT_1 + HCT_2) - (HCT_1 * HCT_2)
. Why the need for transmittance checking?
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 compared this branch to this and there are diffs. I assume it is because either HCT_1 or HCT_2 is 1 but not both where (1 + x) - (1 * x) = 1 and would have been x before. If HCT_1 and HCT_2 are 1 and 0.5, the answer is 0.5. If 0.999999 and 0.5 are used, the answer is 0.9999995. Why is that?
Real64 const HCT_1 = state.dataSolarShading->HCT(NS1);
Real64 const HCT_2 = state.dataSolarShading->HCT(NS2);
state.dataSolarShading->HCT(NS3) = (HCT_1 + HCT_2) - (HCT_1 * HCT_2);
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.
This same function gets used for a variety of cases including: two overlapping shadows on an exterior surface, and shadows that penetrate a window onto an interior surface (for full interior solar dist), and some special overlaps that force once of the HCT_* values to 1.0 in the function parameters which I think is intended to just calculate the overlap area and not care about the transmittance.
Real64 HCT_3 = HCT_1 * HCT_2;
if (HCT_2 != 1.0 && HCT_1 != 1.0) {
HCT_3 = (HCT_1 + HCT_2) - HCT_3;
}
The overlap transmittance calc always needs the product of HCT_1 * HCT_2
.
If either HTC_2 == 1.0 OR HCT_3 == 1.0
, then we're done and skip the more complex calc.
But if they are both != 1.0
then we're doing overlapping shadows and we need the second expression. I originally tried just doing the full expression without checking for 1.0 and it introduced unwanted diffs that I can't fully explain.
I suppose zero is another special case, if either HCT_1 or HCT_2
is zero, then HCT_3 = the nonzero one
So maybe there's a more efficient way to code this. This gets called frequently, so it's worth optimizing.
Here's the before and after using the defect files 1ZoneUncontrolled_Win_AddOverhang and 1ZoneUncontrolled_Win_AddOverhangAndSouthShadeScheduled. For the base wall on the winter day, with develop the sunlit area flattens out mid-day at 0.8 (the south shade transmittance value), ignoring the impact of the overhang. With this branch, the impact of both overhang and partially transmitting shade are accounted for. For the window on the winter day, the overhang has a tiny impact, so develop and this branch both show the correct result of 0.8 from the south shade. For both the wall and the window, on the summer day, with develop adding the scheduled south shade increases the sunlit fraction instead of decreasing it. This branch shows the impact of both. |
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.
Here's a walk-thru. I'll merge in (fast-moving) develop once again and push for final reviews.
Requesting reviews from @rraustad, @Myoldmopar, @vidanovic, and @nealkruis
// Check for exceeding array limits. | ||
if (NS3 > state.dataSolarShading->MaxHCS) { |
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.
Note this if at the top of the function which throws a warning and return
s. There were two places further down that checked the same condition (or the opposite) which have been removed now, because they were moot or even dead.
if (NV3 < state.dataSolarShading->MaxHCV && NS3 <= state.dataSolarShading->MaxHCS) { | ||
if (NV3 < state.dataSolarShading->MaxHCV) { |
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 the second condition was false, we would have returned already up above.
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.
This also looks correct.
@@ -4791,26 +4785,6 @@ void DeterminePolygonOverlap(EnergyPlusData &state, | |||
state.dataSolarShading->TrackTooManyVertices(state.dataSolarShading->NumTooManyVertices).SurfIndex2 = | |||
state.dataSolarShading->CurrentSurfaceBeingShadowed; | |||
} | |||
|
|||
} else if (NS3 > state.dataSolarShading->MaxHCS) { |
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.
This entire else if
duplicates the block at the top of the function. This is dead code.
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.
NS3 is const so that didn't change. As long as MaxHCS hasn't changed (from a call in this function like ClipPoly?) then I agree.
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.
MaxHCS
is a user input in ShadowCalculation "Maximum Figures in Shadow Overlap Calculations". It's never changed, just used to set some array sizes and then checked against in multiple places.
if (state.dataSolarShading->HCAREA(NS1) * state.dataSolarShading->HCAREA(NS2) > 0.0) | ||
if (state.dataSolarShading->HCAREA(NS1) * state.dataSolarShading->HCAREA(NS2) > 0.0) { |
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.
Just changing a one-line if to multi-line for clarity.
} else if (NV3 > state.dataSolarShading->MaxHCV) { | ||
} else { |
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.
This if is checking the number of vertices (not MaxHCS
) but a simple else is adequate here.
if (HCT_2 == 1.0 || HCT_1 == 1.0) { | ||
state.dataSolarShading->HCT(NS3) = HCT_1 * HCT_2; | ||
} else { | ||
// Determine transmission of overlap which corrects for prior shadows | ||
// The resulting transmission of overlapping shadows is HCT_1 * HCT_2 | ||
// Shadows with HCT_1 and HCT_2 have already been applied in the overlapping area | ||
// so the correction is the difference between (HCT_1+HCT_2) and (HCT_1*HCT_2) | ||
state.dataSolarShading->HCT(NS3) = (HCT_1 + HCT_2) - HCT_1 * HCT_2; |
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.
Here's the heart of the fix.
int FINSHC; // HC location of first back surface overlap | ||
int FRVLHC; // HC location of first reveal surface | ||
int FSBSHC; // HC location of first subsurface | ||
int FBKSHC = 0; // HC location of first back surface |
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.
Added some missing initial values that were the root cause of differing unit test results on linux and windows. The unit tests were also updated.
@@ -3973,3 +3974,1055 @@ TEST_F(EnergyPlusFixture, ShadowCalculation_CSV) | |||
EXPECT_EQ(expected_values, stream_str); | |||
} | |||
} | |||
|
|||
TEST_F(EnergyPlusFixture, SolarShadingTest_PolygonOverlap) |
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.
Three new unit tests.
SolarShadingTest_PolygonOverlap
- tests similar to the 1Zone defect file with an overhang and a tree.
SolarShadingTest_PolygonOverlap2
- tests two large south shades that both fully shade the wall and window, one behind the other. The expected results are simply the product of the transmittances.
SolarShadingTest_PolygonOverlap3
- tests three large south shades, just to be sure the method works for more overlaps.
In case you're interested, here's the test failures with develop:
PR10040-ShadingTransmittance-UnitTestFailureswithDevelop.txt
\end{equation} | ||
|
||
The actual $\tau$ of overlapping polygons i and j is the product of $\tau$\(_{i}\) and $\tau$\(_{j}\). For example, if a wall is fully shaded by two partially transparent shades, one with a transmittance of 0.8 and one with a transmittance of 0.5, the resulting sunlit fraction would be $0.8 * 0.5 = 0.4$. Because each shadow is first applied with its own transmittance, the transmittance for the overlap in the summation is the difference between the sum and the product of the shadow transmittances: | ||
|
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.
This is the only issue I have with these changes. And is the fact that I can't resolve this math. The first sentance gives the correct answer of 0.4 and I agree with that transmittance calculation. The second sentance defines that calculation as (0.8+0.5)-(0.8*0.5)=0.9. The figure you show in the PR discussion looks correct but I can't get past this math. I guess somewhere after this calculation there is more calculatioins that use the original (0.8+0.5)=1.3 which is then reduced by the result here of 0.9 giving 0.4 as the final answer. Could you show that same figure using just (HCT_1*HCT_2) as the result (or add it to the figure)? At least then, if the result looks incorrect, I could sleep. I did look at the unit test and need to look again for the sunlit area fraction of the "overlap 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.
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.
You can't argue with a spreadsheet! In the derivation spreadsheet, seeing a "transmittance" of 0.88 when it is obviously 0.32 is mind blowing. An area is not being calculated, nor is a transmittance. The sunlit area fraction is being calculated. So:
What you have already said in the docs, plus: "The overlapped area (transmittance?) correction factor used to calculate sunlit area fraction is derived from the individual surface transmittance values as (your formula)".
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.
or, "When shading surfaces overlap, the sunlit area fraction is calculated using the shading surface transmittances and a correction for the overlapping shaded area. The area weighted sunlit area fraction is calculated as the unshaded area, plus shading area 1 multiplied by shading 1 surface transmittance, plus shading area 2 multiplied by shading 2 surface transmittance (note here the overlapped area has been included twice), plus the overlapped area multiplied by a correction factor of ()." you get the idea. Whatever you like, just don't call the overlap multiplier a transmittance.
\begin{longtable}[c]{@{}ll@{}} | ||
\caption{Surface / Area Characteristic / Convention \label{table:surface-area-characteristic-convention}} \tabularnewline | ||
Each shadow's area is subtracted from the receiving surface and so on through multiple overlaps where the sign of the overlap area is the product of the signs of the overlapping areas. For the shadows in Figure~\ref{fig:multiple-shadow-overlaps}, start with the receiving surface area A, subtract shadow area B, then subtract shadow area C. Because shadows B and C overlap, the overlap area D has been subtracted twice, so add back area D to get the final sunlit area on the receiving surface. | ||
|
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 understand the math now. The key is the sign convention when adding in the area * transmittance values. The 2 shadows "area * trans" are "subtracted" from the total area. That winds up being a (1 - HCT_1 + HCT_2) term for the overlap. Then the overlap is "added" back to that value using the difference of applied trans - actual trans which gets back to actual trans as (1 - HCT_1 + HCT_2) + (HCT_1 + HCT_2) - (HCT_1 * HCT_2) for the overlap. This yields (1 - (HCT_1 * HCT_2)) as the sunlit fraction for the overlap area. This is the original equation in the model. But that is not what needs to get added back as @mjwitte has shown. The overlap area needs to add back (HCT_1 + HCT_2) - (HCT_1 * HCT_2) to get back to the correct value which @mjwitte has said multiple times. This model therefore calculates the resulting sunlit area fraction based on shading surface transmittances.
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.
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.
@rraustad Welcome to my rabbit hole! I'm working on some doc additions, including sample calculations a la spreadsheet.
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 did make sure to check the other extreme here with overlapped shades with area 19.9999 with miniscule NON overlap (D = 19.9998) and expected a result of 0.49. The actual result was 0.490002.
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.
Everything in the branch makes sense. Whether doc updates are needed or not is left to the developer.
@mjwitte I do not see any changes in sun patches coming through the windows which is good. |
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.
Suppose for my window tests I should add a file that will have exterior overhang so that incoming rays are tested as well. As already said, I do not see changes at the interior patch distributions and that is good.
Unit tests run successfully. @Myoldmopar this is ready to merge. |
I'm just doing a quick local build and test of this, and if it's all clean I'll merge it next. |
Everything is happy, merging. Thanks @mjwitte . As an aside, this set of CI results on develop should be back to all clean except the SSC Battery failure on the debug build, which may be commented out soon because it's not helpful just always failing. |
Hi, does this also fix this? |
Yes, it does! Here's plot of the results for the roof shaded just by one flat shade at 0.1 transmittance, compared to the roof with the 0.1 flat shade plus a 0.1 transmittance box above it, with an expected shading fraction of 0.01 when the sun is overhead (the shading surfaces are slightly above the roof, so, some solar gets in from the side). You can see how v23.1 was clearly wrong (more solar with the added shades), and v23.2 shows the expected result of 0.1x0.1=0.01 sunlit fraction. Also tested with other transmittances of 0.9 and 0.55. |
Pull request overview
SolarShading::DeterminePolygonOverlap
.SolarShading.hh
.Diffs
Diffs are expected for all test files based on SolarShadingTest and SurfaceTest, because these have shading surfaces with a transmittance schedule.
eio diffs occur even in the files that use imported surface shading, because the sky diffuse view factors reported in the eio are impacted by the fix.
The theory
When overlapping shadows impact a surface, the surface sunlit area is calculated by subtracting each shadow, and then adding back the area of each overlap to avoid subtracting the area twice. This works great for opaque shadows, but for partially transmitting shadows, the transmittance of the overlap was the product
Tau1*Tau2
. That sounds correct, because that's what you get with two overlapping shades, of say 50% and 20% transmittance, you should get 10% after passing through both. But that's what the final result should be, not the value for the correcting term in the overlap area. Here's a screen shot from a spreadsheet that shows derivation of the new formula and some check calculations (attached if you want to play with values: 10022 Shading Frac Details.xlsx).The updated Engineering Reference section "Overlapping Shadows" offers another explanation of this. Changes begin after Figure 5.11: Complex Overlapping Condition.
Pull Request Author
Add to this list or remove from it as applicable. This is a simple templated set of guidelines.
If IDD requires transition, transition source, rules, ExpandObjects, and IDFs must be updated, and add IDDChange labelIf structural output changes, add to output rules file and add OutputChange labelIf adding/removing any LaTeX docs or figures, update that document's CMakeLists file dependenciesReviewer
This will not be exhaustively relevant to every PR.