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 0 : Real64 MaterialEMPD::calcDepthFromPeriod(EnergyPlusData &state,
111 : Real64 const period // in seconds
112 : )
113 : {
114 : // Assume T, RH, P
115 0 : Real64 constexpr T = 24.0; // C
116 0 : Real64 constexpr RH = 0.45;
117 0 : Real64 constexpr P_amb = 101325; // Pa
118 :
119 : // Calculate saturation vapor pressure at assumed temperature
120 0 : Real64 const PV_sat = Psychrometrics::PsyPsatFnTemp(state, T, "CalcDepthFromPeriod");
121 :
122 : // Calculate slope of moisture sorption curve
123 0 : Real64 const slope_MC = this->moistACoeff * this->moistBCoeff * std::pow(RH, this->moistBCoeff - 1) +
124 0 : this->moistCCoeff * this->moistDCoeff * std::pow(RH, this->moistDCoeff - 1);
125 :
126 : // Equation for the diffusivity of water vapor in air
127 0 : 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 0 : Real64 const EMPDdiffusivity = diffusivity_air / this->mu;
131 :
132 : // Calculate penetration depth
133 0 : return std::sqrt(EMPDdiffusivity * PV_sat * period / (this->Density * slope_MC * Constant::Pi));
134 : }
135 :
136 2 : 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 2 : 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 2 : Array1D<Real64> MaterialProps(9); // Temporary array to transfer material properties
157 2 : bool ErrorsFound(false); // If errors detected in input
158 :
159 : int EMPDMat; // EMPD Moisture Material additional properties for each base material
160 2 : Array1D_bool EMPDzone; // EMPD property check for each zone
161 :
162 2 : auto &s_ip = state.dataInputProcessing->inputProcessor;
163 2 : auto &s_ipsc = state.dataIPShortCut;
164 2 : auto &s_mat = state.dataMaterial;
165 :
166 : // Load the additional EMPD Material properties
167 2 : s_ipsc->cCurrentModuleObject = "MaterialProperty:MoisturePenetrationDepth:Settings";
168 2 : EMPDMat = s_ip->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
169 :
170 2 : 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 18 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
179 6 : s_ipsc->cCurrentModuleObject,
180 : Loop,
181 : MaterialNames,
182 : MaterialNumAlpha,
183 : MaterialProps,
184 : MaterialNumProp,
185 : IOStat,
186 6 : s_ipsc->lNumericFieldBlanks,
187 6 : s_ipsc->lAlphaFieldBlanks,
188 6 : s_ipsc->cAlphaFieldNames,
189 6 : s_ipsc->cNumericFieldNames);
190 :
191 6 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, MaterialNames(1)};
192 :
193 : // Load the material derived type from the input data.
194 6 : int matNum = Material::GetMaterialNum(state, MaterialNames(1));
195 6 : if (matNum == 0) {
196 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(1), MaterialNames(1));
197 0 : ErrorsFound = true;
198 0 : continue;
199 : }
200 :
201 6 : auto *mat = s_mat->materials(matNum);
202 6 : 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 6 : auto *matEMPD = new MaterialEMPD;
210 6 : matEMPD->MaterialBase::operator=(*mat); // deep copy parent object (explicitly call operator=)
211 :
212 6 : delete mat;
213 6 : s_mat->materials(matNum) = matEMPD; // This should work, material name remains the same
214 :
215 6 : matEMPD->hasEMPD = true;
216 :
217 : // Once the material derived type number is found then load the additional moisture material properties
218 6 : matEMPD->mu = MaterialProps(1);
219 6 : matEMPD->moistACoeff = MaterialProps(2);
220 6 : matEMPD->moistBCoeff = MaterialProps(3);
221 6 : matEMPD->moistCCoeff = MaterialProps(4);
222 6 : matEMPD->moistDCoeff = MaterialProps(5);
223 6 : if (s_ipsc->lNumericFieldBlanks(6) || MaterialProps(6) == Constant::AutoCalculate) {
224 0 : matEMPD->surfaceDepth = matEMPD->calcDepthFromPeriod(state, 24 * 3600); // 1 day
225 : } else {
226 6 : matEMPD->surfaceDepth = MaterialProps(6);
227 : }
228 6 : if (s_ipsc->lNumericFieldBlanks(7) || MaterialProps(7) == Constant::AutoCalculate) {
229 0 : matEMPD->deepDepth = matEMPD->calcDepthFromPeriod(state, 21 * 24 * 3600); // 3 weeks
230 : } else {
231 6 : matEMPD->deepDepth = MaterialProps(7);
232 : }
233 6 : matEMPD->coatingThickness = MaterialProps(8);
234 6 : matEMPD->muCoating = MaterialProps(9);
235 :
236 6 : 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 2 : EMPDzone.dimension(state.dataGlobal->NumOfZones, false);
244 58 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; ++SurfNum) {
245 56 : auto const &surf = state.dataSurface->Surface(SurfNum);
246 56 : if (!surf.HeatTransSurf || surf.Class == DataSurfaces::SurfaceClass::Window) {
247 10 : continue; // Heat transfer surface only and not a window
248 : }
249 46 : if (surf.HeatTransferAlgorithm != DataSurfaces::HeatTransferModel::EMPD) {
250 3 : continue;
251 : }
252 :
253 43 : auto const &constr = state.dataConstruction->Construct(surf.Construction);
254 43 : auto const *mat = dynamic_cast<const MaterialEMPD *>(s_mat->materials(constr.LayerPoint(constr.TotLayers)));
255 : // assert(mat != nullptr);
256 :
257 43 : if (mat && mat->mu > 0.0 && surf.Zone > 0) {
258 43 : EMPDzone(surf.Zone) = true;
259 : } else {
260 0 : ++state.dataMoistureBalEMPD->ErrCount;
261 0 : if (state.dataMoistureBalEMPD->ErrCount == 1 && !state.dataGlobal->DisplayExtraWarnings) {
262 0 : ShowMessage(state, "GetMoistureBalanceEMPDInput: EMPD properties are not assigned to the inside layer of Surfaces");
263 0 : ShowContinueError(state, "...use Output:Diagnostics,DisplayExtraWarnings; to show more details on individual surfaces.");
264 : }
265 0 : if (state.dataGlobal->DisplayExtraWarnings) {
266 0 : ShowMessage(state,
267 0 : format("GetMoistureBalanceEMPDInput: EMPD properties are not assigned to the inside layer in Surface={}", surf.Name));
268 0 : ShowContinueError(state, format("with Construction={}", constr.Name));
269 : }
270 : }
271 :
272 43 : if (constr.TotLayers == 1) {
273 15 : continue; // One layer construction
274 : }
275 :
276 : // Multiple layer construction
277 28 : auto const *mat1 = s_mat->materials(constr.LayerPoint(1));
278 28 : if (mat1->hasEMPD && surf.ExtBoundCond <= 0) { // The external layer is not exposed to zone
279 0 : ShowSevereError(state, format("{}: EMPD properties are assigned to the outside layer in Construction = {}", routineName, constr.Name));
280 0 : ShowContinueError(state, format("..Outside layer material with EMPD properties = {}", mat1->Name));
281 0 : ShowContinueError(state, "..A material with EMPD properties must be assigned to the inside layer of a construction.");
282 0 : ErrorsFound = true;
283 0 : continue;
284 : }
285 :
286 68 : for (int Layer = 2; Layer <= constr.TotLayers - 1; ++Layer) {
287 40 : auto const *matL = s_mat->materials(constr.LayerPoint(Layer));
288 40 : if (matL->hasEMPD) {
289 0 : ShowSevereError(state, format("{}: EMPD properties are assigned to a middle layer in Construction = {}", routineName, constr.Name));
290 0 : ShowContinueError(state, format("..Middle layer material with EMPD properties = {}", matL->Name));
291 0 : ShowContinueError(state, "..A material with EMPD properties must be assigned to the inside layer of a construction.");
292 0 : ErrorsFound = true;
293 : }
294 : }
295 : }
296 :
297 9 : for (int Loop = 1; Loop <= state.dataGlobal->NumOfZones; ++Loop) {
298 7 : if (!EMPDzone(Loop)) {
299 0 : ShowSevereError(state,
300 0 : format("{}: None of the constructions for zone = {} has an inside layer with EMPD properties",
301 : routineName,
302 0 : state.dataHeatBal->Zone(Loop).Name));
303 0 : ShowContinueError(state, "..For each zone, the inside layer of at least one construction must have EMPD properties");
304 0 : ErrorsFound = true;
305 : }
306 : }
307 :
308 2 : EMPDzone.deallocate();
309 :
310 2 : ReportMoistureBalanceEMPD(state);
311 :
312 2 : if (ErrorsFound) {
313 0 : ShowFatalError(state, "GetMoistureBalanceEMPDInput: Errors found getting EMPD material properties, program terminated.");
314 : }
315 2 : }
316 :
317 14 : void InitMoistureBalanceEMPD(EnergyPlusData &state)
318 : {
319 :
320 : // SUBROUTINE INFORMATION:
321 : // Authors: Muthusamy Swami and Lixing Gu
322 : // Date written: August, 1999
323 : // Modified: na
324 : // Re-engineered: na
325 :
326 : // PURPOSE OF THIS SUBROUTINE:
327 : // Create dynamic array for surface moisture calculation
328 :
329 : // USE STATEMENTS:
330 : using Psychrometrics::PsyRhovFnTdbRh;
331 : using Psychrometrics::PsyRhovFnTdbWPb_fast;
332 :
333 14 : if (state.dataMoistureBalEMPD->InitEnvrnFlag) {
334 2 : state.dataMstBalEMPD->RVSurfaceOld.allocate(state.dataSurface->TotSurfaces);
335 2 : state.dataMstBalEMPD->RVSurface.allocate(state.dataSurface->TotSurfaces);
336 2 : state.dataMstBalEMPD->HeatFluxLatent.allocate(state.dataSurface->TotSurfaces);
337 2 : state.dataMoistureBalEMPD->EMPDReportVars.allocate(state.dataSurface->TotSurfaces);
338 2 : state.dataMstBalEMPD->RVSurfLayer.allocate(state.dataSurface->TotSurfaces);
339 2 : state.dataMstBalEMPD->RVSurfLayerOld.allocate(state.dataSurface->TotSurfaces);
340 2 : state.dataMstBalEMPD->RVDeepLayer.allocate(state.dataSurface->TotSurfaces);
341 2 : state.dataMstBalEMPD->RVdeepOld.allocate(state.dataSurface->TotSurfaces);
342 2 : state.dataMstBalEMPD->RVwall.allocate(state.dataSurface->TotSurfaces);
343 : }
344 :
345 538 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; ++SurfNum) {
346 524 : int ZoneNum = state.dataSurface->Surface(SurfNum).Zone;
347 524 : if (!state.dataSurface->Surface(SurfNum).HeatTransSurf) {
348 40 : continue;
349 : }
350 : Real64 const rv_air_in_initval =
351 484 : min(PsyRhovFnTdbWPb_fast(state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum).MAT,
352 484 : max(state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum).airHumRat, 1.0e-5),
353 484 : state.dataEnvrn->OutBaroPress),
354 484 : PsyRhovFnTdbRh(state, state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum).MAT, 1.0, "InitMoistureBalanceEMPD"));
355 484 : state.dataMstBalEMPD->RVSurfaceOld(SurfNum) = rv_air_in_initval;
356 484 : state.dataMstBalEMPD->RVSurface(SurfNum) = rv_air_in_initval;
357 484 : state.dataMstBalEMPD->RVSurfLayer(SurfNum) = rv_air_in_initval;
358 484 : state.dataMstBalEMPD->RVSurfLayerOld(SurfNum) = rv_air_in_initval;
359 484 : state.dataMstBalEMPD->RVDeepLayer(SurfNum) = rv_air_in_initval;
360 484 : state.dataMstBalEMPD->RVdeepOld(SurfNum) = rv_air_in_initval;
361 484 : state.dataMstBalEMPD->RVwall(SurfNum) = rv_air_in_initval;
362 : }
363 14 : if (!state.dataMoistureBalEMPD->InitEnvrnFlag) {
364 12 : return;
365 : }
366 : // Initialize the report variable
367 :
368 2 : GetMoistureBalanceEMPDInput(state);
369 :
370 58 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; ++SurfNum) {
371 56 : if (!state.dataSurface->Surface(SurfNum).HeatTransSurf) {
372 10 : continue;
373 : }
374 52 : if (state.dataSurface->Surface(SurfNum).Class == DataSurfaces::SurfaceClass::Window) {
375 6 : continue;
376 : }
377 46 : EMPDReportVarsData &rvd = state.dataMoistureBalEMPD->EMPDReportVars(SurfNum);
378 46 : const std::string surf_name = state.dataSurface->Surface(SurfNum).Name;
379 92 : SetupOutputVariable(state,
380 : "EMPD Surface Inside Face Water Vapor Density",
381 : Constant::Units::kg_m3,
382 46 : rvd.rv_surface,
383 : OutputProcessor::TimeStepType::Zone,
384 : OutputProcessor::StoreType::Average,
385 : surf_name);
386 92 : SetupOutputVariable(state,
387 : "EMPD Surface Layer Moisture Content",
388 : Constant::Units::kg_m3,
389 46 : rvd.u_surface_layer,
390 : OutputProcessor::TimeStepType::Zone,
391 : OutputProcessor::StoreType::Average,
392 : surf_name);
393 92 : SetupOutputVariable(state,
394 : "EMPD Deep Layer Moisture Content",
395 : Constant::Units::kg_m3,
396 46 : rvd.u_deep_layer,
397 : OutputProcessor::TimeStepType::Zone,
398 : OutputProcessor::StoreType::Average,
399 : surf_name);
400 92 : SetupOutputVariable(state,
401 : "EMPD Surface Layer Equivalent Relative Humidity",
402 : Constant::Units::Perc,
403 46 : rvd.RH_surface_layer,
404 : OutputProcessor::TimeStepType::Zone,
405 : OutputProcessor::StoreType::Average,
406 : surf_name);
407 92 : SetupOutputVariable(state,
408 : "EMPD Deep Layer Equivalent Relative Humidity",
409 : Constant::Units::Perc,
410 46 : rvd.RH_deep_layer,
411 : OutputProcessor::TimeStepType::Zone,
412 : OutputProcessor::StoreType::Average,
413 : surf_name);
414 92 : SetupOutputVariable(state,
415 : "EMPD Surface Layer Equivalent Humidity Ratio",
416 : Constant::Units::kgWater_kgDryAir,
417 46 : rvd.w_surface_layer,
418 : OutputProcessor::TimeStepType::Zone,
419 : OutputProcessor::StoreType::Average,
420 : surf_name);
421 92 : SetupOutputVariable(state,
422 : "EMPD Deep Layer Equivalent Humidity Ratio",
423 : Constant::Units::kgWater_kgDryAir,
424 46 : rvd.w_deep_layer,
425 : OutputProcessor::TimeStepType::Zone,
426 : OutputProcessor::StoreType::Average,
427 : surf_name);
428 92 : SetupOutputVariable(state,
429 : "EMPD Surface Moisture Flux to Zone",
430 : Constant::Units::kg_m2s,
431 46 : rvd.mass_flux_zone,
432 : OutputProcessor::TimeStepType::Zone,
433 : OutputProcessor::StoreType::Average,
434 : surf_name);
435 92 : SetupOutputVariable(state,
436 : "EMPD Deep Layer Moisture Flux",
437 : Constant::Units::kg_m2s,
438 46 : rvd.mass_flux_deep,
439 : OutputProcessor::TimeStepType::Zone,
440 : OutputProcessor::StoreType::Average,
441 : surf_name);
442 46 : }
443 :
444 2 : if (state.dataMoistureBalEMPD->InitEnvrnFlag) {
445 2 : state.dataMoistureBalEMPD->InitEnvrnFlag = false;
446 : }
447 : }
448 :
449 703579 : void CalcMoistureBalanceEMPD(EnergyPlusData &state,
450 : int const SurfNum,
451 : Real64 const SurfTempIn, // INSIDE SURFACE TEMPERATURE at current time step
452 : Real64 const TempZone, // Zone temperature at current time step.
453 : Real64 &TempSat // Saturated surface temperature.
454 : )
455 : {
456 :
457 : // SUBROUTINE INFORMATION:
458 : // Authors: Muthusamy Swami and Lixing Gu
459 : // Date written: August, 1999
460 : // Modified: na
461 : // Re-engineered: na
462 :
463 : // PURPOSE OF THIS SUBROUTINE:
464 : // Calculate surface moisture level using EMPD model
465 :
466 : // Using/Aliasing
467 : using DataMoistureBalanceEMPD::Lam;
468 : using Psychrometrics::PsyCpAirFnW;
469 : using Psychrometrics::PsyPsatFnTemp;
470 : using Psychrometrics::PsyRhFnTdbRhov;
471 : using Psychrometrics::PsyRhFnTdbRhovLBnd0C;
472 : using Psychrometrics::PsyRhFnTdbWPb;
473 : using Psychrometrics::PsyRhoAirFnPbTdbW;
474 : using Psychrometrics::PsyRhovFnTdbRh;
475 : using Psychrometrics::PsyRhovFnTdbWPb;
476 : using Psychrometrics::PsyRhovFnTdbWPb_fast;
477 : using Psychrometrics::PsyWFnTdbRhPb;
478 :
479 : static constexpr std::string_view routineName = "CalcMoistureEMPD";
480 :
481 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
482 : Real64 hm_deep_layer; // Overall deep-layer transfer coefficient
483 : Real64 RSurfaceLayer; // Mass transfer resistance between actual surface and surface layer node
484 : Real64 Taver; // Average zone temperature between current time and previous time
485 : // REAL(r64) :: Waver ! Average zone humidity ratio between current time and previous time
486 : Real64 RHaver; // Average zone relative humidity {0-1} between current time and previous time
487 : Real64 RVaver; // Average zone vapor density
488 : Real64 dU_dRH;
489 : Real64 PVsurf; // Surface vapor pressure
490 : Real64 PV_surf_layer; // Vapor pressure of surface layer
491 : Real64 PV_deep_layer;
492 : Real64 PVsat; // saturation vapor pressure at the surface
493 : Real64 RH_surf_layer_old;
494 : Real64 RH_deep_layer_old;
495 : Real64 EMPDdiffusivity;
496 : Real64 Rcoating;
497 : Real64 RH_surf_layer;
498 : Real64 RH_surf_layer_tmp;
499 : Real64 RH_deep_layer;
500 :
501 703579 : auto &s_mat = state.dataMaterial;
502 :
503 703579 : if (state.dataGlobal->BeginEnvrnFlag && state.dataMoistureBalEMPD->OneTimeFlag) {
504 14 : InitMoistureBalanceEMPD(state);
505 14 : state.dataMoistureBalEMPD->OneTimeFlag = false;
506 : }
507 :
508 703579 : if (!state.dataGlobal->BeginEnvrnFlag) {
509 700861 : state.dataMoistureBalEMPD->OneTimeFlag = true;
510 : }
511 :
512 703579 : auto const &surface(state.dataSurface->Surface(SurfNum)); // input
513 703579 : auto &rv_surface(state.dataMstBalEMPD->RVSurface(SurfNum)); // output
514 703579 : auto const &rv_surface_old(state.dataMstBalEMPD->RVSurfaceOld(SurfNum)); // input
515 703579 : auto const &h_mass_conv_in_fd(state.dataMstBal->HMassConvInFD(SurfNum)); // input
516 703579 : auto const &rho_vapor_air_in(state.dataMstBal->RhoVaporAirIn(SurfNum)); // input
517 : Real64 RHZone;
518 : Real64 mass_flux_surf_deep;
519 : Real64 mass_flux_surf_deep_max;
520 : Real64 mass_flux_zone_surf;
521 : Real64 mass_flux_zone_surf_max;
522 : Real64 mass_flux_surf_layer;
523 : Real64 mass_flux_deep_layer;
524 : Real64 mass_flux_zone;
525 703579 : auto &rv_surf_layer(state.dataMstBalEMPD->RVSurfLayer(SurfNum)); // output
526 703579 : auto const &rv_surf_layer_old(state.dataMstBalEMPD->RVSurfLayerOld(SurfNum)); // input
527 : Real64 hm_surf_layer;
528 703579 : auto &rv_deep_layer(state.dataMstBalEMPD->RVDeepLayer(SurfNum)); // output
529 703579 : auto const &rv_deep_old(state.dataMstBalEMPD->RVdeepOld(SurfNum)); // input
530 703579 : auto &heat_flux_latent(state.dataMstBalEMPD->HeatFluxLatent(SurfNum)); // output
531 :
532 703579 : heat_flux_latent = 0.0;
533 703579 : if (!surface.HeatTransSurf) {
534 0 : return;
535 : }
536 :
537 703579 : auto const &constr = state.dataConstruction->Construct(surface.Construction);
538 703579 : auto const *mat = dynamic_cast<MaterialEMPD const *>(s_mat->materials(constr.LayerPoint(constr.TotLayers)));
539 703579 : assert(mat != nullptr);
540 703579 : if (mat->mu <= 0.0) {
541 0 : rv_surface =
542 0 : PsyRhovFnTdbWPb(TempZone, state.dataZoneTempPredictorCorrector->zoneHeatBalance(surface.Zone).airHumRat, state.dataEnvrn->OutBaroPress);
543 0 : return;
544 : }
545 :
546 703579 : Taver = SurfTempIn;
547 : // Calculate average vapor density [kg/m^3], and RH for use in material property calculations.
548 703579 : RVaver = rv_surface_old;
549 703579 : RHaver = RVaver * 461.52 * (Taver + Constant::Kelvin) * std::exp(-23.7093 + 4111.0 / (Taver + 237.7));
550 :
551 : // Calculate the saturated vapor pressure, surface vapor pressure and dewpoint. Used to check for condensation in HeatBalanceSurfaceManager
552 703579 : PVsat = PsyPsatFnTemp(state, Taver, routineName);
553 703579 : PVsurf = RHaver * std::exp(23.7093 - 4111.0 / (Taver + 237.7));
554 703579 : TempSat = 4111.0 / (23.7093 - std::log(PVsurf)) + 35.45 - Constant::Kelvin;
555 :
556 : // Convert vapor resistance factor (user input) to diffusivity. Evaluate at local surface temperature.
557 : // 2e-7*T^0.81/P = vapor diffusivity in air. [kg/m-s-Pa]
558 : // 461.52 = universal gas constant for water [J/kg-K]
559 : // EMPDdiffusivity = [m^2/s]
560 703579 : EMPDdiffusivity = (2.0e-7 * pow(Taver + Constant::Kelvin, 0.81) / state.dataEnvrn->OutBaroPress) / mat->mu * 461.52 * (Taver + Constant::Kelvin);
561 :
562 : // Calculate slope of moisture sorption curve at current RH. [kg/kg-RH]
563 703579 : dU_dRH = mat->moistACoeff * mat->moistBCoeff * pow(RHaver, mat->moistBCoeff - 1) +
564 703579 : mat->moistCCoeff * mat->moistDCoeff * pow(RHaver, mat->moistDCoeff - 1);
565 :
566 : // Convert vapor density and temperature of zone air to RH
567 703579 : RHZone = rho_vapor_air_in * 461.52 * (TempZone + Constant::Kelvin) * std::exp(-23.7093 + 4111.0 / ((TempZone + Constant::Kelvin) - 35.45));
568 :
569 : // Convert stored vapor density from previous timestep to RH.
570 703579 : RH_deep_layer_old = PsyRhFnTdbRhov(state, Taver, rv_deep_old);
571 703579 : RH_surf_layer_old = PsyRhFnTdbRhov(state, Taver, rv_surf_layer_old);
572 :
573 : // If coating vapor resistance factor equals 0, coating resistance is zero (avoid divide by zero).
574 : // Otherwise, calculate coating resistance with coating vapor resistance factor and thickness. [s/m]
575 703579 : if (mat->muCoating <= 0.0) {
576 703579 : Rcoating = 0;
577 : } else {
578 0 : Rcoating = mat->coatingThickness * mat->muCoating * state.dataEnvrn->OutBaroPress /
579 0 : (2.0e-7 * pow(Taver + Constant::Kelvin, 0.81) * 461.52 * (Taver + Constant::Kelvin));
580 : }
581 :
582 : // Calculate mass-transfer coefficient between zone air and center of surface layer. [m/s]
583 703579 : hm_surf_layer = 1.0 / (0.5 * mat->surfaceDepth / EMPDdiffusivity + 1.0 / h_mass_conv_in_fd + Rcoating);
584 : // Calculate mass-transfer coefficient between center of surface layer and center of deep layer. [m/s]
585 : // If deep layer depth = 0, set mass-transfer coefficient to zero (simulates with no deep layer).
586 703579 : if (mat->deepDepth <= 0.0) {
587 123219 : hm_deep_layer = 0;
588 : } else {
589 580360 : hm_deep_layer = 2.0 * EMPDdiffusivity / (mat->deepDepth + mat->surfaceDepth);
590 : }
591 : // Calculate resistance between surface-layer/air interface and center of surface layer. [s/m]
592 : // This is the physical surface of the material.
593 703579 : RSurfaceLayer = 1.0 / hm_surf_layer - 1.0 / h_mass_conv_in_fd;
594 :
595 : // Calculate vapor flux leaving surface layer, entering deep layer, and entering zone.
596 703579 : mass_flux_surf_deep_max =
597 703579 : mat->deepDepth * mat->Density * dU_dRH * (RH_surf_layer_old - RH_deep_layer_old) / (state.dataGlobal->TimeStepZone * 3600.0);
598 703579 : mass_flux_surf_deep = hm_deep_layer * (rv_surf_layer_old - rv_deep_old);
599 703579 : if (std::abs(mass_flux_surf_deep_max) < std::abs(mass_flux_surf_deep)) {
600 0 : mass_flux_surf_deep = mass_flux_surf_deep_max;
601 : }
602 :
603 703579 : mass_flux_zone_surf_max = mat->surfaceDepth * mat->Density * dU_dRH * (RHZone - RH_surf_layer_old) / (state.dataGlobal->TimeStepZone * 3600.0);
604 703579 : mass_flux_zone_surf = hm_surf_layer * (rho_vapor_air_in - rv_surf_layer_old);
605 703579 : if (std::abs(mass_flux_zone_surf_max) < std::abs(mass_flux_zone_surf)) {
606 10758 : mass_flux_zone_surf = mass_flux_zone_surf_max;
607 : }
608 :
609 : // mass_flux_surf_layer = -mass_flux_zone_surf + mass_flux_surf_deep;
610 : // mass_flux_deep_layer = mass_flux_surf_deep;
611 : // mass_flux_zone = -mass_flux_zone_surf;
612 :
613 703579 : 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);
614 703579 : mass_flux_deep_layer = hm_deep_layer * (rv_surf_layer_old - rv_deep_old);
615 703579 : mass_flux_zone = hm_surf_layer * (rv_surf_layer_old - rho_vapor_air_in);
616 :
617 : // Calculate new surface layer RH using mass balance on surface layer
618 703579 : RH_surf_layer_tmp =
619 703579 : RH_surf_layer_old + state.dataGlobal->TimeStepZone * 3600.0 * (-mass_flux_surf_layer / (mat->Density * mat->surfaceDepth * dU_dRH));
620 :
621 : // RH_surf_layer = RH_surf_layer_tmp;
622 :
623 703579 : if (RH_surf_layer_old < RH_deep_layer_old && RH_surf_layer_old < RHZone) {
624 199368 : if (RHZone > RH_deep_layer_old) {
625 107065 : if (RH_surf_layer_tmp > RHZone) {
626 0 : RH_surf_layer = RHZone;
627 : } else {
628 107065 : RH_surf_layer = RH_surf_layer_tmp;
629 : }
630 92303 : } else if (RH_surf_layer_tmp > RH_deep_layer_old) {
631 0 : RH_surf_layer = RH_deep_layer_old;
632 : } else {
633 92303 : RH_surf_layer = RH_surf_layer_tmp;
634 : }
635 :
636 504211 : } else if (RH_surf_layer_old < RH_deep_layer_old && RH_surf_layer_old > RHZone) {
637 210414 : if (RH_surf_layer_tmp > RH_deep_layer_old) {
638 180 : RH_surf_layer = RH_deep_layer_old;
639 210234 : } else if (RH_surf_layer_tmp < RHZone) {
640 1739 : RH_surf_layer = RHZone;
641 : } else {
642 208495 : RH_surf_layer = RH_surf_layer_tmp;
643 : }
644 293797 : } else if (RH_surf_layer_old > RH_deep_layer_old && RH_surf_layer_old < RHZone) {
645 114713 : if (RH_surf_layer_tmp > RHZone) {
646 598 : RH_surf_layer = RHZone;
647 114115 : } else if (RH_surf_layer_tmp < RH_deep_layer_old) {
648 593 : RH_surf_layer = RH_deep_layer_old;
649 : } else {
650 113522 : RH_surf_layer = RH_surf_layer_tmp;
651 : }
652 179084 : } else if (RHZone < RH_deep_layer_old) {
653 90985 : if (RH_surf_layer_tmp < RHZone) {
654 1 : RH_surf_layer = RHZone;
655 : } else {
656 90984 : RH_surf_layer = RH_surf_layer_tmp;
657 : }
658 88099 : } else if (RH_surf_layer_tmp < RH_deep_layer_old) {
659 246 : RH_surf_layer = RH_deep_layer_old;
660 : } else {
661 87853 : RH_surf_layer = RH_surf_layer_tmp;
662 : }
663 :
664 : // Calculate new deep layer RH using mass balance on deep layer (unless depth <= 0).
665 703579 : if (mat->deepDepth <= 0.0) {
666 123219 : RH_deep_layer = RH_deep_layer_old;
667 : } else {
668 580360 : RH_deep_layer = RH_deep_layer_old + state.dataGlobal->TimeStepZone * 3600.0 * mass_flux_deep_layer / (mat->Density * mat->deepDepth * dU_dRH);
669 : }
670 : // Convert calculated RH back to vapor density of surface and deep layers.
671 703579 : rv_surf_layer = PsyRhovFnTdbRh(state, Taver, RH_surf_layer);
672 703579 : rv_deep_layer = PsyRhovFnTdbRh(state, Taver, RH_deep_layer);
673 :
674 : // Calculate surface-layer and deep-layer vapor pressures [Pa]
675 703579 : PV_surf_layer = RH_surf_layer * std::exp(23.7093 - 4111.0 / (Taver + 237.7));
676 703579 : PV_deep_layer = RH_deep_layer * std::exp(23.7093 - 4111.0 / (Taver + 237.7));
677 :
678 : // Calculate vapor density at physical material surface (surface-layer/air interface). This is used to calculate total moisture flow terms for
679 : // each zone in HeatBalanceSurfaceManager
680 703579 : rv_surface = rv_surf_layer - mass_flux_zone * RSurfaceLayer;
681 :
682 : // Calculate heat flux from latent-sensible conversion due to moisture adsorption [W/m^2]
683 703579 : heat_flux_latent = mass_flux_zone * Lam;
684 :
685 : // Put results in the reporting variables
686 : // Will add RH and W of deep layer as outputs
687 : // 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
688 703579 : EMPDReportVarsData &rvd = state.dataMoistureBalEMPD->EMPDReportVars(SurfNum);
689 703579 : rvd.rv_surface = rv_surface;
690 703579 : rvd.RH_surface_layer = RH_surf_layer * 100.0;
691 703579 : rvd.RH_deep_layer = RH_deep_layer * 100.0;
692 703579 : rvd.w_surface_layer = 0.622 * PV_surf_layer / (state.dataEnvrn->OutBaroPress - PV_surf_layer);
693 703579 : rvd.w_deep_layer = 0.622 * PV_deep_layer / (state.dataEnvrn->OutBaroPress - PV_deep_layer);
694 703579 : rvd.mass_flux_zone = mass_flux_zone;
695 703579 : rvd.mass_flux_deep = mass_flux_deep_layer;
696 703579 : rvd.u_surface_layer = mat->moistACoeff * pow(RH_surf_layer, mat->moistBCoeff) + mat->moistCCoeff * pow(RH_surf_layer, mat->moistDCoeff);
697 703579 : rvd.u_deep_layer = mat->moistACoeff * pow(RH_deep_layer, mat->moistBCoeff) + mat->moistCCoeff * pow(RH_deep_layer, mat->moistDCoeff);
698 : }
699 :
700 150018 : void UpdateMoistureBalanceEMPD(EnergyPlusData &state, int const SurfNum) // Surface number
701 : {
702 :
703 : // SUBROUTINE INFORMATION:
704 : // Authors: Muthusamy Swami and Lixing Gu
705 : // Date writtenn: August, 1999
706 : // Modified: na
707 : // Re-engineered: na
708 :
709 : // PURPOSE OF THIS SUBROUTINE:
710 : // Update inside surface vapor density
711 :
712 150018 : state.dataMstBalEMPD->RVSurfaceOld(SurfNum) = state.dataMstBalEMPD->RVSurface(SurfNum);
713 150018 : state.dataMstBalEMPD->RVdeepOld(SurfNum) = state.dataMstBalEMPD->RVDeepLayer(SurfNum);
714 150018 : state.dataMstBalEMPD->RVSurfLayerOld(SurfNum) = state.dataMstBalEMPD->RVSurfLayer(SurfNum);
715 150018 : }
716 :
717 2 : void ReportMoistureBalanceEMPD(EnergyPlusData &state)
718 : {
719 :
720 : // SUBROUTINE INFORMATION:
721 : // AUTHOR Lixing Gu
722 : // DATE WRITTEN August 2005
723 : // MODIFIED na
724 : // RE-ENGINEERED na
725 :
726 : // PURPOSE OF THIS SUBROUTINE:
727 : // This routine gives a detailed report to the user about
728 : // EMPD Properties of each construction.
729 :
730 : // Using/Aliasing
731 : using General::ScanForReports;
732 :
733 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
734 : bool DoReport;
735 :
736 2 : auto &s_mat = state.dataMaterial;
737 :
738 6 : ScanForReports(state, "Constructions", DoReport, "Constructions");
739 :
740 2 : if (!DoReport) {
741 0 : return;
742 : }
743 : // Write Descriptions
744 2 : print(state.files.eio,
745 : "{}",
746 : "! <Construction EMPD>, Construction Name, Inside Layer Material Name, Vapor Resistance Factor, a, b, "
747 : "c, d, Surface Penetration Depth {m}, Deep Penetration Depth {m}, Coating Vapor Resistance Factor, "
748 : "Coating Thickness {m}\n");
749 :
750 13 : for (int ConstrNum = 1; ConstrNum <= state.dataHeatBal->TotConstructs; ++ConstrNum) {
751 11 : auto const &constr = state.dataConstruction->Construct(ConstrNum);
752 11 : if (constr.TypeIsWindow) {
753 2 : continue;
754 : }
755 :
756 9 : auto const *mat = s_mat->materials(constr.LayerPoint(constr.TotLayers));
757 9 : if (!mat->hasEMPD) {
758 3 : continue;
759 : }
760 :
761 6 : auto const *matEMPD = dynamic_cast<MaterialEMPD const *>(mat);
762 6 : assert(matEMPD != nullptr);
763 :
764 : static constexpr std::string_view Format_700(
765 : " Construction EMPD, {}, {}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}, {:8.4F}\n");
766 6 : print(state.files.eio,
767 : Format_700,
768 6 : constr.Name,
769 6 : matEMPD->Name,
770 6 : matEMPD->mu,
771 6 : matEMPD->moistACoeff,
772 6 : matEMPD->moistBCoeff,
773 6 : matEMPD->moistCCoeff,
774 6 : matEMPD->moistDCoeff,
775 6 : matEMPD->surfaceDepth,
776 6 : matEMPD->deepDepth,
777 6 : matEMPD->muCoating,
778 6 : matEMPD->coatingThickness);
779 : }
780 : } // ReportMoistureBalanceEMPD()
781 :
782 : } // namespace EnergyPlus::MoistureBalanceEMPDManager
|