lwbgt 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. lwbgt-0.3.0/.github/workflows/ci.yml +46 -0
  2. lwbgt-0.3.0/.github/workflows/release.yml +177 -0
  3. lwbgt-0.3.0/.github/workflows/wheels.yml +140 -0
  4. lwbgt-0.3.0/.gitignore +9 -0
  5. lwbgt-0.3.0/ABI.md +136 -0
  6. lwbgt-0.3.0/CHANGELOG.md +63 -0
  7. lwbgt-0.3.0/CITATION.cff +42 -0
  8. lwbgt-0.3.0/CMakeLists.txt +327 -0
  9. lwbgt-0.3.0/LICENSE +201 -0
  10. lwbgt-0.3.0/LICENSES/LicenseRef-UChicago-Argonne-WBGT-1.1.txt +41 -0
  11. lwbgt-0.3.0/LICENSING.md +22 -0
  12. lwbgt-0.3.0/NOTICE +15 -0
  13. lwbgt-0.3.0/PKG-INFO +237 -0
  14. lwbgt-0.3.0/README.md +208 -0
  15. lwbgt-0.3.0/RELEASING.md +56 -0
  16. lwbgt-0.3.0/UPSTREAM.md +19 -0
  17. lwbgt-0.3.0/benchmarks/README.md +45 -0
  18. lwbgt-0.3.0/benchmarks/baseline-gcc-13.3.0.json +62 -0
  19. lwbgt-0.3.0/benchmarks/benchmark.c +183 -0
  20. lwbgt-0.3.0/benchmarks/optimization-1-gcc-13.3.0.json +62 -0
  21. lwbgt-0.3.0/benchmarks/optimization-2-gcc-13.3.0.json +62 -0
  22. lwbgt-0.3.0/benchmarks/optimization-3-environment.json +41 -0
  23. lwbgt-0.3.0/benchmarks/optimization-3-gcc-13.3.0.json +62 -0
  24. lwbgt-0.3.0/benchmarks/optimization-3-gcc-16.2.0.json +62 -0
  25. lwbgt-0.3.0/benchmarks/optimization-3-profile.md +62 -0
  26. lwbgt-0.3.0/benchmarks/v0.2.0-environment.json +38 -0
  27. lwbgt-0.3.0/benchmarks/v0.2.0-gcc-13.3.0.json +62 -0
  28. lwbgt-0.3.0/cmake/lwbgt.def +5 -0
  29. lwbgt-0.3.0/cmake/lwbgt.exports +3 -0
  30. lwbgt-0.3.0/cmake/lwbgt.map +8 -0
  31. lwbgt-0.3.0/cmake/lwbgt.pc.in +11 -0
  32. lwbgt-0.3.0/cmake/lwbgtConfig.cmake.in +3 -0
  33. lwbgt-0.3.0/examples/README.md +22 -0
  34. lwbgt-0.3.0/examples/julia/example.jl +63 -0
  35. lwbgt-0.3.0/examples/python/example.py +84 -0
  36. lwbgt-0.3.0/examples/r/example.R +26 -0
  37. lwbgt-0.3.0/examples/r/lwbgt_r.c +107 -0
  38. lwbgt-0.3.0/include/lwbgt.h +88 -0
  39. lwbgt-0.3.0/pyproject.toml +69 -0
  40. lwbgt-0.3.0/python/lwbgt/__init__.py +7 -0
  41. lwbgt-0.3.0/python/lwbgt/_ffi.py +174 -0
  42. lwbgt-0.3.0/python/lwbgt/_types.py +40 -0
  43. lwbgt-0.3.0/python/lwbgt/py.typed +1 -0
  44. lwbgt-0.3.0/src/lwbgt_ffi.c +75 -0
  45. lwbgt-0.3.0/src/wbgt.c +1170 -0
  46. lwbgt-0.3.0/tests/API.md +63 -0
  47. lwbgt-0.3.0/tests/BASELINE.md +12 -0
  48. lwbgt-0.3.0/tests/HEATSTRESSBENCH.md +35 -0
  49. lwbgt-0.3.0/tests/RELEASE-0.1.0.md +36 -0
  50. lwbgt-0.3.0/tests/RELEASE-0.2.0.md +51 -0
  51. lwbgt-0.3.0/tests/RELEASE-0.2.1.md +32 -0
  52. lwbgt-0.3.0/tests/RELEASE.md +44 -0
  53. lwbgt-0.3.0/tests/batch.c +201 -0
  54. lwbgt-0.3.0/tests/check_distribution.py +86 -0
  55. lwbgt-0.3.0/tests/check_exports.py +51 -0
  56. lwbgt-0.3.0/tests/check_versions.py +54 -0
  57. lwbgt-0.3.0/tests/compare.py +111 -0
  58. lwbgt-0.3.0/tests/consumer/CMakeLists.txt +11 -0
  59. lwbgt-0.3.0/tests/consumer/main.c +14 -0
  60. lwbgt-0.3.0/tests/consumer/main.cpp +28 -0
  61. lwbgt-0.3.0/tests/generate_cases.py +86 -0
  62. lwbgt-0.3.0/tests/probe.c +100 -0
  63. lwbgt-0.3.0/tests/python/_fixtures.py +63 -0
  64. lwbgt-0.3.0/tests/python/test_api.py +72 -0
  65. lwbgt-0.3.0/tests/python/test_batch.py +115 -0
  66. lwbgt-0.3.0/tests/python/test_distribution_check.py +36 -0
  67. lwbgt-0.3.0/tests/python/test_layout.py +61 -0
  68. lwbgt-0.3.0/tests/python/test_packaged_library.py +33 -0
  69. lwbgt-0.3.0/tests/test_install.cmake +35 -0
  70. lwbgt-0.3.0/upstream/wbgt.c.original +1144 -0
@@ -0,0 +1,46 @@
1
+ name: ci
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ build:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ include:
15
+ - os: ubuntu-latest
16
+ cc: gcc
17
+ - os: macos-15-intel
18
+ cc: clang
19
+ - os: windows-latest
20
+ cc: gcc
21
+ runs-on: ${{ matrix.os }}
22
+ env:
23
+ CC: ${{ matrix.cc }}
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+ - uses: actions/setup-python@v6
27
+ with:
28
+ python-version: '3.10'
29
+ - name: Check version coherence
30
+ run: python tests/check_versions.py
31
+ - uses: r-lib/actions/setup-r@v2
32
+ with:
33
+ r-version: '4.3'
34
+ windows-path-include-mingw: true
35
+ - uses: julia-actions/setup-julia@v3
36
+ with:
37
+ version: '1.10'
38
+ - name: Configure
39
+ run: >-
40
+ cmake -S . -B build -G Ninja
41
+ -DCMAKE_BUILD_TYPE=Release
42
+ -DLWBGT_REQUIRE_ALL_BINDING_TESTS=ON
43
+ - name: Build
44
+ run: cmake --build build --parallel
45
+ - name: Test
46
+ run: ctest --test-dir build --output-on-failure
@@ -0,0 +1,177 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ release-tag:
13
+ name: Verify annotated release tag
14
+ runs-on: ubuntu-24.04
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ with:
18
+ fetch-depth: 0
19
+ - name: Require an annotated tag object
20
+ run: test "$(git cat-file -t "$GITHUB_REF_NAME")" = tag
21
+
22
+ artifacts:
23
+ needs: release-tag
24
+ uses: ./.github/workflows/wheels.yml
25
+
26
+ testpypi-publish:
27
+ name: Publish verified artifacts to TestPyPI
28
+ needs: artifacts
29
+ runs-on: ubuntu-24.04
30
+ environment:
31
+ name: testpypi
32
+ url: https://test.pypi.org/project/lwbgt/
33
+ permissions:
34
+ contents: read
35
+ id-token: write
36
+ steps:
37
+ - uses: actions/download-artifact@v8
38
+ with:
39
+ pattern: dist-*
40
+ path: dist
41
+ merge-multiple: true
42
+ - name: Require the complete release matrix
43
+ run: |
44
+ test "$(find dist -maxdepth 1 -type f -name '*.whl' | wc -l)" -eq 5
45
+ test "$(find dist -maxdepth 1 -type f -name '*.tar.gz' | wc -l)" -eq 1
46
+ - name: Publish through TestPyPI Trusted Publishing
47
+ uses: pypa/gh-action-pypi-publish@release/v1
48
+ with:
49
+ repository-url: https://test.pypi.org/legacy/
50
+
51
+ testpypi-smoke:
52
+ name: Verify TestPyPI artifact identity and installation
53
+ needs: testpypi-publish
54
+ runs-on: ubuntu-24.04
55
+ steps:
56
+ - uses: actions/checkout@v6
57
+ - uses: actions/setup-python@v6
58
+ with:
59
+ python-version: '3.10'
60
+ - uses: actions/download-artifact@v8
61
+ with:
62
+ pattern: dist-*
63
+ path: dist
64
+ merge-multiple: true
65
+ - name: Download the release wheel from TestPyPI
66
+ run: |
67
+ version="${GITHUB_REF_NAME#v}"
68
+ mkdir testpypi
69
+ for attempt in $(seq 1 12); do
70
+ if python -m pip download \
71
+ --index-url https://test.pypi.org/simple/ \
72
+ --only-binary=:all: --no-cache-dir --no-deps \
73
+ --dest testpypi "lwbgt==$version"; then
74
+ break
75
+ fi
76
+ if test "$attempt" -eq 12; then
77
+ exit 1
78
+ fi
79
+ sleep 10
80
+ done
81
+ - name: Match the complete tested artifact set byte-for-byte
82
+ run: |
83
+ python - <<'PY'
84
+ import hashlib
85
+ import json
86
+ import os
87
+ import time
88
+ from pathlib import Path
89
+ from urllib.request import urlopen
90
+
91
+ version = os.environ["GITHUB_REF_NAME"].removeprefix("v")
92
+ expected = {
93
+ path.name: hashlib.sha256(path.read_bytes()).hexdigest()
94
+ for path in Path("dist").iterdir()
95
+ if path.is_file()
96
+ }
97
+ release_url = f"https://test.pypi.org/pypi/lwbgt/{version}/json"
98
+ published = {}
99
+ for attempt in range(12):
100
+ try:
101
+ with urlopen(release_url) as response:
102
+ release = json.load(response)
103
+ published = {
104
+ item["filename"]: item["digests"]["sha256"]
105
+ for item in release["urls"]
106
+ }
107
+ except OSError:
108
+ published = {}
109
+ if published == expected:
110
+ break
111
+ if attempt != 11:
112
+ time.sleep(10)
113
+ else:
114
+ raise SystemExit(
115
+ f"TestPyPI release differs: published={published}, expected={expected}"
116
+ )
117
+
118
+ downloaded = list(Path("testpypi").glob("*.whl"))
119
+ if len(downloaded) != 1:
120
+ raise SystemExit(f"expected one TestPyPI wheel, got {downloaded}")
121
+ expected_wheel = Path("dist", downloaded[0].name)
122
+ if not expected_wheel.is_file():
123
+ raise SystemExit(f"TestPyPI returned an unexpected artifact: {downloaded[0].name}")
124
+ actual_hash = hashlib.sha256(downloaded[0].read_bytes()).hexdigest()
125
+ expected_hash = hashlib.sha256(expected_wheel.read_bytes()).hexdigest()
126
+ if actual_hash != expected_hash:
127
+ raise SystemExit(
128
+ f"TestPyPI artifact hash mismatch: {actual_hash} != {expected_hash}"
129
+ )
130
+ print(f"TestPyPI release identity: {len(expected)} artifacts: ok")
131
+ print(f"TestPyPI wheel download: {downloaded[0].name}: {actual_hash}")
132
+ PY
133
+ - name: Test the wheel retrieved from TestPyPI
134
+ run: |
135
+ python -m venv testpypi-test
136
+ testpypi-test/bin/python -m pip install --no-index testpypi/*.whl
137
+ testpypi-test/bin/python -m unittest discover -s tests/python -v
138
+
139
+ publish:
140
+ name: Publish verified artifacts to PyPI
141
+ needs: [artifacts, testpypi-smoke]
142
+ runs-on: ubuntu-24.04
143
+ environment:
144
+ name: pypi
145
+ url: https://pypi.org/p/lwbgt
146
+ permissions:
147
+ contents: read
148
+ id-token: write
149
+ steps:
150
+ - uses: actions/download-artifact@v8
151
+ with:
152
+ pattern: dist-*
153
+ path: dist
154
+ merge-multiple: true
155
+ - name: Require the complete release matrix
156
+ run: |
157
+ test "$(find dist -maxdepth 1 -type f -name '*.whl' | wc -l)" -eq 5
158
+ test "$(find dist -maxdepth 1 -type f -name '*.tar.gz' | wc -l)" -eq 1
159
+ - name: Publish through PyPI Trusted Publishing
160
+ uses: pypa/gh-action-pypi-publish@release/v1
161
+
162
+ github-release:
163
+ name: Create GitHub release
164
+ needs: publish
165
+ runs-on: ubuntu-24.04
166
+ permissions:
167
+ contents: write
168
+ steps:
169
+ - uses: actions/download-artifact@v8
170
+ with:
171
+ pattern: dist-*
172
+ path: dist
173
+ merge-multiple: true
174
+ - name: Create release for the existing annotated tag
175
+ env:
176
+ GH_TOKEN: ${{ github.token }}
177
+ run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --generate-notes
@@ -0,0 +1,140 @@
1
+ name: wheels
2
+
3
+ on:
4
+ pull_request:
5
+ workflow_call:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build-wheels:
12
+ name: ${{ matrix.artifact }} wheel
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ include:
17
+ - runner: ubuntu-24.04
18
+ artifact: linux-x86_64
19
+ arch: x86_64
20
+ platform: linux
21
+ - runner: ubuntu-24.04-arm
22
+ artifact: linux-aarch64
23
+ arch: aarch64
24
+ platform: linux
25
+ - runner: macos-15-intel
26
+ artifact: macos-x86_64
27
+ arch: x86_64
28
+ platform: macos
29
+ - runner: macos-15
30
+ artifact: macos-arm64
31
+ arch: arm64
32
+ platform: macos
33
+ - runner: windows-2025
34
+ artifact: windows-amd64
35
+ arch: AMD64
36
+ platform: windows
37
+ runs-on: ${{ matrix.runner }}
38
+ env:
39
+ CIBW_BUILD: cp310-*
40
+ CIBW_SKIP: '*-musllinux_*'
41
+ CIBW_ARCHS: ${{ matrix.arch }}
42
+ CIBW_TEST_COMMAND: python -m unittest discover -s {project}/tests/python -v
43
+ CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >-
44
+ delvewheel repair --analyze-existing --exclude ucrtbase.dll
45
+ -w {dest_dir} -v {wheel}
46
+ steps:
47
+ - uses: actions/checkout@v6
48
+ - uses: actions/setup-python@v6
49
+ with:
50
+ python-version: '3.14'
51
+ - name: Provision standalone MinGW GCC
52
+ if: matrix.platform == 'windows'
53
+ id: mingw
54
+ uses: egor-tensin/setup-mingw@v3
55
+ with:
56
+ platform: x64
57
+ version: 12.2.0
58
+ - name: Select MinGW compiler
59
+ if: matrix.platform == 'windows'
60
+ shell: pwsh
61
+ run: |
62
+ "CC=${{ steps.mingw.outputs.gcc }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
63
+ "CMAKE_GENERATOR=Ninja" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
64
+ - name: Check version coherence
65
+ run: python tests/check_versions.py
66
+ - name: Build, repair, and test wheel
67
+ uses: pypa/cibuildwheel@v4.1.1
68
+ - name: Inspect wheel contents
69
+ run: python tests/check_distribution.py "wheelhouse/*.whl"
70
+ - name: Inspect Linux dependencies
71
+ if: matrix.platform == 'linux'
72
+ run: |
73
+ python -m pip install auditwheel
74
+ for wheel in wheelhouse/*.whl; do
75
+ auditwheel show "$wheel"
76
+ done
77
+ - name: Inspect macOS dependencies
78
+ if: matrix.platform == 'macos'
79
+ run: python -m pip install delocate && delocate-listdeps wheelhouse/*.whl
80
+ - name: Inspect Windows dependencies
81
+ if: matrix.platform == 'windows'
82
+ run: python -m pip install delvewheel && delvewheel show --analyze-existing wheelhouse\*.whl
83
+ - uses: actions/upload-artifact@v7
84
+ with:
85
+ name: dist-${{ matrix.artifact }}
86
+ path: wheelhouse/*.whl
87
+ if-no-files-found: error
88
+
89
+ build-sdist:
90
+ name: source distribution
91
+ runs-on: ubuntu-24.04
92
+ steps:
93
+ - uses: actions/checkout@v6
94
+ - uses: actions/setup-python@v6
95
+ with:
96
+ python-version: '3.14'
97
+ - name: Check version coherence
98
+ run: python tests/check_versions.py
99
+ - name: Build source distribution
100
+ run: |
101
+ python -m pip install build twine
102
+ python -m build --sdist
103
+ python tests/check_distribution.py "dist/*.tar.gz"
104
+ python -m twine check dist/*
105
+ - name: Rebuild wheel from source distribution
106
+ run: |
107
+ python -m pip wheel --no-deps --wheel-dir sdist-wheel dist/*.tar.gz
108
+ python tests/check_distribution.py "sdist-wheel/*.whl"
109
+ - name: Test source-built wheel in a clean environment
110
+ run: |
111
+ python -m venv sdist-test
112
+ sdist-test/bin/python -m pip install --no-index sdist-wheel/*.whl
113
+ sdist-test/bin/python -m unittest discover -s tests/python -v
114
+ - uses: actions/upload-artifact@v7
115
+ with:
116
+ name: dist-sdist
117
+ path: dist/*.tar.gz
118
+ if-no-files-found: error
119
+
120
+ python-compatibility:
121
+ name: Linux wheel on Python ${{ matrix.python }}
122
+ needs: build-wheels
123
+ runs-on: ubuntu-24.04
124
+ strategy:
125
+ fail-fast: false
126
+ matrix:
127
+ python: ['3.10', '3.x']
128
+ steps:
129
+ - uses: actions/checkout@v6
130
+ - uses: actions/setup-python@v6
131
+ with:
132
+ python-version: ${{ matrix.python }}
133
+ - uses: actions/download-artifact@v8
134
+ with:
135
+ name: dist-linux-x86_64
136
+ path: dist
137
+ - name: Install the same ABI-independent wheel
138
+ run: |
139
+ python -m pip install --no-index dist/*.whl
140
+ python -m unittest discover -s tests/python -v
lwbgt-0.3.0/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /build/
2
+ /build-gcc16/
3
+ /build-profile/
4
+ __pycache__/
5
+ *.py[cod]
6
+ /dist/
7
+ /wheelhouse/
8
+ /sdist-wheel/
9
+ /sdist-test/
lwbgt-0.3.0/ABI.md ADDED
@@ -0,0 +1,136 @@
1
+ # ABI contract
2
+
3
+ This document defines the supported binary interface for lwbgt. Public symbols
4
+ and layouts described here are compatibility commitments; other link-visible
5
+ symbols in the static archive are implementation details.
6
+
7
+ ## Versioning
8
+
9
+ The project release version and FFI ABI version are independent:
10
+
11
+ - `LWBGT_VERSION_MAJOR`, `LWBGT_VERSION_MINOR`, and `LWBGT_VERSION_PATCH`
12
+ identify the project release.
13
+ - `LWBGT_FFI_ABI_VERSION` identifies the versioned structure and batch API.
14
+ - The `calc_wbgt` and `esat` scalar symbols retain the original compatibility
15
+ ABI.
16
+ - The `lwbgt_input_v1`, `lwbgt_output_v1`, and `lwbgt_calc_batch_v1` names are
17
+ permanent. Incompatible layouts or behavior require new `v2` names while the
18
+ v1 entry point remains available.
19
+
20
+ New symbols and backward-compatible documentation clarifications may be added
21
+ without changing the v1 ABI. Fields must not be reordered, resized, removed, or
22
+ repurposed.
23
+
24
+ ## Data model and layout
25
+
26
+ The v1 ABI requires 8-bit bytes, 32-bit `int32_t` and `float`, 64-bit `double`,
27
+ and the field offsets below. Supported release builds enforce the total sizes
28
+ and boundary offsets with compile-time assertions.
29
+
30
+ ### `lwbgt_input_v1`
31
+
32
+ Total size: 104 bytes. Integer fields occupy the first 32 bytes; every floating
33
+ point input is an ABI-boundary `double`.
34
+
35
+ | Offset | Field | Type | Units and meaning |
36
+ |---:|---|---|---|
37
+ | 0 | `year` | `int32_t` | Four-digit Gregorian year |
38
+ | 4 | `month` | `int32_t` | Month 1–12; 0 means `day` is day-of-year |
39
+ | 8 | `day` | `int32_t` | Day of month, or day-of-year when `month == 0` |
40
+ | 12 | `hour` | `int32_t` | Local standard-time hour, 0–23 |
41
+ | 16 | `minute` | `int32_t` | Minutes past the hour |
42
+ | 20 | `gmt_offset_hours` | `int32_t` | Local standard time minus GMT, in hours |
43
+ | 24 | `averaging_minutes` | `int32_t` | Input averaging interval in minutes |
44
+ | 28 | `urban` | `int32_t` | 0 selects rural; 1 selects urban wind scaling |
45
+ | 32 | `latitude_deg_north` | `double` | Degrees north; south is negative |
46
+ | 40 | `longitude_deg_east` | `double` | Degrees east; west is negative |
47
+ | 48 | `solar_w_m2` | `double` | Solar irradiance, W/m² |
48
+ | 56 | `pressure_hpa` | `double` | Barometric pressure, hPa (equivalent to mb) |
49
+ | 64 | `air_temperature_c` | `double` | Dry-bulb air temperature, °C |
50
+ | 72 | `relative_humidity_percent` | `double` | Relative humidity, percent |
51
+ | 80 | `wind_speed_m_s` | `double` | Wind speed, m/s |
52
+ | 88 | `wind_height_m` | `double` | Wind measurement height, m |
53
+ | 96 | `vertical_temperature_difference_c` | `double` | Upper-minus-lower temperature difference, °C |
54
+
55
+ ### `lwbgt_output_v1`
56
+
57
+ Total size: 24 bytes.
58
+
59
+ | Offset | Field | Type | Meaning |
60
+ |---:|---|---|---|
61
+ | 0 | `status` | `int32_t` | Per-record scalar solver status |
62
+ | 4 | `estimated_wind_speed_m_s` | `float` | Effective wind speed at 2 m, m/s |
63
+ | 8 | `globe_temperature_c` | `float` | Globe temperature, °C |
64
+ | 12 | `natural_wet_bulb_c` | `float` | Natural wet-bulb temperature, °C |
65
+ | 16 | `psychrometric_wet_bulb_c` | `float` | Psychrometric wet-bulb temperature, °C |
66
+ | 20 | `wbgt_c` | `float` | Outdoor wet-bulb globe temperature, °C |
67
+
68
+ ## Batch call contract
69
+
70
+ ```c
71
+ int lwbgt_calc_batch_v1(
72
+ const lwbgt_input_v1 *inputs,
73
+ lwbgt_output_v1 *outputs,
74
+ size_t count
75
+ );
76
+ ```
77
+
78
+ - `count == 0` returns `LWBGT_BATCH_OK`; either pointer may be null.
79
+ - For nonzero `count`, a null pointer returns
80
+ `LWBGT_BATCH_INVALID_ARGUMENT` without modifying outputs.
81
+ - The caller owns both arrays and must provide at least `count` elements. The
82
+ input and output storage must not overlap.
83
+ - Records execute serially, in ascending array order, by calling the scalar
84
+ implementation once per record.
85
+ - The function return value reports call validity. Each output `status` reports
86
+ that record's scalar solver result: 0 for success and -1 for failure to
87
+ converge. A failed record uses the upstream `-9999` result convention.
88
+ - Inputs are forwarded without domain validation, clamping, unit conversion, or
89
+ missing-value policy beyond behavior already present in the scalar model.
90
+ - When wind is already measured at 2 m, the batch API returns the supplied wind
91
+ as `estimated_wind_speed_m_s`. This makes the batch result deterministic; the
92
+ legacy scalar function instead leaves that output pointer untouched when no
93
+ estimation is performed.
94
+
95
+ ## Scalar ABI
96
+
97
+ The declarations in `lwbgt.h` are authoritative. Scalar floating-point inputs
98
+ to `calc_wbgt` use `double` at the ABI boundary because the original K&R
99
+ `float` parameters undergo default argument promotion. Output pointers remain
100
+ `float *`.
101
+
102
+ `esat` accepts temperature in kelvin. `phase == 0` computes saturation over
103
+ liquid water; `phase == 1` computes saturation over ice. Other phase values are
104
+ outside the supported contract.
105
+
106
+ ## Concurrency and ownership
107
+
108
+ The implementation has no mutable shared calculation state. Independent scalar
109
+ or batch calls are thread-safe when callers use separate output buffers. A
110
+ single batch call is serial and does not create threads. The API retains no
111
+ input or output pointers after return and performs no allocation.
112
+
113
+ ## Symbols and platforms
114
+
115
+ The shared library exports only:
116
+
117
+ ```text
118
+ calc_wbgt
119
+ esat
120
+ lwbgt_calc_batch_v1
121
+ ```
122
+
123
+ The release CI validates Linux with GCC, macOS with AppleClang, and Windows with
124
+ MinGW GCC. MSVC is unsupported because it cannot compile the preserved GNU89
125
+ numerical source. The public header is valid C and C++ and is tested through
126
+ installed C and C++ consumers.
127
+
128
+ ## Official Python binding
129
+
130
+ The `lwbgt` Python distribution loads its bundled unversioned runtime through
131
+ `importlib.resources` and `ctypes`. Its public `Input` and `Result` records map
132
+ field-for-field to the v1 structures above. `calculate` and `calculate_batch`
133
+ both use `lwbgt_calc_batch_v1`; the latter submits the entire iterable in one
134
+ native call. `esat` directly exposes the scalar symbol. The binding performs no
135
+ unit conversion, domain validation, clamping, missing-data handling, or solver
136
+ failure substitution.
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ ## v0.3.0 — 2026-08-19
4
+
5
+ - Added the official dependency-free Python API: immutable `Input` and `Result`
6
+ records plus `calculate`, `calculate_batch`, and `esat`.
7
+ - Added an unversioned, wheel-only shared target built from the existing native
8
+ objects with the same restricted three-symbol export surface.
9
+ - Added platform-specific, Python-ABI-independent wheel and rebuildable sdist
10
+ packaging through scikit-build-core.
11
+ - Added installed-wheel ABI layout, resource loading, scalar, batch, failure,
12
+ `esat`, deterministic fixture, archive licensing, and version-coherence tests.
13
+ - Added cibuildwheel coverage for manylinux x86_64/aarch64, macOS x86_64/arm64,
14
+ and Windows amd64, plus OIDC Trusted Publishing release automation.
15
+
16
+ ## v0.2.1 — 2026-08-18
17
+
18
+ - Licensed project-authored files under Apache-2.0 while preserving the
19
+ Liljegren-derived numerical files under their UChicago Argonne terms.
20
+ - Added the authoritative v1 ABI contract covering layouts, units, status and
21
+ ownership semantics, concurrency, symbols, and compatibility policy.
22
+ - Added an installed C++ consumer that validates public-header compatibility,
23
+ structure sizes and offsets, and shared-library linkage.
24
+
25
+ ## v0.2.0 — 2026-08-18
26
+
27
+ - Added the fixed-layout `lwbgt_input_v1` and `lwbgt_output_v1` structures and
28
+ serial `lwbgt_calc_batch_v1` FFI entry point without changing the scalar ABI.
29
+ - Added versioned shared-library builds with a three-symbol dynamic export
30
+ surface while retaining the existing static archive.
31
+ - Added dependency-light Python, R, and Julia examples and made them release
32
+ gates on Linux/GCC, macOS/Clang, and Windows/MinGW CI.
33
+ - Added relocatable CMake and `pkg-config` installation metadata.
34
+ - Verified the final static and shared artifacts on Linux/GCC,
35
+ macOS/AppleClang, and Windows/MinGW, including installed consumers and all
36
+ three language examples.
37
+ - Revalidated the position-independent static build at 1.249× overall median
38
+ throughput with exact compatibility and all release gates passing.
39
+
40
+ ## v0.1.0 — 2026-08-18
41
+
42
+ The release gate passes with 1.316× median single-thread throughput on GCC
43
+ 13.3.0 and 1.289× on GCC 16.2.0. All 454 compatibility cases remain
44
+ bit-identical and every benchmark cohort exceeds 1.25×.
45
+
46
+ - Pinned Liljegren WBGT v1.1 commit
47
+ `cd672a886880b67f3f27bdbf75038d8f7ff0bac2` and source blob
48
+ `7bc6e6ddd76a538d6454b27e9b252667846e6c9b`.
49
+ - Added static `liblwbgt.a` and the permanent `calc_wbgt`/`esat` compatibility
50
+ declarations in `lwbgt.h`.
51
+ - Skipped radiative work that was multiplied by zero in the psychrometric
52
+ wet-bulb solve.
53
+ - Reused rounded air viscosity in cylinder and sphere convective coefficients.
54
+ - Hoisted invariant atmospheric, surface, and solar radiation terms from the
55
+ iterative globe and natural wet-bulb solves after profiling identified both
56
+ solves as the remaining hot paths.
57
+ - Proved exact output equality with GCC 13.3.0 and GCC 16.2.0.
58
+ - Proved that all three existing HeatStressBench C adapters compile unchanged
59
+ and produce byte-identical generated, decomposition, NASA POWER sample, and
60
+ ERA5 sample results when only the link input changes.
61
+ - Audited the static archive export surface and fixed the permanent supported
62
+ API at `calc_wbgt` and `esat`; inherited helper exports remain implementation
63
+ details.
@@ -0,0 +1,42 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use lwbgt, please cite this software and the underlying Liljegren method."
3
+ title: "lwbgt"
4
+ type: software
5
+ abstract: "A stable low-level C/FFI computational kernel for Liljegren outdoor WBGT, derived from the Liljegren WBGT v1.1 C implementation."
6
+ authors:
7
+ - family-names: "Zheng"
8
+ given-names: "Yifei"
9
+ orcid: "https://orcid.org/0009-0001-4858-2025"
10
+ version: 0.3.0
11
+ date-released: 2026-08-19
12
+ repository-code: "https://github.com/zyf0717/lwbgt"
13
+ url: "https://github.com/zyf0717/lwbgt"
14
+ license-url: "https://github.com/zyf0717/lwbgt/blob/main/LICENSING.md"
15
+ keywords:
16
+ - WBGT
17
+ - Liljegren
18
+ - heat stress
19
+ - C
20
+ - FFI
21
+ - Python
22
+ references:
23
+ - type: article
24
+ authors:
25
+ - family-names: "Liljegren"
26
+ given-names: "James C."
27
+ - family-names: "Carhart"
28
+ given-names: "Richard A."
29
+ - family-names: "Lawday"
30
+ given-names: "Philip"
31
+ - family-names: "Tschopp"
32
+ given-names: "Stephen"
33
+ - family-names: "Sharp"
34
+ given-names: "Robert"
35
+ title: "Modeling the Wet Bulb Globe Temperature Using Standard Meteorological Measurements"
36
+ journal: "Journal of Occupational and Environmental Hygiene"
37
+ year: 2008
38
+ volume: 5
39
+ issue: 10
40
+ start: 645
41
+ end: 655
42
+ doi: "10.1080/15459620802310770"