Skip to content

Conversation

jmarrec
Copy link
Contributor

@jmarrec jmarrec commented Oct 2, 2025

Pull request overview

Description of the purpose of this PR

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

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 self-assigned this Oct 2, 2025
@jmarrec jmarrec added Defect Includes code to repair a defect in EnergyPlus NotIDDChange Code does not impact IDD (can be merged after IO freeze) labels Oct 2, 2025
@jmarrec
Copy link
Contributor Author

jmarrec commented Oct 2, 2025

Running the defect files on dev support.

  • primary_school_setpoint_ctrl_v2420.idf: this is Setpoint Controlled, but there is no setpoint on the component outlet
  • primary_school_setpoint_ctrl-addSetPoint_v2420.idf: this places the same setpoint as the overall loop setpoint on the component outlet
    • It is provided as a reference point

Diff of the two files:

image

Now, the result. As expected, the addSetPoint case is unchanged with this branch. The other one correctly assumes the overall loop setpoint in the after case.

image

Note that the eplusout.err does not include the new warning because that file has EMS.

@jmarrec
Copy link
Contributor Author

jmarrec commented Oct 2, 2025

I have a MCVE on DevSupport too, where I do see the new warning.

   ** Warning ** EIRPlantLoopHeatPump : oneTimeInit: Missing temperature setpoint for Setpoint Controlled HeatPump:PlantLoop:EIR:Heating name = "HEATING COIL"
   **   ~~~   **   A temperature setpoint is needed at the load side outlet node, use a SetpointManager
   **   ~~~   **   The overall loop setpoint will be assumed for the Heat Pump. The simulation continues ... 
   ** Warning ** EIRPlantLoopHeatPump : oneTimeInit: Missing temperature setpoint for Setpoint Controlled HeatPump:PlantLoop:EIR:Cooling name = "COOLING COIL"
   **   ~~~   **   A temperature setpoint is needed at the load side outlet node, use a SetpointManager
   **   ~~~   **   The overall loop setpoint will be assumed for the Heat Pump. The simulation continues ... 

Comment on lines +2672 to +2716
if (this->sysControlType == ControlType::Setpoint) {

// check if setpoint on outlet node
if ((state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint == DataLoopNode::SensedNodeFlagValue) &&
(state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointHi == DataLoopNode::SensedNodeFlagValue)) {
if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
if (!this->SetpointSetToLoopErrDone) {
ShowWarningError(state,
format("{}: Missing temperature setpoint for Setpoint Controlled {} name = \"{}\"",
routineName,
DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
this->name));
ShowContinueError(state, " A temperature setpoint is needed at the load side outlet node, use a SetpointManager");
ShowContinueError(state, " The overall loop setpoint will be assumed for the Heat Pump. The simulation continues ... ");
this->SetpointSetToLoopErrDone = true;
}
} else {
// need call to EMS to check node
bool fatalError = false; // but not really fatal yet, but should be.
EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->loadSideNodes.outlet, HVAC::CtrlVarType::Temp, fatalError);
state.dataLoopNodes->NodeSetpointCheck(this->loadSideNodes.outlet).needsSetpointChecking = false;
if (fatalError) {
if (!this->SetpointSetToLoopErrDone) {
ShowWarningError(state,
format("{}: Missing temperature setpoint for Setpoint Controlled {} name = \"{}\"",
routineName,
DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
this->name));
ShowContinueError(state, " A temperature setpoint is needed at the load side outlet node when ControlType = Setpoint");
ShowContinueError(state, " use a Setpoint Manager to establish a setpoint at the outlet node ");
ShowContinueError(state, " or use an EMS actuator to establish a setpoint at the outlet node ");
ShowContinueError(state, " The overall loop setpoint will be assumed for the Heat Pump. The simulation continues ... ");
this->SetpointSetToLoopErrDone = true;
}
}
}
this->SetpointSetToLoop = true;
state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint =
state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPoint;
state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointLo =
state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPointLo;
state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointHi =
state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPointHi;
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In oneTimeInit, when SetpointCOntrolled, we check if there's no setpoint, and assume the overall loop setpoint.

Error is dumped once only, and the "SetpointSetToLoop" is set to true.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am surprised this model does not check for a valid set point since it's been around for quite some time. It points to a set point node but where is the check for DataLoopNode::SensedNodeFlagValue? Maybe at a higher level in the plant manager. Anyway, I think getLoadSideOutletSetPointTemp already handles where the set point is located. I think you just need to correct old line 142 to new line 155 and always call this function. Once you made that 1-line change you fixed it. The new warnings are nice but I have to believe someone has already thought of plant node set point temperatures and warnings for all the plant equipment that can be controlled to a set point, at the component outlet or loop outlet based on the control type scheme.

Real64 EIRPlantLoopHeatPump::getLoadSideOutletSetPointTemp(EnergyPlusData &state)

    if (thisLoadPlantLoop.LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
        if (thisLoadComp.CurOpSchemeType == DataPlant::OpScheme::CompSetPtBased) {
            // there will be a valid set-point on outlet
            return state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint;
        } else { // use plant loop overall set-point
            return state.dataLoopNodes->Node(thisLoadPlantLoop.TempSetPointNodeNum).TempSetPoint;
        }
    } else if (thisLoadPlantLoop.LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand) {

Copy link
Contributor

@rraustad rraustad Oct 8, 2025

Choose a reason for hiding this comment

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

PlantManager line 2483:

if (this->SupplyEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {

    for (int LoopNum = 1; LoopNum <= this->plant->TotNumLoops; ++LoopNum) {
        // check if setpoints being placed on node properly
        if (this->plant->PlantLoop(LoopNum).LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand) {
            if (state.dataLoopNodes->Node(this->plant->PlantLoop(LoopNum).TempSetPointNodeNum).TempSetPointHi == SensedNodeFlagValue) {
                if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Note that the boiler and chillers use this as the equipment set point control node. I searched for TempSetPointNodeNum and it shows up in most/all plant equipment. Maybe that needs to be used instead of what getLoadSideOutletSetPointTemp does? This is weird, the EIR HP can be set point controlled while the plant can use other control types at the same time. If the EIR HP is SP controlled and has SPs at it's outlet node, and the plant is SP controlled and has SPs at the loop outlet node then which node has the priority for use as the SP? Maybe what you have done is more accurate for equipment that have a ControlType input field where the SP should be at the equipment outlet. I am, however, thinking it would be better to figure this out early and use a new this->setPointNodeNum variable that just points to the correct node and jetison copying node data each iteration. Maybe that would be a good follow up to this PR and fix all this hokeyness in the equipment models after thinking this through.

this->plant->PlantLoop(LoopNum).TempSetPointNodeNum

Copy link
Contributor

@rraustad rraustad Oct 11, 2025

Choose a reason for hiding this comment

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

Summary of what is needed to complete this work. If the EIR HP is set point controlled then a warning that the SP is missing at the outlet is fine. The plant does have a SP at the plant outlet node. But getLoadSideOutletSetPointTemp will only use that SP if the plant op scheme is NOT CompSetPtBased. I just don't like the idea of copying node data each iteration so I would rather find a different solution. What I would do is either fatal out if there is not SP at the EIR HP outlet node, or right here check if state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint == DataLoopNode::SensedNodeFlagValue and use the plant loop outlet node SP instead. @mjwitte ?

    if (this->loadSidePlantLoc.comp->CurOpSchemeType == DataPlant::OpScheme::CompSetPtBased) {
        // there will be a valid set-point on outlet
        return state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint;
    } else { // use plant loop overall set-point
        return state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPoint;
    }

I do like the idea of using this in the future, but for this issue what this change is doing is fine. setPointNodeNum would be set up early to point to the correct node, either the EIR HP outlet node or the plant loop outlet node.

return state.dataLoopNodes->Node(this->setPointNodeNum).TempSetPoint;

in getInput or Init choose either:

this->setPointNodeNum = this->loadSideNodes.outlet,     or
this->setPointNodeNum = this->loadSidePlantLoc.loop->TempSetPointNodeNum

This would simplify the code in getLoadSideOutletSetPointTemp.

Comment on lines +126 to +127
bool SetpointSetToLoop = false; // True if the setpoint is missing at the outlet node
bool SetpointSetToLoopErrDone = false; // True if setpoint warning issued
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 state vars

Comment on lines 140 to +152
if (this->running) {
if (this->sysControlType == ControlType::Setpoint) {
Real64 leavingSetpoint = state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint;
if (this->SetpointSetToLoop) {
// Copy the overall loop setpoints to the Heat Pump outlet node
state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint =
state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPoint;
// Hi is used when (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) {
state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointHi =
state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPointHi;
// Lo when HeatPumpEIRHeating
state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointLo =
state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPointLo;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If SetpointSetToLoop is true, in simulate we just copy the plant overall setpoints onto the outlet node.

Comment on lines +154 to +155
// Call the helper so we handle SingleSetpoint versus DualSetpointDeadband instead of just grabbing TempSetPoint;
Real64 leavingSetpoint = this->getLoadSideOutletSetPointTemp(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.

This was prexisting as

Real64 leavingSetpoint = state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint;

But in case the Setpoint is actually DualSetpointWithDeadband, I don't think this is working as intended. So we call the helper instead.

I hope I got that right?

Comment on lines +2009 to +2010
EXPECT_NEAR(30.0, thisHeatingPLHP->getLoadSideOutletSetPointTemp(*state), 0.001);
EXPECT_NEAR(30.0, state->dataLoopNodes->Node(thisHeatingPLHP->loadSideNodes.outlet).TempSetPoint, 0.001);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

before fix, -999...

Comment on lines +2037 to +2040
EXPECT_NEAR(10.0, thisHeatingPLHP->getLoadSideOutletSetPointTemp(*state), 0.001);
EXPECT_NEAR(DataLoopNode::SensedNodeFlagValue, state->dataLoopNodes->Node(thisHeatingPLHP->loadSideNodes.outlet).TempSetPoint, 0.001);
EXPECT_NEAR(30.0, state->dataLoopNodes->Node(thisHeatingPLHP->loadSideNodes.outlet).TempSetPointHi, 0.001);
EXPECT_NEAR(10.0, state->dataLoopNodes->Node(thisHeatingPLHP->loadSideNodes.outlet).TempSetPointLo, 0.001);
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 here, not 10.0 (or 30.0) but -999 before fix

Copy link

github-actions bot commented Oct 2, 2025

⚠️ Regressions detected on macos-14 for commit 5339267

Regression Summary
  • EIO: 3
  • ERR: 3
  • ESO Big Diffs: 3
  • Table Big Diffs: 1
  • Table Small Diffs: 1

Copy link

github-actions bot commented Oct 2, 2025

⚠️ Regressions detected on ubuntu-24.04 for commit 5339267

Regression Summary
  • EIO: 3
  • ERR: 3
  • ESO Big Diffs: 3
  • Table Big Diffs: 1
  • Table Small Diffs: 1

@mitchute mitchute requested review from mitchute and rraustad October 8, 2025 19:58
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 NotIDDChange Code does not impact IDD (can be merged after IO freeze)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HeatPump:PlantLoop:EIR:Heating with Setpoint control not operating

3 participants