Skip to content

Conversation

lymereJ
Copy link
Collaborator

@lymereJ lymereJ commented Apr 17, 2025

Pull request overview

Add new inputs/outputs to model air leakage in parallel fan-powered terminal units.

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 structural output changes, add to output rules file and add OutputChange label

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

@lymereJ lymereJ added NewFeature Includes code to add a new feature to EnergyPlus IDDChange Code changes impact the IDD file (cannot be merged after IO freeze) OutputChange Code changes output in such a way that it cannot be merged after IO freeze labels Apr 17, 2025
@NREL NREL deleted a comment from github-actions bot Apr 17, 2025
Copy link

⚠️ Regressions detected on macos-14 for commit e54efea

Regression Summary
  • Audit: 14
  • RDD: 14

@NREL NREL deleted a comment from github-actions bot Apr 28, 2025
@NREL NREL deleted a comment from github-actions bot Apr 28, 2025
@NREL NREL deleted a comment from github-actions bot Apr 28, 2025
Copy link

⚠️ Regressions detected on macos-14 for commit ff48bf9

Regression Summary
  • Audit: 14
  • RDD: 14

@nrel-bot-2
Copy link

@lymereJ @Myoldmopar it has been 31 days since this pull request was last updated.

@nrel-bot-2c
Copy link

@lymereJ @Myoldmopar it has been 34 days since this pull request was last updated.

@nrel-bot-2c
Copy link

@lymereJ @Myoldmopar it has been 30 days since this pull request was last updated.

@nrel-bot-2c
Copy link

@lymereJ @Myoldmopar it has been 28 days since this pull request was last updated.

@mjwitte
Copy link
Contributor

mjwitte commented Sep 25, 2025

@lymereJ Is this targeted for 25.2?

@lymereJ
Copy link
Collaborator Author

lymereJ commented Sep 26, 2025

@mjwitte - Ideally, yes, but it's not quite ready. I'm hoping to be able to get back to it sometime next week.

@lymereJ lymereJ added this to the EnergyPlus 25.2 IO Freeze milestone Sep 26, 2025
@lymereJ lymereJ force-pushed the parallel_piu_leakage branch from d7d35c6 to 4254b29 Compare October 10, 2025 18:15
Copy link

⚠️ Regressions detected on macos-14 for commit 07ecd15

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 10d8a24

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on macos-14 for commit 10d8a24

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 6ba6784

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on macos-14 for commit 6ba6784

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on macos-14 for commit 0713f64

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 0713f64

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 83fc13a

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on macos-14 for commit f2c8d98

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit f2c8d98

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 9510b09

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on macos-14 for commit 9510b09

Regression Summary
  • Audit: 4
  • RDD: 4

@lymereJ
Copy link
Collaborator Author

lymereJ commented Oct 11, 2025

The following plots show results generated using this branch for the new example file, for two cases: with and without leakage for the parallel PIU terminal serving "Space1-1". The terminal fan pulls air from "Space5-1", that is also the zone that is impacted by the leaks since they occur through the backdraft damper.

Heating and dead-band operations:

  • Air is leaking only when the terminal fan is off
  • During dead-band the flow to the zone (SPACE1-1 IN NODE) is equal to the primary air flow rate - the leaks
image

Cooling operations:

  • During cooling operation, the primary air ramps up, so do the leaks (top, maroon)
  • The impact of the leaks on "Space5-1" are shown on the bottom panel
image image

Copy link
Collaborator Author

@lymereJ lymereJ left a comment

Choose a reason for hiding this comment

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

Code walk-through:

Comment on lines +549 to +582

// Get damper leakage inputs
if (cCurrentModuleObject == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
std::string damperLeakageFractionCurveName =
ip->getAlphaFieldValue(fields, objectSchemaProps, "backdraft_damper_leakage_fraction_curve_name");
thisPIU.leakFracCurve = Curve::GetCurveIndex(state, damperLeakageFractionCurveName);

if (!damperLeakageFractionCurveName.empty() && thisPIU.leakFracCurve == 0) {
ShowSevereError(state,
format("The air leakage fraction curve for the {} {} is missing. No air leakage will be modeled.",
cCurrentModuleObject,
thisPIU.Name));
} else if (thisPIU.leakFracCurve > 0) {
// Find the secondary zone or plenum index
// The secondary air inlet node should either be a zone exhaust air node...
for (int zoneNum = 1; zoneNum <= state.dataGlobal->NumOfZones; ++zoneNum) {
for (int exhaustNum = 1; exhaustNum <= state.dataZoneEquip->ZoneEquipConfig(zoneNum).NumExhaustNodes; ++exhaustNum) {
if (thisPIU.SecAirInNode == state.dataZoneEquip->ZoneEquipConfig(zoneNum).ExhaustNode(exhaustNum)) {
state.dataHeatBal->Zone(zoneNum).isSourceForParallelPIU = true;
break;
}
}
}
// ... or an induced air node of a return plenum
for (int zoneNum = 1; zoneNum <= state.dataZonePlenum->NumZoneSupplyPlenums; ++zoneNum) {
for (int nodeNum = 1; nodeNum <= state.dataZonePlenum->ZoneRetPlenCond(zoneNum).NumInducedNodes; ++nodeNum) {
if (thisPIU.SecAirInNode == state.dataZonePlenum->ZoneRetPlenCond(zoneNum).InducedNode(nodeNum)) {
state.dataHeatBal->Zone(zoneNum).isSourceForParallelPIU = true;
break;
}
}
}
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New input validation for the leakage curve.

Comment on lines +672 to +680
if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
SetupOutputVariable(state,
"Zone Air Terminal Backdraft Damper Leakage Mass Flow Rate",
Constant::Units::kg_s,
thisPIU.leakFlow,
OutputProcessor::TimeStepType::System,
OutputProcessor::StoreType::Average,
state.dataPowerInductionUnits->PIU(PIURpt).Name);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New input is only valid for parallel PIUs.

Comment on lines +1887 to +1894
// PIU leakage calculations
if (thisPIU.leakFracCurve > 0 && state.dataHVACGlobal->TurnFansOn == false) {
// Determine leakage fraction as a function of the primary airflow fraction
const Real64 airflowFrac = thisPIU.PriAirMassFlow / thisPIU.MaxPriAirMassFlow;
thisPIU.leakFrac = min(1.0, Curve::CurveValue(state, thisPIU.leakFracCurve, airflowFrac));
// Determine leakage rate that won't make it to the zone served by the terminal
thisPIU.leakFlow = thisPIU.leakFrac * thisPIU.PriAirMassFlow;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We're in dead-band operation here so we only determine the leakage based on the minimum primary air flow rate.

// Determine leakage rate that won't make it to the zone served by the terminal
thisPIU.leakFlow = thisPIU.leakFrac * thisPIU.PriAirMassFlow;
// Increase the primary flow rate to meet the cooling load with leakage
thisPIU.PriAirMassFlow *= 1 / (1 - thisPIU.leakFrac); //(1 + thisPIU.leakFrac);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same as above except that we're increasing the primary air flow rate to meet the load and when taking leaks into account.

}
// Make sure that the primary airflow doesn't exceed the maximum when leakage is modeled
// When the primary airflow is limited to the maximum, the load won't likely be met
thisPIU.PriAirMassFlow = min(thisPIU.PriAirMassFlow, PriAirMassFlowMax);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Primary flow rate cannot be larger than the maximum value.

Comment on lines +4487 to +4498
if (state.dataHeatBal->Zone(zoneNum).isSourceForParallelPIU) {
for (int iExhNode = 1; iExhNode <= state.dataZoneEquip->ZoneEquipConfig(zoneNum).NumExhaustNodes; ++iExhNode) {
int piuNum =
PoweredInductionUnits::getParallelPIUNumFromSecNodeNum(state, state.dataZoneEquip->ZoneEquipConfig(zoneNum).ExhaustNode(iExhNode));
if (piuNum > 0) {
auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
MoistureMassFlowRate += (thisPIU.leakFlow * state.dataLoopNodes->Node(thisPIU.PriAirInNode).HumRat) / ZoneMult;
ZoneMassFlowRate += thisPIU.leakFlow / ZoneMult;
}
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Impact of leaks on the zone conditions. Here, humidity, below temperature and sensible load.

Comment on lines +997 to +1000
state.dataDefineEquipment->AirDistUnit(ADUNum).MassFlowRateDnStrLk +
state.dataDefineEquipment->AirDistUnit(ADUNum)
.massFlowRateParallelPIULk; // Even though the PIU leakage flow rate doesn't necessarily go through the plenum, the same amount
// ends-up being returned
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Account for PIU leaks separately from SDLM downstream leaks.

Comment on lines +758 to +775
if ((airDistUnit.UpStreamLeak || airDistUnit.DownStreamLeak || airDistUnit.parallelPIUTerminalLeakFrac > 0.0) &&
MassFlowRateMaxAvail > 0.0) {
airDistUnit.MassFlowRateTU = state.dataLoopNodes->Node(InNodeNum).MassFlowRate;
airDistUnit.MassFlowRateZSup = airDistUnit.MassFlowRateTU * (1.0 - airDistUnit.DownStreamLeakFrac);
airDistUnit.MassFlowRateZSup =
max(airDistUnit.MassFlowRateTU * (1.0 - airDistUnit.DownStreamLeakFrac - airDistUnit.parallelPIUTerminalLeakFrac), 0.0);
airDistUnit.MassFlowRateDnStrLk = airDistUnit.MassFlowRateTU * airDistUnit.DownStreamLeakFrac;
airDistUnit.massFlowRateParallelPIULk = airDistUnit.MassFlowRateTU * airDistUnit.parallelPIUTerminalLeakFrac;
airDistUnit.MassFlowRateSup = airDistUnit.MassFlowRateTU + airDistUnit.MassFlowRateUpStrLk;
state.dataLoopNodes->Node(InNodeNum).MassFlowRate = airDistUnit.MassFlowRateSup;
state.dataLoopNodes->Node(OutNodeNum).MassFlowRate = airDistUnit.MassFlowRateZSup;
state.dataLoopNodes->Node(OutNodeNum).MassFlowRateMaxAvail =
max(0.0, MassFlowRateMaxAvail - airDistUnit.MassFlowRateDnStrLk - airDistUnit.MassFlowRateUpStrLk);
max(0.0,
MassFlowRateMaxAvail - airDistUnit.MassFlowRateDnStrLk - airDistUnit.MassFlowRateUpStrLk -
airDistUnit.massFlowRateParallelPIULk);
state.dataLoopNodes->Node(OutNodeNum).MassFlowRateMinAvail =
max(0.0, MassFlowRateMinAvail - airDistUnit.MassFlowRateDnStrLk - airDistUnit.MassFlowRateUpStrLk);
max(0.0,
MassFlowRateMinAvail - airDistUnit.MassFlowRateDnStrLk - airDistUnit.MassFlowRateUpStrLk -
airDistUnit.massFlowRateParallelPIULk);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Leaks are "removed" from terminal outlet here.

Comment on lines +321 to +345
// Nineth test - Cooling load TurnFansOn is false, yes primary flow - expecting secondary flow, leakage
state->dataLoopNodes->Node(PriNodeNum).MassFlowRate = state->dataPowerInductionUnits->PIU(SysNum).MaxPriAirMassFlow;
state->dataLoopNodes->Node(PriNodeNum).MassFlowRateMaxAvail = state->dataPowerInductionUnits->PIU(SysNum).MaxPriAirMassFlow;
state->dataLoopNodes->Node(PriNodeNum).MassFlowRateMinAvail = state->dataPowerInductionUnits->PIU(SysNum).MinPriAirMassFlow;
state->dataZoneEnergyDemand->ZoneSysEnergyDemand(1).RemainingOutputRequired = -2000.0; // Cooling load
state->dataZoneEnergyDemand->ZoneSysEnergyDemand(1).RemainingOutputReqToHeatSP = -2000.0;
state->dataZoneEnergyDemand->CurDeadBandOrSetback(1) = false;
state->dataHVACGlobal->TurnFansOn = false;
state->dataPowerInductionUnits->PIU(SysNum).leakFracCurve = Curve::GetCurveIndex(*state, "CONSTANT_LEAKAGE");
PoweredInductionUnits::CalcParallelPIU(*state, SysNum, ZoneNum, ZoneNodeNum, FirstHVACIteration);

Real64 SysOutputProvided = 0.0;
Real64 NonAirSysOutput = 0.0;
Real64 LatOutputProvided = 0.0;
int AirDistUnitNum = 1;
state->dataPowerInductionUnits->GetPIUInputFlag = false;
ZoneAirLoopEquipmentManager::SimZoneAirLoopEquipment(*state,
AirDistUnitNum,
SysOutputProvided,
NonAirSysOutput,
LatOutputProvided,
FirstHVACIteration,
state->dataPowerInductionUnits->PIU(SysNum).CtrlZoneNum);
EXPECT_TRUE(state->dataDefineEquipment->AirDistUnit(1).MassFlowRateTU > state->dataDefineEquipment->AirDistUnit(1).MassFlowRateZSup);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Piggy backing on existing test to test leak flow calculations.

Comment on lines +1044 to +1057

// Check that parallel PIU leakage is accounted for
state->dataHeatBal->Zone(1).isSourceForParallelPIU = true;
state->dataPowerInductionUnits->GetPIUInputFlag = false;
state->dataPowerInductionUnits->NumPIUs = 1;
state->dataPowerInductionUnits->PIU.allocate(1);
state->dataPowerInductionUnits->PIU(1).SecAirInNode = 3; // exhaust node
state->dataPowerInductionUnits->PIU(1).PriAirInNode = 5; // primary air node
state->dataPowerInductionUnits->PIU(1).leakFlow = 0.1;
state->dataLoopNodes->Node(5).HumRat = 0.008;
state->dataLoopNodes->Node(5).Temp = 12.8;
thisZoneHB.calcZoneOrSpaceSums(*state, true, ZoneNum);
EXPECT_NEAR(402.67958, thisZoneHB.SumSysMCp, 0.0001);
EXPECT_NEAR(7328.768356, thisZoneHB.SumSysMCpT, 0.0001);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same but for impacts of leaks to the zone conditions.

@lymereJ lymereJ marked this pull request as ready for review October 11, 2025 05:30
@lymereJ lymereJ marked this pull request as draft October 11, 2025 05:58
Copy link

⚠️ Regressions detected on macos-14 for commit f6f0d17

Regression Summary
  • Audit: 4
  • RDD: 4

Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit f6f0d17

Regression Summary
  • Audit: 4
  • RDD: 4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

IDDChange Code changes impact the IDD file (cannot be merged after IO freeze) NewFeature Includes code to add a new feature to EnergyPlus OutputChange Code changes output in such a way that it cannot be merged after IO freeze

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants