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 : // ObjexxFCL Headers
49 : #include <ObjexxFCL/Array.functions.hh>
50 : #include <ObjexxFCL/Array1D.hh>
51 : #include <ObjexxFCL/ArrayS.functions.hh>
52 : // #include <ObjexxFCL/Fmath.hh>
53 : #include <ObjexxFCL/member.functions.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Data/EnergyPlusData.hh>
57 : #include <EnergyPlus/DataEnvironment.hh>
58 : #include <EnergyPlus/DataErrorTracking.hh>
59 : #include <EnergyPlus/DataHVACGlobals.hh>
60 : #include <EnergyPlus/DataHeatBalFanSys.hh>
61 : #include <EnergyPlus/DataHeatBalance.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/DataRoomAirModel.hh>
64 : #include <EnergyPlus/DataSurfaces.hh>
65 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
66 : #include <EnergyPlus/DataZoneEquipment.hh>
67 : #include <EnergyPlus/FluidProperties.hh>
68 : #include <EnergyPlus/General.hh>
69 : #include <EnergyPlus/InternalHeatGains.hh>
70 : #include <EnergyPlus/OutputProcessor.hh>
71 : #include <EnergyPlus/Psychrometrics.hh>
72 : #include <EnergyPlus/RoomAirModelUserTempPattern.hh>
73 : #include <EnergyPlus/ScheduleManager.hh>
74 : #include <EnergyPlus/UtilityRoutines.hh>
75 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
76 :
77 : namespace EnergyPlus::RoomAir {
78 :
79 : // MODULE INFORMATION:
80 : // AUTHOR Brent Griffith
81 : // DATE WRITTEN August 2005 (started in January 2004)
82 : // RE-ENGINEERED
83 :
84 : // PURPOSE OF THIS MODULE:
85 : // This module is the main module for running the
86 : // user-defined temperature pattern model.
87 : // This "air model" doesn't predict anything about the room air
88 : // but provides a method for users to model the
89 : // impact of non-uniform air temps. the distribution of air temperatures
90 : // is defined by the user and referred to as a "pattern"
91 :
92 : // METHODOLOGY EMPLOYED:
93 : // This module contains all subroutines required by the
94 : // user defined temperature pattern roomair modeling.
95 : // See DataRoomAir.cc for variable declarations
96 :
97 : // Functions
98 :
99 38016 : void ManageUserDefinedPatterns(EnergyPlusData &state, int const ZoneNum) // index number for the specified zone
100 : {
101 :
102 : // SUBROUTINE INFORMATION:
103 : // AUTHOR Brent Griffith
104 : // DATE WRITTEN January 2004/Aug 2005
105 : // MODIFIED na
106 : // RE-ENGINEERED na
107 :
108 : // PURPOSE OF THIS SUBROUTINE:
109 : // manage the user-defined air temp. distribution model
110 :
111 : // transfer data from surface domain to air domain for the specified zone
112 38016 : InitTempDistModel(state, ZoneNum);
113 :
114 38016 : GetSurfHBDataForTempDistModel(state, ZoneNum);
115 :
116 : // perform TempDist model calculations
117 38016 : CalcTempDistModel(state, ZoneNum);
118 :
119 : // transfer data from air domain back to surface domain for the specified zone
120 38016 : SetSurfHBDataForTempDistModel(state, ZoneNum);
121 38016 : }
122 :
123 : //****************************************************
124 :
125 38016 : void InitTempDistModel(EnergyPlusData &state, int const ZoneNum) // index number for the specified zone
126 : {
127 :
128 : // SUBROUTINE INFORMATION:
129 : // AUTHOR <author>
130 : // DATE WRITTEN <date_written>
131 :
132 38016 : if (state.dataRoomAirModelTempPattern->MyOneTimeFlag) {
133 1 : state.dataRoomAirModelTempPattern->MyEnvrnFlag.dimension(state.dataGlobal->NumOfZones, true);
134 1 : state.dataRoomAirModelTempPattern->MyOneTimeFlag = false;
135 : }
136 :
137 38016 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
138 38016 : if (state.dataGlobal->BeginEnvrnFlag && state.dataRoomAirModelTempPattern->MyEnvrnFlag(ZoneNum)) {
139 81 : patternZoneInfo.TairMean = 23.0;
140 81 : patternZoneInfo.Tstat = 23.0;
141 81 : patternZoneInfo.Tleaving = 23.0;
142 81 : patternZoneInfo.Texhaust = 23.0;
143 81 : patternZoneInfo.Gradient = 0.0;
144 3780 : for (int SurfNum = 1; SurfNum <= patternZoneInfo.totNumSurfs; ++SurfNum) {
145 3699 : patternZoneInfo.Surf(SurfNum).TadjacentAir = 23.0;
146 : }
147 81 : state.dataRoomAirModelTempPattern->MyEnvrnFlag(ZoneNum) = false;
148 : }
149 :
150 38016 : if (!state.dataGlobal->BeginEnvrnFlag) {
151 37800 : state.dataRoomAirModelTempPattern->MyEnvrnFlag(ZoneNum) = true;
152 : }
153 :
154 : // init report variable
155 38016 : patternZoneInfo.Gradient = 0.0;
156 38016 : }
157 :
158 38016 : void GetSurfHBDataForTempDistModel(EnergyPlusData &state, int const ZoneNum) // index number for the specified zone
159 : {
160 :
161 : // SUBROUTINE INFORMATION:
162 : // AUTHOR B. Griffith
163 : // DATE WRITTEN August 2005
164 :
165 : // PURPOSE OF THIS SUBROUTINE:
166 : // map data from Heat Balance domain to Room Air Modeling Domain
167 : // for the current zone, (only need mean air temp)
168 : // also acts as an init routine
169 :
170 : // METHODOLOGY EMPLOYED:
171 : // use ZT from DataHeatBalFanSys
172 :
173 38016 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
174 38016 : auto const &zoneHeatBal = state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum);
175 : // initialize in preparation for calculations
176 38016 : patternZoneInfo.Tstat = zoneHeatBal.MAT;
177 38016 : patternZoneInfo.Tleaving = zoneHeatBal.MAT;
178 38016 : patternZoneInfo.Texhaust = zoneHeatBal.MAT;
179 1774080 : for (auto &e : patternZoneInfo.Surf) {
180 1736064 : e.TadjacentAir = zoneHeatBal.MAT;
181 : }
182 :
183 : // the only input this method needs is the zone MAT or ZT or ZTAV ? (original was ZT)
184 38016 : patternZoneInfo.TairMean = zoneHeatBal.MAT; // this is lagged from previous corrector result
185 38016 : }
186 :
187 : //*****************************************************************************************
188 :
189 38016 : void CalcTempDistModel(EnergyPlusData &state, int const ZoneNum) // index number for the specified zone
190 : {
191 :
192 : // SUBROUTINE INFORMATION:
193 : // AUTHOR Brent Griffith
194 : // DATE WRITTEN August 2005
195 : // MODIFIED
196 : // RE-ENGINEERED
197 :
198 : // PURPOSE OF THIS SUBROUTINE:
199 : // figure out which pattern is scheduled and call
200 : // appropriate subroutine
201 :
202 : // Using/Aliasing
203 : using General::FindNumberInList;
204 :
205 38016 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
206 : // first determine availability
207 38016 : Real64 AvailTest = patternZoneInfo.availSched->getCurrentVal();
208 :
209 38016 : if ((AvailTest != 1.0) || (!patternZoneInfo.IsUsed)) {
210 : // model not to be used. Use complete mixing method
211 :
212 3466 : patternZoneInfo.Tstat = patternZoneInfo.TairMean;
213 3466 : patternZoneInfo.Tleaving = patternZoneInfo.TairMean;
214 3466 : patternZoneInfo.Texhaust = patternZoneInfo.TairMean;
215 164635 : for (auto &e : patternZoneInfo.Surf) {
216 161169 : e.TadjacentAir = patternZoneInfo.TairMean;
217 : }
218 :
219 3466 : return;
220 :
221 : } else { // choose pattern and call subroutine
222 :
223 34550 : int CurntPatternKey = patternZoneInfo.patternSched->getCurrentVal();
224 :
225 34550 : int CurPatrnID = FindNumberInList(CurntPatternKey, state.dataRoomAir->AirPattern, &TemperaturePattern::PatrnID);
226 :
227 34550 : if (CurPatrnID == 0) {
228 : // throw error here ? way to test schedules before getting to this point?
229 0 : ShowFatalError(state, format("User defined room air pattern index not found: {}", CurntPatternKey));
230 0 : return;
231 : }
232 :
233 34550 : switch (state.dataRoomAir->AirPattern(CurPatrnID).PatternMode) {
234 10582 : case UserDefinedPatternType::ConstGradTemp: {
235 10582 : FigureConstGradPattern(state, CurPatrnID, ZoneNum);
236 10582 : } break;
237 18235 : case UserDefinedPatternType::TwoGradInterp: {
238 18235 : FigureTwoGradInterpPattern(state, CurPatrnID, ZoneNum);
239 18235 : } break;
240 4925 : case UserDefinedPatternType::NonDimenHeight: {
241 4925 : FigureHeightPattern(state, CurPatrnID, ZoneNum);
242 4925 : } break;
243 808 : case UserDefinedPatternType::SurfMapTemp: {
244 808 : FigureSurfMapPattern(state, CurPatrnID, ZoneNum);
245 808 : } break;
246 0 : default: {
247 0 : assert(false);
248 : } break;
249 : }
250 : } // availability control construct
251 : }
252 :
253 808 : void FigureSurfMapPattern(EnergyPlusData &state, int const PattrnID, int const ZoneNum)
254 : {
255 :
256 : // SUBROUTINE INFORMATION:
257 : // AUTHOR B Griffith
258 : // DATE WRITTEN August 2005
259 : // MODIFIED na
260 : // RE-ENGINEERED na
261 :
262 : // PURPOSE OF THIS SUBROUTINE:
263 : // main calculation routine for surface pattern
264 :
265 : // METHODOLOGY EMPLOYED:
266 : // simple polling and applying prescribed
267 : // delta Tai's to current mean air temp
268 : // on a surface by surface basis
269 :
270 : // Using/Aliasing
271 : using General::FindNumberInList;
272 :
273 808 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
274 808 : auto &pattern = state.dataRoomAir->AirPattern(PattrnID);
275 808 : Real64 Tmean = patternZoneInfo.TairMean;
276 :
277 40400 : for (int i = 1; i <= patternZoneInfo.totNumSurfs; ++i) {
278 : // cycle through zone surfaces and look for match
279 39592 : int found = FindNumberInList(patternZoneInfo.Surf(i).SurfID, pattern.MapPatrn.SurfID, pattern.MapPatrn.NumSurfs);
280 39592 : if (found != 0) { // if surf is in map then assign, else give it MAT
281 7272 : patternZoneInfo.Surf(i).TadjacentAir = pattern.MapPatrn.DeltaTai(found) + Tmean;
282 : } else {
283 32320 : patternZoneInfo.Surf(i).TadjacentAir = Tmean;
284 : }
285 : }
286 :
287 808 : patternZoneInfo.Tstat = pattern.DeltaTstat + Tmean;
288 808 : patternZoneInfo.Tleaving = pattern.DeltaTleaving + Tmean;
289 808 : patternZoneInfo.Texhaust = pattern.DeltaTexhaust + Tmean;
290 808 : }
291 :
292 4925 : void FigureHeightPattern(EnergyPlusData &state, int const PattrnID, int const ZoneNum)
293 : {
294 :
295 : // SUBROUTINE INFORMATION:
296 : // AUTHOR B Griffith
297 : // DATE WRITTEN August 2005
298 :
299 : // PURPOSE OF THIS SUBROUTINE:
300 : // calculate the pattern for non-dimensional vertical profile
301 :
302 : // METHODOLOGY EMPLOYED:
303 : // treat profile as lookup table and interpolate
304 :
305 : // Using/Aliasing
306 : using Fluid::FindArrayIndex;
307 :
308 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
309 :
310 4925 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
311 4925 : auto &pattern = state.dataRoomAir->AirPattern(PattrnID);
312 4925 : Real64 tmpDeltaTai = 0.0;
313 4925 : Real64 Tmean = patternZoneInfo.TairMean;
314 :
315 200505 : for (int i = 1; i <= patternZoneInfo.totNumSurfs; ++i) {
316 :
317 195580 : Real64 zeta = patternZoneInfo.Surf(i).Zeta;
318 195580 : int lowSideID = FindArrayIndex(zeta, pattern.VertPatrn.ZetaPatrn);
319 195580 : int highSideID = lowSideID + 1;
320 195580 : if (lowSideID == 0) {
321 4925 : lowSideID = 1; // protect against array bounds
322 : }
323 :
324 195580 : Real64 lowSideZeta = pattern.VertPatrn.ZetaPatrn(lowSideID);
325 195580 : Real64 hiSideZeta = (highSideID <= isize(pattern.VertPatrn.ZetaPatrn)) ? pattern.VertPatrn.ZetaPatrn(highSideID) : lowSideZeta;
326 :
327 195580 : if ((hiSideZeta - lowSideZeta) != 0.0) {
328 185730 : Real64 fractBtwn = (zeta - lowSideZeta) / (hiSideZeta - lowSideZeta);
329 185730 : tmpDeltaTai = pattern.VertPatrn.DeltaTaiPatrn(lowSideID) +
330 185730 : fractBtwn * (pattern.VertPatrn.DeltaTaiPatrn(highSideID) - pattern.VertPatrn.DeltaTaiPatrn(lowSideID));
331 :
332 : } else { // would divide by zero, using low side value
333 :
334 9850 : tmpDeltaTai = pattern.VertPatrn.DeltaTaiPatrn(lowSideID);
335 : }
336 :
337 195580 : patternZoneInfo.Surf(i).TadjacentAir = tmpDeltaTai + Tmean;
338 :
339 : } // surfaces in this zone
340 :
341 4925 : patternZoneInfo.Tstat = pattern.DeltaTstat + Tmean;
342 4925 : patternZoneInfo.Tleaving = pattern.DeltaTleaving + Tmean;
343 4925 : patternZoneInfo.Texhaust = pattern.DeltaTexhaust + Tmean;
344 4925 : }
345 :
346 18235 : void FigureTwoGradInterpPattern(EnergyPlusData &state, int const PattrnID, int const ZoneNum)
347 : {
348 :
349 : // SUBROUTINE INFORMATION:
350 : // AUTHOR B Griffith
351 : // DATE WRITTEN Aug 2005
352 :
353 : // PURPOSE OF THIS SUBROUTINE:
354 : // calculate two gradient interpolation pattern
355 :
356 : // METHODOLOGY EMPLOYED:
357 : // Case statement controls how interpolations are done
358 : // based on user selected mode.
359 : // calculations vary by mode
360 :
361 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
362 : Real64 Grad; // vertical temperature gradient C/m
363 :
364 18235 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
365 18235 : auto const &pattern = state.dataRoomAir->AirPattern(PattrnID);
366 :
367 18235 : if (state.dataRoomAirModelTempPattern->MyOneTimeFlag2) {
368 1 : state.dataRoomAirModelTempPattern->SetupOutputFlag.dimension(state.dataGlobal->NumOfZones, true); // init
369 1 : state.dataRoomAirModelTempPattern->MyOneTimeFlag2 = false;
370 : }
371 :
372 18235 : if (state.dataRoomAirModelTempPattern->SetupOutputFlag(ZoneNum)) {
373 12 : SetupOutputVariable(state,
374 : "Room Air Zone Vertical Temperature Gradient",
375 : Constant::Units::K_m,
376 6 : patternZoneInfo.Gradient,
377 : OutputProcessor::TimeStepType::System,
378 : OutputProcessor::StoreType::Average,
379 6 : patternZoneInfo.ZoneName);
380 :
381 6 : state.dataRoomAirModelTempPattern->SetupOutputFlag(ZoneNum) = false;
382 : }
383 :
384 18235 : Real64 Tmean = patternZoneInfo.TairMean;
385 :
386 18235 : auto const &twoGrad = pattern.TwoGradPatrn;
387 : // determine gradient depending on mode
388 18235 : switch (pattern.TwoGradPatrn.InterpolationMode) {
389 2779 : case UserDefinedPatternMode::OutdoorDryBulb: {
390 2779 : Grad = OutdoorDryBulbGrad(state.dataHeatBal->Zone(ZoneNum).OutDryBulbTemp,
391 2779 : twoGrad.UpperBoundTempScale,
392 2779 : twoGrad.HiGradient,
393 2779 : twoGrad.LowerBoundTempScale,
394 2779 : twoGrad.LowGradient);
395 2779 : } break;
396 4517 : case UserDefinedPatternMode::ZoneAirTemp: {
397 4517 : if (Tmean >= twoGrad.UpperBoundTempScale) {
398 1250 : Grad = twoGrad.HiGradient;
399 3267 : } else if (Tmean <= twoGrad.LowerBoundTempScale) {
400 2280 : Grad = twoGrad.LowGradient;
401 987 : } else if ((twoGrad.UpperBoundTempScale - twoGrad.LowerBoundTempScale) == 0.0) {
402 : // bad user input, trapped during get input
403 0 : Grad = twoGrad.LowGradient;
404 : } else {
405 987 : Grad = twoGrad.LowGradient + ((Tmean - twoGrad.LowerBoundTempScale) / (twoGrad.UpperBoundTempScale - twoGrad.LowerBoundTempScale)) *
406 987 : (twoGrad.HiGradient - twoGrad.LowGradient);
407 : }
408 4517 : } break;
409 2491 : case UserDefinedPatternMode::DeltaOutdoorZone: {
410 2491 : Real64 DeltaT = state.dataHeatBal->Zone(ZoneNum).OutDryBulbTemp - Tmean;
411 2491 : if (DeltaT >= twoGrad.UpperBoundTempScale) {
412 576 : Grad = twoGrad.HiGradient;
413 1915 : } else if (DeltaT <= twoGrad.LowerBoundTempScale) {
414 1216 : Grad = twoGrad.LowGradient;
415 699 : } else if ((twoGrad.UpperBoundTempScale - twoGrad.LowerBoundTempScale) == 0.0) {
416 0 : Grad = twoGrad.LowGradient;
417 : } else {
418 699 : Grad = twoGrad.LowGradient + ((DeltaT - twoGrad.LowerBoundTempScale) / (twoGrad.UpperBoundTempScale - twoGrad.LowerBoundTempScale)) *
419 699 : (twoGrad.HiGradient - twoGrad.LowGradient);
420 : }
421 2491 : } break;
422 4224 : case UserDefinedPatternMode::SensibleCooling: {
423 4224 : Real64 CoolLoad = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).airSysCoolRate;
424 4224 : if (CoolLoad >= twoGrad.UpperBoundHeatRateScale) {
425 270 : Grad = twoGrad.HiGradient;
426 :
427 3954 : } else if (CoolLoad <= twoGrad.LowerBoundHeatRateScale) {
428 :
429 2570 : Grad = twoGrad.LowGradient;
430 : } else { // interpolate
431 1384 : if ((twoGrad.UpperBoundHeatRateScale - twoGrad.LowerBoundHeatRateScale) == 0.0) {
432 0 : Grad = twoGrad.LowGradient;
433 : } else {
434 :
435 1384 : Grad = twoGrad.LowGradient +
436 1384 : ((CoolLoad - twoGrad.LowerBoundHeatRateScale) / (twoGrad.UpperBoundHeatRateScale - twoGrad.LowerBoundHeatRateScale)) *
437 1384 : (twoGrad.HiGradient - twoGrad.LowGradient);
438 : }
439 : }
440 4224 : } break;
441 4224 : case UserDefinedPatternMode::SensibleHeating: {
442 4224 : Real64 HeatLoad = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).airSysHeatRate;
443 4224 : if (HeatLoad >= twoGrad.UpperBoundHeatRateScale) {
444 2343 : Grad = twoGrad.HiGradient;
445 1881 : } else if (HeatLoad <= twoGrad.LowerBoundHeatRateScale) {
446 1880 : Grad = twoGrad.LowGradient;
447 1 : } else if ((twoGrad.UpperBoundHeatRateScale - twoGrad.LowerBoundHeatRateScale) == 0.0) {
448 0 : Grad = twoGrad.LowGradient;
449 : } else {
450 1 : Grad = twoGrad.LowGradient +
451 1 : ((HeatLoad - twoGrad.LowerBoundHeatRateScale) / (twoGrad.UpperBoundHeatRateScale - twoGrad.LowerBoundHeatRateScale)) *
452 1 : (twoGrad.HiGradient - twoGrad.LowGradient);
453 : }
454 4224 : } break;
455 0 : default:
456 0 : break;
457 : }
458 :
459 18235 : Real64 ZetaTmean = 0.5; // by definition,
460 :
461 875270 : for (int i = 1; i <= patternZoneInfo.totNumSurfs; ++i) {
462 857035 : Real64 zeta = patternZoneInfo.Surf(i).Zeta;
463 857035 : Real64 DeltaHeight = -1.0 * (ZetaTmean - zeta) * patternZoneInfo.ZoneHeight;
464 857035 : patternZoneInfo.Surf(i).TadjacentAir = (DeltaHeight * Grad) + Tmean;
465 : }
466 :
467 18235 : patternZoneInfo.Tstat = -1.0 * (0.5 * patternZoneInfo.ZoneHeight - twoGrad.TstatHeight) * Grad + Tmean;
468 18235 : patternZoneInfo.Tleaving = -1.0 * (0.5 * patternZoneInfo.ZoneHeight - twoGrad.TleavingHeight) * Grad + Tmean;
469 18235 : patternZoneInfo.Texhaust = -1.0 * (0.5 * patternZoneInfo.ZoneHeight - twoGrad.TexhaustHeight) * Grad + Tmean;
470 18235 : patternZoneInfo.Gradient = Grad;
471 18235 : }
472 :
473 2779 : Real64 OutdoorDryBulbGrad(Real64 DryBulbTemp, // Zone(ZoneNum).OutDryBulbTemp
474 : Real64 UpperBound, // RoomAirPattern(PattrnID).TwoGradPatrn.UpperBoundTempScale
475 : Real64 HiGradient, // RoomAirPattern(PattrnID).TwoGradPatrn.HiGradient
476 : Real64 LowerBound, // RoomAirPattern(PattrnID).TwoGradPatrn.LowerBoundTempScale
477 : Real64 LowGradient // RoomAirPattern(PattrnID).TwoGradPatrn.LowGradient
478 : )
479 : {
480 2779 : if (DryBulbTemp >= UpperBound) {
481 1069 : return HiGradient;
482 1710 : } else if (DryBulbTemp <= LowerBound) {
483 1504 : return LowGradient;
484 206 : } else if ((UpperBound - LowerBound) == 0.0) {
485 0 : return LowGradient;
486 : } else {
487 206 : return LowGradient + ((DryBulbTemp - LowerBound) / (UpperBound - LowerBound)) * (HiGradient - LowGradient);
488 : }
489 : }
490 :
491 10582 : void FigureConstGradPattern(EnergyPlusData &state, int const PattrnID, int const ZoneNum)
492 : {
493 :
494 : // SUBROUTINE INFORMATION:
495 : // AUTHOR B. Griffith
496 : // DATE WRITTEN August 2005
497 :
498 10582 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
499 10582 : auto const &pattern = state.dataRoomAir->AirPattern(PattrnID);
500 10582 : Real64 Tmean = patternZoneInfo.TairMean; // MAT
501 10582 : Real64 Grad = pattern.GradPatrn.Gradient; // Vertical temperature gradient
502 :
503 10582 : Real64 ZetaTmean = 0.5; // non-dimensional height for MAT
504 :
505 493270 : for (int i = 1; i <= patternZoneInfo.totNumSurfs; ++i) {
506 482688 : Real64 zeta = patternZoneInfo.Surf(i).Zeta;
507 482688 : Real64 DeltaHeight = -1.0 * (ZetaTmean - zeta) * patternZoneInfo.ZoneHeight;
508 482688 : patternZoneInfo.Surf(i).TadjacentAir = DeltaHeight * Grad + Tmean;
509 : }
510 :
511 10582 : patternZoneInfo.Tstat = pattern.DeltaTstat + Tmean;
512 10582 : patternZoneInfo.Tleaving = pattern.DeltaTleaving + Tmean;
513 10582 : patternZoneInfo.Texhaust = pattern.DeltaTexhaust + Tmean;
514 10582 : }
515 :
516 : //*****************************************************************************************
517 :
518 402 : Real64 FigureNDheightInZone(EnergyPlusData &state, int const thisHBsurf) // index in main Surface array
519 : {
520 : // FUNCTION INFORMATION:
521 : // AUTHOR B.Griffith
522 : // DATE WRITTEN aug 2005, Jan2004
523 :
524 : // PURPOSE OF THIS FUNCTION:
525 : // return a non-dimensional height zeta
526 :
527 : // METHODOLOGY EMPLOYED:
528 : // figure average floor height (follows code in surfacegeometry.cc
529 : // use ceiling height from Zone structure
530 : // non dimensionalize surface's centroid's Z value
531 :
532 : // FUNCTION PARAMETER DEFINITIONS:
533 402 : Real64 constexpr TolValue(0.0001);
534 :
535 : // Get the centroid height for the surface
536 402 : Real64 Zcm = state.dataSurface->Surface(thisHBsurf).Centroid.z;
537 402 : auto &zone = state.dataHeatBal->Zone(state.dataSurface->Surface(thisHBsurf).Zone);
538 :
539 : // this next Do block is copied from SurfaceGeometry.cc with modification for just floor Z
540 : // used find floor z.
541 402 : int FloorCount = 0;
542 402 : Real64 ZFlrAvg = 0.0;
543 402 : Real64 ZMax = 0.0;
544 402 : Real64 ZMin = 0.0;
545 402 : int Count = 0;
546 804 : for (int spaceNum : zone.spaceIndexes) {
547 402 : auto const &thisSpace = state.dataHeatBal->space(spaceNum);
548 18860 : for (int SurfNum = thisSpace.HTSurfaceFirst; SurfNum <= thisSpace.HTSurfaceLast; ++SurfNum) {
549 18458 : auto const &surf = state.dataSurface->Surface(SurfNum);
550 18458 : if (surf.Class == DataSurfaces::SurfaceClass::Floor) {
551 : // Use Average Z for surface, more important for roofs than floors...
552 402 : ++FloorCount;
553 402 : Real64 Z1 = minval(surf.Vertex, &Vector3<Real64>::z);
554 402 : Real64 Z2 = maxval(surf.Vertex, &Vector3<Real64>::z);
555 402 : ZFlrAvg += (Z1 + Z2) / 2.0;
556 18056 : } else if (surf.Class == DataSurfaces::SurfaceClass::Wall) {
557 : // Use Wall calculation in case no floor in zone
558 14472 : ++Count;
559 14472 : if (Count == 1) {
560 402 : ZMax = surf.Vertex(1).z;
561 402 : ZMin = ZMax;
562 : }
563 14472 : ZMax = max(ZMax, maxval(surf.Vertex, &Vector3<Real64>::z));
564 14472 : ZMin = min(ZMin, minval(surf.Vertex, &Vector3<Real64>::z));
565 : }
566 : }
567 402 : }
568 :
569 402 : ZFlrAvg = (FloorCount > 0.0) ? (ZFlrAvg / FloorCount) : ZMin;
570 :
571 402 : Real64 ZoneZorig = ZFlrAvg; // Z floor [M]
572 402 : Real64 ZoneCeilHeight = zone.CeilingHeight;
573 :
574 : // first check if some basic things are reasonable
575 :
576 402 : Real64 SurfMinZ = minval(state.dataSurface->Surface(thisHBsurf).Vertex, &Vector3<Real64>::z);
577 402 : Real64 SurfMaxZ = maxval(state.dataSurface->Surface(thisHBsurf).Vertex, &Vector3<Real64>::z);
578 :
579 402 : if (SurfMinZ < (ZoneZorig - TolValue)) {
580 0 : if (state.dataGlobal->DisplayExtraWarnings) {
581 0 : ShowWarningError(state, "RoomAirModelUserTempPattern: Problem in non-dimensional height calculation");
582 0 : ShowContinueError(state, format("too low surface: {} in zone: {}", state.dataSurface->Surface(thisHBsurf).Name, zone.Name));
583 0 : ShowContinueError(state, format("**** Average floor height of zone is: {:.3R}", ZoneZorig));
584 0 : ShowContinueError(state, format("**** Surface minimum height is: {:.3R}", SurfMinZ));
585 : } else {
586 0 : ++state.dataErrTracking->TotalRoomAirPatternTooLow;
587 : }
588 : }
589 :
590 402 : if (SurfMaxZ > (ZoneZorig + ZoneCeilHeight + TolValue)) {
591 0 : if (state.dataGlobal->DisplayExtraWarnings) {
592 0 : ShowWarningError(state, "RoomAirModelUserTempPattern: Problem in non-dimensional height calculation");
593 0 : ShowContinueError(state, format(" too high surface: {} in zone: {}", state.dataSurface->Surface(thisHBsurf).Name, zone.Name));
594 0 : ShowContinueError(state, format("**** Average Ceiling height of zone is: {:.3R}", (ZoneZorig + ZoneCeilHeight)));
595 0 : ShowContinueError(state, format("**** Surface Maximum height is: {:.3R}", SurfMaxZ));
596 : } else {
597 0 : ++state.dataErrTracking->TotalRoomAirPatternTooHigh;
598 : }
599 : }
600 :
601 : // non dimensionalize.
602 402 : Real64 Zeta = (Zcm - ZoneZorig) / ZoneCeilHeight;
603 402 : if (Zeta > 0.99) {
604 9 : Zeta = 0.99;
605 393 : } else if (Zeta < 0.01) {
606 9 : Zeta = 0.01;
607 : }
608 :
609 402 : return Zeta;
610 : }
611 :
612 : //***************************************************
613 :
614 38016 : void SetSurfHBDataForTempDistModel(EnergyPlusData &state, int const ZoneNum) // index number for the specified zone
615 : {
616 :
617 : // SUBROUTINE INFORMATION:
618 : // AUTHOR Brent Griffith
619 : // DATE WRITTEN August 2005,Feb. 2004
620 :
621 : // PURPOSE OF THIS SUBROUTINE:
622 : // map data from air domain back to surface domain for each zone
623 : // collects code couples to remote data structures
624 :
625 : // METHODOLOGY EMPLOYED:
626 : // sets values in Heat balance variables
627 :
628 : // Using/Aliasing
629 : using HVAC::RetTempMax;
630 : using HVAC::RetTempMin;
631 : using InternalHeatGains::SumAllReturnAirLatentGains;
632 : using Psychrometrics::PsyCpAirFnW;
633 : using Psychrometrics::PsyHFnTdbW;
634 : using Psychrometrics::PsyHgAirFnWTdb;
635 : using Psychrometrics::PsyRhoAirFnPbTdbW;
636 :
637 : // set air system leaving node conditions
638 : // this is not so easy. THis task is normally done in CalcZoneLeavingConditions
639 : // but efforts to do this update there were not successful.
640 : // Need to revisit how to best implement this. Ended up taking code from CalcZoneLeavingConditions
641 : // ZoneNum is already equal to ActualZoneNum , changed block of source
642 :
643 38016 : auto &patternZoneInfo = state.dataRoomAir->AirPatternZoneInfo(ZoneNum);
644 :
645 38016 : if (patternZoneInfo.ZoneNodeID != 0) {
646 : // the zone system node should get the conditions leaving the zone (but before return air heat gains are added).
647 38016 : state.dataLoopNodes->Node(patternZoneInfo.ZoneNodeID).Temp = patternZoneInfo.Tleaving;
648 : }
649 :
650 : // What if ZoneNodeID is 0?
651 :
652 38016 : auto &zoneNode = state.dataLoopNodes->Node(patternZoneInfo.ZoneNodeID);
653 38016 : auto const &zone = state.dataHeatBal->Zone(ZoneNum);
654 38016 : auto &zoneHeatBal = state.dataZoneTempPredictorCorrector->zoneHeatBalance(ZoneNum);
655 :
656 38016 : int ZoneMult = zone.Multiplier * zone.ListMultiplier;
657 :
658 76032 : for (int returnNodeNum : state.dataZoneEquip->ZoneEquipConfig(ZoneNum).ReturnNode) {
659 : // BEGIN BLOCK of code from CalcZoneLeavingConditions*********************************
660 38016 : auto &returnNode = state.dataLoopNodes->Node(returnNodeNum);
661 :
662 : // RETURN AIR HEAT GAIN from the Lights statement; this heat gain is stored in
663 : // Add sensible heat gain from refrigerated cases with under case returns
664 38016 : Real64 QRetAir = InternalHeatGains::zoneSumAllReturnAirConvectionGains(state, ZoneNum, returnNodeNum);
665 :
666 38016 : Real64 CpAir = PsyCpAirFnW(zoneNode.HumRat);
667 :
668 : // Need to add the energy to the return air from lights and from airflow windows. Where the heat
669 : // is added depends on if there is system flow or not. If there is system flow the heat is added
670 : // to the Zone Return Node. If there is no system flow then the heat is added back to the zone in the
671 : // Correct step through the SysDepZoneLoads variable.
672 :
673 38016 : Real64 MassFlowRA = returnNode.MassFlowRate / ZoneMult;
674 38016 : Real64 TempZoneAir = patternZoneInfo.Tleaving; // key difference from
675 38016 : Real64 TempRetAir = TempZoneAir;
676 38016 : Real64 WinGapFlowToRA = 0.0;
677 38016 : Real64 WinGapTtoRA = 0.0;
678 38016 : Real64 WinGapFlowTtoRA = 0.0;
679 :
680 38016 : if (zone.HasAirFlowWindowReturn) {
681 0 : for (int spaceNum : zone.spaceIndexes) {
682 0 : auto const &thisSpace = state.dataHeatBal->space(spaceNum);
683 0 : for (int SurfNum = thisSpace.HTSurfaceFirst; SurfNum <= thisSpace.HTSurfaceLast; ++SurfNum) {
684 0 : if (state.dataSurface->SurfWinAirflowThisTS(SurfNum) > 0.0 &&
685 0 : state.dataSurface->SurfWinAirflowDestination(SurfNum) == DataSurfaces::WindowAirFlowDestination::Return) {
686 : Real64 FlowThisTS =
687 0 : PsyRhoAirFnPbTdbW(
688 0 : state, state.dataEnvrn->OutBaroPress, state.dataSurface->SurfWinTAirflowGapOutlet(SurfNum), zoneNode.HumRat) *
689 0 : state.dataSurface->SurfWinAirflowThisTS(SurfNum) * state.dataSurface->Surface(SurfNum).Width;
690 0 : WinGapFlowToRA += FlowThisTS;
691 0 : WinGapFlowTtoRA += FlowThisTS * state.dataSurface->SurfWinTAirflowGapOutlet(SurfNum);
692 : }
693 : }
694 0 : }
695 : }
696 38016 : if (WinGapFlowToRA > 0.0) {
697 0 : WinGapTtoRA = WinGapFlowTtoRA / WinGapFlowToRA;
698 : }
699 :
700 38016 : if (!zone.NoHeatToReturnAir) {
701 38016 : if (MassFlowRA > 0.0) {
702 37656 : if (WinGapFlowToRA > 0.0) {
703 : // Add heat-to-return from window gap airflow
704 0 : if (MassFlowRA >= WinGapFlowToRA) {
705 0 : TempRetAir = (WinGapFlowTtoRA + (MassFlowRA - WinGapFlowToRA) * TempZoneAir) / MassFlowRA;
706 : } else {
707 : // All of return air comes from flow through airflow windows
708 0 : TempRetAir = WinGapTtoRA;
709 : // Put heat from window airflow that exceeds return air flow into zone air
710 0 : zoneHeatBal.SysDepZoneLoads += (WinGapFlowToRA - MassFlowRA) * CpAir * (WinGapTtoRA - TempZoneAir);
711 : }
712 : }
713 : // Add heat-to-return from lights
714 37656 : TempRetAir += QRetAir / (MassFlowRA * CpAir);
715 37656 : if (TempRetAir > RetTempMax) {
716 1 : returnNode.Temp = RetTempMax;
717 1 : if (!state.dataGlobal->ZoneSizingCalc) {
718 0 : zoneHeatBal.SysDepZoneLoads += CpAir * MassFlowRA * (TempRetAir - RetTempMax);
719 : }
720 37655 : } else if (TempRetAir < RetTempMin) {
721 0 : returnNode.Temp = RetTempMin;
722 0 : if (!state.dataGlobal->ZoneSizingCalc) {
723 0 : zoneHeatBal.SysDepZoneLoads += CpAir * MassFlowRA * (TempRetAir - RetTempMin);
724 : }
725 : } else {
726 37655 : returnNode.Temp = TempRetAir;
727 : }
728 : } else { // No return air flow
729 : // Assign all heat-to-return from window gap airflow to zone air
730 360 : if (WinGapFlowToRA > 0.0) {
731 0 : zoneHeatBal.SysDepZoneLoads += WinGapFlowToRA * CpAir * (WinGapTtoRA - TempZoneAir);
732 : }
733 : // Assign all heat-to-return from lights to zone air
734 360 : if (QRetAir > 0.0) {
735 360 : zoneHeatBal.SysDepZoneLoads += QRetAir;
736 : }
737 360 : returnNode.Temp = zoneNode.Temp;
738 : }
739 : } else {
740 0 : returnNode.Temp = zoneNode.Temp;
741 : }
742 :
743 : // Update the rest of the Return Air Node conditions, if the return air system exists!
744 38016 : returnNode.Press = zoneNode.Press;
745 :
746 38016 : Real64 H2OHtOfVap = PsyHgAirFnWTdb(zoneNode.HumRat, returnNode.Temp);
747 :
748 : // Include impact of under case returns for refrigerated display cases when updating return node
749 : // humidity ratio
750 38016 : if (!zone.NoHeatToReturnAir) {
751 38016 : if (MassFlowRA > 0) {
752 37656 : Real64 SumRetAirLatentGainRate = SumAllReturnAirLatentGains(state, ZoneNum, returnNodeNum);
753 37656 : returnNode.HumRat = zoneNode.HumRat + (SumRetAirLatentGainRate / (H2OHtOfVap * MassFlowRA));
754 : } else {
755 : // If no mass flow rate exists, include the latent HVAC case credit with the latent Zone case credit
756 360 : returnNode.HumRat = zoneNode.HumRat;
757 360 : state.dataHeatBal->RefrigCaseCredit(ZoneNum).LatCaseCreditToZone += state.dataHeatBal->RefrigCaseCredit(ZoneNum).LatCaseCreditToHVAC;
758 : // shouldn't the HVAC term be zeroed out then?
759 360 : Real64 SumRetAirLatentGainRate = SumAllReturnAirLatentGains(state, ZoneNum, 0);
760 360 : zoneHeatBal.latentGain += SumRetAirLatentGainRate;
761 : }
762 : } else {
763 0 : returnNode.HumRat = zoneNode.HumRat;
764 0 : state.dataHeatBal->RefrigCaseCredit(ZoneNum).LatCaseCreditToZone += state.dataHeatBal->RefrigCaseCredit(ZoneNum).LatCaseCreditToHVAC;
765 : // shouldn't the HVAC term be zeroed out then?
766 :
767 0 : zoneHeatBal.latentGain += SumAllReturnAirLatentGains(state, ZoneNum, returnNodeNum);
768 : }
769 :
770 38016 : returnNode.Enthalpy = PsyHFnTdbW(returnNode.Temp, returnNode.HumRat);
771 :
772 : // END BLOCK of code from CalcZoneLeavingConditions*********************************
773 : }
774 :
775 : // set exhaust node leaving temp if present
776 38016 : if (allocated(patternZoneInfo.ExhaustAirNodeID)) {
777 46464 : for (int exhaustAirNodeID : patternZoneInfo.ExhaustAirNodeID) {
778 8448 : state.dataLoopNodes->Node(exhaustAirNodeID).Temp = patternZoneInfo.Texhaust;
779 : }
780 : }
781 :
782 : // set thermostat reading for air system .
783 38016 : state.dataHeatBalFanSys->TempTstatAir(ZoneNum) = patternZoneInfo.Tstat;
784 :
785 : // set results for all surface
786 76032 : for (int spaceNum : zone.spaceIndexes) {
787 38016 : auto const &thisSpace = state.dataHeatBal->space(spaceNum);
788 1774080 : for (int i = thisSpace.HTSurfaceFirst, j = 0; i <= thisSpace.HTSurfaceLast; ++i) {
789 1736064 : state.dataHeatBal->SurfTempEffBulkAir(i) = patternZoneInfo.Surf(++j).TadjacentAir;
790 : }
791 38016 : }
792 :
793 : // set flag for reference air temperature mode
794 76032 : for (int spaceNum : zone.spaceIndexes) {
795 38016 : auto const &thisSpace = state.dataHeatBal->space(spaceNum);
796 1774080 : for (int i = thisSpace.HTSurfaceFirst; i <= thisSpace.HTSurfaceLast; ++i) {
797 1736064 : state.dataSurface->SurfTAirRef(i) = DataSurfaces::RefAirTemp::AdjacentAirTemp;
798 1736064 : state.dataSurface->SurfTAirRefRpt(i) = DataSurfaces::SurfTAirRefReportVals[state.dataSurface->SurfTAirRef(i)];
799 : }
800 38016 : }
801 38016 : }
802 :
803 : //*****************************************************************************************
804 :
805 : } // namespace EnergyPlus::RoomAir
|