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