pythermalcomfort
pythermalcomfort is a Python toolkit for computing thermal comfort indices, heat/cold stress metrics, and thermophysiological responses. Its implementations adhere to international standards and peer-reviewed research, offering researchers, engineers, and building scientists reliable, standards-compliant calculations without the burden of implementing them manually.
Key Features
Thermal Comfort Models – PMV/PPD, adaptive comfort assessments, SET, and more bundled into a single API surface.
Heat & Cold Stress Indices – UTCI, Heat Index, Wind Chill, Humidex, and other commonly-referenced metrics.
Thermophysiological Modeling – two-node (Gagge) and multinode (JOS-3) models for estimating core/skin temperatures and skin wettedness.
Standards Compliance – Calculations based on ASHRAE 55, ISO 7730, EN 16798, and supporting references.
Vectorized Inputs – Accepts scalars, lists, or NumPy arrays; most functions broadcast across input arrays automatically.
Pythonic API – Simple, documented entry points that plug into analysis workflows and pipelines.
Rich Documentation – Tutorials, examples, and reference guides for each supported model and index.
Active Development – Frequent releases, new features, and responsive issue resolution.
Open Source – MIT licensed and developed transparently on GitHub.
Why Choose pythermalcomfort?
Precision – Accurate evaluations of comfort and stress that engineers can trust.
Efficiency – Eliminates repetitive code so teams can focus on insights, not implementation details.
Versatility – Useful in building science, HVAC design, biometeorology, sports science, and thermal physiology.
Evidence-Based Decisions – Supports data-driven HVAC sizing, occupant comfort strategies, and performance benchmarking.
Installation
Install from PyPI:
pip install pythermalcomfort
For alternative installation instructions, including development builds and optional dependencies, see the official docs.
Requirements
Python 3.10+
NumPy, SciPy, Numba, setuptools (installed automatically)
Optional: pandas, Matplotlib, or other plotting libraries for examples and visualizations
Quick Start
A few lines are all you need to get started:
from pythermalcomfort.models import pmv_ppd_iso, utci
# Calculate PMV and PPD using ISO 7730 standard
result = pmv_ppd_iso(
tdb=25, # dry-bulb temperature in °C
tr=25, # mean radiant temperature in °C
vr=0.1, # relative air speed in m/s
rh=50, # relative humidity in %
met=1.4, # metabolic rate in met
clo=0.5, # clothing insulation in clo
model="7730-2005",
)
print(f"PMV: {result.pmv}, PPD: {result.ppd}")
# Calculate UTCI for heat stress assessment
result = utci(tdb=30, tr=30, v=0.5, rh=50)
print(result.utci)
# Most functions also accept arrays for bulk calculations
result = utci(tdb=[28, 30, 35], tr=[28, 30, 35], v=0.5, rh=50)
print(result.utci)
For a full list of models and indices, see the API reference.
Support pythermalcomfort
Maintaining an open-source scientific package takes time. You can help by:
Submitting code, docs, or tests via a pull request
Reporting reproducible bugs or feature requests in the issue tracker
Assisting with testing, translations, or PR reviews
Starring or sharing the project to raise awareness
Contributions
We welcome all contributions. Please read the contributing guide before you start.
Quick checklist
Open an issue when planning large changes to align on scope.
Fork the repo and create a feature branch.
Add or update tests for new behavior.
Run linters/formatters and fix the reported issues.
Update docs or the changelog when the public API changes.
Submit clear, focused PRs with related issues linked.
Common commands
# clone your fork and add upstream remote
git clone git@github.com:your-username/pythermalcomfort.git
cd pythermalcomfort
git remote add upstream git@github.com:pythermalcomfort/pythermalcomfort.git
git fetch upstream
# create a branch and work on it
git checkout -b Feature/awesome-feature
tox # run the full matrix (slow)
tox -e py312 # run a single env
pytest -k test_name_fragment
# fix linting/formatting
ruff check --fix
ruff format
docformatter --in-place --wrap-summaries 88 --wrap-descriptions 88 pythermalcomfort/*.py
# commit and push
git add .
git commit -m "feat: short description of change"
git push origin Feature/awesome-feature
Release process
Releases are tag-driven and published via GitHub Actions Trusted Publishing (OIDC — no PYPI_API_TOKEN or TEST_PYPI_API_TOKEN secret is required).
The standard cycle is:
Develop and test a release candidate on development → TestPyPI.
Merge development → master via pull request.
Finalize the version on master → PyPI.
Step 1 — pre-release on development (TestPyPI)
git checkout development
git pull --ff-only
git fetch --tags --prune
# Start the RC cycle for the next patch release (e.g. 3.9.8 → 3.9.9rc1):
bump-my-version bump patch
# Push the bump commit and tag — this triggers tests + TestPyPI deploy:
git push
git push --tags
If the RC needs additional fixes, make the commits then create another RC:
bump-my-version bump pre_n # e.g. 3.9.9rc1 → 3.9.9rc2
git push
git push --tags
Step 2 — open and merge a pull request from development to master
CI runs the full test suite on the PR. Once it passes, merge via GitHub.
Step 3 — finalize on master (PyPI)
git checkout master
git pull --ff-only
git fetch --tags --prune
# Strip the rc suffix to produce the stable version.
# Replace X.Y.Z with the target version (e.g. 3.9.9):
bump-my-version bump --new-version X.Y.Z patch
# Push the bump commit and tag — this triggers tests + PyPI deploy:
git push
git push --tags
Rules:
All RC tags (vX.Y.ZrcN) must be pushed from development.
All stable tags (vX.Y.Z) must be pushed from master after merging.
Tag format: vX.Y.Z for stable, vX.Y.ZrcN for pre-release.
Do not push a stable tag before the corresponding development → master PR has been merged; the CI deploy job will reject it.
Getting Help
Open an issue on GitHub with a minimal reproduction in the issue tracker.
Ask questions in PR comments for implementation guidance.
Review the contribution guidelines for testing, documentation, and changelog expectations.
Consult the API reference and examples at https://pythermalcomfort.readthedocs.io/en/latest/
Changelog
A full list of changes per release is available in the CHANGELOG.
License
pythermalcomfort is released under the MIT License.
Stats
Changelog
Unreleased
4.6.1 (2026-10-06)
Fixed AdaptivePlot.plot()’s default x-axis label to use the terminology of the active standard: Prevailing Mean Outdoor Air Temperature [°C] for ASHRAE 55 (previously missing “Air”) and Running Mean Outdoor Temperature [°C] for EN 16798 (previously used the ASHRAE wording) (#418).
Fixed vertical_tmp_grad_ppd returning negative ppd_vg values for small gradients or warm thermal sensation; the result is now set to 0 when the logistic model is below the 34.5 % baseline (Liu et al. 2020, eq. 3).
Fixed AdaptivePlot losing or mislabeling legend entries for comfort bands and the center line when the legend is rebuilt after adding measured data (#415).
PsychrometricPlot draws constant-RH background curves at 25 % intervals instead of 10 %, and RH labels use Matplotlib’s default font size instead of a fixed 8 pt, for readability on charts of different sizes.
Fixed AdaptivePlot.plot() showing Matplotlib’s internal auto-generated label (e.g. _child0) in the legend when fill_kws or center_line_kws explicitly passed label=None, instead of falling back to the band’s or center line’s configured default label.
4.6.0 (2026-09-17)
Standardized the default plot palette: cooler regions progress from pale to muted blue, warmer regions from pale to muted terracotta, and a true central comfort region uses neutral gray. Out-of-model-limit areas use a contrasting neutral gray, and plots limit each axis to six major tick labels by default.
Added an annual PMV heatmap-and-summary Matplotlib recipe to the plotting examples.
Deprecated the following legacy public import paths. They continue to work for two minor releases and emit DeprecationWarning pointing to their new locations; this is not an immediate breaking change.
Environment calculations:
pythermalcomfort.utilities.mean_radiant_tmp → pythermalcomfort.environment.mean_radiant_tmp
pythermalcomfort.utilities.operative_tmp → pythermalcomfort.environment.operative_tmp
pythermalcomfort.utilities.running_mean_outdoor_temperature → pythermalcomfort.environment.running_mean_outdoor_temperature
pythermalcomfort.utilities.transpose_sharp_altitude → pythermalcomfort.environment.transpose_sharp_altitude
pythermalcomfort.utilities.f_svv → pythermalcomfort.environment.f_svv
pythermalcomfort.utilities.v_relative → pythermalcomfort.environment.v_relative
pythermalcomfort.utils.scale_wind_speed_log → pythermalcomfort.environment.scale_wind_speed_log
Psychrometric calculations:
pythermalcomfort.utilities.p_sat → pythermalcomfort.psychrometrics.p_sat
pythermalcomfort.utilities.p_sat_torr → pythermalcomfort.psychrometrics.p_sat_torr
pythermalcomfort.utilities.antoine → pythermalcomfort.psychrometrics.antoine
pythermalcomfort.utilities.psy_ta_rh → pythermalcomfort.psychrometrics.psy_ta_rh
pythermalcomfort.utilities.hr_to_rh → pythermalcomfort.psychrometrics.hr_to_rh
pythermalcomfort.utilities.wet_bulb_tmp → pythermalcomfort.psychrometrics.wet_bulb_tmp
pythermalcomfort.utilities.dew_point_tmp → pythermalcomfort.psychrometrics.dew_point_tmp
pythermalcomfort.utilities.enthalpy_air → pythermalcomfort.psychrometrics.enthalpy_air
Clothing calculations:
pythermalcomfort.utilities.clo_dynamic_ashrae → pythermalcomfort.clothing.clo_dynamic_ashrae
pythermalcomfort.utilities.clo_dynamic_iso → pythermalcomfort.clothing.clo_dynamic_iso
pythermalcomfort.utilities.clo_intrinsic_insulation_ensemble → pythermalcomfort.clothing.clo_intrinsic_insulation_ensemble
pythermalcomfort.utilities.clo_area_factor → pythermalcomfort.clothing.clo_area_factor
pythermalcomfort.utilities.clo_insulation_air_layer → pythermalcomfort.clothing.clo_insulation_air_layer
pythermalcomfort.utilities.clo_total_insulation → pythermalcomfort.clothing.clo_total_insulation
pythermalcomfort.utilities.clo_correction_factor_environment → pythermalcomfort.clothing.clo_correction_factor_environment
Moved internal-only valid_range and mapping from pythermalcomfort.shared_functions to pythermalcomfort._internal.validation as _valid_range and _mapping. These private helpers were never public API, so no compatibility aliases are provided.
Fixed validate_type so NumPy scalar inputs are returned as native Python scalars, and updated input dataclasses to store those normalized values.
Breaking (plots only): ThresholdPlot (and therefore PsychrometricPlot) now finds region boundaries by root-finding instead of contouring a raster grid. For every row of the plot it bisects to the exact place where the output crosses a threshold, and where the model leaves its applicability limits, then fills between the resulting curves. Region edges and the out-of-model-limits area follow smooth curves rather than grid steps. Where a boundary lies no longer depends on resolution at all; sampling only decides whether a feature is found, so a model that turns sharply enough to hide a crossing between two samples still needs a finer setting. Charts where several limits clip each other – pmv_ppd_iso, whose PMV, dry-bulb and vapour-pressure limits used to leave a staircase of grey squares – benefit most.
A threshold may be crossed more than once along a row, and each crossing gets its own curve. ppd is the usual case: it falls to a minimum at neutrality and rises again, so “PPD below 10” is a strip with “above 10” on both sides. Such a chart used to be drawn on the raster path and came out jagged; it is now solved like any other.
The boundaries come back as coordinate arrays in result.boundaries, one BoundaryCurve per threshold per branch, with .threshold, .branch, .x and .y, so they can be re-used, exported or re-styled directly.
The contour backend is gone, along with plot()’s backend argument and result.backend. Geometry that cannot be laid out the same way in every row – an internal hole in the model’s valid area, or a pair of crossings that only appears partway up the chart – now raises ValueError rather than silently falling back to a grid-stepped rendering. Narrowing the axis ranges to where the model is well behaved is usually the fix; utci(), for instance, needs tdb capped near 42 degC before its polynomial stops diverging (see #410).
result.fills is consequently a list of one fill_between polygon per band rather than a single ContourSet, and fill_kws reaches ax.fill_between rather than ax.contourf. Code testing isinstance(artist, QuadContourSet) no longer matches.
resolution is now optional on set_x_axis and set_y_axis. It never set the precision of a boundary – bisection does – so it only matters for a model that turns sharply enough to step over a feature between samples. Both axes have sampling floors, so omitting it gives a chart that is already smooth.
Threshold boundary lines are now hidden by default (show_lines=True brings them back): the region fills meet exactly on the boundary, so the colour change already marks it and the extra line mostly added weight. The out-of-model-limits shading is a light neutral gray (#C4C9CC). The grid and the top and right spines are now set on the axis rather than through rc_context, which fixes charts drawn on a caller-supplied ax keeping whatever frame and grid the caller’s rcParams gave them – multi-panel figures were previously styled inconsistently. Call result.ax.grid(True) to put the grid back.
Threshold and psychrometric charts evaluate their model with round_output=False. Models round for display – pmv_ppd_iso to 0.01 PMV – which turns the output into a staircase, and bisecting output >= threshold on a staircase parks the boundary on the edge of a quantisation plateau instead of the real crossing: about 0.03 degC of dry-bulb at a typical PMV slope. Setting round_output through set_params still overrides this.
Threshold and psychrometric charts no longer relay the models’ out-of-applicability-limits warnings. Sweeping across those limits is how the chart finds the out-of-model-limits area, so the warning fired on every grid evaluation and said nothing the chart was not about to shade – one 129-point sweep of pmv_ppd_iso raises two warnings of about 500 characters each, and a notebook full of charts drowned in them. Calling a model directly still warns exactly as before, and only that one message is filtered – warnings reporting a calculation going wrong, such as cooling_effect’s solver returning zero, still reach the caller.
A chart title now sits above however many rows its legend needs, instead of at a fixed height: a five-region chart wrapped its legend onto two rows and the title landed in the middle of it. The fixed height was marginally too low even for a one-row legend, so titles were always very slightly clipped.
PsychrometricPlot writes each constant-RH label on its curve, rotated to follow it and set in a gap left in the curve, rather than parking it at the curve’s end. The curves fan out, and a label beside the bundle is easy to read against the wrong line.
Fixed heat_index_rothfusz and heat_index_schoen classifying heat stress from the rounded heat index. Categories now use the unrounded SI value, so round_output only affects the returned numeric heat index (#381).
Sped up two_nodes_gagge_ji by compiling its per-simulation time loop with Numba and parallelizing independent array inputs.
4.5.0 (2026-09-15)
Breaking (plots only): PsychrometricPlot’s y-axis is now expressed in g of water per kg of dry air instead of kg/kg. Typical indoor humidity ratios are 5-20 g/kg, which is far easier to read than 0.005-0.020 kg/kg. Pass .set_y_axis("hr", 0.0, 30.0, resolution=1.0) where you previously passed .set_y_axis("hr", 0.0, 0.030, resolution=0.001). A y-axis whose upper bound is below 1 g/kg now emits a UserWarning explaining the change, so an un-migrated call is flagged rather than silently rendering a blank chart. It warns rather than raises because humidity ratios below 1 g/kg are physically real in cold or very dry air (at -20 degC, 0.5 g/kg is roughly 80 % RH), which this package supports (#338).
This does not change the psychrometric utilities. psy_ta_rh(...).hr still returns kg/kg dry air, and hr_to_rh() and enthalpy_air() still accept it, which is the SI convention and matches ASHRAE Fundamentals. So multiply by 1000 when plotting psy_ta_rh(...).hr on this chart, and divide by 1000 when passing a value read off this chart to hr_to_rh() or enthalpy_air().
PsychrometricPlot now labels its own y-axis. Previously it inherited ThresholdPlot’s behaviour of labelling the axis with the raw parameter name, so the axis read hr unless the caller set a label. Every caller therefore wrote its own and they disagreed with each other about the units. Override with result.ax.set_ylabel(...) if needed.
Fixed two_nodes_gagge_sleep silently truncating or coercing a non-integer ltime keyword argument (e.g. 1.5 became one iteration, "1" was accepted as a string) instead of raising. Non-int values now raise TypeError, and values below 1 now raise ValueError rather than running zero iterations.
Fixed invalid Numba annotations on the vectorised helpers in heat_index_lu, heat_index_rothfusz, heat_index_schoen, and utci. The scalar kernels keep ordinary float annotations and the vectorize decorators are now typed to reflect that they accept both scalars and arrays. The explicit Numba signatures are unchanged, so results are unaffected (#393).
Documentation: clarified the pmv_ppd_iso model parameters, distinguished the ASHRAE and EN acceptability outputs of the adaptive models, and documented the air-speed assumptions behind AdaptivePlot scatter overlays.
4.4.3 (2026-09-14)
Fixed JOS3.dict_results() returning body part names instead of simulated values (#264). Each per-segment column was built by zipping its keys against a JOS3BodyParts __dict__; iterating a dict yields its keys, so roughly 570 of the 577 columns held strings such as "head" rather than temperatures. Only the aggregate scalars (t_skin_mean and similar) were correct. JOS3.to_csv() is affected too, since it is built on dict_results().
Fixed per-segment values for variables defined on only part of the body (t_muscle, t_fat) in JOS3.dict_results(). Their column names came from VINDEX while their values were taken as the first n entries of a full 17-segment container, so t_muscle_pelvis carried the neck’s value. Names and values are now selected with the same indices. Note t_superficial_vein remains mislabelled: it packs 12 limb values into the container’s first 12 slots, and relabelling requires confirming the intended segment mapping.
Fixed examples/calc_jos3.py setting model.icl, which JOS3 does not define. Clothing insulation is exposed as clo, so the assignment created an unused attribute and the Stolwijk & Hardy validation ran at 0 clo, i.e. a nude subject, rather than the intended 0.3 clo pattern.
The JOS-3 human-subject reference data ships as CSV instead of .xlsx. Reading it previously required openpyxl, which is not a dependency of this package, so validation_simulation() failed for anyone running the examples as documented. The values are unchanged; read them with pd.read_csv(..., float_precision="round_trip").
Added examples/manuscript-v4/, the reproducible scripts behind the figures in the Building Simulation manuscript describing this package, including a new JOS-3 transient example comparing simulated rectal and mean skin temperature against Stolwijk & Hardy (1966) human-subject data.
Sped up two_nodes_gagge_sleep by compiling its stateful simulation loop with Numba while preserving its public output values and shapes. Empty tdb/tr/v/rh/clo/thickness_quilt inputs now raise a clear ValueError instead of failing with an unrelated TypeError.
Addressed Copilot review feedback on the 4.4.1 phs fix: pass param_name explicitly to valid_range() for the (tr - tdb) check, and added regression tests for the applicability-limit and minute-1 skin-temperature behavior.
Fixed the saturation vapour pressure calculation in utci (#372): the Hardy/Wexler equation’s ln(T) term used np.log1p (which computes ln(1 + T)) instead of np.log, inflating the saturation vapour pressure by ~1%. The resulting UTCI error is negligible in mild conditions (~0.03 °C at 25 °C, 50% RH) but grows to ~0.7 °C at 40 °C, 80% RH, where UTCI matters most for heat stress assessment. Updated the affected hard-coded test expectations and added a regression test cross-checking utci’s vapour pressure against p_sat.
4.4.2 (2026-09-02)
Pinned tests/conftest.py’s validation-data-comfort-models fixture URL to the v1.0.0 tag instead of main, so upstream fixture changes can’t silently affect CI before the pin is deliberately bumped and reviewed. See CONTRIBUTING.rst’s “Keeping the validation-data-comfort-models pin current” section.
4.4.1 (2026-08-18)
- Fixed the phs applicability limits (#225):
the tr limit is now checked against ISO 7933 Annex A, Table A.1’s actual 0 < (tr - tdb) < 60 range, instead of checking raw tr against (0, 60).
the metabolic rate limit is now standard-specific: 100-450 W/m2 (1.7-7.5 met) for the 2004 standard, 56-250 W/m2 (0.96-4.3 met) for the 2023 standard, instead of always using the 2004 range.
Added the ISO 7933:2023 Annex E minute-1 skin temperature special case to the phs 2023 model (t_sk is forced to its equilibrium value on the first minute), matching the standard’s own reference program. This special case is not present in the 2004 edition.
4.4.0 (2026-07-25)
Added ireq model to calculate Required Clothing Insulation (IREQ) and Duration Limited Exposure (DLE) based on ISO 11079.
4.3.0 (2026-07-24)
Added SummaryPlot.set_categories() for summarizing an already-classified per-row category array — e.g. adaptive comfort’s per-row acceptability bands, which can’t be reduced to one continuous column plus fixed thresholds the way set_regions() handles PMV/UTCI-style outputs. See the set_categories() docstring for an np.select-based recipe.
4.2.0 (2026-07-24)
Added a pa (water vapour partial pressure) applicability check to pmv_ppd_iso, per the ISO 7730 Clause 4 limit of 0 Pa to 2 700 Pa. Inputs outside this range (e.g. tdb=30, rh=100 gives pa ~4 243 Pa) now return nan instead of a value outside the standard’s applicability.
Fixed clo_dynamic_iso to estimate walking speed using the ISO 7730 Annex C / ISO 9920 formula for undefined walking speed (v_walk = 0.0052 * (met * 58.15 - 58), clipped to 0-0.7 m/s) instead of reusing v_relative’s activity-generated-air-speed formula, which is a distinct formula intended for the whole-body PMV heat balance rather than the clothing dynamic insulation correction.
Corrected the initial guess for clothing surface temperature in pmv_ppd_iso to match the corrected Annex D formula in ISO 7730:2025 (3.5 * (6.45 * icl + 0.1), missing the 6.45 * factor present in the ISO 7730:2005 Annex D listing). This only affects the starting point of the iterative solver and does not change any output value.
Added "7730-2025" as a supported model value for pmv_ppd_iso and made it the default, since ISO 7730:2025 is now the current edition of the standard. "7730-2005" remains supported for backwards compatibility; both currently return identical results since the PMV/PPD formulae are unchanged between editions.
4.1.1 (2026-07-20)
Sped up cooling_effect (~10x) by calling the already numba-jitted Gagge two-node kernel directly instead of the full set_tmp() public API on every root-finding iteration.
Sped up solar_gain (~100x+) with numba: table-based interpolation and posture handling converted to JIT-compiled code.
Pinned pillow>=10.3.0 in docs requirements to resolve a transitive Snyk-flagged vulnerability.
Reduced the build-test-publish-testPyPI.yml CI matrix to speed up TestPyPI release checks.
4.1.0 (2026-07-20)
Added heat_index_schoen, the Temperature-Humidity Index (THI) heat index model in accordance with Schoen (2005).
Sped up heat_index_rothfusz, heat_index_schoen, and heat_index_lu with numba JIT compilation. heat_index_lu (an iterative root-solver) sees the largest gain, roughly 29x faster.
4.0.3 (2026-07-20)
Added a Sports.CROQUET preset to sports_heat_stress_risk (clo=0.7, met=4.5, vr=0.5, duration=90).
Fixed the extreme-risk interpolation segment so it reaches a risk level of 4.9 exactly 5 °C above the extreme threshold, instead of reaching it early at +4.5 °C and leaving the last 0.5 °C of the range dead.
Fixed an inconsistency in sports_heat_stress_risk’s extreme-risk interpolation: the upper anchor temperature used internally to scale the risk level was the raw, unrounded solver output, while the t_extreme value returned to callers is rounded to one decimal. This could produce a risk level inconsistent with the documented/returned thresholds. The interpolation now uses the same rounded t_extreme that is returned.
4.0.2 (2026-06-23)
Improved sports heat stress risk interpolation within the extreme range: the upper anchor temperature (where risk reaches 4.9) is now computed dynamically as t_extreme + 5 °C. This makes the extreme-range scale consistent across humidity conditions — the risk always spans exactly 5 °C above the humidity-dependent extreme threshold regardless of ambient conditions.
4.0.1 (2026-06-17)
Added Python 3.14 support. Removed pytest-travis-fold (unmaintained, incompatible with Python 3.14) and lifted the pytest<7 cap.
Fixed duplicate parametrize IDs in the ridge-regression test suite, which pytest 9 now rejects (ast.Str was removed in Python 3.14).
4.0.0 (2026-06-16)
New plotting module (pythermalcomfort.plots.matplotlib)
ThresholdPlot — shade comfort/stress regions on any two-axis chart (e.g. operative temperature vs. relative humidity, temperature vs. air speed). Configure regions via set_regions(thresholds, labels, colors).
SummaryPlot — horizontal or vertical bar-summary chart built from a pandas.DataFrame; useful for comparing multiple spaces or scenarios at a glance.
AdaptivePlot — ready-made adaptive comfort chart for ASHRAE 55 and EN 16798, with configurable comfort bands.
PsychrometricPlot — psychrometric chart with overlaid comfort regions.
All classes share a common BasePlot base and centralised visual defaults (_shared.py), making it easy to apply a consistent house style.
Other changes
Refactored type hints across model function signatures to use a NumericInput alias (float | int | np.floating | np.integer), improving IDE auto-complete and static-analysis accuracy.
Added hr_to_rh utility for humidity-ratio → relative-humidity conversion.
Minor cooling-effect calculation streamlining and constant centralisation.
3.9.8 (2026-05-25)
Added optional round_output parameter to adaptive_ashrae and adaptive_en to control rounding of output values.
Added limit_inputs parameter to ankle_draft and vertical_tmp_grad_ppd, consistent with other model functions.
ankle_draft and vertical_tmp_grad_ppd now raise UserWarning when inputs exceed model applicability limits.
Fixed compliance attribute being included in non-ASHRAE PMV model outputs; it is now only returned by pmv_ppd_ashrae.
Fixed UTCI stress category mapping when units="IP"; categories were incorrectly mapped before IP unit conversion.
3.9.3 (2026-05-01)
Maintenance release: internal CI pipeline improvements and dependency updates. No user-facing changes.
3.9.2 (2026-04-14)
Updated sports_heat_stress_risk so risk_level_interpolated now uses 1.0-4.0 instead of 0.0-3.0.
Updated sports_heat_stress_risk to enforce the sport-specific minimum air speed (sport.vr).
3.9.1 (2026-02-25)
Improved speed of PHS model.
3.9.0 (2026-02-03)
Added sports_heat_stress_risk function to assess heat stress risk for athletes during outdoor sports activities based on environmental conditions. Addresses issue #237.
3.7.0 (2025-10-28)
Added machine learning model to predict skin and rectal temperature ridge_regression_predict_t_re_t_sk.
3.6.1 (2025-10-07)
Fix issue with disc calculation in the two_nodes_gagge model and limiting its value to 6. Close #251
Improve documentation for the disc function.
PMV ASHRAE model returns the compliance boolean value with the ASHRAE 55:2023 standard. Close #253
Improve formatting of models outputs to the console.
3.6.0 (2025-09-22)
3.5.1 (2025-09-15)
Improved documentation on how to contribute to the project
3.5.0 (2025-09-10)
Added the scale_winds_speed_log function to scale wind speed.
3.4.3 (2025-07-31)
fix: wind chill temperature was not imported in pythermalcomfort.models
3.4.2 (2025-07-22)
fixed unit of sweat_rate in the PHS model
3.4.1 (2025-07-14)
fixed some typo in the documentation
better formatted the code
3.4.0 (2025-06-08)
Added the work_capacity_dunne.
Added the work_capacity_hothaps.
Added the work_capacity_iso.
Added the work_capacity_niosh.
3.3.0 (2025-06-05)
Added the two_nodes_gagge_ji function to calculate the two-node model for older individuals
Added the Temperature-Humidity Index (THI).
3.2.0 (2025-05-20)
Added the two_nodes_gagge_sleep function to calculate the two-node model for sleeping individuals
Added the ESI function to calculate the Environmental Stress Index.
3.1.0 (2025-04-28)
- Updated the PHS model in compliance with the ISO 7933:2023 standard
Added default‑kwarg overrides for 2023 mode (f_r, t_re, t_cr_eq)
removed unused variable round from default_kwargs
Included test cases according to the ISO 7933:2023 standard
Added AutoStrMixin to provide a formatted __str__ representation for result classes
3.0.1 (2025-04-14)
allow np.float and np.int as inputs to all functions
fixed documentation for phs - met units
3.0.0 (2025-02-03)
2.10.0 (2024-03-18)
allow n-dimensional arrays for pet_steady and speedup p_sat calculation
2.9.1 (2024-01-19)
Fixed error calculation of mass sweating in PET mode, the unit was incorrect
2.9.0 (2024-01-15)
The PHS model accepts arrays as inputs
2.8.11 (2023-10-26)
wrote more test and improved code
2.8.11 (2023-10-26)
fixed issues with the documentation and sorted the models in alphabetical order
2.8.7 (2023-10-23)
Adaptive ASHRAE now returns a dataclass
2.8.6 (2023-10-09)
re-structured and linted the code
2.8.4 (2023-09-20)
calculation of cooling effect in pmv (standard=’ashrae’) triggered only when v>0.1 m/s
2.8.3 (2023-09-14)
general improvements in the JOS3 model
2.8.2 (2023-09-04)
general improvements in the JOS3 model
fixed error when e_max == 0
2.8.1 (2023-07-05)
pythermalcomfort needs Python version > 3.8
fixed issue in Cooling Effect calculation
2.8.0 (2023-07-03)
allowing the cooling effect to range from 0 to 40
fixed PHS documentation
improved JOS3 documentation
2.7.0 (2023-02-16)
changed coefficient of vasodilation in set_tmp() to 120 to match ASHRAE 55 2020 code
slightly modified value in validation tables
2.6.0 (2023-01-17)
max sweating rate can be passed to two node model
max skin wettedness can be passed to two node model
rounding w to two decimals
use_fans_heatwave function accepts arrays
fixed typos unit documentation
2.5.4 (2022-10-12)
PHS model accepts all required inputs to be run on a minute by minute basis
fix error check compliance PHS model
2.5.0 (2022-06-13)
Added the adaptive thermal heat balance (ATHB) model
2.4.0 (2022-06-10)
Added e_pmv model - Adjusted Predicted Mean Votes with Expectancy Factor
Added a_pmv model - Adaptive Predicted Mean Vote
2.3.0 (2022-06-01)
Added discomfort index
2.2.0 (2022-05-17)
Implemented a better equation to calculate the mean radiant temperature
2.1.1 (2022-05-17)
Fixed how DISC is calculated
2.1.0 (2022-04-20)
Added Physiological Equivalent Temperature (PET) model
In PMV and PPD function you can specify if occupants has control over airspeed
2.0.2 (2022-04-12)
UTCI accepts lists as inputs
2.0.0 (2022-04-07)
Allowing users to pass Numpy arrays or lists as input to the pmv_ppd, pmv, clo_tout, both adaptive models, utci, set_tmp, two_nodes
Changed the input variable from return_invalid to limit_input
Increased speed by using Numba @vectorize decorator
Changed ASHRAE 55 2020 limits to match new addenda
Improved documentation
1.11.0 (2022-03-16)
Allowing users to pass a Numpy array as input into the UTCI function
Numpy is now a requirement of pythermalcomfort
Improved PMV, JOS-3, and UTCI documentation
Testing PMV, SET, and solar gains models using online reference tables
1.10.0 (2021-11-15)
Added JOS-3 model
1.9.0 (2021-10-07)
Added Normal Effective Temperature (NET)
Added Apparent Temperature (AT)
Added Wind Chill Index (WCI)
1.8.0 (2021-09-28)
Gagge’s two-node model
Added WBGT equation
Added Heat index (HI)
Added humidex index
1.7.1 (2021-09-08)
Added ASHRAE equation to calculate the operative temperature
1.7.0 (2021-07-29)
Implemented function to calculate the if fans are beneficial during heatwaves
Fixed error in the SET equation to calculated radiative heat transfer coefficient
Fixed error in SET definition
Moved functions optimized with Numba to new file
1.6.2 (2021-07-08)
Updated equation clo_dynamic based on ANSI/ASHRAE Addendum f to ANSI/ASHRAE Standard 55-2020
Fixed import errors in examples
1.6.1 (2021-07-05)
optimized UTCI function with Numba
1.6.0 (2021-05-21)
(BREAKING CHANGE) moved some of the functions from psychrometrics to utilities
added equation to calculate body surface area
1.5.2 (2021-05-05)
return stress category UTCI
1.5.1 (2021-04-29)
optimized phs with Numba
1.5.0 (2021-04-21)
added Predicted Heat Strain (PHS) index from ISO 7933:2004
1.4.6 (2021-03-30)
changed equation to calculate convective heat transfer coefficient in set_tmp() as per Gagge’s 1986
fixed vasodilation coefficient in set_tmp()
docs changed term air velocity with air speed and improved documentation
added new tests for comfort functions
1.3.6 (2021-02-04)
fixed error calculation solar_altitude and sharp for supine person in solar_gain
1.3.5 (2021-02-02)
not rounding SET temperature when calculating cooling effect
1.3.3 (2020-12-14)
added function to calculate sky-vault view fraction
1.3.2 (2020-12-14)
replaced input solar_azimuth with sharp in the solar_gain() function
fixed small error in example pmv calculation
1.3.1 (2020-10-30)
Fixed error calculation of cooling effect with elevated air temperatures
1.3.0 (2020-10-19)
Changed PMV elevated air speed limit from 0.2 to 0.1 m/s
1.2.3 (2020-09-09)
Fixed error in the calculation of erf
Updated validation table erf
1.2.2 (2020-08-21)
Changed default diameter in mean_radiant_tmp
Improved documentation
1.2.0 (2020-07-29)
Significantly improved calculation speed using numba. Wrapped set and pmv functions
1.0.6 (2020-07-24)
Minor speed improvement changed math.pow with **
Added validation PMV validation table from ISO 7730
1.0.4 (2020-07-20)
Improved speed calculation of the Cooling Effect
Bisection has been replaced with Brentq function from scipy
1.0.3 (2020-07-01)
Annotated variables in the SET code.
1.0.2 (2020-06-11)
Fixed an error in the bisection equation used to calculated Cooling Effect.
1.0.0 (2020-06-09)
Major stable release.
0.7.0 (2020-06-09)
Added equation to calculate the dynamic clothing insulation
0.6.3 (2020-04-11)
Fixed error in calculation adaptive ASHRAE
Added some examples
0.6.3 (2020-03-17)
Renamed function to_calc to t_o
Fixed error calculation of relative air speed
renamed input parameter ta to tdb
Added function to calculate mean radiant temperature from black globe temperature
Added function to calculate solar gain on people
Added functions to calculate vapour pressure, wet-bulb temperature, dew point temperature, and psychrometric data from dry bulb temperature and RH
Added authors
Added dictionaries with reference clo and met values
Added function to calculate enthalpy_air
0.5.2 (2020-03-11)
Added function to calculate the running mean outdoor temperature
0.5.1 (2020-03-06)
There was an error in version 0.4.2 in the calculation of PMV and PPD with elevated air speed, i.e. vr > 0.2 which has been fixed in this version
Added function to calculate the cooling effect in accordance with ASHRAE
0.4.1 (2020-02-17)
Removed compatibility with python 2.7 and 3.5
0.4.0 (2020-02-17)
Created adaptive_EN, v_relative, t_clo, vertical_tmp_gradient, ankle_draft functions and wrote tests.
Added possibility to decide with measuring system to use SI or IP.
0.3.0 (2020-02-13)
Created set_tmp, adaptive_ashrae, UTCI functions and wrote tests.
Added warning to let the user know if inputs entered do not comply with Standards applicability limits.
0.1.0 (2020-02-11)
Created pmv, pmv_ppd functions and wrote tests.
Documented code.
0.0.0 (2020-02-11)
First release on PyPI.
Metadata
Release files for pythermalcomfort 4.6.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pythermalcomfort-4.6.1.tar.gz | 2.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pythermalcomfort-4.6.1-py2.py3-none-any.whl | Python 3, Python 2 | none | any | Details |
Total release size: 2.7 MB
Release files / pythermalcomfort-4.6.1.tar.gz
| Download URL | pythermalcomfort-4.6.1.tar.gz |
|---|---|
| Size | 2.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0c3e53fe3d65a835983876fd880b9923fafa90f7e367203f267f27c6eeb23a0a
|
|
BLAKE2b-256 checksum How to use checksums |
1ba90c34da19df5dcc99811dee2b008b451a8aa7fe42c2bc372f6ff6f4729524
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logRelease files / pythermalcomfort-4.6.1-py2.py3-none-any.whl
| Download URL | pythermalcomfort-4.6.1-py2.py3-none-any.whl |
|---|---|
| Size | 352.5 kB |
| Tags | Python 2 Python 3 |
|
SHA-256 checksum How to use checksums |
403299cd028a57bac783984da7d5956e50afcdeb3fb2e2e98f3d21723c10edc9
|
|
BLAKE2b-256 checksum How to use checksums |
d128509a1e75f3cbbc5d1c1a4bdd133b541800a7e98e19fd0bef5dc058f9b906
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency log