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 249945 : 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 249945 : if (state.dataThermalComforts->FirstTimeFlag) {
113 103 : InitThermalComfort(state); // Mainly sets up output stuff
114 103 : state.dataThermalComforts->FirstTimeFlag = false;
115 : }
116 :
117 249945 : if (state.dataGlobal->DayOfSim == 1) {
118 57959 : if (state.dataGlobal->HourOfDay < 7) {
119 14819 : state.dataThermalComforts->TemporarySixAMTemperature = 1.868132;
120 43140 : } else if (state.dataGlobal->HourOfDay == 7) {
121 2382 : if (state.dataGlobal->TimeStep == 1) {
122 432 : state.dataThermalComforts->TemporarySixAMTemperature = state.dataEnvrn->OutDryBulbTemp;
123 : }
124 : }
125 : } else {
126 191986 : if (state.dataGlobal->HourOfDay == 7) {
127 7999 : if (state.dataGlobal->TimeStep == 1) {
128 1601 : state.dataThermalComforts->TemporarySixAMTemperature = state.dataEnvrn->OutDryBulbTemp;
129 : }
130 : }
131 : }
132 :
133 249945 : if (InitializeOnly) {
134 0 : return;
135 : }
136 :
137 249945 : if (state.dataGlobal->BeginEnvrnFlag) {
138 482 : state.dataThermalComforts->ZoneOccHrs = 0.0;
139 : }
140 :
141 249945 : if (!state.dataGlobal->DoingSizing && !state.dataGlobal->WarmupFlag) {
142 18995 : CalcThermalComfortFanger(state);
143 18995 : if (state.dataHeatBal->AnyThermalComfortPierceModel) {
144 0 : CalcThermalComfortPierceASHRAE(state);
145 : }
146 18995 : if (state.dataHeatBal->AnyThermalComfortKSUModel) {
147 0 : CalcThermalComfortKSU(state);
148 : }
149 18995 : if (state.dataHeatBal->AnyThermalComfortCoolingEffectModel) {
150 0 : CalcThermalComfortCoolingEffectASH(state);
151 : }
152 18995 : if (state.dataHeatBal->AnyThermalComfortAnkleDraftModel) {
153 0 : CalcThermalComfortAnkleDraftASH(state);
154 : }
155 18995 : CalcThermalComfortSimpleASH55(state);
156 18995 : CalcIfSetPointMet(state);
157 18995 : if (state.dataHeatBal->AdaptiveComfortRequested_ASH55) {
158 0 : CalcThermalComfortAdaptiveASH55(state, false);
159 : }
160 18995 : if (state.dataHeatBal->AdaptiveComfortRequested_CEN15251) {
161 0 : CalcThermalComfortAdaptiveCEN15251(state, false);
162 : }
163 : }
164 : }
165 :
166 103 : 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 103 : std::string CurrentGroupName;
176 :
177 103 : state.dataThermalComforts->ThermalComfortData.allocate(state.dataHeatBal->TotPeople);
178 :
179 153 : for (Loop = 1; Loop <= state.dataHeatBal->TotPeople; ++Loop) {
180 :
181 50 : 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 50 : if (state.dataHeatBal->People(Loop).Fanger) {
186 24 : SetupOutputVariable(state,
187 : "Zone Thermal Comfort Fanger Model PMV",
188 : Constant::Units::None,
189 12 : state.dataThermalComforts->ThermalComfortData(Loop).FangerPMV,
190 : OutputProcessor::TimeStepType::Zone,
191 : OutputProcessor::StoreType::Average,
192 12 : state.dataHeatBal->People(Loop).Name);
193 24 : SetupOutputVariable(state,
194 : "Zone Thermal Comfort Fanger Model PPD",
195 : Constant::Units::Perc,
196 12 : state.dataThermalComforts->ThermalComfortData(Loop).FangerPPD,
197 : OutputProcessor::TimeStepType::Zone,
198 : OutputProcessor::StoreType::Average,
199 12 : state.dataHeatBal->People(Loop).Name);
200 24 : SetupOutputVariable(state,
201 : "Zone Thermal Comfort Clothing Surface Temperature",
202 : Constant::Units::C,
203 12 : state.dataThermalComforts->ThermalComfortData(Loop).CloSurfTemp,
204 : OutputProcessor::TimeStepType::Zone,
205 : OutputProcessor::StoreType::Average,
206 12 : state.dataHeatBal->People(Loop).Name);
207 : }
208 :
209 50 : if (state.dataHeatBal->People(Loop).Pierce) {
210 0 : SetupOutputVariable(state,
211 : "Zone Thermal Comfort Pierce Model Effective Temperature PMV",
212 : Constant::Units::None,
213 0 : state.dataThermalComforts->ThermalComfortData(Loop).PiercePMVET,
214 : OutputProcessor::TimeStepType::Zone,
215 : OutputProcessor::StoreType::Average,
216 0 : state.dataHeatBal->People(Loop).Name);
217 0 : SetupOutputVariable(state,
218 : "Zone Thermal Comfort Pierce Model Standard Effective Temperature PMV",
219 : Constant::Units::None,
220 0 : state.dataThermalComforts->ThermalComfortData(Loop).PiercePMVSET,
221 : OutputProcessor::TimeStepType::Zone,
222 : OutputProcessor::StoreType::Average,
223 0 : state.dataHeatBal->People(Loop).Name);
224 0 : SetupOutputVariable(state,
225 : "Zone Thermal Comfort Pierce Model Discomfort Index",
226 : Constant::Units::None,
227 0 : state.dataThermalComforts->ThermalComfortData(Loop).PierceDISC,
228 : OutputProcessor::TimeStepType::Zone,
229 : OutputProcessor::StoreType::Average,
230 0 : state.dataHeatBal->People(Loop).Name);
231 0 : SetupOutputVariable(state,
232 : "Zone Thermal Comfort Pierce Model Thermal Sensation Index",
233 : Constant::Units::None,
234 0 : state.dataThermalComforts->ThermalComfortData(Loop).PierceTSENS,
235 : OutputProcessor::TimeStepType::Zone,
236 : OutputProcessor::StoreType::Average,
237 0 : state.dataHeatBal->People(Loop).Name);
238 0 : SetupOutputVariable(state,
239 : "Zone Thermal Comfort Pierce Model Standard Effective Temperature",
240 : Constant::Units::C,
241 0 : state.dataThermalComforts->ThermalComfortData(Loop).PierceSET,
242 : OutputProcessor::TimeStepType::Zone,
243 : OutputProcessor::StoreType::Average,
244 0 : state.dataHeatBal->People(Loop).Name);
245 : }
246 :
247 50 : if (state.dataHeatBal->People(Loop).KSU) {
248 0 : SetupOutputVariable(state,
249 : "Zone Thermal Comfort KSU Model Thermal Sensation Vote",
250 : Constant::Units::None,
251 0 : state.dataThermalComforts->ThermalComfortData(Loop).KsuTSV,
252 : OutputProcessor::TimeStepType::Zone,
253 : OutputProcessor::StoreType::Average,
254 0 : state.dataHeatBal->People(Loop).Name);
255 : }
256 :
257 50 : if ((state.dataHeatBal->People(Loop).Fanger) || (state.dataHeatBal->People(Loop).Pierce) || (state.dataHeatBal->People(Loop).KSU)) {
258 24 : SetupOutputVariable(state,
259 : "Zone Thermal Comfort Mean Radiant Temperature",
260 : Constant::Units::C,
261 12 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortMRT,
262 : OutputProcessor::TimeStepType::Zone,
263 : OutputProcessor::StoreType::Average,
264 12 : state.dataHeatBal->People(Loop).Name);
265 24 : SetupOutputVariable(state,
266 : "Zone Thermal Comfort Operative Temperature",
267 : Constant::Units::C,
268 12 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortOpTemp,
269 : OutputProcessor::TimeStepType::Zone,
270 : OutputProcessor::StoreType::Average,
271 12 : state.dataHeatBal->People(Loop).Name);
272 24 : SetupOutputVariable(state,
273 : "Zone Thermal Comfort Clothing Value",
274 : Constant::Units::clo,
275 12 : state.dataThermalComforts->ThermalComfortData(Loop).ClothingValue,
276 : OutputProcessor::TimeStepType::Zone,
277 : OutputProcessor::StoreType::Average,
278 12 : state.dataHeatBal->People(Loop).Name);
279 : }
280 :
281 50 : if (state.dataHeatBal->People(Loop).AdaptiveASH55) {
282 0 : SetupOutputVariable(state,
283 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model 90% Acceptability Status",
284 : Constant::Units::None,
285 0 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveASH5590,
286 : OutputProcessor::TimeStepType::Zone,
287 : OutputProcessor::StoreType::Average,
288 0 : state.dataHeatBal->People(Loop).Name);
289 0 : SetupOutputVariable(state,
290 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model 80% Acceptability Status",
291 : Constant::Units::None,
292 0 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveASH5580,
293 : OutputProcessor::TimeStepType::Zone,
294 : OutputProcessor::StoreType::Average,
295 0 : state.dataHeatBal->People(Loop).Name);
296 0 : SetupOutputVariable(state,
297 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model Running Average Outdoor Air Temperature",
298 : Constant::Units::C,
299 0 : state.dataThermalComforts->ThermalComfortData(Loop).ASHRAE55RunningMeanOutdoorTemp,
300 : OutputProcessor::TimeStepType::Zone,
301 : OutputProcessor::StoreType::Average,
302 0 : state.dataHeatBal->People(Loop).Name);
303 0 : SetupOutputVariable(state,
304 : "Zone Thermal Comfort ASHRAE 55 Adaptive Model Temperature",
305 : Constant::Units::C,
306 0 : state.dataThermalComforts->ThermalComfortData(Loop).TComfASH55,
307 : OutputProcessor::TimeStepType::Zone,
308 : OutputProcessor::StoreType::Average,
309 0 : state.dataHeatBal->People(Loop).Name);
310 : }
311 :
312 50 : if (state.dataHeatBal->People(Loop).AdaptiveCEN15251) {
313 0 : SetupOutputVariable(state,
314 : "Zone Thermal Comfort CEN 15251 Adaptive Model Category I Status",
315 : Constant::Units::None,
316 0 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveCEN15251CatI,
317 : OutputProcessor::TimeStepType::Zone,
318 : OutputProcessor::StoreType::Average,
319 0 : state.dataHeatBal->People(Loop).Name);
320 0 : SetupOutputVariable(state,
321 : "Zone Thermal Comfort CEN 15251 Adaptive Model Category II Status",
322 : Constant::Units::None,
323 0 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveCEN15251CatII,
324 : OutputProcessor::TimeStepType::Zone,
325 : OutputProcessor::StoreType::Average,
326 0 : state.dataHeatBal->People(Loop).Name);
327 0 : SetupOutputVariable(state,
328 : "Zone Thermal Comfort CEN 15251 Adaptive Model Category III Status",
329 : Constant::Units::None,
330 0 : state.dataThermalComforts->ThermalComfortData(Loop).ThermalComfortAdaptiveCEN15251CatIII,
331 : OutputProcessor::TimeStepType::Zone,
332 : OutputProcessor::StoreType::Average,
333 0 : state.dataHeatBal->People(Loop).Name);
334 0 : SetupOutputVariable(state,
335 : "Zone Thermal Comfort CEN 15251 Adaptive Model Running Average Outdoor Air Temperature",
336 : Constant::Units::C,
337 0 : state.dataThermalComforts->ThermalComfortData(Loop).CEN15251RunningMeanOutdoorTemp,
338 : OutputProcessor::TimeStepType::Zone,
339 : OutputProcessor::StoreType::Average,
340 0 : state.dataHeatBal->People(Loop).Name);
341 0 : SetupOutputVariable(state,
342 : "Zone Thermal Comfort CEN 15251 Adaptive Model Temperature",
343 : Constant::Units::C,
344 0 : state.dataThermalComforts->ThermalComfortData(Loop).TComfCEN15251,
345 : OutputProcessor::TimeStepType::Zone,
346 : OutputProcessor::StoreType::Average,
347 0 : state.dataHeatBal->People(Loop).Name);
348 : }
349 50 : if (state.dataHeatBal->People(Loop).CoolingEffectASH55) {
350 0 : SetupOutputVariable(state,
351 : "Zone Thermal Comfort ASHRAE 55 Elevated Air Speed Cooling Effect",
352 : Constant::Units::C,
353 0 : state.dataThermalComforts->ThermalComfortData(Loop).CoolingEffectASH55,
354 : OutputProcessor::TimeStepType::Zone,
355 : OutputProcessor::StoreType::Average,
356 0 : state.dataHeatBal->People(Loop).Name);
357 0 : SetupOutputVariable(state,
358 : "Zone Thermal Comfort ASHRAE 55 Elevated Air Speed Cooling Effect Adjusted PMV",
359 : Constant::Units::None,
360 0 : state.dataThermalComforts->ThermalComfortData(Loop).CoolingEffectAdjustedPMVASH55,
361 : OutputProcessor::TimeStepType::Zone,
362 : OutputProcessor::StoreType::Average,
363 0 : state.dataHeatBal->People(Loop).Name);
364 0 : SetupOutputVariable(state,
365 : "Zone Thermal Comfort ASHRAE 55 Elevated Air Speed Cooling Effect Adjusted PPD",
366 : Constant::Units::None,
367 0 : state.dataThermalComforts->ThermalComfortData(Loop).CoolingEffectAdjustedPPDASH55,
368 : OutputProcessor::TimeStepType::Zone,
369 : OutputProcessor::StoreType::Average,
370 0 : state.dataHeatBal->People(Loop).Name);
371 : }
372 50 : if (state.dataHeatBal->People(Loop).AnkleDraftASH55) {
373 0 : SetupOutputVariable(state,
374 : "Zone Thermal Comfort ASHRAE 55 Ankle Draft PPD",
375 : Constant::Units::None,
376 0 : state.dataThermalComforts->ThermalComfortData(Loop).AnkleDraftPPDASH55,
377 : OutputProcessor::TimeStepType::Zone,
378 : OutputProcessor::StoreType::Average,
379 0 : state.dataHeatBal->People(Loop).Name);
380 : }
381 : }
382 103 : 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 153 : for (Loop = 1; Loop <= state.dataHeatBal->TotPeople; ++Loop) {
386 50 : if (state.dataHeatBal->People(Loop).Show55Warning) {
387 2 : state.dataThermalComforts->ThermalComfortInASH55(state.dataHeatBal->People(Loop).ZonePtr).Enable55Warning = true;
388 : }
389 : }
390 :
391 : // CurrentModuleObject='Zone'
392 226 : for (Loop = 1; Loop <= state.dataGlobal->NumOfZones; ++Loop) {
393 246 : SetupOutputVariable(state,
394 : "Zone Thermal Comfort ASHRAE 55 Simple Model Summer Clothes Not Comfortable Time",
395 : Constant::Units::hr,
396 123 : state.dataThermalComforts->ThermalComfortInASH55(Loop).timeNotSummer,
397 : OutputProcessor::TimeStepType::Zone,
398 : OutputProcessor::StoreType::Sum,
399 123 : state.dataHeatBal->Zone(Loop).Name);
400 246 : SetupOutputVariable(state,
401 : "Zone Thermal Comfort ASHRAE 55 Simple Model Winter Clothes Not Comfortable Time",
402 : Constant::Units::hr,
403 123 : state.dataThermalComforts->ThermalComfortInASH55(Loop).timeNotWinter,
404 : OutputProcessor::TimeStepType::Zone,
405 : OutputProcessor::StoreType::Sum,
406 123 : state.dataHeatBal->Zone(Loop).Name);
407 246 : SetupOutputVariable(state,
408 : "Zone Thermal Comfort ASHRAE 55 Simple Model Summer or Winter Clothes Not Comfortable Time",
409 : Constant::Units::hr,
410 123 : state.dataThermalComforts->ThermalComfortInASH55(Loop).timeNotEither,
411 : OutputProcessor::TimeStepType::Zone,
412 : OutputProcessor::StoreType::Sum,
413 123 : state.dataHeatBal->Zone(Loop).Name);
414 : }
415 412 : SetupOutputVariable(state,
416 : "Facility Thermal Comfort ASHRAE 55 Simple Model Summer Clothes Not Comfortable Time",
417 : Constant::Units::hr,
418 103 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer,
419 : OutputProcessor::TimeStepType::Zone,
420 : OutputProcessor::StoreType::Sum,
421 : "Facility");
422 412 : SetupOutputVariable(state,
423 : "Facility Thermal Comfort ASHRAE 55 Simple Model Winter Clothes Not Comfortable Time",
424 : Constant::Units::hr,
425 103 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter,
426 : OutputProcessor::TimeStepType::Zone,
427 : OutputProcessor::StoreType::Sum,
428 : "Facility");
429 412 : SetupOutputVariable(state,
430 : "Facility Thermal Comfort ASHRAE 55 Simple Model Summer or Winter Clothes Not Comfortable Time",
431 : Constant::Units::hr,
432 103 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either,
433 : OutputProcessor::TimeStepType::Zone,
434 : OutputProcessor::StoreType::Sum,
435 : "Facility");
436 :
437 103 : state.dataThermalComforts->ThermalComfortSetPoint.allocate(state.dataGlobal->NumOfZones);
438 226 : for (Loop = 1; Loop <= state.dataGlobal->NumOfZones; ++Loop) {
439 246 : SetupOutputVariable(state,
440 : "Zone Heating Setpoint Not Met Time",
441 : Constant::Units::hr,
442 123 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetHeating,
443 : OutputProcessor::TimeStepType::Zone,
444 : OutputProcessor::StoreType::Sum,
445 123 : state.dataHeatBal->Zone(Loop).Name);
446 246 : SetupOutputVariable(state,
447 : "Zone Heating Setpoint Not Met While Occupied Time",
448 : Constant::Units::hr,
449 123 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetHeatingOccupied,
450 : OutputProcessor::TimeStepType::Zone,
451 : OutputProcessor::StoreType::Sum,
452 123 : state.dataHeatBal->Zone(Loop).Name);
453 246 : SetupOutputVariable(state,
454 : "Zone Cooling Setpoint Not Met Time",
455 : Constant::Units::hr,
456 123 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetCooling,
457 : OutputProcessor::TimeStepType::Zone,
458 : OutputProcessor::StoreType::Sum,
459 123 : state.dataHeatBal->Zone(Loop).Name);
460 246 : SetupOutputVariable(state,
461 : "Zone Cooling Setpoint Not Met While Occupied Time",
462 : Constant::Units::hr,
463 123 : state.dataThermalComforts->ThermalComfortSetPoint(Loop).notMetCoolingOccupied,
464 : OutputProcessor::TimeStepType::Zone,
465 : OutputProcessor::StoreType::Sum,
466 123 : state.dataHeatBal->Zone(Loop).Name);
467 : }
468 :
469 412 : SetupOutputVariable(state,
470 : "Facility Heating Setpoint Not Met Time",
471 : Constant::Units::hr,
472 103 : state.dataThermalComforts->AnyZoneNotMetHeating,
473 : OutputProcessor::TimeStepType::Zone,
474 : OutputProcessor::StoreType::Sum,
475 : "Facility");
476 412 : SetupOutputVariable(state,
477 : "Facility Cooling Setpoint Not Met Time",
478 : Constant::Units::hr,
479 103 : state.dataThermalComforts->AnyZoneNotMetCooling,
480 : OutputProcessor::TimeStepType::Zone,
481 : OutputProcessor::StoreType::Sum,
482 : "Facility");
483 412 : SetupOutputVariable(state,
484 : "Facility Heating Setpoint Not Met While Occupied Time",
485 : Constant::Units::hr,
486 103 : state.dataThermalComforts->AnyZoneNotMetHeatingOccupied,
487 : OutputProcessor::TimeStepType::Zone,
488 : OutputProcessor::StoreType::Sum,
489 : "Facility");
490 412 : SetupOutputVariable(state,
491 : "Facility Cooling Setpoint Not Met While Occupied Time",
492 : Constant::Units::hr,
493 103 : state.dataThermalComforts->AnyZoneNotMetCoolingOccupied,
494 : OutputProcessor::TimeStepType::Zone,
495 : OutputProcessor::StoreType::Sum,
496 : "Facility");
497 :
498 103 : GetAngleFactorList(state);
499 :
500 103 : state.dataThermalComforts->ZoneOccHrs.dimension(state.dataGlobal->NumOfZones, 0.0);
501 103 : }
502 :
503 19000 : 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 29450 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
534 10450 : ++state.dataThermalComforts->PeopleNum) { // Is there a reason why this is a state variable and not a local variable?
535 :
536 10450 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
537 10450 : 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 10450 : if (present(PNum)) {
541 0 : if (state.dataThermalComforts->PeopleNum != PNum) {
542 8621 : continue;
543 : }
544 : }
545 :
546 : // If optional argument is used do not cycle regardless of thermal comfort reporting type
547 10450 : if ((!people.Fanger) && (!present(PNum))) {
548 8621 : continue;
549 : }
550 :
551 1829 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
552 1829 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum);
553 1829 : if (present(PNum)) {
554 0 : state.dataThermalComforts->AirTemp = Tset;
555 : } else {
556 1829 : state.dataThermalComforts->AirTemp = thisZoneHB.ZTAVComf;
557 : }
558 1829 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
559 0 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
560 0 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
561 0 : 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 0 : } 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 1829 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
573 : // Use mean air temp for calculating RH when thermal comfort control is used
574 1829 : if (present(PNum)) {
575 0 : state.dataThermalComforts->RelHum =
576 0 : PsyRhFnTdbWPb(state,
577 0 : state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum).MAT,
578 : thisZoneHB.airHumRatAvgComf,
579 0 : state.dataEnvrn->OutBaroPress);
580 : } else {
581 3658 : state.dataThermalComforts->RelHum =
582 1829 : PsyRhFnTdbWPb(state, state.dataThermalComforts->AirTemp, thisZoneHB.airHumRatAvgComf, state.dataEnvrn->OutBaroPress);
583 : }
584 1829 : people.TemperatureInZone = state.dataThermalComforts->AirTemp;
585 1829 : people.RelativeHumidityInZone = state.dataThermalComforts->RelHum * 100.0;
586 :
587 : // Metabolic rate of body (W/m2)
588 1829 : state.dataThermalComforts->ActLevel = people.activityLevelSched->getCurrentVal() / BodySurfArea;
589 : // Energy consumption by external work (W/m2)
590 1829 : state.dataThermalComforts->WorkEff = people.workEffSched->getCurrentVal() * state.dataThermalComforts->ActLevel;
591 : // Clothing unit
592 : Real64 IntermediateClothing;
593 1829 : switch (people.clothingType) {
594 1829 : case DataHeatBalance::ClothingType::InsulationSchedule:
595 1829 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
596 1829 : break;
597 0 : case DataHeatBalance::ClothingType::DynamicAshrae55:
598 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
599 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
600 0 : DynamicClothingModel(state);
601 0 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
602 0 : break;
603 0 : case DataHeatBalance::ClothingType::CalculationSchedule:
604 0 : IntermediateClothing = people.clothingMethodSched->getCurrentVal();
605 0 : if (IntermediateClothing == 1.0) {
606 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
607 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
608 0 : } else if (IntermediateClothing == 2.0) {
609 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
610 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
611 0 : DynamicClothingModel(state);
612 0 : 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 0 : break;
619 0 : default:
620 0 : ShowSevereError(state, format("PEOPLE=\"{}\", Incorrect Clothing Type", people.Name));
621 : }
622 :
623 1829 : 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 1829 : state.dataThermalComforts->AirVel = people.airVelocitySched->getCurrentVal();
633 : // Ensure air velocity within the reasonable range. Otherwise reccusive warnings is provided
634 1829 : 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 1829 : Real64 PMV = CalcFangerPMV(state,
653 1829 : state.dataThermalComforts->AirTemp,
654 1829 : state.dataThermalComforts->RadTemp,
655 1829 : state.dataThermalComforts->RelHum,
656 1829 : state.dataThermalComforts->AirVel,
657 1829 : state.dataThermalComforts->ActLevel,
658 1829 : state.dataThermalComforts->CloUnit,
659 1829 : state.dataThermalComforts->WorkEff);
660 :
661 1829 : comfort.FangerPMV = PMV;
662 : // Pass resulting PMV based on temperature setpoint (Tset) when using thermal comfort control
663 1829 : if (present(PNum)) {
664 0 : PMVResult = PMV;
665 : }
666 1829 : comfort.ThermalComfortMRT = state.dataThermalComforts->RadTemp;
667 1829 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
668 1829 : comfort.CloSurfTemp = state.dataThermalComforts->CloSurfTemp;
669 :
670 : // Calculate the Fanger PPD (Predicted Percentage of Dissatisfied), as a %
671 1829 : Real64 PPD = CalcFangerPPD(PMV);
672 1829 : comfort.FangerPPD = PPD;
673 : }
674 19000 : }
675 :
676 1834 : 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 1834 : int constexpr MaxIter(150); // Limit of iteration
685 1834 : 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 1834 : state.dataThermalComforts->VapPress = PsyPsatFnTemp(state, AirTemp); // use psych routines inside E+ , returns Pa
699 :
700 1834 : state.dataThermalComforts->VapPress *= RelHum; // in units of [Pa]
701 :
702 1834 : state.dataThermalComforts->IntHeatProd = ActLevel - WorkEff;
703 :
704 : // Compute the Corresponding Clothed Body Ratio
705 1834 : 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 1834 : if (CloUnit < 0.5) {
709 0 : state.dataThermalComforts->CloBodyRat = state.dataThermalComforts->CloBodyRat - 0.05 + 0.1 * CloUnit;
710 : }
711 :
712 1834 : state.dataThermalComforts->AbsRadTemp = RadTemp + TAbsConv;
713 1834 : state.dataThermalComforts->AbsAirTemp = AirTemp + TAbsConv;
714 :
715 1834 : state.dataThermalComforts->CloInsul = CloUnit * state.dataThermalComforts->CloBodyRat * 0.155; // Thermal resistance of the clothing // icl
716 :
717 1834 : P2 = state.dataThermalComforts->CloInsul * 3.96;
718 1834 : P3 = state.dataThermalComforts->CloInsul * 100.0;
719 1834 : P1 = state.dataThermalComforts->CloInsul * state.dataThermalComforts->AbsAirTemp; // p4
720 1834 : 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 1834 : state.dataThermalComforts->AbsCloSurfTemp = state.dataThermalComforts->AbsAirTemp + (35.5 - AirTemp) / (3.5 * (CloUnit + 0.1));
724 1834 : XN = state.dataThermalComforts->AbsCloSurfTemp / 100.0;
725 1834 : state.dataThermalComforts->HcFor = 12.1 * std::sqrt(AirVel); // Heat transfer coefficient by forced convection
726 1834 : state.dataThermalComforts->IterNum = 0;
727 1834 : XF = XN;
728 :
729 : // COMPUTE SURFACE TEMPERATURE OF CLOTHING BY ITERATIONS
730 11578 : while (((std::abs(XN - XF) > StopIterCrit) || (state.dataThermalComforts->IterNum == 0)) && (state.dataThermalComforts->IterNum < MaxIter)) {
731 9744 : XF = (XF + XN) / 2.0;
732 19488 : state.dataThermalComforts->HcNat =
733 9744 : 2.38 * root_4(std::abs(100.0 * XF - state.dataThermalComforts->AbsAirTemp)); // Heat transfer coefficient by natural convection
734 9744 : state.dataThermalComforts->Hc =
735 9744 : max(state.dataThermalComforts->HcFor, state.dataThermalComforts->HcNat); // Determination of convective heat transfer coefficient
736 9744 : XN = (P4 + P1 * state.dataThermalComforts->Hc - P2 * pow_4(XF)) / (100.0 + P3 * state.dataThermalComforts->Hc);
737 9744 : ++state.dataThermalComforts->IterNum;
738 9744 : if (state.dataThermalComforts->IterNum > MaxIter) {
739 0 : ShowWarningError(state, "Max iteration exceeded in CalcThermalFanger");
740 : }
741 : }
742 1834 : state.dataThermalComforts->AbsCloSurfTemp = 100.0 * XN;
743 1834 : 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 3668 : state.dataThermalComforts->RadHeatLoss =
752 1834 : 3.96 * state.dataThermalComforts->CloBodyRat *
753 1834 : (pow_4(state.dataThermalComforts->AbsCloSurfTemp * 0.01) - pow_4(state.dataThermalComforts->AbsRadTemp * 0.01));
754 :
755 1834 : state.dataThermalComforts->ConvHeatLoss = state.dataThermalComforts->CloBodyRat * state.dataThermalComforts->Hc *
756 1834 : (state.dataThermalComforts->CloSurfTemp - AirTemp); // Heat loss by convection
757 :
758 1834 : state.dataThermalComforts->DryHeatLoss = state.dataThermalComforts->RadHeatLoss + state.dataThermalComforts->ConvHeatLoss;
759 :
760 : // Evaporative heat loss
761 : // Heat loss by regulatory sweating
762 1834 : state.dataThermalComforts->EvapHeatLossRegComf = 0.0;
763 1834 : if (state.dataThermalComforts->IntHeatProd > 58.2) {
764 1445 : 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 3668 : state.dataThermalComforts->EvapHeatLossDiff =
771 1834 : 3.05 * 0.001 *
772 1834 : (5733.0 - 6.99 * state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->VapPress); // ln 440 in ASHRAE 55 Append. D
773 :
774 1834 : 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 3668 : state.dataThermalComforts->LatRespHeatLoss =
778 1834 : 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 1834 : state.dataThermalComforts->DryRespHeatLoss = 0.0014 * ActLevel * (34.0 - AirTemp); // Heat loss by dry respiration.
785 :
786 1834 : state.dataThermalComforts->RespHeatLoss = state.dataThermalComforts->LatRespHeatLoss + state.dataThermalComforts->DryRespHeatLoss;
787 :
788 1834 : state.dataThermalComforts->ThermSensTransCoef = 0.303 * std::exp(-0.036 * ActLevel) + 0.028; // Thermal transfer coefficient to calculate PMV
789 :
790 1834 : PMV = state.dataThermalComforts->ThermSensTransCoef * (state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->EvapHeatLoss -
791 1834 : state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->DryHeatLoss);
792 :
793 1834 : return PMV;
794 : }
795 :
796 1831 : Real64 CalcFangerPPD(Real64 PMV)
797 : {
798 : Real64 PPD;
799 1831 : Real64 expTest1 = -0.03353 * pow_4(PMV) - 0.2179 * pow_2(PMV);
800 1831 : if (expTest1 > DataPrecisionGlobals::EXP_LowerLimit) {
801 1604 : PPD = 100.0 - 95.0 * std::exp(expTest1);
802 : } else {
803 227 : PPD = 100.0;
804 : }
805 :
806 1831 : if (PPD < 0.0) {
807 0 : PPD = 0.0;
808 1831 : } else if (PPD > 100.0) {
809 0 : PPD = 100.0;
810 : }
811 1831 : return PPD;
812 : }
813 :
814 5 : Real64 CalcRelativeAirVelocity(Real64 AirVel, Real64 ActMet)
815 : {
816 5 : if (ActMet > 1) {
817 3 : return AirVel + 0.3 * (ActMet - 1);
818 : } else {
819 2 : return AirVel;
820 : }
821 : }
822 :
823 3 : void GetThermalComfortInputsASHRAE(EnergyPlusData &state)
824 : {
825 3 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
826 3 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
827 :
828 3 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
829 3 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum);
830 : // (var TA)
831 3 : state.dataThermalComforts->AirTemp = thisZoneHB.ZTAVComf;
832 3 : 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 3 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
840 : // (var RH)
841 6 : state.dataThermalComforts->RelHum =
842 3 : PsyRhFnTdbWPb(state, state.dataThermalComforts->AirTemp, thisZoneHB.airHumRatAvgComf, state.dataEnvrn->OutBaroPress);
843 : // Metabolic rate of body (W/m2) (var RM, M)
844 3 : state.dataThermalComforts->ActLevel = people.activityLevelSched->getCurrentVal() / BodySurfAreaPierce;
845 : // Energy consumption by external work (W/m2) (var WME)
846 3 : state.dataThermalComforts->WorkEff = people.workEffSched->getCurrentVal() * state.dataThermalComforts->ActLevel;
847 :
848 : // Clothing unit (var CLO)
849 : Real64 IntermediateClothing;
850 3 : switch (people.clothingType) {
851 3 : case DataHeatBalance::ClothingType::InsulationSchedule:
852 3 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
853 3 : break;
854 0 : case DataHeatBalance::ClothingType::DynamicAshrae55:
855 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
856 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
857 0 : DynamicClothingModel(state);
858 0 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
859 0 : 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 3 : state.dataThermalComforts->AirVel = people.airVelocitySched->getCurrentVal();
880 : // (var MET)
881 3 : state.dataThermalComforts->ActMet = state.dataThermalComforts->ActLevel / ActLevelConv;
882 3 : }
883 :
884 44 : 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 44 : constexpr Real64 CloFac(0.25); // Clothing factor determined experimentally (var KCLO)
890 44 : constexpr Real64 BodyWeight(69.9); // (var BODYWEIGHT)
891 44 : constexpr Real64 SweatContConst(170.0); // Proportionality constant for sweat control; g/m2.hr (var CSW)
892 44 : constexpr Real64 DriCoeffVasodilation(120); // driving coefficient for vasodilation (var CDIL)
893 44 : constexpr Real64 DriCoeffVasoconstriction(0.5); // (var CSTR)
894 44 : constexpr Real64 MaxSkinBloodFlow(90.0); // Max. value of skin blood flow
895 44 : constexpr Real64 MinSkinBloodFlow(0.5); // Min. value of skin blood flow
896 44 : 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 44 : constexpr Real64 SkinTempSet(33.7); // (var TempSkinNeutral)
904 44 : constexpr Real64 CoreTempSet(36.8); // (var TempCoreNeutral)
905 44 : constexpr Real64 SkinBloodFlowSet(6.3); // (var SkinBloodFlowNeutral)
906 44 : constexpr Real64 SkinMassRatSet(0.1); // (var ALFA)
907 :
908 44 : if (AirVel < 0.1) {
909 0 : AirVel = 0.1;
910 : }
911 :
912 : // (var VaporPressure)
913 44 : state.dataThermalComforts->VapPress = RelHum * CalcSatVapPressFromTempTorr(AirTemp);
914 44 : Real64 ActLevel = ActLevelConv * ActMet;
915 44 : state.dataThermalComforts->IntHeatProd = ActLevel - WorkEff;
916 :
917 : // Step 1: CALCULATE VARIABLESS THAT REMAIN CONSTANT FOR AN HOUR
918 44 : Real64 PInAtmospheres = state.dataEnvrn->OutBaroPress / 101325;
919 44 : Real64 RClo = CloUnit * 0.155; // (var RCL)
920 44 : Real64 TotCloFac = 1.0 + 0.15 * CloUnit;
921 44 : 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 44 : state.dataThermalComforts->SkinTemp = SkinTempSet;
926 44 : state.dataThermalComforts->CoreTemp = CoreTempSet;
927 44 : Real64 SkinBloodFlow = SkinBloodFlowSet;
928 44 : Real64 SkinMassRat = SkinMassRatSet;
929 :
930 : // Mass transfer equation between skin and environment
931 : // CloInsul is efficiency of mass transfer for CloUnit.
932 44 : 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 44 : EvapEff = 0.59 * std::pow(AirVel, -0.08); // (var ICL)
937 44 : state.dataThermalComforts->CloInsul = 0.45;
938 : }
939 :
940 44 : Real64 CorrectedHC = 3.0 * std::pow(PInAtmospheres, 0.53); // corrected convective heat transfer coefficient
941 44 : Real64 ForcedHC = 8.600001 * std::pow((AirVel * PInAtmospheres), 0.53); // forced convective heat transfer coefficient, W/(m2 °C) (CHCV)
942 44 : state.dataThermalComforts->Hc = std::max(CorrectedHC, ForcedHC); // (CHC)
943 44 : state.dataThermalComforts->Hr = 4.7; // (CHR)
944 44 : state.dataThermalComforts->EvapHeatLoss = 0.1 * ActMet;
945 44 : Real64 RAir = 1.0 / (TotCloFac * (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr)); // resistance of air layer to dry heat (RA)
946 44 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->Hr * RadTemp + state.dataThermalComforts->Hc * AirTemp) /
947 44 : (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr); // operative temperature (TOP)
948 44 : Real64 ActLevelStart = ActLevel; // ActLevel gets increased by shivering in the following
949 44 : 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 2684 : for (int IterMin = 1; IterMin <= 60; ++IterMin) {
956 : // Dry heat balance: solve for CloSurfTemp and Hr, GUESS CloSurfTemp TO START
957 5280 : state.dataThermalComforts->CloSurfTemp =
958 2640 : (RAir * state.dataThermalComforts->SkinTemp + RClo * state.dataThermalComforts->OpTemp) / (RAir + RClo);
959 2640 : bool converged = false;
960 5336 : while (!converged) {
961 5392 : state.dataThermalComforts->Hr =
962 2696 : 4.0 * StefanBoltz * std::pow((state.dataThermalComforts->CloSurfTemp + RadTemp) / 2.0 + 273.15, 3) * 0.72;
963 2696 : RAir = 1.0 / (TotCloFac * (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr));
964 2696 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->Hr * RadTemp + state.dataThermalComforts->Hc * AirTemp) /
965 2696 : (state.dataThermalComforts->Hc + state.dataThermalComforts->Hr);
966 2696 : Real64 CloSurfTempNew = (RAir * state.dataThermalComforts->SkinTemp + RClo * state.dataThermalComforts->OpTemp) / (RAir + RClo);
967 2696 : if (std::abs(CloSurfTempNew - state.dataThermalComforts->CloSurfTemp) <= 0.01) {
968 2640 : converged = true;
969 : }
970 2696 : state.dataThermalComforts->CloSurfTemp = CloSurfTempNew;
971 : }
972 :
973 : // CALCULATE THE COMBINED HEAT TRANSFER COEFF. (H)
974 2640 : state.dataThermalComforts->H = state.dataThermalComforts->Hr + state.dataThermalComforts->Hc;
975 : // Heat flow from Clothing surface to environment
976 2640 : state.dataThermalComforts->DryHeatLoss = (state.dataThermalComforts->SkinTemp - state.dataThermalComforts->OpTemp) / (RAir + RClo);
977 :
978 : // dry and latent respiratory heat losses
979 5280 : state.dataThermalComforts->LatRespHeatLoss =
980 2640 : 0.0023 * ActLevel * (44.0 - state.dataThermalComforts->VapPress); // latent heat loss due to respiration
981 2640 : state.dataThermalComforts->DryRespHeatLoss = 0.0014 * ActLevel * (34.0 - AirTemp);
982 :
983 2640 : 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 5280 : state.dataThermalComforts->HeatFlow =
987 2640 : (state.dataThermalComforts->CoreTemp - state.dataThermalComforts->SkinTemp) * (5.28 + 1.163 * SkinBloodFlow);
988 :
989 2640 : Real64 CoreHeatStorage = ActLevel - state.dataThermalComforts->HeatFlow - state.dataThermalComforts->RespHeatLoss -
990 2640 : WorkEff; // rate of energy storage in the core
991 2640 : Real64 SkinHeatStorage = state.dataThermalComforts->HeatFlow - state.dataThermalComforts->DryHeatLoss -
992 2640 : state.dataThermalComforts->EvapHeatLoss; // rate of energy storage in the skin
993 :
994 : // Thermal capacities
995 2640 : state.dataThermalComforts->CoreThermCap = 0.97 * (1 - SkinMassRat) * BodyWeight;
996 2640 : state.dataThermalComforts->SkinThermCap = 0.97 * SkinMassRat * BodyWeight;
997 :
998 : // Temperature changes in 1 minute
999 2640 : state.dataThermalComforts->CoreTempChange = (CoreHeatStorage * BodySurfAreaPierce / (state.dataThermalComforts->CoreThermCap * 60.0));
1000 2640 : state.dataThermalComforts->SkinTempChange = (SkinHeatStorage * BodySurfAreaPierce) / (state.dataThermalComforts->SkinThermCap * 60.0);
1001 :
1002 2640 : state.dataThermalComforts->CoreTemp += state.dataThermalComforts->CoreTempChange;
1003 2640 : state.dataThermalComforts->SkinTemp += state.dataThermalComforts->SkinTempChange;
1004 5280 : state.dataThermalComforts->AvgBodyTemp =
1005 2640 : SkinMassRat * state.dataThermalComforts->SkinTemp + (1.0 - SkinMassRat) * state.dataThermalComforts->CoreTemp;
1006 :
1007 : Real64 SkinThermSigWarm; // vasodilation signal (WARMS)
1008 : Real64 SkinThermSigCold; // vasoconstriction signal
1009 2640 : Real64 SkinSignal = state.dataThermalComforts->SkinTemp - SkinTempSet; // thermoregulatory control signal from the skin
1010 2640 : if (SkinSignal > 0) {
1011 765 : SkinThermSigWarm = SkinSignal;
1012 765 : SkinThermSigCold = 0.0;
1013 : } else {
1014 1875 : SkinThermSigCold = -SkinSignal;
1015 1875 : SkinThermSigWarm = 0.0;
1016 : }
1017 :
1018 : Real64 CoreThermSigWarm; // vasodialtion signal (WARMC)
1019 : Real64 CoreThermSigCold; // vasoconstriction signal
1020 2640 : Real64 CoreSignal = state.dataThermalComforts->CoreTemp - CoreTempSet; // thermoregulatory control signal from the skin, °C
1021 2640 : if (CoreSignal > 0) {
1022 2356 : CoreThermSigWarm = CoreSignal;
1023 2356 : CoreThermSigCold = 0.0;
1024 : } else {
1025 284 : CoreThermSigCold = -CoreSignal;
1026 284 : CoreThermSigWarm = 0.0;
1027 : }
1028 :
1029 : Real64 BodyThermSigWarm; // WARMB
1030 2640 : Real64 BodySignal = state.dataThermalComforts->AvgBodyTemp - AvgBodyTempSet;
1031 :
1032 2640 : if (BodySignal > 0) {
1033 666 : BodyThermSigWarm = BodySignal;
1034 : } else {
1035 1974 : BodyThermSigWarm = 0.0;
1036 : }
1037 :
1038 2640 : state.dataThermalComforts->VasodilationFac = DriCoeffVasodilation * CoreThermSigWarm;
1039 2640 : state.dataThermalComforts->VasoconstrictFac = DriCoeffVasoconstriction * SkinThermSigCold;
1040 2640 : 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 2640 : if (SkinBloodFlow < MinSkinBloodFlow) {
1043 0 : SkinBloodFlow = MinSkinBloodFlow;
1044 : }
1045 2640 : if (SkinBloodFlow > MaxSkinBloodFlow) {
1046 0 : SkinBloodFlow = MaxSkinBloodFlow;
1047 : }
1048 2640 : SkinMassRat = 0.0417737 + 0.7451832 / (SkinBloodFlow + 0.585417); // ratio of skin-core masses change with SkinBloodFlow
1049 :
1050 2640 : Real64 RegSweat = SweatContConst * BodyThermSigWarm * std::exp(SkinThermSigWarm / 10.7); // control of regulatory sweating
1051 2640 : if (RegSweat > RegSweatMax) {
1052 0 : RegSweat = RegSweatMax;
1053 : }
1054 2640 : state.dataThermalComforts->EvapHeatLossRegSweat = 0.68 * RegSweat; // heat lost by vaporization sweat
1055 :
1056 : // adjustment of metabolic heat due to shivering (Stolwijk, Hardy)
1057 2640 : state.dataThermalComforts->ShivResponse = 19.4 * SkinThermSigCold * CoreThermSigCold;
1058 2640 : ActLevel = ActLevelStart + state.dataThermalComforts->ShivResponse;
1059 :
1060 : // Evaluation of heat transfer by evaporation at skin surface
1061 2640 : Real64 AirEvapHeatResist = 1.0 / (LewisRatio * TotCloFac * state.dataThermalComforts->Hc); // evaporative resistance air layer
1062 2640 : Real64 CloEvapHeatResist = RClo / (LewisRatio * state.dataThermalComforts->CloInsul);
1063 2640 : Real64 TotEvapHeatResist = AirEvapHeatResist + CloEvapHeatResist;
1064 2640 : state.dataThermalComforts->SatSkinVapPress = CalcSatVapPressFromTempTorr(state.dataThermalComforts->SkinTemp); // PSSK
1065 5280 : state.dataThermalComforts->EvapHeatLossMax =
1066 2640 : (state.dataThermalComforts->SatSkinVapPress - state.dataThermalComforts->VapPress) / TotEvapHeatResist; // TotEvapHeatResist;
1067 5280 : state.dataThermalComforts->SkinWetSweat =
1068 2640 : state.dataThermalComforts->EvapHeatLossRegSweat /
1069 2640 : state.dataThermalComforts->EvapHeatLossMax; // ratio heat loss sweating to max heat loss sweating
1070 :
1071 5280 : state.dataThermalComforts->SkinWetDiff =
1072 2640 : (1.0 - state.dataThermalComforts->SkinWetSweat) * 0.06; // 0.06 if SkinWetDiff for nonsweating skin --- Kerslake
1073 2640 : state.dataThermalComforts->EvapHeatLossDiff = state.dataThermalComforts->SkinWetDiff * state.dataThermalComforts->EvapHeatLossMax;
1074 2640 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->EvapHeatLossRegSweat + state.dataThermalComforts->EvapHeatLossDiff;
1075 2640 : state.dataThermalComforts->SkinWetTot = state.dataThermalComforts->EvapHeatLoss / state.dataThermalComforts->EvapHeatLossMax;
1076 :
1077 : // Beginning of dripping (Sweat not evaporated on skin surface)
1078 2640 : if (state.dataThermalComforts->SkinWetTot >= EvapEff) {
1079 0 : state.dataThermalComforts->SkinWetTot = EvapEff;
1080 0 : state.dataThermalComforts->SkinWetSweat = EvapEff / 0.94;
1081 0 : state.dataThermalComforts->EvapHeatLossRegSweat =
1082 0 : state.dataThermalComforts->SkinWetSweat * state.dataThermalComforts->EvapHeatLossMax;
1083 0 : state.dataThermalComforts->SkinWetDiff = (1.0 - state.dataThermalComforts->SkinWetSweat) * 0.06;
1084 0 : state.dataThermalComforts->EvapHeatLossDiff = state.dataThermalComforts->SkinWetDiff * state.dataThermalComforts->EvapHeatLossMax;
1085 0 : state.dataThermalComforts->EvapHeatLoss =
1086 0 : state.dataThermalComforts->EvapHeatLossRegSweat + state.dataThermalComforts->EvapHeatLossDiff;
1087 : }
1088 :
1089 : // When EvapHeatLossMax<0. condensation on skin occurs.
1090 2640 : if (state.dataThermalComforts->EvapHeatLossMax < 0.0) {
1091 0 : state.dataThermalComforts->SkinWetDiff = 0.0;
1092 0 : state.dataThermalComforts->EvapHeatLossDiff = 0.0;
1093 0 : state.dataThermalComforts->EvapHeatLoss = 0.0;
1094 0 : state.dataThermalComforts->SkinWetTot = EvapEff;
1095 0 : state.dataThermalComforts->SkinWetSweat = EvapEff;
1096 0 : state.dataThermalComforts->EvapHeatLossRegSweat = 0.0;
1097 : }
1098 : // Vapor pressure at skin (as measured by dewpoint sensors)
1099 5280 : state.dataThermalComforts->SkinVapPress = state.dataThermalComforts->SkinWetTot * state.dataThermalComforts->SatSkinVapPress +
1100 2640 : (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 44 : 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 44 : Real64 EffectSkinHeatLoss = state.dataThermalComforts->DryHeatLoss + state.dataThermalComforts->EvapHeatLoss;
1109 : // ET*(standardization humidity/REAL(r64) CloUnit, StdAtm and Hc)
1110 44 : state.dataThermalComforts->CloBodyRat = 1.0 + CloFac * CloUnit;
1111 : Real64 EffectCloUnit =
1112 44 : CloUnit - (state.dataThermalComforts->CloBodyRat - 1.0) / (0.155 * state.dataThermalComforts->CloBodyRat * state.dataThermalComforts->H);
1113 44 : Real64 EffectCloThermEff = 1.0 / (1.0 + 0.155 * state.dataThermalComforts->Hc * EffectCloUnit);
1114 88 : state.dataThermalComforts->CloPermeatEff =
1115 44 : 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 44 : Real64 ET = state.dataThermalComforts->SkinTemp - EffectSkinHeatLoss / (state.dataThermalComforts->H * EffectCloThermEff);
1118 : Real64 EnergyBalErrET;
1119 : while (true) {
1120 865 : Real64 StdVapPressET = CalcSatVapPressFromTempTorr(ET); // THE STANDARD VAPOR PRESSURE AT THE EFFECTIVE TEMP : StdVapPressET
1121 865 : EnergyBalErrET = EffectSkinHeatLoss - state.dataThermalComforts->H * EffectCloThermEff * (state.dataThermalComforts->SkinTemp - ET) -
1122 865 : state.dataThermalComforts->SkinWetTot * LewisRatio * state.dataThermalComforts->Hc *
1123 865 : state.dataThermalComforts->CloPermeatEff * (state.dataThermalComforts->SatSkinVapPress - StdVapPressET / 2.0);
1124 865 : if (EnergyBalErrET >= 0.0) {
1125 44 : break;
1126 : }
1127 821 : ET += 0.1;
1128 821 : }
1129 44 : state.dataThermalComforts->EffTemp = ET;
1130 :
1131 : // Standard effective temperature SET* standardized humidity. Hc, CloUnit, StdAtm normalized for given ActLel AirVel
1132 : // Standard environment
1133 44 : Real64 StdHr = state.dataThermalComforts->Hr;
1134 : Real64 StdHc; // standard conv. heat tr. coeff. (level walking/still air)
1135 44 : 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 44 : StdHc = 5.66 * std::pow(ActMet - 0.85, 0.39);
1139 : }
1140 44 : if (StdHc <= 3.0) {
1141 43 : StdHc = 3.0;
1142 : }
1143 44 : Real64 StdH = StdHc + StdHr; // StdH Standard combined heat transfer coefficient
1144 : // standard MET - StdCloUnit relation gives SET* = 24 C when PMV = 0
1145 44 : Real64 StdCloUnit = 1.52 / (ActMet - WorkEff / ActLevelConv + 0.6944) - 0.1835; // RCLOS
1146 44 : Real64 StdRClo = 0.155 * StdCloUnit; // RCLS
1147 44 : Real64 StdCloBodyRat = 1.0 + CloFac * StdCloUnit; // FACLS
1148 44 : Real64 StdEffectCloThermEff = 1.0 / (1.0 + 0.155 * StdCloBodyRat * StdH * StdCloUnit); // FCLS
1149 44 : Real64 StdCloInsul = state.dataThermalComforts->CloInsul * StdHc / StdH * (1 - StdEffectCloThermEff) /
1150 44 : (StdHc / StdH - state.dataThermalComforts->CloInsul * StdEffectCloThermEff);
1151 44 : Real64 StdREvap = 1.0 / (LewisRatio * StdCloBodyRat * StdHc);
1152 44 : Real64 StdREvapClo = StdRClo / (LewisRatio * StdCloInsul);
1153 44 : Real64 StdHEvap = 1.0 / (StdREvap + StdREvapClo);
1154 44 : Real64 StdRAir = 1.0 / (StdCloBodyRat * StdH);
1155 44 : Real64 StdHDry = 1.0 / (StdRAir + StdRClo);
1156 :
1157 : // Get a low approximation for SET* and solve balance equ. by iteration
1158 44 : Real64 StdEffectSkinHeatLoss = state.dataThermalComforts->DryHeatLoss + state.dataThermalComforts->EvapHeatLoss;
1159 44 : Real64 OldSET = round((state.dataThermalComforts->SkinTemp - StdEffectSkinHeatLoss / StdHDry) * 100) / 100;
1160 44 : Real64 delta = 0.0001;
1161 44 : Real64 err = 100.0;
1162 138 : while (std::abs(err) > 0.01) {
1163 94 : Real64 StdVapPressSET_1 = CalcSatVapPressFromTempTorr(OldSET); // StdVapPressSET *= VapPressConv;
1164 : Real64 EnergyBalErrSET_1 =
1165 94 : StdEffectSkinHeatLoss - StdHDry * (state.dataThermalComforts->SkinTemp - OldSET) -
1166 94 : state.dataThermalComforts->SkinWetTot * StdHEvap * (state.dataThermalComforts->SatSkinVapPress - StdVapPressSET_1 / 2.0);
1167 94 : Real64 StdVapPressSET_2 = CalcSatVapPressFromTempTorr(OldSET + delta);
1168 : Real64 EnergyBalErrSET_2 =
1169 94 : StdEffectSkinHeatLoss - StdHDry * (state.dataThermalComforts->SkinTemp - (OldSET + delta)) -
1170 94 : state.dataThermalComforts->SkinWetTot * StdHEvap * (state.dataThermalComforts->SatSkinVapPress - StdVapPressSET_2 / 2.0);
1171 94 : Real64 NewSET = OldSET - delta * EnergyBalErrSET_1 / (EnergyBalErrSET_2 - EnergyBalErrSET_1);
1172 94 : err = NewSET - OldSET;
1173 94 : OldSET = NewSET;
1174 : }
1175 44 : Real64 SET = OldSET;
1176 : // PMV*(PMVET in prgm) uses ET instead of OpTemp
1177 44 : state.dataThermalComforts->DryHeatLossET = StdH * StdEffectCloThermEff * (state.dataThermalComforts->SkinTemp - ET);
1178 : // SPMV*(PMVSET in prgm) uses SET instead of OpTemp
1179 44 : state.dataThermalComforts->DryHeatLossSET = StdH * StdEffectCloThermEff * (state.dataThermalComforts->SkinTemp - SET);
1180 44 : return SET;
1181 : }
1182 :
1183 0 : 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 0 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1189 0 : ++state.dataThermalComforts->PeopleNum) {
1190 0 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1191 0 : if (!people.Pierce) {
1192 0 : continue;
1193 : }
1194 :
1195 0 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1196 :
1197 : // STEP 1: Get input (TA, TR, RH, VEL, CLO, MET, WME)
1198 0 : GetThermalComfortInputsASHRAE(state);
1199 :
1200 : // STEP 2: Calculate SET.
1201 0 : Real64 SET = CalcStandardEffectiveTemp(state,
1202 0 : state.dataThermalComforts->AirTemp,
1203 0 : state.dataThermalComforts->RadTemp,
1204 0 : state.dataThermalComforts->RelHum,
1205 0 : state.dataThermalComforts->AirVel,
1206 0 : state.dataThermalComforts->ActMet,
1207 0 : state.dataThermalComforts->CloUnit,
1208 0 : state.dataThermalComforts->WorkEff);
1209 :
1210 : // STEP 3: Report SET related variables.
1211 : // Fanger's comfort equation. Thermal transfer coefficient to calculate PMV
1212 0 : 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 0 : state.dataThermalComforts->EvapHeatLossRegComf = (state.dataThermalComforts->IntHeatProd - ActLevelConv) * 0.42;
1215 0 : comfort.PiercePMVET =
1216 0 : state.dataThermalComforts->ThermSensTransCoef *
1217 0 : (state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->DryHeatLossET -
1218 0 : state.dataThermalComforts->EvapHeatLossDiff - state.dataThermalComforts->EvapHeatLossRegComf);
1219 0 : comfort.PiercePMVSET =
1220 0 : state.dataThermalComforts->ThermSensTransCoef *
1221 0 : (state.dataThermalComforts->IntHeatProd - state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->DryHeatLossSET -
1222 0 : 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 0 : comfort.PierceDISC = 5.0 * (state.dataThermalComforts->EvapHeatLossRegSweat - state.dataThermalComforts->EvapHeatLossRegComf) /
1226 0 : (state.dataThermalComforts->EvapHeatLossMax - state.dataThermalComforts->EvapHeatLossRegComf -
1227 0 : 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 0 : 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 0 : 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 0 : if (state.dataThermalComforts->AvgBodyTemp > AvgBodyTempLow) {
1238 0 : comfort.PierceTSENS = 4.7 * (state.dataThermalComforts->AvgBodyTemp - AvgBodyTempLow) / (AvgBodyTempHigh - AvgBodyTempLow);
1239 :
1240 : } else {
1241 0 : comfort.PierceTSENS = 0.68175 * (state.dataThermalComforts->AvgBodyTemp - AvgBodyTempLow);
1242 0 : comfort.PierceDISC = comfort.PierceTSENS;
1243 : }
1244 :
1245 0 : comfort.ThermalComfortMRT = state.dataThermalComforts->RadTemp;
1246 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1247 0 : comfort.PierceSET = SET;
1248 : }
1249 0 : }
1250 :
1251 2 : 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 4 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1257 2 : ++state.dataThermalComforts->PeopleNum) {
1258 2 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1259 2 : if (!people.CoolingEffectASH55) {
1260 0 : continue;
1261 : }
1262 :
1263 2 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1264 :
1265 : // Get input (TA, TR, RH, VEL, CLO, MET, WME)
1266 2 : GetThermalComfortInputsASHRAE(state);
1267 :
1268 : // Calculate elevated air cooling effect using the SET function.
1269 2 : Real64 CoolingEffect = 0;
1270 : Real64 CoolingEffectAdjustedPMV;
1271 2 : CalcCoolingEffectAdjustedPMV(state, CoolingEffect, CoolingEffectAdjustedPMV);
1272 :
1273 : // Report.
1274 2 : comfort.CoolingEffectASH55 = CoolingEffect;
1275 2 : comfort.CoolingEffectAdjustedPMVASH55 = CoolingEffectAdjustedPMV;
1276 2 : comfort.CoolingEffectAdjustedPPDASH55 = CalcFangerPPD(CoolingEffectAdjustedPMV);
1277 : }
1278 2 : }
1279 :
1280 2 : void CalcCoolingEffectAdjustedPMV(EnergyPlusData &state, Real64 &CoolingEffect, Real64 &CoolingEffectAdjustedPMV)
1281 : {
1282 2 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1283 :
1284 : // Calculate SET without cooling effect.
1285 2 : Real64 RelAirVel = CalcRelativeAirVelocity(state.dataThermalComforts->AirVel, state.dataThermalComforts->ActMet);
1286 2 : Real64 SET = CalcStandardEffectiveTemp(state,
1287 2 : state.dataThermalComforts->AirTemp,
1288 2 : state.dataThermalComforts->RadTemp,
1289 2 : state.dataThermalComforts->RelHum,
1290 : RelAirVel,
1291 2 : state.dataThermalComforts->ActMet,
1292 2 : state.dataThermalComforts->CloUnit,
1293 2 : 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 2 : Real64 ASHRAE55PMV = CalcFangerPMV(state,
1297 2 : state.dataThermalComforts->AirTemp,
1298 2 : state.dataThermalComforts->RadTemp,
1299 2 : state.dataThermalComforts->RelHum,
1300 : RelAirVel,
1301 2 : state.dataThermalComforts->ActLevel,
1302 2 : state.dataThermalComforts->CloUnit,
1303 2 : state.dataThermalComforts->WorkEff);
1304 :
1305 2 : Real64 StillAirVel = 0.1;
1306 30 : auto ce_root_function = [&state, &StillAirVel, &SET](Real64 x) {
1307 210 : return CalcStandardEffectiveTemp(state,
1308 30 : state.dataThermalComforts->AirTemp - x,
1309 30 : state.dataThermalComforts->RadTemp - x,
1310 30 : state.dataThermalComforts->RelHum,
1311 : StillAirVel,
1312 30 : state.dataThermalComforts->ActMet,
1313 30 : state.dataThermalComforts->CloUnit,
1314 30 : state.dataThermalComforts->WorkEff) -
1315 30 : SET;
1316 2 : };
1317 :
1318 28 : auto ce_root_termination = [](Real64 min, Real64 max) { return abs(max - min) <= 0.01; };
1319 2 : Real64 lowerBound = 0.0;
1320 2 : Real64 upperBound = 50.0;
1321 :
1322 : // We have yet another solver?
1323 : try {
1324 2 : std::pair<Real64, Real64> solverResult = boost::math::tools::bisect(ce_root_function, lowerBound, upperBound, ce_root_termination);
1325 2 : 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 2 : if (CoolingEffect > 0) {
1335 2 : CoolingEffectAdjustedPMV = CalcFangerPMV(state,
1336 2 : state.dataThermalComforts->AirTemp - CoolingEffect,
1337 2 : state.dataThermalComforts->RadTemp - CoolingEffect,
1338 2 : state.dataThermalComforts->RelHum,
1339 : StillAirVel,
1340 2 : state.dataThermalComforts->ActLevel,
1341 2 : state.dataThermalComforts->CloUnit,
1342 2 : state.dataThermalComforts->WorkEff);
1343 : } else {
1344 0 : CoolingEffectAdjustedPMV = ASHRAE55PMV;
1345 : }
1346 2 : }
1347 :
1348 1 : void CalcThermalComfortAnkleDraftASH(EnergyPlusData &state)
1349 : {
1350 : // This subroutine calculates ASHRAE Ankle draft PPD
1351 : // Reference: ANSI/ASHRAE Standard 55-2017 Appendix I.
1352 :
1353 2 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1354 1 : ++state.dataThermalComforts->PeopleNum) {
1355 :
1356 1 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1357 1 : if (!people.AnkleDraftASH55) {
1358 0 : continue;
1359 : }
1360 :
1361 1 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1362 :
1363 1 : GetThermalComfortInputsASHRAE(state);
1364 1 : Real64 RelAirVel = CalcRelativeAirVelocity(state.dataThermalComforts->AirVel, state.dataThermalComforts->ActMet);
1365 1 : Real64 PPD_AD = -1.0;
1366 1 : if (state.dataThermalComforts->ActMet < 1.3 && state.dataThermalComforts->CloUnit < 0.7 && RelAirVel < 0.2) {
1367 1 : Real64 AnkleAirVel = people.ankleAirVelocitySched->getCurrentVal();
1368 1 : Real64 PMV = CalcFangerPMV(state,
1369 1 : state.dataThermalComforts->AirTemp,
1370 1 : state.dataThermalComforts->RadTemp,
1371 1 : state.dataThermalComforts->RelHum,
1372 : RelAirVel,
1373 1 : state.dataThermalComforts->ActLevel,
1374 1 : state.dataThermalComforts->CloUnit,
1375 1 : state.dataThermalComforts->WorkEff);
1376 1 : 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 0 : 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 1 : comfort.AnkleDraftPPDASH55 = PPD_AD;
1416 : }
1417 1 : }
1418 :
1419 0 : 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 0 : 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 0 : 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 0 : TimeInterval = 1.0;
1470 0 : TSVMax = 4.0;
1471 0 : StartDayNum = 1;
1472 0 : LastDayNum = 1;
1473 0 : IncreDayNum = 1;
1474 0 : TimeExpos = 1.0;
1475 0 : TempDiffer = 0.5;
1476 :
1477 0 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
1478 0 : ++state.dataThermalComforts->PeopleNum) {
1479 : // THE NEXT SIX VARIABLES WILL BE READ IN FROM INPUT DECK
1480 0 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
1481 0 : if (!people.KSU) {
1482 0 : continue;
1483 : }
1484 :
1485 0 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
1486 :
1487 0 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
1488 0 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum);
1489 :
1490 0 : state.dataThermalComforts->AirTemp = thisZoneHB.ZTAVComf;
1491 0 : 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 0 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
1498 0 : state.dataThermalComforts->RelHum =
1499 0 : PsyRhFnTdbWPb(state, state.dataThermalComforts->AirTemp, thisZoneHB.airHumRatAvgComf, state.dataEnvrn->OutBaroPress);
1500 0 : state.dataThermalComforts->ActLevel = people.activityLevelSched->getCurrentVal() / BodySurfArea;
1501 0 : state.dataThermalComforts->WorkEff = people.workEffSched->getCurrentVal() * state.dataThermalComforts->ActLevel;
1502 :
1503 0 : switch (people.clothingType) {
1504 0 : case DataHeatBalance::ClothingType::InsulationSchedule: {
1505 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
1506 0 : } break;
1507 0 : case DataHeatBalance::ClothingType::DynamicAshrae55: {
1508 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1509 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
1510 0 : DynamicClothingModel(state);
1511 0 : state.dataThermalComforts->CloUnit = comfort.ClothingValue;
1512 0 : } break;
1513 0 : case DataHeatBalance::ClothingType::CalculationSchedule: {
1514 0 : IntermediateClothing = people.clothingMethodSched->getCurrentVal();
1515 0 : if (IntermediateClothing == 1.0) {
1516 0 : state.dataThermalComforts->CloUnit = people.clothingSched->getCurrentVal();
1517 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
1518 0 : } else if (IntermediateClothing == 2.0) {
1519 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1520 0 : comfort.ClothingValue = state.dataThermalComforts->CloUnit;
1521 0 : DynamicClothingModel(state);
1522 0 : 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 0 : } break;
1529 0 : default:
1530 0 : ShowSevereError(state, format("PEOPLE=\"{}\", Incorrect Clothing Type", people.Name));
1531 : }
1532 :
1533 0 : state.dataThermalComforts->AirVel = people.airVelocitySched->getCurrentVal();
1534 0 : 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 0 : BodyWt = 70.0;
1538 0 : state.dataThermalComforts->CoreTemp = 37.0;
1539 0 : state.dataThermalComforts->SkinTemp = 31.0;
1540 :
1541 : // CALCULATIONS NEEDED FOR THE PASSIVE STATE EQUATIONS
1542 0 : state.dataThermalComforts->CoreThermCap = 0.9 * BodyWt * 0.97 / BodySurfArea;
1543 0 : state.dataThermalComforts->SkinThermCap = 0.1 * BodyWt * 0.97 / BodySurfArea;
1544 : // KERSLAKE'S FORMULA (0.05<AirVel<5. M/S)
1545 0 : if (state.dataThermalComforts->AirVel < 0.137) {
1546 0 : state.dataThermalComforts->AirVel = 0.137;
1547 : }
1548 0 : state.dataThermalComforts->Hc = 8.3 * std::sqrt(state.dataThermalComforts->AirVel);
1549 0 : EmissAvg = RadSurfEff * CloEmiss + (1.0 - RadSurfEff) * 1.0;
1550 : // IBERALL EQUATION
1551 0 : state.dataThermalComforts->Hr = EmissAvg * (3.87 + 0.031 * state.dataThermalComforts->RadTemp);
1552 0 : state.dataThermalComforts->H = state.dataThermalComforts->Hr + state.dataThermalComforts->Hc;
1553 0 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->Hc * state.dataThermalComforts->AirTemp +
1554 0 : state.dataThermalComforts->Hr * state.dataThermalComforts->RadTemp) /
1555 0 : state.dataThermalComforts->H;
1556 0 : state.dataThermalComforts->VapPress = CalcSatVapPressFromTemp(state.dataThermalComforts->AirTemp);
1557 0 : state.dataThermalComforts->VapPress *= state.dataThermalComforts->RelHum;
1558 0 : state.dataThermalComforts->CloBodyRat = 1.0 + 0.2 * state.dataThermalComforts->CloUnit;
1559 0 : state.dataThermalComforts->CloThermEff =
1560 0 : 1.0 / (1.0 + 0.155 * state.dataThermalComforts->H * state.dataThermalComforts->CloBodyRat * state.dataThermalComforts->CloUnit);
1561 0 : state.dataThermalComforts->CloPermeatEff = 1.0 / (1.0 + 0.143 * state.dataThermalComforts->Hc * state.dataThermalComforts->CloUnit);
1562 : // BASIC INFORMATION FOR THERMAL SENSATION.
1563 0 : IntHeatProdMet = state.dataThermalComforts->IntHeatProd / ActLevelConv;
1564 0 : IntHeatProdMetMax = max(1.0, IntHeatProdMet);
1565 0 : ThermCndctNeut = 12.05 * std::exp(0.2266 * (IntHeatProdMetMax - 1.0));
1566 0 : SkinWetNeut = 0.02 + 0.4 * (1.0 - std::exp(-0.6 * (IntHeatProdMetMax - 1.0)));
1567 0 : ThermCndctMin = (ThermCndctNeut - 5.3) * 0.26074074 + 5.3;
1568 0 : Real64 const ThemCndct_75_fac(1.0 / (75.0 - ThermCndctNeut));
1569 0 : 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 0 : assert(IncreDayNum > 0); // Autodesk:F2C++ Loop setup assumption
1573 0 : for (NumDay = StartDayNum; NumDay <= LastDayNum; NumDay += IncreDayNum) {
1574 : // INITIAL CONDITIONS IN AN EXPOSURE
1575 0 : DayNum = double(NumDay);
1576 0 : state.dataThermalComforts->Time = 0.0;
1577 0 : state.dataThermalComforts->TimeChange = 0.01;
1578 0 : SweatSuppFac = 1.0;
1579 0 : state.dataThermalComforts->Temp(1) = state.dataThermalComforts->CoreTemp;
1580 0 : state.dataThermalComforts->Temp(2) = state.dataThermalComforts->SkinTemp;
1581 0 : state.dataThermalComforts->Coeff(1) = state.dataThermalComforts->Coeff(2) = 0.0;
1582 : // PHYSIOLOGICAL ADJUSTMENTS IN HEAT ACCLIMATION.
1583 0 : state.dataThermalComforts->AcclPattern = 1.0 - std::exp(-0.12 * (DayNum - 1.0));
1584 0 : state.dataThermalComforts->CoreTempNeut = 36.9 - 0.6 * state.dataThermalComforts->AcclPattern;
1585 0 : state.dataThermalComforts->SkinTempNeut = 33.8 - 1.6 * state.dataThermalComforts->AcclPattern;
1586 0 : state.dataThermalComforts->ActLevel -= 0.07 * state.dataThermalComforts->ActLevel * state.dataThermalComforts->AcclPattern;
1587 0 : Real64 const SkinTempNeut_fac(1.0 / (1.0 - SkinWetNeut));
1588 : // CALCULATION OF CoreTempChange/TempChange & SkinTempChange/TempChange
1589 0 : 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 0 : SkinWetFac = (state.dataThermalComforts->SkinWetSweat - SkinWetNeut) * SkinTempNeut_fac;
1594 0 : state.dataThermalComforts->VasodilationFac = (state.dataThermalComforts->ThermCndct - ThermCndctNeut) * ThemCndct_75_fac;
1595 0 : 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 0 : if (state.dataThermalComforts->VasodilationFac < 0) {
1600 0 : comfort.KsuTSV = -1.46153 * state.dataThermalComforts->VasoconstrictFac +
1601 0 : 3.74721 * pow_2(state.dataThermalComforts->VasoconstrictFac) -
1602 0 : 6.168856 * pow_3(state.dataThermalComforts->VasoconstrictFac);
1603 : } else {
1604 0 : comfort.KsuTSV = (5.0 - 6.56 * (state.dataThermalComforts->RelHum - 0.50)) * SkinWetFac;
1605 0 : if (comfort.KsuTSV > TSVMax) {
1606 0 : comfort.KsuTSV = TSVMax;
1607 : }
1608 : }
1609 :
1610 0 : comfort.ThermalComfortMRT = state.dataThermalComforts->RadTemp;
1611 0 : comfort.ThermalComfortOpTemp = (state.dataThermalComforts->RadTemp + state.dataThermalComforts->AirTemp) / 2.0;
1612 :
1613 0 : state.dataThermalComforts->CoreTemp = state.dataThermalComforts->Temp(1);
1614 0 : state.dataThermalComforts->SkinTemp = state.dataThermalComforts->Temp(2);
1615 0 : state.dataThermalComforts->EvapHeatLossSweatPrev = state.dataThermalComforts->EvapHeatLossSweat;
1616 :
1617 0 : RKG(state,
1618 : TempIndiceNum,
1619 0 : state.dataThermalComforts->TimeChange,
1620 0 : state.dataThermalComforts->Time,
1621 0 : state.dataThermalComforts->Temp,
1622 0 : state.dataThermalComforts->TempChange,
1623 0 : state.dataThermalComforts->Coeff);
1624 :
1625 0 : if (state.dataThermalComforts->Time > TimeExpos) {
1626 0 : break;
1627 : }
1628 : }
1629 : }
1630 : }
1631 0 : }
1632 :
1633 0 : 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 0 : 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 0 : CoreSignalWarm = state.dataThermalComforts->CoreTemp - 36.98;
1701 0 : SkinSignalWarm = state.dataThermalComforts->SkinTemp - 33.8;
1702 0 : SkinSignalCold = 32.1 - state.dataThermalComforts->SkinTemp;
1703 0 : CoreSignalSkinSens = state.dataThermalComforts->CoreTemp - 35.15;
1704 0 : CoreSignalWarmMax = max(0.0, CoreSignalWarm);
1705 0 : SkinSignalWarmMax = max(0.0, SkinSignalWarm);
1706 0 : SkinSignalColdMax = max(0.0, SkinSignalCold);
1707 :
1708 : // SIGNALS FOR EvapHeatLossSweat.
1709 0 : CoreTempSweat = state.dataThermalComforts->CoreTemp;
1710 0 : if (CoreTempSweat > 38.29) {
1711 0 : CoreTempSweat = 38.29;
1712 : }
1713 0 : CoreSignalSweatWarm = CoreTempSweat - state.dataThermalComforts->CoreTempNeut;
1714 0 : SkinTempSweat = state.dataThermalComforts->SkinTemp;
1715 0 : if (SkinTempSweat > 36.1) {
1716 0 : SkinTempSweat = 36.1;
1717 : }
1718 0 : SkinSignalSweatWarm = SkinTempSweat - state.dataThermalComforts->SkinTempNeut;
1719 0 : CoreSignalSweatMax = max(0.0, CoreSignalSweatWarm);
1720 0 : SkinSignalSweatMax = max(0.0, SkinSignalSweatWarm);
1721 0 : SkinSignalSweatCold = 33.37 - state.dataThermalComforts->SkinTemp;
1722 0 : if (state.dataThermalComforts->SkinTempNeut < 33.37) {
1723 0 : SkinSignalSweatCold = state.dataThermalComforts->SkinTempNeut - state.dataThermalComforts->SkinTemp;
1724 : }
1725 0 : SkinSignalSweatColdMax = max(0.0, SkinSignalSweatCold);
1726 :
1727 : // SIGNALS FOR SHIVERING.
1728 0 : CoreSignalShiv = 36.9 - state.dataThermalComforts->CoreTemp;
1729 0 : SkinSignalShiv = 32.5 - state.dataThermalComforts->SkinTemp;
1730 0 : CoreSignalShivMax = max(0.0, CoreSignalShiv);
1731 0 : SkinSignalShivMax = max(0.0, SkinSignalShiv);
1732 :
1733 : // CONTROLLING FUNCTIONS :
1734 : // SHIVERING RESPONSE IN W/M**2.
1735 0 : state.dataThermalComforts->ShivResponse = 20.0 * CoreSignalShivMax * SkinSignalShivMax + 5.0 * SkinSignalShivMax;
1736 0 : if (state.dataThermalComforts->CoreTemp >= 37.1) {
1737 0 : state.dataThermalComforts->ShivResponse = 0.0;
1738 : }
1739 :
1740 : // SWEAT FUNCTION IN W/M**2.
1741 0 : WeighFac = 260.0 + 70.0 * state.dataThermalComforts->AcclPattern;
1742 0 : SweatCtrlFac = 1.0 + 0.05 * std::pow(SkinSignalSweatColdMax, 2.4);
1743 :
1744 : // EvapHeatLossDrySweat = SWEAT WHEN SkinWetTot < 0.4.
1745 0 : EvapHeatLossDrySweat =
1746 0 : ((WeighFac * CoreSignalSweatMax + 0.1 * WeighFac * SkinSignalSweatMax) * std::exp(SkinSignalSweatMax / 8.5)) / SweatCtrlFac;
1747 :
1748 : // MAXIMUM EVAPORATIVE POWER, EvapHeatLossMax, IN W/M**2.
1749 0 : state.dataThermalComforts->SkinVapPress = CalcSatVapPressFromTemp(state.dataThermalComforts->SkinTemp);
1750 0 : state.dataThermalComforts->EvapHeatLossMax = 2.2 * state.dataThermalComforts->Hc *
1751 0 : (state.dataThermalComforts->SkinVapPress - state.dataThermalComforts->VapPress) *
1752 0 : state.dataThermalComforts->CloPermeatEff;
1753 0 : if (state.dataThermalComforts->EvapHeatLossMax > 0.0) {
1754 0 : state.dataThermalComforts->SkinWetSweat = EvapHeatLossDrySweat / state.dataThermalComforts->EvapHeatLossMax;
1755 0 : state.dataThermalComforts->EvapHeatLossDiff = 0.408 * (state.dataThermalComforts->SkinVapPress - state.dataThermalComforts->VapPress);
1756 0 : state.dataThermalComforts->EvapHeatLoss = state.dataThermalComforts->SkinWetSweat * state.dataThermalComforts->EvapHeatLossMax +
1757 0 : (1.0 - state.dataThermalComforts->SkinWetSweat) * state.dataThermalComforts->EvapHeatLossDiff;
1758 0 : state.dataThermalComforts->SkinWetTot = state.dataThermalComforts->EvapHeatLoss / state.dataThermalComforts->EvapHeatLossMax;
1759 0 : if (state.dataThermalComforts->Time == 0.0) {
1760 0 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossDrySweat;
1761 0 : state.dataThermalComforts->EvapHeatLossSweatPrev = EvapHeatLossDrySweat;
1762 : }
1763 0 : if (state.dataThermalComforts->SkinWetTot > 0.4) {
1764 :
1765 : // ITERATION FOR SWEAT WHEN SkinWetTot IS GREATER THAT 0.4.
1766 0 : state.dataThermalComforts->IterNum = 0;
1767 0 : if (state.dataThermalComforts->SkinWetSweat > 1.0) {
1768 0 : state.dataThermalComforts->SkinWetSweat = 1.0;
1769 : }
1770 : while (true) {
1771 0 : EvapHeatLossSweatEst = state.dataThermalComforts->EvapHeatLossSweatPrev;
1772 0 : state.dataThermalComforts->SkinWetSweat = EvapHeatLossSweatEst / state.dataThermalComforts->EvapHeatLossMax;
1773 :
1774 0 : if (state.dataThermalComforts->SkinWetSweat > 1.0) {
1775 0 : state.dataThermalComforts->SkinWetSweat = 1.0;
1776 : }
1777 :
1778 0 : state.dataThermalComforts->EvapHeatLossDiff =
1779 0 : 0.408 * (state.dataThermalComforts->SkinVapPress - state.dataThermalComforts->VapPress);
1780 0 : state.dataThermalComforts->EvapHeatLoss =
1781 0 : (1.0 - state.dataThermalComforts->SkinWetTot) * state.dataThermalComforts->EvapHeatLossDiff +
1782 0 : state.dataThermalComforts->EvapHeatLossSweat;
1783 0 : state.dataThermalComforts->SkinWetTot = state.dataThermalComforts->EvapHeatLoss / state.dataThermalComforts->EvapHeatLossMax;
1784 :
1785 0 : if (state.dataThermalComforts->SkinWetTot > 1.0) {
1786 0 : state.dataThermalComforts->SkinWetTot = 1.0;
1787 : }
1788 :
1789 0 : SkinWetSignal = max(0.0, state.dataThermalComforts->SkinWetTot - 0.4);
1790 0 : SweatSuppFac = 0.5 + 0.5 * std::exp(-5.6 * SkinWetSignal);
1791 0 : EvapHeatLossSweatEstNew = SweatSuppFac * EvapHeatLossDrySweat;
1792 :
1793 0 : if (state.dataThermalComforts->IterNum == 0) {
1794 0 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossSweatEstNew;
1795 : }
1796 :
1797 0 : Err = EvapHeatLossSweatEst - EvapHeatLossSweatEstNew;
1798 :
1799 0 : if (state.dataThermalComforts->IterNum != 0) {
1800 0 : if ((ErrPrev * Err) < 0.0) {
1801 0 : state.dataThermalComforts->EvapHeatLossSweat = (EvapHeatLossSweatEst + EvapHeatLossSweatEstNew) / 2.0;
1802 : }
1803 0 : if ((ErrPrev * Err) >= 0.0) {
1804 0 : state.dataThermalComforts->EvapHeatLossSweat = EvapHeatLossSweatEstNew;
1805 : }
1806 : }
1807 :
1808 : // STOP CRITERION FOR THE ITERATION.
1809 0 : if ((std::abs(Err) <= 0.5) || (state.dataThermalComforts->IterNum >= 10)) {
1810 0 : break;
1811 : }
1812 0 : ++state.dataThermalComforts->IterNum;
1813 0 : state.dataThermalComforts->EvapHeatLossSweatPrev = state.dataThermalComforts->EvapHeatLossSweat;
1814 0 : ErrPrev = Err;
1815 : }
1816 :
1817 : } else {
1818 0 : 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 0 : SkinCndctDilation = 42.45 * CoreSignalWarmMax + 8.15 * std::pow(CoreSignalSkinSens, 0.8) * SkinSignalWarmMax;
1832 0 : SkinCndctConstriction = 1.0 + 0.4 * SkinSignalColdMax;
1833 : // ThermCndct IS EQUIVALENT TO KS
1834 0 : state.dataThermalComforts->ThermCndct = 5.3 + (6.75 + SkinCndctDilation) / SkinCndctConstriction;
1835 0 : SkinCndctMax = 75.0 + 10.0 * state.dataThermalComforts->AcclPattern;
1836 0 : 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 0 : ActLevelTot = state.dataThermalComforts->ActLevel + state.dataThermalComforts->ShivResponse;
1843 0 : IntHeatProdTot = ActLevelTot - state.dataThermalComforts->WorkEff;
1844 : // RESPIRATION HEAT LOSS, RespHeatLoss, IN W/M**0.
1845 0 : state.dataThermalComforts->LatRespHeatLoss = 0.0023 * ActLevelTot * (44.0 - state.dataThermalComforts->VapPress);
1846 0 : state.dataThermalComforts->DryRespHeatLoss = 0.0014 * ActLevelTot * (34.0 - state.dataThermalComforts->AirTemp);
1847 0 : state.dataThermalComforts->RespHeatLoss = state.dataThermalComforts->LatRespHeatLoss + state.dataThermalComforts->DryRespHeatLoss;
1848 : // HEAT FLOW FROM CORE TO SKIN, HeatFlow, IN W/M**2.
1849 0 : state.dataThermalComforts->HeatFlow =
1850 0 : state.dataThermalComforts->ThermCndct * (state.dataThermalComforts->CoreTemp - state.dataThermalComforts->SkinTemp);
1851 : // TempChange(1) = CoreTempChange/TempChange, IN C/HR.
1852 0 : TempChange(1) = (IntHeatProdTot - state.dataThermalComforts->RespHeatLoss - state.dataThermalComforts->HeatFlow) /
1853 0 : state.dataThermalComforts->CoreThermCap;
1854 0 : 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 0 : state.dataThermalComforts->DryHeatLoss = state.dataThermalComforts->H * state.dataThermalComforts->CloBodyRat *
1860 0 : state.dataThermalComforts->CloThermEff *
1861 0 : (state.dataThermalComforts->SkinTemp - state.dataThermalComforts->OpTemp);
1862 : // TempChange(2) = SkinTempChange/TempChange, IN C/HR.
1863 0 : TempChange(2) = (state.dataThermalComforts->HeatFlow - state.dataThermalComforts->EvapHeatLoss - state.dataThermalComforts->DryHeatLoss) /
1864 0 : state.dataThermalComforts->SkinThermCap;
1865 0 : }
1866 :
1867 0 : 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 0 : EP_SIZE_CHECK(Y, NEQ);
1888 0 : EP_SIZE_CHECK(DY, NEQ);
1889 0 : 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 0 : H2 = 0.5 * H;
1899 :
1900 0 : DERIV(state, NEQ, Y, DY);
1901 0 : for (I = 1; I <= NEQ; ++I) {
1902 0 : B = H2 * DY(I) - C(I);
1903 0 : Y(I) += B;
1904 0 : C(I) += 3.0 * B - H2 * DY(I);
1905 : }
1906 :
1907 0 : X += H2;
1908 :
1909 0 : for (J = 0; J < 2; ++J) {
1910 0 : DERIV(state, NEQ, Y, DY);
1911 0 : for (I = 1; I <= NEQ; ++I) {
1912 0 : B = A[J] * (H * DY(I) - C(I));
1913 0 : Y(I) += B;
1914 0 : C(I) += 3.0 * B - A[J] * H * DY(I);
1915 : }
1916 : }
1917 :
1918 0 : X += H2;
1919 0 : DERIV(state, NEQ, Y, DY);
1920 :
1921 0 : for (I = 1; I <= NEQ; ++I) {
1922 0 : B = (H * DY(I) - 2.0 * C(I)) / 6.0;
1923 0 : Y(I) += B;
1924 0 : C(I) += 3.0 * B - H2 * DY(I);
1925 : }
1926 :
1927 0 : DERIV(state, NEQ, Y, DY);
1928 0 : }
1929 :
1930 104 : 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 104 : Real64 constexpr AngleFacLimit(0.01); // To set the limit of sum of angle factors
1939 :
1940 104 : 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 104 : auto &cCurrentModuleObject = state.dataIPShortCut->cCurrentModuleObject;
1945 :
1946 104 : cCurrentModuleObject = "ComfortViewFactorAngles";
1947 104 : int NumOfAngleFactorLists = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCurrentModuleObject);
1948 104 : state.dataThermalComforts->AngleFactorList.allocate(NumOfAngleFactorLists);
1949 :
1950 105 : for (int Item = 1; Item <= NumOfAngleFactorLists; ++Item) {
1951 :
1952 1 : Real64 AllAngleFacSummed = 0.0; // Sum of angle factors in each zone
1953 1 : auto &thisAngFacList(state.dataThermalComforts->AngleFactorList(Item));
1954 :
1955 2 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
1956 : cCurrentModuleObject,
1957 : Item,
1958 1 : state.dataIPShortCut->cAlphaArgs,
1959 : NumAlphas,
1960 1 : state.dataIPShortCut->rNumericArgs,
1961 : NumNumbers,
1962 : IOStatus,
1963 1 : state.dataIPShortCut->lNumericFieldBlanks,
1964 1 : state.dataIPShortCut->lAlphaFieldBlanks,
1965 1 : state.dataIPShortCut->cAlphaFieldNames,
1966 1 : state.dataIPShortCut->cNumericFieldNames);
1967 :
1968 1 : thisAngFacList.Name = state.dataIPShortCut->cAlphaArgs(1); // no need for verification/uniqueness.
1969 :
1970 1 : thisAngFacList.TotAngleFacSurfaces = NumNumbers;
1971 1 : thisAngFacList.SurfaceName.allocate(thisAngFacList.TotAngleFacSurfaces);
1972 1 : thisAngFacList.SurfacePtr.allocate(thisAngFacList.TotAngleFacSurfaces);
1973 1 : thisAngFacList.AngleFactor.allocate(thisAngFacList.TotAngleFacSurfaces);
1974 :
1975 101 : for (int SurfNum = 1; SurfNum <= thisAngFacList.TotAngleFacSurfaces; ++SurfNum) {
1976 100 : thisAngFacList.SurfaceName(SurfNum) = state.dataIPShortCut->cAlphaArgs(SurfNum + 1);
1977 100 : thisAngFacList.SurfacePtr(SurfNum) = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(SurfNum + 1), state.dataSurface->Surface);
1978 100 : thisAngFacList.AngleFactor(SurfNum) = state.dataIPShortCut->rNumericArgs(SurfNum);
1979 : // Error trap for surfaces that do not exist or surfaces not in the zone
1980 100 : 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 100 : auto &thisSurf = state.dataSurface->Surface(thisAngFacList.SurfacePtr(SurfNum));
1996 100 : if (SurfNum == 1) {
1997 1 : thisAngFacList.EnclosurePtr = thisSurf.RadEnclIndex; // Save enclosure num of first surface
1998 : }
1999 100 : 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 100 : AllAngleFacSummed += thisAngFacList.AngleFactor(SurfNum);
2017 : }
2018 :
2019 1 : 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 104 : if (ErrorsFound) {
2030 0 : ShowFatalError(state, "GetAngleFactorList: Program terminated due to preceding errors.");
2031 : }
2032 :
2033 154 : for (int Item = 1; Item <= state.dataHeatBal->TotPeople; ++Item) {
2034 50 : auto &thisPeople = state.dataHeatBal->People(Item);
2035 50 : if (thisPeople.MRTCalcType != DataHeatBalance::CalcMRT::AngleFactor) {
2036 50 : continue;
2037 : }
2038 0 : thisPeople.AngleFactorListPtr = Util::FindItemInList(thisPeople.AngleFactorListName, state.dataThermalComforts->AngleFactorList);
2039 0 : int WhichAFList = thisPeople.AngleFactorListPtr;
2040 0 : 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 0 : auto &thisAngFacList = state.dataThermalComforts->AngleFactorList(WhichAFList);
2046 0 : 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 104 : if (ErrorsFound) {
2061 0 : ShowFatalError(state, "GetAngleFactorList: Program terminated due to preceding errors.");
2062 : }
2063 104 : }
2064 :
2065 1 : 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 1 : Real64 SurfTempEmissAngleFacSummed = 0.0;
2077 1 : Real64 SumSurfaceEmissAngleFactor = 0.0;
2078 :
2079 1 : auto &thisAngFacList(state.dataThermalComforts->AngleFactorList(AngleFacNum));
2080 :
2081 4 : for (int SurfNum = 1; SurfNum <= thisAngFacList.TotAngleFacSurfaces; ++SurfNum) {
2082 3 : Real64 SurfaceTemp = state.dataHeatBalSurf->SurfInsideTempHist(1)(thisAngFacList.SurfacePtr(SurfNum)) + Constant::Kelvin;
2083 : Real64 SurfEAF =
2084 3 : state.dataConstruction->Construct(state.dataSurface->Surface(thisAngFacList.SurfacePtr(SurfNum)).Construction).InsideAbsorpThermal *
2085 3 : thisAngFacList.AngleFactor(SurfNum);
2086 3 : SurfTempEmissAngleFacSummed += SurfEAF * pow_4(SurfaceTemp);
2087 3 : SumSurfaceEmissAngleFactor += SurfEAF;
2088 : }
2089 :
2090 1 : CalcAngleFactorMRT = root_4(SurfTempEmissAngleFacSummed / SumSurfaceEmissAngleFactor) - Constant::Kelvin;
2091 :
2092 1 : return CalcAngleFactorMRT;
2093 : }
2094 :
2095 18 : 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 18 : Real64 CalcSurfaceWeightedMRT = 0.0;
2109 :
2110 : // Initialize ZoneAESum for all zones and SurfaceAE for all surfaces at the start of the simulation
2111 18 : if (state.dataThermalComforts->FirstTimeSurfaceWeightedFlag) {
2112 18 : state.dataThermalComforts->FirstTimeError = true;
2113 18 : state.dataThermalComforts->FirstTimeSurfaceWeightedFlag = false;
2114 36 : for (auto const &thisRadEnclosure : state.dataViewFactor->EnclRadInfo) {
2115 180 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2116 162 : auto &thisSurface2 = state.dataSurface->Surface(SurfNum2);
2117 162 : 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 180 : for (int const SurfNum1 : thisRadEnclosure.SurfacePtr) {
2122 162 : auto &thisSurface1 = state.dataSurface->Surface(SurfNum1);
2123 162 : thisSurface1.enclAESum = 0.0;
2124 1944 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2125 1782 : if (SurfNum2 == SurfNum1) {
2126 162 : continue;
2127 : }
2128 1620 : auto &thisSurface2 = state.dataSurface->Surface(SurfNum2);
2129 1620 : thisSurface1.enclAESum += thisSurface2.AE;
2130 : }
2131 : }
2132 18 : }
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 18 : Real64 sumAET = 0.0; // Intermediate calculational variable (area*emissivity*T) sum
2137 :
2138 18 : auto &thisSurface = state.dataSurface->Surface(SurfNum);
2139 18 : auto &thisRadEnclosure = state.dataViewFactor->EnclRadInfo(thisSurface.RadEnclIndex);
2140 : // Recalc SurfaceEnclAESum only if needed due to window shades or EMS
2141 18 : 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 180 : for (int const SurfNum2 : thisRadEnclosure.SurfacePtr) {
2153 162 : if (SurfNum2 == SurfNum) {
2154 18 : continue;
2155 : }
2156 144 : sumAET += state.dataSurface->Surface(SurfNum2).AE * state.dataHeatBalSurf->SurfInsideTempHist(1)(SurfNum2);
2157 : }
2158 :
2159 : // Now weight the MRT
2160 18 : auto &thisSurfaceTemp = state.dataHeatBalSurf->SurfInsideTempHist(1)(SurfNum);
2161 18 : if (thisSurface.enclAESum > 0.01) {
2162 18 : 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 18 : if (AverageWithSurface) {
2166 9 : 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 18 : return CalcSurfaceWeightedMRT;
2187 : }
2188 :
2189 0 : 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 0 : Real64 const XT(Temp / 100.0);
2208 0 : 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 3737 : Real64 CalcSatVapPressFromTempTorr(Real64 const Temp)
2215 : {
2216 : // Helper function for pierceSET calculates Saturated Vapor Pressure (Torr) at Temperature T (°C)
2217 3737 : return std::exp(18.6686 - 4030.183 / (Temp + 235.0));
2218 : }
2219 :
2220 1832 : 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 1832 : Real64 CalcRadTemp = 0.0;
2255 1832 : Real64 constexpr AreaEff = 1.8; // Effective area of a "standard" person in meters squared
2256 1832 : Real64 constexpr StefanBoltzmannConst = 5.6697e-8; // Stefan-Boltzmann constant in W/(m2*K4)
2257 :
2258 1832 : auto &thisPeople = state.dataHeatBal->People(PeopleListNum);
2259 1832 : switch (thisPeople.MRTCalcType) {
2260 1832 : case DataHeatBalance::CalcMRT::EnclosureAveraged: {
2261 1832 : int enclNum = state.dataHeatBal->space(thisPeople.spaceIndex).radiantEnclosureNum;
2262 1832 : state.dataThermalComforts->RadTemp = state.dataViewFactor->EnclRadInfo(enclNum).MRT;
2263 1832 : } break;
2264 0 : case DataHeatBalance::CalcMRT::SurfaceWeighted: {
2265 0 : state.dataThermalComforts->RadTemp = CalcSurfaceWeightedMRT(state, thisPeople.SurfacePtr);
2266 0 : } break;
2267 0 : case DataHeatBalance::CalcMRT::AngleFactor: {
2268 0 : state.dataThermalComforts->RadTemp = CalcAngleFactorMRT(state, thisPeople.AngleFactorListPtr);
2269 0 : } 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 1832 : state.dataHeatBalFanSys->ZoneQdotRadHVACToPerson(state.dataThermalComforts->ZoneNum) =
2277 1832 : state.dataHeatBalFanSys->ZoneQHTRadSysToPerson(state.dataThermalComforts->ZoneNum) +
2278 1832 : state.dataHeatBalFanSys->ZoneQCoolingPanelToPerson(state.dataThermalComforts->ZoneNum) +
2279 1832 : state.dataHeatBalFanSys->ZoneQHWBaseboardToPerson(state.dataThermalComforts->ZoneNum) +
2280 1832 : state.dataHeatBalFanSys->ZoneQSteamBaseboardToPerson(state.dataThermalComforts->ZoneNum) +
2281 1832 : state.dataHeatBalFanSys->ZoneQElecBaseboardToPerson(state.dataThermalComforts->ZoneNum);
2282 1832 : if (state.dataHeatBalFanSys->ZoneQdotRadHVACToPerson(state.dataThermalComforts->ZoneNum) > 0.0) {
2283 0 : state.dataThermalComforts->RadTemp += Constant::Kelvin; // Convert to Kelvin
2284 0 : state.dataThermalComforts->RadTemp =
2285 0 : root_4(pow_4(state.dataThermalComforts->RadTemp) +
2286 0 : (state.dataHeatBalFanSys->ZoneQdotRadHVACToPerson(state.dataThermalComforts->ZoneNum) / AreaEff / StefanBoltzmannConst));
2287 0 : state.dataThermalComforts->RadTemp -= Constant::Kelvin; // Convert back to Celsius
2288 : }
2289 :
2290 1832 : CalcRadTemp = state.dataThermalComforts->RadTemp;
2291 :
2292 1832 : return CalcRadTemp;
2293 : }
2294 :
2295 18995 : 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 18995 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer = 0.0;
2320 18995 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter = 0.0;
2321 18995 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either = 0.0;
2322 :
2323 : // assume the zone is unoccupied
2324 45070 : for (auto &e : state.dataThermalComforts->ThermalComfortInASH55) {
2325 26075 : e.ZoneIsOccupied = false;
2326 18995 : }
2327 : // loop through the people objects and determine if the zone is currently occupied
2328 29435 : for (auto const &people : state.dataHeatBal->People) {
2329 10440 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
2330 10440 : NumberOccupants = people.NumberOfPeople * people.sched->getCurrentVal();
2331 10440 : if (NumberOccupants > 0) {
2332 7624 : state.dataThermalComforts->ThermalComfortInASH55(state.dataThermalComforts->ZoneNum).ZoneIsOccupied = true;
2333 : }
2334 18995 : }
2335 : // loop through the zones and determine if in simple ashrae 55 comfort regions
2336 : // MJW MRT ToDo: Extend ASHRAE 55 to spaces?
2337 45070 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2338 26075 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).ZoneIsOccupied) {
2339 7624 : auto &thisZoneHB = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone);
2340 : // keep track of occupied hours
2341 7624 : state.dataThermalComforts->ZoneOccHrs(iZone) += state.dataGlobal->TimeStepZone;
2342 7624 : Real64 CurAirTemp = thisZoneHB.ZTAVComf;
2343 7624 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
2344 0 : if (state.dataRoomAir->IsZoneDispVent3Node(iZone) || state.dataRoomAir->IsZoneUFAD(iZone)) {
2345 0 : CurAirTemp = state.dataRoomAir->TCMF(iZone);
2346 : }
2347 : }
2348 7624 : Real64 CurMeanRadiantTemp = thisZoneHB.MRT;
2349 7624 : 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 7624 : 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 7624 : isInQuadrilateral(OperTemp, thisZoneHB.airHumRatAvgComf, 21.7, 0.0, 19.6, 0.012, 23.9, 0.012, 26.3, 0.0);
2383 7624 : if (isComfortableWithSummerClothes) {
2384 1342 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotSummer = 0.0;
2385 : } else {
2386 6282 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotSummer = state.dataGlobal->TimeStepZone;
2387 6282 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer += state.dataGlobal->TimeStepZone;
2388 6282 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer = state.dataGlobal->TimeStepZone;
2389 : }
2390 7624 : if (isComfortableWithWinterClothes) {
2391 1427 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotWinter = 0.0;
2392 : } else {
2393 6197 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotWinter = state.dataGlobal->TimeStepZone;
2394 6197 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter += state.dataGlobal->TimeStepZone;
2395 6197 : state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter = state.dataGlobal->TimeStepZone;
2396 : }
2397 7624 : if (isComfortableWithSummerClothes || isComfortableWithWinterClothes) {
2398 2654 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotEither = 0.0;
2399 : } else {
2400 4970 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotEither = state.dataGlobal->TimeStepZone;
2401 4970 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither += state.dataGlobal->TimeStepZone;
2402 4970 : 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 18451 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotSummer = 0.0;
2407 18451 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotWinter = 0.0;
2408 18451 : state.dataThermalComforts->ThermalComfortInASH55(iZone).timeNotEither = 0.0;
2409 : }
2410 : }
2411 : // accumulate total time
2412 18995 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer += state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Summer;
2413 18995 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter += state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Winter;
2414 18995 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either += state.dataThermalComforts->AnyZoneTimeNotSimpleASH55Either;
2415 :
2416 18995 : if (state.dataGlobal->EndDesignDayEnvrnsFlag) {
2417 87 : allowedHours = double(state.dataGlobal->NumOfDayInEnvrn) * 24.0 * 0.04;
2418 : // first check if warning should be printed
2419 87 : showWarning = false;
2420 205 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2421 118 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).Enable55Warning) {
2422 2 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither > allowedHours) {
2423 2 : showWarning = true;
2424 : }
2425 : }
2426 : }
2427 : // if any zones should be warning print it out
2428 87 : if (showWarning) {
2429 2 : ShowWarningError(state, format("More than 4% of time ({:.1R} hours) uncomfortable in one or more zones ", allowedHours));
2430 4 : ShowContinueError(state, "Based on ASHRAE 55-2004 graph (Section 5.2.1.1)");
2431 2 : if (state.dataEnvrn->RunPeriodEnvironment) {
2432 0 : ShowContinueError(state,
2433 0 : format("During Environment [{}]: {}", state.dataEnvrn->EnvironmentStartEnd, state.dataEnvrn->EnvironmentName));
2434 : } else {
2435 4 : ShowContinueError(
2436 : state,
2437 4 : format("During SizingPeriod Environment [{}]: {}", state.dataEnvrn->EnvironmentStartEnd, state.dataEnvrn->EnvironmentName));
2438 : }
2439 6 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2440 4 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).Enable55Warning) {
2441 2 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither > allowedHours) {
2442 4 : ShowContinueError(state,
2443 4 : format("{:.1R} hours were uncomfortable in zone: {}",
2444 2 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither,
2445 2 : state.dataHeatBal->Zone(iZone).Name));
2446 : }
2447 : }
2448 : }
2449 : }
2450 : // put in predefined reports
2451 205 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2452 236 : PreDefTableEntry(state,
2453 118 : state.dataOutRptPredefined->pdchSCwinterClothes,
2454 118 : state.dataHeatBal->Zone(iZone).Name,
2455 118 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter);
2456 236 : PreDefTableEntry(state,
2457 118 : state.dataOutRptPredefined->pdchSCsummerClothes,
2458 118 : state.dataHeatBal->Zone(iZone).Name,
2459 118 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer);
2460 236 : PreDefTableEntry(state,
2461 118 : state.dataOutRptPredefined->pdchSCeitherClothes,
2462 118 : state.dataHeatBal->Zone(iZone).Name,
2463 118 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither);
2464 : }
2465 261 : PreDefTableEntry(
2466 174 : state, state.dataOutRptPredefined->pdchSCwinterClothes, "Facility", state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter);
2467 261 : PreDefTableEntry(
2468 174 : state, state.dataOutRptPredefined->pdchSCsummerClothes, "Facility", state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer);
2469 261 : PreDefTableEntry(
2470 174 : state, state.dataOutRptPredefined->pdchSCeitherClothes, "Facility", state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either);
2471 : // set value for ABUPS report
2472 87 : state.dataOutRptPredefined->TotalTimeNotSimpleASH55EitherForABUPS = state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either;
2473 : // reset accumulation for new environment
2474 205 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2475 118 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotWinter = 0.0;
2476 118 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotSummer = 0.0;
2477 118 : state.dataThermalComforts->ThermalComfortInASH55(iZone).totalTimeNotEither = 0.0;
2478 : }
2479 87 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Winter = 0.0;
2480 87 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Summer = 0.0;
2481 87 : state.dataThermalComforts->TotalAnyZoneTimeNotSimpleASH55Either = 0.0;
2482 : // report how the aggregation is conducted
2483 87 : switch (state.dataGlobal->KindOfSim) {
2484 87 : case Constant::KindOfSim::DesignDay: {
2485 87 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstSimpleComfort, "Aggregated over the Design Days");
2486 87 : } break;
2487 0 : case Constant::KindOfSim::RunPeriodDesign: {
2488 0 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstSimpleComfort, "Aggregated over the RunPeriods for Design");
2489 0 : } break;
2490 0 : case Constant::KindOfSim::RunPeriodWeather: {
2491 0 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstSimpleComfort, "Aggregated over the RunPeriods for Weather");
2492 0 : } break;
2493 0 : default:
2494 0 : break;
2495 : }
2496 : // report number of occupied hours per week for LEED report
2497 205 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2498 236 : PreDefTableEntry(state,
2499 118 : state.dataOutRptPredefined->pdchLeedSutHrsWeek,
2500 118 : state.dataHeatBal->Zone(iZone).Name,
2501 118 : 7 * 24 * (state.dataThermalComforts->ZoneOccHrs(iZone) / (state.dataGlobal->NumOfDayInEnvrn * 24)));
2502 : }
2503 : }
2504 18995 : }
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 19006 : 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 19006 : Real64 const deviationFromSetPtThresholdClg = state.dataHVACGlobal->deviationFromSetPtThresholdClg;
2536 19006 : 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 19006 : state.dataThermalComforts->AnyZoneNotMetHeating = 0.0;
2548 19006 : state.dataThermalComforts->AnyZoneNotMetCooling = 0.0;
2549 19006 : state.dataThermalComforts->AnyZoneNotMetOccupied = 0.0;
2550 19006 : state.dataThermalComforts->AnyZoneNotMetHeatingOccupied = 0.0;
2551 19006 : state.dataThermalComforts->AnyZoneNotMetCoolingOccupied = 0.0;
2552 45092 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2553 26086 : auto const &zoneTstatSetpt = state.dataHeatBalFanSys->zoneTstatSetpts(iZone);
2554 :
2555 26086 : SensibleLoadPredictedNoAdj = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(iZone).TotalOutputRequired;
2556 26086 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCooling = 0.0;
2557 26086 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeating = 0.0;
2558 26086 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCoolingOccupied = 0.0;
2559 26086 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeatingOccupied = 0.0;
2560 :
2561 26086 : testHeating = (state.dataHeatBalFanSys->TempControlType(iZone) != HVAC::SetptType::SingleCool);
2562 26086 : testCooling = (state.dataHeatBalFanSys->TempControlType(iZone) != HVAC::SetptType::SingleHeat);
2563 :
2564 26086 : if (testHeating && (SensibleLoadPredictedNoAdj > 0)) { // heating
2565 6064 : if (state.dataRoomAir->AirModel(iZone).AirModel != RoomAir::RoomAirModel::Mixing) {
2566 0 : deltaT = state.dataHeatBalFanSys->TempTstatAir(iZone) - zoneTstatSetpt.setptLo;
2567 : } else {
2568 6064 : if (state.dataZoneTempPredictorCorrector->NumOnOffCtrZone > 0) {
2569 1 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptLoAver;
2570 : } else {
2571 6063 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptLo;
2572 : }
2573 : }
2574 6064 : if (deltaT < deviationFromSetPtThresholdHtg) {
2575 1792 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeating = state.dataGlobal->TimeStepZone;
2576 1792 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating += state.dataGlobal->TimeStepZone;
2577 1792 : if (state.dataThermalComforts->AnyZoneNotMetHeating == 0.0) {
2578 1504 : state.dataThermalComforts->AnyZoneNotMetHeating = state.dataGlobal->TimeStepZone;
2579 : }
2580 1792 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).ZoneIsOccupied) {
2581 1060 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetHeatingOccupied = state.dataGlobal->TimeStepZone;
2582 1060 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied += state.dataGlobal->TimeStepZone;
2583 1060 : if (state.dataThermalComforts->AnyZoneNotMetHeatingOccupied == 0.0) {
2584 868 : state.dataThermalComforts->AnyZoneNotMetHeatingOccupied = state.dataGlobal->TimeStepZone;
2585 : }
2586 1060 : if (state.dataThermalComforts->AnyZoneNotMetOccupied == 0.0) {
2587 868 : state.dataThermalComforts->AnyZoneNotMetOccupied = state.dataGlobal->TimeStepZone;
2588 : }
2589 : }
2590 : }
2591 20022 : } else if (testCooling && (SensibleLoadPredictedNoAdj < 0)) { // cooling
2592 5684 : if (state.dataRoomAir->AirModel(iZone).AirModel != RoomAir::RoomAirModel::Mixing) {
2593 0 : deltaT = state.dataHeatBalFanSys->TempTstatAir(iZone) - zoneTstatSetpt.setptHi;
2594 : } else {
2595 5684 : if (state.dataZoneTempPredictorCorrector->NumOnOffCtrZone > 0) {
2596 1 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptHiAver;
2597 : } else {
2598 5683 : deltaT = state.dataZoneTempPredictorCorrector->zoneHeatBalance(iZone).ZTAV - zoneTstatSetpt.setptHi;
2599 : }
2600 : }
2601 :
2602 5684 : if (state.dataHeatBal->Zone(iZone).HasAdjustedReturnTempByITE) {
2603 0 : deltaT = state.dataHeatBalFanSys->TempTstatAir(iZone) - state.dataHeatBal->Zone(iZone).AdjustedReturnTempByITE;
2604 : }
2605 5684 : if (deltaT > deviationFromSetPtThresholdClg) {
2606 1418 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCooling = state.dataGlobal->TimeStepZone;
2607 1418 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling += state.dataGlobal->TimeStepZone;
2608 1418 : if (state.dataThermalComforts->AnyZoneNotMetCooling == 0.0) {
2609 781 : state.dataThermalComforts->AnyZoneNotMetCooling = state.dataGlobal->TimeStepZone;
2610 : }
2611 1418 : if (state.dataThermalComforts->ThermalComfortInASH55(iZone).ZoneIsOccupied) {
2612 1238 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).notMetCoolingOccupied = state.dataGlobal->TimeStepZone;
2613 1238 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied += state.dataGlobal->TimeStepZone;
2614 1238 : if (state.dataThermalComforts->AnyZoneNotMetCoolingOccupied == 0.0) {
2615 643 : state.dataThermalComforts->AnyZoneNotMetCoolingOccupied = state.dataGlobal->TimeStepZone;
2616 : }
2617 1238 : if (state.dataThermalComforts->AnyZoneNotMetOccupied == 0.0) {
2618 643 : state.dataThermalComforts->AnyZoneNotMetOccupied = state.dataGlobal->TimeStepZone;
2619 : }
2620 : }
2621 : }
2622 : }
2623 : }
2624 19006 : state.dataThermalComforts->TotalAnyZoneNotMetHeating += state.dataThermalComforts->AnyZoneNotMetHeating;
2625 19006 : state.dataThermalComforts->TotalAnyZoneNotMetCooling += state.dataThermalComforts->AnyZoneNotMetCooling;
2626 19006 : state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied += state.dataThermalComforts->AnyZoneNotMetHeatingOccupied;
2627 19006 : state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied += state.dataThermalComforts->AnyZoneNotMetCoolingOccupied;
2628 19006 : state.dataThermalComforts->TotalAnyZoneNotMetOccupied += state.dataThermalComforts->AnyZoneNotMetOccupied;
2629 :
2630 : // was EndEnvrnsFlag prior to CR7562
2631 19006 : if (state.dataGlobal->EndDesignDayEnvrnsFlag) {
2632 205 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2633 236 : PreDefTableEntry(state,
2634 118 : state.dataOutRptPredefined->pdchULnotMetHeat,
2635 118 : state.dataHeatBal->Zone(iZone).Name,
2636 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating);
2637 236 : PreDefTableEntry(state,
2638 118 : state.dataOutRptPredefined->pdchULnotMetCool,
2639 118 : state.dataHeatBal->Zone(iZone).Name,
2640 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling);
2641 236 : PreDefTableEntry(state,
2642 118 : state.dataOutRptPredefined->pdchULnotMetHeatOcc,
2643 118 : state.dataHeatBal->Zone(iZone).Name,
2644 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied);
2645 236 : PreDefTableEntry(state,
2646 118 : state.dataOutRptPredefined->pdchULnotMetCoolOcc,
2647 118 : state.dataHeatBal->Zone(iZone).Name,
2648 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied);
2649 : }
2650 87 : PreDefTableEntry(state, state.dataOutRptPredefined->pdchULnotMetHeat, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetHeating);
2651 87 : PreDefTableEntry(state, state.dataOutRptPredefined->pdchULnotMetCool, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetCooling);
2652 261 : PreDefTableEntry(
2653 174 : state, state.dataOutRptPredefined->pdchULnotMetHeatOcc, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied);
2654 261 : PreDefTableEntry(
2655 174 : state, state.dataOutRptPredefined->pdchULnotMetCoolOcc, "Facility", state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied);
2656 : // set value for ABUPS report
2657 87 : state.dataOutRptPredefined->TotalNotMetHeatingOccupiedForABUPS = state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied;
2658 87 : state.dataOutRptPredefined->TotalNotMetCoolingOccupiedForABUPS = state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied;
2659 87 : state.dataOutRptPredefined->TotalNotMetOccupiedForABUPS = state.dataThermalComforts->TotalAnyZoneNotMetOccupied;
2660 : // reset counters
2661 205 : for (iZone = 1; iZone <= state.dataGlobal->NumOfZones; ++iZone) {
2662 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeating = 0.0;
2663 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCooling = 0.0;
2664 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetHeatingOccupied = 0.0;
2665 118 : state.dataThermalComforts->ThermalComfortSetPoint(iZone).totalNotMetCoolingOccupied = 0.0;
2666 : }
2667 87 : state.dataThermalComforts->TotalAnyZoneNotMetHeating = 0.0;
2668 87 : state.dataThermalComforts->TotalAnyZoneNotMetCooling = 0.0;
2669 87 : state.dataThermalComforts->TotalAnyZoneNotMetHeatingOccupied = 0.0;
2670 87 : state.dataThermalComforts->TotalAnyZoneNotMetCoolingOccupied = 0.0;
2671 87 : state.dataThermalComforts->TotalAnyZoneNotMetOccupied = 0.0;
2672 : // report how the aggregation is conducted
2673 87 : switch (state.dataGlobal->KindOfSim) {
2674 87 : case Constant::KindOfSim::DesignDay: {
2675 87 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstUnmetLoads, "Aggregated over the Design Days");
2676 87 : } break;
2677 0 : case Constant::KindOfSim::RunPeriodDesign: {
2678 0 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstUnmetLoads, "Aggregated over the RunPeriods for Design");
2679 0 : } break;
2680 0 : case Constant::KindOfSim::RunPeriodWeather: {
2681 0 : addFootNoteSubTable(state, state.dataOutRptPredefined->pdstUnmetLoads, "Aggregated over the RunPeriods for Weather");
2682 0 : } break;
2683 0 : default:
2684 0 : break;
2685 : }
2686 : }
2687 19006 : }
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 1 : 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 1 : 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 1 : std::string lineAvg;
2741 1 : 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 1 : if (initiate) { // not optional on initiate=true. would otherwise check for presence
2759 0 : weathersimulation = wthrsim;
2760 0 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2761 0 : state.dataThermalComforts->runningAverageASH = 0.0;
2762 0 : state.dataThermalComforts->monthlyTemp = 0.0;
2763 0 : inavgdrybulb = avgdrybulb;
2764 : } else {
2765 1 : weathersimulation = false;
2766 1 : inavgdrybulb = 0.0;
2767 : }
2768 :
2769 1 : if (initiate && weathersimulation) {
2770 0 : const bool statFileExists = FileSystem::fileExists(state.files.inStatFilePath.filePath);
2771 0 : const bool epwFileExists = FileSystem::fileExists(state.files.inputWeatherFilePath.filePath);
2772 :
2773 0 : readStat = 0;
2774 0 : if (statFileExists) {
2775 0 : auto statFile = state.files.inStatFilePath.open(state, "CalcThermalComfortAdapctiveASH55");
2776 0 : while (statFile.good()) {
2777 0 : auto lineIn = statFile.readLine();
2778 0 : if (has(lineIn.data, "Monthly Statistics for Dry Bulb temperatures")) {
2779 0 : for (i = 1; i <= 7; ++i) {
2780 0 : lineIn = statFile.readLine();
2781 : }
2782 0 : lineIn = statFile.readLine();
2783 0 : lineAvg = lineIn.data;
2784 0 : break;
2785 : }
2786 0 : }
2787 0 : for (i = 1; i <= 12; ++i) {
2788 0 : state.dataThermalComforts->monthlyTemp(i) = StrToReal(GetColumnUsingTabs(lineAvg, i + 2));
2789 : }
2790 0 : state.dataThermalComforts->useStatData = true;
2791 0 : } 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 1 : } else if (initiate && !weathersimulation) {
2866 0 : state.dataThermalComforts->runningAverageASH = inavgdrybulb;
2867 0 : state.dataThermalComforts->monthlyTemp = inavgdrybulb;
2868 0 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2869 : }
2870 :
2871 1 : if (initiate) {
2872 0 : return;
2873 : }
2874 :
2875 1 : if (state.dataGlobal->BeginDayFlag && state.dataThermalComforts->useEpwData) {
2876 : // Update the running average, reset the daily avg
2877 1 : state.dataThermalComforts->DailyAveOutTemp(30) = state.dataThermalComforts->avgDryBulbASH;
2878 1 : Real64 sum = 0.0;
2879 30 : for (i = 1; i <= 29; i++) {
2880 29 : sum += state.dataThermalComforts->DailyAveOutTemp(i);
2881 : }
2882 1 : state.dataThermalComforts->runningAverageASH = (sum + state.dataThermalComforts->avgDryBulbASH) / 30.0;
2883 30 : for (i = 1; i <= 29; i++) {
2884 29 : state.dataThermalComforts->DailyAveOutTemp(i) = state.dataThermalComforts->DailyAveOutTemp(i + 1);
2885 : }
2886 1 : state.dataThermalComforts->avgDryBulbASH = 0.0;
2887 : }
2888 :
2889 : // If exists BeginMonthFlag we can use it to call InvJulianDay once per month.
2890 1 : 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 1 : if (state.dataGlobal->BeginHourFlag) {
2899 0 : state.dataThermalComforts->avgDryBulbASH += (state.dataEnvrn->OutDryBulbTemp / 24.0);
2900 : }
2901 :
2902 1 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
2903 0 : ++state.dataThermalComforts->PeopleNum) {
2904 :
2905 0 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
2906 0 : if (!people.AdaptiveASH55) {
2907 0 : continue;
2908 : }
2909 :
2910 0 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
2911 :
2912 0 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
2913 0 : state.dataThermalComforts->AirTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum).ZTAVComf;
2914 0 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
2915 0 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
2916 0 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
2917 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum);
2918 : }
2919 : }
2920 0 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
2921 0 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->AirTemp + state.dataThermalComforts->RadTemp) / 2.0;
2922 0 : comfort.ThermalComfortOpTemp = state.dataThermalComforts->OpTemp;
2923 0 : comfort.ASHRAE55RunningMeanOutdoorTemp = state.dataThermalComforts->runningAverageASH;
2924 0 : if (state.dataThermalComforts->runningAverageASH >= 10.0 && state.dataThermalComforts->runningAverageASH <= 33.5) {
2925 : // Calculate the comfort here (people/output handling loop)
2926 0 : numOccupants = people.NumberOfPeople * people.sched->getCurrentVal();
2927 0 : tComf = 0.31 * state.dataThermalComforts->runningAverageASH + 17.8;
2928 0 : comfort.TComfASH55 = tComf;
2929 0 : if (numOccupants > 0) {
2930 0 : if (state.dataThermalComforts->OpTemp < tComf + 2.5 && state.dataThermalComforts->OpTemp > tComf - 2.5) {
2931 : // 80% and 90% limits okay
2932 0 : comfort.ThermalComfortAdaptiveASH5590 = 1;
2933 0 : comfort.ThermalComfortAdaptiveASH5580 = 1;
2934 0 : } else if (state.dataThermalComforts->OpTemp < tComf + 3.5 && state.dataThermalComforts->OpTemp > tComf - 3.5) {
2935 : // 80% only
2936 0 : comfort.ThermalComfortAdaptiveASH5590 = 0;
2937 0 : comfort.ThermalComfortAdaptiveASH5580 = 1;
2938 0 : people.TimeNotMetASH5590 += SysTimeElapsed;
2939 : } else {
2940 : // Neither
2941 0 : comfort.ThermalComfortAdaptiveASH5590 = 0;
2942 0 : comfort.ThermalComfortAdaptiveASH5580 = 0;
2943 0 : people.TimeNotMetASH5580 += SysTimeElapsed;
2944 0 : people.TimeNotMetASH5590 += SysTimeElapsed;
2945 : }
2946 : } else {
2947 : // Unoccupied
2948 0 : comfort.ThermalComfortAdaptiveASH5590 = -1;
2949 0 : comfort.ThermalComfortAdaptiveASH5580 = -1;
2950 : }
2951 : } else {
2952 : // Monthly temp out of range
2953 0 : comfort.ThermalComfortAdaptiveASH5590 = -1;
2954 0 : comfort.ThermalComfortAdaptiveASH5580 = -1;
2955 0 : comfort.TComfASH55 = -1.0;
2956 : }
2957 : }
2958 1 : }
2959 :
2960 26 : 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 26 : 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 26 : 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 26 : int constexpr numHeaderRowsInEpw = 8;
3006 :
3007 26 : if (initiate) { // not optional on initiate=true. would otherwise check for presence
3008 1 : weathersimulation = wthrsim;
3009 1 : inavgdrybulb = avgdrybulb;
3010 1 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3011 1 : state.dataThermalComforts->runningAverageCEN = 0.0;
3012 : } else {
3013 25 : weathersimulation = false;
3014 25 : inavgdrybulb = 0.0;
3015 : }
3016 :
3017 26 : 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 0 : calcStartHr = 24 * calcStartDay + 1;
3037 0 : for (i = 1; i <= calcStartHr - 1; ++i) {
3038 0 : epwFile.readLine();
3039 : }
3040 0 : state.dataThermalComforts->runningAverageCEN = 0.0;
3041 0 : for (i = 0; i < 7; ++i) {
3042 0 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3043 0 : for (j = 1; j <= 24; ++j) {
3044 0 : epwLine = epwFile.readLine().data;
3045 0 : for (ind = 1; ind <= 6; ++ind) {
3046 0 : pos = index(epwLine, ',');
3047 0 : epwLine.erase(0, pos + 1);
3048 : }
3049 0 : pos = index(epwLine, ',');
3050 0 : dryBulb = StrToReal(epwLine.substr(0, pos));
3051 0 : state.dataThermalComforts->avgDryBulbCEN += (dryBulb / 24.0);
3052 : }
3053 0 : state.dataThermalComforts->runningAverageCEN += alpha_pow[i] * state.dataThermalComforts->avgDryBulbCEN;
3054 : }
3055 : } else { // Do special things for wrapping the epw
3056 1 : calcEndDay = jStartDay;
3057 1 : calcStartDay += DaysInYear;
3058 1 : calcEndHr = 24 * calcEndDay;
3059 1 : calcStartHr = 24 * calcStartDay + 1;
3060 1 : 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 8593 : for (i = calcEndHr + 1; i <= calcStartHr - 1; ++i) {
3075 8592 : epwFile.readLine();
3076 : }
3077 8 : for (i = 0; i < 7 - calcEndDay; ++i) {
3078 7 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3079 175 : for (j = 1; j <= 24; ++j) {
3080 168 : epwLine = epwFile.readLine().data;
3081 1176 : for (ind = 1; ind <= 6; ++ind) {
3082 1008 : pos = index(epwLine, ',');
3083 1008 : epwLine.erase(0, pos + 1);
3084 : }
3085 168 : pos = index(epwLine, ',');
3086 168 : dryBulb = StrToReal(epwLine.substr(0, pos));
3087 168 : state.dataThermalComforts->avgDryBulbCEN += (dryBulb / 24.0);
3088 : }
3089 7 : 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 26 : } else if (initiate && !weathersimulation) {
3098 0 : state.dataThermalComforts->runningAverageCEN = inavgdrybulb;
3099 0 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3100 : }
3101 26 : if (initiate) {
3102 1 : return;
3103 : }
3104 :
3105 25 : if (state.dataGlobal->BeginDayFlag && !state.dataThermalComforts->firstDaySet) {
3106 : // Update the running average, reset the daily avg
3107 2 : state.dataThermalComforts->runningAverageCEN =
3108 1 : alpha * state.dataThermalComforts->runningAverageCEN + (1.0 - alpha) * state.dataThermalComforts->avgDryBulbCEN;
3109 1 : state.dataThermalComforts->avgDryBulbCEN = 0.0;
3110 : }
3111 :
3112 25 : state.dataThermalComforts->firstDaySet = false;
3113 :
3114 : // Update the daily average
3115 25 : if (state.dataGlobal->BeginHourFlag) {
3116 25 : state.dataThermalComforts->avgDryBulbCEN += (state.dataEnvrn->OutDryBulbTemp / 24.0);
3117 : }
3118 :
3119 25 : for (state.dataThermalComforts->PeopleNum = 1; state.dataThermalComforts->PeopleNum <= state.dataHeatBal->TotPeople;
3120 0 : ++state.dataThermalComforts->PeopleNum) {
3121 0 : auto &people = state.dataHeatBal->People(state.dataThermalComforts->PeopleNum);
3122 0 : if (!people.AdaptiveCEN15251) {
3123 0 : continue;
3124 : }
3125 :
3126 0 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
3127 0 : state.dataThermalComforts->ZoneNum = people.ZonePtr;
3128 0 : state.dataThermalComforts->AirTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(state.dataThermalComforts->ZoneNum).ZTAVComf;
3129 0 : if (state.dataRoomAir->anyNonMixingRoomAirModel) {
3130 0 : if (state.dataRoomAir->IsZoneDispVent3Node(state.dataThermalComforts->ZoneNum) ||
3131 0 : state.dataRoomAir->IsZoneUFAD(state.dataThermalComforts->ZoneNum)) {
3132 0 : state.dataThermalComforts->AirTemp = state.dataRoomAir->TCMF(state.dataThermalComforts->ZoneNum);
3133 : }
3134 : }
3135 0 : state.dataThermalComforts->RadTemp = CalcRadTemp(state, state.dataThermalComforts->PeopleNum);
3136 0 : state.dataThermalComforts->OpTemp = (state.dataThermalComforts->AirTemp + state.dataThermalComforts->RadTemp) / 2.0;
3137 0 : comfort.ThermalComfortOpTemp = state.dataThermalComforts->OpTemp;
3138 0 : comfort.CEN15251RunningMeanOutdoorTemp = state.dataThermalComforts->runningAverageCEN;
3139 0 : if (state.dataThermalComforts->runningAverageCEN >= 10.0 && state.dataThermalComforts->runningAverageCEN <= 30.0) {
3140 : // Calculate the comfort here (people/output handling loop)
3141 0 : numOccupants = people.NumberOfPeople * people.sched->getCurrentVal();
3142 0 : tComf = 0.33 * state.dataThermalComforts->runningAverageCEN + 18.8;
3143 0 : comfort.TComfCEN15251 = tComf;
3144 0 : if (numOccupants > 0) {
3145 0 : if (state.dataThermalComforts->runningAverageCEN < 15) {
3146 0 : tComfLow = 23.75; // Lower limit is constant in this region
3147 : } else {
3148 0 : tComfLow = tComf;
3149 : }
3150 0 : if (state.dataThermalComforts->OpTemp < tComf + 2.0 && state.dataThermalComforts->OpTemp > tComfLow - 2.0) {
3151 : // Within Cat I, II, III Limits
3152 0 : comfort.ThermalComfortAdaptiveCEN15251CatI = 1;
3153 0 : comfort.ThermalComfortAdaptiveCEN15251CatII = 1;
3154 0 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 1;
3155 0 : } else if (state.dataThermalComforts->OpTemp < tComf + 3.0 && state.dataThermalComforts->OpTemp > tComfLow - 3.0) {
3156 : // Within Cat II, III Limits
3157 0 : comfort.ThermalComfortAdaptiveCEN15251CatI = 0;
3158 0 : comfort.ThermalComfortAdaptiveCEN15251CatII = 1;
3159 0 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 1;
3160 0 : people.TimeNotMetCEN15251CatI += SysTimeElapsed;
3161 0 : } else if (state.dataThermalComforts->OpTemp < tComf + 4.0 && state.dataThermalComforts->OpTemp > tComfLow - 4.0) {
3162 : // Within Cat III Limits
3163 0 : comfort.ThermalComfortAdaptiveCEN15251CatI = 0;
3164 0 : comfort.ThermalComfortAdaptiveCEN15251CatII = 0;
3165 0 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 1;
3166 0 : people.TimeNotMetCEN15251CatI += SysTimeElapsed;
3167 0 : people.TimeNotMetCEN15251CatII += SysTimeElapsed;
3168 : } else {
3169 : // None
3170 0 : comfort.ThermalComfortAdaptiveCEN15251CatI = 0;
3171 0 : comfort.ThermalComfortAdaptiveCEN15251CatII = 0;
3172 0 : comfort.ThermalComfortAdaptiveCEN15251CatIII = 0;
3173 0 : people.TimeNotMetCEN15251CatI += SysTimeElapsed;
3174 0 : people.TimeNotMetCEN15251CatII += SysTimeElapsed;
3175 0 : people.TimeNotMetCEN15251CatIII += SysTimeElapsed;
3176 : }
3177 : } else {
3178 : // Unoccupied
3179 0 : comfort.ThermalComfortAdaptiveCEN15251CatI = -1;
3180 0 : comfort.ThermalComfortAdaptiveCEN15251CatII = -1;
3181 0 : comfort.ThermalComfortAdaptiveCEN15251CatIII = -1;
3182 : }
3183 : } else {
3184 : // Monthly temp out of range
3185 0 : comfort.ThermalComfortAdaptiveCEN15251CatI = -1;
3186 0 : comfort.ThermalComfortAdaptiveCEN15251CatII = -1;
3187 0 : comfort.ThermalComfortAdaptiveCEN15251CatIII = -1;
3188 0 : comfort.TComfCEN15251 = -1.0;
3189 : }
3190 : }
3191 26 : }
3192 :
3193 0 : 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 0 : auto &comfort = state.dataThermalComforts->ThermalComfortData(state.dataThermalComforts->PeopleNum);
3203 :
3204 0 : if (state.dataThermalComforts->TemporarySixAMTemperature < -5.0) { // A Temporary state variable?
3205 0 : comfort.ClothingValue = 1.0;
3206 0 : } else if ((state.dataThermalComforts->TemporarySixAMTemperature >= -5.0) && (state.dataThermalComforts->TemporarySixAMTemperature < 5.0)) {
3207 0 : comfort.ClothingValue = 0.818 - 0.0364 * state.dataThermalComforts->TemporarySixAMTemperature;
3208 0 : } else if ((state.dataThermalComforts->TemporarySixAMTemperature >= 5.0) && (state.dataThermalComforts->TemporarySixAMTemperature < 26.0)) {
3209 0 : TemporaryVariable = -0.1635 - 0.0066 * state.dataThermalComforts->TemporarySixAMTemperature;
3210 0 : comfort.ClothingValue = std::pow(10.0, TemporaryVariable);
3211 0 : } else if (state.dataThermalComforts->TemporarySixAMTemperature >= 26.0) {
3212 0 : comfort.ClothingValue = 0.46;
3213 : }
3214 0 : }
3215 :
3216 : } // namespace ThermalComfort
3217 :
3218 : } // namespace EnergyPlus
|