diff --git a/design/FY2026/NFP_SkyLW_Actuator.md b/design/FY2026/NFP_SkyLW_Actuator.md new file mode 100644 index 00000000000..29f94db6b2e --- /dev/null +++ b/design/FY2026/NFP_SkyLW_Actuator.md @@ -0,0 +1,240 @@ +# EMS Actuator for CondFD Exterior Sky Longwave Radiation Override + +**Brian Ball, NatLabRockies** + +- March 2026 - Initial Draft + +## Overview + +Building energy simulation researchers coupling EnergyPlus with external radiation solvers (COMSOL, RadTherm, custom view factor codes) need to override the linearized sky longwave radiation calculation in the Conduction Finite Difference (CondFD) heat balance. Currently, EnergyPlus computes sky longwave (LW) radiation as `hsky*(Tsky - Tsurf)` using a linearized h-coefficient approach. This is adequate for standard simulations but insufficient when: + +- Users have measured net sky radiation data (pyrgeometer) +- External tools compute view-factor-accurate LW exchange with complex sky models +- Research requires decoupling the sky radiation model from the EnergyPlus surface solver + +## Background + +### Longwave Radiation at a Building Surface + +Every building surface exchanges thermal (longwave/infrared) radiation with its surroundings: the sky, the ground, and neighboring surfaces. For a roof, the dominant exchange is with the sky. Because the sky is much colder than the surface, this exchange is a net heat *loss* — the surface radiates more energy toward the sky than it receives back. This is why roofs cool well below air temperature on clear nights. + +The fundamental radiation exchange between a surface and the sky is governed by the Stefan-Boltzmann law: + +``` +Q_sky = ε · σ · F_sky · (Tsky⁴ − Tsurf⁴) +``` + +where: +- `ε` = surface emissivity (0–1, typically ~0.9 for building materials) +- `σ` = Stefan-Boltzmann constant (5.67 × 10⁻⁸ W/m²·K⁴) +- `F_sky` = view factor to sky (fraction of the hemisphere "seen" by the surface that is sky vs. ground/surroundings; ≈1.0 for a flat roof, ≈0.5 for a vertical wall) +- `Tsky` = effective sky temperature [K] +- `Tsurf` = exterior surface temperature [K] + +### EnergyPlus Linearization + +The `T⁴` nonlinearity makes direct solution expensive. EnergyPlus linearizes this into a convection-like form: + +``` +Q_sky ≈ hsky · (Tsky − Tsurf) +``` + +where `hsky` is a *linearized radiative heat transfer coefficient* (W/m²·K) that absorbs the emissivity, Stefan-Boltzmann constant, view factor, and a temperature-dependent scaling factor. This linearization is recomputed each timestep and is accurate for small temperature differences, but it is an approximation. + +### Why Override? + +The linearized approach cannot easily incorporate: +- **Measured data** — pyrgeometer readings give a direct net flux, not an h-coefficient +- **External solver results** — tools like COMSOL or RadTherm compute the full nonlinear `T⁴` exchange with detailed geometry and atmospheric models, producing a net flux value +- **Custom sky models** — researchers developing improved sky radiation correlations need to inject their own flux values + +This actuator lets users bypass the linearization entirely by supplying their own net sky LW flux value, called **Enet** (defined below). + +### Definition of Enet + +**Enet** is the user-specified net sky longwave radiation heat flux at the exterior surface, in W/m². It replaces the linearized `hsky·(Tsky − Tsurf)` term in the CondFD exterior heat balance equation. + +Enet is not computed by EnergyPlus — it is an *input* supplied by the user via the EMS (Energy Management System) actuator, from any source: measured data, an external solver, or a custom model. + +### Key Terminology + +| Term | Meaning | +|------|---------| +| **CondFD** | Conduction Finite Difference — an EnergyPlus algorithm that solves 1-D transient heat conduction through walls/roofs by discretizing layers into nodes. Alternative to the default Conduction Transfer Function (CTF) method. | +| **EMS** | Energy Management System — EnergyPlus's runtime scripting system that lets users read sensor values and override ("actuate") simulation variables during a run, via IDF programs or the Python API. | +| **CTF** | Conduction Transfer Function — EnergyPlus's default wall conduction algorithm. Uses pre-computed transfer functions; faster but less flexible than CondFD. Not affected by this proposal. | +| **EMPD** | Effective Moisture Penetration Depth — a moisture-aware conduction algorithm. Not affected by this proposal. | +| **Linearized h-coefficient** | A technique that rewrites the nonlinear `σ·T⁴` radiation exchange as `h·ΔT`, where `h` is updated each timestep. Converts a radiation problem into a form that looks like convection, enabling a simple implicit solve. | +| **View factor (F)** | The fraction of a surface's hemispherical "field of view" occupied by another surface or the sky. A flat roof sees almost all sky (F_sky ≈ 1); a vertical wall sees half sky, half ground (F_sky ≈ 0.5). | + +## Justification + +The CondFD algorithm already supports EMS actuators for material conductivity, specific heat, and internal heat flux (added FY2021-2022). This proposal extends that pattern to the exterior sky radiation boundary condition — the last remaining piece needed for full external coupling of the CondFD exterior heat balance. + +## Implementation + +Add an EMS actuator `"CondFD Surface" / "Sky Longwave Radiation Override"` (W/m2) that replaces the `hsky*(Tsky - Tsurf)` term in the CondFD exterior boundary equation (`ExteriorBCEqns` in `HeatBalFiniteDiffManager.cc`). When not actuated, existing behavior is unchanged. + +### Sign Convention + +Positive = heat INTO the surface (consistent with all existing EnergyPlus reporting): +- `Enet > 0` — net sky LW heats the surface (uncommon but physically real; occurs when heavy cloud cover or a warm air mass causes the atmosphere to radiate more LW toward the surface than the surface emits upward — see references below) +- `Enet < 0` — net sky LW cools the surface (typical for buildings; surfaces radiate more energy toward the cold sky than they receive back, especially on clear nights) +- `Enet = 0` — no sky LW exchange (surface and sky in thermal equilibrium, or sky LW intentionally disabled for research) + +#### Physical Basis for Sign Convention + +Net longwave radiation at a surface equals downwelling LW (from sky/clouds) minus upwelling LW (emitted by the surface). Whether the net value is positive or negative depends on the temperature of the atmosphere relative to the surface. For most building surfaces in temperate climates, the sky is colder than the surface, so the net is negative (cooling). However, under heavy overcast with warm, low cloud bases — or when a warm air mass advects over a radiatively cooled surface — downwelling LW can exceed upwelling, producing a positive (heating) net flux. This is well-documented in Arctic and snow-surface research and has been directly measured with pyrgeometers. + +**References:** +1. Viúdez-Mora, A., Costa-Surós, M., Calbó, J., & González, J.A. (2015). "Modeling atmospheric longwave radiation at the surface during overcast skies: The role of cloud base height." *J. Geophys. Res. Atmospheres*, DOI: [10.1002/2014JD022310](https://agupubs.onlinelibrary.wiley.com/doi/full/10.1002/2014JD022310) — Shows cloud base height governs downwelling LW magnitude under overcast skies. +2. Park, M.-S., et al. (2018). "The observed relationship of cloud to surface longwave radiation and air temperature at Ny-Ålesund, Svalbard." *Tellus B*, DOI: [10.1080/16000889.2018.1450589](https://www.tandfonline.com/doi/full/10.1080/16000889.2018.1450589) — Documents warm air mass advection driving increased downwelling LW and surface warming at an Arctic station. +3. Bertrand, L., Kay, J.E., & de Boer, G. (2025). "Increasing wintertime cloud opacity increases surface longwave radiation at a long-term Arctic observatory." *Nature Communications*, 16. [Link](https://www.nature.com/articles/s41467-025-64441-8) — Two decades of measurements showing increasing cloud opacity drives increasing net surface LW radiation. +4. Ritter, M.E. "The Radiation Balance." *The Physical Environment*, Geosciences LibreTexts. [Link](https://geo.libretexts.org/Bookshelves/Geography_(Physical)/The_Physical_Environment_(Ritter)/04:_Energy_and_Radiation/4.03:_Radiation_and_Energy_Balance_of_the_Earth_System/4.3.01:_The_Radiation_Balance) — States: "If the air is warmer than the ground a positive value exists. This could occur with heavy cloud cover...or if a warm air mass travels over a colder surface." +5. U.S. Army Corps of Engineers. "Longwave Radiation." *HEC-HMS Technical Reference Manual*. [Link](https://www.hec.usace.army.mil/confluence/hmsdocs/hmsum/4.8/meteorology-description/longwave-radiation) — Confirms: "Whether the atmosphere and clouds are a net source of longwave radiation to the land surface depends on their temperature relative to the land surface temperature." + +### Scope + +- CondFD algorithm only (CTF/EMPD not affected) +- Replaces only the sky component; ground, surrounding surface, and air LW terms unchanged +- All 4 solver paths patched: R-layer, Crank-Nicolson, fully implicit, movable insulation +- Reporting (QNetSurfFromOutside, SurfQdotRadOutRepPerArea) updated consistently + +### Actuator Details + +| Field | Value | +|-------|-------| +| Component Type | `CondFD Surface` | +| Unique ID | Surface name (e.g., `Zn001:Roof001`) | +| Control Type | `Sky Longwave Radiation Override` | +| Units | `[W/m2]` | + +One actuator per exterior CondFD surface, controllable via IDF EMS programs or Python API. + +### New Output Variable + +`CondFD EMS Sky Longwave Radiation Override Heat Flux [W/m2]` — reports the actuated value (0.0 when not actuated). + +### Files Modified + +| File | Change | +|------|--------| +| `HeatBalFiniteDiffManager.hh` | Add `enetActuator` field to `SurfaceDataFD` | +| `HeatBalFiniteDiffManager.cc` | Actuator registration + 6 solver location overrides | + +### Physics + +The CondFD exterior heat balance solves for the outside surface node temperature `T_o` implicitly. The standard linearized form (simplified, showing only the key terms) is: + +``` + QSolarAbs + hconv·T_air + hsky·Tsky + hgnd·Tgnd + hcond·T_i +T_o = ───────────────────────────────────────────────────────────── + hconv + hsky + hgnd + hcond +``` + +where: +- `hconv` = convective heat transfer coefficient, `T_air` = outdoor air temperature +- `hsky` = linearized sky radiative coefficient, `Tsky` = effective sky temperature +- `hgnd` = linearized ground radiative coefficient, `Tgnd` = ground temperature +- `hcond` = conduction coupling to the next interior node, `T_i` = next node temperature +- `QSolarAbs` = absorbed solar radiation (a fixed flux, not temperature-dependent) + +The `hsky·Tsky` term in the numerator and `hsky` in the denominator together represent the sky LW contribution in this implicit formulation. + +**When Enet is actuated**, the sky terms are replaced: + +``` + QSolarAbs + hconv·T_air + Enet + hgnd·Tgnd + hcond·T_i +T_o = ───────────────────────────────────────────────────────── + hconv + hgnd + hcond +``` + +- **Numerator:** `hsky · Tsky` is replaced by `Enet` (a fixed flux, like solar) +- **Denominator:** `hsky` is removed entirely + +This works because Enet is a *fixed flux* — it does not depend on the surface temperature being solved for. Just like absorbed solar radiation, it contributes a known quantity of heat and does not need to appear in the denominator. The linearized `hsky` term, by contrast, is temperature-dependent (it multiplies a temperature difference), which is why it appears in both numerator and denominator. + +This substitution is applied consistently across all 6 code locations in `ExteriorBCEqns`: the R-layer solver, Crank-Nicolson solver, fully implicit solver, movable insulation path, net flux calculation, and LW reporting. + +## Testing + +- Unit test: `HeatBalFiniteDiffManager_EnetActuatorOverride` — exercises ExteriorBCEqns with actuator OFF, Enet=0, Enet=-200, Enet=+200; verifies correct temperature ordering +- Integration tests: `1ZoneCondFD_Enet_Test.idf` (baseline) and `1ZoneCondFD_Enet_EMS.idf` (EMS with Enet=-200) +- Validated against Phase 2 hardcoded results (roof temps match within 0.1C) + +### Phase 2 Validation Results + +| Run | Enet (W/m2) | Roof Temp (C) | LW Report (W/m2) | +|-----|-------------|---------------|-------------------| +| Baseline | n/a | -23.8 | -32.0 | +| Enet=0 | 0 | -16.0 | 0.0 | +| Enet=-200 | -200 | -55.8 | -200.0 | +| Enet=+200 | +200 | +12.6 | +200.0 | + +## Documentation + +EMS Application Guide will be updated to describe the new actuator, including: +- Usage with IDF EMS programs +- Usage with Python plugin API +- Usage with standalone Python API (Jupyter notebook example provided) + +## IDD Changes and Transition + +None required. The actuator is registered programmatically via `SetupEMSActuator`. + +## Example Files + +- `testfiles/1ZoneCondFD_Enet_Test.idf` — baseline CondFD test +- `testfiles/1ZoneCondFD_Enet_EMS.idf` — EMS actuator with Enet=-200 +- `scripts/CondFD_Enet_Override.ipynb` — Jupyter notebook for interactive use with any IDF/EPW + +## Possible Enhancements + +### Branch Hoist / Equation Unification in `ExteriorBCEqns` + +Pure refactor — no behavior change. + +**Problem.** `ExteriorBCEqns` in `src/EnergyPlus/HeatBalFiniteDiffManager.cc` contains 6 `if (enetAct.isActuated)` sites (R-layer, Crank-Nicolson 2nd-order, Fully-Implicit 1st-order, MovInsul `TInsulOut`, `QNetSurfFromOutside`, `SurfQdotRadOutRepPerArea`). Each duplicates the surrounding equation. Introduced in commit 2487d34a39. + +**Performance.** Not a real concern. +- `enetAct.isActuated` is a single bool, hoisted once per call via `auto const &enetAct = ...`. +- Called per exterior CondFD surface × outer-face node × CondFD sub-iter × HVAC timestep. High, not innermost. +- Branch predictor ~100% accurate: value is constant across all sub-iterations of a timestep. EMS can flip at most once per zone timestep, so at worst one mispredict (~10-20 cycles) amortized over thousands of calls. +- Both arms are straight-line arithmetic, in I-cache. Cost is in the noise vs. divides / `pow_2` / property lookups / reporting around them. +- Mid-run EMS toggling does not change this analysis — the bool is re-read every call. + +**Real cost: maintenance.** 4 duplicated equations + 2 duplicated report lines. Risk of silent drift if someone fixes the sky term in one arm but not the other. + +**Recommended refactor.** Hoist the branch once at the top of the `CondFD` block in `ExteriorBCEqns`, compute effective locals, then use unified equations: + +```cpp +Real64 const hsky_eff = enetAct.isActuated ? 0.0 : hsky; +Real64 const QskyTerm = enetAct.isActuated ? enetAct.actuatedValue : hsky * Tsky; +// Form used by QNet / report lines, which need (Tsky - TDT_i): +Real64 const QskyNetTerm = enetAct.isActuated ? enetAct.actuatedValue : hsky * (Tsky - TDT_i); +``` + +Then every site collapses. R-layer example: + +```cpp +TDT_i = (TDT_p + (QRadSWOutFD + hgnd*Tgnd + (hconvo+hrad)*Toa + QskyTerm + hsurr*Tsurr) * Rlayer) + / (1.0 + (hconvo + hgnd + hrad + hsky_eff + hsurr) * Rlayer); +``` + +Works identically for CN 2nd-order, FI 1st-order, `TInsulOut`. For `QNetSurfFromOutside` / `SurfQdotRadOutRepPerArea`, replace `hsky*(Tsky-TDT_i)` (resp. `hsky*(TDT_i-Tsky)`) with `QskyNetTerm` (resp. `-QskyNetTerm`). + +**Properties.** +- Bit-for-bit identical when not actuated (same operand order, same linearization). +- Correct under arbitrary mid-run EMS toggling — locals recomputed every call from current bool. +- Removes all 6 duplicated sites; 4 equations shrink to 1 form each. +- Same or slightly faster: compiler gets straight-line code; ternaries typically lower to `cmov`. + +**Rejected alternatives.** +- *Function-pointer / template dispatch per surface* — indirect call overhead, I-cache pressure, zero upside. +- *Partition surfaces into actuated / not-actuated lists at init* — over-engineered, and wrong if EMS can flip mid-run. +- *Always treat sky as a flux (drop `hsky` from denominator universally)* — changes the implicit linearization in the non-actuated path; likely worse convergence. Do not do. +- *Hoist the branch outside `ExteriorBCEqns` scope* — breaks mid-run toggling. + +**Open questions.** +- Extend same pattern to CTF / HAMT / Kiva, or CondFD-only? +- Land as part of the SkyLW actuator PR, or separate follow-up? diff --git a/doc/input-output-reference/src/overview/group-surface-construction-elements.tex b/doc/input-output-reference/src/overview/group-surface-construction-elements.tex index 118cdd0bcec..710ffd7fcaf 100644 --- a/doc/input-output-reference/src/overview/group-surface-construction-elements.tex +++ b/doc/input-output-reference/src/overview/group-surface-construction-elements.tex @@ -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. diff --git a/doc/readthedocs/sphinx/ems-application-guide/ems-application-guide.rst b/doc/readthedocs/sphinx/ems-application-guide/ems-application-guide.rst index dcd61152bd3..d1c13b37788 100644 --- a/doc/readthedocs/sphinx/ems-application-guide/ems-application-guide.rst +++ b/doc/readthedocs/sphinx/ems-application-guide/ems-application-guide.rst @@ -2517,6 +2517,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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -2548,6 +2562,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 ------------ diff --git a/src/EnergyPlus/HeatBalFiniteDiffManager.cc b/src/EnergyPlus/HeatBalFiniteDiffManager.cc index 17adb5fe11c..a94bade0b80 100644 --- a/src/EnergyPlus/HeatBalFiniteDiffManager.cc +++ b/src/EnergyPlus/HeatBalFiniteDiffManager.cc @@ -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).enetActuator.actuatedValue, + 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. @@ -1707,6 +1725,7 @@ namespace HeatBalFiniteDiffManager { Real64 const Toa(state.dataMstBal->TempOutsideAirFD(Surf)); Real64 const Tgnd(Tgndsurface); Real64 const Tsurr(TsurrSurface); + auto const &enetAct = s_hbfd->SurfaceFD(Surf).enetActuator; if (surface.HeatTransferAlgorithm == DataSurfaces::HeatTransferModel::CondFD) { @@ -1724,8 +1743,13 @@ 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); + if (enetAct.isActuated) { + TDT_i = (TDT_p + (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + enetAct.actuatedValue + hsurr * Tsurr) * Rlayer) / + (1.0 + (hconvo + hgnd + hrad + hsurr) * Rlayer); + } else { + TDT_i = (TDT_p + (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + hsky * Tsky + hsurr * Tsurr) * Rlayer) / + (1.0 + (hconvo + hgnd + hrad + hsky + hsurr) * Rlayer); + } } else { // Regular or phase change material layer @@ -1793,17 +1817,30 @@ 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)); - 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) / - (hsum + kt_2DelX + Cp_DelX_RhoS_2Delt); + if (enetAct.isActuated) { + Real64 const hsum(0.5 * (hconvo + hgnd + hrad + hsurr)); + TDT_i = (QRadSWOutFD + Cp_DelX_RhoS_2Delt * TD_i + kt_2DelX * (TDT_p - TD_i + TD(i + 1)) + hgnd * Tgnd + + (hconvo + hrad) * Toa + enetAct.actuatedValue + hsurr * Tsurr - hsum * TD_i) / + (hsum + kt_2DelX + Cp_DelX_RhoS_2Delt); + } else { + Real64 const hsum(0.5 * (hconvo + hgnd + hrad + hsky + 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) / + (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) + - Cp_DelX2_RhoS * TD_i + Two_Delt_kt * TDT_p) / - (Two_Delt_DelX * (hconvo + hgnd + hrad + hsky + hsurr) + Two_Delt_kt + Cp_DelX2_RhoS); + if (enetAct.isActuated) { + TDT_i = (Two_Delt_DelX * (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + enetAct.actuatedValue + hsurr * Tsurr) + + Cp_DelX2_RhoS * TD_i + Two_Delt_kt * TDT_p) / + (Two_Delt_DelX * (hconvo + hgnd + hrad + hsurr) + Two_Delt_kt + Cp_DelX2_RhoS); + } else { + TDT_i = (Two_Delt_DelX * (QRadSWOutFD + hgnd * Tgnd + (hconvo + hrad) * Toa + hsky * Tsky + 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); + } } } else { // HMovInsul > 0.0: Transparent insulation on outside @@ -1811,9 +1848,15 @@ namespace HeatBalFiniteDiffManager { // 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 TInsulOut; + if (enetAct.isActuated) { + TInsulOut = (QRadSWOutMvInsulFD + hgnd * Tgnd + HMovInsul * TDT_i + (hconvo + hrad) * Toa + enetAct.actuatedValue + + hsurr * Tsurr) / + (hconvo + hgnd + HMovInsul + hrad + hsurr); + } else { + TInsulOut = (QRadSWOutMvInsulFD + hgnd * Tgnd + HMovInsul * TDT_i + (hconvo + hrad) * Toa + hsky * Tsky + hsurr * Tsurr) / + (hconvo + hgnd + HMovInsul + hrad + hsky + 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); @@ -1844,15 +1887,26 @@ 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 QNetSurfFromOutside; + if (enetAct.isActuated) { + QNetSurfFromOutside = + QRadSWOutFD + (hgnd * (-TDT_i + Tgnd) + (hconvo + hrad) * Toa_TDT_i + enetAct.actuatedValue + hsurr * (-TDT_i + Tsurr)); + } else { + QNetSurfFromOutside = + QRadSWOutFD + (hgnd * (-TDT_i + Tgnd) + (hconvo + hrad) * Toa_TDT_i + hsky * (-TDT_i + Tsky) + 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)); + if (enetAct.isActuated) { + state.dataHeatBalSurf->SurfQdotRadOutRepPerArea(Surf) = + -(hgnd * (TDT_i - Tgnd) + hrad * (-Toa_TDT_i) + (-enetAct.actuatedValue) + hsurr * (TDT_i - Tsurr)); + } else { + state.dataHeatBalSurf->SurfQdotRadOutRepPerArea(Surf) = + -(hgnd * (TDT_i - Tgnd) + hrad * (-Toa_TDT_i) + hsky * (TDT_i - Tsky) + 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; diff --git a/src/EnergyPlus/HeatBalFiniteDiffManager.hh b/src/EnergyPlus/HeatBalFiniteDiffManager.hh index 69a77d3f998..b036667838c 100644 --- a/src/EnergyPlus/HeatBalFiniteDiffManager.hh +++ b/src/EnergyPlus/HeatBalFiniteDiffManager.hh @@ -166,6 +166,7 @@ namespace HeatBalFiniteDiffManager { // Includes the EMS heat source Array1D heatSourceEMSFluxLayerReport; Array1D heatSourceEMSFluxEnergyLayerReport; + MaterialActuatorData enetActuator; // Sky LW radiation EMS override [W/m2] // Default Constructor SurfaceDataFD() : SourceNodeNum(0), QSource(0.0), GSloopCounter(0), MaxNodeDelTemp(0.0), EnthalpyM(0.0), EnthalpyF(0.0), PhaseChangeState(0) diff --git a/testfiles/1ZoneCondFD_Enet_EMS.idf b/testfiles/1ZoneCondFD_Enet_EMS.idf new file mode 100644 index 00000000000..f65c71e05bf --- /dev/null +++ b/testfiles/1ZoneCondFD_Enet_EMS.idf @@ -0,0 +1,395 @@ +!-Generator IDFEditor 1.34 +!-Option SortedOrder +! 1ZoneCondFD_Enet_EMS.idf +! EMS actuator test for CondFD sky LW radiation override. +! Based on 1ZoneCondFD_Enet_Test.idf with EMS program setting Enet = -200 W/m2. + + Version,26.1; + +!- =========== ALL OBJECTS IN CLASS: SIMULATIONCONTROL =========== + + SimulationControl, + No, !- Do Zone Sizing Calculation + No, !- Do System Sizing Calculation + No, !- Do Plant Sizing Calculation + Yes, !- Run Simulation for Sizing Periods + No, !- Run Simulation for Weather File Run Periods + No, !- Do HVAC Sizing Simulation for Sizing Periods + 1; !- Maximum Number of HVAC Sizing Simulation Passes + +!- =========== ALL OBJECTS IN CLASS: BUILDING =========== + + Building, + Enet LW Test Box, !- Name + 0, !- North Axis {deg} + Suburbs, !- Terrain + 0.04, !- Loads Convergence Tolerance Value {W} + 0.004, !- Temperature Convergence Tolerance Value {deltaC} + MinimalShadowing, !- Solar Distribution + 30, !- Maximum Number of Warmup Days + 6; !- Minimum Number of Warmup Days + +!- =========== ALL OBJECTS IN CLASS: ALGORITHMS =========== + + SurfaceConvectionAlgorithm:Inside,TARP; + + SurfaceConvectionAlgorithm:Outside,TARP; + + HeatBalanceAlgorithm,ConductionFiniteDifference; + + Timestep,20; + +!- =========== ALL OBJECTS IN CLASS: SITE:LOCATION =========== + + Site:Location, + Denver Centennial Golden N_CO_USA Design_Conditions, !- Name + 39.74, !- Latitude {deg} + -105.18, !- Longitude {deg} + -7.00, !- Time Zone {hr} + 1829.00; !- Elevation {m} + +!- =========== ALL OBJECTS IN CLASS: SIZINGPERIOD:DESIGNDAY =========== + + SizingPeriod:DesignDay, + Denver Centennial Golden Intl Arpt Ann Htg 99% Condns DB, !- Name + 12, !- Month + 21, !- Day of Month + WinterDesignDay, !- Day Type + -16, !- Maximum Dry-Bulb Temperature {C} + 0.0, !- Daily Dry-Bulb Temperature Range {deltaC} + , !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Day Schedule Name + Wetbulb, !- Humidity Condition Type + -16, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C} + , !- Humidity Condition Day Schedule Name + , !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir} + , !- Enthalpy at Maximum Dry-Bulb {J/kg} + , !- Daily Wet-Bulb Temperature Range {deltaC} + 83411., !- Barometric Pressure {Pa} + 2.3, !- Wind Speed {m/s} + 180, !- Wind Direction {deg} + No, !- Rain Indicator + No, !- Snow Indicator + No, !- Daylight Saving Time Indicator + ASHRAEClearSky, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + , !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless} + , !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless} + 0.00; !- Sky Clearness + + SizingPeriod:DesignDay, + Denver Centennial Golden Intl Arpt Ann Clg 1% Condns DB=>MWB, !- Name + 7, !- Month + 21, !- Day of Month + SummerDesignDay, !- Day Type + 32.6, !- Maximum Dry-Bulb Temperature {C} + 15.2, !- Daily Dry-Bulb Temperature Range {deltaC} + , !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Day Schedule Name + Wetbulb, !- Humidity Condition Type + 15.6, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C} + , !- Humidity Condition Day Schedule Name + , !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir} + , !- Enthalpy at Maximum Dry-Bulb {J/kg} + , !- Daily Wet-Bulb Temperature Range {deltaC} + 83411., !- Barometric Pressure {Pa} + 4, !- Wind Speed {m/s} + 120, !- Wind Direction {deg} + No, !- Rain Indicator + No, !- Snow Indicator + No, !- Daylight Saving Time Indicator + ASHRAEClearSky, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + , !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless} + , !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless} + 1.00; !- Sky Clearness + +!- =========== ALL OBJECTS IN CLASS: SITE:GROUNDTEMPERATURE:BUILDINGSURFACE =========== + + Site:GroundTemperature:BuildingSurface,18.89,18.92,19.02,19.12,19.21,19.23,19.07,19.32,19.09,19.21,19.13,18.96; + +!- =========== ALL OBJECTS IN CLASS: SCHEDULETYPELIMITS =========== + + ScheduleTypeLimits, + Fraction, !- Name + 0.0, !- Lower Limit Value + 1.0, !- Upper Limit Value + CONTINUOUS; !- Numeric Type + + ScheduleTypeLimits, + On/Off, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + DISCRETE; !- Numeric Type + +!- =========== ALL OBJECTS IN CLASS: SCHEDULES =========== + + Schedule:Day:Hourly, + On, !- Name + On/Off, !- Schedule Type Limits Name + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.; + + Schedule:Week:Daily, + On Weeks, !- Name + On,On,On,On,On,On,On,On,On,On,On,On; + + Schedule:Year, + AlwaysOn, !- Name + On/Off, !- Schedule Type Limits Name + On Weeks, !- Schedule:Week Name 1 + 1, !- Start Month 1 + 1, !- Start Day 1 + 12, !- End Month 1 + 31; !- End Day 1 + +!- =========== ALL OBJECTS IN CLASS: MATERIAL =========== + + Material, + C5 - 4 IN HW CONCRETE, !- Name + MediumRough, !- Roughness + 0.1014984, !- Thickness {m} + 1.729577, !- Conductivity {W/m-K} + 2242.585, !- Density {kg/m3} + 836.8000, !- Specific Heat {J/kg-K} + 0.9000000, !- Thermal Absorptance + 0.6500000, !- Solar Absorptance + 0.6500000; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: MATERIAL:NOMASS =========== + + Material:NoMass, + R13LAYER, !- Name + Rough, !- Roughness + 2.290965, !- Thermal Resistance {m2-K/W} + 0.9000000, !- Thermal Absorptance + 0.7500000, !- Solar Absorptance + 0.7500000; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: CONSTRUCTION =========== + + Construction, + R13WALL, !- Name + R13LAYER; !- Outside Layer + + Construction, + FLOOR, !- Name + C5 - 4 IN HW CONCRETE; !- Outside Layer + + Construction, + ROOF_CONCRETE, !- Name + C5 - 4 IN HW CONCRETE; !- Outside Layer + +!- =========== ALL OBJECTS IN CLASS: GLOBALGEOMETRYRULES =========== + + GlobalGeometryRules, + UpperLeftCorner, !- Starting Vertex Position + CounterClockWise, !- Vertex Entry Direction + World; !- Coordinate System + +!- =========== ALL OBJECTS IN CLASS: ZONE =========== + + Zone, + ZONE ONE, !- Name + 0, !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + 1, !- Type + 1, !- Multiplier + autocalculate, !- Ceiling Height {m} + autocalculate; !- Volume {m3} + +!- =========== ALL OBJECTS IN CLASS: BUILDINGSURFACE:DETAILED =========== + + BuildingSurface:Detailed, + Zn001:Wall001, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 0,0,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 0,0,0, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,0,0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,0,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall002, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,0,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 15.24000,0,0, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,15.24000,0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall003, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,15.24000,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 15.24000,15.24000,0, !- X,Y,Z ==> Vertex 2 {m} + 0,15.24000,0, !- X,Y,Z ==> Vertex 3 {m} + 0,15.24000,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall004, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 0,15.24000,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 0,15.24000,0, !- X,Y,Z ==> Vertex 2 {m} + 0,0,0, !- X,Y,Z ==> Vertex 3 {m} + 0,0,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Flr001, !- Name + Floor, !- Surface Type + FLOOR, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Surface, !- Outside Boundary Condition + Zn001:Flr001, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + 1.000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,0.000000,0.0, !- X,Y,Z ==> Vertex 1 {m} + 0.000000,0.000000,0.0, !- X,Y,Z ==> Vertex 2 {m} + 0.000000,15.24000,0.0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,0.0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Roof001, !- Name + Roof, !- Surface Type + ROOF_CONCRETE, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0, !- View Factor to Ground + 4, !- Number of Vertices + 0.000000,15.24000,4.572, !- X,Y,Z ==> Vertex 1 {m} + 0.000000,0.000000,4.572, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,0.000000,4.572, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,4.572; !- X,Y,Z ==> Vertex 4 {m} + +!- =========== ALL OBJECTS IN CLASS: EXTERIOR:LIGHTS =========== + + Exterior:Lights, + ExtLights, !- Name + AlwaysOn, !- Schedule Name + 5250, !- Design Level {W} + AstronomicalClock, !- Control Option + Grounds Lights; !- End-Use Subcategory + +!- =========== ALL OBJECTS IN CLASS: OUTPUT =========== + + Output:VariableDictionary,regular,Name; + + Output:Table:SummaryReports, + AllSummary; !- Report 1 Name + + OutputControl:Table:Style, + Comma; !- Column Separator (CSV for post-processing) + + Output:SQLite, + SimpleAndTabular; !- Option Type + + Output:Diagnostics, + DisplayAdvancedReportVariables; !- Key 1 + +! --- Environment --- + Output:Variable,*,Site Outdoor Air Drybulb Temperature,timestep; + Output:Variable,*,Site Sky Temperature,timestep; + +! --- Zone --- + Output:Variable,*,Zone Mean Air Temperature,timestep; + Output:Variable,*,Zone Mean Radiant Temperature,timestep; + +! --- Surface temperatures --- + Output:Variable,*,Surface Inside Face Temperature,timestep; + Output:Variable,*,Surface Outside Face Temperature,timestep; + +! --- Conduction through wall --- + Output:Variable,*,Surface Inside Face Conduction Heat Transfer Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Conduction Heat Transfer Rate per Area,timestep; + +! --- LW radiation (Q_LWR) — the target for Enet override --- + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Rate,timestep; + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Energy,timestep; + +! --- LW radiation coefficient breakdown --- + Output:Variable,*,Surface Outside Face Thermal Radiation to Air Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Outside Face Thermal Radiation to Sky Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Outside Face Thermal Radiation to Ground Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Outside Face Thermal Radiation to Air Heat Transfer Rate,timestep; + Output:Variable,*,Surface Outside Face Heat Emission to Air Rate,timestep; + +! --- Convection (should be unchanged between baseline and Enet runs) --- + Output:Variable,*,Surface Outside Face Convection Heat Gain Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Convection Heat Transfer Coefficient,timestep; + +! --- Inside face (not modified by Enet, for reference) --- + Output:Variable,*,Surface Inside Face Convection Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Inside Face Net Surface Thermal Radiation Heat Gain Rate per Area,timestep; + +! --- CondFD node temperatures --- + Output:Variable,*,CondFD Surface Temperature Node 1,timestep; + Output:Variable,*,CondFD Surface Temperature Node 2,timestep; + Output:Variable,*,CondFD Surface Temperature Node 3,timestep; + Output:Variable,*,CondFD Surface Temperature Node 4,timestep; + Output:Variable,*,CondFD Surface Temperature Node 5,timestep; + +! --- EMS Sky LW override output --- + Output:Variable,*,CondFD EMS Sky Longwave Radiation Override Heat Flux,timestep; + +! --- EMS Actuator: override sky LW radiation on roof --- + + EnergyManagementSystem:Actuator, + EnetOverride, !- Name + Zn001:Roof001, !- Actuated Component Unique Name + CondFD Surface, !- Actuated Component Type + Sky Longwave Radiation Override; !- Actuated Component Control Type + + EnergyManagementSystem:ProgramCallingManager, + Enet Override Manager, !- Name + BeginTimestepBeforePredictor, !- EnergyPlus Model Calling Point + SetEnetProgram; !- Program Name 1 + + EnergyManagementSystem:Program, + SetEnetProgram, !- Name + Set EnetOverride = -200.0; !- W/m2 (negative = sky cooling) diff --git a/testfiles/1ZoneCondFD_Enet_Test.idf b/testfiles/1ZoneCondFD_Enet_Test.idf new file mode 100644 index 00000000000..280f5427280 --- /dev/null +++ b/testfiles/1ZoneCondFD_Enet_Test.idf @@ -0,0 +1,377 @@ +!-Generator IDFEditor 1.34 +!-Option SortedOrder +! 1ZoneCondFD_Enet_Test.idf +! Simple 1-zone box with CondFD heat balance for testing Enet (LW radiation override). +! Based on 1ZoneUncontrolledCondFDWithVariableKat24C.idf. +! Changes: plain HW concrete roof (no variable-K), comprehensive outside face +! heat balance outputs at timestep frequency, CSV output format. + + Version,26.1; + +!- =========== ALL OBJECTS IN CLASS: SIMULATIONCONTROL =========== + + SimulationControl, + No, !- Do Zone Sizing Calculation + No, !- Do System Sizing Calculation + No, !- Do Plant Sizing Calculation + Yes, !- Run Simulation for Sizing Periods + No, !- Run Simulation for Weather File Run Periods + No, !- Do HVAC Sizing Simulation for Sizing Periods + 1; !- Maximum Number of HVAC Sizing Simulation Passes + +!- =========== ALL OBJECTS IN CLASS: BUILDING =========== + + Building, + Enet LW Test Box, !- Name + 0, !- North Axis {deg} + Suburbs, !- Terrain + 0.04, !- Loads Convergence Tolerance Value {W} + 0.004, !- Temperature Convergence Tolerance Value {deltaC} + MinimalShadowing, !- Solar Distribution + 30, !- Maximum Number of Warmup Days + 6; !- Minimum Number of Warmup Days + +!- =========== ALL OBJECTS IN CLASS: ALGORITHMS =========== + + SurfaceConvectionAlgorithm:Inside,TARP; + + SurfaceConvectionAlgorithm:Outside,TARP; + + HeatBalanceAlgorithm,ConductionFiniteDifference; + + Timestep,20; + +!- =========== ALL OBJECTS IN CLASS: SITE:LOCATION =========== + + Site:Location, + Denver Centennial Golden N_CO_USA Design_Conditions, !- Name + 39.74, !- Latitude {deg} + -105.18, !- Longitude {deg} + -7.00, !- Time Zone {hr} + 1829.00; !- Elevation {m} + +!- =========== ALL OBJECTS IN CLASS: SIZINGPERIOD:DESIGNDAY =========== + + SizingPeriod:DesignDay, + Denver Centennial Golden Intl Arpt Ann Htg 99% Condns DB, !- Name + 12, !- Month + 21, !- Day of Month + WinterDesignDay, !- Day Type + -16, !- Maximum Dry-Bulb Temperature {C} + 0.0, !- Daily Dry-Bulb Temperature Range {deltaC} + , !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Day Schedule Name + Wetbulb, !- Humidity Condition Type + -16, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C} + , !- Humidity Condition Day Schedule Name + , !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir} + , !- Enthalpy at Maximum Dry-Bulb {J/kg} + , !- Daily Wet-Bulb Temperature Range {deltaC} + 83411., !- Barometric Pressure {Pa} + 2.3, !- Wind Speed {m/s} + 180, !- Wind Direction {deg} + No, !- Rain Indicator + No, !- Snow Indicator + No, !- Daylight Saving Time Indicator + ASHRAEClearSky, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + , !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless} + , !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless} + 0.00; !- Sky Clearness + + SizingPeriod:DesignDay, + Denver Centennial Golden Intl Arpt Ann Clg 1% Condns DB=>MWB, !- Name + 7, !- Month + 21, !- Day of Month + SummerDesignDay, !- Day Type + 32.6, !- Maximum Dry-Bulb Temperature {C} + 15.2, !- Daily Dry-Bulb Temperature Range {deltaC} + , !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Day Schedule Name + Wetbulb, !- Humidity Condition Type + 15.6, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C} + , !- Humidity Condition Day Schedule Name + , !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir} + , !- Enthalpy at Maximum Dry-Bulb {J/kg} + , !- Daily Wet-Bulb Temperature Range {deltaC} + 83411., !- Barometric Pressure {Pa} + 4, !- Wind Speed {m/s} + 120, !- Wind Direction {deg} + No, !- Rain Indicator + No, !- Snow Indicator + No, !- Daylight Saving Time Indicator + ASHRAEClearSky, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + , !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless} + , !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless} + 1.00; !- Sky Clearness + +!- =========== ALL OBJECTS IN CLASS: SITE:GROUNDTEMPERATURE:BUILDINGSURFACE =========== + + Site:GroundTemperature:BuildingSurface,18.89,18.92,19.02,19.12,19.21,19.23,19.07,19.32,19.09,19.21,19.13,18.96; + +!- =========== ALL OBJECTS IN CLASS: SCHEDULETYPELIMITS =========== + + ScheduleTypeLimits, + Fraction, !- Name + 0.0, !- Lower Limit Value + 1.0, !- Upper Limit Value + CONTINUOUS; !- Numeric Type + + ScheduleTypeLimits, + On/Off, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + DISCRETE; !- Numeric Type + +!- =========== ALL OBJECTS IN CLASS: SCHEDULES =========== + + Schedule:Day:Hourly, + On, !- Name + On/Off, !- Schedule Type Limits Name + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.; + + Schedule:Week:Daily, + On Weeks, !- Name + On,On,On,On,On,On,On,On,On,On,On,On; + + Schedule:Year, + AlwaysOn, !- Name + On/Off, !- Schedule Type Limits Name + On Weeks, !- Schedule:Week Name 1 + 1, !- Start Month 1 + 1, !- Start Day 1 + 12, !- End Month 1 + 31; !- End Day 1 + +!- =========== ALL OBJECTS IN CLASS: MATERIAL =========== + + Material, + C5 - 4 IN HW CONCRETE, !- Name + MediumRough, !- Roughness + 0.1014984, !- Thickness {m} + 1.729577, !- Conductivity {W/m-K} + 2242.585, !- Density {kg/m3} + 836.8000, !- Specific Heat {J/kg-K} + 0.9000000, !- Thermal Absorptance + 0.6500000, !- Solar Absorptance + 0.6500000; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: MATERIAL:NOMASS =========== + + Material:NoMass, + R13LAYER, !- Name + Rough, !- Roughness + 2.290965, !- Thermal Resistance {m2-K/W} + 0.9000000, !- Thermal Absorptance + 0.7500000, !- Solar Absorptance + 0.7500000; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: CONSTRUCTION =========== + + Construction, + R13WALL, !- Name + R13LAYER; !- Outside Layer + + Construction, + FLOOR, !- Name + C5 - 4 IN HW CONCRETE; !- Outside Layer + + Construction, + ROOF_CONCRETE, !- Name + C5 - 4 IN HW CONCRETE; !- Outside Layer + +!- =========== ALL OBJECTS IN CLASS: GLOBALGEOMETRYRULES =========== + + GlobalGeometryRules, + UpperLeftCorner, !- Starting Vertex Position + CounterClockWise, !- Vertex Entry Direction + World; !- Coordinate System + +!- =========== ALL OBJECTS IN CLASS: ZONE =========== + + Zone, + ZONE ONE, !- Name + 0, !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + 1, !- Type + 1, !- Multiplier + autocalculate, !- Ceiling Height {m} + autocalculate; !- Volume {m3} + +!- =========== ALL OBJECTS IN CLASS: BUILDINGSURFACE:DETAILED =========== + + BuildingSurface:Detailed, + Zn001:Wall001, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 0,0,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 0,0,0, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,0,0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,0,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall002, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,0,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 15.24000,0,0, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,15.24000,0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall003, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,15.24000,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 15.24000,15.24000,0, !- X,Y,Z ==> Vertex 2 {m} + 0,15.24000,0, !- X,Y,Z ==> Vertex 3 {m} + 0,15.24000,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall004, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 0,15.24000,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 0,15.24000,0, !- X,Y,Z ==> Vertex 2 {m} + 0,0,0, !- X,Y,Z ==> Vertex 3 {m} + 0,0,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Flr001, !- Name + Floor, !- Surface Type + FLOOR, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Surface, !- Outside Boundary Condition + Zn001:Flr001, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + 1.000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,0.000000,0.0, !- X,Y,Z ==> Vertex 1 {m} + 0.000000,0.000000,0.0, !- X,Y,Z ==> Vertex 2 {m} + 0.000000,15.24000,0.0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,0.0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Roof001, !- Name + Roof, !- Surface Type + ROOF_CONCRETE, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0, !- View Factor to Ground + 4, !- Number of Vertices + 0.000000,15.24000,4.572, !- X,Y,Z ==> Vertex 1 {m} + 0.000000,0.000000,4.572, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,0.000000,4.572, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,4.572; !- X,Y,Z ==> Vertex 4 {m} + +!- =========== ALL OBJECTS IN CLASS: EXTERIOR:LIGHTS =========== + + Exterior:Lights, + ExtLights, !- Name + AlwaysOn, !- Schedule Name + 5250, !- Design Level {W} + AstronomicalClock, !- Control Option + Grounds Lights; !- End-Use Subcategory + +!- =========== ALL OBJECTS IN CLASS: OUTPUT =========== + + Output:VariableDictionary,regular,Name; + + Output:Table:SummaryReports, + AllSummary; !- Report 1 Name + + OutputControl:Table:Style, + Comma; !- Column Separator (CSV for post-processing) + + Output:SQLite, + SimpleAndTabular; !- Option Type + + Output:Diagnostics, + DisplayAdvancedReportVariables; !- Key 1 + +! --- Environment --- + Output:Variable,*,Site Outdoor Air Drybulb Temperature,timestep; + Output:Variable,*,Site Sky Temperature,timestep; + +! --- Zone --- + Output:Variable,*,Zone Mean Air Temperature,timestep; + Output:Variable,*,Zone Mean Radiant Temperature,timestep; + +! --- Surface temperatures --- + Output:Variable,*,Surface Inside Face Temperature,timestep; + Output:Variable,*,Surface Outside Face Temperature,timestep; + +! --- Conduction through wall --- + Output:Variable,*,Surface Inside Face Conduction Heat Transfer Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Conduction Heat Transfer Rate per Area,timestep; + +! --- LW radiation (Q_LWR) — the target for Enet override --- + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Rate,timestep; + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Energy,timestep; + +! --- LW radiation coefficient breakdown --- + Output:Variable,*,Surface Outside Face Thermal Radiation to Air Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Outside Face Thermal Radiation to Sky Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Outside Face Thermal Radiation to Ground Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Outside Face Thermal Radiation to Air Heat Transfer Rate,timestep; + Output:Variable,*,Surface Outside Face Heat Emission to Air Rate,timestep; + +! --- Convection (should be unchanged between baseline and Enet runs) --- + Output:Variable,*,Surface Outside Face Convection Heat Gain Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Convection Heat Transfer Coefficient,timestep; + +! --- Inside face (not modified by Enet, for reference) --- + Output:Variable,*,Surface Inside Face Convection Heat Transfer Coefficient,timestep; + Output:Variable,*,Surface Inside Face Net Surface Thermal Radiation Heat Gain Rate per Area,timestep; + +! --- CondFD node temperatures --- + Output:Variable,*,CondFD Surface Temperature Node 1,timestep; + Output:Variable,*,CondFD Surface Temperature Node 2,timestep; + Output:Variable,*,CondFD Surface Temperature Node 3,timestep; + Output:Variable,*,CondFD Surface Temperature Node 4,timestep; + Output:Variable,*,CondFD Surface Temperature Node 5,timestep; diff --git a/testfiles/CMakeLists.txt b/testfiles/CMakeLists.txt index 8ea128fe03c..42715b11f94 100644 --- a/testfiles/CMakeLists.txt +++ b/testfiles/CMakeLists.txt @@ -27,6 +27,8 @@ add_simulation_test(IDF_FILE 1ZoneUncontrolled.idf EPW_FILE USA_CO_Golden-NREL.7 add_simulation_test(IDF_FILE _1ZoneUncontrolled_9161.idf EPW_FILE weather_file_issue_9161.epw) add_simulation_test(IDF_FILE 1ZoneUncontrolled3SurfaceZone.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw) add_simulation_test(IDF_FILE 1ZoneUncontrolledAnnualOutputs.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw) +add_simulation_test(IDF_FILE 1ZoneCondFD_Enet_Test.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw DESIGN_DAY_ONLY) +add_simulation_test(IDF_FILE 1ZoneCondFD_Enet_EMS.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw DESIGN_DAY_ONLY) add_simulation_test(IDF_FILE 1ZoneUncontrolledCondFDWithVariableKat24C.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw) add_simulation_test(IDF_FILE 1ZoneUncontrolledFourAlgorithms.idf EPW_FILE 94810-1956.epw) add_simulation_test(IDF_FILE 1ZoneUncontrolledResLayers.idf EPW_FILE 94810-1956.epw) @@ -493,6 +495,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) diff --git a/testfiles/PythonPluginCondFD_Enet.idf b/testfiles/PythonPluginCondFD_Enet.idf new file mode 100644 index 00000000000..76ea8618f99 --- /dev/null +++ b/testfiles/PythonPluginCondFD_Enet.idf @@ -0,0 +1,367 @@ +!-Generator IDFEditor 1.34 +!-Option SortedOrder +! PythonPluginCondFD_Enet.idf +! Python plugin test for CondFD sky LW radiation override. +! Based on 1ZoneCondFD_Enet_Test.idf with PythonPlugin setting Enet = -200 W/m2. + + Version,26.1; + +!- =========== ALL OBJECTS IN CLASS: SIMULATIONCONTROL =========== + + SimulationControl, + No, !- Do Zone Sizing Calculation + No, !- Do System Sizing Calculation + No, !- Do Plant Sizing Calculation + Yes, !- Run Simulation for Sizing Periods + No, !- Run Simulation for Weather File Run Periods + No, !- Do HVAC Sizing Simulation for Sizing Periods + 1; !- Maximum Number of HVAC Sizing Simulation Passes + +!- =========== ALL OBJECTS IN CLASS: BUILDING =========== + + Building, + Enet LW Test Box, !- Name + 0, !- North Axis {deg} + Suburbs, !- Terrain + 0.04, !- Loads Convergence Tolerance Value {W} + 0.004, !- Temperature Convergence Tolerance Value {deltaC} + MinimalShadowing, !- Solar Distribution + 30, !- Maximum Number of Warmup Days + 6; !- Minimum Number of Warmup Days + +!- =========== ALL OBJECTS IN CLASS: ALGORITHMS =========== + + SurfaceConvectionAlgorithm:Inside,TARP; + + SurfaceConvectionAlgorithm:Outside,TARP; + + HeatBalanceAlgorithm,ConductionFiniteDifference; + + Timestep,20; + +!- =========== ALL OBJECTS IN CLASS: SITE:LOCATION =========== + + Site:Location, + Denver Centennial Golden N_CO_USA Design_Conditions, !- Name + 39.74, !- Latitude {deg} + -105.18, !- Longitude {deg} + -7.00, !- Time Zone {hr} + 1829.00; !- Elevation {m} + +!- =========== ALL OBJECTS IN CLASS: SIZINGPERIOD:DESIGNDAY =========== + + SizingPeriod:DesignDay, + Denver Centennial Golden Intl Arpt Ann Htg 99% Condns DB, !- Name + 12, !- Month + 21, !- Day of Month + WinterDesignDay, !- Day Type + -16, !- Maximum Dry-Bulb Temperature {C} + 0.0, !- Daily Dry-Bulb Temperature Range {deltaC} + , !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Day Schedule Name + Wetbulb, !- Humidity Condition Type + -16, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C} + , !- Humidity Condition Day Schedule Name + , !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir} + , !- Enthalpy at Maximum Dry-Bulb {J/kg} + , !- Daily Wet-Bulb Temperature Range {deltaC} + 83411., !- Barometric Pressure {Pa} + 2.3, !- Wind Speed {m/s} + 180, !- Wind Direction {deg} + No, !- Rain Indicator + No, !- Snow Indicator + No, !- Daylight Saving Time Indicator + ASHRAEClearSky, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + , !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless} + , !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless} + 0.00; !- Sky Clearness + + SizingPeriod:DesignDay, + Denver Centennial Golden Intl Arpt Ann Clg 1% Condns DB=>MWB, !- Name + 7, !- Month + 21, !- Day of Month + SummerDesignDay, !- Day Type + 32.6, !- Maximum Dry-Bulb Temperature {C} + 15.2, !- Daily Dry-Bulb Temperature Range {deltaC} + , !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Day Schedule Name + Wetbulb, !- Humidity Condition Type + 15.6, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C} + , !- Humidity Condition Day Schedule Name + , !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir} + , !- Enthalpy at Maximum Dry-Bulb {J/kg} + , !- Daily Wet-Bulb Temperature Range {deltaC} + 83411., !- Barometric Pressure {Pa} + 4, !- Wind Speed {m/s} + 120, !- Wind Direction {deg} + No, !- Rain Indicator + No, !- Snow Indicator + No, !- Daylight Saving Time Indicator + ASHRAEClearSky, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + , !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless} + , !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless} + 1.00; !- Sky Clearness + +!- =========== ALL OBJECTS IN CLASS: SITE:GROUNDTEMPERATURE:BUILDINGSURFACE =========== + + Site:GroundTemperature:BuildingSurface,18.89,18.92,19.02,19.12,19.21,19.23,19.07,19.32,19.09,19.21,19.13,18.96; + +!- =========== ALL OBJECTS IN CLASS: SCHEDULETYPELIMITS =========== + + ScheduleTypeLimits, + Fraction, !- Name + 0.0, !- Lower Limit Value + 1.0, !- Upper Limit Value + CONTINUOUS; !- Numeric Type + + ScheduleTypeLimits, + On/Off, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + DISCRETE; !- Numeric Type + +!- =========== ALL OBJECTS IN CLASS: SCHEDULES =========== + + Schedule:Day:Hourly, + On, !- Name + On/Off, !- Schedule Type Limits Name + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.; + + Schedule:Week:Daily, + On Weeks, !- Name + On,On,On,On,On,On,On,On,On,On,On,On; + + Schedule:Year, + AlwaysOn, !- Name + On/Off, !- Schedule Type Limits Name + On Weeks, !- Schedule:Week Name 1 + 1, !- Start Month 1 + 1, !- Start Day 1 + 12, !- End Month 1 + 31; !- End Day 1 + +!- =========== ALL OBJECTS IN CLASS: MATERIAL =========== + + Material, + C5 - 4 IN HW CONCRETE, !- Name + MediumRough, !- Roughness + 0.1014984, !- Thickness {m} + 1.729577, !- Conductivity {W/m-K} + 2242.585, !- Density {kg/m3} + 836.8000, !- Specific Heat {J/kg-K} + 0.9000000, !- Thermal Absorptance + 0.6500000, !- Solar Absorptance + 0.6500000; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: MATERIAL:NOMASS =========== + + Material:NoMass, + R13LAYER, !- Name + Rough, !- Roughness + 2.290965, !- Thermal Resistance {m2-K/W} + 0.9000000, !- Thermal Absorptance + 0.7500000, !- Solar Absorptance + 0.7500000; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: CONSTRUCTION =========== + + Construction, + R13WALL, !- Name + R13LAYER; !- Outside Layer + + Construction, + FLOOR, !- Name + C5 - 4 IN HW CONCRETE; !- Outside Layer + + Construction, + ROOF_CONCRETE, !- Name + C5 - 4 IN HW CONCRETE; !- Outside Layer + +!- =========== ALL OBJECTS IN CLASS: GLOBALGEOMETRYRULES =========== + + GlobalGeometryRules, + UpperLeftCorner, !- Starting Vertex Position + CounterClockWise, !- Vertex Entry Direction + World; !- Coordinate System + +!- =========== ALL OBJECTS IN CLASS: ZONE =========== + + Zone, + ZONE ONE, !- Name + 0, !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + 1, !- Type + 1, !- Multiplier + autocalculate, !- Ceiling Height {m} + autocalculate; !- Volume {m3} + +!- =========== ALL OBJECTS IN CLASS: BUILDINGSURFACE:DETAILED =========== + + BuildingSurface:Detailed, + Zn001:Wall001, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 0,0,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 0,0,0, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,0,0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,0,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall002, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,0,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 15.24000,0,0, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,15.24000,0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall003, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,15.24000,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 15.24000,15.24000,0, !- X,Y,Z ==> Vertex 2 {m} + 0,15.24000,0, !- X,Y,Z ==> Vertex 3 {m} + 0,15.24000,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Wall004, !- Name + Wall, !- Surface Type + R13WALL, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0.5000000, !- View Factor to Ground + 4, !- Number of Vertices + 0,15.24000,4.572000, !- X,Y,Z ==> Vertex 1 {m} + 0,15.24000,0, !- X,Y,Z ==> Vertex 2 {m} + 0,0,0, !- X,Y,Z ==> Vertex 3 {m} + 0,0,4.572000; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Flr001, !- Name + Floor, !- Surface Type + FLOOR, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Surface, !- Outside Boundary Condition + Zn001:Flr001, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + 1.000000, !- View Factor to Ground + 4, !- Number of Vertices + 15.24000,0.000000,0.0, !- X,Y,Z ==> Vertex 1 {m} + 0.000000,0.000000,0.0, !- X,Y,Z ==> Vertex 2 {m} + 0.000000,15.24000,0.0, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,0.0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + Zn001:Roof001, !- Name + Roof, !- Surface Type + ROOF_CONCRETE, !- Construction Name + ZONE ONE, !- Zone Name + , !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + 0, !- View Factor to Ground + 4, !- Number of Vertices + 0.000000,15.24000,4.572, !- X,Y,Z ==> Vertex 1 {m} + 0.000000,0.000000,4.572, !- X,Y,Z ==> Vertex 2 {m} + 15.24000,0.000000,4.572, !- X,Y,Z ==> Vertex 3 {m} + 15.24000,15.24000,4.572; !- X,Y,Z ==> Vertex 4 {m} + +!- =========== ALL OBJECTS IN CLASS: EXTERIOR:LIGHTS =========== + + Exterior:Lights, + ExtLights, !- Name + AlwaysOn, !- Schedule Name + 5250, !- Design Level {W} + AstronomicalClock, !- Control Option + Grounds Lights; !- End-Use Subcategory + +!- =========== ALL OBJECTS IN CLASS: PYTHONPLUGIN =========== + + PythonPlugin:Instance, + EnetOverridePlugin, !- Name + Yes, !- Run During Warmup Days + PythonPluginCondFD_Enet, !- Python Module Name + EnetSkyOverride; !- Plugin Class Name + +!- =========== ALL OBJECTS IN CLASS: OUTPUT =========== + + Output:VariableDictionary,regular,Name; + + Output:Table:SummaryReports, + AllSummary; !- Report 1 Name + + OutputControl:Table:Style, + Comma; !- Column Separator (CSV for post-processing) + + Output:SQLite, + SimpleAndTabular; !- Option Type + + Output:Diagnostics, + DisplayAdvancedReportVariables; !- Key 1 + +! --- Environment --- + Output:Variable,*,Site Outdoor Air Drybulb Temperature,timestep; + Output:Variable,*,Site Sky Temperature,timestep; + +! --- Zone --- + Output:Variable,*,Zone Mean Air Temperature,timestep; + Output:Variable,*,Zone Mean Radiant Temperature,timestep; + +! --- Surface temperatures --- + Output:Variable,*,Surface Inside Face Temperature,timestep; + Output:Variable,*,Surface Outside Face Temperature,timestep; + +! --- Conduction through wall --- + Output:Variable,*,Surface Inside Face Conduction Heat Transfer Rate per Area,timestep; + Output:Variable,*,Surface Outside Face Conduction Heat Transfer Rate per Area,timestep; + +! --- LW radiation --- + Output:Variable,*,Surface Outside Face Net Thermal Radiation Heat Gain Rate per Area,timestep; + +! --- CondFD node temperatures --- + Output:Variable,*,CondFD Surface Temperature Node 1,timestep; + Output:Variable,*,CondFD Surface Temperature Node 2,timestep; + Output:Variable,*,CondFD Surface Temperature Node 3,timestep; + +! --- EMS Sky LW override output --- + Output:Variable,*,CondFD EMS Sky Longwave Radiation Override Heat Flux,timestep; diff --git a/testfiles/PythonPluginCondFD_Enet.py b/testfiles/PythonPluginCondFD_Enet.py new file mode 100644 index 00000000000..459f9d5d1b5 --- /dev/null +++ b/testfiles/PythonPluginCondFD_Enet.py @@ -0,0 +1,111 @@ +# EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the +# University of Illinois, The Regents of the University of California, through +# Lawrence Berkeley National Laboratory (subject to receipt of any required +# approvals from the U.S. Dept. of Energy), Oak Ridge National Laboratory, +# managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other +# contributors. All rights reserved. +# +# NOTICE: This Software was developed under funding from the U.S. Department of +# Energy and the U.S. Government consequently retains certain rights. As such, +# the U.S. Government has been granted for itself and others acting on its +# behalf a paid-up, nonexclusive, irrevocable, worldwide license in the +# Software to reproduce, distribute copies to the public, prepare derivative +# works, and perform publicly and display publicly, and to permit others to do +# so. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# (1) Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# (2) Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# (3) Neither the name of the University of California, Lawrence Berkeley +# National Laboratory, the University of Illinois, U.S. Dept. of Energy nor +# the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in +# stand-alone form without changes from the version obtained under this +# License, or (ii) Licensee makes a reference solely to the software +# portion of its product, Licensee must refer to the software as +# "EnergyPlus version X" software, where "X" is the version number Licensee +# obtained under this License and may not use a different name for the +# software. Except as specifically required in this Section (4), Licensee +# shall not use in a company name, a product name, in advertising, +# publicity, or other promotional activities any name, trade name, +# trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or +# confusingly similar designation, without the U.S. Department of Energy's +# prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +"""CondFD Sky Longwave Radiation Override — Python Plugin Example. + +Overrides the sky LW radiation term on a CondFD exterior surface with a +constant Enet value (W/m2). Positive = heat INTO surface. + +To use with any IDF: +1. Set SURFACE_NAME to your target exterior CondFD surface +2. Set ENET_VALUE to desired sky LW flux (W/m2) +3. Add to your IDF: + + PythonPlugin:SearchPaths, + Yes, !- Add Current Working Directory + Yes; !- Add Input File Directory + + PythonPlugin:Instance, + EnetOverridePlugin, !- Name + Yes, !- Run During Warmup Days + PythonPluginCondFD_Enet, !- Module Name + EnetSkyOverride; !- Class Name +""" + +from pyenergyplus.plugin import EnergyPlusPlugin + +# === Configuration === +SURFACE_NAME = "Zn001:Roof001" +ENET_VALUE = -200.0 # W/m2 + + +class EnetSkyOverride(EnergyPlusPlugin): + + def __init__(self): + super().__init__() + self.need_to_get_handles = True + self.enet_handle = None + + def on_begin_timestep_before_predictor(self, state) -> int: + if self.need_to_get_handles: + if not self.api.exchange.api_data_fully_ready(state): + return 0 + self.enet_handle = self.api.exchange.get_actuator_handle( + state, + "CondFD Surface", + "Sky Longwave Radiation Override", + SURFACE_NAME, + ) + if self.enet_handle == -1: + self.api.runtime.issue_severe( + state, + f"EnetSkyOverride: no actuator for surface '{SURFACE_NAME}'. " + "Surface must be exterior + ConductionFiniteDifference.", + ) + return 1 + self.need_to_get_handles = False + + self.api.exchange.set_actuator_value(state, self.enet_handle, ENET_VALUE) + return 0 diff --git a/tst/EnergyPlus/unit/HeatBalFiniteDiffManager.unit.cc b/tst/EnergyPlus/unit/HeatBalFiniteDiffManager.unit.cc index 9b1a4757839..54d5b2eac9e 100644 --- a/tst/EnergyPlus/unit/HeatBalFiniteDiffManager.unit.cc +++ b/tst/EnergyPlus/unit/HeatBalFiniteDiffManager.unit.cc @@ -55,6 +55,8 @@ #include #include #include +#include +#include #include #include #include @@ -897,4 +899,672 @@ TEST_F(EnergyPlusFixture, HeatBalFiniteDiffManager_setSizeMaxPropertiesTest) EXPECT_EQ(functionAnswer, expectedAnswer); } +TEST_F(EnergyPlusFixture, HeatBalFiniteDiffManager_EnetActuatorOverride) +{ + // Test that the sky LW radiation EMS actuator correctly modifies ExteriorBCEqns behavior. + // Uses FullyImplicitFirstOrder scheme with a single-layer concrete surface. + // Compares: actuator OFF (baseline), ON with Enet=0 (no sky cooling), ON with Enet=-200 (strong sky cooling). + + int constexpr SurfNum = 1; + int constexpr TotNodes = 3; + int constexpr TotLayers = 1; + + // Allocate surfaces + state->dataSurface->TotSurfaces = 1; + state->dataSurface->Surface.allocate(1); + auto &surf = state->dataSurface->Surface(SurfNum); + surf.Name = "ZN001:ROOF001"; + surf.HeatTransSurf = true; + surf.HeatTransferAlgorithm = DataSurfaces::HeatTransferModel::CondFD; + surf.ExtBoundCond = DataSurfaces::ExternalEnvironment; + surf.Construction = 1; + surf.Area = 10.0; + surf.Class = DataSurfaces::SurfaceClass::Roof; + + // Construct with one concrete layer + state->dataHeatBal->TotConstructs = 1; + state->dataConstruction->Construct.allocate(1); + auto &constr = state->dataConstruction->Construct(1); + constr.TotLayers = TotLayers; + constr.LayerPoint.allocate(TotLayers); + constr.LayerPoint(1) = 1; + + // Material: HW concrete + auto *mat = new Material::MaterialBase; + mat->Name = "C5 - 4 IN HW CONCRETE"; + mat->group = Material::Group::Regular; + mat->Roughness = Material::SurfaceRoughness::MediumRough; + mat->Thickness = 0.1016; + mat->Conductivity = 1.311; + mat->Density = 2240.0; + mat->SpecHeat = 836.8; + mat->AbsorpThermal = 0.9; + mat->AbsorpSolar = 0.85; + mat->AbsorpVisible = 0.85; + mat->ROnly = false; + mat->hasPCM = false; + state->dataMaterial->materials.push_back(mat); + mat->Num = state->dataMaterial->materials.isize(); + + // CondFD data + auto &s_hbfd = state->dataHeatBalFiniteDiffMgr; + s_hbfd->CondFDSchemeType = CondFDScheme::FullyImplicitFirstOrder; + + // ConstructFD + s_hbfd->ConstructFD.allocate(1); + s_hbfd->ConstructFD(1).TotNodes = TotNodes; + s_hbfd->ConstructFD(1).DelX.allocate(TotLayers); + s_hbfd->ConstructFD(1).DelX(1) = 0.0254; // ~1 inch per node + s_hbfd->ConstructFD(1).NodeNumPoint.allocate(TotLayers); + s_hbfd->ConstructFD(1).NodeNumPoint(1) = TotNodes; + + // MaterialFD + s_hbfd->MaterialFD.allocate(1); + s_hbfd->MaterialFD(1).tk1 = 0.0; + s_hbfd->MaterialFD(1).numTempEnth = 0; + s_hbfd->MaterialFD(1).numTempCond = 0; + // TempCond and TempEnth: 2D arrays with negative sentinel values to skip variable property branches + s_hbfd->MaterialFD(1).TempCond.allocate(2, 3); + s_hbfd->MaterialFD(1).TempCond = -1.0; + s_hbfd->MaterialFD(1).TempEnth.allocate(2, 3); + s_hbfd->MaterialFD(1).TempEnth = -1.0; + + // SurfaceFD + s_hbfd->SurfaceFD.allocate(1); + auto &surfFD = s_hbfd->SurfaceFD(SurfNum); + int const numNodes = TotNodes + 1; // include inside face node + surfFD.T.allocate(numNodes); + surfFD.TOld.allocate(numNodes); + surfFD.TT.allocate(numNodes); + surfFD.Rhov.allocate(numNodes); + surfFD.RhovOld.allocate(numNodes); + surfFD.RhoT.allocate(numNodes); + surfFD.TD.allocate(numNodes); + surfFD.TDT.allocate(numNodes); + surfFD.TDTLast.allocate(numNodes); + surfFD.TDOld.allocate(numNodes); + surfFD.TDreport.allocate(numNodes); + surfFD.RH.allocate(numNodes); + surfFD.RHreport.allocate(numNodes); + surfFD.EnthOld.allocate(numNodes); + surfFD.EnthNew.allocate(numNodes); + surfFD.EnthLast.allocate(numNodes); + surfFD.QDreport.allocate(numNodes); + surfFD.CpDelXRhoS1.allocate(numNodes); + surfFD.CpDelXRhoS2.allocate(numNodes); + surfFD.TDpriortimestep.allocate(numNodes); + surfFD.PhaseChangeState.allocate(numNodes); + surfFD.PhaseChangeStateOld.allocate(numNodes); + surfFD.PhaseChangeStateOldOld.allocate(numNodes); + surfFD.PhaseChangeStateRep.allocate(numNodes); + surfFD.PhaseChangeStateOldRep.allocate(numNodes); + surfFD.PhaseChangeStateOldOldRep.allocate(numNodes); + surfFD.PhaseChangeTemperatureReverse.allocate(numNodes); + surfFD.condMaterialActuators.allocate(TotLayers); + surfFD.specHeatMaterialActuators.allocate(TotLayers); + surfFD.condNodeReport.allocate(numNodes); + surfFD.specHeatNodeReport.allocate(numNodes); + surfFD.heatSourceFluxMaterialActuators.allocate(1); + surfFD.heatSourceInternalFluxLayerReport.allocate(1); + surfFD.heatSourceInternalFluxEnergyLayerReport.allocate(1); + surfFD.heatSourceEMSFluxLayerReport.allocate(1); + surfFD.heatSourceEMSFluxEnergyLayerReport.allocate(1); + + // Initialize arrays + surfFD.T = 20.0; + surfFD.TOld = 20.0; + surfFD.TT = 20.0; + surfFD.Rhov = 0.0; + surfFD.RhovOld = 0.0; + surfFD.RhoT = 0.0; + surfFD.TD = 20.0; + surfFD.TDT = 20.0; + surfFD.TDTLast = 20.0; + surfFD.TDOld = 20.0; + surfFD.TDreport = 20.0; + surfFD.RH = 0.0; + surfFD.RHreport = 0.0; + surfFD.EnthOld = 100.0; + surfFD.EnthNew = 100.0; + surfFD.EnthLast = 100.0; + surfFD.QDreport = 0.0; + surfFD.CpDelXRhoS1 = 0.0; + surfFD.CpDelXRhoS2 = 0.0; + surfFD.TDpriortimestep = 20.0; + surfFD.PhaseChangeState = Material::Phase::Transition; + surfFD.PhaseChangeStateOld = Material::Phase::Transition; + surfFD.PhaseChangeStateOldOld = Material::Phase::Transition; + surfFD.PhaseChangeTemperatureReverse = 50.0; + surfFD.condNodeReport = 0.0; + surfFD.specHeatNodeReport = 0.0; + + // Allocate heat balance surface arrays + state->dataHeatBalSurf->SurfOpaqInsFaceCondFlux.allocate(1); + state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux.allocate(1); + state->dataHeatBalSurf->SurfQdotRadOutRepPerArea.allocate(1); + state->dataHeatBalSurf->SurfQdotRadOutRep.allocate(1); + state->dataHeatBalSurf->SurfQRadOutReport.allocate(1); + state->dataHeatBalSurf->SurfOpaqQRadSWOutAbs.allocate(1); + state->dataHeatBalSurf->SurfQRadSWOutMvIns.allocate(1); + state->dataHeatBalSurf->SurfOpaqInsFaceCondFlux(1) = 0.0; + state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1) = 0.0; + state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1) = 0.0; + state->dataHeatBalSurf->SurfQdotRadOutRep(1) = 0.0; + state->dataHeatBalSurf->SurfQRadOutReport(1) = 0.0; + state->dataHeatBalSurf->SurfOpaqQRadSWOutAbs(1) = 0.0; + state->dataHeatBalSurf->SurfQRadSWOutMvIns(1) = 0.0; + + // Allocate moisture balance arrays + state->dataMstBal->TempOutsideAirFD.allocate(1); + state->dataMstBal->RhoVaporAirOut.allocate(1); + state->dataMstBal->HConvExtFD.allocate(1); + state->dataMstBal->HSkyFD.allocate(1); + state->dataMstBal->HGrndFD.allocate(1); + state->dataMstBal->HAirFD.allocate(1); + state->dataMstBal->HSurrFD.allocate(1); + + // Set exterior boundary conditions + constexpr Real64 Toa = 10.0; // outdoor air temp, C + constexpr Real64 Tsky = -20.0; // sky temp, C + state->dataMstBal->TempOutsideAirFD(1) = Toa; + state->dataMstBal->RhoVaporAirOut(1) = 0.005; + state->dataMstBal->HConvExtFD(1) = 10.0; // convection coefficient + state->dataMstBal->HSkyFD(1) = 5.0; // sky radiation coefficient + state->dataMstBal->HGrndFD(1) = 3.0; // ground radiation coefficient + state->dataMstBal->HAirFD(1) = 1.0; // air radiation coefficient + state->dataMstBal->HSurrFD(1) = 0.0; // surrounding surfaces coefficient + + state->dataEnvrn->SkyTemp = Tsky; + state->dataEnvrn->IsRain = false; + state->dataGlobal->TimeStepZoneSec = 600.0; + + // Surface ground temp + surf.UseSurfPropertyGndSurfTemp = false; + surf.SurfHasSurroundingSurfProperty = false; + + // QHeatOutFlux + s_hbfd->QHeatOutFlux.allocate(1); + s_hbfd->QHeatOutFlux(1) = 0.0; + + // ExteriorBCEqns parameters + constexpr int Delt = 600; + constexpr int nodeIdx = 1; // outside face node + constexpr int Lay = 1; // first layer + constexpr Real64 HMovInsul = 0.0; + + // Create local arrays for function call + Array1D T_arr(numNodes, 20.0); + Array1D TT_arr(numNodes, 20.0); + Array1D Rhov_arr(numNodes, 0.0); + Array1D RhoT_arr(numNodes, 0.0); + Array1D RH_arr(numNodes, 0.0); + Array1D TD_arr(numNodes, 20.0); + Array1D TDT_arr(numNodes, 20.0); + Array1D EnthOld_arr(numNodes, 100.0); + Array1D EnthNew_arr(numNodes, 100.0); + + // --- Sub-case 1: Actuator OFF (baseline with sky radiation) --- + surfFD.enetActuator.isActuated = false; + surfFD.enetActuator.actuatedValue = 0.0; + TDT_arr = 20.0; // reset + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_baseline = TDT_arr(nodeIdx); + Real64 const QRad_baseline = state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1); + Real64 const CondFlux_baseline = state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1); + + // With sky at -20C and surface at 20C, baseline has large net outgoing radiation → QRad negative + EXPECT_LT(QRad_baseline, 0.0); + + // --- Sub-case 2: Actuator ON, Enet=0 (no sky LW exchange) --- + surfFD.enetActuator.isActuated = true; + surfFD.enetActuator.actuatedValue = 0.0; + TDT_arr = 20.0; // reset + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_enet0 = TDT_arr(nodeIdx); + Real64 const QRad_enet0 = state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1); + Real64 const CondFlux_enet0 = state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1); + + // With Enet=0, no sky cooling, so surface should be warmer than baseline (where sky cools it) + EXPECT_GT(TDT_enet0, TDT_baseline); + // Sky at -20C vs surface at 20C → baseline has large outgoing radiation; Enet=0 removes that sky term + EXPECT_GT(QRad_enet0, QRad_baseline); + // Less radiation out → less heat pulled through wall → smaller outward conduction flux + EXPECT_LT(CondFlux_enet0, CondFlux_baseline); + + // --- Sub-case 3: Actuator ON, Enet=-200 (strong sky cooling) --- + surfFD.enetActuator.isActuated = true; + surfFD.enetActuator.actuatedValue = -200.0; + TDT_arr = 20.0; // reset + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_enetNeg200 = TDT_arr(nodeIdx); + Real64 const QRad_enetNeg200 = state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1); + Real64 const CondFlux_enetNeg200 = state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1); + + // Enet=-200 is much stronger cooling than the default sky term, so surface should be colder + EXPECT_LT(TDT_enetNeg200, TDT_baseline); + + // --- Sub-case 4: Actuator ON, Enet=+200 (sky heats surface) --- + surfFD.enetActuator.isActuated = true; + surfFD.enetActuator.actuatedValue = 200.0; + TDT_arr = 20.0; // reset + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_enetPos200 = TDT_arr(nodeIdx); + Real64 const QRad_enetPos200 = state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1); + Real64 const CondFlux_enetPos200 = state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1); + + // Enet=+200 W/m² net incoming → QRad sign flips positive (surface gains radiation) + EXPECT_GT(QRad_enetPos200, 0.0); + + // Ordering: Enet=-200 < baseline < Enet=0 < Enet=+200 + EXPECT_LT(TDT_enetNeg200, TDT_baseline); + EXPECT_LT(TDT_baseline, TDT_enet0); + EXPECT_LT(TDT_enet0, TDT_enetPos200); + + // QRad and CondFlux have matching monotonic ordering: stronger cooling → more negative QRad, larger outward CondFlux + EXPECT_LT(QRad_enetNeg200, QRad_baseline); + EXPECT_LT(QRad_baseline, QRad_enet0); + EXPECT_LT(QRad_enet0, QRad_enetPos200); + EXPECT_GT(CondFlux_enetNeg200, CondFlux_baseline); + EXPECT_GT(CondFlux_baseline, CondFlux_enet0); + EXPECT_GT(CondFlux_enet0, CondFlux_enetPos200); + + // --- Sub-case 5: Actuator toggled back OFF → recovers baseline --- + surfFD.enetActuator.isActuated = false; + surfFD.enetActuator.actuatedValue = 0.0; + TDT_arr = 20.0; // reset + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + EXPECT_NEAR(TDT_arr(nodeIdx), TDT_baseline, 1e-10); + EXPECT_NEAR(state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1), QRad_baseline, 1e-10); + EXPECT_NEAR(state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1), CondFlux_baseline, 1e-10); +} + +TEST_F(EnergyPlusFixture, HeatBalFiniteDiffManager_EnetActuatorOverride_CrankNicolson) +{ + // Same as EnetActuatorOverride but with CrankNicholsonSecondOrder scheme. + // Verifies the CN code path produces the same temperature ordering. + + int constexpr SurfNum = 1; + int constexpr TotNodes = 3; + int constexpr TotLayers = 1; + + // Allocate surfaces + state->dataSurface->TotSurfaces = 1; + state->dataSurface->Surface.allocate(1); + auto &surf = state->dataSurface->Surface(SurfNum); + surf.Name = "ZN001:ROOF001"; + surf.HeatTransSurf = true; + surf.HeatTransferAlgorithm = DataSurfaces::HeatTransferModel::CondFD; + surf.ExtBoundCond = DataSurfaces::ExternalEnvironment; + surf.Construction = 1; + surf.Area = 10.0; + surf.Class = DataSurfaces::SurfaceClass::Roof; + + // Construct with one concrete layer + state->dataHeatBal->TotConstructs = 1; + state->dataConstruction->Construct.allocate(1); + auto &constr = state->dataConstruction->Construct(1); + constr.TotLayers = TotLayers; + constr.LayerPoint.allocate(TotLayers); + constr.LayerPoint(1) = 1; + + // Material: HW concrete + auto *mat = new Material::MaterialBase; + mat->Name = "C5 - 4 IN HW CONCRETE"; + mat->group = Material::Group::Regular; + mat->Roughness = Material::SurfaceRoughness::MediumRough; + mat->Thickness = 0.1016; + mat->Conductivity = 1.311; + mat->Density = 2240.0; + mat->SpecHeat = 836.8; + mat->AbsorpThermal = 0.9; + mat->AbsorpSolar = 0.85; + mat->AbsorpVisible = 0.85; + mat->ROnly = false; + mat->hasPCM = false; + state->dataMaterial->materials.push_back(mat); + mat->Num = state->dataMaterial->materials.isize(); + + // CondFD data — CrankNicholsonSecondOrder + auto &s_hbfd = state->dataHeatBalFiniteDiffMgr; + s_hbfd->CondFDSchemeType = CondFDScheme::CrankNicholsonSecondOrder; + + // ConstructFD + s_hbfd->ConstructFD.allocate(1); + s_hbfd->ConstructFD(1).TotNodes = TotNodes; + s_hbfd->ConstructFD(1).DelX.allocate(TotLayers); + s_hbfd->ConstructFD(1).DelX(1) = 0.0254; + s_hbfd->ConstructFD(1).NodeNumPoint.allocate(TotLayers); + s_hbfd->ConstructFD(1).NodeNumPoint(1) = TotNodes; + + // MaterialFD + s_hbfd->MaterialFD.allocate(1); + s_hbfd->MaterialFD(1).tk1 = 0.0; + s_hbfd->MaterialFD(1).numTempEnth = 0; + s_hbfd->MaterialFD(1).numTempCond = 0; + s_hbfd->MaterialFD(1).TempCond.allocate(2, 3); + s_hbfd->MaterialFD(1).TempCond = -1.0; + s_hbfd->MaterialFD(1).TempEnth.allocate(2, 3); + s_hbfd->MaterialFD(1).TempEnth = -1.0; + + // SurfaceFD + s_hbfd->SurfaceFD.allocate(1); + auto &surfFD = s_hbfd->SurfaceFD(SurfNum); + int const numNodes = TotNodes + 1; + surfFD.T.allocate(numNodes); + surfFD.TOld.allocate(numNodes); + surfFD.TT.allocate(numNodes); + surfFD.Rhov.allocate(numNodes); + surfFD.RhovOld.allocate(numNodes); + surfFD.RhoT.allocate(numNodes); + surfFD.TD.allocate(numNodes); + surfFD.TDT.allocate(numNodes); + surfFD.TDTLast.allocate(numNodes); + surfFD.TDOld.allocate(numNodes); + surfFD.TDreport.allocate(numNodes); + surfFD.RH.allocate(numNodes); + surfFD.RHreport.allocate(numNodes); + surfFD.EnthOld.allocate(numNodes); + surfFD.EnthNew.allocate(numNodes); + surfFD.EnthLast.allocate(numNodes); + surfFD.QDreport.allocate(numNodes); + surfFD.CpDelXRhoS1.allocate(numNodes); + surfFD.CpDelXRhoS2.allocate(numNodes); + surfFD.TDpriortimestep.allocate(numNodes); + surfFD.PhaseChangeState.allocate(numNodes); + surfFD.PhaseChangeStateOld.allocate(numNodes); + surfFD.PhaseChangeStateOldOld.allocate(numNodes); + surfFD.PhaseChangeStateRep.allocate(numNodes); + surfFD.PhaseChangeStateOldRep.allocate(numNodes); + surfFD.PhaseChangeStateOldOldRep.allocate(numNodes); + surfFD.PhaseChangeTemperatureReverse.allocate(numNodes); + surfFD.condMaterialActuators.allocate(TotLayers); + surfFD.specHeatMaterialActuators.allocate(TotLayers); + surfFD.condNodeReport.allocate(numNodes); + surfFD.specHeatNodeReport.allocate(numNodes); + surfFD.heatSourceFluxMaterialActuators.allocate(1); + surfFD.heatSourceInternalFluxLayerReport.allocate(1); + surfFD.heatSourceInternalFluxEnergyLayerReport.allocate(1); + surfFD.heatSourceEMSFluxLayerReport.allocate(1); + surfFD.heatSourceEMSFluxEnergyLayerReport.allocate(1); + + // Initialize arrays + surfFD.T = 20.0; + surfFD.TOld = 20.0; + surfFD.TT = 20.0; + surfFD.Rhov = 0.0; + surfFD.RhovOld = 0.0; + surfFD.RhoT = 0.0; + surfFD.TD = 20.0; + surfFD.TDT = 20.0; + surfFD.TDTLast = 20.0; + surfFD.TDOld = 20.0; + surfFD.TDreport = 20.0; + surfFD.RH = 0.0; + surfFD.RHreport = 0.0; + surfFD.EnthOld = 100.0; + surfFD.EnthNew = 100.0; + surfFD.EnthLast = 100.0; + surfFD.QDreport = 0.0; + surfFD.CpDelXRhoS1 = 0.0; + surfFD.CpDelXRhoS2 = 0.0; + surfFD.TDpriortimestep = 20.0; + surfFD.PhaseChangeState = Material::Phase::Transition; + surfFD.PhaseChangeStateOld = Material::Phase::Transition; + surfFD.PhaseChangeStateOldOld = Material::Phase::Transition; + surfFD.PhaseChangeTemperatureReverse = 50.0; + surfFD.condNodeReport = 0.0; + surfFD.specHeatNodeReport = 0.0; + + // Allocate heat balance surface arrays + state->dataHeatBalSurf->SurfOpaqInsFaceCondFlux.allocate(1); + state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux.allocate(1); + state->dataHeatBalSurf->SurfQdotRadOutRepPerArea.allocate(1); + state->dataHeatBalSurf->SurfQdotRadOutRep.allocate(1); + state->dataHeatBalSurf->SurfQRadOutReport.allocate(1); + state->dataHeatBalSurf->SurfOpaqQRadSWOutAbs.allocate(1); + state->dataHeatBalSurf->SurfQRadSWOutMvIns.allocate(1); + state->dataHeatBalSurf->SurfOpaqInsFaceCondFlux(1) = 0.0; + state->dataHeatBalSurf->SurfOpaqOutFaceCondFlux(1) = 0.0; + state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1) = 0.0; + state->dataHeatBalSurf->SurfQdotRadOutRep(1) = 0.0; + state->dataHeatBalSurf->SurfQRadOutReport(1) = 0.0; + state->dataHeatBalSurf->SurfOpaqQRadSWOutAbs(1) = 0.0; + state->dataHeatBalSurf->SurfQRadSWOutMvIns(1) = 0.0; + + // Allocate moisture balance arrays + state->dataMstBal->TempOutsideAirFD.allocate(1); + state->dataMstBal->RhoVaporAirOut.allocate(1); + state->dataMstBal->HConvExtFD.allocate(1); + state->dataMstBal->HSkyFD.allocate(1); + state->dataMstBal->HGrndFD.allocate(1); + state->dataMstBal->HAirFD.allocate(1); + state->dataMstBal->HSurrFD.allocate(1); + + // Set exterior boundary conditions + constexpr Real64 Toa = 10.0; + constexpr Real64 Tsky = -20.0; + state->dataMstBal->TempOutsideAirFD(1) = Toa; + state->dataMstBal->RhoVaporAirOut(1) = 0.005; + state->dataMstBal->HConvExtFD(1) = 10.0; + state->dataMstBal->HSkyFD(1) = 5.0; + state->dataMstBal->HGrndFD(1) = 3.0; + state->dataMstBal->HAirFD(1) = 1.0; + state->dataMstBal->HSurrFD(1) = 0.0; + + state->dataEnvrn->SkyTemp = Tsky; + state->dataEnvrn->IsRain = false; + state->dataGlobal->TimeStepZoneSec = 600.0; + + surf.UseSurfPropertyGndSurfTemp = false; + surf.SurfHasSurroundingSurfProperty = false; + + s_hbfd->QHeatOutFlux.allocate(1); + s_hbfd->QHeatOutFlux(1) = 0.0; + + constexpr int Delt = 600; + constexpr int nodeIdx = 1; + constexpr int Lay = 1; + constexpr Real64 HMovInsul = 0.0; + + int const numNodesArr = numNodes; + Array1D T_arr(numNodesArr, 20.0); + Array1D TT_arr(numNodesArr, 20.0); + Array1D Rhov_arr(numNodesArr, 0.0); + Array1D RhoT_arr(numNodesArr, 0.0); + Array1D RH_arr(numNodesArr, 0.0); + Array1D TD_arr(numNodesArr, 20.0); + Array1D TDT_arr(numNodesArr, 20.0); + Array1D EnthOld_arr(numNodesArr, 100.0); + Array1D EnthNew_arr(numNodesArr, 100.0); + + // --- Sub-case 1: Actuator OFF (baseline) --- + surfFD.enetActuator.isActuated = false; + surfFD.enetActuator.actuatedValue = 0.0; + TDT_arr = 20.0; + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_baseline = TDT_arr(nodeIdx); + Real64 const QRad_baseline = state->dataHeatBalSurf->SurfQdotRadOutRepPerArea(1); + + EXPECT_LT(QRad_baseline, 0.0); + + // --- Sub-case 2: Actuator ON, Enet=0 --- + surfFD.enetActuator.isActuated = true; + surfFD.enetActuator.actuatedValue = 0.0; + TDT_arr = 20.0; + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_enet0 = TDT_arr(nodeIdx); + + // --- Sub-case 3: Actuator ON, Enet=-200 --- + surfFD.enetActuator.isActuated = true; + surfFD.enetActuator.actuatedValue = -200.0; + TDT_arr = 20.0; + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_enetNeg200 = TDT_arr(nodeIdx); + + // --- Sub-case 4: Actuator ON, Enet=+200 --- + surfFD.enetActuator.isActuated = true; + surfFD.enetActuator.actuatedValue = 200.0; + TDT_arr = 20.0; + + ExteriorBCEqns(*state, + Delt, + nodeIdx, + Lay, + SurfNum, + T_arr, + TT_arr, + Rhov_arr, + RhoT_arr, + RH_arr, + TD_arr, + TDT_arr, + EnthOld_arr, + EnthNew_arr, + TotNodes, + HMovInsul); + + Real64 const TDT_enetPos200 = TDT_arr(nodeIdx); + + // Same ordering: Enet=-200 < baseline < Enet=0 < Enet=+200 + EXPECT_LT(TDT_enetNeg200, TDT_baseline); + EXPECT_LT(TDT_baseline, TDT_enet0); + EXPECT_LT(TDT_enet0, TDT_enetPos200); +} + } // namespace EnergyPlus