Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <algorithm>
50 : #include <boost/math/tools/roots.hpp>
51 : #include <cassert>
52 : #include <cmath>
53 :
54 : // ObjexxFCL Headers
55 : #include <ObjexxFCL/string.functions.hh>
56 :
57 : // EnergyPlus Headers
58 : #include <EnergyPlus/Construction.hh>
59 : #include <EnergyPlus/Data/EnergyPlusData.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataHeatBalFanSys.hh>
62 : #include <EnergyPlus/DataHeatBalSurface.hh>
63 : #include <EnergyPlus/DataHeatBalance.hh>
64 : #include <EnergyPlus/DataIPShortCuts.hh>
65 : #include <EnergyPlus/DataPrecisionGlobals.hh>
66 : #include <EnergyPlus/DataRoomAirModel.hh>
67 : #include <EnergyPlus/DataViewFactorInformation.hh>
68 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
69 : #include <EnergyPlus/FileSystem.hh>
70 : #include <EnergyPlus/General.hh>
71 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
72 : #include <EnergyPlus/OutputProcessor.hh>
73 : #include <EnergyPlus/OutputReportPredefined.hh>
74 : #include <EnergyPlus/OutputReportTabular.hh>
75 : #include <EnergyPlus/Psychrometrics.hh>
76 : #include <EnergyPlus/ScheduleManager.hh>
77 : #include <EnergyPlus/ThermalComfort.hh>
78 : #include <EnergyPlus/UtilityRoutines.hh>
79 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
80 :
81 : namespace EnergyPlus {
82 :
83 : namespace ThermalComfort {
84 :
85 : // Module containing the routines dealing with the CalcThermalComfortFanger,
86 : // CalcThermalComfortPierce, and CalcThermalComfortKSU
87 :
88 : // MODULE INFORMATION:
89 : // AUTHOR Jaewook Lee
90 : // DATE WRITTEN January 2000
91 : // MODIFIED Rick Strand (for E+ implementation February 2000)
92 :
93 : // PURPOSE OF THIS MODULE:
94 : // To calculate thermal comfort indices based on the
95 : // three thermal comfort prediction models (Fanger, Pierce, KSU)
96 :
97 : // METHODOLOGY EMPLOYED:
98 : // For each thermal comfort model type, the subroutines will loop through
99 : // the people statements and perform the requested thermal comfort evaluations
100 :
101 : // Using/Aliasing
102 : using DataHeatBalance::PeopleData;
103 : using Psychrometrics::PsyRhFnTdbWPb;
104 :
105 2828213 : void ManageThermalComfort(EnergyPlusData &state, bool const InitializeOnly) // when called from ZTPC and calculations aren't needed
106 : {
107 :
108 : // SUBROUTINE INFORMATION:
109 : // AUTHOR Rick Strand
110 : // DATE WRITTEN February 2000
111 :
112 2828213 : if (state.dataThermalComforts->FirstTimeFlag) {
113 801 : InitThermalComfort(state); // Mainly sets up output stuff
114 801 : state.dataThermalComforts->FirstTimeFlag = false;
115 : }
116 :
117 2828213 : if (state.dataGlobal->DayOfSim == 1) {
118 711245 : if (state.dataGlobal->HourOfDay < 7) {
119 182624 : state.dataThermalComforts->TemporarySixAMTemperature = 1.868132;
120 528621 : } else if (state.dataGlobal->HourOfDay == 7) {
121 29154 : if (state.dataGlobal->TimeStep == 1) {
122 5292 : state.dataThermalComforts->TemporarySixAMTemperature = state.dataEnvrn->OutDryBulbTemp;
123 : }
124 : }
125 : } else {
126 2116968 : if (state.dataGlobal->HourOfDay == 7) {
127 88207 : if (state.dataGlobal->TimeStep == 1) {
128 16796 : state.dataThermalComforts->TemporarySixAMTemperature = state.dataEnvrn->OutDryBulbTemp;
129 : }
130 : }
131 : }
132 :
133 2828213 : if (InitializeOnly) {
134 1 : return;
135 : }
136 :
137 2828212 : if (state.dataGlobal->BeginEnvrnFlag) {
138 6496 : state.dataThermalComforts->ZoneOccHrs = 0.0;
139 : }
140 :
141 2828212 : if (!state.dataGlobal->DoingSizing && !state.dataGlobal->WarmupFlag) {
142 491880 : CalcThermalComfortFanger(state);
143 491880 : if (state.dataHeatBal->AnyThermalComfortPierceModel) {
144 1968 : CalcThermalComfortPierceASHRAE(state);
145 : }
146 491880 : if (state.dataHeatBal->AnyThermalComfortKSUModel) {
147 624 : CalcThermalComfortKSU(state);
148 : }
149 491880 : if (state.dataHeatBal->AnyThermalComfortCoolingEffectModel) {
150 192 : CalcThermalComfortCoolingEffectASH(state);
151 : }
152 491880 : if (state.dataHeatBal->AnyThermalComfortAnkleDraftModel) {
153 192 : CalcThermalComfortAnkleDraftASH(state);
154 : }
155 491880 : CalcThermalComfortSimpleASH55(state);
156 491880 : CalcIfSetPointMet(state);
157 491880 : if (state.dataHeatBal->AdaptiveComfortRequested_ASH55) {
158 960 : CalcThermalComfortAdaptiveASH55(state, false);
159 : }
160 491880 : if (state.dataHeatBal->AdaptiveComfortRequested_CEN15251) {
161 288 : CalcThermalComfortAdaptiveCEN15251(state, false);
162 : }
163 : }
164 : }
165 :
166 801 : void InitThermalComfort(EnergyPlusData &state)
167 : {
168 :
169 : // SUBROUTINE INFORMATION:
170 : // AUTHOR Rick Strand
171 : // DATE WRITTEN February 2000
172 :
173 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
174 : int Loop; // DO loop counter
175 801 : std::string CurrentGroupName;
176 :
177 801 : state.dataThermalComforts->ThermalComfortData.allocate(state.dataHeatBal->TotPeople);
178 :
179 5039 : for (Loop = 1; Loop <= state.dataHeatBal->TotPeople; ++Loop) {
180 :
181 4238 : CurrentGroupName = state.dataHeatBal->People(Loop).Name;
182 :
183 : // CurrentModuleObject='People'
184 : // MJW MRT ToDo: Rename most Zone Thermal Comfort output variables to People Thermal Comfort ('cause they're keyed by People name)
185 4238 : if (state.dataHeatBal->People(Loop).Fanger) {
186 5328 : SetupOutputVariable(state,
187 : "Zone Thermal Comfort Fanger Model PMV",
188 : Constant::Units::None,
189 2664 : state.dataThermalComforts->ThermalComfortData(Loop).FangerPMV,
190 : OutputProcessor::TimeStepType::Zone,
191 : OutputProcessor::StoreType::Average,
192 2664 : state.dataHeatBal->People(Loop).Name);
193 5328 : SetupOutputVariable(state,
194 : "Zone Thermal Comfort Fanger Model PPD",
195 : Constant::Units::Perc,
196 2664 : state.dataThermalComforts->ThermalComfortData(Loop).FangerPPD,
197 : OutputProcessor::TimeStepType::Zone,
198 : OutputProcessor::StoreType::Average,
199 2664 : state.dataHeatBal->People(Loop).Name);
200 5328 : SetupOutputVariable(state,
201 : "Zone Thermal Comfort Clothing Surface Temperature",
202 : Constant::Units::C,
203 2664 : state.dataThermalComforts->ThermalComfortData(Loop).CloSurfTemp,
204 : OutputProcessor::TimeStepType::Zone,
205 : OutputProcessor::StoreType::Average,
206 2664 : state.dataHeatBal->People(Loop).Name);
207 : }
208 :
209 4238 : if (state.dataHeatBal->People(Loop).Pierce) {
210 22 : SetupOutputVariable(state,
211 : "Zone Thermal Comfort Pierce Model Effective Temperature PMV",
212 : Constant::Units::None,
213 11 : state.dataThermalComforts->ThermalComfortData(Loop).PiercePMVET,
214 : OutputProcessor::TimeStepType::Zone,
215 : OutputProcessor::StoreType::Average,
216 11 : state.dataHeatBal->People(Loop).Name);
217 22 : SetupOutputVariable(state,
218 : "Zone Thermal Comfort Pierce Model Standard Effective Temperature PMV",
219 : Constant::Units::None,
220 11 : state.dataThermalComforts->ThermalComfortData(Loop).PiercePMVSET,
221 : OutputProcessor::TimeStepType::Zone,
222 : OutputProcessor::StoreType::Average,
223 11 : state.dataHeatBal->People(Loop).Name);
224 22 : SetupOutputVariable(state,
225 : "Zone Thermal Comfort Pierce Model Discomfort Index",
226 : Constant::Units::None,
227 11 : state.dataThermalComforts->ThermalComfortData(Loop).PierceDISC,
228 : OutputProcessor::TimeStepType::Zone,
229 : OutputProcessor::StoreType::Average,
230 11 : state.dataHeatBal->People(Loop).Name);
231 22 : SetupOutputVariable(state,
232 : "Zone Thermal Comfort Pierce Model Thermal Sensation Index",
233 : Constant::Units::None,
234 11 : state.dataThermalComforts->ThermalComfortData(Loop).PierceTSENS,
235 : OutputProcessor::TimeStepType::Zone,
236 : OutputProcessor::StoreType::Average,
237 11 : state.dataHeatBal->People(Loop).Name);
238 22 : SetupOutputVariable(state,
239 : "Zone Thermal Comfort Pierce Model Standard Effective Temperature",
240 : Constant::Units::C,
241 11 : state.dataThermalComforts->ThermalComfortData(Loop).PierceSET,
242 : OutputProcessor::TimeStepType::Zone,
243 : OutputProcessor::StoreType::Average,
244 11 : state.dataHeatBal->People(Loop).Name);
245 : }
246 :
247 4238 : if (state.dataHeatBal->People(Loop).KSU) {
248 12 : SetupOutputVariable(state,
249 : "Zone Thermal Comfort KSU Model Thermal Sensation Vote",
250 : Constant::Units::None,
251 6 : state.dataThermalComforts->ThermalComfortData(Loop).KsuTSV,
252 : OutputProcessor::TimeStepType::Zone,
253 : OutputProcessor::StoreType::Average,
254 6 : state.dataHeatBal->People(Loop).Name);
255 : }
256 :
257 4238 : if ((state.dataHeatBal->People(Loop).Fanger) || (state.dataHeatBal->People(Loop).Pierce) || (state.dataHeatBal->People(Loop).KSU)) {
258 5342 : SetupOutputVariable(state,
259 : "Zone Thermal Comfort Mean Radiant Temperature",
260 : Constant::Units::C,
261 2671 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortMRT,
262 : OutputProcessor::TimeStepType::Zone,
263 : OutputProcessor::StoreType::Average,
264 2671 : state.dataHeatBal->People(Loop).Name);
265 5342 : SetupOutputVariable(state,
266 : "Zone Thermal Comfort Operative Temperature",
267 : Constant::Units::C,
268 2671 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortOpTemp,
269 : OutputProcessor::TimeStepType::Zone,
270 : OutputProcessor::StoreType::Average,
271 2671 : state.dataHeatBal->People(Loop).Name);
272 5342 : SetupOutputVariable(state,
273 : "Zone Thermal Comfort Clothing Value",
274 : Constant::Units::clo,
275 2671 : state.dataThermalComforts->ThermalComfortData(Loop).ClothingValue,
276 : OutputProcessor::TimeStepType::Zone,
277 : OutputProcessor::StoreType::Average,
278 2671 : state.dataHeatBal->People(Loop).Name);
279 : }
280 :
281 4238 : if (state.dataHeatBal->People(Loop).AdaptiveASH55) {
282 6 : SetupOutputVariable(state,
283 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model 90% Acceptability Status",
284 : Constant::Units::None,
285 6 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveASH5590,
286 : OutputProcessor::TimeStepType::Zone,
287 : OutputProcessor::StoreType::Average,
288 6 : state.dataHeatBal->People(Loop).Name);
289 6 : SetupOutputVariable(state,
290 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model 80% Acceptability Status",
291 : Constant::Units::None,
292 6 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveASH5580,
293 : OutputProcessor::TimeStepType::Zone,
294 : OutputProcessor::StoreType::Average,
295 6 : state.dataHeatBal->People(Loop).Name);
296 12 : SetupOutputVariable(state,
297 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model Running Average Outdoor Air Temperature",
298 : Constant::Units::C,
299 6 : state.dataThermalComforts->ThermalComfortData(Loop).ASHRAE55RunningMeanOutdoorTemp,
300 : OutputProcessor::TimeStepType::Zone,
301 : OutputProcessor::StoreType::Average,
302 6 : state.dataHeatBal->People(Loop).Name);
303 12 : SetupOutputVariable(state,
304 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model Temperature",
305 : Constant::Units::C,
306 6 : state.dataThermalComforts->ThermalComfortData(Loop).TComfASH55,
307 : OutputProcessor::TimeStepType::Zone,
308 : OutputProcessor::StoreType::Average,
309 6 : state.dataHeatBal->People(Loop).Name);
310 : }
311 :
312 4238 : if (state.dataHeatBal->People(Loop).AdaptiveCEN15251) {
313 1 : SetupOutputVariable(state,
314 : "Zone Thermal Comfort CEN 15251 Adaptive Model Category I Status",
315 : Constant::Units::None,
316 1 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveCEN15251CatI,
317 : OutputProcessor::TimeStepType::Zone,
318 : OutputProcessor::StoreType::Average,
319 1 : state.dataHeatBal->People(Loop).Name);
320 1 : SetupOutputVariable(state,
321 : "Zone Thermal Comfort CEN 15251 Adaptive Model Category II Status",
322 : Constant::Units::None,
323 1 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveCEN15251CatII,
324 : OutputProcessor::TimeStepType::Zone,
325 : OutputProcessor::StoreType::Average,
326 1 : state.dataHeatBal->People(Loop).Name);
327 1 : SetupOutputVariable(state,
328 : "Zone Thermal Comfort CEN 15251 Adaptive Model Category III Status",
329 : Constant::Units::None,
330 1 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveCEN15251CatIII,
331 : OutputProcessor::TimeStepType::Zone,
332 : OutputProcessor::StoreType::Average,
333 1 : state.dataHeatBal->People(Loop).Name);
334 2 : SetupOutputVariable(state,
335 : "Zone Thermal Comfort CEN 15251 Adaptive Model Running Average Outdoor Air Temperature",
336 : Constant::Units::C,
337 1 : state.dataThermalComforts->ThermalComfortData(Loop).CEN15251RunningMeanOutdoorTemp,
338 : OutputProcessor::TimeStepType::Zone,
339 : OutputProcessor::StoreType::Average,
340 1 : state.dataHeatBal->People(Loop).Name);
341 2 : SetupOutputVariable(state,
342 : "Zone Thermal Comfort CEN 15251 Adaptive Model Temperature",
343 : Constant::Units::C,
344 1 : state.dataThermalComforts->ThermalComfortData(Loop).TComfCEN15251,
345 : OutputProcessor::TimeStepType::Zone,
346 : OutputProcessor::StoreType::Average,
347 1 : state.dataHeatBal->People(Loop).Name);
348 : }
349 4238 : if (state.dataHeatBal->People(Loop).CoolingEffectASH55) {
350 2 : SetupOutputVariable(state,
351 : "Zone Thermal Comfort ASHRAE 55 Elevated Air Speed Cooling Effect",
352 : Constant::Units::C,
353 1 : state.dataThermalComforts->ThermalComfortData(Loop).CoolingEffectASH55,
354 : OutputProcessor::TimeStepType::Zone,
355 : OutputProcessor::StoreType::Average,
356 1 : state.dataHeatBal->People(Loop).Name);
357 2 : SetupOutputVariable(state,
358 : "Zone Thermal Comfort ASHRAE 55 Elevated Air Speed Cooling Effect Adjusted PMV",
359 : Constant::Units::None,
360 1 : state.dataThermalComforts->ThermalComfortData(Loop).CoolingEffectAdjustedPMVASH55,
361 : OutputProcessor::TimeStepType::Zone,
362 : OutputProcessor::StoreType::Average,
363 1 : state.dataHeatBal->People(Loop).Name);
364 2 : SetupOutputVariable(state,
365 : "Zone Thermal Comfort ASHRAE 55 Elevated Air Speed Cooling Effect Adjusted PPD",
366 : Constant::Units::None,
367 1 : state.dataThermalComforts->ThermalComfortData(Loop).CoolingEffectAdjustedPPDASH55,
368 : OutputProcessor::TimeStepType::Zone,
369 : OutputProcessor::StoreType::Average,
370 1 : state.dataHeatBal->People(Loop).Name);
371 : }
372 4238 : if (state.dataHeatBal->People(Loop).AnkleDraftASH55) {
373 2 : SetupOutputVariable(state,
374 : "Zone Thermal Comfort ASHRAE 55 Ankle Draft PPD",
375 : Constant::Units::None,
376 1 : state.dataThermalComforts->ThermalComfortData(Loop).AnkleDraftPPDASH55,
377 : OutputProcessor::TimeStepType::Zone,
378 : OutputProcessor::StoreType::Average,
379 1 : state.dataHeatBal->People(Loop).Name);
380 : }
381 : }
382 801 : state.dataThermalComforts->ThermalComfortInASH55.allocate(state.dataGlobal->NumOfZones);
383 :
384 : // ASHRAE 55 Warning. If any people statement for a zone is true, set that zone to true
385 5039 : for (Loop = 1; Loop <= state.dataHeatBal->TotPeople; ++Loop) {
386 4238 : if (state.dataHeatBal->People(Loop).Show55Warning) {
387 0 : state.dataThermalComforts->ThermalComfortInASH55(state.dataHeatBal->People(Loop).ZonePtr).Enable55Warning = true;
388 : }
389 : }
390 :
391 : // CurrentModuleObject='Zone'
392 6002 : for (Loop = 1; Loop <= state.dataGlobal->NumOfZones; ++Loop) {
393 10402 : SetupOutputVariable(state,
394 : "Zone Thermal Comfort ASHRAE 55 Simple Model Summer Clothes Not Comfortable Time",
395 : Constant::Units::hr,
396 5201 : state.dataThermalComforts->ThermalComfortInASH55(Loop).timeNotSummer,
397 : OutputProcessor::TimeStepType::Zone,
398 : OutputProcessor::StoreType::Sum,
399 5201 : state.dataHeatBal->Zone(Loop).Name);
400 10402 : SetupOutputVariable(state,
401 : "Zone Thermal Comfort ASHRAE 55 Simple Model Winter Clothes Not Comfortable Time",
402 : Constant::Units::hr,
403 5201 : state.dataThermalComforts->ThermalComfortInASH55(Loop).timeNotWinter,
404 : OutputProcessor::TimeStepType::Zone,
405 : OutputProcessor::StoreType::Sum,
406 5201 : state.dataHeatBal->Zone(Loop).Name);
407 10402 : SetupOutputVariable(state,
408 : "Zone Thermal Comfort ASHRAE 55 Simple Model Summer or Winter Clothes Not Comfortable Time",
409 : Constant::Units::hr,
410 5201 : state.dataThermalComforts->ThermalComfortInASH55(Loop).timeNotEither,
411 : OutputProcessor::TimeStepType::Zone,
412 : OutputProcessor::StoreType::Sum,
413 5201 : state.dataHeatBal->Zone(Loop).Name);
414 : }
415 3204 : SetupOutputVariable(state,
416 : "Facility Thermal Comfort ASHRAE 55 Simple Model Summer Clothes Not Comfortable Time",
417 : Constant::Units::hr,
418 801 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer,
419 : OutputProcessor::TimeStepType::Zone,
420 : OutputProcessor::StoreType::Sum,
421 : "Facility");
422 3204 : SetupOutputVariable(state,
423 : "Facility Thermal Comfort ASHRAE 55 Simple Model Winter Clothes Not Comfortable Time",
424 : Constant::Units::hr,
425 801 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter,
426 : OutputProcessor::TimeStepType::Zone,
427 : OutputProcessor::StoreType::Sum,
428 : "Facility");
429 3204 : SetupOutputVariable(state,
430 : "Facility Thermal Comfort ASHRAE 55 Simple Model Summer or Winter Clothes Not Comfortable Time",
431 : Constant::Units::hr,
432 801 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either,
433 : OutputProcessor::TimeStepType::Zone,
434 : OutputProcessor::StoreType::Sum,
435 : "Facility");
436 :
437 801 : state.dataThermalComforts->ThermalComfortSetPoint.allocate(state.dataGlobal->NumOfZones);
438 6002 : for (Loop = 1; Loop <= state.dataGlobal->NumOfZones; ++Loop) {
439 10402 : SetupOutputVariable(state,
440 : "Zone Heating Setpoint Not Met Time",
441 : Constant::Units::hr,
442 5201 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetHeating,
443 : OutputProcessor::TimeStepType::Zone,
444 : OutputProcessor::StoreType::Sum,
445 5201 : state.dataHeatBal->Zone(Loop).Name);
446 10402 : SetupOutputVariable(state,
447 : "Zone Heating Setpoint Not Met While Occupied Time",
448 : Constant::Units::hr,
449 5201 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetHeatingOccupied,
450 : OutputProcessor::TimeStepType::Zone,
451 : OutputProcessor::StoreType::Sum,
452 5201 : state.dataHeatBal->Zone(Loop).Name);
453 10402 : SetupOutputVariable(state,
454 : "Zone Cooling Setpoint Not Met Time",
455 : Constant::Units::hr,
456 5201 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetCooling,
457 : OutputProcessor::TimeStepType::Zone,
458 : OutputProcessor::StoreType::Sum,
459 5201 : state.dataHeatBal->Zone(Loop).Name);
460 10402 : SetupOutputVariable(state,
461 : "Zone Cooling Setpoint Not Met While Occupied Time",
462 : Constant::Units::hr,
463 5201 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetCoolingOccupied,
464 : OutputProcessor::TimeStepType::Zone,
465 : OutputProcessor::StoreType::Sum,
466 5201 : state.dataHeatBal->Zone(Loop).Name);
467 : }
468 :
469 3204 : SetupOutputVariable(state,
470 : "Facility Heating Setpoint Not Met Time",
471 : Constant::Units::hr,
472 801 : state.dataThermalComforts->AnyZoneNotMetHeating,
473 : OutputProcessor::TimeStepType::Zone,
474 : OutputProcessor::StoreType::Sum,
475 : "Facility");
476 3204 : SetupOutputVariable(state,
477 : "Facility Cooling Setpoint Not Met Time",
478 : Constant::Units::hr,
479 801 : state.dataThermalComforts->AnyZoneNotMetCooling,
480 : OutputProcessor::TimeStepType::Zone,
481 : OutputProcessor::StoreType::Sum,
482 : "Facility");
483 3204 : SetupOutputVariable(state,
484 : "Facility Heating Setpoint Not Met While Occupied Time",
485 : Constant::Units::hr,
486 801 : state.dataThermalComforts->AnyZoneNotMetHeatingOccupied,
487 : OutputProcessor::TimeStepType::Zone,
488 : OutputProcessor::StoreType::Sum,
489 : "Facility");
490 3204 : SetupOutputVariable(state,
491 : "Facility Cooling Setpoint Not Met While Occupied Time",
492 : Constant::Units::hr,
493 801 : state.dataThermalComforts->AnyZoneNotMetCoolingOccupied,
494 : OutputProcessor::TimeStepType::Zone,
495 : OutputProcessor::StoreType::Sum,
496 : "Facility");
497 :
498 801 : GetAngleFactorList(state);
499 :
500 801 : state.dataThermalComforts->ZoneOccHrs.dimension(state.dataGlobal->NumOfZones, 0.0);
501 801 : }
502 :
503 501631 : void CalcThermalComfortFanger(EnergyPlusData &state,
504 : ObjexxFCL::Optional_int_const PNum, // People number for thermal comfort control
505 : ObjexxFCL::Optional<Real64 const> Tset, // Temperature setpoint for thermal comfort control
506 : ObjexxFCL::Optional<Real64> PMVResult // PMV value for thermal comfort control
507 : )
508 : {
509 :
510 : // SUBROUTINE INFORMATION:
511 : // AUTHOR Jaewook Lee
512 : // DATE WRITTEN January 2000
513 : // MODIFIED Rick Strand (for E+ implementation February 2000)
514 : // Brent Griffith modifications for CR 5641 (October 2005)
515 : // L. Gu, Added optional arguments for thermal comfort control (May 2006)
516 : // T. Hong, added Fanger PPD (April 2009)
517 :
518 : // PURPOSE OF THIS SUBROUTINE:
519 : // This subroutine calculates PMV(Predicted Mean Vote) using the Fanger thermal
520 : // comfort model. This subroutine is also used for thermal comfort control by determining
521 : // the temperature at which the PMV is equal to a PMV setpoint specified by the user.
522 :
523 : // METHODOLOGY EMPLOYED:
524 : // This subroutine is based heavily upon the work performed by Dan Maloney for
525 : // the BLAST program. Many of the equations are based on the original Fanger
526 : // development. See documentation for further details and references.
527 :
528 : // REFERENCES:
529 : // Maloney, Dan, M.S. Thesis, University of Illinois at Urbana-Champaign
530 : // BG note (10/21/2005), This formulation is based on the the BASIC program
531 : // that is included in ASHRAE Standard 55 Normative Appendix D.
532 :
533 2645692 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
534 2144061 : ++state.dataThermalComforts->PeopleNum) { // Is there a reason why this is a state variable and not a local variable?
535 :
536 2144061 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
537 2144061 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
538 :
539 : // Optional argument is used to access people object when thermal comfort control is used
540 2144061 : if (present(PNum)) {
541 29253 : if (state.dataThermalComforts->PeopleNum != PNum) {
542 941366 : continue;
543 : }
544 : }
545 :
546 : // If optional argument is used do not cycle regardless of thermal comfort reporting type
547 2124559 : if ((!people.Fanger) && (!present(PNum))) {
548 921864 : continue;
549 : }
550 :
551 1202695 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
552 1202695 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum);
553 1202695 : if (present(PNum)) {
554 9751 : state.dataThermalComforts->AirTemp = Tset;
555 : } else {
556 1192944 : state.dataThermalComforts->AirTemp = thisZoneHB.ZTAVComf;
557 : }
558 1202695 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
559 3168 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
560 6336 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
561 3168 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
562 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum); // PH 3/7/04
563 : // UCSD-CV
564 3168 : } else if (state.dataRoomAir->IsZoneCrossVent(state.dataThermalComforts->ZoneNum)) {
565 0 : if (state.dataRoomAir->ZoneCrossVent(state.dataThermalComforts->ZoneNum).VforComfort == RoomAir::Comfort::Jet) {
566 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->ZTJET(state.dataThermalComforts->ZoneNum);
567 0 : } else if (state.dataRoomAir->ZoneCrossVent(state.dataThermalComforts->ZoneNum).VforComfort == RoomAir::Comfort::Recirculation) {
568 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->ZTJET(state.dataThermalComforts->ZoneNum);
569 : }
570 : }
571 : }
572 1202695 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
573 : // Use mean air temp for calculating RH when thermal comfort control is used
574 1202695 : if (present(PNum)) {
575 19502 : state.dataThermalComforts->RelHum =
576 19502 : PsyRhFnTdbWPb(state,
577 9751 : state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum).MAT,
578 : thisZoneHB.airHumRatAvgComf,
579 9751 : state.dataEnvrn->OutBaroPress);
580 : } else {
581 2385888 : state.dataThermalComforts->RelHum =
582 1192944 : PsyRhFnTdbWPb(state, state.dataThermalComforts->AirTemp, thisZoneHB.airHumRatAvgComf, state.dataEnvrn->OutBaroPress);
583 : }
584 1202695 : people.TemperatureInZone = state.dataThermalComforts->AirTemp;
585 1202695 : people.RelativeHumidityInZone = state.dataThermalComforts->RelHum * 100.0;
586 :
587 : // Metabolic rate of body (W/m2)
588 1202695 : state.dataThermalComforts->ActLevel = people.activityLevelSched->getCurrentVal() / BodySurfArea;
589 : // Energy consumption by external work (W/m2)
590 1202695 : state.dataThermalComforts->WorkEff = people.workEffSched->getCurrentVal() * state.dataThermalComforts->ActLevel;
591 : // Clothing unit
592 : Real64 IntermediateClothing;
593 1202695 : switch (people.clothingType) {
594 1202551 : case DataHeatBalance::ClothingType::InsulationSchedule:
595 1202551 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
596 1202551 : break;
597 96 : case DataHeatBalance::ClothingType::DynamicAshrae55:
598 96 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
599 96 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
600 96 : DynamicClothingModel(state);
601 96 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
602 96 : break;
603 48 : case DataHeatBalance::ClothingType::CalculationSchedule:
604 48 : IntermediateClothing = people.clothingMethodSched->getCurrentVal();
605 48 : if (IntermediateClothing == 1.0) {
606 12 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
607 12 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
608 36 : } else if (IntermediateClothing == 2.0) {
609 36 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
610 36 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
611 36 : DynamicClothingModel(state);
612 36 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
613 : } else {
614 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
615 0 : ShowWarningError(
616 0 : state, format("PEOPLE=\"{}\", Scheduled clothing value will be used rather than clothing calculation method.", people.Name));
617 : }
618 48 : break;
619 0 : default:
620 0 : ShowSevereError(state, format("PEOPLE=\"{}\", Incorrect Clothing Type", people.Name));
621 : }
622 :
623 1202695 : if (state.dataRoomAir->anyNonMixingRoomAirModel && state.dataRoomAir->IsZoneCrossVent(state.dataThermalComforts->ZoneNum)) {
624 0 : if (state.dataRoomAir->ZoneCrossVent(state.dataThermalComforts->ZoneNum).VforComfort == RoomAir::Comfort::Jet) {
625 0 : state.dataThermalComforts->AirVel = state.dataRoomAir->Ujet(state.dataThermalComforts->ZoneNum);
626 0 : } else if (state.dataRoomAir->ZoneCrossVent(state.dataThermalComforts->ZoneNum).VforComfort == RoomAir::Comfort::Recirculation) {
627 0 : state.dataThermalComforts->AirVel = state.dataRoomAir->Urec(state.dataThermalComforts->ZoneNum);
628 : } else {
629 0 : state.dataThermalComforts->AirVel = 0.2;
630 : }
631 : } else {
632 1202695 : state.dataThermalComforts->AirVel = people.airVelocitySched->getCurrentVal();
633 : // Ensure air velocity within the reasonable range. Otherwise reccusive warnings is provided
634 1202695 : if (present(PNum) && (state.dataThermalComforts->AirVel < 0.1 || state.dataThermalComforts->AirVel > 0.5)) {
635 0 : if (people.AirVelErrIndex == 0) {
636 0 : ShowWarningMessage(
637 : state,
638 0 : format("PEOPLE=\"{}\", Air velocity is beyond the reasonable range (0.1,0.5) for thermal comfort control.", people.Name));
639 0 : ShowContinueErrorTimeStamp(state, "");
640 : }
641 0 : ShowRecurringWarningErrorAtEnd(state,
642 0 : "PEOPLE=\"" + people.Name + "\",Air velocity is still beyond the reasonable range (0.1,0.5)",
643 0 : people.AirVelErrIndex,
644 0 : state.dataThermalComforts->AirVel,
645 0 : state.dataThermalComforts->AirVel,
646 : _,
647 : "[m/s]",
648 : "[m/s]");
649 : }
650 : }
651 :
652 1202695 : Real64 PMV = CalcFangerPMV(state,
653 1202695 : state.dataThermalComforts->AirTemp,
654 1202695 : state.dataThermalComforts->RadTemp,
655 1202695 : state.dataThermalComforts->RelHum,
656 1202695 : state.dataThermalComforts->AirVel,
657 1202695 : state.dataThermalComforts->ActLevel,
658 1202695 : state.dataThermalComforts->CloUnit,
659 1202695 : state.dataThermalComforts->WorkEff);
660 :
661 1202695 : comfort.FangerPMV = PMV;
662 : // Pass resulting PMV based on temperature setpoint (Tset) when using thermal comfort control
663 1202695 : if (present(PNum)) {
664 9751 : PMVResult = PMV;
665 : }
666 1202695 : comfort.ThermalComfortMRT = state.dataThermalComforts->RadTemp;
667 1202695 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
668 1202695 : comfort.CloSurfTemp = state.dataThermalComforts->CloSurfTemp;
669 :
670 : // Calculate the Fanger PPD (Predicted Percentage of Dissatisfied), as a %
671 1202695 : Real64 PPD = CalcFangerPPD(PMV);
672 1202695 : comfort.FangerPPD = PPD;
673 : }
674 501631 : }
675 :
676 1203175 : Real64 CalcFangerPMV(
677 : EnergyPlusData &state, Real64 AirTemp, Real64 RadTemp, Real64 RelHum, Real64 AirVel, Real64 ActLevel, Real64 CloUnit, Real64 WorkEff)
678 : {
679 :
680 : // Using/Aliasing
681 : using Psychrometrics::PsyPsatFnTemp;
682 :
683 : // SUBROUTINE PARAMETER DEFINITIONS:
684 1203175 : int constexpr MaxIter(150); // Limit of iteration
685 1203175 : Real64 constexpr StopIterCrit(0.00015); // Stop criteria for iteration
686 :
687 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
688 : Real64 P1; // Intermediate variables to calculate clothed body ratio and clothing temperature
689 : Real64 P2; // Intermediate variables to calculate clothed body ratio and clothing temperature
690 : Real64 P3; // Intermediate variables to calculate clothed body ratio and clothing temperature
691 : Real64 P4; // Intermediate variables to calculate clothed body ratio and clothing temperature
692 : Real64 XF; // Intermediate variables to calculate clothed body ratio and clothing temperature
693 : Real64 XN; // Intermediate variables to calculate clothed body ratio and clothing temperature
694 : Real64 PMV; // temporary variable to store calculated Fanger PMV value
695 : // VapPress = CalcSatVapPressFromTemp(AirTemp) !original
696 : // VapPress = RelHum*VapPress !original might be in torrs
697 :
698 1203175 : state.dataThermalComforts->VapPress = PsyPsatFnTemp(state, AirTemp); // use psych routines inside E+ , returns Pa
699 :
700 1203175 : state.dataThermalComforts->VapPress *= RelHum; // in units of [Pa]
701 :
702 1203175 : state.dataThermalComforts->IntHeatProd = ActLevel - WorkEff;
703 :
704 : // Compute the Corresponding Clothed Body Ratio
705 1203175 : state.dataThermalComforts->CloBodyRat = 1.05 + 0.1 * CloUnit; // The ratio of the surface area of the clothed body
706 : // to the surface area of nude body
707 :
708 1203175 : if (CloUnit < 0.5) {
709 48 : state.dataThermalComforts->CloBodyRat = state.dataThermalComforts->CloBodyRat - 0.05 + 0.1 * CloUnit;
710 : }
711 :
712 1203175 : state.dataThermalComforts->AbsRadTemp = RadTemp + TAbsConv;
713 1203175 : state.dataThermalComforts->AbsAirTemp = AirTemp + TAbsConv;
714 :
715 1203175 : state.dataThermalComforts->CloInsul = CloUnit * state.dataThermalComforts->CloBodyRat * 0.155; // Thermal resistance of the clothing // icl
716 :
717 1203175 : P2 = state.dataThermalComforts->CloInsul * 3.96;
718 1203175 : P3 = state.dataThermalComforts->CloInsul * 100.0;
719 1203175 : P1 = state.dataThermalComforts->CloInsul * state.dataThermalComforts->AbsAirTemp; // p4
720 1203175 : P4 = 308.7 - 0.028 * state.dataThermalComforts->IntHeatProd + P2 * pow_4(state.dataThermalComforts->AbsRadTemp / 100.0); // p5
721 :
722 : // First guess for clothed surface temperature
723 1203175 : state.dataThermalComforts->AbsCloSurfTemp = state.dataThermalComforts->AbsAirTemp + (35.5 - AirTemp) / (3.5 * (CloUnit + 0.1));
724 1203175 : XN = state.dataThermalComforts->AbsCloSurfTemp / 100.0;
725 1203175 : state.dataThermalComforts->HcFor = 12.1 * std::sqrt(AirVel); // Heat transfer coefficient by forced convection
726 1203175 : state.dataThermalComforts->IterNum = 0;
727 1203175 : XF = XN;
728 :
729 : // COMPUTE SURFACE TEMPERATURE OF CLOTHING BY ITERATIONS
730 7131991 : while (((std::abs(XN - XF) > StopIterCrit) || (state.dataThermalComforts->IterNum == 0)) && (state.dataThermalComforts->IterNum < MaxIter)) {
731 5928816 : XF = (XF + XN) / 2.0;
732 11857632 : state.dataThermalComforts->HcNat =
733 5928816 : 2.38 * root_4(std::abs(100.0 * XF - state.dataThermalComforts->AbsAirTemp)); // Heat transfer coefficient by natural convection
734 5928816 : state.dataThermalComforts->Hc =
735 5928816 : max(state.dataThermalComforts->HcFor, state.dataThermalComforts->HcNat); // Determination of convective heat transfer coefficient
736 5928816 : XN = (P4 + P1 * state.dataThermalComforts->Hc - P2 * pow_4(XF)) / (100.0 + P3 * state.dataThermalComforts->Hc);
737 5928816 : ++state.dataThermalComforts->IterNum;
738 5928816 : if (state.dataThermalComforts->IterNum > MaxIter) {
739 0 : ShowWarningError(state, "Max iteration exceeded in CalcThermalFanger");
740 : }
741 : }
742 1203175 : state.dataThermalComforts->AbsCloSurfTemp = 100.0 * XN;
743 1203175 : state.dataThermalComforts->CloSurfTemp = state.dataThermalComforts->AbsCloSurfTemp - TAbsConv;
744 :
745 : // COMPUTE PREDICTED MEAN VOTE
746 : // Sensible heat loss
747 : // RadHeatLoss = RadSurfEff*CloBodyRat*SkinEmiss*StefanBoltz* & !original
748 : // (AbsCloSurfTemp**4 - AbsRadTemp**4) ! Heat loss by radiation
749 :
750 : // following line is ln 480 in ASHRAE 55 append. D
751 2406350 : state.dataThermalComforts->RadHeatLoss =
752 1203175 : 3.96 * state.dataThermalComforts->CloBodyRat *
753 1203175 : (pow_4(state.dataThermalComforts->AbsCloSurfTemp * 0.01) - pow_4(state.dataThermalComforts->AbsRadTemp * 0.01));
754 :
755 1203175 : state.dataThermalComforts->ConvHeatLoss = state.dataThermalComforts->CloBodyRat * state.dataThermalComforts->Hc *
756 1203175 : (state.dataThermalComforts->CloSurfTemp - AirTemp); // Heat loss by convection
757 :
758 1203175 : state.dataThermalComforts->DryHeatLoss = state.dataThermalComforts->RadHeatLoss + state.dataThermalComforts->ConvHeatLoss;
759 :
760 : // Evaporative heat loss
761 : // Heat loss by regulatory sweating
762 1203175 : state.dataThermalComforts->EvapHeatLossRegComf = 0.0;
763 1203175 : if (state.dataThermalComforts->IntHeatProd > 58.2) {
764 1203175 : state.dataThermalComforts->EvapHeatLossRegComf = 0.42 * (state.dataThermalComforts->IntHeatProd - ActLevelConv);
765 : }
766 : // SkinTempComf = 35.7 - 0.028*IntHeatProd ! Skin temperature required to achieve thermal comfort
767 : // SatSkinVapPress = 1.92*SkinTempComf - 25.3 ! Water vapor pressure at required skin temperature
768 : // Heat loss by diffusion
769 : // EvapHeatLossDiff = 0.4148*(SatSkinVapPress - VapPress) !original
770 2406350 : state.dataThermalComforts->EvapHeatLossDiff =
771 1203175 : 3.05 * 0.001 *
772 1203175 : (5733.0 - 6.99 * state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->VapPress); // ln 440 in ASHRAE 55 Append. D
773 :
774 1203175 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->EvapHeatLossRegComf + state.dataThermalComforts->EvapHeatLossDiff;
775 : // Heat loss by respiration
776 : // original: LatRespHeatLoss = 0.0023*ActLevel*(44. - VapPress) ! Heat loss by latent respiration
777 2406350 : state.dataThermalComforts->LatRespHeatLoss =
778 1203175 : 1.7 * 0.00001 * ActLevel * (5867.0 - state.dataThermalComforts->VapPress); // ln 460 in ASHRAE 55 Append. D
779 :
780 : // LatRespHeatLoss = 0.017251*ActLevel*(5.8662 - VapPress)
781 : // V-1.2.2 'fix' BG 3/2005 5th term in LHS Eq (58) in 2001 HOF Ch. 8
782 : // this was wrong because VapPress needed to be kPa
783 :
784 1203175 : state.dataThermalComforts->DryRespHeatLoss = 0.0014 * ActLevel * (34.0 - AirTemp); // Heat loss by dry respiration.
785 :
786 1203175 : state.dataThermalComforts->RespHeatLoss = state.dataThermalComforts->LatRespHeatLoss + state.dataThermalComforts->DryRespHeatLoss;
787 :
788 1203175 : state.dataThermalComforts->ThermSensTransCoef = 0.303 * std::exp(-0.036 * ActLevel) + 0.028; // Thermal transfer coefficient to calculate PMV
789 :
790 1203175 : PMV = state.dataThermalComforts->ThermSensTransCoef * (state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->EvapHeatLoss -
791 1203175 : state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->DryHeatLoss);
792 :
793 1203175 : return PMV;
794 : }
795 :
796 1202887 : Real64 CalcFangerPPD(Real64 PMV)
797 : {
798 : Real64 PPD;
799 1202887 : Real64 expTest1 = -0.03353 * pow_4(PMV) - 0.2179 * pow_2(PMV);
800 1202887 : if (expTest1 > DataPrecisionGlobals::EXP_LowerLimit) {
801 1195188 : PPD = 100.0 - 95.0 * std::exp(expTest1);
802 : } else {
803 7699 : PPD = 100.0;
804 : }
805 :
806 1202887 : if (PPD < 0.0) {
807 0 : PPD = 0.0;
808 1202887 : } else if (PPD > 100.0) {
809 0 : PPD = 100.0;
810 : }
811 1202887 : return PPD;
812 : }
813 :
814 384 : Real64 CalcRelativeAirVelocity(Real64 AirVel, Real64 ActMet)
815 : {
816 384 : if (ActMet > 1) {
817 384 : return AirVel + 0.3 * (ActMet - 1);
818 : } else {
819 0 : return AirVel;
820 : }
821 : }
822 :
823 2736 : void GetThermalComfortInputsASHRAE(EnergyPlusData &state)
824 : {
825 2736 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
826 2736 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
827 :
828 2736 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
829 2736 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum);
830 : // (var TA)
831 2736 : state.dataThermalComforts->AirTemp = thisZoneHB.ZTAVComf;
832 2736 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
833 0 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
834 0 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
835 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum); // PH 3/7/04
836 : }
837 : }
838 : // (var TR)
839 2736 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
840 : // (var RH)
841 5472 : state.dataThermalComforts->RelHum =
842 2736 : PsyRhFnTdbWPb(state, state.dataThermalComforts->AirTemp, thisZoneHB.airHumRatAvgComf, state.dataEnvrn->OutBaroPress);
843 : // Metabolic rate of body (W/m2) (var RM, M)
844 2736 : state.dataThermalComforts->ActLevel = people.activityLevelSched->getCurrentVal() / BodySurfAreaPierce;
845 : // Energy consumption by external work (W/m2) (var WME)
846 2736 : state.dataThermalComforts->WorkEff = people.workEffSched->getCurrentVal() * state.dataThermalComforts->ActLevel;
847 :
848 : // Clothing unit (var CLO)
849 : Real64 IntermediateClothing;
850 2736 : switch (people.clothingType) {
851 2688 : case DataHeatBalance::ClothingType::InsulationSchedule:
852 2688 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
853 2688 : break;
854 48 : case DataHeatBalance::ClothingType::DynamicAshrae55:
855 48 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
856 48 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
857 48 : DynamicClothingModel(state);
858 48 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
859 48 : break;
860 0 : case DataHeatBalance::ClothingType::CalculationSchedule:
861 0 : IntermediateClothing = people.clothingMethodSched->getCurrentVal();
862 0 : if (IntermediateClothing == 1.0) {
863 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
864 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
865 0 : } else if (IntermediateClothing == 2.0) {
866 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
867 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
868 0 : DynamicClothingModel(state);
869 0 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
870 : } else {
871 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
872 0 : ShowWarningError(state, "Scheduled clothing value will be used rather than clothing calculation method.");
873 : }
874 0 : break;
875 0 : default:
876 0 : ShowSevereError(state, "Incorrect Clothing Type");
877 : }
878 : // (var VEL)
879 2736 : state.dataThermalComforts->AirVel = people.airVelocitySched->getCurrentVal();
880 : // (var MET)
881 2736 : state.dataThermalComforts->ActMet = state.dataThermalComforts->ActLevel / ActLevelConv;
882 2736 : }
883 :
884 5424 : Real64 CalcStandardEffectiveTemp(
885 : EnergyPlusData &state, Real64 AirTemp, Real64 RadTemp, Real64 RelHum, Real64 AirVel, Real64 ActMet, Real64 CloUnit, Real64 WorkEff)
886 : {
887 :
888 : // Thermal const
889 5424 : constexpr Real64 CloFac(0.25); // Clothing factor determined experimentally (var KCLO)
890 5424 : constexpr Real64 BodyWeight(69.9); // (var BODYWEIGHT)
891 5424 : constexpr Real64 SweatContConst(170.0); // Proportionality constant for sweat control; g/m2.hr (var CSW)
892 5424 : constexpr Real64 DriCoeffVasodilation(120); // driving coefficient for vasodilation (var CDIL)
893 5424 : constexpr Real64 DriCoeffVasoconstriction(0.5); // (var CSTR)
894 5424 : constexpr Real64 MaxSkinBloodFlow(90.0); // Max. value of skin blood flow
895 5424 : constexpr Real64 MinSkinBloodFlow(0.5); // Min. value of skin blood flow
896 5424 : constexpr Real64 RegSweatMax(500); // Max. value of regulatory sweating; w/m2
897 :
898 : // Standard condition const
899 : // Definition of vascular control signals CoreTempSet, SkinTempSet, and AvgBodyTempSet are the setpoints for core, skin and
900 : // average body temperatures corresponding to physiol. neutrality SkinMassRatSet is the ratio of skin mass to total body mass (skin+core)
901 : // Typical values for CoreTempSet, SkinTempSet and SkinMassRatSet are 36.8, 33.7 and 0.10 SkinMassRat is the actual skin to total body mass
902 : // ratio
903 5424 : constexpr Real64 SkinTempSet(33.7); // (var TempSkinNeutral)
904 5424 : constexpr Real64 CoreTempSet(36.8); // (var TempCoreNeutral)
905 5424 : constexpr Real64 SkinBloodFlowSet(6.3); // (var SkinBloodFlowNeutral)
906 5424 : constexpr Real64 SkinMassRatSet(0.1); // (var ALFA)
907 :
908 5424 : if (AirVel < 0.1) {
909 0 : AirVel = 0.1;
910 : }
911 :
912 : // (var VaporPressure)
913 5424 : state.dataThermalComforts->VapPress = RelHum * CalcSatVapPressFromTempTorr(AirTemp);
914 5424 : Real64 ActLevel = ActLevelConv * ActMet;
915 5424 : state.dataThermalComforts->IntHeatProd = ActLevel - WorkEff;
916 :
917 : // Step 1: CALCULATE VARIABLESS THAT REMAIN CONSTANT FOR AN HOUR
918 5424 : Real64 PInAtmospheres = state.dataEnvrn->OutBaroPress / 101325;
919 5424 : Real64 RClo = CloUnit * 0.155; // (var RCL)
920 5424 : Real64 TotCloFac = 1.0 + 0.15 * CloUnit;
921 5424 : Real64 LewisRatio = 2.2 / PInAtmospheres; // Lewis Relation is 2.2 at sea level, 25C (var LR)
922 : Real64 EvapEff; // evaporative efficiency
923 :
924 : // APPROXIMATE THE FOLLOWING VALUES TO START
925 5424 : state.dataThermalComforts->SkinTemp = SkinTempSet;
926 5424 : state.dataThermalComforts->CoreTemp = CoreTempSet;
927 5424 : Real64 SkinBloodFlow = SkinBloodFlowSet;
928 5424 : Real64 SkinMassRat = SkinMassRatSet;
929 :
930 : // Mass transfer equation between skin and environment
931 : // CloInsul is efficiency of mass transfer for CloUnit.
932 5424 : if (CloUnit <= 0) {
933 0 : EvapEff = 0.38 * std::pow(AirVel, -0.29); // (var WCRIT)
934 0 : state.dataThermalComforts->CloInsul = 1.0;
935 : } else {
936 5424 : EvapEff = 0.59 * std::pow(AirVel, -0.08); // (var ICL)
937 5424 : state.dataThermalComforts->CloInsul = 0.45;
938 : }
939 :
940 5424 : Real64 CorrectedHC = 3.0 * std::pow(PInAtmospheres, 0.53); // corrected convective heat transfer coefficient
941 5424 : Real64 ForcedHC = 8.600001 * std::pow((AirVel * PInAtmospheres), 0.53); // forced convective heat transfer coefficient, W/(m2 °C) (CHCV)
942 5424 : state.dataThermalComforts->Hc = std::max(CorrectedHC, ForcedHC); // (CHC)
943 5424 : state.dataThermalComforts->Hr = 4.7; // (CHR)
944 5424 : state.dataThermalComforts->EvapHeatLoss = 0.1 * ActMet;
945 5424 : Real64 RAir = 1.0 / (TotCloFac * (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr)); // resistance of air layer to dry heat (RA)
946 5424 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->Hr * RadTemp + state.dataThermalComforts->Hc * AirTemp) /
947 5424 : (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr); // operative temperature (TOP)
948 5424 : Real64 ActLevelStart = ActLevel; // ActLevel gets increased by shivering in the following
949 5424 : Real64 AvgBodyTempSet = SkinMassRatSet * SkinTempSet + (1.0 - SkinMassRatSet) * CoreTempSet; // (var TempBodyNeutral)
950 :
951 : // Step 2: BEGIN MINUTE BY MINUTE CALCULATIONS FOR ONE HOUR SIMULATION OF TEMPERATURE REGULATION.
952 : // This section simulates the temperature regulation over 1 minute.
953 : // Inputs are the physiological data from the previous time step and the current environmental conditions. Loop and must be increased from the
954 : // start level, not perpetually increased
955 330864 : for (int IterMin = 1; IterMin <= 60; ++IterMin) {
956 : // Dry heat balance: solve for CloSurfTemp and Hr, GUESS CloSurfTemp TO START
957 650880 : state.dataThermalComforts->CloSurfTemp =
958 325440 : (RAir * state.dataThermalComforts->SkinTemp + RClo * state.dataThermalComforts->OpTemp) / (RAir + RClo);
959 325440 : bool converged = false;
960 661595 : while (!converged) {
961 672310 : state.dataThermalComforts->Hr =
962 336155 : 4.0 * StefanBoltz * std::pow((state.dataThermalComforts->CloSurfTemp + RadTemp) / 2.0 + 273.15, 3) * 0.72;
963 336155 : RAir = 1.0 / (TotCloFac * (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr));
964 336155 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->Hr * RadTemp + state.dataThermalComforts->Hc * AirTemp) /
965 336155 : (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr);
966 336155 : Real64 CloSurfTempNew = (RAir * state.dataThermalComforts->SkinTemp + RClo * state.dataThermalComforts->OpTemp) / (RAir + RClo);
967 336155 : if (std::abs(CloSurfTempNew - state.dataThermalComforts->CloSurfTemp) <= 0.01) {
968 325440 : converged = true;
969 : }
970 336155 : state.dataThermalComforts->CloSurfTemp = CloSurfTempNew;
971 : }
972 :
973 : // CALCULATE THE COMBINED HEAT TRANSFER COEFF. (H)
974 325440 : state.dataThermalComforts->H = state.dataThermalComforts->Hr + state.dataThermalComforts->Hc;
975 : // Heat flow from Clothing surface to environment
976 325440 : state.dataThermalComforts->DryHeatLoss = (state.dataThermalComforts->SkinTemp - state.dataThermalComforts->OpTemp) / (RAir + RClo);
977 :
978 : // dry and latent respiratory heat losses
979 650880 : state.dataThermalComforts->LatRespHeatLoss =
980 325440 : 0.0023 * ActLevel * (44.0 - state.dataThermalComforts->VapPress); // latent heat loss due to respiration
981 325440 : state.dataThermalComforts->DryRespHeatLoss = 0.0014 * ActLevel * (34.0 - AirTemp);
982 :
983 325440 : state.dataThermalComforts->RespHeatLoss = state.dataThermalComforts->LatRespHeatLoss + state.dataThermalComforts->DryRespHeatLoss;
984 :
985 : // Heat flows to skin and core: 5.28 is skin conductance in the absence of skin blood flow
986 650880 : state.dataThermalComforts->HeatFlow =
987 325440 : (state.dataThermalComforts->CoreTemp - state.dataThermalComforts->SkinTemp) * (5.28 + 1.163 * SkinBloodFlow);
988 :
989 325440 : Real64 CoreHeatStorage = ActLevel - state.dataThermalComforts->HeatFlow - state.dataThermalComforts->RespHeatLoss -
990 325440 : WorkEff; // rate of energy storage in the core
991 325440 : Real64 SkinHeatStorage = state.dataThermalComforts->HeatFlow - state.dataThermalComforts->DryHeatLoss -
992 325440 : state.dataThermalComforts->EvapHeatLoss; // rate of energy storage in the skin
993 :
994 : // Thermal capacities
995 325440 : state.dataThermalComforts->CoreThermCap = 0.97 * (1 - SkinMassRat) * BodyWeight;
996 325440 : state.dataThermalComforts->SkinThermCap = 0.97 * SkinMassRat * BodyWeight;
997 :
998 : // Temperature changes in 1 minute
999 325440 : state.dataThermalComforts->CoreTempChange = (CoreHeatStorage * BodySurfAreaPierce / (state.dataThermalComforts->CoreThermCap * 60.0));
1000 325440 : state.dataThermalComforts->SkinTempChange = (SkinHeatStorage * BodySurfAreaPierce) / (state.dataThermalComforts->SkinThermCap * 60.0);
1001 :
1002 325440 : state.dataThermalComforts->CoreTemp += state.dataThermalComforts->CoreTempChange;
1003 325440 : state.dataThermalComforts->SkinTemp += state.dataThermalComforts->SkinTempChange;
1004 650880 : state.dataThermalComforts->AvgBodyTemp =
1005 325440 : SkinMassRat * state.dataThermalComforts->SkinTemp + (1.0 - SkinMassRat) * state.dataThermalComforts->CoreTemp;
1006 :
1007 : Real64 SkinThermSigWarm; // vasodilation signal (WARMS)
1008 : Real64 SkinThermSigCold; // vasoconstriction signal
1009 325440 : Real64 SkinSignal = state.dataThermalComforts->SkinTemp - SkinTempSet; // thermoregulatory control signal from the skin
1010 325440 : if (SkinSignal > 0) {
1011 130449 : SkinThermSigWarm = SkinSignal;
1012 130449 : SkinThermSigCold = 0.0;
1013 : } else {
1014 194991 : SkinThermSigCold = -SkinSignal;
1015 194991 : SkinThermSigWarm = 0.0;
1016 : }
1017 :
1018 : Real64 CoreThermSigWarm; // vasodialtion signal (WARMC)
1019 : Real64 CoreThermSigCold; // vasoconstriction signal
1020 325440 : Real64 CoreSignal = state.dataThermalComforts->CoreTemp - CoreTempSet; // thermoregulatory control signal from the skin, °C
1021 325440 : if (CoreSignal > 0) {
1022 214784 : CoreThermSigWarm = CoreSignal;
1023 214784 : CoreThermSigCold = 0.0;
1024 : } else {
1025 110656 : CoreThermSigCold = -CoreSignal;
1026 110656 : CoreThermSigWarm = 0.0;
1027 : }
1028 :
1029 : Real64 BodyThermSigWarm; // WARMB
1030 325440 : Real64 BodySignal = state.dataThermalComforts->AvgBodyTemp - AvgBodyTempSet;
1031 :
1032 325440 : if (BodySignal > 0) {
1033 129060 : BodyThermSigWarm = BodySignal;
1034 : } else {
1035 196380 : BodyThermSigWarm = 0.0;
1036 : }
1037 :
1038 325440 : state.dataThermalComforts->VasodilationFac = DriCoeffVasodilation * CoreThermSigWarm;
1039 325440 : state.dataThermalComforts->VasoconstrictFac = DriCoeffVasoconstriction * SkinThermSigCold;
1040 325440 : SkinBloodFlow = (SkinBloodFlowSet + state.dataThermalComforts->VasodilationFac) / (1.0 + state.dataThermalComforts->VasoconstrictFac);
1041 : // SkinBloodFlow is never below 0.5 liter/(m2.hr) nor above 90 liter/(m2.hr)
1042 325440 : if (SkinBloodFlow < MinSkinBloodFlow) {
1043 0 : SkinBloodFlow = MinSkinBloodFlow;
1044 : }
1045 325440 : if (SkinBloodFlow > MaxSkinBloodFlow) {
1046 23716 : SkinBloodFlow = MaxSkinBloodFlow;
1047 : }
1048 325440 : SkinMassRat = 0.0417737 + 0.7451832 / (SkinBloodFlow + 0.585417); // ratio of skin-core masses change with SkinBloodFlow
1049 :
1050 325440 : Real64 RegSweat = SweatContConst * BodyThermSigWarm * std::exp(SkinThermSigWarm / 10.7); // control of regulatory sweating
1051 325440 : if (RegSweat > RegSweatMax) {
1052 235 : RegSweat = RegSweatMax;
1053 : }
1054 325440 : state.dataThermalComforts->EvapHeatLossRegSweat = 0.68 * RegSweat; // heat lost by vaporization sweat
1055 :
1056 : // adjustment of metabolic heat due to shivering (Stolwijk, Hardy)
1057 325440 : state.dataThermalComforts->ShivResponse = 19.4 * SkinThermSigCold * CoreThermSigCold;
1058 325440 : ActLevel = ActLevelStart + state.dataThermalComforts->ShivResponse;
1059 :
1060 : // Evaluation of heat transfer by evaporation at skin surface
1061 325440 : Real64 AirEvapHeatResist = 1.0 / (LewisRatio * TotCloFac * state.dataThermalComforts->Hc); // evaporative resistance air layer
1062 325440 : Real64 CloEvapHeatResist = RClo / (LewisRatio * state.dataThermalComforts->CloInsul);
1063 325440 : Real64 TotEvapHeatResist = AirEvapHeatResist + CloEvapHeatResist;
1064 325440 : state.dataThermalComforts->SatSkinVapPress = CalcSatVapPressFromTempTorr(state.dataThermalComforts->SkinTemp); // PSSK
1065 650880 : state.dataThermalComforts->EvapHeatLossMax =
1066 325440 : (state.dataThermalComforts->SatSkinVapPress - state.dataThermalComforts->VapPress) / TotEvapHeatResist; // TotEvapHeatResist;
1067 650880 : state.dataThermalComforts->SkinWetSweat =
1068 325440 : state.dataThermalComforts->EvapHeatLossRegSweat /
1069 325440 : state.dataThermalComforts->EvapHeatLossMax; // ratio heat loss sweating to max heat loss sweating
1070 :
1071 650880 : state.dataThermalComforts->SkinWetDiff =
1072 325440 : (1.0 - state.dataThermalComforts->SkinWetSweat) * 0.06; // 0.06 if SkinWetDiff for nonsweating skin --- Kerslake
1073 325440 : state.dataThermalComforts->EvapHeatLossDiff = state.dataThermalComforts->SkinWetDiff * state.dataThermalComforts->EvapHeatLossMax;
1074 325440 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->EvapHeatLossRegSweat + state.dataThermalComforts->EvapHeatLossDiff;
1075 325440 : state.dataThermalComforts->SkinWetTot = state.dataThermalComforts->EvapHeatLoss / state.dataThermalComforts->EvapHeatLossMax;
1076 :
1077 : // Beginning of dripping (Sweat not evaporated on skin surface)
1078 325440 : if (state.dataThermalComforts->SkinWetTot >= EvapEff) {
1079 82551 : state.dataThermalComforts->SkinWetTot = EvapEff;
1080 82551 : state.dataThermalComforts->SkinWetSweat = EvapEff / 0.94;
1081 165102 : state.dataThermalComforts->EvapHeatLossRegSweat =
1082 82551 : state.dataThermalComforts->SkinWetSweat * state.dataThermalComforts->EvapHeatLossMax;
1083 82551 : state.dataThermalComforts->SkinWetDiff = (1.0 - state.dataThermalComforts->SkinWetSweat) * 0.06;
1084 82551 : state.dataThermalComforts->EvapHeatLossDiff = state.dataThermalComforts->SkinWetDiff * state.dataThermalComforts->EvapHeatLossMax;
1085 82551 : state.dataThermalComforts->EvapHeatLoss =
1086 82551 : state.dataThermalComforts->EvapHeatLossRegSweat + state.dataThermalComforts->EvapHeatLossDiff;
1087 : }
1088 :
1089 : // When EvapHeatLossMax<0. condensation on skin occurs.
1090 325440 : if (state.dataThermalComforts->EvapHeatLossMax < 0.0) {
1091 18882 : state.dataThermalComforts->SkinWetDiff = 0.0;
1092 18882 : state.dataThermalComforts->EvapHeatLossDiff = 0.0;
1093 18882 : state.dataThermalComforts->EvapHeatLoss = 0.0;
1094 18882 : state.dataThermalComforts->SkinWetTot = EvapEff;
1095 18882 : state.dataThermalComforts->SkinWetSweat = EvapEff;
1096 18882 : state.dataThermalComforts->EvapHeatLossRegSweat = 0.0;
1097 : }
1098 : // Vapor pressure at skin (as measured by dewpoint sensors)
1099 650880 : state.dataThermalComforts->SkinVapPress = state.dataThermalComforts->SkinWetTot * state.dataThermalComforts->SatSkinVapPress +
1100 325440 : (1.0 - state.dataThermalComforts->SkinWetTot) * state.dataThermalComforts->VapPress;
1101 : } // END OF MINUTE BY MINUTE TEMPERATURE REGULATION LOOP
1102 :
1103 : // EvapHeatLossMax is readjusted for EvapEff
1104 5424 : state.dataThermalComforts->EvapHeatLossMax *= EvapEff;
1105 :
1106 : // Step 3: Heat transfer indices in real environment. Computation of comfort indices.
1107 : // Inputs to this SECTION are the physiological data from the simulation of temperature regulation loop.
1108 5424 : Real64 EffectSkinHeatLoss = state.dataThermalComforts->DryHeatLoss + state.dataThermalComforts->EvapHeatLoss;
1109 : // ET*(standardization humidity/REAL(r64) CloUnit, StdAtm and Hc)
1110 5424 : state.dataThermalComforts->CloBodyRat = 1.0 + CloFac * CloUnit;
1111 : Real64 EffectCloUnit =
1112 5424 : CloUnit - (state.dataThermalComforts->CloBodyRat - 1.0) / (0.155 * state.dataThermalComforts->CloBodyRat * state.dataThermalComforts->H);
1113 5424 : Real64 EffectCloThermEff = 1.0 / (1.0 + 0.155 * state.dataThermalComforts->Hc * EffectCloUnit);
1114 10848 : state.dataThermalComforts->CloPermeatEff =
1115 5424 : 1.0 / (1.0 + (0.155 / state.dataThermalComforts->CloInsul) * state.dataThermalComforts->Hc * EffectCloUnit);
1116 : // Get a low approximation for ET* and solve balance equation by iteration
1117 5424 : Real64 ET = state.dataThermalComforts->SkinTemp - EffectSkinHeatLoss / (state.dataThermalComforts->H * EffectCloThermEff);
1118 : Real64 EnergyBalErrET;
1119 : while (true) {
1120 258551 : Real64 StdVapPressET = CalcSatVapPressFromTempTorr(ET); // THE STANDARD VAPOR PRESSURE AT THE EFFECTIVE TEMP : StdVapPressET
1121 258551 : EnergyBalErrET = EffectSkinHeatLoss - state.dataThermalComforts->H * EffectCloThermEff * (state.dataThermalComforts->SkinTemp - ET) -
1122 258551 : state.dataThermalComforts->SkinWetTot * LewisRatio * state.dataThermalComforts->Hc *
1123 258551 : state.dataThermalComforts->CloPermeatEff * (state.dataThermalComforts->SatSkinVapPress - StdVapPressET / 2.0);
1124 258551 : if (EnergyBalErrET >= 0.0) {
1125 5424 : break;
1126 : }
1127 253127 : ET += 0.1;
1128 253127 : }
1129 5424 : state.dataThermalComforts->EffTemp = ET;
1130 :
1131 : // Standard effective temperature SET* standardized humidity. Hc, CloUnit, StdAtm normalized for given ActLel AirVel
1132 : // Standard environment
1133 5424 : Real64 StdHr = state.dataThermalComforts->Hr;
1134 : Real64 StdHc; // standard conv. heat tr. coeff. (level walking/still air)
1135 5424 : if (ActMet <= 0.85) {
1136 0 : StdHc = 3.0; // minimum value of Hc at sea leAirVel = 3.0 (AirVel = .137 m/s)
1137 : } else {
1138 5424 : StdHc = 5.66 * std::pow(ActMet - 0.85, 0.39);
1139 : }
1140 5424 : if (StdHc <= 3.0) {
1141 0 : StdHc = 3.0;
1142 : }
1143 5424 : Real64 StdH = StdHc + StdHr; // StdH Standard combined heat transfer coefficient
1144 : // standard MET - StdCloUnit relation gives SET* = 24 C when PMV = 0
1145 5424 : Real64 StdCloUnit = 1.52 / (ActMet - WorkEff / ActLevelConv + 0.6944) - 0.1835; // RCLOS
1146 5424 : Real64 StdRClo = 0.155 * StdCloUnit; // RCLS
1147 5424 : Real64 StdCloBodyRat = 1.0 + CloFac * StdCloUnit; // FACLS
1148 5424 : Real64 StdEffectCloThermEff = 1.0 / (1.0 + 0.155 * StdCloBodyRat * StdH * StdCloUnit); // FCLS
1149 5424 : Real64 StdCloInsul = state.dataThermalComforts->CloInsul * StdHc / StdH * (1 - StdEffectCloThermEff) /
1150 5424 : (StdHc / StdH - state.dataThermalComforts->CloInsul * StdEffectCloThermEff);
1151 5424 : Real64 StdREvap = 1.0 / (LewisRatio * StdCloBodyRat * StdHc);
1152 5424 : Real64 StdREvapClo = StdRClo / (LewisRatio * StdCloInsul);
1153 5424 : Real64 StdHEvap = 1.0 / (StdREvap + StdREvapClo);
1154 5424 : Real64 StdRAir = 1.0 / (StdCloBodyRat * StdH);
1155 5424 : Real64 StdHDry = 1.0 / (StdRAir + StdRClo);
1156 :
1157 : // Get a low approximation for SET* and solve balance equ. by iteration
1158 5424 : Real64 StdEffectSkinHeatLoss = state.dataThermalComforts->DryHeatLoss + state.dataThermalComforts->EvapHeatLoss;
1159 5424 : Real64 OldSET = round((state.dataThermalComforts->SkinTemp - StdEffectSkinHeatLoss / StdHDry) * 100) / 100;
1160 5424 : Real64 delta = 0.0001;
1161 5424 : Real64 err = 100.0;
1162 20286 : while (std::abs(err) > 0.01) {
1163 14862 : Real64 StdVapPressSET_1 = CalcSatVapPressFromTempTorr(OldSET); // StdVapPressSET *= VapPressConv;
1164 : Real64 EnergyBalErrSET_1 =
1165 14862 : StdEffectSkinHeatLoss - StdHDry * (state.dataThermalComforts->SkinTemp - OldSET) -
1166 14862 : state.dataThermalComforts->SkinWetTot * StdHEvap * (state.dataThermalComforts->SatSkinVapPress - StdVapPressSET_1 / 2.0);
1167 14862 : Real64 StdVapPressSET_2 = CalcSatVapPressFromTempTorr(OldSET + delta);
1168 : Real64 EnergyBalErrSET_2 =
1169 14862 : StdEffectSkinHeatLoss - StdHDry * (state.dataThermalComforts->SkinTemp - (OldSET + delta)) -
1170 14862 : state.dataThermalComforts->SkinWetTot * StdHEvap * (state.dataThermalComforts->SatSkinVapPress - StdVapPressSET_2 / 2.0);
1171 14862 : Real64 NewSET = OldSET - delta * EnergyBalErrSET_1 / (EnergyBalErrSET_2 - EnergyBalErrSET_1);
1172 14862 : err = NewSET - OldSET;
1173 14862 : OldSET = NewSET;
1174 : }
1175 5424 : Real64 SET = OldSET;
1176 : // PMV*(PMVET in prgm) uses ET instead of OpTemp
1177 5424 : state.dataThermalComforts->DryHeatLossET = StdH * StdEffectCloThermEff * (state.dataThermalComforts->SkinTemp - ET);
1178 : // SPMV*(PMVSET in prgm) uses SET instead of OpTemp
1179 5424 : state.dataThermalComforts->DryHeatLossSET = StdH * StdEffectCloThermEff * (state.dataThermalComforts->SkinTemp - SET);
1180 5424 : return SET;
1181 : }
1182 :
1183 1968 : void CalcThermalComfortPierceASHRAE(EnergyPlusData &state)
1184 : {
1185 : // This subroutine calculates ET, SET, SETPMV, SETPPD using Pierce two-node model.
1186 : // Reference: ANSI/ASHRAE Standard 55-2017 Appendix D.
1187 :
1188 5568 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1189 3600 : ++state.dataThermalComforts->PeopleNum) {
1190 3600 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1191 3600 : if (!people.Pierce) {
1192 1248 : continue;
1193 : }
1194 :
1195 2352 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1196 :
1197 : // STEP 1: Get input (TA, TR, RH, VEL, CLO, MET, WME)
1198 2352 : GetThermalComfortInputsASHRAE(state);
1199 :
1200 : // STEP 2: Calculate SET.
1201 2352 : Real64 SET = CalcStandardEffectiveTemp(state,
1202 2352 : state.dataThermalComforts->AirTemp,
1203 2352 : state.dataThermalComforts->RadTemp,
1204 2352 : state.dataThermalComforts->RelHum,
1205 2352 : state.dataThermalComforts->AirVel,
1206 2352 : state.dataThermalComforts->ActMet,
1207 2352 : state.dataThermalComforts->CloUnit,
1208 2352 : state.dataThermalComforts->WorkEff);
1209 :
1210 : // STEP 3: Report SET related variables.
1211 : // Fanger's comfort equation. Thermal transfer coefficient to calculate PMV
1212 2352 : state.dataThermalComforts->ThermSensTransCoef = 0.303 * std::exp(-0.036 * state.dataThermalComforts->ActLevel) + 0.028;
1213 : // Fanger's reg. sweating at comfort threshold (PMV=0) is:
1214 2352 : state.dataThermalComforts->EvapHeatLossRegComf = (state.dataThermalComforts->IntHeatProd - ActLevelConv) * 0.42;
1215 2352 : comfort.PiercePMVET =
1216 2352 : state.dataThermalComforts->ThermSensTransCoef *
1217 2352 : (state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->DryHeatLossET -
1218 2352 : state.dataThermalComforts->EvapHeatLossDiff - state.dataThermalComforts->EvapHeatLossRegComf);
1219 2352 : comfort.PiercePMVSET =
1220 2352 : state.dataThermalComforts->ThermSensTransCoef *
1221 2352 : (state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->DryHeatLossSET -
1222 2352 : state.dataThermalComforts->EvapHeatLossDiff - state.dataThermalComforts->EvapHeatLossRegComf);
1223 :
1224 : // PHeat stress and heat strain indices derived from EvapHeatLoss, DISC (discomfort) varies with relative thermoregulatory strain
1225 2352 : comfort.PierceDISC = 5.0 * (state.dataThermalComforts->EvapHeatLossRegSweat - state.dataThermalComforts->EvapHeatLossRegComf) /
1226 2352 : (state.dataThermalComforts->EvapHeatLossMax - state.dataThermalComforts->EvapHeatLossRegComf -
1227 2352 : state.dataThermalComforts->EvapHeatLossDiff);
1228 :
1229 : // Thermal sensation TSENS as function of mean body temp.-
1230 : // AvgBodyTempLow is AvgBodyTemp when DISC is 0. (lower limit of zone of evap. regul.)
1231 2352 : Real64 AvgBodyTempLow = (0.185 / ActLevelConv) * (state.dataThermalComforts->ActLevel - state.dataThermalComforts->WorkEff) + 36.313;
1232 : // AvgBodyTempHigh is AvgBodyTemp when HSI=100 (upper limit of zone of evap. regul.)
1233 2352 : Real64 AvgBodyTempHigh = (0.359 / ActLevelConv) * (state.dataThermalComforts->ActLevel - state.dataThermalComforts->WorkEff) + 36.664;
1234 :
1235 : // TSENS=DISC=4.7 when HSI =1 00 (HSI is Belding's classic heat stress index)
1236 : // In cold, DISC &TSENS are the same and neg. fct of AvgBodyTemp
1237 2352 : if (state.dataThermalComforts->AvgBodyTemp > AvgBodyTempLow) {
1238 888 : comfort.PierceTSENS = 4.7 * (state.dataThermalComforts->AvgBodyTemp - AvgBodyTempLow) / (AvgBodyTempHigh - AvgBodyTempLow);
1239 :
1240 : } else {
1241 1464 : comfort.PierceTSENS = 0.68175 * (state.dataThermalComforts->AvgBodyTemp - AvgBodyTempLow);
1242 1464 : comfort.PierceDISC = comfort.PierceTSENS;
1243 : }
1244 :
1245 2352 : comfort.ThermalComfortMRT = state.dataThermalComforts->RadTemp;
1246 2352 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1247 2352 : comfort.PierceSET = SET;
1248 : }
1249 1968 : }
1250 :
1251 192 : void CalcThermalComfortCoolingEffectASH(EnergyPlusData &state)
1252 : {
1253 : // This subroutine calculates ASHRAE Cooling effect adjusted PMV and PPD
1254 : // Reference: ANSI/ASHRAE Standard 55-2017 Appendix D.
1255 :
1256 384 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1257 192 : ++state.dataThermalComforts->PeopleNum) {
1258 192 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1259 192 : if (!people.CoolingEffectASH55) {
1260 0 : continue;
1261 : }
1262 :
1263 192 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1264 :
1265 : // Get input (TA, TR, RH, VEL, CLO, MET, WME)
1266 192 : GetThermalComfortInputsASHRAE(state);
1267 :
1268 : // Calculate elevated air cooling effect using the SET function.
1269 192 : Real64 CoolingEffect = 0;
1270 : Real64 CoolingEffectAdjustedPMV;
1271 192 : CalcCoolingEffectAdjustedPMV(state, CoolingEffect, CoolingEffectAdjustedPMV);
1272 :
1273 : // Report.
1274 192 : comfort.CoolingEffectASH55 = CoolingEffect;
1275 192 : comfort.CoolingEffectAdjustedPMVASH55 = CoolingEffectAdjustedPMV;
1276 192 : comfort.CoolingEffectAdjustedPPDASH55 = CalcFangerPPD(CoolingEffectAdjustedPMV);
1277 : }
1278 192 : }
1279 :
1280 192 : void CalcCoolingEffectAdjustedPMV(EnergyPlusData &state, Real64 &CoolingEffect, Real64 &CoolingEffectAdjustedPMV)
1281 : {
1282 192 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1283 :
1284 : // Calculate SET without cooling effect.
1285 192 : Real64 RelAirVel = CalcRelativeAirVelocity(state.dataThermalComforts->AirVel, state.dataThermalComforts->ActMet);
1286 192 : Real64 SET = CalcStandardEffectiveTemp(state,
1287 192 : state.dataThermalComforts->AirTemp,
1288 192 : state.dataThermalComforts->RadTemp,
1289 192 : state.dataThermalComforts->RelHum,
1290 : RelAirVel,
1291 192 : state.dataThermalComforts->ActMet,
1292 192 : state.dataThermalComforts->CloUnit,
1293 192 : state.dataThermalComforts->WorkEff);
1294 :
1295 : // TODO - This should use the ASHRAE55-2017 PMV calc program. The current Fanger PMV program are not consistent with the new standard.
1296 192 : Real64 ASHRAE55PMV = CalcFangerPMV(state,
1297 192 : state.dataThermalComforts->AirTemp,
1298 192 : state.dataThermalComforts->RadTemp,
1299 192 : state.dataThermalComforts->RelHum,
1300 : RelAirVel,
1301 192 : state.dataThermalComforts->ActLevel,
1302 192 : state.dataThermalComforts->CloUnit,
1303 192 : state.dataThermalComforts->WorkEff);
1304 :
1305 192 : Real64 StillAirVel = 0.1;
1306 2880 : auto ce_root_function = [&state, &StillAirVel, &SET](Real64 x) {
1307 20160 : return CalcStandardEffectiveTemp(state,
1308 2880 : state.dataThermalComforts->AirTemp - x,
1309 2880 : state.dataThermalComforts->RadTemp - x,
1310 2880 : state.dataThermalComforts->RelHum,
1311 : StillAirVel,
1312 2880 : state.dataThermalComforts->ActMet,
1313 2880 : state.dataThermalComforts->CloUnit,
1314 2880 : state.dataThermalComforts->WorkEff) -
1315 2880 : SET;
1316 192 : };
1317 :
1318 2688 : auto ce_root_termination = [](Real64 min, Real64 max) { return abs(max - min) <= 0.01; };
1319 192 : Real64 lowerBound = 0.0;
1320 192 : Real64 upperBound = 50.0;
1321 :
1322 : // We have yet another solver?
1323 : try {
1324 192 : std::pair<Real64, Real64> solverResult = boost::math::tools::bisect(ce_root_function, lowerBound, upperBound, ce_root_termination);
1325 192 : CoolingEffect = (solverResult.first + solverResult.second) / 2;
1326 0 : } catch (const std::exception &e) {
1327 0 : ShowRecurringWarningErrorAtEnd(state,
1328 0 : "The cooling effect could not be solved for People=\"" + people.Name + "\"" +
1329 : "As a result, no cooling effect will be applied to adjust the PMV and PPD results.",
1330 0 : state.dataThermalComforts->CoolingEffectWarningInd);
1331 0 : CoolingEffect = 0;
1332 0 : }
1333 :
1334 192 : if (CoolingEffect > 0) {
1335 192 : CoolingEffectAdjustedPMV = CalcFangerPMV(state,
1336 192 : state.dataThermalComforts->AirTemp - CoolingEffect,
1337 192 : state.dataThermalComforts->RadTemp - CoolingEffect,
1338 192 : state.dataThermalComforts->RelHum,
1339 : StillAirVel,
1340 192 : state.dataThermalComforts->ActLevel,
1341 192 : state.dataThermalComforts->CloUnit,
1342 192 : state.dataThermalComforts->WorkEff);
1343 : } else {
1344 0 : CoolingEffectAdjustedPMV = ASHRAE55PMV;
1345 : }
1346 192 : }
1347 :
1348 192 : void CalcThermalComfortAnkleDraftASH(EnergyPlusData &state)
1349 : {
1350 : // This subroutine calculates ASHRAE Ankle draft PPD
1351 : // Reference: ANSI/ASHRAE Standard 55-2017 Appendix I.
1352 :
1353 384 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1354 192 : ++state.dataThermalComforts->PeopleNum) {
1355 :
1356 192 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1357 192 : if (!people.AnkleDraftASH55) {
1358 0 : continue;
1359 : }
1360 :
1361 192 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1362 :
1363 192 : GetThermalComfortInputsASHRAE(state);
1364 192 : Real64 RelAirVel = CalcRelativeAirVelocity(state.dataThermalComforts->AirVel, state.dataThermalComforts->ActMet);
1365 192 : Real64 PPD_AD = -1.0;
1366 192 : if (state.dataThermalComforts->ActMet < 1.3 && state.dataThermalComforts->CloUnit < 0.7 && RelAirVel < 0.2) {
1367 96 : Real64 AnkleAirVel = people.ankleAirVelocitySched->getCurrentVal();
1368 96 : Real64 PMV = CalcFangerPMV(state,
1369 96 : state.dataThermalComforts->AirTemp,
1370 96 : state.dataThermalComforts->RadTemp,
1371 96 : state.dataThermalComforts->RelHum,
1372 : RelAirVel,
1373 96 : state.dataThermalComforts->ActLevel,
1374 96 : state.dataThermalComforts->CloUnit,
1375 96 : state.dataThermalComforts->WorkEff);
1376 96 : PPD_AD = (std::exp(-2.58 + 3.05 * AnkleAirVel - 1.06 * PMV) / (1 + std::exp(-2.58 + 3.05 * AnkleAirVel - 1.06 * PMV))) * 100.0;
1377 :
1378 : } else {
1379 96 : if (state.dataGlobal->DisplayExtraWarnings) {
1380 0 : if (RelAirVel >= 0.2) {
1381 0 : ShowRecurringWarningErrorAtEnd(
1382 : state,
1383 : "Relative air velocity is above 0.2 m/s in Ankle draft PPD calculations. PPD at ankle draft will be set to -1.0.",
1384 0 : state.dataThermalComforts->AnkleDraftAirVelWarningInd,
1385 : RelAirVel,
1386 : RelAirVel,
1387 : _,
1388 : "[m/s]",
1389 : "[m/s]");
1390 : }
1391 0 : if (state.dataThermalComforts->ActMet >= 1.3) {
1392 0 : ShowRecurringWarningErrorAtEnd(
1393 : state,
1394 : "Metabolic rate is above 1.3 met in Ankle draft PPD calculations. PPD at ankle draft will be set to -1.0.",
1395 0 : state.dataThermalComforts->AnkleDraftActMetWarningInd,
1396 0 : state.dataThermalComforts->ActMet,
1397 0 : state.dataThermalComforts->ActMet,
1398 : _,
1399 : "[m/s]",
1400 : "[m/s]");
1401 : }
1402 0 : if (state.dataThermalComforts->CloUnit >= 0.7) {
1403 0 : ShowRecurringWarningErrorAtEnd(
1404 : state,
1405 : "Clothing unit is above 0.7 in Ankle draft PPD calculations. PPD at ankle draft will be set to -1.0.",
1406 0 : state.dataThermalComforts->AnkleDraftCloUnitWarningInd,
1407 0 : state.dataThermalComforts->CloUnit,
1408 0 : state.dataThermalComforts->CloUnit,
1409 : _,
1410 : "[m/s]",
1411 : "[m/s]");
1412 : }
1413 : }
1414 : }
1415 192 : comfort.AnkleDraftPPDASH55 = PPD_AD;
1416 : }
1417 192 : }
1418 :
1419 624 : void CalcThermalComfortKSU(EnergyPlusData &state)
1420 : {
1421 :
1422 : // SUBROUTINE INFORMATION:
1423 : // AUTHOR Jaewook Lee
1424 : // DATE WRITTEN January 2000
1425 : // MODIFIED Rick Strand (for E+ implementation February 2000)
1426 :
1427 : // PURPOSE OF THIS SUBROUTINE:
1428 : // This subroutine calculates TSV using the KSU 2 Node model.
1429 :
1430 : // METHODOLOGY EMPLOYED:
1431 : // This subroutine is based heavily upon the work performed by Dan Maloney for
1432 : // the BLAST program. Many of the equations are based on the original Pierce
1433 : // development. See documentation for further details and references.
1434 :
1435 : // REFERENCES:
1436 : // Maloney, Dan, M.S. Thesis, University of Illinois at Urbana-Champaign
1437 :
1438 : // SUBROUTINE PARAMETER DEFINITIONS:
1439 624 : Real64 constexpr CloEmiss(0.8); // Clothing Emissivity
1440 :
1441 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1442 : Real64 BodyWt; // Weight of body, kg
1443 : Real64 DayNum; // Number of days of acclimation
1444 : int NumDay; // Loop counter for DayNum
1445 : Real64 EmissAvg; // Average emissivity
1446 : int IncreDayNum; // Number of days of increment in the outputs as desired
1447 : Real64 IntHeatProdMet; // Internal heat production in MET
1448 : Real64 IntHeatProdMetMax; // Maximum value of internal heat production in MET
1449 : int LastDayNum; // Number of days for the last print out
1450 : Real64 SkinWetFac; // Skin wettedness factor
1451 : Real64 SkinWetNeut; // Skin wettedness at neutral state
1452 : int StartDayNum; // Number of days for the first print out
1453 : // Unacclimated man = 1, Acclimated man = 14
1454 : Real64 SweatSuppFac; // Sweat suppression factor due to skin wettedness
1455 : Real64 TempDiffer; // Temperature difference between the rectal and esophageal temperatures
1456 : // If not measured, set it to be 0.5 Deg. C.
1457 : int TempIndiceNum; // Number of temperature indices
1458 : Real64 ThermCndctMin; // Minimum value of thermal conductance
1459 : Real64 ThermCndctNeut; // Thermal conductance at neutral state
1460 : Real64 TimeExpos; // Time period in the exposure, hr
1461 : Real64 TimeInterval; // Time interval of outputs desired, hr
1462 : Real64 TSVMax; // Maximum value of thermal sensation vote
1463 : Real64 IntermediateClothing;
1464 :
1465 624 : TempIndiceNum = 2;
1466 :
1467 : // NEXT GROUP OF VARIABLE ARE FIXED FOR BLAST PROGRAM - UNACCLIMATED MAN
1468 : // THE TSV MODEL CAN BE APPLIED TO UNACCLIMATED MAN ONLY.
1469 624 : TimeInterval = 1.0;
1470 624 : TSVMax = 4.0;
1471 624 : StartDayNum = 1;
1472 624 : LastDayNum = 1;
1473 624 : IncreDayNum = 1;
1474 624 : TimeExpos = 1.0;
1475 624 : TempDiffer = 0.5;
1476 :
1477 2496 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1478 1872 : ++state.dataThermalComforts->PeopleNum) {
1479 : // THE NEXT SIX VARIABLES WILL BE READ IN FROM INPUT DECK
1480 1872 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1481 1872 : if (!people.KSU) {
1482 624 : continue;
1483 : }
1484 :
1485 1248 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1486 :
1487 1248 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
1488 1248 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum);
1489 :
1490 1248 : state.dataThermalComforts->AirTemp = thisZoneHB.ZTAVComf;
1491 1248 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
1492 0 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
1493 0 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
1494 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum); // PH 3/7/04
1495 : }
1496 : }
1497 1248 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
1498 2496 : state.dataThermalComforts->RelHum =
1499 1248 : PsyRhFnTdbWPb(state, state.dataThermalComforts->AirTemp, thisZoneHB.airHumRatAvgComf, state.dataEnvrn->OutBaroPress);
1500 1248 : state.dataThermalComforts->ActLevel = people.activityLevelSched->getCurrentVal() / BodySurfArea;
1501 1248 : state.dataThermalComforts->WorkEff = people.workEffSched->getCurrentVal() * state.dataThermalComforts->ActLevel;
1502 :
1503 1248 : switch (people.clothingType) {
1504 1152 : case DataHeatBalance::ClothingType::InsulationSchedule: {
1505 1152 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
1506 1152 : } break;
1507 48 : case DataHeatBalance::ClothingType::DynamicAshrae55: {
1508 48 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1509 48 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
1510 48 : DynamicClothingModel(state);
1511 48 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
1512 48 : } break;
1513 48 : case DataHeatBalance::ClothingType::CalculationSchedule: {
1514 48 : IntermediateClothing = people.clothingMethodSched->getCurrentVal();
1515 48 : if (IntermediateClothing == 1.0) {
1516 12 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
1517 12 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
1518 36 : } else if (IntermediateClothing == 2.0) {
1519 36 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1520 36 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
1521 36 : DynamicClothingModel(state);
1522 36 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
1523 : } else {
1524 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
1525 0 : ShowWarningError(
1526 0 : state, format("PEOPLE=\"{}\", Scheduled clothing value will be used rather than clothing calculation method.", people.Name));
1527 : }
1528 48 : } break;
1529 0 : default:
1530 0 : ShowSevereError(state, format("PEOPLE=\"{}\", Incorrect Clothing Type", people.Name));
1531 : }
1532 :
1533 1248 : state.dataThermalComforts->AirVel = people.airVelocitySched->getCurrentVal();
1534 1248 : state.dataThermalComforts->IntHeatProd = state.dataThermalComforts->ActLevel - state.dataThermalComforts->WorkEff;
1535 : // THE FOLLOWING ARE TYPICAL VALUES SET FOR BLAST RUNS
1536 : // STANDARD MAN: 70. KG WEIGHT, 1.8 M2 SURFACE AREA
1537 1248 : BodyWt = 70.0;
1538 1248 : state.dataThermalComforts->CoreTemp = 37.0;
1539 1248 : state.dataThermalComforts->SkinTemp = 31.0;
1540 :
1541 : // CALCULATIONS NEEDED FOR THE PASSIVE STATE EQUATIONS
1542 1248 : state.dataThermalComforts->CoreThermCap = 0.9 * BodyWt * 0.97 / BodySurfArea;
1543 1248 : state.dataThermalComforts->SkinThermCap = 0.1 * BodyWt * 0.97 / BodySurfArea;
1544 : // KERSLAKE'S FORMULA (0.05<AirVel<5. M/S)
1545 1248 : if (state.dataThermalComforts->AirVel < 0.137) {
1546 0 : state.dataThermalComforts->AirVel = 0.137;
1547 : }
1548 1248 : state.dataThermalComforts->Hc = 8.3 * std::sqrt(state.dataThermalComforts->AirVel);
1549 1248 : EmissAvg = RadSurfEff * CloEmiss + (1.0 - RadSurfEff) * 1.0;
1550 : // IBERALL EQUATION
1551 1248 : state.dataThermalComforts->Hr = EmissAvg * (3.87 + 0.031 * state.dataThermalComforts->RadTemp);
1552 1248 : state.dataThermalComforts->H = state.dataThermalComforts->Hr + state.dataThermalComforts->Hc;
1553 1248 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->Hc * state.dataThermalComforts->AirTemp +
1554 1248 : state.dataThermalComforts->Hr * state.dataThermalComforts->RadTemp) /
1555 1248 : state.dataThermalComforts->H;
1556 1248 : state.dataThermalComforts->VapPress = CalcSatVapPressFromTemp(state.dataThermalComforts->AirTemp);
1557 1248 : state.dataThermalComforts->VapPress *= state.dataThermalComforts->RelHum;
1558 1248 : state.dataThermalComforts->CloBodyRat = 1.0 + 0.2 * state.dataThermalComforts->CloUnit;
1559 2496 : state.dataThermalComforts->CloThermEff =
1560 1248 : 1.0 / (1.0 + 0.155 * state.dataThermalComforts->H * state.dataThermalComforts->CloBodyRat * state.dataThermalComforts->CloUnit);
1561 1248 : state.dataThermalComforts->CloPermeatEff = 1.0 / (1.0 + 0.143 * state.dataThermalComforts->Hc * state.dataThermalComforts->CloUnit);
1562 : // BASIC INFORMATION FOR THERMAL SENSATION.
1563 1248 : IntHeatProdMet = state.dataThermalComforts->IntHeatProd / ActLevelConv;
1564 1248 : IntHeatProdMetMax = max(1.0, IntHeatProdMet);
1565 1248 : ThermCndctNeut = 12.05 * std::exp(0.2266 * (IntHeatProdMetMax - 1.0));
1566 1248 : SkinWetNeut = 0.02 + 0.4 * (1.0 - std::exp(-0.6 * (IntHeatProdMetMax - 1.0)));
1567 1248 : ThermCndctMin = (ThermCndctNeut - 5.3) * 0.26074074 + 5.3;
1568 1248 : Real64 const ThemCndct_75_fac(1.0 / (75.0 - ThermCndctNeut));
1569 1248 : Real64 const ThemCndct_fac(1.0 / (ThermCndctNeut - ThermCndctMin));
1570 : // CALCULATE THE PHYSIOLOGICAL REACTIONS OF AN UNACCLIMATED
1571 : // MAN (LastDayNum = 1), OR AN ACCLIMATED MAN (LastDayNum = 14, IncreDayNum = 13),
1572 1248 : assert(IncreDayNum > 0); // Autodesk:F2C++ Loop setup assumption
1573 2496 : for (NumDay = StartDayNum; NumDay <= LastDayNum; NumDay += IncreDayNum) {
1574 : // INITIAL CONDITIONS IN AN EXPOSURE
1575 1248 : DayNum = double(NumDay);
1576 1248 : state.dataThermalComforts->Time = 0.0;
1577 1248 : state.dataThermalComforts->TimeChange = 0.01;
1578 1248 : SweatSuppFac = 1.0;
1579 1248 : state.dataThermalComforts->Temp(1) = state.dataThermalComforts->CoreTemp;
1580 1248 : state.dataThermalComforts->Temp(2) = state.dataThermalComforts->SkinTemp;
1581 1248 : state.dataThermalComforts->Coeff(1) = state.dataThermalComforts->Coeff(2) = 0.0;
1582 : // PHYSIOLOGICAL ADJUSTMENTS IN HEAT ACCLIMATION.
1583 1248 : state.dataThermalComforts->AcclPattern = 1.0 - std::exp(-0.12 * (DayNum - 1.0));
1584 1248 : state.dataThermalComforts->CoreTempNeut = 36.9 - 0.6 * state.dataThermalComforts->AcclPattern;
1585 1248 : state.dataThermalComforts->SkinTempNeut = 33.8 - 1.6 * state.dataThermalComforts->AcclPattern;
1586 1248 : state.dataThermalComforts->ActLevel -= 0.07 * state.dataThermalComforts->ActLevel * state.dataThermalComforts->AcclPattern;
1587 1248 : Real64 const SkinTempNeut_fac(1.0 / (1.0 - SkinWetNeut));
1588 : // CALCULATION OF CoreTempChange/TempChange & SkinTempChange/TempChange
1589 1248 : DERIV(state, TempIndiceNum, state.dataThermalComforts->Temp, state.dataThermalComforts->TempChange);
1590 : while (true) {
1591 : // CALCULATION OF THERMAL SENSATION VOTE (TSV).
1592 : // THE TSV MODEL CAN BE APPLIED TO UNACCLIMATED MAN ONLY.
1593 124800 : SkinWetFac = (state.dataThermalComforts->SkinWetSweat - SkinWetNeut) * SkinTempNeut_fac;
1594 124800 : state.dataThermalComforts->VasodilationFac = (state.dataThermalComforts->ThermCndct - ThermCndctNeut) * ThemCndct_75_fac;
1595 124800 : state.dataThermalComforts->VasoconstrictFac = (ThermCndctNeut - state.dataThermalComforts->ThermCndct) * ThemCndct_fac;
1596 : // IF VasodilationFac < 0.0, VASOCONSTRICTION OCCURS AND RESULTS IN COLD SENSATION.
1597 : // OTHERWISE NORMAL BLOOD FLOW OR VASODILATION OCCURS AND RESULTS IN
1598 : // THERMAL NEUTRALITY OR WARM SENSATION.
1599 124800 : if (state.dataThermalComforts->VasodilationFac < 0) {
1600 71095 : comfort.KsuTSV = -1.46153 * state.dataThermalComforts->VasoconstrictFac +
1601 71095 : 3.74721 * pow_2(state.dataThermalComforts->VasoconstrictFac) -
1602 71095 : 6.168856 * pow_3(state.dataThermalComforts->VasoconstrictFac);
1603 : } else {
1604 53705 : comfort.KsuTSV = (5.0 - 6.56 * (state.dataThermalComforts->RelHum - 0.50)) * SkinWetFac;
1605 53705 : if (comfort.KsuTSV > TSVMax) {
1606 0 : comfort.KsuTSV = TSVMax;
1607 : }
1608 : }
1609 :
1610 124800 : comfort.ThermalComfortMRT = state.dataThermalComforts->RadTemp;
1611 124800 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1612 :
1613 124800 : state.dataThermalComforts->CoreTemp = state.dataThermalComforts->Temp(1);
1614 124800 : state.dataThermalComforts->SkinTemp = state.dataThermalComforts->Temp(2);
1615 124800 : state.dataThermalComforts->EvapHeatLossSweatPrev = state.dataThermalComforts->EvapHeatLossSweat;
1616 :
1617 124800 : RKG(state,
1618 : TempIndiceNum,
1619 124800 : state.dataThermalComforts->TimeChange,
1620 124800 : state.dataThermalComforts->Time,
1621 124800 : state.dataThermalComforts->Temp,
1622 124800 : state.dataThermalComforts->TempChange,
1623 124800 : state.dataThermalComforts->Coeff);
1624 :
1625 124800 : if (state.dataThermalComforts->Time > TimeExpos) {
1626 1248 : break;
1627 : }
1628 : }
1629 : }
1630 : }
1631 624 : }
1632 :
1633 625248 : void DERIV(EnergyPlusData &state,
1634 : [[maybe_unused]] int &TempIndiceNum, // Number of temperature indices unused1208
1635 : [[maybe_unused]] Array1D<Real64> &Temp, // Temperature unused1208
1636 : Array1D<Real64> &TempChange // Change of temperature
1637 : )
1638 : {
1639 :
1640 : // SUBROUTINE INFORMATION:
1641 : // AUTHOR Jaewook Lee
1642 : // DATE WRITTEN January 2000
1643 : // MODIFIED Rick Strand (for E+ implementation February 2000)
1644 :
1645 : // PURPOSE OF THIS SUBROUTINE:
1646 : // THIS SUBROUTINE CALCULATES HEAT TRANSFER TERMS INVOLVED IN THE
1647 : // THERMOREGULATORY SYSTEM TO OBTAIN THE RATES OF CHANGE OF CoreTemp & SkinTemp
1648 : // VIZ., CoreTempChange/TempChange & SkinTempChange/TempChange RESPECTIVELY.
1649 :
1650 : // METHODOLOGY EMPLOYED:
1651 : // This subroutine is based heavily upon the work performed by Dan Maloney for
1652 : // the BLAST program. Many of the equations are based on the original Pierce
1653 : // development. See documentation for further details and references.
1654 :
1655 : // REFERENCES:
1656 : // Maloney, Dan, M.S. Thesis, University of Illinois at Urbana-Champaign
1657 :
1658 : // Argument array dimensioning
1659 : // EP_SIZE_CHECK(Temp, 2);
1660 625248 : EP_SIZE_CHECK(TempChange, 2);
1661 :
1662 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1663 : Real64 ActLevelTot; // Total activity level
1664 : Real64 CoreSignalShiv; // Core signal when shivering occurs
1665 : Real64 CoreSignalShivMax; // Maximum value of core signal when shivering occurs
1666 : Real64 CoreSignalSkinSens; // The sensitivity of the skin signal increases
1667 : Real64 CoreSignalSweatMax; // Maximum value of core signal when sweating occurs
1668 : Real64 CoreSignalSweatWarm; // Core signal when sweating occurs
1669 : Real64 CoreTempSweat; // Core temperature when sweating occurs
1670 : Real64 CoreSignalWarm; // Warm core signal
1671 : Real64 CoreSignalWarmMax; // Maximum value of warm core signal
1672 : Real64 EvapHeatLossDrySweat; // Evaporative heat loss by sweating when total skin wettedness < 0.4
1673 : Real64 Err; // Stop criteria for iteration
1674 : Real64 ErrPrev; // Previous value of stop criteria for iteration
1675 : Real64 EvapHeatLossSweatEst; // Estimated evaporative heat loss by sweating
1676 : Real64 EvapHeatLossSweatEstNew; // New value of estimated evaporative heat loss by sweating
1677 : Real64 IntHeatProdTot; // Total internal heat production
1678 : Real64 SkinCndctMax; // Maximum value of skin conductance
1679 : Real64 SkinSignalCold; // Cold skin signal
1680 : Real64 SkinSignalColdMax; // Maximum value of cold skin signal
1681 : Real64 SkinSignalSweatCold; // Cold skin signal for sweat inhibition
1682 : Real64 SkinSignalSweatColdMax; // Maximum value of cold skin signal for sweat inhibition
1683 : Real64 SkinCndctDilation; // Overall skin conductance due to vasodilation
1684 : Real64 SkinCndctConstriction; // Overall skin conductance due to vasoconstriction
1685 : Real64 SkinSignalShiv; // Skin signal when shivering occurs
1686 : Real64 SkinSignalShivMax; // Maximum value of skin signal when shivering occurs
1687 : Real64 SkinSignalSweatMax; // Skin signal when sweating occurs
1688 : Real64 SkinSignalSweatWarm; // Maximum value of skin signal when sweating occurs
1689 : Real64 SkinSignalWarm; // Warm skin signal
1690 : Real64 SkinSignalWarmMax; // Maximum value of warm skin signal
1691 : Real64 SkinTempSweat; // Skin temperature when sweating occurs
1692 : Real64 SkinWetSignal; // Skin wettedness signal
1693 : Real64 SweatCtrlFac; // Sweat control factor
1694 : Real64 SweatSuppFac; // Sweat suppression factor due to skin wettedness
1695 : Real64 WeighFac; // Weighting factor of core signal
1696 :
1697 : // THE CONTROLLING SYSTEM.
1698 : // THE CONTROLLING SIGNALS :
1699 : // SIGNALS FOR KS.
1700 625248 : CoreSignalWarm = state.dataThermalComforts->CoreTemp - 36.98;
1701 625248 : SkinSignalWarm = state.dataThermalComforts->SkinTemp - 33.8;
1702 625248 : SkinSignalCold = 32.1 - state.dataThermalComforts->SkinTemp;
1703 625248 : CoreSignalSkinSens = state.dataThermalComforts->CoreTemp - 35.15;
1704 625248 : CoreSignalWarmMax = max(0.0, CoreSignalWarm);
1705 625248 : SkinSignalWarmMax = max(0.0, SkinSignalWarm);
1706 625248 : SkinSignalColdMax = max(0.0, SkinSignalCold);
1707 :
1708 : // SIGNALS FOR EvapHeatLossSweat.
1709 625248 : CoreTempSweat = state.dataThermalComforts->CoreTemp;
1710 625248 : if (CoreTempSweat > 38.29) {
1711 0 : CoreTempSweat = 38.29;
1712 : }
1713 625248 : CoreSignalSweatWarm = CoreTempSweat - state.dataThermalComforts->CoreTempNeut;
1714 625248 : SkinTempSweat = state.dataThermalComforts->SkinTemp;
1715 625248 : if (SkinTempSweat > 36.1) {
1716 0 : SkinTempSweat = 36.1;
1717 : }
1718 625248 : SkinSignalSweatWarm = SkinTempSweat - state.dataThermalComforts->SkinTempNeut;
1719 625248 : CoreSignalSweatMax = max(0.0, CoreSignalSweatWarm);
1720 625248 : SkinSignalSweatMax = max(0.0, SkinSignalSweatWarm);
1721 625248 : SkinSignalSweatCold = 33.37 - state.dataThermalComforts->SkinTemp;
1722 625248 : if (state.dataThermalComforts->SkinTempNeut < 33.37) {
1723 0 : SkinSignalSweatCold = state.dataThermalComforts->SkinTempNeut - state.dataThermalComforts->SkinTemp;
1724 : }
1725 625248 : SkinSignalSweatColdMax = max(0.0, SkinSignalSweatCold);
1726 :
1727 : // SIGNALS FOR SHIVERING.
1728 625248 : CoreSignalShiv = 36.9 - state.dataThermalComforts->CoreTemp;
1729 625248 : SkinSignalShiv = 32.5 - state.dataThermalComforts->SkinTemp;
1730 625248 : CoreSignalShivMax = max(0.0, CoreSignalShiv);
1731 625248 : SkinSignalShivMax = max(0.0, SkinSignalShiv);
1732 :
1733 : // CONTROLLING FUNCTIONS :
1734 : // SHIVERING RESPONSE IN W/M**2.
1735 625248 : state.dataThermalComforts->ShivResponse = 20.0 * CoreSignalShivMax * SkinSignalShivMax + 5.0 * SkinSignalShivMax;
1736 625248 : if (state.dataThermalComforts->CoreTemp >= 37.1) {
1737 26930 : state.dataThermalComforts->ShivResponse = 0.0;
1738 : }
1739 :
1740 : // SWEAT FUNCTION IN W/M**2.
1741 625248 : WeighFac = 260.0 + 70.0 * state.dataThermalComforts->AcclPattern;
1742 625248 : SweatCtrlFac = 1.0 + 0.05 * std::pow(SkinSignalSweatColdMax, 2.4);
1743 :
1744 : // EvapHeatLossDrySweat = SWEAT WHEN SkinWetTot < 0.4.
1745 625248 : EvapHeatLossDrySweat =
1746 625248 : ((WeighFac * CoreSignalSweatMax + 0.1 * WeighFac * SkinSignalSweatMax) * std::exp(SkinSignalSweatMax / 8.5)) / SweatCtrlFac;
1747 :
1748 : // MAXIMUM EVAPORATIVE POWER, EvapHeatLossMax, IN W/M**2.
1749 625248 : state.dataThermalComforts->SkinVapPress = CalcSatVapPressFromTemp(state.dataThermalComforts->SkinTemp);
1750 625248 : state.dataThermalComforts->EvapHeatLossMax = 2.2 * state.dataThermalComforts->Hc *
1751 625248 : (state.dataThermalComforts->SkinVapPress - state.dataThermalComforts->VapPress) *
1752 625248 : state.dataThermalComforts->CloPermeatEff;
1753 625248 : if (state.dataThermalComforts->EvapHeatLossMax > 0.0) {
1754 625248 : state.dataThermalComforts->SkinWetSweat = EvapHeatLossDrySweat / state.dataThermalComforts->EvapHeatLossMax;
1755 625248 : state.dataThermalComforts->EvapHeatLossDiff = 0.408 * (state.dataThermalComforts->SkinVapPress - state.dataThermalComforts->VapPress);
1756 625248 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->SkinWetSweat * state.dataThermalComforts->EvapHeatLossMax +
1757 625248 : (1.0 - state.dataThermalComforts->SkinWetSweat) * state.dataThermalComforts->EvapHeatLossDiff;
1758 625248 : state.dataThermalComforts->SkinWetTot = state.dataThermalComforts->EvapHeatLoss / state.dataThermalComforts->EvapHeatLossMax;
1759 625248 : if (state.dataThermalComforts->Time == 0.0) {
1760 2496 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossDrySweat;
1761 2496 : state.dataThermalComforts->EvapHeatLossSweatPrev = EvapHeatLossDrySweat;
1762 : }
1763 625248 : if (state.dataThermalComforts->SkinWetTot > 0.4) {
1764 :
1765 : // ITERATION FOR SWEAT WHEN SkinWetTot IS GREATER THAT 0.4.
1766 62255 : state.dataThermalComforts->IterNum = 0;
1767 62255 : if (state.dataThermalComforts->SkinWetSweat > 1.0) {
1768 0 : state.dataThermalComforts->SkinWetSweat = 1.0;
1769 : }
1770 : while (true) {
1771 92470 : EvapHeatLossSweatEst = state.dataThermalComforts->EvapHeatLossSweatPrev;
1772 92470 : state.dataThermalComforts->SkinWetSweat = EvapHeatLossSweatEst / state.dataThermalComforts->EvapHeatLossMax;
1773 :
1774 92470 : if (state.dataThermalComforts->SkinWetSweat > 1.0) {
1775 0 : state.dataThermalComforts->SkinWetSweat = 1.0;
1776 : }
1777 :
1778 184940 : state.dataThermalComforts->EvapHeatLossDiff =
1779 92470 : 0.408 * (state.dataThermalComforts->SkinVapPress - state.dataThermalComforts->VapPress);
1780 184940 : state.dataThermalComforts->EvapHeatLoss =
1781 92470 : (1.0 - state.dataThermalComforts->SkinWetTot) * state.dataThermalComforts->EvapHeatLossDiff +
1782 92470 : state.dataThermalComforts->EvapHeatLossSweat;
1783 92470 : state.dataThermalComforts->SkinWetTot = state.dataThermalComforts->EvapHeatLoss / state.dataThermalComforts->EvapHeatLossMax;
1784 :
1785 92470 : if (state.dataThermalComforts->SkinWetTot > 1.0) {
1786 0 : state.dataThermalComforts->SkinWetTot = 1.0;
1787 : }
1788 :
1789 92470 : SkinWetSignal = max(0.0, state.dataThermalComforts->SkinWetTot - 0.4);
1790 92470 : SweatSuppFac = 0.5 + 0.5 * std::exp(-5.6 * SkinWetSignal);
1791 92470 : EvapHeatLossSweatEstNew = SweatSuppFac * EvapHeatLossDrySweat;
1792 :
1793 92470 : if (state.dataThermalComforts->IterNum == 0) {
1794 62255 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossSweatEstNew;
1795 : }
1796 :
1797 92470 : Err = EvapHeatLossSweatEst - EvapHeatLossSweatEstNew;
1798 :
1799 92470 : if (state.dataThermalComforts->IterNum != 0) {
1800 30215 : if ((ErrPrev * Err) < 0.0) {
1801 30138 : state.dataThermalComforts->EvapHeatLossSweat = (EvapHeatLossSweatEst + EvapHeatLossSweatEstNew) / 2.0;
1802 : }
1803 30215 : if ((ErrPrev * Err) >= 0.0) {
1804 77 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossSweatEstNew;
1805 : }
1806 : }
1807 :
1808 : // STOP CRITERION FOR THE ITERATION.
1809 92470 : if ((std::abs(Err) <= 0.5) || (state.dataThermalComforts->IterNum >= 10)) {
1810 62255 : break;
1811 : }
1812 30215 : ++state.dataThermalComforts->IterNum;
1813 30215 : state.dataThermalComforts->EvapHeatLossSweatPrev = state.dataThermalComforts->EvapHeatLossSweat;
1814 30215 : ErrPrev = Err;
1815 : }
1816 :
1817 : } else {
1818 562993 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossDrySweat;
1819 : }
1820 :
1821 : } else {
1822 0 : state.dataThermalComforts->SkinWetSweat = 1.0;
1823 0 : state.dataThermalComforts->SkinWetTot = 1.0;
1824 0 : state.dataThermalComforts->EvapHeatLossSweat = 0.5 * EvapHeatLossDrySweat;
1825 0 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->EvapHeatLossSweat;
1826 : }
1827 :
1828 : // OVERALL SKIN CONDUCTANCE, KS, IN W/M**2/C.
1829 : // SkinCndctDilation = EFFECT DUE TO VASODILATION.
1830 : // SkinCndctConstriction = EFFECT DUE TO VASOCONSTRICTION.
1831 625248 : SkinCndctDilation = 42.45 * CoreSignalWarmMax + 8.15 * std::pow(CoreSignalSkinSens, 0.8) * SkinSignalWarmMax;
1832 625248 : SkinCndctConstriction = 1.0 + 0.4 * SkinSignalColdMax;
1833 : // ThermCndct IS EQUIVALENT TO KS
1834 625248 : state.dataThermalComforts->ThermCndct = 5.3 + (6.75 + SkinCndctDilation) / SkinCndctConstriction;
1835 625248 : SkinCndctMax = 75.0 + 10.0 * state.dataThermalComforts->AcclPattern;
1836 625248 : if (state.dataThermalComforts->ThermCndct > SkinCndctMax) {
1837 0 : state.dataThermalComforts->ThermCndct = SkinCndctMax;
1838 : }
1839 :
1840 : // PASSIVE ENERGY BALANCE EQUATIONS.
1841 : // TOTAL METABOLIC HEAT PRODUCTION RATE, ActLevel, IN W/M**2.
1842 625248 : ActLevelTot = state.dataThermalComforts->ActLevel + state.dataThermalComforts->ShivResponse;
1843 625248 : IntHeatProdTot = ActLevelTot - state.dataThermalComforts->WorkEff;
1844 : // RESPIRATION HEAT LOSS, RespHeatLoss, IN W/M**0.
1845 625248 : state.dataThermalComforts->LatRespHeatLoss = 0.0023 * ActLevelTot * (44.0 - state.dataThermalComforts->VapPress);
1846 625248 : state.dataThermalComforts->DryRespHeatLoss = 0.0014 * ActLevelTot * (34.0 - state.dataThermalComforts->AirTemp);
1847 625248 : state.dataThermalComforts->RespHeatLoss = state.dataThermalComforts->LatRespHeatLoss + state.dataThermalComforts->DryRespHeatLoss;
1848 : // HEAT FLOW FROM CORE TO SKIN, HeatFlow, IN W/M**2.
1849 1250496 : state.dataThermalComforts->HeatFlow =
1850 625248 : state.dataThermalComforts->ThermCndct * (state.dataThermalComforts->CoreTemp - state.dataThermalComforts->SkinTemp);
1851 : // TempChange(1) = CoreTempChange/TempChange, IN C/HR.
1852 625248 : TempChange(1) = (IntHeatProdTot - state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->HeatFlow) /
1853 625248 : state.dataThermalComforts->CoreThermCap;
1854 625248 : if (state.dataThermalComforts->EvapHeatLoss > state.dataThermalComforts->EvapHeatLossMax) {
1855 0 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->EvapHeatLossMax;
1856 : }
1857 :
1858 : // DRY HEAT EXCHANGE BY RADIATION & CONVECTION, R+C, IN W/M**2.
1859 625248 : state.dataThermalComforts->DryHeatLoss = state.dataThermalComforts->H * state.dataThermalComforts->CloBodyRat *
1860 625248 : state.dataThermalComforts->CloThermEff *
1861 625248 : (state.dataThermalComforts->SkinTemp - state.dataThermalComforts->OpTemp);
1862 : // TempChange(2) = SkinTempChange/TempChange, IN C/HR.
1863 625248 : TempChange(2) = (state.dataThermalComforts->HeatFlow - state.dataThermalComforts->EvapHeatLoss - state.dataThermalComforts->DryHeatLoss) /
1864 625248 : state.dataThermalComforts->SkinThermCap;
1865 625248 : }
1866 :
1867 124800 : void RKG(EnergyPlusData &state, int &NEQ, Real64 const H, Real64 &X, Array1D<Real64> &Y, Array1D<Real64> &DY, Array1D<Real64> &C)
1868 : {
1869 :
1870 : // SUBROUTINE INFORMATION:
1871 : // AUTHOR Jaewook Lee
1872 : // DATE WRITTEN January 2000
1873 : // MODIFIED Rick Strand (for E+ implementation February 2000)
1874 :
1875 : // PURPOSE OF THIS SUBROUTINE:
1876 : // This is a subroutine for integration by Runga-Kutta's method.
1877 :
1878 : // METHODOLOGY EMPLOYED:
1879 : // This subroutine is based heavily upon the work performed by Dan Maloney for
1880 : // the BLAST program. Many of the equations are based on the original Pierce
1881 : // development. See documentation for further details and references.
1882 :
1883 : // REFERENCES:
1884 : // Maloney, Dan, M.S. Thesis, University of Illinois at Urbana-Champaign
1885 :
1886 : // Argument array dimensioning
1887 124800 : EP_SIZE_CHECK(Y, NEQ);
1888 124800 : EP_SIZE_CHECK(DY, NEQ);
1889 124800 : EP_SIZE_CHECK(C, NEQ);
1890 :
1891 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1892 : int I;
1893 : int J;
1894 : Real64 B;
1895 : Real64 H2;
1896 : static constexpr std::array<Real64, 2> A = {0.29289321881345, 1.70710678118654};
1897 :
1898 124800 : H2 = 0.5 * H;
1899 :
1900 124800 : DERIV(state, NEQ, Y, DY);
1901 374400 : for (I = 1; I <= NEQ; ++I) {
1902 249600 : B = H2 * DY(I) - C(I);
1903 249600 : Y(I) += B;
1904 249600 : C(I) += 3.0 * B - H2 * DY(I);
1905 : }
1906 :
1907 124800 : X += H2;
1908 :
1909 374400 : for (J = 0; J < 2; ++J) {
1910 249600 : DERIV(state, NEQ, Y, DY);
1911 748800 : for (I = 1; I <= NEQ; ++I) {
1912 499200 : B = A[J] * (H * DY(I) - C(I));
1913 499200 : Y(I) += B;
1914 499200 : C(I) += 3.0 * B - A[J] * H * DY(I);
1915 : }
1916 : }
1917 :
1918 124800 : X += H2;
1919 124800 : DERIV(state, NEQ, Y, DY);
1920 :
1921 374400 : for (I = 1; I <= NEQ; ++I) {
1922 249600 : B = (H * DY(I) - 2.0 * C(I)) / 6.0;
1923 249600 : Y(I) += B;
1924 249600 : C(I) += 3.0 * B - H2 * DY(I);
1925 : }
1926 :
1927 124800 : DERIV(state, NEQ, Y, DY);
1928 124800 : }
1929 :
1930 801 : void GetAngleFactorList(EnergyPlusData &state)
1931 : {
1932 :
1933 : // SUBROUTINE INFORMATION:
1934 : // AUTHOR Jaewook Lee
1935 : // DATE WRITTEN July 2001
1936 :
1937 : static constexpr std::string_view routineName("GetAngleFactorList: "); // include trailing blank space
1938 801 : Real64 constexpr AngleFacLimit(0.01); // To set the limit of sum of angle factors
1939 :
1940 801 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
1941 : int IOStatus;
1942 : int NumAlphas; // Number of Alphas from InputProcessor
1943 : int NumNumbers; // Number of Numbers from Input Processor
1944 801 : auto &cCurrentModuleObject = state.dataIPShortCut->cCurrentModuleObject;
1945 :
1946 801 : cCurrentModuleObject = "ComfortViewFactorAngles";
1947 801 : int NumOfAngleFactorLists = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCurrentModuleObject);
1948 801 : state.dataThermalComforts->AngleFactorList.allocate(NumOfAngleFactorLists);
1949 :
1950 804 : for (int Item = 1; Item <= NumOfAngleFactorLists; ++Item) {
1951 :
1952 3 : Real64 AllAngleFacSummed = 0.0; // Sum of angle factors in each zone
1953 3 : auto &thisAngFacList(state.dataThermalComforts->AngleFactorList(Item));
1954 :
1955 6 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
1956 : cCurrentModuleObject,
1957 : Item,
1958 3 : state.dataIPShortCut->cAlphaArgs,
1959 : NumAlphas,
1960 3 : state.dataIPShortCut->rNumericArgs,
1961 : NumNumbers,
1962 : IOStatus,
1963 3 : state.dataIPShortCut->lNumericFieldBlanks,
1964 3 : state.dataIPShortCut->lAlphaFieldBlanks,
1965 3 : state.dataIPShortCut->cAlphaFieldNames,
1966 3 : state.dataIPShortCut->cNumericFieldNames);
1967 :
1968 3 : thisAngFacList.Name = state.dataIPShortCut->cAlphaArgs(1); // no need for verification/uniqueness.
1969 :
1970 3 : thisAngFacList.TotAngleFacSurfaces = NumNumbers;
1971 3 : thisAngFacList.SurfaceName.allocate(thisAngFacList.TotAngleFacSurfaces);
1972 3 : thisAngFacList.SurfacePtr.allocate(thisAngFacList.TotAngleFacSurfaces);
1973 3 : thisAngFacList.AngleFactor.allocate(thisAngFacList.TotAngleFacSurfaces);
1974 :
1975 21 : for (int SurfNum = 1; SurfNum <= thisAngFacList.TotAngleFacSurfaces; ++SurfNum) {
1976 18 : thisAngFacList.SurfaceName(SurfNum) = state.dataIPShortCut->cAlphaArgs(SurfNum + 1);
1977 18 : thisAngFacList.SurfacePtr(SurfNum) = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(SurfNum + 1), state.dataSurface->Surface);
1978 18 : thisAngFacList.AngleFactor(SurfNum) = state.dataIPShortCut->rNumericArgs(SurfNum);
1979 : // Error trap for surfaces that do not exist or surfaces not in the zone
1980 18 : if (thisAngFacList.SurfacePtr(SurfNum) == 0) {
1981 0 : ShowSevereError(state,
1982 0 : format("{}: invalid {}, entered value={}",
1983 : cCurrentModuleObject,
1984 0 : state.dataIPShortCut->cAlphaFieldNames(SurfNum + 1),
1985 0 : state.dataIPShortCut->cAlphaArgs(SurfNum + 1)));
1986 0 : ShowContinueError(state,
1987 0 : format("ref {}={} not found in {}={}",
1988 0 : state.dataIPShortCut->cAlphaFieldNames(1),
1989 0 : state.dataIPShortCut->cAlphaArgs(1),
1990 0 : state.dataIPShortCut->cAlphaFieldNames(2),
1991 0 : state.dataIPShortCut->cAlphaArgs(2)));
1992 0 : ErrorsFound = true;
1993 : } else {
1994 : // Found Surface, is it in same enclosure?
1995 18 : auto &thisSurf = state.dataSurface->Surface(thisAngFacList.SurfacePtr(SurfNum));
1996 18 : if (SurfNum == 1) {
1997 3 : thisAngFacList.EnclosurePtr = thisSurf.RadEnclIndex; // Save enclosure num of first surface
1998 : }
1999 18 : if (thisAngFacList.EnclosurePtr != thisSurf.RadEnclIndex) {
2000 0 : ShowWarningError(state,
2001 0 : format("{}: For {}=\"{}\", surfaces are not all in the same radiant enclosure.",
2002 : routineName,
2003 : cCurrentModuleObject,
2004 0 : thisAngFacList.Name));
2005 0 : ShowContinueError(state,
2006 0 : format("... Surface=\"{}\" is in enclosure=\"{}\"",
2007 0 : state.dataSurface->Surface(thisAngFacList.SurfacePtr(1)).Name,
2008 0 : state.dataViewFactor->EnclRadInfo(thisAngFacList.EnclosurePtr).Name));
2009 0 : ShowContinueError(state,
2010 0 : format("... Surface=\"{}\" is in enclosure=\"{}\"",
2011 0 : thisSurf.Name,
2012 0 : state.dataViewFactor->EnclRadInfo(thisSurf.RadEnclIndex).Name));
2013 : }
2014 : }
2015 :
2016 18 : AllAngleFacSummed += thisAngFacList.AngleFactor(SurfNum);
2017 : }
2018 :
2019 3 : if (std::abs(AllAngleFacSummed - 1.0) > AngleFacLimit) {
2020 0 : ShowSevereError(state, format("{}=\"{}\", invalid - Sum[AngleFactors]", cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
2021 0 : ShowContinueError(state,
2022 0 : format("...Sum of Angle Factors [{:.3R}] should not deviate from expected sum [1.0] by more than limit [{:.3R}].",
2023 : AllAngleFacSummed,
2024 : AngleFacLimit));
2025 0 : ErrorsFound = true;
2026 : }
2027 : }
2028 :
2029 801 : if (ErrorsFound) {
2030 0 : ShowFatalError(state, "GetAngleFactorList: Program terminated due to preceding errors.");
2031 : }
2032 :
2033 5039 : for (int Item = 1; Item <= state.dataHeatBal->TotPeople; ++Item) {
2034 4238 : auto &thisPeople = state.dataHeatBal->People(Item);
2035 4238 : if (thisPeople.MRTCalcType != DataHeatBalance::CalcMRT::AngleFactor) {
2036 4235 : continue;
2037 : }
2038 3 : thisPeople.AngleFactorListPtr = Util::FindItemInList(thisPeople.AngleFactorListName, state.dataThermalComforts->AngleFactorList);
2039 3 : int WhichAFList = thisPeople.AngleFactorListPtr;
2040 3 : if (WhichAFList == 0 && (thisPeople.Fanger || thisPeople.Pierce || thisPeople.KSU)) {
2041 0 : ShowSevereError(state, format("{}{}=\"{}\", invalid", routineName, cCurrentModuleObject, thisPeople.AngleFactorListName));
2042 0 : ShowContinueError(state, format("... Angle Factor List Name not found for PEOPLE=\"{}\"", thisPeople.Name));
2043 0 : ErrorsFound = true;
2044 : } else {
2045 3 : auto &thisAngFacList = state.dataThermalComforts->AngleFactorList(WhichAFList);
2046 3 : if (state.dataHeatBal->space(thisPeople.spaceIndex).radiantEnclosureNum != thisAngFacList.EnclosurePtr &&
2047 0 : (thisPeople.Fanger || thisPeople.Pierce || thisPeople.KSU)) {
2048 0 : ShowWarningError(state,
2049 0 : format("{}{}=\"{}\", radiant enclosure mismatch.", routineName, cCurrentModuleObject, thisAngFacList.Name));
2050 0 : ShowContinueError(
2051 : state,
2052 0 : format("...Enclosure=\"{}\" doe not match enclosure=\"{}\" for PEOPLE=\"{}\"",
2053 0 : state.dataViewFactor->EnclRadInfo(thisAngFacList.EnclosurePtr).Name,
2054 0 : state.dataViewFactor->EnclRadInfo(state.dataHeatBal->space(thisPeople.spaceIndex).radiantEnclosureNum).Name,
2055 0 : thisPeople.Name));
2056 : }
2057 : }
2058 : }
2059 :
2060 801 : if (ErrorsFound) {
2061 0 : ShowFatalError(state, "GetAngleFactorList: Program terminated due to preceding errors.");
2062 : }
2063 801 : }
2064 :
2065 864 : Real64 CalcAngleFactorMRT(EnergyPlusData &state, int const AngleFacNum)
2066 : {
2067 :
2068 : // SUBROUTINE INFORMATION:
2069 : // AUTHOR Jaewook Lee
2070 : // DATE WRITTEN July 2001
2071 : // MODIFIED November 2017 (R Strand): Added fourth power and emissivity to calculation
2072 :
2073 : // Return value
2074 : Real64 CalcAngleFactorMRT;
2075 :
2076 864 : Real64 SurfTempEmissAngleFacSummed = 0.0;
2077 864 : Real64 SumSurfaceEmissAngleFactor = 0.0;
2078 :
2079 864 : auto &thisAngFacList(state.dataThermalComforts->AngleFactorList(AngleFacNum));
2080 :
2081 6048 : for (int SurfNum = 1; SurfNum <= thisAngFacList.TotAngleFacSurfaces; ++SurfNum) {
2082 5184 : Real64 SurfaceTemp = state.dataHeatBalSurf->SurfInsideTempHist(1)(thisAngFacList.SurfacePtr(SurfNum)) + Constant::Kelvin;
2083 : Real64 SurfEAF =
2084 5184 : state.dataConstruction->Construct(state.dataSurface->Surface(thisAngFacList.SurfacePtr(SurfNum)).Construction).InsideAbsorpThermal *
2085 5184 : thisAngFacList.AngleFactor(SurfNum);
2086 5184 : SurfTempEmissAngleFacSummed += SurfEAF * pow_4(SurfaceTemp);
2087 5184 : SumSurfaceEmissAngleFactor += SurfEAF;
2088 : }
2089 :
2090 864 : CalcAngleFactorMRT = root_4(SurfTempEmissAngleFacSummed / SumSurfaceEmissAngleFactor) - Constant::Kelvin;
2091 :
2092 864 : return CalcAngleFactorMRT;
2093 : }
2094 :
2095 22272 : Real64 CalcSurfaceWeightedMRT(EnergyPlusData &state, int const SurfNum, bool AverageWithSurface)
2096 : {
2097 :
2098 : // Purpose: Calculate a modified zone MRT that excludes the Surface( SurfNum ).
2099 : // This is necessary for the surface weighted option to not in essence
2100 : // double count SurfNum in the MRT calculation when averaged with the Surface( SurfNum ).
2101 : // Other than that, the method here is the same as CalculateZoneMRT. Once a modified zone
2102 : // MRT is calculated, the subroutine then calculates and returns the
2103 : // RadTemp (radiant temperature) for use by the thermal comfort routines
2104 : // that is the average of the surface temperature to be weighted and
2105 : // the modified zone MRT.
2106 :
2107 : // Return value
2108 22272 : Real64 CalcSurfaceWeightedMRT = 0.0;
2109 :
2110 : // Initialize ZoneAESum for all zones and SurfaceAE for all surfaces at the start of the simulation
2111 22272 : if (state.dataThermalComforts->FirstTimeSurfaceWeightedFlag) {
2112 12 : state.dataThermalComforts->FirstTimeError = true;
2113 12 : state.dataThermalComforts->FirstTimeSurfaceWeightedFlag = false;
2114 76 : for (auto const &thisRadEnclosure : state.dataViewFactor->EnclRadInfo) {
2115 636 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2116 572 : auto &thisSurface2 = state.dataSurface->Surface(SurfNum2);
2117 572 : thisSurface2.AE = thisSurface2.Area * state.dataConstruction->Construct(thisSurface2.Construction).InsideAbsorpThermal;
2118 : }
2119 : // Do NOT include the contribution of the Surface that is being surface weighted in this calculation since it will already be
2120 : // accounted for
2121 636 : for (int const SurfNum1 : thisRadEnclosure.SurfacePtr) {
2122 572 : auto &thisSurface1 = state.dataSurface->Surface(SurfNum1);
2123 572 : thisSurface1.enclAESum = 0.0;
2124 6488 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2125 5916 : if (SurfNum2 == SurfNum1) {
2126 572 : continue;
2127 : }
2128 5344 : auto &thisSurface2 = state.dataSurface->Surface(SurfNum2);
2129 5344 : thisSurface1.enclAESum += thisSurface2.AE;
2130 : }
2131 : }
2132 12 : }
2133 : }
2134 :
2135 : // Calculate the sum of area*emissivity and area*emissivity*temperature for all surfaces in the zone EXCEPT the surface being weighted
2136 22272 : Real64 sumAET = 0.0; // Intermediate calculational variable (area*emissivity*T) sum
2137 :
2138 22272 : auto &thisSurface = state.dataSurface->Surface(SurfNum);
2139 22272 : auto &thisRadEnclosure = state.dataViewFactor->EnclRadInfo(thisSurface.RadEnclIndex);
2140 : // Recalc SurfaceEnclAESum only if needed due to window shades or EMS
2141 22272 : if (thisRadEnclosure.radReCalc) {
2142 0 : thisSurface.enclAESum = 0.0;
2143 0 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2144 0 : if (SurfNum2 == SurfNum) {
2145 0 : continue;
2146 : }
2147 0 : auto &thisSurface2 = state.dataSurface->Surface(SurfNum2);
2148 0 : thisSurface2.AE = thisSurface2.Area * state.dataConstruction->Construct(thisSurface2.Construction).InsideAbsorpThermal;
2149 0 : thisSurface.enclAESum += thisSurface2.AE;
2150 : }
2151 : }
2152 503328 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2153 481056 : if (SurfNum2 == SurfNum) {
2154 22272 : continue;
2155 : }
2156 458784 : sumAET += state.dataSurface->Surface(SurfNum2).AE * state.dataHeatBalSurf->SurfInsideTempHist(1)(SurfNum2);
2157 : }
2158 :
2159 : // Now weight the MRT
2160 22272 : auto &thisSurfaceTemp = state.dataHeatBalSurf->SurfInsideTempHist(1)(SurfNum);
2161 22272 : if (thisSurface.enclAESum > 0.01) {
2162 22272 : CalcSurfaceWeightedMRT = sumAET / thisSurface.enclAESum;
2163 : // if averaged with surface--half comes from the surface used for weighting (SurfNum) and the rest from the calculated MRT that excludes
2164 : // this surface
2165 22272 : if (AverageWithSurface) {
2166 2016 : CalcSurfaceWeightedMRT = 0.5 * (thisSurfaceTemp + CalcSurfaceWeightedMRT);
2167 : }
2168 : } else {
2169 0 : if (state.dataThermalComforts->FirstTimeError) {
2170 0 : int spaceNum = thisSurface.spaceNum;
2171 0 : ShowWarningError(state,
2172 0 : format("CalcSurfaceWeightedMRT: Areas*Inside surface emissivities are summing to zero for Enclosure=\"{}\"",
2173 0 : thisRadEnclosure.Name));
2174 0 : ShowContinueError(state,
2175 0 : format("As a result, the MAT for Space={} will be used for MRT when calculating the surface weighted MRT.",
2176 0 : state.dataHeatBal->space(spaceNum).Name));
2177 0 : ShowContinueError(state, format("for Surface={}", thisSurface.Name));
2178 0 : state.dataThermalComforts->FirstTimeError = false;
2179 0 : CalcSurfaceWeightedMRT = state.dataZoneTempPredictorCorrector->spaceHeatBalance(spaceNum).MAT;
2180 0 : if (AverageWithSurface) {
2181 0 : CalcSurfaceWeightedMRT = 0.5 * (thisSurfaceTemp + CalcSurfaceWeightedMRT);
2182 : }
2183 : }
2184 : }
2185 :
2186 22272 : return CalcSurfaceWeightedMRT;
2187 : }
2188 :
2189 626496 : Real64 CalcSatVapPressFromTemp(Real64 const Temp)
2190 : {
2191 :
2192 : // FUNCTION INFORMATION:
2193 : // AUTHOR Jaewook Lee
2194 : // DATE WRITTEN January 2000
2195 : // MODIFIED Rick Strand (for E+ implementation February 2000)
2196 :
2197 : // PURPOSE OF THIS FUNCTION:
2198 : // THIS IS A FUNCTION TO CALCULATE THE SATURATED VAPOR PRESSURE
2199 : // FROM AIR TEMPERATURE
2200 :
2201 : // METHODOLOGY EMPLOYED:
2202 : // This function is based upon the work performed by Dan Maloney for
2203 : // the BLAST program.
2204 : // REFERENCES:
2205 : // Maloney, Dan, M.S. Thesis, University of Illinois at Urbana-Champaign
2206 :
2207 626496 : Real64 const XT(Temp / 100.0);
2208 626496 : return 6.16796 + 358.1855 * pow_2(XT) - 550.3543 * pow_3(XT) + 1048.8115 * pow_4(XT);
2209 :
2210 : // Helper function for pierceSET calculates Saturated Vapor Pressure (Torr) at Temperature T (°C)
2211 : // return Math.exp(18.6686 - 4030.183/(T + 235.0));
2212 : }
2213 :
2214 619139 : Real64 CalcSatVapPressFromTempTorr(Real64 const Temp)
2215 : {
2216 : // Helper function for pierceSET calculates Saturated Vapor Pressure (Torr) at Temperature T (°C)
2217 619139 : return std::exp(18.6686 - 4030.183 / (Temp + 235.0));
2218 : }
2219 :
2220 1208503 : Real64 CalcRadTemp(EnergyPlusData &state, int const PeopleListNum)
2221 : {
2222 :
2223 : // FUNCTION INFORMATION:
2224 : // AUTHOR Jaewook Lee
2225 : // DATE WRITTEN November 2000
2226 : // MODIFIED Rick Strand (for E+ implementation November 2000)
2227 : // Rick Strand (for high temperature radiant heaters March 2001)
2228 :
2229 : // PURPOSE OF THIS FUNCTION:
2230 : // THIS IS A FUNCTION TO CALCULATE EITHER ZONE AVERAGED MRT OR
2231 : // SURFACE WEIGHTED MRT
2232 :
2233 : // METHODOLOGY EMPLOYED:
2234 : // The method here is fairly straight-forward. If the user has selected
2235 : // a zone average MRT calculation, then there is nothing to do other than
2236 : // to assign the function value because the zone MRT has already been
2237 : // calculated. Note that this value is an "area-emissivity" weighted value.
2238 : // If the user wants to place the occupant "near" a particular surface,
2239 : // then at the limit half of the radiant field will be from this surface.
2240 : // As a result, an average of the zone MRT and the surface temperature
2241 : // is taken to arrive at an approximate radiant temperature.
2242 : // If a high temperature radiant heater is present, then this must also be
2243 : // taken into account. The equation used to account for this factor is
2244 : // based on equation 49 on page 150 of Fanger's text (see reference below).
2245 : // The additional assumptions for EnergyPlus are that the radiant energy
2246 : // from the heater must be spread over the average area of a human being
2247 : // (see parameter below) and that the emissivity and absorptivity of the
2248 : // occupant are equivalent for the dominant wavelength of radiant energy
2249 : // from the heater. These assumptions might be off slightly, but it does
2250 : // allow for an approximation of the effects of surfaces and heaters
2251 : // within a space. Future additions might include the effect of direct
2252 : // solar energy on occupants.
2253 :
2254 1208503 : Real64 CalcRadTemp = 0.0;
2255 1208503 : Real64 constexpr AreaEff = 1.8; // Effective area of a "standard" person in meters squared
2256 1208503 : Real64 constexpr StefanBoltzmannConst = 5.6697e-8; // Stefan-Boltzmann constant in W/(m2*K4)
2257 :
2258 1208503 : auto &thisPeople = state.dataHeatBal->People(PeopleListNum);
2259 1208503 : switch (thisPeople.MRTCalcType) {
2260 1205623 : case DataHeatBalance::CalcMRT::EnclosureAveraged: {
2261 1205623 : int enclNum = state.dataHeatBal->space(thisPeople.spaceIndex).radiantEnclosureNum;
2262 1205623 : state.dataThermalComforts->RadTemp = state.dataViewFactor->EnclRadInfo(enclNum).MRT;
2263 1205623 : } break;
2264 2016 : case DataHeatBalance::CalcMRT::SurfaceWeighted: {
2265 2016 : state.dataThermalComforts->RadTemp = CalcSurfaceWeightedMRT(state, thisPeople.SurfacePtr);
2266 2016 : } break;
2267 864 : case DataHeatBalance::CalcMRT::AngleFactor: {
2268 864 : state.dataThermalComforts->RadTemp = CalcAngleFactorMRT(state, thisPeople.AngleFactorListPtr);
2269 864 : } break;
2270 0 : default:
2271 0 : break;
2272 : }
2273 :
2274 : // If high temperature radiant heater present and on, then must account for this in MRT calculation
2275 : // MJW MRT ToDo: Think about what happens here - at a minimum, set a flag to skip this if there isn't any radiant HVAC
2276 1208503 : state.dataHeatBalFanSys->ZoneQdotRadHVACToPerson(state.dataThermalComforts->ZoneNum) =
2277 1208503 : state.dataHeatBalFanSys->ZoneQHTRadSysToPerson(state.dataThermalComforts->ZoneNum) +
2278 1208503 : state.dataHeatBalFanSys->ZoneQCoolingPanelToPerson(state.dataThermalComforts->ZoneNum) +
2279 1208503 : state.dataHeatBalFanSys->ZoneQHWBaseboardToPerson(state.dataThermalComforts->ZoneNum) +
2280 1208503 : state.dataHeatBalFanSys->ZoneQSteamBaseboardToPerson(state.dataThermalComforts->ZoneNum) +
2281 1208503 : state.dataHeatBalFanSys->ZoneQElecBaseboardToPerson(state.dataThermalComforts->ZoneNum);
2282 1208503 : if (state.dataHeatBalFanSys->ZoneQdotRadHVACToPerson(state.dataThermalComforts->ZoneNum) > 0.0) {
2283 1334 : state.dataThermalComforts->RadTemp += Constant::Kelvin; // Convert to Kelvin
2284 1334 : state.dataThermalComforts->RadTemp =
2285 1334 : root_4(pow_4(state.dataThermalComforts->RadTemp) +
2286 1334 : (state.dataHeatBalFanSys->ZoneQdotRadHVACToPerson(state.dataThermalComforts->ZoneNum) / AreaEff / StefanBoltzmannConst));
2287 1334 : state.dataThermalComforts->RadTemp -= Constant::Kelvin; // Convert back to Celsius
2288 : }
2289 :
2290 1208503 : CalcRadTemp = state.dataThermalComforts->RadTemp;
2291 :
2292 1208503 : return CalcRadTemp;
2293 : }
2294 :
2295 491880 : void CalcThermalComfortSimpleASH55(EnergyPlusData &state)
2296 : {
2297 : // SUBROUTINE INFORMATION:
2298 : // AUTHOR Jason Glazer
2299 : // DATE WRITTEN June 2005
2300 :
2301 : // PURPOSE OF THIS SUBROUTINE:
2302 : // Determines if the space is within the ASHRAE 55-2004 comfort region
2303 : // based on operative temperature and humidity ratio
2304 :
2305 : // Using/Aliasing
2306 : using OutputReportTabular::isInQuadrilateral;
2307 : using namespace OutputReportPredefined;
2308 :
2309 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2310 : Real64 OperTemp;
2311 : Real64 NumberOccupants;
2312 : bool isComfortableWithSummerClothes;
2313 : bool isComfortableWithWinterClothes;
2314 : int iPeople;
2315 : int iZone;
2316 : Real64 allowedHours;
2317 : bool showWarning;
2318 :
2319 491880 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer = 0.0;
2320 491880 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter = 0.0;
2321 491880 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either = 0.0;
2322 :
2323 : // assume the zone is unoccupied
2324 2980944 : for (auto &e : state.dataThermalComforts->ThermalComfortInASH55) {
2325 2489064 : e.ZoneIsOccupied = false;
2326 491880 : }
2327 : // loop through the people objects and determine if the zone is currently occupied
2328 2606688 : for (auto const &people : state.dataHeatBal->People) {
2329 2114808 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
2330 2114808 : NumberOccupants = people.NumberOfPeople * people.sched->getCurrentVal();
2331 2114808 : if (NumberOccupants > 0) {
2332 881044 : state.dataThermalComforts->ThermalComfortInASH55(state.dataThermalComforts->ZoneNum).ZoneIsOccupied = true;
2333 : }
2334 491880 : }
2335 : // loop through the zones and determine if in simple ashrae 55 comfort regions
2336 : // MJW MRT ToDo: Extend ASHRAE 55 to spaces?
2337 2980944 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2338 2489064 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).ZoneIsOccupied) {
2339 879128 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone);
2340 : // keep track of occupied hours
2341 879128 : state.dataThermalComforts->ZoneOccHrs(iZone) += state.dataGlobal->TimeStepZone;
2342 879128 : Real64 CurAirTemp = thisZoneHB.ZTAVComf;
2343 879128 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
2344 2524 : if (state.dataRoomAir->IsZoneDispVent3Node(iZone) || state.dataRoomAir->IsZoneUFAD(iZone)) {
2345 808 : CurAirTemp = state.dataRoomAir->TCMF(iZone);
2346 : }
2347 : }
2348 879128 : Real64 CurMeanRadiantTemp = thisZoneHB.MRT;
2349 879128 : OperTemp = CurAirTemp * 0.5 + CurMeanRadiantTemp * 0.5;
2350 : // for debugging
2351 : // ThermalComfortInASH55(iZone)%dCurAirTemp = CurAirTemp
2352 : // ThermalComfortInASH55(iZone)%dCurMeanRadiantTemp = CurMeanRadiantTemp
2353 : // ThermalComfortInASH55(iZone)%dOperTemp = OperTemp
2354 : // ThermalComfortInASH55(iZone)%dHumidRatio = HumidRatio
2355 : // From ASHRAE Standard 55-2004 Appendix D
2356 : // Run AirTemp(C) RH(%) Season HumidRatio
2357 : // 1 19.6 86 Winter 0.012
2358 : // 2 23.9 66 Winter 0.012
2359 : // 3 25.7 15 Winter 0.003
2360 : // 4 21.2 20 Winter 0.003
2361 : // 5 23.6 67 Summer 0.012
2362 : // 6 26.8 56 Summer 0.012
2363 : // 7 27.9 13 Summer 0.003
2364 : // 8 24.7 16 Summer 0.003
2365 : // But the standard says "no recommended lower humidity limit" so it should
2366 : // really extend down to the 0.0 Humidity ratio line. Extrapolating we get
2367 : // the values that are shown in the following table
2368 : // Run AirTemp(C) Season HumidRatio
2369 : // 1 19.6 Winter 0.012
2370 : // 2 23.9 Winter 0.012
2371 : // 3 26.3 Winter 0.000
2372 : // 4 21.7 Winter 0.000
2373 : // 5 23.6 Summer 0.012
2374 : // 6 26.8 Summer 0.012
2375 : // 7 28.3 Summer 0.000
2376 : // 8 25.1 Summer 0.000
2377 : // check summer clothing conditions
2378 : isComfortableWithSummerClothes =
2379 879128 : isInQuadrilateral(OperTemp, thisZoneHB.airHumRatAvgComf, 25.1, 0.0, 23.6, 0.012, 26.8, 0.012, 28.3, 0.0);
2380 : // check winter clothing conditions
2381 : isComfortableWithWinterClothes =
2382 879128 : isInQuadrilateral(OperTemp, thisZoneHB.airHumRatAvgComf, 21.7, 0.0, 19.6, 0.012, 23.9, 0.012, 26.3, 0.0);
2383 879128 : if (isComfortableWithSummerClothes) {
2384 338189 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotSummer = 0.0;
2385 : } else {
2386 540939 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotSummer = state.dataGlobal->TimeStepZone;
2387 540939 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer += state.dataGlobal->TimeStepZone;
2388 540939 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer = state.dataGlobal->TimeStepZone;
2389 : }
2390 879128 : if (isComfortableWithWinterClothes) {
2391 317647 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotWinter = 0.0;
2392 : } else {
2393 561481 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotWinter = state.dataGlobal->TimeStepZone;
2394 561481 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter += state.dataGlobal->TimeStepZone;
2395 561481 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter = state.dataGlobal->TimeStepZone;
2396 : }
2397 879128 : if (isComfortableWithSummerClothes || isComfortableWithWinterClothes) {
2398 595109 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotEither = 0.0;
2399 : } else {
2400 284019 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotEither = state.dataGlobal->TimeStepZone;
2401 284019 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither += state.dataGlobal->TimeStepZone;
2402 284019 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either = state.dataGlobal->TimeStepZone;
2403 : }
2404 : } else {
2405 : // when no one present in that portion of the zone then no one can be uncomfortable
2406 1609936 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotSummer = 0.0;
2407 1609936 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotWinter = 0.0;
2408 1609936 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotEither = 0.0;
2409 : }
2410 : }
2411 : // accumulate total time
2412 491880 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer += state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer;
2413 491880 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter += state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter;
2414 491880 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either += state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either;
2415 :
2416 491880 : if (state.dataGlobal->EndDesignDayEnvrnsFlag) {
2417 819 : allowedHours = double(state.dataGlobal->NumOfDayInEnvrn) * 24.0 * 0.04;
2418 : // first check if warning should be printed
2419 819 : showWarning = false;
2420 6324 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2421 5505 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).Enable55Warning) {
2422 0 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither > allowedHours) {
2423 0 : showWarning = true;
2424 : }
2425 : }
2426 : }
2427 : // if any zones should be warning print it out
2428 819 : if (showWarning) {
2429 0 : ShowWarningError(state, format("More than 4% of time ({:.1R} hours) uncomfortable in one or more zones ", allowedHours));
2430 0 : ShowContinueError(state, "Based on ASHRAE 55-2004 graph (Section 5.2.1.1)");
2431 0 : if (state.dataEnvrn->RunPeriodEnvironment) {
2432 0 : ShowContinueError(state,
2433 0 : format("During Environment [{}]: {}", state.dataEnvrn->EnvironmentStartEnd, state.dataEnvrn->EnvironmentName));
2434 : } else {
2435 0 : ShowContinueError(
2436 : state,
2437 0 : format("During SizingPeriod Environment [{}]: {}", state.dataEnvrn->EnvironmentStartEnd, state.dataEnvrn->EnvironmentName));
2438 : }
2439 0 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2440 0 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).Enable55Warning) {
2441 0 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither > allowedHours) {
2442 0 : ShowContinueError(state,
2443 0 : format("{:.1R} hours were uncomfortable in zone: {}",
2444 0 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither,
2445 0 : state.dataHeatBal->Zone(iZone).Name));
2446 : }
2447 : }
2448 : }
2449 : }
2450 : // put in predefined reports
2451 6324 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2452 11010 : PreDefTableEntry(state,
2453 5505 : state.dataOutRptPredefined->pdchSCwinterClothes,
2454 5505 : state.dataHeatBal->Zone(iZone).Name,
2455 5505 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter);
2456 11010 : PreDefTableEntry(state,
2457 5505 : state.dataOutRptPredefined->pdchSCsummerClothes,
2458 5505 : state.dataHeatBal->Zone(iZone).Name,
2459 5505 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer);
2460 11010 : PreDefTableEntry(state,
2461 5505 : state.dataOutRptPredefined->pdchSCeitherClothes,
2462 5505 : state.dataHeatBal->Zone(iZone).Name,
2463 5505 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither);
2464 : }
2465 2457 : PreDefTableEntry(
2466 1638 : state, state.dataOutRptPredefined->pdchSCwinterClothes, "Facility", state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter);
2467 2457 : PreDefTableEntry(
2468 1638 : state, state.dataOutRptPredefined->pdchSCsummerClothes, "Facility", state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer);
2469 2457 : PreDefTableEntry(
2470 1638 : state, state.dataOutRptPredefined->pdchSCeitherClothes, "Facility", state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either);
2471 : // set value for ABUPS report
2472 819 : state.dataOutRptPredefined->TotalTimeNotSimpleASH55EitherForABUPS = state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either;
2473 : // reset accumulation for new environment
2474 6324 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2475 5505 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter = 0.0;
2476 5505 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer = 0.0;
2477 5505 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither = 0.0;
2478 : }
2479 819 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter = 0.0;
2480 819 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer = 0.0;
2481 819 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either = 0.0;
2482 : // report how the aggregation is conducted
2483 819 : switch (state.dataGlobal->KindOfSim) {
2484 792 : case Constant::KindOfSim::DesignDay: {
2485 792 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstSimpleComfort, "Aggregated over the Design Days");
2486 792 : } break;
2487 3 : case Constant::KindOfSim::RunPeriodDesign: {
2488 3 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstSimpleComfort, "Aggregated over the RunPeriods for Design");
2489 3 : } break;
2490 7 : case Constant::KindOfSim::RunPeriodWeather: {
2491 7 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstSimpleComfort, "Aggregated over the RunPeriods for Weather");
2492 7 : } break;
2493 17 : default:
2494 17 : break;
2495 : }
2496 : // report number of occupied hours per week for LEED report
2497 6324 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2498 11010 : PreDefTableEntry(state,
2499 5505 : state.dataOutRptPredefined->pdchLeedSutHrsWeek,
2500 5505 : state.dataHeatBal->Zone(iZone).Name,
2501 5505 : 7 * 24 * (state.dataThermalComforts->ZoneOccHrs(iZone) / (state.dataGlobal->NumOfDayInEnvrn * 24)));
2502 : }
2503 : }
2504 491880 : }
2505 :
2506 0 : void ResetThermalComfortSimpleASH55(EnergyPlusData &state)
2507 : {
2508 : // Jason Glazer - October 2015
2509 : // Reset thermal comfort table gathering arrays to zero for multi-year simulations
2510 : // so that only last year is reported in tabular reports
2511 : int iZone;
2512 0 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2513 0 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter = 0.0;
2514 0 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer = 0.0;
2515 0 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither = 0.0;
2516 : }
2517 0 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter = 0.0;
2518 0 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer = 0.0;
2519 0 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either = 0.0;
2520 0 : }
2521 :
2522 491880 : void CalcIfSetPointMet(EnergyPlusData &state)
2523 : {
2524 : // SUBROUTINE INFORMATION:
2525 : // AUTHOR Jason Glazer
2526 : // DATE WRITTEN July 2005
2527 :
2528 : // PURPOSE OF THIS SUBROUTINE:
2529 : // Report if the setpoint temperature has been met.
2530 : // Add calculation of how far away from setpoint and if setpoint was not met
2531 : // during all times and during occupancy.
2532 :
2533 : // Using/Aliasing
2534 : using namespace OutputReportPredefined;
2535 491880 : Real64 const deviationFromSetPtThresholdClg = state.dataHVACGlobal->deviationFromSetPtThresholdClg;
2536 491880 : Real64 const deviationFromSetPtThresholdHtg = state.dataHVACGlobal->deviationFromSetPtThresholdHtg;
2537 :
2538 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2539 : Real64 SensibleLoadPredictedNoAdj;
2540 : Real64 deltaT;
2541 : int iZone;
2542 : bool testHeating;
2543 : bool testCooling;
2544 :
2545 : // Get the load predicted - the sign will indicate if heating or cooling
2546 : // was called for
2547 491880 : state.dataThermalComforts->AnyZoneNotMetHeating = 0.0;
2548 491880 : state.dataThermalComforts->AnyZoneNotMetCooling = 0.0;
2549 491880 : state.dataThermalComforts->AnyZoneNotMetOccupied = 0.0;
2550 491880 : state.dataThermalComforts->AnyZoneNotMetHeatingOccupied = 0.0;
2551 491880 : state.dataThermalComforts->AnyZoneNotMetCoolingOccupied = 0.0;
2552 2980944 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2553 2489064 : auto const &zoneTstatSetpt = state.dataHeatBalFanSys->zoneTstatSetpts(iZone);
2554 :
2555 2489064 : SensibleLoadPredictedNoAdj = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(iZone).TotalOutputRequired;
2556 2489064 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCooling = 0.0;
2557 2489064 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeating = 0.0;
2558 2489064 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCoolingOccupied = 0.0;
2559 2489064 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeatingOccupied = 0.0;
2560 :
2561 2489064 : testHeating = (state.dataHeatBalFanSys->TempControlType(iZone) != HVAC::SetptType::SingleCool);
2562 2489064 : testCooling = (state.dataHeatBalFanSys->TempControlType(iZone) != HVAC::SetptType::SingleHeat);
2563 :
2564 2489064 : if (testHeating && (SensibleLoadPredictedNoAdj > 0)) { // heating
2565 695339 : if (state.dataRoomAir->AirModel(iZone).AirModel != RoomAir::RoomAirModel::Mixing) {
2566 2304 : deltaT = state.dataHeatBalFanSys->TempTstatAir(iZone) - zoneTstatSetpt.setptLo;
2567 : } else {
2568 693035 : if (state.dataZoneTempPredictorCorrector->NumOnOffCtrZone > 0) {
2569 1008 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptLoAver;
2570 : } else {
2571 692027 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptLo;
2572 : }
2573 : }
2574 695339 : if (deltaT < deviationFromSetPtThresholdHtg) {
2575 193090 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeating = state.dataGlobal->TimeStepZone;
2576 193090 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating += state.dataGlobal->TimeStepZone;
2577 193090 : if (state.dataThermalComforts->AnyZoneNotMetHeating == 0.0) {
2578 40348 : state.dataThermalComforts->AnyZoneNotMetHeating = state.dataGlobal->TimeStepZone;
2579 : }
2580 193090 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).ZoneIsOccupied) {
2581 40614 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeatingOccupied = state.dataGlobal->TimeStepZone;
2582 40614 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied += state.dataGlobal->TimeStepZone;
2583 40614 : if (state.dataThermalComforts->AnyZoneNotMetHeatingOccupied == 0.0) {
2584 10654 : state.dataThermalComforts->AnyZoneNotMetHeatingOccupied = state.dataGlobal->TimeStepZone;
2585 : }
2586 40614 : if (state.dataThermalComforts->AnyZoneNotMetOccupied == 0.0) {
2587 10646 : state.dataThermalComforts->AnyZoneNotMetOccupied = state.dataGlobal->TimeStepZone;
2588 : }
2589 : }
2590 : }
2591 1793725 : } else if (testCooling && (SensibleLoadPredictedNoAdj < 0)) { // cooling
2592 666891 : if (state.dataRoomAir->AirModel(iZone).AirModel != RoomAir::RoomAirModel::Mixing) {
2593 1848 : deltaT = state.dataHeatBalFanSys->TempTstatAir(iZone) - zoneTstatSetpt.setptHi;
2594 : } else {
2595 665043 : if (state.dataZoneTempPredictorCorrector->NumOnOffCtrZone > 0) {
2596 525 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptHiAver;
2597 : } else {
2598 664518 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptHi;
2599 : }
2600 : }
2601 :
2602 666891 : if (state.dataHeatBal->Zone(iZone).HasAdjustedReturnTempByITE) {
2603 960 : deltaT = state.dataHeatBalFanSys->TempTstatAir(iZone) - state.dataHeatBal->Zone(iZone).AdjustedReturnTempByITE;
2604 : }
2605 666891 : if (deltaT > deviationFromSetPtThresholdClg) {
2606 66038 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCooling = state.dataGlobal->TimeStepZone;
2607 66038 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling += state.dataGlobal->TimeStepZone;
2608 66038 : if (state.dataThermalComforts->AnyZoneNotMetCooling == 0.0) {
2609 28491 : state.dataThermalComforts->AnyZoneNotMetCooling = state.dataGlobal->TimeStepZone;
2610 : }
2611 66038 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).ZoneIsOccupied) {
2612 36781 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCoolingOccupied = state.dataGlobal->TimeStepZone;
2613 36781 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied += state.dataGlobal->TimeStepZone;
2614 36781 : if (state.dataThermalComforts->AnyZoneNotMetCoolingOccupied == 0.0) {
2615 16038 : state.dataThermalComforts->AnyZoneNotMetCoolingOccupied = state.dataGlobal->TimeStepZone;
2616 : }
2617 36781 : if (state.dataThermalComforts->AnyZoneNotMetOccupied == 0.0) {
2618 16019 : state.dataThermalComforts->AnyZoneNotMetOccupied = state.dataGlobal->TimeStepZone;
2619 : }
2620 : }
2621 : }
2622 : }
2623 : }
2624 491880 : state.dataThermalComforts->TotalAnyZoneNotMetHeating += state.dataThermalComforts->AnyZoneNotMetHeating;
2625 491880 : state.dataThermalComforts->TotalAnyZoneNotMetCooling += state.dataThermalComforts->AnyZoneNotMetCooling;
2626 491880 : state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied += state.dataThermalComforts->AnyZoneNotMetHeatingOccupied;
2627 491880 : state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied += state.dataThermalComforts->AnyZoneNotMetCoolingOccupied;
2628 491880 : state.dataThermalComforts->TotalAnyZoneNotMetOccupied += state.dataThermalComforts->AnyZoneNotMetOccupied;
2629 :
2630 : // was EndEnvrnsFlag prior to CR7562
2631 491880 : if (state.dataGlobal->EndDesignDayEnvrnsFlag) {
2632 6324 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2633 11010 : PreDefTableEntry(state,
2634 5505 : state.dataOutRptPredefined->pdchULnotMetHeat,
2635 5505 : state.dataHeatBal->Zone(iZone).Name,
2636 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating);
2637 11010 : PreDefTableEntry(state,
2638 5505 : state.dataOutRptPredefined->pdchULnotMetCool,
2639 5505 : state.dataHeatBal->Zone(iZone).Name,
2640 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling);
2641 11010 : PreDefTableEntry(state,
2642 5505 : state.dataOutRptPredefined->pdchULnotMetHeatOcc,
2643 5505 : state.dataHeatBal->Zone(iZone).Name,
2644 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied);
2645 11010 : PreDefTableEntry(state,
2646 5505 : state.dataOutRptPredefined->pdchULnotMetCoolOcc,
2647 5505 : state.dataHeatBal->Zone(iZone).Name,
2648 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied);
2649 : }
2650 819 : PreDefTableEntry(state, state.dataOutRptPredefined->pdchULnotMetHeat, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetHeating);
2651 819 : PreDefTableEntry(state, state.dataOutRptPredefined->pdchULnotMetCool, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetCooling);
2652 2457 : PreDefTableEntry(
2653 1638 : state, state.dataOutRptPredefined->pdchULnotMetHeatOcc, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied);
2654 2457 : PreDefTableEntry(
2655 1638 : state, state.dataOutRptPredefined->pdchULnotMetCoolOcc, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied);
2656 : // set value for ABUPS report
2657 819 : state.dataOutRptPredefined->TotalNotMetHeatingOccupiedForABUPS = state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied;
2658 819 : state.dataOutRptPredefined->TotalNotMetCoolingOccupiedForABUPS = state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied;
2659 819 : state.dataOutRptPredefined->TotalNotMetOccupiedForABUPS = state.dataThermalComforts->TotalAnyZoneNotMetOccupied;
2660 : // reset counters
2661 6324 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2662 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating = 0.0;
2663 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling = 0.0;
2664 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied = 0.0;
2665 5505 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied = 0.0;
2666 : }
2667 819 : state.dataThermalComforts->TotalAnyZoneNotMetHeating = 0.0;
2668 819 : state.dataThermalComforts->TotalAnyZoneNotMetCooling = 0.0;
2669 819 : state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied = 0.0;
2670 819 : state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied = 0.0;
2671 819 : state.dataThermalComforts->TotalAnyZoneNotMetOccupied = 0.0;
2672 : // report how the aggregation is conducted
2673 819 : switch (state.dataGlobal->KindOfSim) {
2674 792 : case Constant::KindOfSim::DesignDay: {
2675 792 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstUnmetLoads, "Aggregated over the Design Days");
2676 792 : } break;
2677 3 : case Constant::KindOfSim::RunPeriodDesign: {
2678 3 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstUnmetLoads, "Aggregated over the RunPeriods for Design");
2679 3 : } break;
2680 7 : case Constant::KindOfSim::RunPeriodWeather: {
2681 7 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstUnmetLoads, "Aggregated over the RunPeriods for Weather");
2682 7 : } break;
2683 17 : default:
2684 17 : break;
2685 : }
2686 : }
2687 491880 : }
2688 :
2689 0 : void ResetSetPointMet(EnergyPlusData &state)
2690 : {
2691 : // Jason Glazer - October 2015
2692 : // Reset set point not met table gathering arrays to zero for multi-year simulations
2693 : // so that only last year is reported in tabular reports
2694 : int iZone;
2695 0 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2696 0 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating = 0.0;
2697 0 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling = 0.0;
2698 0 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied = 0.0;
2699 0 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied = 0.0;
2700 : }
2701 0 : state.dataThermalComforts->TotalAnyZoneNotMetHeating = 0.0;
2702 0 : state.dataThermalComforts->TotalAnyZoneNotMetCooling = 0.0;
2703 0 : state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied = 0.0;
2704 0 : state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied = 0.0;
2705 0 : state.dataThermalComforts->TotalAnyZoneNotMetOccupied = 0.0;
2706 0 : }
2707 :
2708 973 : void CalcThermalComfortAdaptiveASH55(
2709 : EnergyPlusData &state,
2710 : bool const initiate, // true if supposed to initiate
2711 : ObjexxFCL::Optional_bool_const wthrsim, // true if this is a weather simulation
2712 : ObjexxFCL::Optional<Real64 const> avgdrybulb // approximate avg drybulb for design day. will be used as previous period in design day
2713 : )
2714 : {
2715 :
2716 : // SUBROUTINE INFORMATION:
2717 : // AUTHOR Tyler Hoyt
2718 : // DATE WRITTEN July 2011
2719 :
2720 : // PURPOSE OF THIS SUBROUTINE:
2721 : // Sets up and carries out ASHRAE55-2010 adaptive comfort model calculations.
2722 : // Output provided are state variables for the 80% and 90% acceptability limits
2723 : // in the model, the comfort temperature, and the 30-day running average or
2724 : // monthly average outdoor air temperature as parsed from the .STAT file.
2725 :
2726 : // METHODOLOGY EMPLOYED:
2727 : // In order for the calculations to be possible the user must provide either
2728 : // a .STAT file or .EPW file for the purpose of computing a monthly average
2729 : // temperature or thirty-day running average. The subroutine need only open
2730 : // the relevant file once to initialize, and then operates within the loop.
2731 :
2732 : // Using/Aliasing
2733 973 : Real64 SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
2734 : using OutputReportTabular::GetColumnUsingTabs;
2735 : using OutputReportTabular::StrToReal;
2736 :
2737 : // SUBROUTINE PARAMETER DEFINITIONS:
2738 :
2739 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2740 973 : std::string lineAvg;
2741 973 : std::string epwLine;
2742 : Real64 dryBulb;
2743 : Real64 tComf;
2744 : Real64 numOccupants;
2745 : int readStat;
2746 : int jStartDay;
2747 : int calcStartDay;
2748 : int calcStartHr;
2749 : int calcEndDay;
2750 : int calcEndHr;
2751 : std::string::size_type pos;
2752 : int ind;
2753 : int i;
2754 : int j;
2755 : bool weathersimulation;
2756 : Real64 inavgdrybulb;
2757 :
2758 973 : if (initiate) { // not optional on initiate=true. would otherwise check for presence
2759 13 : weathersimulation = wthrsim;
2760 13 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2761 13 : state.dataThermalComforts->runningAverageASH = 0.0;
2762 13 : state.dataThermalComforts->monthlyTemp = 0.0;
2763 13 : inavgdrybulb = avgdrybulb;
2764 : } else {
2765 960 : weathersimulation = false;
2766 960 : inavgdrybulb = 0.0;
2767 : }
2768 :
2769 973 : if (initiate && weathersimulation) {
2770 5 : const bool statFileExists = FileSystem::fileExists(state.files.inStatFilePath.filePath);
2771 5 : const bool epwFileExists = FileSystem::fileExists(state.files.inputWeatherFilePath.filePath);
2772 :
2773 5 : readStat = 0;
2774 5 : if (statFileExists) {
2775 10 : auto statFile = state.files.inStatFilePath.open(state, "CalcThermalComfortAdapctiveASH55");
2776 460 : while (statFile.good()) {
2777 460 : auto lineIn = statFile.readLine();
2778 460 : if (has(lineIn.data, "Monthly Statistics for Dry Bulb temperatures")) {
2779 40 : for (i = 1; i <= 7; ++i) {
2780 35 : lineIn = statFile.readLine();
2781 : }
2782 5 : lineIn = statFile.readLine();
2783 5 : lineAvg = lineIn.data;
2784 5 : break;
2785 : }
2786 460 : }
2787 65 : for (i = 1; i <= 12; ++i) {
2788 60 : state.dataThermalComforts->monthlyTemp(i) = StrToReal(GetColumnUsingTabs(lineAvg, i + 2));
2789 : }
2790 5 : state.dataThermalComforts->useStatData = true;
2791 5 : } else if (epwFileExists) {
2792 : // determine number of days in year
2793 : int DaysInYear;
2794 0 : if (state.dataEnvrn->CurrentYearIsLeapYear) {
2795 0 : DaysInYear = 366;
2796 : } else {
2797 0 : DaysInYear = 365;
2798 : }
2799 0 : state.dataThermalComforts->DailyAveOutTemp = 0.0;
2800 :
2801 0 : auto epwFile = state.files.inputWeatherFilePath.open(state, "CalcThermalComfortAdaptiveASH55");
2802 0 : for (i = 1; i <= 8; ++i) { // Headers
2803 0 : epwLine = epwFile.readLine().data;
2804 : }
2805 0 : jStartDay = state.dataEnvrn->DayOfYear - 1;
2806 0 : calcStartDay = jStartDay - 30;
2807 0 : if (calcStartDay >= 0) {
2808 0 : calcStartHr = 24 * calcStartDay + 1;
2809 0 : for (i = 1; i <= calcStartHr - 1; ++i) {
2810 0 : epwFile.readLine();
2811 : }
2812 0 : for (i = 1; i <= 30; ++i) {
2813 0 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2814 0 : for (j = 1; j <= 24; ++j) {
2815 0 : epwLine = epwFile.readLine().data;
2816 0 : for (ind = 1; ind <= 6; ++ind) {
2817 0 : pos = index(epwLine, ',');
2818 0 : epwLine.erase(0, pos + 1);
2819 : }
2820 0 : pos = index(epwLine, ',');
2821 0 : dryBulb = StrToReal(epwLine.substr(0, pos));
2822 0 : state.dataThermalComforts->avgDryBulbASH += (dryBulb / 24.0);
2823 : }
2824 0 : state.dataThermalComforts->DailyAveOutTemp(i) = state.dataThermalComforts->avgDryBulbASH;
2825 : }
2826 : } else { // Do special things for wrapping the epw
2827 0 : calcEndDay = jStartDay;
2828 0 : calcStartDay += DaysInYear;
2829 0 : calcEndHr = 24 * calcEndDay;
2830 0 : calcStartHr = 24 * calcStartDay + 1;
2831 0 : for (i = 1; i <= calcEndDay; ++i) {
2832 0 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2833 0 : for (j = 1; j <= 24; ++j) {
2834 0 : epwLine = epwFile.readLine().data;
2835 0 : for (ind = 1; ind <= 6; ++ind) {
2836 0 : pos = index(epwLine, ',');
2837 0 : epwLine.erase(0, pos + 1);
2838 : }
2839 0 : pos = index(epwLine, ',');
2840 0 : dryBulb = StrToReal(epwLine.substr(0, pos));
2841 0 : state.dataThermalComforts->avgDryBulbASH += (dryBulb / 24.0);
2842 : }
2843 0 : state.dataThermalComforts->DailyAveOutTemp(i + 30 - calcEndDay) = state.dataThermalComforts->avgDryBulbASH;
2844 : }
2845 0 : for (i = calcEndHr + 1; i <= calcStartHr - 1; ++i) {
2846 0 : epwLine = epwFile.readLine().data;
2847 : }
2848 0 : for (i = 1; i <= 30 - calcEndDay; ++i) {
2849 0 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2850 0 : for (j = 1; j <= 24; ++j) {
2851 0 : epwLine = epwFile.readLine().data;
2852 0 : for (ind = 1; ind <= 6; ++ind) {
2853 0 : pos = index(epwLine, ',');
2854 0 : epwLine.erase(0, pos + 1);
2855 : }
2856 0 : pos = index(epwLine, ',');
2857 0 : dryBulb = StrToReal(epwLine.substr(0, pos));
2858 0 : state.dataThermalComforts->avgDryBulbASH += (dryBulb / 24.0);
2859 : }
2860 0 : state.dataThermalComforts->DailyAveOutTemp(i) = state.dataThermalComforts->avgDryBulbASH;
2861 : }
2862 : }
2863 0 : state.dataThermalComforts->useEpwData = true;
2864 0 : }
2865 973 : } else if (initiate && !weathersimulation) {
2866 8 : state.dataThermalComforts->runningAverageASH = inavgdrybulb;
2867 8 : state.dataThermalComforts->monthlyTemp = inavgdrybulb;
2868 8 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2869 : }
2870 :
2871 973 : if (initiate) {
2872 13 : return;
2873 : }
2874 :
2875 960 : if (state.dataGlobal->BeginDayFlag && state.dataThermalComforts->useEpwData) {
2876 : // Update the running average, reset the daily avg
2877 0 : state.dataThermalComforts->DailyAveOutTemp(30) = state.dataThermalComforts->avgDryBulbASH;
2878 0 : Real64 sum = 0.0;
2879 0 : for (i = 1; i <= 29; i++) {
2880 0 : sum += state.dataThermalComforts->DailyAveOutTemp(i);
2881 : }
2882 0 : state.dataThermalComforts->runningAverageASH = (sum + state.dataThermalComforts->avgDryBulbASH) / 30.0;
2883 0 : for (i = 1; i <= 29; i++) {
2884 0 : state.dataThermalComforts->DailyAveOutTemp(i) = state.dataThermalComforts->DailyAveOutTemp(i + 1);
2885 : }
2886 0 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2887 : }
2888 :
2889 : // If exists BeginMonthFlag we can use it to call InvJulianDay once per month.
2890 960 : if (state.dataGlobal->BeginDayFlag && state.dataThermalComforts->useStatData) {
2891 : // CALL InvJulianDay(DayOfYear,pMonth,pDay,0)
2892 : // runningAverageASH = monthlyTemp(pMonth)
2893 0 : state.dataThermalComforts->runningAverageASH = state.dataThermalComforts->monthlyTemp(state.dataEnvrn->Month);
2894 : }
2895 :
2896 : // Update the daily average
2897 : // IF (BeginHourFlag .and. useEpwData) THEN
2898 960 : if (state.dataGlobal->BeginHourFlag) {
2899 192 : state.dataThermalComforts->avgDryBulbASH += (state.dataEnvrn->OutDryBulbTemp / 24.0);
2900 : }
2901 :
2902 2496 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
2903 1536 : ++state.dataThermalComforts->PeopleNum) {
2904 :
2905 1536 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
2906 1536 : if (!people.AdaptiveASH55) {
2907 0 : continue;
2908 : }
2909 :
2910 1536 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
2911 :
2912 1536 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
2913 1536 : state.dataThermalComforts->AirTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum).ZTAVComf;
2914 1536 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
2915 288 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
2916 0 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
2917 288 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum);
2918 : }
2919 : }
2920 1536 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
2921 1536 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->AirTemp + state.dataThermalComforts->RadTemp) / 2.0;
2922 1536 : comfort.ThermalComfortOpTemp = state.dataThermalComforts->OpTemp;
2923 1536 : comfort.ASHRAE55RunningMeanOutdoorTemp = state.dataThermalComforts->runningAverageASH;
2924 1536 : if (state.dataThermalComforts->runningAverageASH >= 10.0 && state.dataThermalComforts->runningAverageASH <= 33.5) {
2925 : // Calculate the comfort here (people/output handling loop)
2926 768 : numOccupants = people.NumberOfPeople * people.sched->getCurrentVal();
2927 768 : tComf = 0.31 * state.dataThermalComforts->runningAverageASH + 17.8;
2928 768 : comfort.TComfASH55 = tComf;
2929 768 : if (numOccupants > 0) {
2930 438 : if (state.dataThermalComforts->OpTemp < tComf + 2.5 && state.dataThermalComforts->OpTemp > tComf - 2.5) {
2931 : // 80% and 90% limits okay
2932 336 : comfort.ThermalComfortAdaptiveASH5590 = 1;
2933 336 : comfort.ThermalComfortAdaptiveASH5580 = 1;
2934 102 : } else if (state.dataThermalComforts->OpTemp < tComf + 3.5 && state.dataThermalComforts->OpTemp > tComf - 3.5) {
2935 : // 80% only
2936 41 : comfort.ThermalComfortAdaptiveASH5590 = 0;
2937 41 : comfort.ThermalComfortAdaptiveASH5580 = 1;
2938 41 : people.TimeNotMetASH5590 += SysTimeElapsed;
2939 : } else {
2940 : // Neither
2941 61 : comfort.ThermalComfortAdaptiveASH5590 = 0;
2942 61 : comfort.ThermalComfortAdaptiveASH5580 = 0;
2943 61 : people.TimeNotMetASH5580 += SysTimeElapsed;
2944 61 : people.TimeNotMetASH5590 += SysTimeElapsed;
2945 : }
2946 : } else {
2947 : // Unoccupied
2948 330 : comfort.ThermalComfortAdaptiveASH5590 = -1;
2949 330 : comfort.ThermalComfortAdaptiveASH5580 = -1;
2950 : }
2951 : } else {
2952 : // Monthly temp out of range
2953 768 : comfort.ThermalComfortAdaptiveASH5590 = -1;
2954 768 : comfort.ThermalComfortAdaptiveASH5580 = -1;
2955 768 : comfort.TComfASH55 = -1.0;
2956 : }
2957 : }
2958 986 : }
2959 :
2960 291 : void CalcThermalComfortAdaptiveCEN15251(
2961 : EnergyPlusData &state,
2962 : bool const initiate, // true if supposed to initiate
2963 : ObjexxFCL::Optional_bool_const wthrsim, // true if this is a weather simulation
2964 : ObjexxFCL::Optional<Real64 const> avgdrybulb // approximate avg drybulb for design day. will be used as previous period in design day
2965 : )
2966 : {
2967 :
2968 : // SUBROUTINE INFORMATION:
2969 : // AUTHOR Tyler Hoyt
2970 : // DATE WRITTEN July 2011
2971 :
2972 : // PURPOSE OF THIS SUBROUTINE:
2973 : // Sets up and carries out CEN-15251 adaptive comfort model calculations.
2974 : // Output provided are state variables for the Category I, II, and III
2975 : // limits of the model, the comfort temperature, and the 5-day weighted
2976 : // moving average of the outdoor air temperature.
2977 :
2978 : // Using/Aliasing
2979 291 : Real64 SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
2980 : using OutputReportTabular::GetColumnUsingTabs;
2981 : using OutputReportTabular::StrToReal;
2982 :
2983 : // SUBROUTINE PARAMETER DEFINITIONS:
2984 : static Real64 constexpr alpha(0.8);
2985 : static constexpr std::array<Real64, 7> alpha_pow = {0.262144, 0.32768, 0.4096, 0.512, 0.64, 0.8, 1.0}; // alpha^(6-0)
2986 :
2987 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2988 291 : std::string epwLine;
2989 : Real64 dryBulb;
2990 : Real64 tComf;
2991 : Real64 tComfLow;
2992 : Real64 numOccupants;
2993 : int readStat;
2994 : int jStartDay;
2995 : int calcStartDay;
2996 : int calcStartHr;
2997 : int calcEndDay;
2998 : int calcEndHr;
2999 : std::string::size_type pos;
3000 : int ind;
3001 : int i;
3002 : int j;
3003 : bool weathersimulation;
3004 : Real64 inavgdrybulb;
3005 291 : int constexpr numHeaderRowsInEpw = 8;
3006 :
3007 291 : if (initiate) { // not optional on initiate=true. would otherwise check for presence
3008 3 : weathersimulation = wthrsim;
3009 3 : inavgdrybulb = avgdrybulb;
3010 3 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3011 3 : state.dataThermalComforts->runningAverageCEN = 0.0;
3012 : } else {
3013 288 : weathersimulation = false;
3014 288 : inavgdrybulb = 0.0;
3015 : }
3016 :
3017 291 : if (initiate && weathersimulation) {
3018 1 : const bool epwFileExists = FileSystem::fileExists(state.files.inputWeatherFilePath.filePath);
3019 1 : readStat = 0;
3020 1 : if (epwFileExists) {
3021 : // determine number of days in year
3022 : int DaysInYear;
3023 1 : if (state.dataEnvrn->CurrentYearIsLeapYear) {
3024 0 : DaysInYear = 366;
3025 : } else {
3026 1 : DaysInYear = 365;
3027 : }
3028 :
3029 2 : auto epwFile = state.files.inputWeatherFilePath.open(state, "CalcThermalComfortAdaptiveCEN15251");
3030 9 : for (i = 1; i <= numHeaderRowsInEpw; ++i) {
3031 8 : epwFile.readLine();
3032 : }
3033 1 : jStartDay = state.dataEnvrn->DayOfYear - 1;
3034 1 : calcStartDay = jStartDay - 7;
3035 1 : if (calcStartDay > 0) {
3036 1 : calcStartHr = 24 * calcStartDay + 1;
3037 2713 : for (i = 1; i <= calcStartHr - 1; ++i) {
3038 2712 : epwFile.readLine();
3039 : }
3040 1 : state.dataThermalComforts->runningAverageCEN = 0.0;
3041 8 : for (i = 0; i < 7; ++i) {
3042 7 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3043 175 : for (j = 1; j <= 24; ++j) {
3044 168 : epwLine = epwFile.readLine().data;
3045 1176 : for (ind = 1; ind <= 6; ++ind) {
3046 1008 : pos = index(epwLine, ',');
3047 1008 : epwLine.erase(0, pos + 1);
3048 : }
3049 168 : pos = index(epwLine, ',');
3050 168 : dryBulb = StrToReal(epwLine.substr(0, pos));
3051 168 : state.dataThermalComforts->avgDryBulbCEN += (dryBulb / 24.0);
3052 : }
3053 7 : state.dataThermalComforts->runningAverageCEN += alpha_pow[i] * state.dataThermalComforts->avgDryBulbCEN;
3054 : }
3055 : } else { // Do special things for wrapping the epw
3056 0 : calcEndDay = jStartDay;
3057 0 : calcStartDay += DaysInYear;
3058 0 : calcEndHr = 24 * calcEndDay;
3059 0 : calcStartHr = 24 * calcStartDay + 1;
3060 0 : for (i = 1; i <= calcEndDay; ++i) {
3061 0 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3062 0 : for (j = 1; j <= 24; ++j) {
3063 0 : epwLine = epwFile.readLine().data;
3064 0 : for (ind = 1; ind <= 6; ++ind) {
3065 0 : pos = index(epwLine, ',');
3066 0 : epwLine.erase(0, pos + 1);
3067 : }
3068 0 : pos = index(epwLine, ',');
3069 0 : dryBulb = StrToReal(epwLine.substr(0, pos));
3070 0 : state.dataThermalComforts->avgDryBulbCEN += (dryBulb / 24.0);
3071 : }
3072 0 : state.dataThermalComforts->runningAverageCEN += std::pow(alpha, calcEndDay - i) * state.dataThermalComforts->avgDryBulbCEN;
3073 : }
3074 0 : for (i = calcEndHr + 1; i <= calcStartHr - 1; ++i) {
3075 0 : epwFile.readLine();
3076 : }
3077 0 : for (i = 0; i < 7 - calcEndDay; ++i) {
3078 0 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3079 0 : for (j = 1; j <= 24; ++j) {
3080 0 : epwLine = epwFile.readLine().data;
3081 0 : for (ind = 1; ind <= 6; ++ind) {
3082 0 : pos = index(epwLine, ',');
3083 0 : epwLine.erase(0, pos + 1);
3084 : }
3085 0 : pos = index(epwLine, ',');
3086 0 : dryBulb = StrToReal(epwLine.substr(0, pos));
3087 0 : state.dataThermalComforts->avgDryBulbCEN += (dryBulb / 24.0);
3088 : }
3089 0 : state.dataThermalComforts->runningAverageCEN += alpha_pow[i] * state.dataThermalComforts->avgDryBulbCEN;
3090 : }
3091 : }
3092 1 : state.dataThermalComforts->runningAverageCEN *= (1.0 - alpha);
3093 1 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3094 1 : state.dataThermalComforts->useEpwDataCEN = true;
3095 1 : state.dataThermalComforts->firstDaySet = true;
3096 1 : }
3097 291 : } else if (initiate && !weathersimulation) {
3098 2 : state.dataThermalComforts->runningAverageCEN = inavgdrybulb;
3099 2 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3100 : }
3101 291 : if (initiate) {
3102 3 : return;
3103 : }
3104 :
3105 288 : if (state.dataGlobal->BeginDayFlag && !state.dataThermalComforts->firstDaySet) {
3106 : // Update the running average, reset the daily avg
3107 4 : state.dataThermalComforts->runningAverageCEN =
3108 2 : alpha * state.dataThermalComforts->runningAverageCEN + (1.0 - alpha) * state.dataThermalComforts->avgDryBulbCEN;
3109 2 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3110 : }
3111 :
3112 288 : state.dataThermalComforts->firstDaySet = false;
3113 :
3114 : // Update the daily average
3115 288 : if (state.dataGlobal->BeginHourFlag) {
3116 48 : state.dataThermalComforts->avgDryBulbCEN += (state.dataEnvrn->OutDryBulbTemp / 24.0);
3117 : }
3118 :
3119 576 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
3120 288 : ++state.dataThermalComforts->PeopleNum) {
3121 288 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
3122 288 : if (!people.AdaptiveCEN15251) {
3123 0 : continue;
3124 : }
3125 :
3126 288 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
3127 288 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
3128 288 : state.dataThermalComforts->AirTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum).ZTAVComf;
3129 288 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
3130 288 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
3131 0 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
3132 288 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum);
3133 : }
3134 : }
3135 288 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
3136 288 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->AirTemp + state.dataThermalComforts->RadTemp) / 2.0;
3137 288 : comfort.ThermalComfortOpTemp = state.dataThermalComforts->OpTemp;
3138 288 : comfort.CEN15251RunningMeanOutdoorTemp = state.dataThermalComforts->runningAverageCEN;
3139 288 : if (state.dataThermalComforts->runningAverageCEN >= 10.0 && state.dataThermalComforts->runningAverageCEN <= 30.0) {
3140 : // Calculate the comfort here (people/output handling loop)
3141 144 : numOccupants = people.NumberOfPeople * people.sched->getCurrentVal();
3142 144 : tComf = 0.33 * state.dataThermalComforts->runningAverageCEN + 18.8;
3143 144 : comfort.TComfCEN15251 = tComf;
3144 144 : if (numOccupants > 0) {
3145 78 : if (state.dataThermalComforts->runningAverageCEN < 15) {
3146 0 : tComfLow = 23.75; // Lower limit is constant in this region
3147 : } else {
3148 78 : tComfLow = tComf;
3149 : }
3150 78 : if (state.dataThermalComforts->OpTemp < tComf + 2.0 && state.dataThermalComforts->OpTemp > tComfLow - 2.0) {
3151 : // Within Cat I, II, III Limits
3152 21 : comfort.ThermalComfortAdaptiveCEN15251CatI = 1;
3153 21 : comfort.ThermalComfortAdaptiveCEN15251CatII = 1;
3154 21 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 1;
3155 57 : } else if (state.dataThermalComforts->OpTemp < tComf + 3.0 && state.dataThermalComforts->OpTemp > tComfLow - 3.0) {
3156 : // Within Cat II, III Limits
3157 16 : comfort.ThermalComfortAdaptiveCEN15251CatI = 0;
3158 16 : comfort.ThermalComfortAdaptiveCEN15251CatII = 1;
3159 16 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 1;
3160 16 : people.TimeNotMetCEN15251CatI += SysTimeElapsed;
3161 41 : } else if (state.dataThermalComforts->OpTemp < tComf + 4.0 && state.dataThermalComforts->OpTemp > tComfLow - 4.0) {
3162 : // Within Cat III Limits
3163 25 : comfort.ThermalComfortAdaptiveCEN15251CatI = 0;
3164 25 : comfort.ThermalComfortAdaptiveCEN15251CatII = 0;
3165 25 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 1;
3166 25 : people.TimeNotMetCEN15251CatI += SysTimeElapsed;
3167 25 : people.TimeNotMetCEN15251CatII += SysTimeElapsed;
3168 : } else {
3169 : // None
3170 16 : comfort.ThermalComfortAdaptiveCEN15251CatI = 0;
3171 16 : comfort.ThermalComfortAdaptiveCEN15251CatII = 0;
3172 16 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 0;
3173 16 : people.TimeNotMetCEN15251CatI += SysTimeElapsed;
3174 16 : people.TimeNotMetCEN15251CatII += SysTimeElapsed;
3175 16 : people.TimeNotMetCEN15251CatIII += SysTimeElapsed;
3176 : }
3177 : } else {
3178 : // Unoccupied
3179 66 : comfort.ThermalComfortAdaptiveCEN15251CatI = -1;
3180 66 : comfort.ThermalComfortAdaptiveCEN15251CatII = -1;
3181 66 : comfort.ThermalComfortAdaptiveCEN15251CatIII = -1;
3182 : }
3183 : } else {
3184 : // Monthly temp out of range
3185 144 : comfort.ThermalComfortAdaptiveCEN15251CatI = -1;
3186 144 : comfort.ThermalComfortAdaptiveCEN15251CatII = -1;
3187 144 : comfort.ThermalComfortAdaptiveCEN15251CatIII = -1;
3188 144 : comfort.TComfCEN15251 = -1.0;
3189 : }
3190 : }
3191 291 : }
3192 :
3193 264 : void DynamicClothingModel(EnergyPlusData &state)
3194 : {
3195 : // SUBROUTINE INFORMATION:
3196 : // AUTHOR Kwang Ho Lee
3197 : // DATE WRITTEN June 2013
3198 :
3199 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
3200 : Real64 TemporaryVariable; // LOL
3201 :
3202 264 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
3203 :
3204 264 : if (state.dataThermalComforts->TemporarySixAMTemperature < -5.0) { // A Temporary state variable?
3205 96 : comfort.ClothingValue = 1.0;
3206 168 : } else if ((state.dataThermalComforts->TemporarySixAMTemperature >= -5.0) && (state.dataThermalComforts->TemporarySixAMTemperature < 5.0)) {
3207 72 : comfort.ClothingValue = 0.818 - 0.0364 * state.dataThermalComforts->TemporarySixAMTemperature;
3208 96 : } else if ((state.dataThermalComforts->TemporarySixAMTemperature >= 5.0) && (state.dataThermalComforts->TemporarySixAMTemperature < 26.0)) {
3209 96 : TemporaryVariable = -0.1635 - 0.0066 * state.dataThermalComforts->TemporarySixAMTemperature;
3210 96 : comfort.ClothingValue = std::pow(10.0, TemporaryVariable);
3211 0 : } else if (state.dataThermalComforts->TemporarySixAMTemperature >= 26.0) {
3212 0 : comfort.ClothingValue = 0.46;
3213 : }
3214 264 : }
3215 :
3216 : } // namespace ThermalComfort
3217 :
3218 : } // namespace EnergyPlus
|