Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 : #include <string>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Construction.hh>
57 : #include <EnergyPlus/Data/EnergyPlusData.hh>
58 : #include <EnergyPlus/DataEnvironment.hh>
59 : #include <EnergyPlus/DataHeatBalance.hh>
60 : #include <EnergyPlus/DataIPShortCuts.hh>
61 : #include <EnergyPlus/DataMoistureBalance.hh>
62 : #include <EnergyPlus/DataMoistureBalanceEMPD.hh>
63 : #include <EnergyPlus/DataSurfaces.hh>
64 : #include <EnergyPlus/General.hh>
65 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
66 : #include <EnergyPlus/Material.hh>
67 : #include <EnergyPlus/MoistureBalanceEMPDManager.hh>
68 : #include <EnergyPlus/OutputProcessor.hh>
69 : #include <EnergyPlus/Psychrometrics.hh>
70 : #include <EnergyPlus/UtilityRoutines.hh>
71 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
72 :
73 : namespace EnergyPlus::MoistureBalanceEMPDManager {
74 :
75 : // Module containing the routines to calculate moisture adsorption and desorption
76 : // at interior wall surfaces
77 :
78 : // MODULE INFORMATION:
79 : // Authors: Muthusamy Swami and Lixing Gu
80 : // Date written: August, 1999
81 : // Modified: na
82 : // Re-engineered: Jason Woods and Noel Merket, August 2015
83 :
84 : // PURPOSE OF THIS MODULE:
85 : // To calculate moisture adsorption and desorption at interior wall surfaces
86 : // using EMPD model (Effective Moisture Penetration Depth) developed by
87 : // Florida Solar Energy Center. Input consists of interior surface temperatures
88 : // and sorption curve of interior layer materials. Output consists of moisture
89 : // fluxes from wall interior surfaces, which will be used in zone moisture balance.
90 :
91 : // METHODOLOGY EMPLOYED:
92 : // Add something
93 : // EMPD is a simplified method of analyzing moisture transport in buildings and
94 : // is easy to incorporate into existing building energy analysis computer codes.
95 : // The components of the moisture balance equation involving moisture adsorption
96 : // and desorption are described in detail where the concept of EMPD is discussed.
97 : // The assumptions. parameters required, and limitations of the model are also discussed.
98 : // Results of simulation using the model and comparison with measured data are given.
99 : // Data of isotherms compiled from the literature of some commonly used building materials are also given.
100 :
101 : // REFERENCES:
102 : // Kerestecioglu A A., Swami M V., Kamel A A., "Theoretical and computational
103 : // investigation of simultaneous heat and moisture transfer in buildings: 'Effective
104 : // penetration depth' theory," ASHRAE Trans., 1990, Vol. 96, Part 1, 447-454
105 :
106 : // Using/Aliasing
107 : using namespace DataHeatBalance;
108 : using namespace DataMoistureBalanceEMPD;
109 :
110 2 : Real64 MaterialEMPD::calcDepthFromPeriod(EnergyPlusData &state,
111 : Real64 const period // in seconds
112 : )
113 : {
114 : // Assume T, RH, P
115 2 : Real64 constexpr T = 24.0; // C
116 2 : Real64 constexpr RH = 0.45;
117 2 : Real64 constexpr P_amb = 101325; // Pa
118 :
119 : // Calculate saturation vapor pressure at assumed temperature
120 2 : Real64 const PV_sat = Psychrometrics::PsyPsatFnTemp(state, T, "CalcDepthFromPeriod");
121 :
122 : // Calculate slope of moisture sorption curve
123 2 : Real64 const slope_MC = this->moistACoeff * this->moistBCoeff * std::pow(RH, this->moistBCoeff - 1) +
124 2 : this->moistCCoeff * this->moistDCoeff * std::pow(RH, this->moistDCoeff - 1);
125 :
126 : // Equation for the diffusivity of water vapor in air
127 2 : Real64 const diffusivity_air = 2.0e-7 * std::pow(T + 273.15, 0.81) / P_amb;
128 :
129 : // Convert mu to diffusivity [kg/m^2-s-Pa]
130 2 : Real64 const EMPDdiffusivity = diffusivity_air / this->mu;
131 :
132 : // Calculate penetration depth
133 2 : return std::sqrt(EMPDdiffusivity * PV_sat * period / (this->Density * slope_MC * Constant::Pi));
134 : }
135 :
136 4 : void GetMoistureBalanceEMPDInput(EnergyPlusData &state)
137 : {
138 :
139 : // SUBROUTINE INFORMATION:
140 : // AUTHOR Muthusamy V. Swami and Lixing Gu
141 : // DATE WRITTEN August 2000
142 : // MODIFIED na
143 : // RE-ENGINEERED na
144 :
145 : // PURPOSE OF THIS SUBROUTINE:
146 : // This subroutine is the main driver for initializations within the
147 : // heat balance using the EMPD model.
148 :
149 : static constexpr std::string_view routineName = "GetMoistureBalanceEMPDInput";
150 :
151 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
152 : int IOStat; // IO Status when calling get input subroutine
153 4 : Array1D_string MaterialNames(3); // Number of Material Alpha names defined
154 : int MaterialNumAlpha; // Number of material alpha names being passed
155 : int MaterialNumProp; // Number of material properties being passed
156 4 : Array1D<Real64> MaterialProps(9); // Temporary array to transfer material properties
157 4 : bool ErrorsFound(false); // If errors detected in input
158 :
159 : int EMPDMat; // EMPD Moisture Material additional properties for each base material
160 4 : Array1D_bool EMPDzone; // EMPD property check for each zone
161 :
162 4 : auto &s_ip = state.dataInputProcessing->inputProcessor;
163 4 : auto &s_ipsc = state.dataIPShortCut;
164 4 : auto &s_mat = state.dataMaterial;
165 :
166 : // Load the additional EMPD Material properties
167 4 : s_ipsc->cCurrentModuleObject = "MaterialProperty:MoisturePenetrationDepth:Settings";
168 4 : EMPDMat = s_ip->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
169 :
170 4 : if (EMPDMat == 0) {
171 0 : ShowSevereError(state, format("EMPD Solution requested, but no \"{}\" objects were found.", s_ipsc->cCurrentModuleObject));
172 0 : ErrorsFound = true;
173 : }
174 :
175 8 : for (int Loop = 1; Loop <= EMPDMat; ++Loop) {
176 :
177 : // Call Input Get routine to retrieve material data
178 12 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
179 4 : s_ipsc->cCurrentModuleObject,
180 : Loop,
181 : MaterialNames,
182 : MaterialNumAlpha,
183 : MaterialProps,
184 : MaterialNumProp,
185 : IOStat,
186 4 : s_ipsc->lNumericFieldBlanks,
187 4 : s_ipsc->lAlphaFieldBlanks,
188 4 : s_ipsc->cAlphaFieldNames,
189 4 : s_ipsc->cNumericFieldNames);
190 :
191 4 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, MaterialNames(1)};
192 :
193 : // Load the material derived type from the input data.
194 4 : int matNum = Material::GetMaterialNum(state, MaterialNames(1));
195 4 : if (matNum == 0) {
196 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(1), MaterialNames(1));
197 0 : ErrorsFound = true;
198 0 : continue;
199 : }
200 :
201 4 : auto *mat = s_mat->materials(matNum);
202 4 : if (mat->group != Material::Group::Regular || mat->ROnly) {
203 0 : ShowSevereCustom(state, eoh, "Reference Material is not appropriate type for EMPD properties, must have regular properties (L,Cp,K,D)");
204 0 : ErrorsFound = true;
205 0 : continue;
206 : }
207 :
208 : // This is a regular material and we need to replace/upgrade it with an opaque material (subclass)
209 4 : auto *matEMPD = new MaterialEMPD;
210 4 : matEMPD->MaterialBase::operator=(*mat); // deep copy parent object (explicitly call operator=)
211 :
212 4 : delete mat;
213 4 : s_mat->materials(matNum) = matEMPD; // This should work, material name remains the same
214 :
215 4 : matEMPD->hasEMPD = true;
216 :
217 : // Once the material derived type number is found then load the additional moisture material properties
218 4 : matEMPD->mu = MaterialProps(1);
219 4 : matEMPD->moistACoeff = MaterialProps(2);
220 4 : matEMPD->moistBCoeff = MaterialProps(3);
221 4 : matEMPD->moistCCoeff = MaterialProps(4);
222 4 : matEMPD->moistDCoeff = MaterialProps(5);
223 4 : if (s_ipsc->lNumericFieldBlanks(6) || MaterialProps(6) == Constant::AutoCalculate) {
224 1 : matEMPD->surfaceDepth = matEMPD->calcDepthFromPeriod(state, 24 * 3600); // 1 day
225 : } else {
226 3 : matEMPD->surfaceDepth = MaterialProps(6);
227 : }
228 4 : if (s_ipsc->lNumericFieldBlanks(7) || MaterialProps(7) == Constant::AutoCalculate) {
229 1 : matEMPD->deepDepth = matEMPD->calcDepthFromPeriod(state, 21 * 24 * 3600); // 3 weeks
230 : } else {
231 3 : matEMPD->deepDepth = MaterialProps(7);
232 : }
233 4 : matEMPD->coatingThickness = MaterialProps(8);
234 4 : matEMPD->muCoating = MaterialProps(9);
235 :
236 4 : if (matEMPD->deepDepth <= matEMPD->surfaceDepth && matEMPD->deepDepth != 0.0) {
237 0 : ShowWarningError(state, format("{}: material=\"{}\"", s_ipsc->cCurrentModuleObject, matEMPD->Name));
238 0 : ShowContinueError(state, "Deep-layer penetration depth should be zero or greater than the surface-layer penetration depth.");
239 : }
240 : }
241 :
242 : // Ensure at least one interior EMPD surface for each zone
243 4 : EMPDzone.dimension(state.dataGlobal->NumOfZones, false);
244 7 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; ++SurfNum) {
245 3 : auto const &surf = state.dataSurface->Surface(SurfNum);
246 3 : if (!surf.HeatTransSurf || surf.Class == DataSurfaces::SurfaceClass::Window) continue; // Heat transfer surface only and not a window
247 3 : if (surf.HeatTransferAlgorithm != DataSurfaces::HeatTransferModel::EMPD) continue;
248 :
249 0 : auto const &constr = state.dataConstruction->Construct(surf.Construction);
250 0 : auto const *mat = dynamic_cast<const MaterialEMPD *>(s_mat->materials(constr.LayerPoint(constr.TotLayers)));
251 : // assert(mat != nullptr);
252 :
253 0 : if (mat && mat->mu > 0.0 && surf.Zone > 0) {
254 0 : EMPDzone(surf.Zone) = true;
255 : } else {
256 0 : ++state.dataMoistureBalEMPD->ErrCount;
257 0 : if (state.dataMoistureBalEMPD->ErrCount == 1 && !state.dataGlobal->DisplayExtraWarnings) {
258 0 : ShowMessage(state, "GetMoistureBalanceEMPDInput: EMPD properties are not assigned to the inside layer of Surfaces");
259 0 : ShowContinueError(state, "...use Output:Diagnostics,DisplayExtraWarnings; to show more details on individual surfaces.");
260 : }
261 0 : if (state.dataGlobal->DisplayExtraWarnings) {
262 0 : ShowMessage(state,
263 0 : format("GetMoistureBalanceEMPDInput: EMPD properties are not assigned to the inside layer in Surface={}", surf.Name));
264 0 : ShowContinueError(state, format("with Construction={}", constr.Name));
265 : }
266 : }
267 :
268 0 : if (constr.TotLayers == 1) continue; // One layer construction
269 :
270 : // Multiple layer construction
271 0 : auto const *mat1 = s_mat->materials(constr.LayerPoint(1));
272 0 : if (mat1->hasEMPD && surf.ExtBoundCond <= 0) { // The external layer is not exposed to zone
273 0 : ShowSevereError(state, format("{}: EMPD properties are assigned to the outside layer in Construction = {}", routineName, constr.Name));
274 0 : ShowContinueError(state, format("..Outside layer material with EMPD properties = {}", mat1->Name));
275 0 : ShowContinueError(state, "..A material with EMPD properties must be assigned to the inside layer of a construction.");
276 0 : ErrorsFound = true;
277 0 : continue;
278 : }
279 :
280 0 : for (int Layer = 2; Layer <= constr.TotLayers - 1; ++Layer) {
281 0 : auto const *matL = s_mat->materials(constr.LayerPoint(Layer));
282 0 : if (matL->hasEMPD) {
283 0 : ShowSevereError(state, format("{}: EMPD properties are assigned to a middle layer in Construction = {}", routineName, constr.Name));
284 0 : ShowContinueError(state, format("..Middle layer material with EMPD properties = {}", matL->Name));
285 0 : ShowContinueError(state, "..A material with EMPD properties must be assigned to the inside layer of a construction.");
286 0 : ErrorsFound = true;
287 : }
288 : }
289 : }
290 :
291 4 : for (int Loop = 1; Loop <= state.dataGlobal->NumOfZones; ++Loop) {
292 0 : if (!EMPDzone(Loop)) {
293 0 : ShowSevereError(state,
294 0 : format("{}: None of the constructions for zone = {} has an inside layer with EMPD properties",
295 : routineName,
296 0 : state.dataHeatBal->Zone(Loop).Name));
297 0 : ShowContinueError(state, "..For each zone, the inside layer of at least one construction must have EMPD properties");
298 0 : ErrorsFound = true;
299 : }
300 : }
301 :
302 4 : EMPDzone.deallocate();
303 :
304 4 : ReportMoistureBalanceEMPD(state);
305 :
306 4 : if (ErrorsFound) {
307 0 : ShowFatalError(state, "GetMoistureBalanceEMPDInput: Errors found getting EMPD material properties, program terminated.");
308 : }
309 4 : }
310 :
311 3 : void InitMoistureBalanceEMPD(EnergyPlusData &state)
312 : {
313 :
314 : // SUBROUTINE INFORMATION:
315 : // Authors: Muthusamy Swami and Lixing Gu
316 : // Date written: August, 1999
317 : // Modified: na
318 : // Re-engineered: na
319 :
320 : // PURPOSE OF THIS SUBROUTINE:
321 : // Create dynamic array for surface moisture calculation
322 :
323 : // USE STATEMENTS:
324 : using Psychrometrics::PsyRhovFnTdbRh;
325 : using Psychrometrics::PsyRhovFnTdbWPb_fast;
326 :
327 3 : if (state.dataMoistureBalEMPD->InitEnvrnFlag) {
328 3 : state.dataMstBalEMPD->RVSurfaceOld.allocate(state.dataSurface->TotSurfaces);
329 3 : state.dataMstBalEMPD->RVSurface.allocate(state.dataSurface->TotSurfaces);
330 3 : state.dataMstBalEMPD->HeatFluxLatent.allocate(state.dataSurface->TotSurfaces);
331 3 : state.dataMoistureBalEMPD->EMPDReportVars.allocate(state.dataSurface->TotSurfaces);
332 3 : state.dataMstBalEMPD->RVSurfLayer.allocate(state.dataSurface->TotSurfaces);
333 3 : state.dataMstBalEMPD->RVSurfLayerOld.allocate(state.dataSurface->TotSurfaces);
334 3 : state.dataMstBalEMPD->RVDeepLayer.allocate(state.dataSurface->TotSurfaces);
335 3 : state.dataMstBalEMPD->RVdeepOld.allocate(state.dataSurface->TotSurfaces);
336 3 : state.dataMstBalEMPD->RVwall.allocate(state.dataSurface->TotSurfaces);
337 : }
338 :
339 6 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; ++SurfNum) {
340 3 : int ZoneNum = state.dataSurface->Surface(SurfNum).Zone;
341 3 : if (!state.dataSurface->Surface(SurfNum).HeatTransSurf) continue;
342 : Real64 const rv_air_in_initval =
343 3 : min(PsyRhovFnTdbWPb_fast(state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum).MAT,
344 3 : max(state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum).airHumRat, 1.0e-5),
345 3 : state.dataEnvrn->OutBaroPress),
346 3 : PsyRhovFnTdbRh(state, state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum).MAT, 1.0, "InitMoistureBalanceEMPD"));
347 3 : state.dataMstBalEMPD->RVSurfaceOld(SurfNum) = rv_air_in_initval;
348 3 : state.dataMstBalEMPD->RVSurface(SurfNum) = rv_air_in_initval;
349 3 : state.dataMstBalEMPD->RVSurfLayer(SurfNum) = rv_air_in_initval;
350 3 : state.dataMstBalEMPD->RVSurfLayerOld(SurfNum) = rv_air_in_initval;
351 3 : state.dataMstBalEMPD->RVDeepLayer(SurfNum) = rv_air_in_initval;
352 3 : state.dataMstBalEMPD->RVdeepOld(SurfNum) = rv_air_in_initval;
353 3 : state.dataMstBalEMPD->RVwall(SurfNum) = rv_air_in_initval;
354 : }
355 3 : if (!state.dataMoistureBalEMPD->InitEnvrnFlag) return;
356 : // Initialize the report variable
357 :
358 3 : GetMoistureBalanceEMPDInput(state);
359 :
360 6 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; ++SurfNum) {
361 3 : if (!state.dataSurface->Surface(SurfNum).HeatTransSurf) continue;
362 3 : if (state.dataSurface->Surface(SurfNum).Class == DataSurfaces::SurfaceClass::Window) continue;
363 3 : EMPDReportVarsData &rvd = state.dataMoistureBalEMPD->EMPDReportVars(SurfNum);
364 3 : const std::string surf_name = state.dataSurface->Surface(SurfNum).Name;
365 6 : SetupOutputVariable(state,
366 : "EMPD Surface Inside Face Water Vapor Density",
367 : Constant::Units::kg_m3,
368 3 : rvd.rv_surface,
369 : OutputProcessor::TimeStepType::Zone,
370 : OutputProcessor::StoreType::Average,
371 : surf_name);
372 6 : SetupOutputVariable(state,
373 : "EMPD Surface Layer Moisture Content",
374 : Constant::Units::kg_m3,
375 3 : rvd.u_surface_layer,
376 : OutputProcessor::TimeStepType::Zone,
377 : OutputProcessor::StoreType::Average,
378 : surf_name);
379 6 : SetupOutputVariable(state,
380 : "EMPD Deep Layer Moisture Content",
381 : Constant::Units::kg_m3,
382 3 : rvd.u_deep_layer,
383 : OutputProcessor::TimeStepType::Zone,
384 : OutputProcessor::StoreType::Average,
385 : surf_name);
386 6 : SetupOutputVariable(state,
387 : "EMPD Surface Layer Equivalent Relative Humidity",
388 : Constant::Units::Perc,
389 3 : rvd.RH_surface_layer,
390 : OutputProcessor::TimeStepType::Zone,
391 : OutputProcessor::StoreType::Average,
392 : surf_name);
393 6 : SetupOutputVariable(state,
394 : "EMPD Deep Layer Equivalent Relative Humidity",
395 : Constant::Units::Perc,
396 3 : rvd.RH_deep_layer,
397 : OutputProcessor::TimeStepType::Zone,
398 : OutputProcessor::StoreType::Average,
399 : surf_name);
400 6 : SetupOutputVariable(state,
401 : "EMPD Surface Layer Equivalent Humidity Ratio",
402 : Constant::Units::kgWater_kgDryAir,
403 3 : rvd.w_surface_layer,
404 : OutputProcessor::TimeStepType::Zone,
405 : OutputProcessor::StoreType::Average,
406 : surf_name);
407 6 : SetupOutputVariable(state,
408 : "EMPD Deep Layer Equivalent Humidity Ratio",
409 : Constant::Units::kgWater_kgDryAir,
410 3 : rvd.w_deep_layer,
411 : OutputProcessor::TimeStepType::Zone,
412 : OutputProcessor::StoreType::Average,
413 : surf_name);
414 6 : SetupOutputVariable(state,
415 : "EMPD Surface Moisture Flux to Zone",
416 : Constant::Units::kg_m2s,
417 3 : rvd.mass_flux_zone,
418 : OutputProcessor::TimeStepType::Zone,
419 : OutputProcessor::StoreType::Average,
420 : surf_name);
421 6 : SetupOutputVariable(state,
422 : "EMPD Deep Layer Moisture Flux",
423 : Constant::Units::kg_m2s,
424 3 : rvd.mass_flux_deep,
425 : OutputProcessor::TimeStepType::Zone,
426 : OutputProcessor::StoreType::Average,
427 : surf_name);
428 3 : }
429 :
430 3 : if (state.dataMoistureBalEMPD->InitEnvrnFlag) state.dataMoistureBalEMPD->InitEnvrnFlag = false;
431 : }
432 :
433 3 : void CalcMoistureBalanceEMPD(EnergyPlusData &state,
434 : int const SurfNum,
435 : Real64 const SurfTempIn, // INSIDE SURFACE TEMPERATURE at current time step
436 : Real64 const TempZone, // Zone temperature at current time step.
437 : Real64 &TempSat // Saturated surface temperature.
438 : )
439 : {
440 :
441 : // SUBROUTINE INFORMATION:
442 : // Authors: Muthusamy Swami and Lixing Gu
443 : // Date written: August, 1999
444 : // Modified: na
445 : // Re-engineered: na
446 :
447 : // PURPOSE OF THIS SUBROUTINE:
448 : // Calculate surface moisture level using EMPD model
449 :
450 : // Using/Aliasing
451 : using DataMoistureBalanceEMPD::Lam;
452 : using Psychrometrics::PsyCpAirFnW;
453 : using Psychrometrics::PsyPsatFnTemp;
454 : using Psychrometrics::PsyRhFnTdbRhov;
455 : using Psychrometrics::PsyRhFnTdbRhovLBnd0C;
456 : using Psychrometrics::PsyRhFnTdbWPb;
457 : using Psychrometrics::PsyRhoAirFnPbTdbW;
458 : using Psychrometrics::PsyRhovFnTdbRh;
459 : using Psychrometrics::PsyRhovFnTdbWPb;
460 : using Psychrometrics::PsyRhovFnTdbWPb_fast;
461 : using Psychrometrics::PsyWFnTdbRhPb;
462 :
463 : static constexpr std::string_view routineName = "CalcMoistureEMPD";
464 :
465 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
466 : Real64 hm_deep_layer; // Overall deep-layer transfer coefficient
467 : Real64 RSurfaceLayer; // Mass transfer resistance between actual surface and surface layer node
468 : Real64 Taver; // Average zone temperature between current time and previous time
469 : // REAL(r64) :: Waver ! Average zone humidity ratio between current time and previous time
470 : Real64 RHaver; // Average zone relative humidity {0-1} between current time and previous time
471 : Real64 RVaver; // Average zone vapor density
472 : Real64 dU_dRH;
473 : Real64 PVsurf; // Surface vapor pressure
474 : Real64 PV_surf_layer; // Vapor pressure of surface layer
475 : Real64 PV_deep_layer;
476 : Real64 PVsat; // saturation vapor pressure at the surface
477 : Real64 RH_surf_layer_old;
478 : Real64 RH_deep_layer_old;
479 : Real64 EMPDdiffusivity;
480 : Real64 Rcoating;
481 : Real64 RH_surf_layer;
482 : Real64 RH_surf_layer_tmp;
483 : Real64 RH_deep_layer;
484 :
485 3 : auto &s_mat = state.dataMaterial;
486 :
487 3 : if (state.dataGlobal->BeginEnvrnFlag && state.dataMoistureBalEMPD->OneTimeFlag) {
488 0 : InitMoistureBalanceEMPD(state);
489 0 : state.dataMoistureBalEMPD->OneTimeFlag = false;
490 : }
491 :
492 3 : if (!state.dataGlobal->BeginEnvrnFlag) {
493 3 : state.dataMoistureBalEMPD->OneTimeFlag = true;
494 : }
495 :
496 3 : auto const &surface(state.dataSurface->Surface(SurfNum)); // input
497 3 : auto &rv_surface(state.dataMstBalEMPD->RVSurface(SurfNum)); // output
498 3 : auto const &rv_surface_old(state.dataMstBalEMPD->RVSurfaceOld(SurfNum)); // input
499 3 : auto const &h_mass_conv_in_fd(state.dataMstBal->HMassConvInFD(SurfNum)); // input
500 3 : auto const &rho_vapor_air_in(state.dataMstBal->RhoVaporAirIn(SurfNum)); // input
501 : Real64 RHZone;
502 : Real64 mass_flux_surf_deep;
503 : Real64 mass_flux_surf_deep_max;
504 : Real64 mass_flux_zone_surf;
505 : Real64 mass_flux_zone_surf_max;
506 : Real64 mass_flux_surf_layer;
507 : Real64 mass_flux_deep_layer;
508 : Real64 mass_flux_zone;
509 3 : auto &rv_surf_layer(state.dataMstBalEMPD->RVSurfLayer(SurfNum)); // output
510 3 : auto const &rv_surf_layer_old(state.dataMstBalEMPD->RVSurfLayerOld(SurfNum)); // input
511 : Real64 hm_surf_layer;
512 3 : auto &rv_deep_layer(state.dataMstBalEMPD->RVDeepLayer(SurfNum)); // output
513 3 : auto const &rv_deep_old(state.dataMstBalEMPD->RVdeepOld(SurfNum)); // input
514 3 : auto &heat_flux_latent(state.dataMstBalEMPD->HeatFluxLatent(SurfNum)); // output
515 :
516 3 : heat_flux_latent = 0.0;
517 3 : if (!surface.HeatTransSurf) {
518 0 : return;
519 : }
520 :
521 3 : auto const &constr = state.dataConstruction->Construct(surface.Construction);
522 3 : auto const *mat = dynamic_cast<MaterialEMPD const *>(s_mat->materials(constr.LayerPoint(constr.TotLayers)));
523 3 : assert(mat != nullptr);
524 3 : if (mat->mu <= 0.0) {
525 0 : rv_surface =
526 0 : PsyRhovFnTdbWPb(TempZone, state.dataZoneTempPredictorCorrector->zoneHeatBalance(surface.Zone).airHumRat, state.dataEnvrn->OutBaroPress);
527 0 : return;
528 : }
529 :
530 3 : Taver = SurfTempIn;
531 : // Calculate average vapor density [kg/m^3], and RH for use in material property calculations.
532 3 : RVaver = rv_surface_old;
533 3 : RHaver = RVaver * 461.52 * (Taver + Constant::Kelvin) * std::exp(-23.7093 + 4111.0 / (Taver + 237.7));
534 :
535 : // Calculate the saturated vapor pressure, surface vapor pressure and dewpoint. Used to check for condensation in HeatBalanceSurfaceManager
536 3 : PVsat = PsyPsatFnTemp(state, Taver, routineName);
537 3 : PVsurf = RHaver * std::exp(23.7093 - 4111.0 / (Taver + 237.7));
538 3 : TempSat = 4111.0 / (23.7093 - std::log(PVsurf)) + 35.45 - Constant::Kelvin;
539 :
540 : // Convert vapor resistance factor (user input) to diffusivity. Evaluate at local surface temperature.
541 : // 2e-7*T^0.81/P = vapor diffusivity in air. [kg/m-s-Pa]
542 : // 461.52 = universal gas constant for water [J/kg-K]
543 : // EMPDdiffusivity = [m^2/s]
544 3 : EMPDdiffusivity = (2.0e-7 * pow(Taver + Constant::Kelvin, 0.81) / state.dataEnvrn->OutBaroPress) / mat->mu * 461.52 * (Taver + Constant::Kelvin);
545 :
546 : // Calculate slope of moisture sorption curve at current RH. [kg/kg-RH]
547 3 : dU_dRH = mat->moistACoeff * mat->moistBCoeff * pow(RHaver, mat->moistBCoeff - 1) +
548 3 : mat->moistCCoeff * mat->moistDCoeff * pow(RHaver, mat->moistDCoeff - 1);
549 :
550 : // Convert vapor density and temperature of zone air to RH
551 3 : RHZone = rho_vapor_air_in * 461.52 * (TempZone + Constant::Kelvin) * std::exp(-23.7093 + 4111.0 / ((TempZone + Constant::Kelvin) - 35.45));
552 :
553 : // Convert stored vapor density from previous timestep to RH.
554 3 : RH_deep_layer_old = PsyRhFnTdbRhov(state, Taver, rv_deep_old);
555 3 : RH_surf_layer_old = PsyRhFnTdbRhov(state, Taver, rv_surf_layer_old);
556 :
557 : // If coating vapor resistance factor equals 0, coating resistance is zero (avoid divide by zero).
558 : // Otherwise, calculate coating resistance with coating vapor resistance factor and thickness. [s/m]
559 3 : if (mat->muCoating <= 0.0) {
560 1 : Rcoating = 0;
561 : } else {
562 2 : Rcoating = mat->coatingThickness * mat->muCoating * state.dataEnvrn->OutBaroPress /
563 2 : (2.0e-7 * pow(Taver + Constant::Kelvin, 0.81) * 461.52 * (Taver + Constant::Kelvin));
564 : }
565 :
566 : // Calculate mass-transfer coefficient between zone air and center of surface layer. [m/s]
567 3 : hm_surf_layer = 1.0 / (0.5 * mat->surfaceDepth / EMPDdiffusivity + 1.0 / h_mass_conv_in_fd + Rcoating);
568 : // Calculate mass-transfer coefficient between center of surface layer and center of deep layer. [m/s]
569 : // If deep layer depth = 0, set mass-transfer coefficient to zero (simulates with no deep layer).
570 3 : if (mat->deepDepth <= 0.0) {
571 0 : hm_deep_layer = 0;
572 : } else {
573 3 : hm_deep_layer = 2.0 * EMPDdiffusivity / (mat->deepDepth + mat->surfaceDepth);
574 : }
575 : // Calculate resistance between surface-layer/air interface and center of surface layer. [s/m]
576 : // This is the physical surface of the material.
577 3 : RSurfaceLayer = 1.0 / hm_surf_layer - 1.0 / h_mass_conv_in_fd;
578 :
579 : // Calculate vapor flux leaving surface layer, entering deep layer, and entering zone.
580 3 : mass_flux_surf_deep_max =
581 3 : mat->deepDepth * mat->Density * dU_dRH * (RH_surf_layer_old - RH_deep_layer_old) / (state.dataGlobal->TimeStepZone * 3600.0);
582 3 : mass_flux_surf_deep = hm_deep_layer * (rv_surf_layer_old - rv_deep_old);
583 3 : if (std::abs(mass_flux_surf_deep_max) < std::abs(mass_flux_surf_deep)) {
584 0 : mass_flux_surf_deep = mass_flux_surf_deep_max;
585 : }
586 :
587 3 : mass_flux_zone_surf_max = mat->surfaceDepth * mat->Density * dU_dRH * (RHZone - RH_surf_layer_old) / (state.dataGlobal->TimeStepZone * 3600.0);
588 3 : mass_flux_zone_surf = hm_surf_layer * (rho_vapor_air_in - rv_surf_layer_old);
589 3 : if (std::abs(mass_flux_zone_surf_max) < std::abs(mass_flux_zone_surf)) {
590 0 : mass_flux_zone_surf = mass_flux_zone_surf_max;
591 : }
592 :
593 : // mass_flux_surf_layer = -mass_flux_zone_surf + mass_flux_surf_deep;
594 : // mass_flux_deep_layer = mass_flux_surf_deep;
595 : // mass_flux_zone = -mass_flux_zone_surf;
596 :
597 3 : mass_flux_surf_layer = hm_surf_layer * (rv_surf_layer_old - rho_vapor_air_in) + hm_deep_layer * (rv_surf_layer_old - rv_deep_old);
598 3 : mass_flux_deep_layer = hm_deep_layer * (rv_surf_layer_old - rv_deep_old);
599 3 : mass_flux_zone = hm_surf_layer * (rv_surf_layer_old - rho_vapor_air_in);
600 :
601 : // Calculate new surface layer RH using mass balance on surface layer
602 3 : RH_surf_layer_tmp =
603 3 : RH_surf_layer_old + state.dataGlobal->TimeStepZone * 3600.0 * (-mass_flux_surf_layer / (mat->Density * mat->surfaceDepth * dU_dRH));
604 :
605 : // RH_surf_layer = RH_surf_layer_tmp;
606 :
607 3 : if (RH_surf_layer_old < RH_deep_layer_old && RH_surf_layer_old < RHZone) {
608 0 : if (RHZone > RH_deep_layer_old) {
609 0 : if (RH_surf_layer_tmp > RHZone) {
610 0 : RH_surf_layer = RHZone;
611 : } else {
612 0 : RH_surf_layer = RH_surf_layer_tmp;
613 : }
614 0 : } else if (RH_surf_layer_tmp > RH_deep_layer_old) {
615 0 : RH_surf_layer = RH_deep_layer_old;
616 : } else {
617 0 : RH_surf_layer = RH_surf_layer_tmp;
618 : }
619 :
620 3 : } else if (RH_surf_layer_old < RH_deep_layer_old && RH_surf_layer_old > RHZone) {
621 0 : if (RH_surf_layer_tmp > RH_deep_layer_old) {
622 0 : RH_surf_layer = RH_deep_layer_old;
623 0 : } else if (RH_surf_layer_tmp < RHZone) {
624 0 : RH_surf_layer = RHZone;
625 : } else {
626 0 : RH_surf_layer = RH_surf_layer_tmp;
627 : }
628 3 : } else if (RH_surf_layer_old > RH_deep_layer_old && RH_surf_layer_old < RHZone) {
629 3 : if (RH_surf_layer_tmp > RHZone) {
630 0 : RH_surf_layer = RHZone;
631 3 : } else if (RH_surf_layer_tmp < RH_deep_layer_old) {
632 0 : RH_surf_layer = RH_deep_layer_old;
633 : } else
634 3 : RH_surf_layer = RH_surf_layer_tmp;
635 0 : } else if (RHZone < RH_deep_layer_old) {
636 0 : if (RH_surf_layer_tmp < RHZone) {
637 0 : RH_surf_layer = RHZone;
638 : } else {
639 0 : RH_surf_layer = RH_surf_layer_tmp;
640 : }
641 0 : } else if (RH_surf_layer_tmp < RH_deep_layer_old) {
642 0 : RH_surf_layer = RH_deep_layer_old;
643 : } else {
644 0 : RH_surf_layer = RH_surf_layer_tmp;
645 : }
646 :
647 : // Calculate new deep layer RH using mass balance on deep layer (unless depth <= 0).
648 3 : if (mat->deepDepth <= 0.0) {
649 0 : RH_deep_layer = RH_deep_layer_old;
650 : } else {
651 3 : RH_deep_layer = RH_deep_layer_old + state.dataGlobal->TimeStepZone * 3600.0 * mass_flux_deep_layer / (mat->Density * mat->deepDepth * dU_dRH);
652 : }
653 : // Convert calculated RH back to vapor density of surface and deep layers.
654 3 : rv_surf_layer = PsyRhovFnTdbRh(state, Taver, RH_surf_layer);
655 3 : rv_deep_layer = PsyRhovFnTdbRh(state, Taver, RH_deep_layer);
656 :
657 : // Calculate surface-layer and deep-layer vapor pressures [Pa]
658 3 : PV_surf_layer = RH_surf_layer * std::exp(23.7093 - 4111.0 / (Taver + 237.7));
659 3 : PV_deep_layer = RH_deep_layer * std::exp(23.7093 - 4111.0 / (Taver + 237.7));
660 :
661 : // Calculate vapor density at physical material surface (surface-layer/air interface). This is used to calculate total moisture flow terms for
662 : // each zone in HeatBalanceSurfaceManager
663 3 : rv_surface = rv_surf_layer - mass_flux_zone * RSurfaceLayer;
664 :
665 : // Calculate heat flux from latent-sensible conversion due to moisture adsorption [W/m^2]
666 3 : heat_flux_latent = mass_flux_zone * Lam;
667 :
668 : // Put results in the reporting variables
669 : // Will add RH and W of deep layer as outputs
670 : // Need to also add moisture content (kg/kg) of surface and deep layers, and moisture flow from each surface (kg/s), per Rongpeng's suggestion
671 3 : EMPDReportVarsData &rvd = state.dataMoistureBalEMPD->EMPDReportVars(SurfNum);
672 3 : rvd.rv_surface = rv_surface;
673 3 : rvd.RH_surface_layer = RH_surf_layer * 100.0;
674 3 : rvd.RH_deep_layer = RH_deep_layer * 100.0;
675 3 : rvd.w_surface_layer = 0.622 * PV_surf_layer / (state.dataEnvrn->OutBaroPress - PV_surf_layer);
676 3 : rvd.w_deep_layer = 0.622 * PV_deep_layer / (state.dataEnvrn->OutBaroPress - PV_deep_layer);
677 3 : rvd.mass_flux_zone = mass_flux_zone;
678 3 : rvd.mass_flux_deep = mass_flux_deep_layer;
679 3 : rvd.u_surface_layer = mat->moistACoeff * pow(RH_surf_layer, mat->moistBCoeff) + mat->moistCCoeff * pow(RH_surf_layer, mat->moistDCoeff);
680 3 : rvd.u_deep_layer = mat->moistACoeff * pow(RH_deep_layer, mat->moistBCoeff) + mat->moistCCoeff * pow(RH_deep_layer, mat->moistDCoeff);
681 : }
682 :
683 3 : void UpdateMoistureBalanceEMPD(EnergyPlusData &state, int const SurfNum) // Surface number
684 : {
685 :
686 : // SUBROUTINE INFORMATION:
687 : // Authors: Muthusamy Swami and Lixing Gu
688 : // Date writtenn: August, 1999
689 : // Modified: na
690 : // Re-engineered: na
691 :
692 : // PURPOSE OF THIS SUBROUTINE:
693 : // Update inside surface vapor density
694 :
695 3 : state.dataMstBalEMPD->RVSurfaceOld(SurfNum) = state.dataMstBalEMPD->RVSurface(SurfNum);
696 3 : state.dataMstBalEMPD->RVdeepOld(SurfNum) = state.dataMstBalEMPD->RVDeepLayer(SurfNum);
697 3 : state.dataMstBalEMPD->RVSurfLayerOld(SurfNum) = state.dataMstBalEMPD->RVSurfLayer(SurfNum);
698 3 : }
699 :
700 4 : void ReportMoistureBalanceEMPD(EnergyPlusData &state)
701 : {
702 :
703 : // SUBROUTINE INFORMATION:
704 : // AUTHOR Lixing Gu
705 : // DATE WRITTEN August 2005
706 : // MODIFIED na
707 : // RE-ENGINEERED na
708 :
709 : // PURPOSE OF THIS SUBROUTINE:
710 : // This routine gives a detailed report to the user about
711 : // EMPD Properties of each construction.
712 :
713 : // Using/Aliasing
714 : using General::ScanForReports;
715 :
716 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
717 : bool DoReport;
718 :
719 4 : auto &s_mat = state.dataMaterial;
720 :
721 12 : ScanForReports(state, "Constructions", DoReport, "Constructions");
722 :
723 4 : if (!DoReport) return;
724 : // Write Descriptions
725 0 : print(state.files.eio,
726 : "{}",
727 : "! <Construction EMPD>, Construction Name, Inside Layer Material Name, Vapor Resistance Factor, a, b, "
728 : "c, d, Surface Penetration Depth {m}, Deep Penetration Depth {m}, Coating Vapor Resistance Factor, "
729 : "Coating Thickness {m}\n");
730 :
731 0 : for (int ConstrNum = 1; ConstrNum <= state.dataHeatBal->TotConstructs; ++ConstrNum) {
732 0 : auto const &constr = state.dataConstruction->Construct(ConstrNum);
733 0 : if (constr.TypeIsWindow) continue;
734 :
735 0 : auto const *mat = s_mat->materials(constr.LayerPoint(constr.TotLayers));
736 0 : if (!mat->hasEMPD) continue;
737 :
738 0 : auto const *matEMPD = dynamic_cast<MaterialEMPD const *>(mat);
739 0 : assert(matEMPD != nullptr);
740 :
741 : static constexpr std::string_view Format_700(
742 : " Construction EMPD, {}, {}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}\n");
743 0 : print(state.files.eio,
744 : Format_700,
745 0 : constr.Name,
746 0 : matEMPD->Name,
747 0 : matEMPD->mu,
748 0 : matEMPD->moistACoeff,
749 0 : matEMPD->moistBCoeff,
750 0 : matEMPD->moistCCoeff,
751 0 : matEMPD->moistDCoeff,
752 0 : matEMPD->surfaceDepth,
753 0 : matEMPD->deepDepth,
754 0 : matEMPD->muCoating,
755 0 : matEMPD->coatingThickness);
756 : }
757 : } // ReportMoistureBalanceEMPD()
758 :
759 : } // namespace EnergyPlus::MoistureBalanceEMPDManager
|