Source code for pythermalcomfort.models.jos3

import csv
import datetime as dt
import os
import re

import numpy as np

from pythermalcomfort.__init__ import __version__
from pythermalcomfort.jos3_functions import construction as cons
from pythermalcomfort.jos3_functions import matrix
from pythermalcomfort.jos3_functions import thermoregulation as threg
from pythermalcomfort.jos3_functions.construction import _to17array
from pythermalcomfort.jos3_functions.matrix import (
    NUM_NODES,
    INDEX,
    VINDEX,
    BODY_NAMES,
    remove_body_name,
)
from pythermalcomfort.jos3_functions.parameters import Default, ALL_OUT_PARAMS
from pythermalcomfort.models import pmv

# # testing morris equation
# from scipy import optimize
# import matplotlib.pyplot as plt
# import numpy as np
# f, ax = plt.subplots()
# for person in ["young", "old", "meds"]:
#     t_lim = []
#     rh_array = list(range(5, 100))
#     for rh in rh_array:
#
#         def function(x):
#             return use_fans_morris(x, 3.5, rh, 70, target_person=person)
#
#         t_lim.append(optimize.brentq(function, 30, 70))
#
#     z = np.polyfit(rh_array, t_lim, 4)
#     p = np.poly1d(z)
#     y_new = p(rh_array)
#     # plt.plot(rh_array, t_lim, "o")
#     plt.plot(rh_array, y_new, "-", label=person)
# ax.set(
#     ylabel="Temperature [°C]",
#     xlabel="Relative Humidity [Rh]",
#     ylim=(24, 52),
#     xlim=(5, 70),
# )
# plt.legend()


# include also the following models:
#  radiant_tmp_asymmetry
#  draft
#  floor_surface_tmp
#  Perceived temperature (Blazejczyk2012)
#  Physiological subjective temperature and physiological strain (Blazejczyk2012)
#  more models here: https://www.rdocumentation.org/packages/comf/versions/0.1.9
#  more models here: https://rdrr.io/cran/comf/man/
#  to print the R source code use comf::pmv


[docs]class JOS3: """JOS-3 model simulates human thermal physiology including skin temperature, core temperature, sweating rate, etc. for the whole body and 17 local body parts. This model was developed at Shin-ichi Tanabe Laboratory, Waseda University and was derived from 65 Multi-Node model (https://doi.org/10.1016/S0378-7788(02)00014-2) and JOS-2 model (https://doi.org/10.1016/j.buildenv.2013.04.013). To use this model, create an instance of the JOS3 class with optional body parameters such as body height, weight, age, sex, etc. Environmental conditions such as air temperature, mean radiant temperature, air velocity, etc. can be set using the setter methods. (ex. X.tdb, X.tr X.v) If you want to set the different conditions in each body part, set them as a 17 lengths of list, dictionary, or numpy array format. List or numpy array format input must be 17 lengths and means the order of "head", "neck", "chest", "back", "pelvis", "left_shoulder", "left_arm", "left_hand", "right_shoulder", "right_arm", "right_hand", "left_thigh", "left_leg", "left_foot", "right_thigh", "right_leg" and "right_foot". The model output includes local and mean skin temperature, local core temperature, local and mean skin wettedness, and heat loss from the skin etc. The model output can be accessed using "dict_results()" method and be converted to a csv file using "to_csv" method. Each output parameter also can be accessed using getter methods. (ex. X.t_skin, X.t_skin_mean, X.t_core) If you use this package, please cite us as follows and mention the version of pythermalcomfort used: Y. Takahashi, A. Nomoto, S. Yoda, R. Hisayama, M. Ogata, Y. Ozeki, S. Tanabe, Thermoregulation Model JOS-3 with New Open Source Code, Energy & Buildings (2020), doi: https://doi.org/10.1016/j.enbuild.2020.110575 Note: To maintain consistency in variable names for pythermalcomfort, some variable names differ from those used in the original paper. """
[docs] def __init__( self, height=Default.height, weight=Default.weight, fat=Default.body_fat, age=Default.age, sex=Default.sex, ci=Default.cardiac_index, bmr_equation=Default.bmr_equation, bsa_equation=Default.bsa_equation, ex_output=None, ): """Initialize a new instance of JOS3 class, which is designed to model and simulate various physiological parameters related to human thermoregulation. This class uses mathematical models to calculate and predict body temperature, basal metabolic rate, body surface area, and other related parameters. Parameters ---------- height : float, optional body height, in [m]. The default is 1.72. weight : float, optional body weight, in [kg]. The default is 74.43. fat : float, optional fat percentage, in [%]. The default is 15. age : int, optional age, in [years]. The default is 20. sex : str, optional sex ("male" or "female"). The default is "male". ci : float, optional Cardiac index, in [L/min/m2]. The default is 2.6432. bmr_equation : str, optional The equation used to calculate basal metabolic rate (BMR). Choose a BMR equation. The default is "harris-benedict" equation created uding Caucasian's data. (DOI: doi.org/10.1073/pnas.4.12.370) If the Ganpule's equation (DOI: doi.org/10.1038/sj.ejcn.1602645) for Japanese people is used, input "japanese". bsa_equation : str, optional The equation used to calculate body surface area (bsa). Choose a bsa equation. You can choose "dubois", "fujimoto", "kruazumi", or "takahira". The default is "dubois". The body surface area can be calculated using the function :py:meth:`pythermalcomfort.utilities.body_surface_area`. ex_output : None, list or "all", optional This is used when you want to display results other than the default output parameters (ex.skin temperature); by default, JOS outputs only the most necessary parameters in order to reduce the computational load. If the parameters other than the default output parameters are needed, specify the list of the desired parameter names in string format like ["bf_skin", "bf_core", "t_artery"]. If you want to display all output results, set ex_output is "all". Attributes ---------- tdb : float, int, or array-like Dry bulb air temperature [°C]. tr : float, int, or array-like Mean radiant temperature [°C]. to : float, int, or array-like Operative temperature [°C]. v : float, int, or array-like Air speed [m/s]. rh : float, int, or array-like Relative humidity [%]. clo : float or array-like Clothing insulation [clo]. Note: If you want to input clothing insulation to each body part, it can be input using the dictionaly in "utilities.py" in "jos3_function" folder. :py:meth:`pythermalcomfort.jos3_functions.utilities.local_clo_typical_ensembles`. par : float Physical activity ratio [-]. This equals the ratio of metabolic rate to basal metabolic rate. The par of sitting quietly is 1.2. posture : str Body posture [-]. Choose a posture from standing, sitting or lying. body_temp : numpy.ndarray (85,) All segment temperatures of JOS-3 Methods ------- simulate(times, dtime, output): Run JOS-3 model for given times. dict_results(): Get results as a dictionary with pandas.DataFrame values. to_csv(path=None, folder=None, unit=True, meaning=True): Export results as csv format. Returns ------- cardiac_output: cardiac output (the sum of the whole blood flow) [L/h] cycle_time: the counts of executing one cycle calculation [-] dt : time step [sec] pythermalcomfort_version: version of pythermalcomfort [-] q_res : heat loss by respiration [W] q_skin2env: total heat loss from the skin (each body part) [W] q_thermogenesis_total: total thermogenesis of the whole body [W] simulation_time: simulation times [sec] t_core : core temperature (each body part) [°C] t_skin : skin temperature (each body part) [°C] t_skin_mean: mean skin temperature [°C] w : skin wettedness (each body part) [-] w_mean : mean skin wettedness [-] weight_loss_by_evap_and_res: weight loss by the evaporation and respiration of the whole body [g/sec] OPTIONAL PARAMETERS : the paramters listed below are returned if ex_output = "all" age : age [years] bf_ava_foot: AVA blood flow rate of one foot [L/h] bf_ava_hand: AVA blood flow rate of one hand [L/h] bf_core : core blood flow rate (each body part) [L/h] bf_fat : fat blood flow rate (each body part) [L/h] bf_muscle: muscle blood flow rate (each body part) [L/h] bf_skin : skin blood flow rate (each body part) [L/h] bsa : body surface area (each body part) [m2] clo : clothing insulation (each body part) [clo] e_max : maximum evaporative heat loss from the skin (each body part) [W] e_skin : evaporative heat loss from the skin (each body part) [W] e_sweat : evaporative heat loss from the skin by only sweating (each body part) [W] fat : body fat rate [%] height : body height [m] name : name of the model [-] par : physical activity ratio [-] q_bmr_core: core thermogenesis by basal metabolism (each body part) [W] q_bmr_fat: fat thermogenesis by basal metabolism (each body part) [W] q_bmr_muscle: muscle thermogenesis by basal metabolism (each body part) [W] q_bmr_skin: skin thermogenesis by basal metabolism (each body part) [W] q_nst : core thermogenesis by non-shivering (each body part) [W] q_res_latent: latent heat loss by respiration (each body part) [W] q_res_sensible: sensible heat loss by respiration (each body part) [W] q_shiv : core or muscle thermogenesis by shivering (each body part) [W] q_skin2env_latent: latent heat loss from the skin (each body part) [W] q_skin2env_sensible: sensible heat loss from the skin (each body part) [W] q_thermogenesis_core: core total thermogenesis (each body part) [W] q_thermogenesis_fat: fat total thermogenesis (each body part) [W] q_thermogenesis_muscle: muscle total thermogenesis (each body part) [W] q_thermogenesis_skin: skin total thermogenesis (each body part) [W] q_work : core or muscle thermogenesis by work (each body part) [W] r_et : total clothing evaporative heat resistance (each body part) [(m2*kPa)/W] r_t : total clothing heat resistance (each body part) [(m2*K)/W] rh : relative humidity (each body part) [%] sex : sex [-] t_artery: arterial temperature (each body part) [°C] t_cb : central blood temperature [°C] t_core_set: core set point temperature (each body part) [°C] t_fat : fat temperature (each body part) [°C] t_muscle: muscle temperature (each body part) [°C] t_skin_set: skin set point temperature (each body part) [°C] t_superficial_vein: superficial vein temperature (each body part) [°C] t_vein : vein temperature (each body part) [°C] tdb : dry bulb air temperature (each body part) [°C] to : operative temperature (each body part) [°C] tr : mean radiant temperature (each body part) [°C] v : air velocity (each body part) [m/s] weight : body weight [kg] Examples -------- Build a model and set a body built Create an instance of the JOS3 class with optional body parameters such as body height, weight, age, sex, etc. .. code-block:: python >>> import numpy as np >>> import pandas as pd >>> import matplotlib.pyplot as plt >>> import os >>> from pythermalcomfort.models import JOS3 >>> from pythermalcomfort.jos3_functions.utilities import local_clo_typical_ensembles >>> >>> directory_name = "jos3_output_example" >>> current_directory = os.getcwd() >>> jos3_example_directory = os.path.join(current_directory, directory_name) >>> if not os.path.exists(jos3_example_directory): >>> os.makedirs(jos3_example_directory) >>> >>> model = JOS3( >>> height=1.7, >>> weight=60, >>> fat=20, >>> age=30, >>> sex="male", >>> bmr_equation="japanese", >>> bsa_equation="fujimoto", >>> ex_output="all", >>> ) >>> # Set environmental conditions such as air temperature, mean radiant temperature using the setter methods. >>> # Set the first condition >>> # Environmental parameters can be input as int, float, list, dict, numpy array format. >>> model.tdb = 28 # Air temperature [°C] >>> model.tr = 30 # Mean radiant temperature [°C] >>> model.rh = 40 # Relative humidity [%] >>> model.v = np.array( # Air velocity [m/s] >>> [ >>> 0.2, # head >>> 0.4, # neck >>> 0.4, # chest >>> 0.1, # back >>> 0.1, # pelvis >>> 0.4, # left shoulder >>> 0.4, # left arm >>> 0.4, # left hand >>> 0.4, # right shoulder >>> 0.4, # right arm >>> 0.4, # right hand >>> 0.1, # left thigh >>> 0.1, # left leg >>> 0.1, # left foot >>> 0.1, # right thigh >>> 0.1, # right leg >>> 0.1, # right foot >>> ] >>> ) >>> model.clo = local_clo_typical_ensembles["briefs, socks, undershirt, work jacket, work pants, safety shoes"]["local_body_part"] >>> # par should be input as int, float. >>> model.par = 1.2 # Physical activity ratio [-], assuming a sitting position >>> # posture should be input as int (0, 1, or 2) or str ("standing", "sitting" or "lying"). >>> # (0="standing", 1="sitting" or 2="lying") >>> model.posture = "sitting" # Posture [-], assuming a sitting position >>> >>> # Run JOS-3 model >>> model.simulate( >>> times=30, # Number of loops of a simulation >>> dtime=60, # Time delta [sec]. The default is 60. >>> ) # Exposure time = 30 [loops] * 60 [sec] = 30 [min] >>> # Set the next condition (You only need to change the parameters that you want to change) >>> model.to = 20 # Change operative temperature >>> model.v = { # Air velocity [m/s], assuming to use a desk fan >>> 'head' : 0.2, >>> 'neck' : 0.4, >>> 'chest' : 0.4, >>> 'back': 0.1, >>> 'pelvis' : 0.1, >>> 'left_shoulder' : 0.4, >>> 'left_arm' : 0.4, >>> 'left_hand' : 0.4, >>> 'right_shoulder' : 0.4, >>> 'right_arm' : 0.4, >>> 'right_hand' : 0.4, >>> 'left_thigh' : 0.1, >>> 'left_leg' : 0.1, >>> 'left_foot' : 0.1, >>> 'right_thigh' : 0.1, >>> 'right_leg' : 0.1, >>> 'right_foot' : 0.1 >>> } >>> # Run JOS-3 model >>> model.simulate( >>> times=60, # Number of loops of a simulation >>> dtime=60, # Time delta [sec]. The default is 60. >>> ) # Additional exposure time = 60 [loops] * 60 [sec] = 60 [min] >>> # Set the next condition (You only need to change the parameters that you want to change) >>> model.tdb = 30 # Change air temperature [°C] >>> model.tr = 35 # Change mean radiant temperature [°C] >>> # Run JOS-3 model >>> model.simulate( >>> times=30, # Number of loops of a simulation >>> dtime=60, # Time delta [sec]. The default is 60. >>> ) # Additional exposure time = 30 [loops] * 60 [sec] = 30 [min] >>> # Show the results >>> df = pd.DataFrame(model.dict_results()) # Make pandas.DataFrame >>> df[["t_skin_mean", "t_skin_head", "t_skin_chest", "t_skin_left_hand"]].plot() # Plot time series of local skin temperature. >>> plt.legend(["Mean", "Head", "Chest", "Left hand"]) # Reset the legends >>> plt.ylabel("Skin temperature [°C]") # Set y-label as 'Skin temperature [°C]' >>> plt.xlabel("Time [min]") # Set x-label as 'Time [min]' >>> plt.savefig(os.path.join(jos3_example_directory, "jos3_example2_skin_temperatures.png")) # Save plot at the current directory >>> plt.show() # Show the plot >>> # Exporting the results as csv >>> model.to_csv(os.path.join(jos3_example_directory, "jos3_example2 (all output).csv")) """ # Version of pythermalcomfort version_string = ( __version__ # get the current version of pythermalcomfort package ) version_number_string = re.findall("\d+\.\d+\.\d+", version_string)[0] self._version = version_number_string # (ex. 'X.Y.Z') # Initialize basic attributes self._height = height self._weight = weight self._fat = fat self._sex = sex self._age = age self._ci = ci self._bmr_equation = bmr_equation self._bsa_equation = bsa_equation self._ex_output = ex_output # Calculate body surface area (bsa) rate self._bsa_rate = cons.bsa_rate(height, weight, bsa_equation) # Calculate local body surface area self._bsa = cons.local_bsa(height, weight, bsa_equation) # Calculate basal blood flow (BFB) rate [-] self._bfb_rate = cons.bfb_rate(height, weight, bsa_equation, age, ci) # Calculate thermal conductance (CDT) [W/K] self._cdt = cons.conductance(height, weight, bsa_equation, fat) # Calculate thermal capacity [J/K] self._cap = cons.capacity(height, weight, bsa_equation, age, ci) # Set initial core and skin temperature set points [°C] self.setpt_cr = np.ones(17) * Default.core_temperature self.setpt_sk = np.ones(17) * Default.skin_temperature # Initialize body temperature [°C] self._bodytemp = np.ones(NUM_NODES) * Default.other_body_temperature # Initialize environmental conditions and other factors # (Default values of input conditions) self._ta = np.ones(17) * Default.dry_bulb_air_temperature self._tr = np.ones(17) * Default.mean_radiant_temperature self._rh = np.ones(17) * Default.relative_humidity self._va = np.ones(17) * Default.air_speed self._clo = np.ones(17) * Default.clothing_insulation self._iclo = np.ones(17) * Default.clothing_vapor_permeation_efficiency self._par = Default.physical_activity_ratio self._posture = Default.posture self._hc = None # Convective heat transfer coefficient self._hr = None # Radiative heat transfer coefficient self.ex_q = np.zeros(NUM_NODES) # External heat gain self._t = dt.timedelta(0) # Elapsed time self._cycle = 0 # Cycle time self.model_name = "JOS3" # Model name self.options = { "nonshivering_thermogenesis": True, "cold_acclimated": False, "shivering_threshold": False, "limit_dshiv/dt": False, "bat_positive": False, "ava_zero": False, "shivering": False, } # Set shivering threshold = 0 threg.PRE_SHIV = 0 # Initialize history to store model parameters self._history = [] # Set elapsed time and cycle time to 0 self._t = dt.timedelta(0) # Elapsed time self._cycle = 0 # Cycle time # Reset set-point temperature and save the last model parameters dictout = self._reset_setpt(par=Default.physical_activity_ratio) self._history.append(dictout)
def _calculate_operative_temp_when_pmv_is_zero( self, va=Default.air_speed, rh=Default.relative_humidity, met=Default.metabolic_rate, clo=Default.clothing_insulation, ) -> float: """Calculate operative temperature [°C] when PMV=0. Parameters ---------- va : float, optional Air velocity [m/s]. The default is 0.1. rh : float, optional Relative humidity [%]. The default is 50. met : float, optional Metabolic rate [met]. The default is 1. clo : float, optional Clothing insulation [clo]. The default is 0. Returns ------- to : float Operative temperature [°C]. """ to = 28 # initial operative temperature # Iterate until the PMV (Predicted Mean Vote) value is less than 0.001 for _ in range(100): vpmv = pmv(tdb=to, tr=to, vr=va, rh=rh, met=met, clo=clo) # Break the loop if the absolute value of PMV is less than 0.001 if abs(vpmv) < 0.001: break # Update the temperature based on the PMV value else: to = to - vpmv / 3 return to def _reset_setpt(self, par=Default.physical_activity_ratio): """Reset set-point temperatures under steady state calculation. Set-point temperatures are hypothetical core or skin temperatures in a thermally neutral state when at rest (similar to room set-point temperature for air conditioning). This function is used during initialization to calculate the set-point temperatures as a reference for thermoregulation. Be careful, input parameters (tdb, tr, rh, v, clo, par) and body temperatures are also reset. Returns ------- dict Parameters of JOS-3 model. """ # Set operative temperature under PMV=0 environment # 1 met = 58.15 W/m2 w_per_m2_to_met = 1 / 58.15 # unit converter W/m2 to met met = self.bmr * par * w_per_m2_to_met # [met] self.to = self._calculate_operative_temp_when_pmv_is_zero(met=met) self.rh = Default.relative_humidity self.v = Default.air_speed self.clo = Default.clothing_insulation self.par = par # Physical activity ratio # Steady-calculation self.options["ava_zero"] = True for _ in range(10): dict_out = self._run(dtime=60000, passive=True) # Set new set-point temperatures for core and skin self.setpt_cr = self.t_core self.setpt_sk = self.t_skin self.options["ava_zero"] = False return dict_out
[docs] def simulate(self, times, dtime=60, output=True): """Run JOS-3 model. Parameters ---------- times : int Number of loops of a simulation. dtime : int or float, optional Time delta in seconds. The default is 60. output : bool, optional If you don't want to record parameters, set False. The default is True. Returns ------- None. """ # Loop through the simulation for the given number of times for _ in range(times): # Increment the elapsed time by the time delta self._t += dt.timedelta(0, dtime) # Increment the cycle counter self._cycle += 1 # Execute the simulation step dict_data = self._run(dtime=dtime, output=output) # If output is True, append the results to the history if output: self._history.append(dict_data)
def _run(self, dtime=60, passive=False, output=True): """Runs the model once and gets the model parameters. The function then calculates several thermoregulation parameters using the input data, such as convective and radiative heat transfer coefficients, operative temperature, heat resistance, and blood flow rates. It also calculates the thermogenesis by shivering and non-shivering, basal thermogenesis, and thermogenesis by work. The function then calculates the total heat loss and gains, including respiratory, sweating, and extra heat gain, and builds the matrices required to solve for the new body temperature. It then calculates the new body temperature by solving the matrices using numpy's linalg library. Finally, the function returns a dictionary of the simulation results. The output parameters include cycle time, model time, t_skin_mean, t_skin, t_core, w_mean, w, weight_loss_by_evap_and_res, cardiac_output, q_thermogenesis_total, q_res, and q_skin_env. Additionally, if the _ex_output variable is set to "all" or is a list of keys, the function also returns a detailed dictionary of all the thermoregulation parameters and other variables used in the simulation. Parameters ---------- dtime : int or float, optional Time delta [sec]. Default is 60. passive : bool, optional If you run a passive model, set to True. Default is False. output : bool, optional If you don't need parameters, set to False. Default is True. Returns ------- dict_out : dictionary Output parameters. """ # Compute convective and radiative heat transfer coefficient [W/(m2*K)] # based on posture, air velocity, air temperature, and skin temperature. # Manual setting is possible by setting self._hc and self._hr. # Compute heat and evaporative heat resistance [m2.K/W], [m2.kPa/W] # Get core and skin temperatures tcr = self.t_core tsk = self.t_skin # Convective and radiative heat transfer coefficients [W/(m2*K)] hc = threg.fixed_hc( threg.conv_coef( self._posture, self._va, self._ta, tsk, ), self._va, ) hr = threg.fixed_hr( threg.rad_coef( self._posture, ) ) # Manually set convective and radiative heat transfer coefficients if necessary if self._hc is not None: hc = self._hc if self._hr is not None: hr = self._hr # Compute operative temp. [°C], clothing heat and evaporative resistance [m2.K/W], [m2.kPa/W] # Operative temp. [°C] to = threg.operative_temp( self._ta, self._tr, hc, hr, ) # Clothing heat resistance [m2.K/W] r_t = threg.dry_r(hc, hr, self._clo) # Clothing evaporative resistance [m2.kPa/W] r_et = threg.wet_r(hc, self._clo, self._iclo) # ------------------------------------------------------------------ # Thermoregulation # 1) Sweating # 2) Vasoconstriction, Vasodilation # 3) Shivering and non-shivering thermogenesis # ------------------------------------------------------------------ # Compute the difference between the set-point temperature and body temperatures # and other thermoregulation parameters. # If running a passive model, the set-point temperature of thermoregulation is # set to the current body temperature. # set-point temperature for thermoregulation if passive: setpt_cr = tcr.copy() setpt_sk = tsk.copy() else: setpt_cr = self.setpt_cr.copy() setpt_sk = self.setpt_sk.copy() # Error signal = Difference between set-point and body temperatures err_cr = tcr - setpt_cr err_sk = tsk - setpt_sk # SWEATING THERMOREGULATION # Skin wettedness [-], e_skin, e_max, e_sweat [W] # Calculate skin wettedness, sweating heat loss, maximum sweating rate, and total sweat rate wet, e_sk, e_max, e_sweat = threg.evaporation( err_cr, err_sk, tsk, self._ta, self._rh, r_et, self._height, self._weight, self._bsa_equation, self._age, ) # VASOCONSTRICTION, VASODILATION # Calculate skin blood flow and basal skin blood flow [L/h] bf_skin = threg.skin_blood_flow( err_cr, err_sk, self._height, self._weight, self._bsa_equation, self._age, self._ci, ) # Calculate hands and feet's AVA blood flow [L/h] bf_ava_hand, bf_ava_foot = threg.ava_blood_flow( err_cr, err_sk, self._height, self._weight, self._bsa_equation, self._age, self._ci, ) if self.options["ava_zero"] and passive: bf_ava_hand = 0 bf_ava_foot = 0 # SHIVERING AND NON-SHIVERING # Calculate shivering thermogenesis [W] q_shiv = threg.shivering( err_cr, err_sk, tcr, tsk, self._height, self._weight, self._bsa_equation, self._age, self._sex, dtime, self.options, ) # Calculate non-shivering thermogenesis (NST) [W] if self.options["nonshivering_thermogenesis"]: q_nst = threg.nonshivering( err_sk, self._height, self._weight, self._bsa_equation, self._age, self.options["cold_acclimated"], self.options["bat_positive"], ) else: # not consider NST q_nst = np.zeros(17) # ------------------------------------------------------------------ # Thermogenesis # ------------------------------------------------------------------ # Calculate local basal metabolic rate (BMR) [W] q_bmr_local = threg.local_mbase( self._height, self._weight, self._age, self._sex, self._bmr_equation, ) # Calculate overall basal metabolic rate (BMR) [W] q_bmr_total = sum([m.sum() for m in q_bmr_local]) # Calculate thermogenesis by work [W] q_work = threg.local_q_work(q_bmr_total, self._par) # Calculate the sum of thermogenesis in core, muscle, fat, skin [W] ( q_thermogenesis_core, q_thermogenesis_muscle, q_thermogenesis_fat, q_thermogenesis_skin, ) = threg.sum_m( q_bmr_local, q_work, q_shiv, q_nst, ) q_thermogenesis_total = ( q_thermogenesis_core.sum() + q_thermogenesis_muscle.sum() + q_thermogenesis_fat.sum() + q_thermogenesis_skin.sum() ) # ------------------------------------------------------------------ # Others # ------------------------------------------------------------------ # Calculate blood flow in core, muscle, fat [L/h] bf_core, bf_muscle, bf_fat = threg.cr_ms_fat_blood_flow( q_work, q_shiv, self._height, self._weight, self._bsa_equation, self._age, self._ci, ) # Calculate heat loss by respiratory p_a = threg.antoine(self._ta) * self._rh / 100 res_sh, res_lh = threg.resp_heat_loss( self._ta[0], p_a[0], q_thermogenesis_total ) # Calculate sensible heat loss [W] shl_sk = (tsk - to) / r_t * self._bsa # Calculate cardiac output [L/h] co = threg.sum_bf(bf_core, bf_muscle, bf_fat, bf_skin, bf_ava_hand, bf_ava_foot) # Calculate weight loss rate by evaporation [g/sec] wlesk = (e_sweat + 0.06 * e_max) / 2418 wleres = res_lh / 2418 # ------------------------------------------------------------------ # Matrix # This code section is focused on constructing and calculating # various matrices required for modeling the thermoregulation # of the human body. # Since JOS-3 has 85 thermal nodes, the determinant of 85*85 is to be solved. # ------------------------------------------------------------------ # Matrix A = Matrix for heat exchange due to blood flow and conduction occurring between tissues # (85, 85,) ndarray # Calculates the blood flow in arteries and veins for core, muscle, fat, skin, # and arteriovenous anastomoses (AVA) in hands and feet, # and combines them into two arrays: # 1) bf_local for the local blood flow and 2) bf_whole for the whole-body blood flow. # These arrays are then combined to form arr_bf. bf_art, bf_vein = matrix.vessel_blood_flow( bf_core, bf_muscle, bf_fat, bf_skin, bf_ava_hand, bf_ava_foot ) bf_local = matrix.local_arr( bf_core, bf_muscle, bf_fat, bf_skin, bf_ava_hand, bf_ava_foot ) bf_whole = matrix.whole_body(bf_art, bf_vein, bf_ava_hand, bf_ava_foot) arr_bf = np.zeros((NUM_NODES, NUM_NODES)) arr_bf += bf_local arr_bf += bf_whole # Adjusts the units of arr_bf from [W/K] to [/sec] and then to [-] # by dividing by the heat capacity self._cap and multiplying by the time step dtime. arr_bf /= self._cap.reshape((NUM_NODES, 1)) # Change unit [W/K] to [/sec] arr_bf *= dtime # Change unit [/sec] to [-] # Performs similar unit conversions for the convective heat transfer coefficient array arr_cdt # (also divided by self._cap and multiplied by dtime). arr_cdt = self._cdt.copy() arr_cdt /= self._cap.reshape((NUM_NODES, 1)) # Change unit [W/K] to [/sec] arr_cdt *= dtime # Change unit [/sec] to [-] # Matrix B = Matrix for heat transfer between skin and environment arr_b = np.zeros(NUM_NODES) arr_b[INDEX["skin"]] += 1 / r_t * self._bsa arr_b /= self._cap # Change unit [W/K] to [/sec] arr_b *= dtime # Change unit [/sec] to [-] # Calculates the off-diagonal and diagonal elements of the matrix A, # which represents the heat transfer coefficients between different parts of the body, # and combines them to form the full matrix A (arrA). # Then, the inverse of matrix A is computed (arrA_inv). arr_a_tria = -(arr_cdt + arr_bf) arr_a_dia = arr_cdt + arr_bf arr_a_dia = arr_a_dia.sum(axis=1) + arr_b arr_a_dia = np.diag(arr_a_dia) arr_a_dia += np.eye(NUM_NODES) arr_a = arr_a_tria + arr_a_dia arr_a_inv = np.linalg.inv(arr_a) # Matrix Q = Matrix for heat generation rate from thermogenesis, respiratory, sweating, # and extra heat gain processes in different body parts. # Matrix Q [W] / [J/K] * [sec] = [-] # Thermogensis arr_q = np.zeros(NUM_NODES) arr_q[INDEX["core"]] += q_thermogenesis_core arr_q[INDEX["muscle"]] += q_thermogenesis_muscle[VINDEX["muscle"]] arr_q[INDEX["fat"]] += q_thermogenesis_fat[VINDEX["fat"]] arr_q[INDEX["skin"]] += q_thermogenesis_skin # Respiratory [W] arr_q[INDEX["core"][2]] -= res_sh + res_lh # chest core # Sweating [W] arr_q[INDEX["skin"]] -= e_sk # Extra heat gain [W] arr_q += self.ex_q.copy() arr_q /= self._cap # Change unit [W]/[J/K] to [K/sec] arr_q *= dtime # Change unit [K/sec] to [K] # Boundary batrix [°C] arr_to = np.zeros(NUM_NODES) arr_to[INDEX["skin"]] += to # Combines the current body temperature, the boundary matrix, and the heat generation matrix # to calculate the new body temperature distribution (arr). arr = self._bodytemp + arr_b * arr_to + arr_q # ------------------------------------------------------------------ # New body temp. [°C] # ------------------------------------------------------------------ self._bodytemp = np.dot(arr_a_inv, arr) # ------------------------------------------------------------------ # Output parameters # ------------------------------------------------------------------ dict_out = {} if output: # Default output dict_out["pythermalcomfort_version"] = self._version dict_out["cycle_time"] = self._cycle dict_out["simulation_time"] = self._t dict_out["dt"] = dtime dict_out["t_skin_mean"] = self.t_skin_mean dict_out["t_skin"] = self.t_skin dict_out["t_core"] = self.t_core dict_out["w_mean"] = np.average(wet, weights=Default.local_bsa) dict_out["w"] = wet dict_out["weight_loss_by_evap_and_res"] = wlesk.sum() + wleres dict_out["cardiac_output"] = co dict_out["q_thermogenesis_total"] = q_thermogenesis_total dict_out["q_res"] = res_sh + res_lh dict_out["q_skin2env"] = shl_sk + e_sk detail_out = {} if self._ex_output and output: detail_out["name"] = self.model_name detail_out["height"] = self._height detail_out["weight"] = self._weight detail_out["bsa"] = self._bsa detail_out["fat"] = self._fat detail_out["sex"] = self._sex detail_out["age"] = self._age detail_out["t_core_set"] = setpt_cr detail_out["t_skin_set"] = setpt_sk detail_out["t_cb"] = self.t_cb detail_out["t_artery"] = self.t_artery detail_out["t_vein"] = self.t_vein detail_out["t_superficial_vein"] = self.t_superficial_vein detail_out["t_muscle"] = self.t_muscle detail_out["t_fat"] = self.t_fat detail_out["to"] = to detail_out["r_t"] = r_t detail_out["r_et"] = r_et detail_out["tdb"] = self._ta.copy() detail_out["tr"] = self._tr.copy() detail_out["rh"] = self._rh.copy() detail_out["v"] = self._va.copy() detail_out["par"] = self._par detail_out["clo"] = self._clo.copy() detail_out["e_skin"] = e_sk detail_out["e_max"] = e_max detail_out["e_sweat"] = e_sweat detail_out["bf_core"] = bf_core detail_out["bf_muscle"] = bf_muscle[VINDEX["muscle"]] detail_out["bf_fat"] = bf_fat[VINDEX["fat"]] detail_out["bf_skin"] = bf_skin detail_out["bf_ava_hand"] = bf_ava_hand detail_out["bf_ava_foot"] = bf_ava_foot detail_out["q_bmr_core"] = q_bmr_local[0] detail_out["q_bmr_muscle"] = q_bmr_local[1][VINDEX["muscle"]] detail_out["q_bmr_fat"] = q_bmr_local[2][VINDEX["fat"]] detail_out["q_bmr_skin"] = q_bmr_local[3] detail_out["q_work"] = q_work detail_out["q_shiv"] = q_shiv detail_out["q_nst"] = q_nst detail_out["q_thermogenesis_core"] = q_thermogenesis_core detail_out["q_thermogenesis_muscle"] = q_thermogenesis_muscle[ VINDEX["muscle"] ] detail_out["q_thermogenesis_fat"] = q_thermogenesis_fat[VINDEX["fat"]] detail_out["q_thermogenesis_skin"] = q_thermogenesis_skin dict_out["q_skin2env_sensible"] = shl_sk dict_out["q_skin2env_latent"] = e_sk dict_out["q_res_sensible"] = res_sh dict_out["q_res_latent"] = res_lh if self._ex_output == "all": dict_out.update(detail_out) elif isinstance(self._ex_output, list): # if ex_out type is list out_keys = detail_out.keys() for key in self._ex_output: if key in out_keys: dict_out[key] = detail_out[key] return dict_out
[docs] def dict_results(self): """Get results as a dictionary with pandas.DataFrame values. Returns ------- dict A dictionary of the results, with keys as column names and values as pandas.DataFrame objects. """ if not self._history: print("The model has no data.") return None def check_word_contain(word, *args): """Check if word contains *args.""" bool_filter = False for arg in args: if arg in word: bool_filter = True return bool_filter # Set column titles # If the values are iter, add the body names as suffix words. # If the values are not iter and the single value data, convert it to iter. key2keys = {} # Column keys for key, value in self._history[0].items(): try: length = len(value) if isinstance(value, str): keys = [key] # str is iter. Convert to list without suffix elif check_word_contain(key, "sve", "sfv", "superficialvein"): keys = [key + "_" + BODY_NAMES[i] for i in VINDEX["sfvein"]] elif check_word_contain(key, "ms", "muscle"): keys = [key + "_" + BODY_NAMES[i] for i in VINDEX["muscle"]] elif check_word_contain(key, "fat"): keys = [key + "_" + BODY_NAMES[i] for i in VINDEX["fat"]] elif length == 17: # if data contains 17 values keys = [key + "_" + bn for bn in BODY_NAMES] else: keys = [key + "_" + BODY_NAMES[i] for i in range(length)] except TypeError: # if the value is not iter. keys = [key] # convert to iter key2keys.update({key: keys}) data = [] for i, dictout in enumerate(self._history): row = {} for key, value in dictout.items(): keys = key2keys[key] if len(keys) == 1: values = [value] # make list if value is not iter else: values = value row.update(dict(zip(keys, values))) data.append(row) out_dict = dict(zip(data[0].keys(), [[] for _ in range(len(data[0].keys()))])) for row in data: for k in data[0].keys(): out_dict[k].append(row[k]) return out_dict
[docs] def to_csv( self, path: str = None, folder: str = None, unit: bool = True, meaning: bool = True, ) -> None: """Export results as csv format. Parameters ---------- path : str, optional Output path. If you don't use the default file name, set a name. The default is None. folder : str, optional Output folder. If you use the default file name with the current time, set a only folder path. The default is None. unit : bool, optional Write units in csv file. The default is True. meaning : bool, optional Write meanings of the parameters in csv file. The default is True. Returns ------- None Examples ---------- >>> from pythermalcomfort.models import JOS3 >>> model = JOS3() >>> model.simulate(60) >>> model.to_csv() """ # Use the model name and current time as default output file name if path is None: now_time = dt.datetime.now().strftime("%Y%m%d-%H%M%S") path = "{}_{}.csv".format(self.model_name, now_time) if folder: os.makedirs(folder, exist_ok=True) path = folder + os.sep + path elif not ((path[-4:] == ".csv") or (path[-4:] == ".txt")): path += ".csv" # Get simulation results as a dictionary dict_out = self.dict_results() # Get column names, units and meanings columns = [k for k in dict_out.keys()] units = [] meanings = [] for col in columns: param, body_name = remove_body_name(col) if param in ALL_OUT_PARAMS: u = ALL_OUT_PARAMS[param]["unit"] units.append(u) m = ALL_OUT_PARAMS[param]["meaning"] if body_name: # Replace underscores with spaces body_name_with_spaces = body_name.replace("_", " ") meanings.append(m.replace("each body part", body_name_with_spaces)) else: meanings.append(m) else: units.append("") meanings.append("") # Write to csv file with open(path, "wt", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(list(columns)) if unit: writer.writerow(units) if meaning: writer.writerow(meanings) for i in range(len(dict_out["cycle_time"])): row = [] for k in columns: row.append(dict_out[k][i]) writer.writerow(row)
def _set_ex_q(self, tissue, value): """Set extra heat gain by tissue name. Parameters ---------- tissue : str Tissue name. "core", "skin", or "artery".... If you set value to head muscle and other segment's core, set "all_muscle". value : int, float, array Heat gain [W] Returns ------- array Extra heat gain of model. """ self.ex_q[INDEX[tissue]] = value return self.ex_q @property def tdb(self): """Dry-bulb air temperature. The setter accepts int, float, dict, list, ndarray. The inputs are used to create a 17-element array. dict should be passed with BODY_NAMES as keys. Returns ------- ndarray A NumPy array of shape (17,). """ return self._ta @tdb.setter def tdb(self, inp): self._ta = _to17array(inp) @property def tr(self): """tr : numpy.ndarray (17,) Mean radiant temperature [°C].""" return self._tr @tr.setter def tr(self, inp): self._tr = _to17array(inp) @property def to(self): """to : numpy.ndarray (17,) Operative temperature [°C].""" hc = threg.fixed_hc( threg.conv_coef( self._posture, self._va, self._ta, self.t_skin, ), self._va, ) hr = threg.fixed_hr( threg.rad_coef( self._posture, ) ) to = threg.operative_temp( self._ta, self._tr, hc, hr, ) return to @to.setter def to(self, inp): self._ta = _to17array(inp) self._tr = _to17array(inp) @property def rh(self): """rh : numpy.ndarray (17,) Relative humidity [%].""" return self._rh @rh.setter def rh(self, inp): self._rh = _to17array(inp) @property def v(self): """v : numpy.ndarray (17,) Air velocity [m/s].""" return self._va @v.setter def v(self, inp): self._va = _to17array(inp) @property def posture(self): """posture : str Current JOS3 posture.""" return self._posture @posture.setter def posture(self, inp): if inp == 0: self._posture = "standing" elif inp == 1: self._posture = "sitting" elif inp == 2: self._posture = "lying" elif type(inp) == str: if inp.lower() == "standing": self._posture = "standing" elif inp.lower() in ["sitting", "sedentary"]: self._posture = "sitting" elif inp.lower() in ["lying", "supine"]: self._posture = "lying" else: self._posture = "standing" print('posture must be 0="standing", 1="sitting" or 2="lying".') print('posture was set "standing".') @property def clo(self): """clo : numpy.ndarray (17,) Clothing insulation [clo].""" return self._clo @clo.setter def clo(self, inp): self._clo = _to17array(inp) @property def par(self): """par : float Physical activity ratio [-].This equals the ratio of metabolic rate to basal metabolic rate. par of sitting quietly is 1.2.""" return self._par @par.setter def par(self, inp): self._par = inp @property def body_temp(self): """body_temp : numpy.ndarray (85,) All segment temperatures of JOS-3""" return self._bodytemp @body_temp.setter def body_temp(self, inp): self._bodytemp = inp.copy() @property def bsa(self): """bsa : numpy.ndarray (17,) Body surface areas by local body segments [m2].""" return self._bsa.copy() @property def r_t(self): """r_t : numpy.ndarray (17,) Dry heat resistances between the skin and ambience areas by local body segments [(m2*K)/W].""" hc = threg.fixed_hc( threg.conv_coef( self._posture, self._va, self._ta, self.t_skin, ), self._va, ) hr = threg.fixed_hr( threg.rad_coef( self._posture, ) ) return threg.dry_r(hc, hr, self._clo) @property def r_et(self): """r_et : numpy.ndarray (17,) w (Evaporative) heat resistances between the skin and ambience areas by local body segments [(m2*kPa)/W].""" hc = threg.fixed_hc( threg.conv_coef( self._posture, self._va, self._ta, self.t_skin, ), self._va, ) return threg.wet_r(hc, self._clo, self._iclo) @property def w(self): """w : numpy.ndarray (17,) Skin wettedness on local body segments [-].""" err_cr = self.t_core - self.setpt_cr err_sk = self.t_skin - self.setpt_sk wet, *_ = threg.evaporation( err_cr, err_sk, self.t_skin, self._ta, self._rh, self.r_et, self._bsa_rate, self._age, ) return wet @property def w_mean(self): """w_mean : float Mean skin wettedness of the whole body [-].""" wet = self.w return np.average(wet, weights=Default.local_bsa) @property def t_skin_mean(self): """t_skin_mean : float Mean skin temperature of the whole body [°C].""" return np.average(self._bodytemp[INDEX["skin"]], weights=Default.local_bsa) @property def t_skin(self): """t_skin : numpy.ndarray (17,) Skin temperatures by the local body segments [°C].""" return self._bodytemp[INDEX["skin"]].copy() @t_skin.setter def t_skin(self, inp): self._bodytemp[INDEX["skin"]] = _to17array(inp) @property def t_core(self): """t_core : numpy.ndarray (17,) Skin temperatures by the local body segments [°C].""" return self._bodytemp[INDEX["core"]].copy() @property def t_cb(self): """t_cb : numpy.ndarray (1,) Temperature at central blood pool [°C].""" return self._bodytemp[0].copy() @property def t_artery(self): """t_artery : numpy.ndarray (17,) Arterial temperatures by the local body segments [°C].""" return self._bodytemp[INDEX["artery"]].copy() @property def t_vein(self): """t_vein : numpy.ndarray (17,) Vein temperatures by the local body segments [°C].""" return self._bodytemp[INDEX["vein"]].copy() @property def t_superficial_vein(self): """t_superficial_vein : numpy.ndarray (12,) Superficial vein temperatures by the local body segments [°C].""" return self._bodytemp[INDEX["sfvein"]].copy() @property def t_muscle(self): """t_muscle : numpy.ndarray (2,) Muscle temperatures of head and pelvis [°C].""" return self._bodytemp[INDEX["muscle"]].copy() @property def t_fat(self): """t_fat : numpy.ndarray (2,) fat temperatures of head and pelvis [°C].""" return self._bodytemp[INDEX["fat"]].copy() @property def body_names(self): """body_names : list JOS3 body names""" return BODY_NAMES @property def results(self): """Results of the model: dict.""" return self.dict_results() @property def bmr(self): """bmr : float Basal metabolic rate [W/m2].""" tcr = threg.basal_met( self._height, self._weight, self._age, self._sex, self._bmr_equation, ) return tcr / self.bsa.sum() @property def version(self): """version : float The current version of pythermalcomfort.""" return self._version