Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2487d34
Add EMS actuator for CondFD sky longwave radiation override
brianlball Mar 17, 2026
05466ff
Add unit test for CondFD sky LW actuator override
brianlball Mar 17, 2026
6d84283
Add integration test IDFs for CondFD sky LW actuator
brianlball Mar 17, 2026
26b3c1e
Add Python examples and NFP for CondFD sky LW actuator
brianlball Mar 17, 2026
619ca65
Strengthen CondFD sky LW actuator unit test assertions
brianlball Mar 17, 2026
6a8179f
update debugging notebook with rain_flag to explain large spike in flux
brianlball Mar 17, 2026
7052057
Move NFP to design/FY2026/, add background/physics/references for non…
brianlball Mar 18, 2026
9917c80
Add docs and CN unit test for CondFD sky LW actuator
brianlball Mar 18, 2026
4f30e9a
add test idf file
brianlball Mar 18, 2026
d017d56
Fix const -> constexpr for literal-initialized locals in CondFD unit …
brianlball Mar 18, 2026
e9a9b4e
Apply clang-format v19 to CondFD source and unit test
brianlball Mar 18, 2026
02a82b2
NFP: add perf refactor note for SkyLW actuator; untrack notebook
brianlball Apr 6, 2026
b9f6bd0
Merge branch 'develop' into longwaverad_act
mitchute Apr 30, 2026
9a155c5
separate actuator and report variable, reset report variable to 0 whe…
mitchute Apr 30, 2026
0a95799
remove test file testfiles/1ZoneCondFD_Enet_EMS.idf
brianlball May 1, 2026
18311ae
remove testfiles/1ZoneCondFD_Enet_Test.idf
brianlball May 1, 2026
e0e25e0
reduce sky longwave rad actuator conditionals usage
mitchute May 2, 2026
417a781
Merge branch 'develop' into longwaverad_act
mitchute May 2, 2026
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
240 changes: 240 additions & 0 deletions design/FY2026/NFP_SkyLW_Actuator.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4071,6 +4071,10 @@ \subsubsection{Outputs}\label{outputs-36-1}

This output is the heat energy added after material layer N from the EMS heat flux actuator (Component type: ``CondFD Surface Material Layer''; Control type: ``Heat Flux''). Energy is aggregated on the electricity meter and is only valid for the CondFD solution algorithm.

\paragraph{CondFD EMS Sky Longwave Radiation Override Heat Flux}

This output is the user-specified net sky longwave radiation heat flux applied to the exterior surface via the EMS actuator (Component type: ``CondFD Surface''; Control type: ``Sky Longwave Radiation Override''). Reports 0.0 when not actuated. Only valid for exterior CondFD surfaces.

\subsection{Construction:AirBoundary}\label{constructionairboundary}

Construction:AirBoundary indicates an open boundary between two zones. It may be used for base surfaces and fenestration surfaces.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2523,6 +2523,20 @@ described below.
used to add energy (i.e. positive numbers only), and the energy used
is added to the electric heating sub-meter.

A separate actuator, called “CondFD Surface,” is available for exterior
surfaces that use the Conduction Finite Difference solution algorithm.
Unlike the material-layer actuators above, this actuator operates on the
surface as a whole and is identified by the surface name alone (not
SurfName:MatName).

- “Sky Longwave Radiation Override”. Has units of [W/m2]. Replaces
the default sky longwave radiation term (h_sky * (T_sky - T_surf))
in the exterior face heat balance with a user-specified net heat
flux. Positive values indicate net radiation into the surface;
negative values indicate net radiation leaving the surface (cooling).
Only applies to exterior CondFD surfaces. The actuated component
unique name is the surface name (e.g. “ZN001:ROOF001”).

Conduction Finite Difference Outputs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -2554,6 +2568,15 @@ CondFD EMS Heat Source Energy After Layer N

This output reports the heat energy added after material layer N

- Zone,Average,CondFD EMS Sky Longwave Radiation Override Heat Flux [W/m2]

CondFD EMS Sky Longwave Radiation Override Heat Flux
''''''''''''''''''''''''''''''''''''''''''''''''''''

This output reports the user-specified net sky longwave radiation heat
flux applied to the exterior surface via the EMS actuator. Reports 0.0
when not actuated.

Air Movement
------------

Expand Down
51 changes: 37 additions & 14 deletions src/EnergyPlus/HeatBalFiniteDiffManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,24 @@ namespace HeatBalFiniteDiffManager {
OutputProcessor::Group::Building,
OutputProcessor::EndUseCat::Heating);
}

// Setup EMS Actuator for Sky LW Radiation Override (per-surface)
if (state.dataSurface->Surface(SurfNum).ExtBoundCond == DataSurfaces::ExternalEnvironment) {
EnergyPlus::SetupEMSActuator(state,
"CondFD Surface",
state.dataSurface->Surface(SurfNum).Name,
"Sky Longwave Radiation Override",
"[W/m2]",
SurfaceFD(SurfNum).enetActuator.isActuated,
SurfaceFD(SurfNum).enetActuator.actuatedValue);
SetupOutputVariable(state,
"CondFD EMS Sky Longwave Radiation Override Heat Flux",
Constant::Units::W_m2,
SurfaceFD(SurfNum).enetActuatorReport,
OutputProcessor::TimeStepType::Zone,
OutputProcessor::StoreType::Average,
state.dataSurface->Surface(SurfNum).Name);
}
}

int TotNodes = ConstructFD(state.dataSurface->Surface(SurfNum).Construction).TotNodes; // Full size nodes, start with outside face.
Expand Down Expand Up @@ -1594,6 +1612,8 @@ namespace HeatBalFiniteDiffManager {

auto &s_hbfd = state.dataHeatBalFiniteDiffMgr;
auto const &surface(state.dataSurface->Surface(Surf));
auto &surfaceFD = s_hbfd->SurfaceFD(Surf);
surfaceFD.enetActuatorReport = surfaceFD.enetActuator.isActuated ? surfaceFD.enetActuator.actuatedValue : 0.0;
int const surface_ExtBoundCond(surface.ExtBoundCond);

Real64 Tsky;
Expand Down Expand Up @@ -1625,7 +1645,6 @@ namespace HeatBalFiniteDiffManager {
if (surface_ExtBoundCond == Surf) { // adiabatic surface, PT added since it is not the same as interzone wall
// as Outside Boundary Condition Object can be left blank.

auto &surfaceFD = s_hbfd->SurfaceFD(Surf);
InteriorBCEqns(state,
Delt,
NodeIn,
Expand Down Expand Up @@ -1699,7 +1718,6 @@ namespace HeatBalFiniteDiffManager {

// Boundary Conditions from Simulation for Exterior
Real64 const hconvo(state.dataMstBal->HConvExtFD(Surf));

Real64 const hrad(state.dataMstBal->HAirFD(Surf));
Real64 const hsky(state.dataMstBal->HSkyFD(Surf));
Real64 const hgnd(state.dataMstBal->HGrndFD(Surf));
Expand All @@ -1708,6 +1726,11 @@ namespace HeatBalFiniteDiffManager {
Real64 const Tgnd(Tgndsurface);
Real64 const Tsurr(TsurrSurface);

// Sky longwave radiation actuator and reusable variables
auto const &enetAct = s_hbfd->SurfaceFD(Surf).enetActuator;
Copy link
Copy Markdown
Collaborator

@rraustad rraustad May 1, 2026

Choose a reason for hiding this comment

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

This all looks correct so consider this just a suggestion. There are several new conditionals added to this code below, i.e., if (enetAct.isActuated). And in those uses are the same terms, hsky and hsky * Tsky. It may be more efficient to create those terms here and use these new terms below without needing the conditionals.

Real64 eHsky = (enetAct.isActuated) ? 0.0 : hSky;
Real64 eHskyTsky = (enetAct.isActuated) ? enetAct.actuatedValue : hsky * Tsky;

And then just replace those terms below with these new variables without needing the new conditionals?

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 addressed this here e0e25e0. Hopefully that's the final commit on this, and then we can merge it.

Real64 const eHsky = (enetAct.isActuated) ? 0.0 : hsky;
Real64 const eHskyTsky = (enetAct.isActuated) ? enetAct.actuatedValue : hsky * Tsky;

if (surface.HeatTransferAlgorithm == DataSurfaces::HeatTransferModel::CondFD) {

int const ConstrNum(surface.Construction);
Expand All @@ -1724,8 +1747,8 @@ namespace HeatBalFiniteDiffManager {
if (mat->ROnly || mat->group == Material::Group::AirGap) { // R Layer or Air Layer **********
// Use algebraic equation for TDT based on R
Real64 const Rlayer(mat->Resistance);
TDT_i = (TDT_p + (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + hsky * Tsky + hsurr * Tsurr) * Rlayer) /
(1.0 + (hconvo + hgnd + hrad + hsky + hsurr) * Rlayer);
TDT_i = (TDT_p + (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + eHskyTsky + hsurr * Tsurr) * Rlayer) /
(1.0 + (hconvo + hgnd + hrad + eHsky + hsurr) * Rlayer);

} else { // Regular or phase change material layer

Expand Down Expand Up @@ -1793,27 +1816,27 @@ namespace HeatBalFiniteDiffManager {
if (s_hbfd->CondFDSchemeType == CondFDScheme::CrankNicholsonSecondOrder) { // Second Order equation
Real64 const Cp_DelX_RhoS_2Delt(Cp * DelX * RhoS / (2.0 * Delt));
Real64 const kt_2DelX(kt / (2.0 * DelX));
Real64 const hsum(0.5 * (hconvo + hgnd + hrad + hsky + hsurr));
Real64 const hsum(0.5 * (hconvo + hgnd + hrad + eHsky + hsurr));
TDT_i = (QRadSWOutFD + Cp_DelX_RhoS_2Delt * TD_i + kt_2DelX * (TDT_p - TD_i + TD(i + 1)) + hgnd * Tgnd +
(hconvo + hrad) * Toa + hsky * Tsky + hsurr * Tsurr - hsum * TD_i) /
(hconvo + hrad) * Toa + eHskyTsky + hsurr * Tsurr - hsum * TD_i) /
(hsum + kt_2DelX + Cp_DelX_RhoS_2Delt);
} else if (s_hbfd->CondFDSchemeType == CondFDScheme::FullyImplicitFirstOrder) { // First Order
Real64 const Two_Delt_DelX(2.0 * Delt_DelX);
Real64 const Cp_DelX2_RhoS(Cp * pow_2(DelX) * RhoS);
Real64 const Two_Delt_kt(2.0 * Delt * kt);
TDT_i = (Two_Delt_DelX * (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + hsky * Tsky + hsurr * Tsurr) +
TDT_i = (Two_Delt_DelX * (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + eHskyTsky + hsurr * Tsurr) +
Cp_DelX2_RhoS * TD_i + Two_Delt_kt * TDT_p) /
(Two_Delt_DelX * (hconvo + hgnd + hrad + hsky + hsurr) + Two_Delt_kt + Cp_DelX2_RhoS);
(Two_Delt_DelX * (hconvo + hgnd + hrad + eHsky + hsurr) + Two_Delt_kt + Cp_DelX2_RhoS);
}

} else { // HMovInsul > 0.0: Transparent insulation on outside
// Transparent insulation additions

// Movable Insulation Layer Outside surface temp

Real64 const TInsulOut(
(QRadSWOutMvInsulFD + hgnd * Tgnd + HMovInsul * TDT_i + (hconvo + hrad) * Toa + hsky * Tsky + hsurr * Tsurr) /
(hconvo + hgnd + HMovInsul + hrad + hsky + hsurr)); // Temperature of outside face of Outside Insulation
Real64 const TInsulOut =
(QRadSWOutMvInsulFD + hgnd * Tgnd + HMovInsul * TDT_i + (hconvo + hrad) * Toa + eHskyTsky + hsurr * Tsurr) /
(hconvo + hgnd + HMovInsul + hrad + eHsky + hsurr);
Real64 const Two_Delt_DelX(2.0 * Delt_DelX);
Real64 const Cp_DelX2_RhoS(Cp * pow_2(DelX) * RhoS);
Real64 const Two_Delt_kt(2.0 * Delt * kt);
Expand Down Expand Up @@ -1844,15 +1867,15 @@ namespace HeatBalFiniteDiffManager {
// One formulation that works for Fully Implicit and CrankNicholson and massless wall

Real64 const Toa_TDT_i(Toa - TDT_i);
Real64 const QNetSurfFromOutside(
QRadSWOutFD + (hgnd * (-TDT_i + Tgnd) + (hconvo + hrad) * Toa_TDT_i + hsky * (-TDT_i + Tsky) + hsurr * (-TDT_i + Tsurr)));
Real64 const QNetSurfFromOutside =
QRadSWOutFD + (hgnd * (-TDT_i + Tgnd) + (hconvo + hrad) * Toa_TDT_i - eHsky * TDT_i + eHskyTsky + hsurr * (-TDT_i + Tsurr));

// Same sign convention as CTFs
state.dataHeatBalSurf->SurfOpaqOutFaceCondFlux(Surf) = -QNetSurfFromOutside;

// Report all outside BC heat fluxes
state.dataHeatBalSurf->SurfQdotRadOutRepPerArea(Surf) =
-(hgnd * (TDT_i - Tgnd) + hrad * (-Toa_TDT_i) + hsky * (TDT_i - Tsky) + hsurr * (TDT_i - Tsurr));
-(hgnd * (TDT_i - Tgnd) + hrad * (-Toa_TDT_i) + eHsky * TDT_i - eHskyTsky + hsurr * (TDT_i - Tsurr));
state.dataHeatBalSurf->SurfQdotRadOutRep(Surf) = surface.Area * state.dataHeatBalSurf->SurfQdotRadOutRepPerArea(Surf);
state.dataHeatBalSurf->SurfQRadOutReport(Surf) = state.dataHeatBalSurf->SurfQdotRadOutRep(Surf) * state.dataGlobal->TimeStepZoneSec;

Expand Down
2 changes: 2 additions & 0 deletions src/EnergyPlus/HeatBalFiniteDiffManager.hh
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ namespace HeatBalFiniteDiffManager {
// Includes the EMS heat source
Array1D<Real64> heatSourceEMSFluxLayerReport;
Array1D<Real64> heatSourceEMSFluxEnergyLayerReport;
MaterialActuatorData enetActuator; // Sky LW radiation EMS override [W/m2]
Real64 enetActuatorReport = 0.0; // Reported sky LW radiation EMS override [W/m2], zero when inactive

// Default Constructor
SurfaceDataFD() : SourceNodeNum(0), QSource(0.0), GSloopCounter(0), MaxNodeDelTemp(0.0), EnthalpyM(0.0), EnthalpyF(0.0), PhaseChangeState(0)
Expand Down
1 change: 1 addition & 0 deletions testfiles/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ if (LINK_WITH_PYTHON)
add_simulation_test(IDF_FILE PythonPlugin_SingleFamilyHouse_TwoSpeed_MultiStageElectricSuppCoil.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw)
add_simulation_test(IDF_FILE PythonPlugin1ZoneUncontrolledCondFD.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw)
add_simulation_test(IDF_FILE PythonPlugin1ZoneUncontrolledTrackHistory.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw)
add_simulation_test(IDF_FILE PythonPluginCondFD_Enet.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw DESIGN_DAY_ONLY)
add_simulation_test(IDF_FILE PythonPluginAirflowNetworkOpeningControlByHumidity.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw)
add_simulation_test(IDF_FILE PythonPluginConstantVolumePurchasedAir.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw)
add_simulation_test(IDF_FILE PythonPluginCurveOverride_PackagedTerminalHeatPump.idf EPW_FILE USA_FL_Miami.Intl.AP.722020_TMY3.epw)
Expand Down
Loading
Loading