Skip to content

Object count summary Fixes - #11778

Open
jmarrec wants to merge 4 commits into
developfrom
ObjectCountSummary
Open

Object count summary Fixes#11778
jmarrec wants to merge 4 commits into
developfrom
ObjectCountSummary

Conversation

@jmarrec

@jmarrec jmarrec commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Pull request overview

In a recent PR where I added new Sub Surface Types, I realized we're double counting a few objects #5517 (comment)

I'm about to add some new "Window" surface types (FixedWindow, OperableWindow, Skylight map to Window, and OverheadDoor to Door) and I'm realizing that this basically double counts a GlassDoor/TDD_Diffuser as both a Window and itself.

Description of the purpose of this PR

  • Fix the existing double-counting bug in Object Count Summary report: GlassDoor and TDD_Diffuser objects were being counted both as their own type and as "Window"
  • since new sub surface types (FixedWindow/OperableWindow/Skylight → Window, OverheadDoor → Door) now map by OriginalClass, also drops the legacy "Window" bucket from the report (remapped to "Fixed Window", kept only for backward compat)
  • and adds a unit test covering the refactor

Pull Request Author

  • Title of PR should be user-synopsis style (clearly understandable in a standalone changelog context)
  • Label the PR with at least one of: Defect, Refactoring, NewFeature, Performance, and/or DoNoPublish
  • Pull requests that impact EnergyPlus code must also include unit tests to cover enhancement or defect repair
  • Author should provide a "walkthrough" of relevant code changes using a GitHub code review comment process
  • If any diffs are expected, author must demonstrate they are justified using plots and descriptions
  • If changes fix a defect, the fix should be demonstrated in plots and descriptions
  • If any defect files are updated to a more recent version, upload new versions here or on DevSupport
  • If IDD requires transition, transition source, rules, ExpandObjects, and IDFs must be updated, and add IDDChange label
  • If structural output changes, add to output rules file and add OutputChange label
  • If adding/removing any LaTeX docs or figures, update that document's CMakeLists file dependencies
  • If adding/removing any output files (e.g., eplustbl.*)
    • Update ..\scripts\Epl-run.bat
    • Update ..\scripts\RunEPlus.bat
    • Update ..\src\EPLaunch\ MainModule.bas, epl-ui.frm, and epl.vbp (VersionComments)
    • Update ...github\workflows\energyplus.py

Reviewer

  • Perform a Code Review on GitHub
  • If branch is behind develop, merge develop and build locally to check for side effects of the merge
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified
  • Check that performance is not impacted (CI Linux results include performance check)
  • Run Unit Test(s) locally
  • Check any new function arguments for performance impacts
  • Verify IDF naming conventions and styles, memos and notes and defaults
  • If new idf included, locally check the err file and other outputs

@jmarrec jmarrec added the Defect Includes code to repair a defect in EnergyPlus label Sep 3, 2026
Comment on lines -3836 to -3838
// TEST_F( EnergyPlusFixture, FinAndOverhangCount )
//{
//// based on 4ZoneWithShading_Simple_2.idf

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Remove tests that were commented out 9 years ago and are WAAAAAY too broad to be useful (they have full HVAC systems etc)

Comment on lines -1342 to -1348
if (surface.Class == DataSurfaces::SurfaceClass::Window) {
if (surface.OriginalClass == DataSurfaces::SurfaceClass::GlassDoor || surface.OriginalClass == DataSurfaces::SurfaceClass::TDD_Diffuser) {
++numSurfaces((int)surface.OriginalClass);
if (isExterior) {
++numExtSurfaces((int)surface.OriginalClass);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The double counting was here, since it was already doing ++numExtSurfaces(Window) when OriginalClass is TDD_Diffuser

Comment on lines +1336 to +1348
// Object Count Summary
// This is only for unit tests: on a real run, GetSurfaceData will unconditionally do OriginalClass = Class
assert(surface.OriginalClass < DataSurfaces::SurfaceClass::Num);
assert(surface.OriginalClass > DataSurfaces::SurfaceClass::None);

DataSurfaces::SurfaceClass currSurfaceClass = surface.OriginalClass;
if (currSurfaceClass == DataSurfaces::SurfaceClass::Window) {
currSurfaceClass = DataSurfaces::SurfaceClass::FixedWindow;
}
if (surface.Class == DataSurfaces::SurfaceClass::Window) {
if (surface.OriginalClass == DataSurfaces::SurfaceClass::GlassDoor || surface.OriginalClass == DataSurfaces::SurfaceClass::TDD_Diffuser) {
++numSurfaces((int)surface.OriginalClass);
if (isExterior) {
++numExtSurfaces((int)surface.OriginalClass);
}
}
int const iCurrSurfaceClass = static_cast<int>(currSurfaceClass);
++numSurfaces(iCurrSurfaceClass);
if (isExterior) {
++numExtSurfaces(iCurrSurfaceClass);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In Object Count Summary, report by OriginalClass and avoid double counting GlassDoor/TDD_Diffuser

Comment on lines +1342 to 1344
if (currSurfaceClass == DataSurfaces::SurfaceClass::Window) {
currSurfaceClass = DataSurfaces::SurfaceClass::FixedWindow;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Window as an IDD type was kept only for backward compat, when reporting, we treat it as "Fixed Window"

Comment on lines +1460 to +1474
// IDD Entry / OriginalClass == Window is kept for backward compatibility only and is treated as a FixedWindow
assert(numSurfaces(int(DataSurfaces::SurfaceClass::Window)) == 0);
assert(numExtSurfaces(int(DataSurfaces::SurfaceClass::Window)) == 0);
OutputReportPredefined::PreDefTableEntry(
state, state.dataOutRptPredefined->pdchSurfCntTot, "Fixed Window", numSurfaces(int(DataSurfaces::SurfaceClass::FixedWindow)));
OutputReportPredefined::PreDefTableEntry(
state, state.dataOutRptPredefined->pdchSurfCntExt, "Fixed Window", numExtSurfaces(int(DataSurfaces::SurfaceClass::FixedWindow)));
OutputReportPredefined::PreDefTableEntry(
state, state.dataOutRptPredefined->pdchSurfCntTot, "Operable Window", numSurfaces(int(DataSurfaces::SurfaceClass::OperableWindow)));
OutputReportPredefined::PreDefTableEntry(
state, state.dataOutRptPredefined->pdchSurfCntTot, "Window", numSurfaces(int(DataSurfaces::SurfaceClass::Window)));
state, state.dataOutRptPredefined->pdchSurfCntExt, "Operable Window", numExtSurfaces(int(DataSurfaces::SurfaceClass::OperableWindow)));
OutputReportPredefined::PreDefTableEntry(
state, state.dataOutRptPredefined->pdchSurfCntExt, "Window", numExtSurfaces(int(DataSurfaces::SurfaceClass::Window)));
state, state.dataOutRptPredefined->pdchSurfCntTot, "Skylight", numSurfaces(int(DataSurfaces::SurfaceClass::Skylight)));
OutputReportPredefined::PreDefTableEntry(
state, state.dataOutRptPredefined->pdchSurfCntExt, "Skylight", numExtSurfaces(int(DataSurfaces::SurfaceClass::Skylight)));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Report the OriginalClass ones

@@ -7691,13 +5574,15 @@ TEST_F(EnergyPlusFixture, InteriorSurfaceEnvelopeSummaryReport)
state->dataSurface->Surface(i).Name = "Interzonal_Wall_" + std::to_string((i + 1) / 2);
state->dataSurface->Surface(i).GrossArea = 200.;
state->dataSurface->Surface(i).Class = DataSurfaces::SurfaceClass::Wall;
state->dataSurface->Surface(i).OriginalClass = state->dataSurface->Surface(i).Class;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As for why I am modifiying the tests: these are explicitly construction Surface objects and were not setting OriginalClass, so they hit the assert(surface.OriginalClass > DataSurfaces::SurfaceClass::None)

This is only for unit tests: on a real run, GetSurfaceData will unconditionally do OriginalClass = Class

Comment on lines +5439 to +5544

state->dataHeatBal->space.allocate(1);
state->dataHeatBal->Zone.allocate(1);
state->dataHeatBal->Zone(1).Multiplier = 1;
state->dataHeatBal->Zone(1).ListMultiplier = 1;

state->dataConstruction->Construct.allocate(2);
state->dataConstruction->Construct(1).Name = "Glazing Construction";
// Avoid triggering CalcNominalWindowCond
state->dataConstruction->Construct(1).SummerSHGC = 0.70;
state->dataConstruction->Construct(1).VisTransNorm = 0.80;
state->dataConstruction->Construct(2).Name = "Opaque Construction";

state->dataHeatBal->NominalU.allocate(2);
state->dataHeatBal->NominalU(1) = 2.0;
state->dataHeatBal->NominalU(2) = 0.2;

struct SurfaceSpec
{
std::string name;
DataSurfaces::SurfaceClass Class;
DataSurfaces::SurfaceClass OriginalClass;
int construction;
};
// clang-format off
std::vector<SurfaceSpec> const specs{
{"Wall_1", DataSurfaces::SurfaceClass::Wall, DataSurfaces::SurfaceClass::Wall, 2},
{"Window_1", DataSurfaces::SurfaceClass::Window, DataSurfaces::SurfaceClass::Window, 1}, // legacy IDD choice
{"FixedWindow_1", DataSurfaces::SurfaceClass::Window, DataSurfaces::SurfaceClass::FixedWindow, 1},
{"OperableWindow_1",DataSurfaces::SurfaceClass::Window, DataSurfaces::SurfaceClass::OperableWindow,1},
{"Skylight_1", DataSurfaces::SurfaceClass::Window, DataSurfaces::SurfaceClass::Skylight, 1},
{"GlassDoor_1", DataSurfaces::SurfaceClass::Window, DataSurfaces::SurfaceClass::GlassDoor, 1},
{"TDDDiffuser_1", DataSurfaces::SurfaceClass::Window, DataSurfaces::SurfaceClass::TDD_Diffuser, 1},
{"TDDDome_1", DataSurfaces::SurfaceClass::TDD_Dome, DataSurfaces::SurfaceClass::TDD_Dome, 1},
{"Door_1", DataSurfaces::SurfaceClass::Door, DataSurfaces::SurfaceClass::Door, 2},
{"OverheadDoor_1", DataSurfaces::SurfaceClass::Door, DataSurfaces::SurfaceClass::OverheadDoor, 2},
};
// clang-format on

int const numSurfs = static_cast<int>(specs.size());
state->dataSurface->TotSurfaces = numSurfs;
state->dataSurface->Surface.allocate(numSurfs);
state->dataSurface->SurfaceWindow.allocate(numSurfs);
SurfaceGeometry::AllocateSurfaceWindows(*state, numSurfs);

int const wallSurfNum = 1; // "Wall_1", used as the BaseSurf for every subsurface below
for (int i = 1; i <= numSurfs; ++i) {
auto &surface = state->dataSurface->Surface(i);
auto const &spec = specs[i - 1];
surface.Name = spec.name;
surface.Class = spec.Class;
surface.OriginalClass = spec.OriginalClass;
surface.Construction = spec.construction;
surface.HeatTransSurf = true;
surface.ExtBoundCond = ExternalEnvironment;
surface.GrossArea = 10.0;
surface.Tilt = 90.0;
surface.Zone = 1;
surface.spaceNum = 1;
state->dataSurface->AllSurfaceListReportOrder.push_back(i);
if (i != wallSurfNum) {
surface.BaseSurf = wallSurfNum;
surface.BaseSurfName = specs[wallSurfNum - 1].name;
}
}
// TDD:Dome subsurfaces act as their own base surface (see SurfaceGeometry.cc)
state->dataSurface->Surface(8).BaseSurf = 8;

HeatBalanceSurfaceManager::GatherForPredefinedReport(*state);

auto &dORP = state->dataOutRptPredefined;
// The legacy "Window" is treated as a FixedWindow for backward compatibility, so its count is folded
// in with the true FixedWindow -- and the generic "Window" row is never created at all.
EXPECT_EQ("NOT FOUND", OutputReportPredefined::RetrievePreDefTableEntry(*state, dORP->pdchSurfCntTot, "Window"));
EXPECT_EQ("NOT FOUND", OutputReportPredefined::RetrievePreDefTableEntry(*state, dORP->pdchSurfCntExt, "Window"));

struct ExpectedCount
{
std::string rowName;
int count;
};
std::vector<ExpectedCount> const expected{
{"Wall", 1},
{"Fixed Window", 2}, // legacy "Window_1" + "FixedWindow_1"
{"Operable Window", 1},
{"Skylight", 1},
{"Glass Door", 1},
{"Tubular Daylighting Device Diffuser", 1},
{"Tubular Daylighting Device Dome", 1},
{"Door", 1},
{"Overhead Door", 1},
};
for (auto const &e : expected) {
EXPECT_EQ(std::to_string(e.count), OutputReportPredefined::RetrievePreDefTableEntry(*state, dORP->pdchSurfCntTot, e.rowName))
<< "Row = " << e.rowName;
// All surfaces above are exterior, so Outdoors count should match Total count
EXPECT_EQ(std::to_string(e.count), OutputReportPredefined::RetrievePreDefTableEntry(*state, dORP->pdchSurfCntExt, e.rowName))
<< "Row = " << e.rowName;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

new Unit test

…nting GlassDoor/TDD_Diffuser

Fix a bug: the original code basically double counted a GlassDoor/TDD_Diffuser as both a Window and itself.

see #5517 (comment)

As for why I am modifiying the tests: these are explicitly construction Surface objects and were not setting OriginalClass, so they hit the `assert(surface.OriginalClass > DataSurfaces::SurfaceClass::None)`
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

⚠️ Regressions detected on macos-14 for commit 1fe6dac

Regression Summary
  • Audit: 81
  • Table Big Diffs: 71

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

⚠️ Regressions detected on macos-14 for commit e095ab7

Regression Summary
  • Audit: 818
  • Table Big Diffs: 684

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

⚠️ Regressions detected on ubuntu-24.04 for commit e095ab7

Regression Summary
  • Audit: 822
  • Table Big Diffs: 686

@jmarrec
jmarrec requested review from mitchute and a lite review from Copilot September 8, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The logic change is narrowly scoped to counting/reporting, is covered by a targeted unit test, and no functional regressions were identified in the reviewed diff.

Pull request overview

Fixes Object Count Summary reporting so fenestration/door subtypes that are internally remapped to Window/Door for calculations are counted exactly once (by OriginalClass), eliminating prior double-counting (e.g., GlassDoor, TDD_Diffuser) and updating the reported “Window” bucket behavior.

Changes:

  • Refactors Object Count Summary counting to use Surface.OriginalClass (with legacy Window mapped into FixedWindow) and adds explicit rows for Fixed Window, Operable Window, Skylight, and Overhead Door.
  • Adds a focused unit test verifying all remapped surface classes are counted once and that the generic “Window” row is not produced.
  • Updates existing unit-test surface setups to populate OriginalClass so they reflect production behavior.
File summaries
File Description
tst/EnergyPlus/unit/OutputReportTabular.unit.cc Adds/updates unit tests to validate the new Object Count Summary behavior and sets OriginalClass in test-created surfaces.
src/EnergyPlus/HeatBalanceSurfaceManager.cc Updates Object Count Summary logic to count by OriginalClass, remap legacy Window to FixedWindow, and emit new/renamed summary rows.
Review details
  • Files reviewed: 1/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1336 to +1338
// Object Count Summary
// This is only for unit tests: on a real run, GetSurfaceData will unconditionally do OriginalClass = Class
assert(surface.OriginalClass < DataSurfaces::SurfaceClass::Num);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Defect Includes code to repair a defect in EnergyPlus

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants