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