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