Skip to content
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 #10857 - Report System Summary:Thermostat Schedules depends on order of ZoneControl:Thermostat control types #10861

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from

Conversation

jmarrec
Copy link
Contributor

@jmarrec jmarrec commented Dec 17, 2024

Pull request overview

Pull Request Author

Add to this list or remove from it as applicable. This is a simple templated set of guidelines.

  • 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

Reviewer

This will not be exhaustively relevant to every PR.

  • 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 Dec 17, 2024
@jmarrec jmarrec self-assigned this Dec 17, 2024
@jmarrec jmarrec force-pushed the 10857_ReportSystemSummaryThermostats branch from 2bdd43a to c6f493a Compare December 17, 2024 14:08
@jmarrec jmarrec force-pushed the 10857_ReportSystemSummaryThermostats branch from c6f493a to 15fc6e5 Compare December 17, 2024 14:18
Comment on lines +1863 to +1864
EXPECT_EQ("SINGLEHEATINGSCH", RetrievePreDefTableEntry(*state, orp.pdchStatSchdHeatName, "zoneA"));
EXPECT_EQ("NOT FOUND", RetrievePreDefTableEntry(*state, orp.pdchStatSchdCoolName, "zoneA"));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Existing test was incorrect, cf #10857 (comment)

Comment on lines +1870 to +1871
EXPECT_EQ("NOT FOUND", RetrievePreDefTableEntry(*state, orp.pdchStatSchdHeatName, "zoneB"));
EXPECT_EQ("SINGLECOOLINGSCH", RetrievePreDefTableEntry(*state, orp.pdchStatSchdCoolName, "zoneB"));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same

Comment on lines +1887 to +1953

TEST_F(EnergyPlusFixture, FillPredefinedTableOnThermostatSchedules_MultipleControls)
{
using namespace EnergyPlus::OutputReportPredefined;

state->dataScheduleMgr->Schedule.allocate(6);
constexpr int SingleHeatingSchIndex = 1;
constexpr int SingleCoolingSchIndex = 2;
constexpr int SingleHeatCoolSchIndex = 3;
constexpr int DualSetPointWDeadBandHeatSchIndex = 4;
constexpr int DualSetPointWDeadBandCoolSchIndex = 5;
constexpr int CTSchedIndex = 6;
state->dataScheduleMgr->Schedule(SingleHeatingSchIndex).Name = "SINGLEHEATINGSCH";
state->dataScheduleMgr->Schedule(SingleCoolingSchIndex).Name = "SINGLECOOLINGSCH";
state->dataScheduleMgr->Schedule(SingleHeatCoolSchIndex).Name = "SINGLEHEATCOOLSCH";
state->dataScheduleMgr->Schedule(DualSetPointWDeadBandHeatSchIndex).Name = "DUALSETPOINTWDEADBANDHEATSCH";
state->dataScheduleMgr->Schedule(DualSetPointWDeadBandCoolSchIndex).Name = "DUALSETPOINTWDEADBANDCOOLSCH";
state->dataScheduleMgr->Schedule(CTSchedIndex).Name = "CONTROL SCHEDULE";

state->dataScheduleMgr->ScheduleInputProcessed = true;

auto &orp = *state->dataOutRptPredefined;
auto &dzc = *state->dataZoneCtrls;

SetPredefinedTables(*state);

constexpr int NumControlTypes = 4;
dzc.NumTempControlledZones = NumControlTypes;
dzc.TempControlledZone.allocate(dzc.NumTempControlledZones);

// [1, 2, 3, 4]
std::vector<int> order(NumControlTypes);
std::iota(order.begin(), order.end(), 1);
for (size_t i = 0; i < order.size(); ++i) {
char zoneLetter = char(int('A') + i);
// Simple left rotate: [2, 3, 4, 1], etc
std::rotate(order.begin(), std::next(order.begin()), order.end());
auto &tcz = dzc.TempControlledZone(i + 1);

const std::string ZoneName = fmt::format("ZONE {}", zoneLetter);
tcz.ZoneName = ZoneName;
tcz.Name = fmt::format("TSTAT {}", zoneLetter);
tcz.ControlTypeSchedName = state->dataScheduleMgr->Schedule(CTSchedIndex).Name;
tcz.CTSchedIndex = CTSchedIndex;
tcz.NumControlTypes = NumControlTypes;
tcz.ControlTypeEnum.allocate(NumControlTypes);
tcz.ControlTypeName.allocate(NumControlTypes);

tcz.ControlTypeEnum(order.at(0)) = HVAC::ThermostatType::SingleHeating;
tcz.ControlTypeName(order.at(0)) = "SINGLEHEATING CTRL";
tcz.SchIndx_SingleHeatSetPoint = SingleHeatingSchIndex;

tcz.ControlTypeEnum(order.at(1)) = HVAC::ThermostatType::SingleCooling;
tcz.ControlTypeName(order.at(1)) = "SINGLECOOLING CTRL";
tcz.SchIndx_SingleCoolSetPoint = SingleCoolingSchIndex;

tcz.ControlTypeEnum(order.at(2)) = HVAC::ThermostatType::SingleHeatCool;
tcz.ControlTypeName(order.at(2)) = "SINGLEHEATCOOL CTRL";
tcz.SchIndx_SingleHeatCoolSetPoint = SingleHeatCoolSchIndex;

tcz.ControlTypeEnum(order.at(3)) = HVAC::ThermostatType::DualSetPointWithDeadBand;
tcz.ControlTypeName(order.at(3)) = "DUALSETPOINTWITHDEADBAND CTRL";
tcz.SchIndx_DualSetPointWDeadBandHeat = DualSetPointWDeadBandHeatSchIndex;
tcz.SchIndx_DualSetPointWDeadBandCool = DualSetPointWDeadBandCoolSchIndex;
}

FillPredefinedTableOnThermostatSchedules(*state);
Copy link
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.

I define ZoneControlThermostats with each having all of these:

  • ThermostatSetpoint:SingleHeating
  • ThermostatSetpoint:SingleCooling
  • ThermostatSetpoint:SingleHeatingOrCooling
  • ThermostatSetpoint:DualSetpoint

I test 4 different orders of declaration (I use a left rotate), since it seems the wanted feature on the #10857 was it shouldn't differ no matter in which order the control types were defined.


ZONE A
i=0, order=[2, 3, 4, 1]
SingleCooling, SingleHeatCool, DualSetPointWithDeadBand, SingleHeating, 

ZONE B
i=1, order=[3, 4, 1, 2]
SingleHeatCool, DualSetPointWithDeadBand, SingleHeating, SingleCooling, 

ZONE C
i=2, order=[4, 1, 2, 3]
DualSetPointWithDeadBand, SingleHeating, SingleCooling, SingleHeatCool, 

ZONE D
i=3, order=[1, 2, 3, 4]
SingleHeating, SingleCooling, SingleHeatCool, DualSetPointWithDeadBand, 

Comment on lines +1955 to +1972
for (size_t i = 0; i < order.size(); ++i) {
char zoneLetter = char(int('A') + i);
const std::string ZoneName = fmt::format("ZONE {}", zoneLetter);
EXPECT_EQ(fmt::format("TSTAT {}", zoneLetter), RetrievePreDefTableEntry(*state, orp.pdchStatName, ZoneName)) << "Failed for " << ZoneName;
EXPECT_EQ("CONTROL SCHEDULE", RetrievePreDefTableEntry(*state, orp.pdchStatCtrlTypeSchd, ZoneName)) << "Failed for " << ZoneName;
EXPECT_EQ("DualSetPointWithDeadBand, SingleCooling, SingleHeatCool, SingleHeating",
RetrievePreDefTableEntry(*state, orp.pdchStatSchdType1, ZoneName))
<< "Failed for " << ZoneName;
EXPECT_EQ("DUALSETPOINTWITHDEADBAND CTRL, SINGLECOOLING CTRL, SINGLEHEATCOOL CTRL, SINGLEHEATING CTRL",
RetrievePreDefTableEntry(*state, orp.pdchStatSchdTypeName1, ZoneName))
<< "Failed for " << ZoneName;
EXPECT_EQ("DUALSETPOINTWDEADBANDHEATSCH, SINGLEHEATCOOLSCH, SINGLEHEATINGSCH",
RetrievePreDefTableEntry(*state, orp.pdchStatSchdHeatName, ZoneName))
<< "Failed for " << ZoneName;
EXPECT_EQ("DUALSETPOINTWDEADBANDCOOLSCH, SINGLECOOLINGSCH, SINGLEHEATCOOLSCH",
RetrievePreDefTableEntry(*state, orp.pdchStatSchdCoolName, ZoneName))
<< "Failed for " << ZoneName;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Order stays the same.

I join the names when multiple found with a ", "

Comment on lines +7026 to +7042
// Helper struct so we can sort to ensure a consistent order.
// No matter the order in which the multiple Field Sets (Control Object Type, Control Name), the same thing is reported to the tabular outputs
struct ControlTypeInfo
{
// HVAC::ThermostatType tType = HVAC::ThermostatType::Invalid;
std::string thermostatType;
std::string controlTypeName;
std::string heatSchName;
std::string coolSchName;

// Only need the operator<, and we use C++17 so I can't use a defaulted 3-way operator<=>
bool operator<(const ControlTypeInfo &other) const
{
return std::tie(this->thermostatType, this->controlTypeName, this->heatSchName, this->coolSchName) <
std::tie(other.thermostatType, other.controlTypeName, other.heatSchName, other.coolSchName);
}
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A struct so we can collect the multiple control types, and sort them, so the order doesn't matter.

Comment on lines +7043 to +7056
using ControlTypeInfoMemPtr = std::string ControlTypeInfo::*;

auto joinStrings = [](const std::vector<ControlTypeInfo> &infos, ControlTypeInfoMemPtr memPtr) -> std::string {
std::vector<std::string> result;
result.reserve(infos.size());
for (const auto &info : infos) {
std::string val = info.*memPtr;
if (val.empty()) {
continue;
}
result.emplace_back(std::move(val));
}
return fmt::format("{}", fmt::join(result, ", "));
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some shenanigans with a Pointer to member to collect and join.

Comment on lines +7063 to +7086
std::vector<ControlTypeInfo> infos;
infos.reserve(tcz.NumControlTypes);
for (int ctInx = 1; ctInx <= tcz.NumControlTypes; ++ctInx) {
PreDefTableEntry(state, orp->pdchStatSchdType1, tcz.ZoneName, HVAC::thermostatTypeNames[(int)tcz.ControlTypeEnum(ctInx)]);
PreDefTableEntry(state, orp->pdchStatSchdTypeName1, tcz.ZoneName, tcz.ControlTypeName(1));
ControlTypeInfo info;
info.thermostatType = HVAC::thermostatTypeNames[(int)tcz.ControlTypeEnum(ctInx)];
info.controlTypeName = tcz.ControlTypeName(ctInx);
switch (tcz.ControlTypeEnum(ctInx)) {
case HVAC::ThermostatType::DualSetPointWithDeadBand:
PreDefTableEntry(
state, orp->pdchStatSchdHeatName, tcz.ZoneName, ScheduleManager::GetScheduleName(state, tcz.SchIndx_DualSetPointWDeadBandHeat));
PreDefTableEntry(
state, orp->pdchStatSchdCoolName, tcz.ZoneName, ScheduleManager::GetScheduleName(state, tcz.SchIndx_DualSetPointWDeadBandCool));
info.coolSchName = ScheduleManager::GetScheduleName(state, tcz.SchIndx_DualSetPointWDeadBandCool);
info.heatSchName = ScheduleManager::GetScheduleName(state, tcz.SchIndx_DualSetPointWDeadBandHeat);
break;
case HVAC::ThermostatType::SingleHeatCool:
PreDefTableEntry(
state, orp->pdchStatSchdHeatName, tcz.ZoneName, ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleHeatCoolSetPoint));
PreDefTableEntry(
state, orp->pdchStatSchdCoolName, tcz.ZoneName, ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleHeatCoolSetPoint));
info.coolSchName = ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleHeatCoolSetPoint);
info.heatSchName = ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleHeatCoolSetPoint);
break;
case HVAC::ThermostatType::SingleCooling:
PreDefTableEntry(
state, orp->pdchStatSchdHeatName, tcz.ZoneName, ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleCoolSetPoint));
info.coolSchName = ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleCoolSetPoint);
break;
case HVAC::ThermostatType::SingleHeating:
PreDefTableEntry(
state, orp->pdchStatSchdCoolName, tcz.ZoneName, ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleHeatSetPoint));
info.heatSchName = ScheduleManager::GetScheduleName(state, tcz.SchIndx_SingleHeatSetPoint);
break;
}
infos.emplace_back(std::move(info));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, collect the Control Type infos.

break;
}
infos.emplace_back(std::move(info));
}
std::sort(infos.begin(), infos.end());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sort them

Comment on lines +7089 to +7095
PreDefTableEntry(state, orp->pdchStatSchdType1, tcz.ZoneName, joinStrings(infos, &ControlTypeInfo::thermostatType));
PreDefTableEntry(state, orp->pdchStatSchdTypeName1, tcz.ZoneName, joinStrings(infos, &ControlTypeInfo::controlTypeName));
if (auto heatSchNames = joinStrings(infos, &ControlTypeInfo::heatSchName); !heatSchNames.empty()) {
PreDefTableEntry(state, orp->pdchStatSchdHeatName, tcz.ZoneName, heatSchNames);
}
if (auto coolSchNames = joinStrings(infos, &ControlTypeInfo::coolSchName); !coolSchNames.empty()) {
PreDefTableEntry(state, orp->pdchStatSchdCoolName, tcz.ZoneName, coolSchNames);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then join them with ", ".

For HeatSchNames / CoolSchNames, it's possible it's empty, and writing a "" is not the same as a null, so an extra if statement there

Copy link

⚠️ Regressions detected on macos-14 for commit f35325f

Regression Summary
  • Table String Diffs: 308
  • Audit: 304

Copy link

⚠️ Regressions detected on macos-14 for commit 7b68cb9

Regression Summary
  • Table String Diffs: 308
  • Audit: 304

Copy link

⚠️ Regressions detected on macos-14 for commit 8397ca6

Regression Summary
  • Table String Diffs: 308
  • Audit: 304

@nrel-bot-2c
Copy link

@jmarrec it has been 7 days since this pull request was last updated.

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.

Report System Summary:Thermostat Schedules depends on order of ZoneControl:Thermostat control types
2 participants