Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 131 additions & 38 deletions src/EnergyPlus/UnitarySystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ namespace UnitarySystems {
return;
}

// Save the current AFN coil runtime fraction for comparison with the one calculated below
Real64 refAFNLoopHeatingCoilMaxRTF(0.0);
Real64 refAFNLoopCoolingCoilMaxRTF(0.0);
if (state.afn->distribution_simulated && this->m_sysType != SysType::PackagedAC && this->m_sysType != SysType::PackagedHP &&
this->m_sysType != SysType::PackagedWSHP && AirLoopNum > 0) {
refAFNLoopHeatingCoilMaxRTF = state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF;
refAFNLoopCoolingCoilMaxRTF = state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF;
Copy link
Copy Markdown
Collaborator

@rraustad rraustad Apr 1, 2026

Choose a reason for hiding this comment

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

Can't you just move these last 2 lines down inside of the block below?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so because I think this needs to happen before controlUnitarySystemtoSP or controlUnitarySystemtoLoad are called.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see. Because these same AFN variables are set in the child. In this parent there can be 2 heating coils so the larger RTF should be used and calling the control functions will set AFNLoopHeatingCoilMaxRTF to the RTF of the last coil called.

}

// MassFlowRateMaxAvail issues are impeding non-VAV air loop equipment by limiting air flow
// temporarily open up flow limits while simulating, and then set this same value at the INLET after this parent has simulated
Real64 tempMassFlowRateMaxAvail = state.dataLoopNodes->Node(this->AirInNode).MassFlowRateMaxAvail;
Expand All @@ -160,6 +169,128 @@ namespace UnitarySystems {
// Report the current output
this->reportUnitarySystem(state, AirLoopNum);

// Get the actual maximum RTF for AFN simulations
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Similar changes as for #11367. We save the RTFs at the system level instead of at the coil level and make the determination here, that way we can also include the supplemental heating coil.

if (state.afn->distribution_simulated && this->m_sysType != SysType::PackagedAC && this->m_sysType != SysType::PackagedHP &&
this->m_sysType != SysType::PackagedWSHP && AirLoopNum > 0) {
Comment thread
lymereJ marked this conversation as resolved.
Outdated
Real64 heatingCoilRTF = 0.0;
Real64 coolingCoilRTF = 0.0;
Real64 suppHeatingCoilRTF = 0.0;
bool errorFound(false);
switch (this->m_HeatingCoilType_Num) {
case HVAC::Coil_HeatingGasOrOtherFuel:
case HVAC::Coil_HeatingElectric:
case HVAC::Coil_HeatingDesuperheater:
case HVAC::Coil_HeatingElectric_MultiStage:
case HVAC::Coil_HeatingGas_MultiStage: {
if (this->m_HeatingCoilIndex > 0) {
heatingCoilRTF = state.dataHeatingCoils->HeatingCoil(this->m_HeatingCoilIndex).RTF;
}
} break;
case HVAC::CoilDX_HeatingEmpirical:
case HVAC::CoilDX_MultiSpeedHeating: {
if (this->m_HeatingCoilIndex > 0) {
heatingCoilRTF = state.dataDXCoils->DXCoil(this->m_HeatingCoilIndex).HeatingCoilRuntimeFraction;
}
} break;
case HVAC::Coil_HeatingWaterToAirHPSimple: {
if (this->m_HeatingCoilIndex > 0) {
heatingCoilRTF = state.dataWaterToAirHeatPumpSimple->SimpleWatertoAirHP(this->m_HeatingCoilIndex).RunFrac;
}
} break;
case HVAC::Coil_HeatingWaterToAirHP: {
if (this->m_HeatingCoilIndex > 0) {
heatingCoilRTF = state.dataWaterToAirHeatPump->WatertoAirHP(this->m_HeatingCoilIndex).RunFrac;
}
} break;
case HVAC::Coil_HeatingWaterToAirHPVSEquationFit:
case HVAC::Coil_HeatingAirToAirVariableSpeed: {
if (this->m_HeatingCoilIndex > 0) {
heatingCoilRTF = state.dataVariableSpeedCoils->VarSpeedCoil(this->m_HeatingCoilIndex).RunFrac;
}
} break;
case HVAC::Coil_HeatingSteam:
case HVAC::Coil_HeatingWater:
case HVAC::Coil_UserDefined: {
heatingCoilRTF = 1.0;
} break;
default:;
}
if (errorFound) {
ShowSevereError(state, EnergyPlus::format("The index of \"{}\" is not found", this->m_HeatingCoilName));
ShowContinueError(state, EnergyPlus::format("...occurs for {}", this->m_HeatingCoilName));
errorFound = false;
}
switch (this->m_SuppHeatCoilType_Num) {
case HVAC::Coil_HeatingGasOrOtherFuel:
case HVAC::Coil_HeatingElectric:
case HVAC::Coil_HeatingDesuperheater:
case HVAC::Coil_HeatingElectric_MultiStage: {
if (this->m_SuppHeatCoilIndex > 0) {
suppHeatingCoilRTF = state.dataHeatingCoils->HeatingCoil(this->m_SuppHeatCoilIndex).RTF;
}
} break;
case HVAC::Coil_HeatingSteam:
case HVAC::Coil_HeatingWater:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are these types hard coded to RTF = 1 here?

Why not

suppHeatingCoilRTF = this->m_SuppHeatPartLoadFrac

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yup, good catch, I did not propagate the changes from the heating coil to the supplemental coil.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That might work. Here's how those coil types use PLR to modulate fluid flow. So it really depends on how AFN uses these global RTF variables to solve the network. The fan would provide an RTF for airflow and these coils would provide an RTF for temp? pressure drop? not sure. I suspect it's a guess either way unless someone dissects AFN to see how these RTF variables are used.

                this->m_SuppHeatPartLoadFrac = PartLoadFrac;
                this->m_SuppHeatingCycRatio = CycRatio;
                this->m_SuppHeatingSpeedRatio = SpeedRatio;

    if (this->m_suppHeatCoilType == HVAC::CoilType::HeatingWater || this->m_suppHeatCoilType == HVAC::CoilType::HeatingSteam) {
        mdot = PartLoadFrac * this->m_MaxSuppCoilFluidFlow;
        PlantUtilities::SetComponentFlowRate(
            state, mdot, this->m_SuppCoilFluidInletNode, this->m_SuppCoilFluidOutletNodeNum, this->m_SuppCoilPlantLoc);
    }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks to me as if this is an indicator of air flow so I am not at all sure why AFN is looking at a coil RTF. What difference would it make on infiltration if a compressor cycled on and off? A fan yes, but a coil? I'm still not sure how/why these coil RTF variables are used.

            LoopOnOffFanRunTimeFraction(AirLoopNum) = max(m_state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF,
                                                          m_state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopOnOffFanRTF,
                                                          m_state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF);

                if (AirflowNetworkNodeData(i).AirLoopNum == AirLoopNum) {
                    RepOnOffFanRunTimeFraction = LoopOnOffFanRunTimeFraction(AirLoopNum);
                }

                AirflowNetworkReportData(i).MultiZoneInfiSenGainW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiSenGainJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiSenLossW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiSenLossJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiLatGainW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiLatGainJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiLatLossW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneInfiLatLossJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentSenGainW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentSenGainJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentSenLossW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentSenLossJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentLatGainW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentLatGainJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentLatLossW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneVentLatLossJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixSenGainW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixSenGainJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixSenLossW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixSenLossJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixLatGainW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixLatGainJ *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixLatLossW *= RepOnOffFanRunTimeFraction;
                AirflowNetworkReportData(i).MultiZoneMixLatLossJ *= RepOnOffFanRunTimeFraction;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think that it is used to determine the impact of cycling fan operation on the AFN (infiltration, duct leakage, conduction, etc.). For this particular issue, the fact that the runtime fraction was not picked up correctly resulted in these adjustment to not be applicable:

https://github.com/NatLabRockies/EnergyPlus/blob/develop/src/EnergyPlus/AirflowNetwork/src/Solver.cpp#L10063-L10150

Copy link
Copy Markdown
Collaborator Author

@lymereJ lymereJ Apr 30, 2026

Choose a reason for hiding this comment

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

It looks to me as if this is an indicator of air flow so I am not at all sure why AFN is looking at a coil RTF. What difference would it make on infiltration if a compressor cycled on and off? A fan yes, but a coil?

I agree, fan RTF sounds like a better indicator but it looks like we take the max of coils and fan RTF:

OnOffFanRunTimeFraction = max(m_state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF,
m_state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopOnOffFanRTF,
m_state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF);

case HVAC::Coil_UserDefined: {
heatingCoilRTF = 1.0;
} break;
default:;
}
if (errorFound) {
ShowSevereError(state, EnergyPlus::format("The index of \"{}\" is not found", this->m_SuppHeatCoilName));
ShowContinueError(state, EnergyPlus::format("...occurs for {}", this->m_SuppHeatCoilName));
errorFound = false;
}
state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF =
max(refAFNLoopHeatingCoilMaxRTF, heatingCoilRTF, suppHeatingCoilRTF);

switch (this->m_CoolingCoilType_Num) {
case HVAC::CoilDX_Cooling: {
if (this->m_CoolingCoilIndex > 0) {
coolingCoilRTF = state.dataCoilCoolingDX->coilCoolingDXs[this->m_CoolingCoilIndex].coolingCoilRuntimeFraction;
}
} break;
case HVAC::CoilDX_CoolingSingleSpeed:
case HVAC::CoilDX_MultiSpeedCooling:
case HVAC::CoilDX_CoolingTwoSpeed:
case HVAC::CoilDX_CoolingTwoStageWHumControl: {
if (this->m_CoolingCoilIndex > 0) {
coolingCoilRTF = state.dataDXCoils->DXCoil(this->m_CoolingCoilIndex).CoolingCoilRuntimeFraction;
}
} break;
case HVAC::CoilDX_PackagedThermalStorageCooling: {
if (this->m_CoolingCoilIndex > 0) {
coolingCoilRTF = state.dataPackagedThermalStorageCoil->TESCoil(this->m_CoolingCoilIndex).RuntimeFraction;
}
} break;
case HVAC::Coil_CoolingWaterToAirHPSimple: {
if (this->m_CoolingCoilIndex > 0) {
coolingCoilRTF = state.dataWaterToAirHeatPumpSimple->SimpleWatertoAirHP(this->m_CoolingCoilIndex).RunFrac;
}
} break;
case HVAC::Coil_CoolingWaterToAirHP: {
if (this->m_CoolingCoilIndex > 0) {
coolingCoilRTF = state.dataWaterToAirHeatPump->WatertoAirHP(this->m_CoolingCoilIndex).RunFrac;
}
} break;
case HVAC::Coil_CoolingWaterToAirHPVSEquationFit:
case HVAC::Coil_CoolingAirToAirVariableSpeed: {
if (this->m_CoolingCoilIndex > 0) {
coolingCoilRTF = state.dataVariableSpeedCoils->VarSpeedCoil(this->m_CoolingCoilIndex).RunFrac;
}
} break;
case HVAC::Coil_CoolingWater:
case HVAC::Coil_CoolingWaterDetailed:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does this need to pick the RTF off of the child coil data structs? Something like this?

case HVAC::CoilType::CoolingDXHXAssisted: {
    if (this->m_CoolingCoilIndex > 0) {
        auto const &hxCoil = state.dataHVACAssistedCC->HXAssistedCoil(this->m_CoolingCoilIndex);
        if (hxCoil.CoolingCoilIndex > 0) {
            switch (hxCoil.coolCoilType) {
            case HVAC::CoilType::CoolingDX: {
                coolingCoilRTF = state.dataCoilCoolingDX->coilCoolingDXs[hxCoil.CoolingCoilIndex].coolingCoilRuntimeFraction;
            } break;
            case HVAC::CoilType::CoolingDXSingleSpeed: {
                coolingCoilRTF = state.dataDXCoils->DXCoil(hxCoil.CoolingCoilIndex).CoolingCoilRuntimeFraction;
            } break;
            case HVAC::CoilType::CoolingDXVariableSpeed: {
                coolingCoilRTF = state.dataVariableSpeedCoils->VarSpeedCoil(hxCoil.CoolingCoilIndex).RunFrac;
            } break;
            default: {
                coolingCoilRTF = this->m_CoolingPartLoadFrac;
            } break;
            }
        }
    }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The snippet above isn't tight enough - I'm specifically referring to the CoolingDXHXAssisted branch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that seems like a better approach. I'll push changes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@jasondegraw can you give us a lesson in fan RTF vs coil RTF in how AFN uses these vars in the solver? Why does AFN need to know a coil RTF?

case HVAC::Coil_UserDefined:
case HVAC::CoilDX_CoolingHXAssisted:
case HVAC::CoilWater_CoolingHXAssisted: {
coolingCoilRTF = 1.0;
} break;
default:;
Comment thread
lymereJ marked this conversation as resolved.
}
state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF = max(refAFNLoopCoolingCoilMaxRTF, coolingCoilRTF);
}

// CoolActive = false; // set in call from ZoneEquipmentManager
if (this->m_CoolingPartLoadFrac * double(CompressorOn) > 0.0) {
CoolActive = true;
Expand Down Expand Up @@ -14597,17 +14728,6 @@ namespace UnitarySystems {
int CompIndex = this->m_HeatingCoilIndex;
HVAC::FanOp fanOp = this->m_FanOpMode;
Real64 DesOutTemp = this->m_DesiredOutletTemp;

Real64 LoopHeatingCoilMaxRTFSave = 0.0;
Real64 LoopDXCoilMaxRTFSave = 0.0;
if (state.afn->distribution_simulated && this->m_sysType != SysType::PackagedAC && this->m_sysType != SysType::PackagedHP &&
this->m_sysType != SysType::PackagedWSHP) {
LoopHeatingCoilMaxRTFSave = state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF;
state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF = 0.0;
LoopDXCoilMaxRTFSave = state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF;
state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF = 0.0;
}

Real64 PartLoadFrac = 0.0;
Real64 SpeedRatio = 0.0;
Real64 CycRatio = 0.0;
Expand Down Expand Up @@ -15233,14 +15353,6 @@ namespace UnitarySystems {
this->m_HeatingCycRatio = CycRatio;
HeatCoilLoad = ReqOutput;

if (state.afn->distribution_simulated && this->m_sysType != SysType::PackagedAC && this->m_sysType != SysType::PackagedHP &&
this->m_sysType != SysType::PackagedWSHP) {
state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF =
max(state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopHeatingCoilMaxRTF, LoopHeatingCoilMaxRTFSave);
state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF =
max(state.dataAirLoop->AirLoopAFNInfo(AirLoopNum).AFNLoopDXCoilRTF, LoopDXCoilMaxRTFSave);
}

if (this->m_HeatingCoilType_Num == HVAC::Coil_HeatingWater || this->m_HeatingCoilType_Num == HVAC::Coil_HeatingSteam) {
mdot = PartLoadFrac * this->MaxHeatCoilFluidFlow;
PlantUtilities::SetComponentFlowRate(state, mdot, this->HeatCoilFluidInletNode, this->HeatCoilFluidOutletNodeNum, this->HeatCoilPlantLoc);
Expand Down Expand Up @@ -15283,17 +15395,6 @@ namespace UnitarySystems {
Real64 DesOutTemp = this->m_DesiredOutletTemp;
std::string_view CompName = this->m_SuppHeatCoilName;

Real64 LoopHeatingCoilMaxRTFSave = 0.0;
Real64 LoopDXCoilMaxRTFSave = 0.0;
if (state.afn->distribution_simulated && this->m_sysType != SysType::PackagedAC && this->m_sysType != SysType::PackagedHP &&
this->m_sysType != SysType::PackagedWSHP) {
auto &afnInfo = state.dataAirLoop->AirLoopAFNInfo(AirLoopNum);
LoopHeatingCoilMaxRTFSave = afnInfo.AFNLoopHeatingCoilMaxRTF;
afnInfo.AFNLoopHeatingCoilMaxRTF = 0.0;
LoopDXCoilMaxRTFSave = afnInfo.AFNLoopDXCoilRTF;
afnInfo.AFNLoopDXCoilRTF = 0.0;
}

// IF there is a fault of coil SAT Sensor
if (this->m_FaultyCoilSATFlag) {
// calculate the sensor offset using fault information
Expand Down Expand Up @@ -15646,14 +15747,6 @@ namespace UnitarySystems {
} // IF SENSIBLE LOAD
} // IF((GetCurrentScheduleValue(state, UnitarySystem(UnitarySysNum)%m_SysAvailSchedPtr) > 0.0d0) .AND. &

// LoopHeatingCoilMaxRTF used for AirflowNetwork gets set in child components (gas and fuel)
if (state.afn->distribution_simulated && this->m_sysType != SysType::PackagedAC && this->m_sysType != SysType::PackagedHP &&
this->m_sysType != SysType::PackagedWSHP) {
auto &afnInfo = state.dataAirLoop->AirLoopAFNInfo(AirLoopNum);
afnInfo.AFNLoopHeatingCoilMaxRTF = max(afnInfo.AFNLoopHeatingCoilMaxRTF, LoopHeatingCoilMaxRTFSave);
afnInfo.AFNLoopDXCoilRTF = max(afnInfo.AFNLoopDXCoilRTF, LoopDXCoilMaxRTFSave);
}

if (this->m_SuppHeatCoilType_Num == HVAC::Coil_HeatingWater || this->m_SuppHeatCoilType_Num == HVAC::Coil_HeatingSteam) {
mdot = PartLoadFrac * this->m_MaxSuppCoilFluidFlow;
PlantUtilities::SetComponentFlowRate(
Expand Down
Loading