solweig-gpu 2.0.0__tar.gz → 2.0.1__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.
- {solweig_gpu-2.0.0/solweig_gpu.egg-info → solweig_gpu-2.0.1}/PKG-INFO +3 -3
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/README.md +2 -2
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/setup.py +1 -1
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/__init__.py +1 -1
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/landcoverclasses_2016a.txt +1 -1
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/shadow.py +57 -36
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/solweig_gpu.py +2 -2
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/utci_process.py +5 -7
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1/solweig_gpu.egg-info}/PKG-INFO +3 -3
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/SOURCES.txt +1 -0
- solweig_gpu-2.0.1/tests/test_shadow.py +74 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/LICENSE +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/Logo.png +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/MANIFEST.in +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/UTCI_New_Delhi.jpeg +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/pyproject.toml +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/requirements.txt +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/setup.cfg +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_diagram.png +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/Tgmaps_v1.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/calculate_utci.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/calculate_wbgt.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/cli.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/create_inputs.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/preprocessor.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/solweig.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/sun_position.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/walls_aspect.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/wind_ext_coeff.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/dependency_links.txt +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/entry_points.txt +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/requires.txt +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/top_level.txt +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/__init__.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/conftest.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_calculate_utci.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_cli.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_create_inputs_austin.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_era5_processing.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_gpu_smoke.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_integration.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_integration_end_to_end.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_preprocessor.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_sun_position.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_tiling_and_metfiles.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_walls_aspect.py +0 -0
- {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_wrf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solweig-gpu
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: GPU-accelerated SOLWEIG model for urban thermal comfort simulation
|
|
5
5
|
Home-page: https://github.com/nvnsudharsan/SOLWEIG-GPU
|
|
6
6
|
Author: Harsh Kamath, Naveen Sudharsan
|
|
@@ -56,7 +56,7 @@ Dynamic: summary
|
|
|
56
56
|
<a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPLv3-%230ab5b3.svg" alt="License: GPL v3"></a>
|
|
57
57
|
<a href="https://pepy.tech/projects/solweig-gpu"><img src="https://static.pepy.tech/personalized-badge/solweig-gpu?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=GREEN&left_text=downloads" alt="PyPI Downloads"></a>
|
|
58
58
|
<a href="https://joss.theoj.org/papers/27faa2bf5f6058d981df8b565f8e9a34"><img src="https://joss.theoj.org/papers/27faa2bf5f6058d981df8b565f8e9a34/status.svg"></a>
|
|
59
|
-
<a href="https://doi.org/10.5194/gmd-19-7389-2026"><img src="https://
|
|
59
|
+
<a href="https://doi.org/10.5194/gmd-19-7389-2026"><img src="https://img.shields.io/badge/DOI-10.5194%2Fgmd--19--7389--2026-blue" alt="GMD"></a>
|
|
60
60
|
<a href="https://github.com/nvnsudharsan/solweig-gpu/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/Tests-Passing-%23ffb703.svg" alt="Tests"></a>
|
|
61
61
|
</p>
|
|
62
62
|
|
|
@@ -77,7 +77,7 @@ Dynamic: summary
|
|
|
77
77
|
|
|
78
78
|
1. Kamath, H. G., Sudharsan, N., Singh, M., Wallenberg, N., Lindberg, F., & Niyogi, D. (2026). SOLWEIG-GPU: GPU-Accelerated Thermal Comfort Modeling Framework for Urban Digital Twins. Journal of Open Source Software, 11(118), 9535. https://doi.org/10.21105/joss.09535
|
|
79
79
|
|
|
80
|
-
2. Zonato, A., Kamath, H.G., Sudharsan, N., Monaco, L., Kittner, J., Wolf, L., Demuzere, M
|
|
80
|
+
2. Zonato, A., Kamath, H. G., Sudharsan, N., Monaco, L., Kittner, J., Wolf, L., Demuzere, M., Middel, A., Bechtel, B., and Milelli, M.: GLIDE-SOL: a GPU-accelerated global lightweight infrastructure for diagnostic environmental modeling with SOLWEIG, Geosci. Model Dev., 19, 7389–7413, https://doi.org/10.5194/gmd-19-7389-2026, 2026.
|
|
81
81
|
|
|
82
82
|
**SOLWEIG** was originally developed by Dr. Fredrik Lindberg's group. Journal reference: Lindberg, F., Holmer, B. & Thorsson, S. SOLWEIG 1.0 – Modelling spatial variations of 3D radiant fluxes and mean radiant temperature in complex urban settings. *Int J Biometeorol* 52, 697–713 (2008). https://doi.org/10.1007/s00484-008-0162-7
|
|
83
83
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPLv3-%230ab5b3.svg" alt="License: GPL v3"></a>
|
|
14
14
|
<a href="https://pepy.tech/projects/solweig-gpu"><img src="https://static.pepy.tech/personalized-badge/solweig-gpu?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=GREEN&left_text=downloads" alt="PyPI Downloads"></a>
|
|
15
15
|
<a href="https://joss.theoj.org/papers/27faa2bf5f6058d981df8b565f8e9a34"><img src="https://joss.theoj.org/papers/27faa2bf5f6058d981df8b565f8e9a34/status.svg"></a>
|
|
16
|
-
<a href="https://doi.org/10.5194/gmd-19-7389-2026"><img src="https://
|
|
16
|
+
<a href="https://doi.org/10.5194/gmd-19-7389-2026"><img src="https://img.shields.io/badge/DOI-10.5194%2Fgmd--19--7389--2026-blue" alt="GMD"></a>
|
|
17
17
|
<a href="https://github.com/nvnsudharsan/solweig-gpu/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/Tests-Passing-%23ffb703.svg" alt="Tests"></a>
|
|
18
18
|
</p>
|
|
19
19
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
1. Kamath, H. G., Sudharsan, N., Singh, M., Wallenberg, N., Lindberg, F., & Niyogi, D. (2026). SOLWEIG-GPU: GPU-Accelerated Thermal Comfort Modeling Framework for Urban Digital Twins. Journal of Open Source Software, 11(118), 9535. https://doi.org/10.21105/joss.09535
|
|
36
36
|
|
|
37
|
-
2. Zonato, A., Kamath, H.G., Sudharsan, N., Monaco, L., Kittner, J., Wolf, L., Demuzere, M
|
|
37
|
+
2. Zonato, A., Kamath, H. G., Sudharsan, N., Monaco, L., Kittner, J., Wolf, L., Demuzere, M., Middel, A., Bechtel, B., and Milelli, M.: GLIDE-SOL: a GPU-accelerated global lightweight infrastructure for diagnostic environmental modeling with SOLWEIG, Geosci. Model Dev., 19, 7389–7413, https://doi.org/10.5194/gmd-19-7389-2026, 2026.
|
|
38
38
|
|
|
39
39
|
**SOLWEIG** was originally developed by Dr. Fredrik Lindberg's group. Journal reference: Lindberg, F., Holmer, B. & Thorsson, S. SOLWEIG 1.0 – Modelling spatial variations of 3D radiant fluxes and mean radiant temperature in complex urban settings. *Int J Biometeorol* 52, 697–713 (2008). https://doi.org/10.1007/s00484-008-0162-7
|
|
40
40
|
|
|
@@ -18,7 +18,7 @@ long_description = (this_directory / "README.md").read_text(encoding="utf-8")
|
|
|
18
18
|
|
|
19
19
|
setup(
|
|
20
20
|
name="solweig-gpu",
|
|
21
|
-
version="2.0.
|
|
21
|
+
version="2.0.1",
|
|
22
22
|
description="GPU-accelerated SOLWEIG model for urban thermal comfort simulation",
|
|
23
23
|
long_description=long_description,
|
|
24
24
|
long_description_content_type='text/markdown',
|
|
@@ -32,6 +32,25 @@ gdal.UseExceptions()
|
|
|
32
32
|
|
|
33
33
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
34
34
|
|
|
35
|
+
# Increment when cached shadow/SVF outputs become incompatible with the
|
|
36
|
+
# current shadow algorithm. Version 2 adds UMEP's pergola-aware vegetation
|
|
37
|
+
# ray tracking.
|
|
38
|
+
SVF_CACHE_VERSION = 2
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_current_svf_cache(npz_path):
|
|
42
|
+
"""Return whether a shadow-matrix cache uses the current algorithm."""
|
|
43
|
+
if not os.path.isfile(npz_path):
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with np.load(npz_path) as npz:
|
|
48
|
+
if "cache_version" not in npz.files:
|
|
49
|
+
return False
|
|
50
|
+
return int(np.asarray(npz["cache_version"]).item()) == SVF_CACHE_VERSION
|
|
51
|
+
except (OSError, ValueError, TypeError):
|
|
52
|
+
return False
|
|
53
|
+
|
|
35
54
|
def load_raster_to_tensor(dem_path, device=device):
|
|
36
55
|
dataset = gdal.Open(dem_path)
|
|
37
56
|
if dataset is None:
|
|
@@ -158,6 +177,7 @@ def save_svf_zip_npz_outputs(output_dir, gdal_dsm, svf, svfE, svfS, svfW, svfN,
|
|
|
158
177
|
|
|
159
178
|
np.savez_compressed(
|
|
160
179
|
npz_path,
|
|
180
|
+
cache_version=np.asarray(SVF_CACHE_VERSION, dtype=np.int32),
|
|
161
181
|
shadowmat=tensor_to_numpy(shmat).astype(np.float32),
|
|
162
182
|
vegshadowmat=tensor_to_numpy(vegshmat).astype(np.float32),
|
|
163
183
|
vbshmat=tensor_to_numpy(vbshvegshmat).astype(np.float32),
|
|
@@ -191,14 +211,14 @@ def shadow(amaxvalue, a, vegdem, vegdem2, bush, azimuth, altitude, scale):
|
|
|
191
211
|
- Implements anisotropic shadow casting
|
|
192
212
|
- Accounts for vegetation transmittance
|
|
193
213
|
"""
|
|
194
|
-
device =
|
|
214
|
+
device = a.device
|
|
195
215
|
degrees = torch.pi / 180.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
azimuth = ensure_tensor(azimuth)
|
|
199
|
-
altitude = ensure_tensor(altitude)
|
|
200
|
-
azimuth = azimuth * degrees
|
|
201
|
-
altitude = altitude * degrees
|
|
216
|
+
# Use float64 for ray angles. In float32, tan(90 degrees) is a large
|
|
217
|
+
# negative value and makes the zenith patch cast horizontal shadows.
|
|
218
|
+
azimuth = ensure_tensor(azimuth, device=device).to(device=device, dtype=torch.float64)
|
|
219
|
+
altitude = ensure_tensor(altitude, device=device).to(device=device, dtype=torch.float64)
|
|
220
|
+
azimuth = azimuth * degrees
|
|
221
|
+
altitude = altitude * degrees
|
|
202
222
|
|
|
203
223
|
dx = 0.
|
|
204
224
|
dy = 0.
|
|
@@ -206,8 +226,6 @@ def shadow(amaxvalue, a, vegdem, vegdem2, bush, azimuth, altitude, scale):
|
|
|
206
226
|
sizex = a.shape[0]
|
|
207
227
|
sizey = a.shape[1]
|
|
208
228
|
|
|
209
|
-
device = a.device
|
|
210
|
-
|
|
211
229
|
dx = torch.tensor(dx, device=device)
|
|
212
230
|
dy = torch.tensor(dy, device=device)
|
|
213
231
|
dz = torch.tensor(dz, device=device)
|
|
@@ -217,10 +235,10 @@ def shadow(amaxvalue, a, vegdem, vegdem2, bush, azimuth, altitude, scale):
|
|
|
217
235
|
tempvegdem2 = torch.zeros((sizex, sizey), device=device)
|
|
218
236
|
sh = torch.zeros((sizex, sizey), device=device)
|
|
219
237
|
vbshvegsh = torch.zeros((sizex, sizey), device=device)
|
|
220
|
-
|
|
238
|
+
templastfabovea = torch.zeros((sizex, sizey), device=device)
|
|
239
|
+
templastgabovea = torch.zeros((sizex, sizey), device=device)
|
|
221
240
|
|
|
222
241
|
f = a.clone()
|
|
223
|
-
g = torch.zeros((sizex, sizey), device=device)
|
|
224
242
|
bushplant = (bush > 1.).float()
|
|
225
243
|
vegsh = torch.zeros((sizex, sizey), device=device) + bushplant
|
|
226
244
|
|
|
@@ -237,9 +255,14 @@ def shadow(amaxvalue, a, vegdem, vegdem2, bush, azimuth, altitude, scale):
|
|
|
237
255
|
dscos = torch.abs((1. / cosazimuth))
|
|
238
256
|
tanaltitudebyscale = torch.tan(altitude) / scale
|
|
239
257
|
|
|
240
|
-
|
|
258
|
+
# The previous ray height is required to detect vegetation layers that
|
|
259
|
+
# fall between two ray steps (for example, thin pergola canopies).
|
|
260
|
+
index = 0
|
|
261
|
+
dzprev = torch.tensor(0.0, device=device)
|
|
241
262
|
fabovea = None
|
|
242
263
|
gabovea = None
|
|
264
|
+
lastfabovea = None
|
|
265
|
+
lastgabovea = None
|
|
243
266
|
vegsh2 = None
|
|
244
267
|
|
|
245
268
|
while (amaxvalue >= dz and torch.abs(dx) < sizex and torch.abs(dy) < sizey):
|
|
@@ -257,6 +280,8 @@ def shadow(amaxvalue, a, vegdem, vegdem2, bush, azimuth, altitude, scale):
|
|
|
257
280
|
tempvegdem.zero_()
|
|
258
281
|
tempvegdem2.zero_()
|
|
259
282
|
temp.zero_()
|
|
283
|
+
templastfabovea.zero_()
|
|
284
|
+
templastgabovea.zero_()
|
|
260
285
|
absdx = torch.abs(dx)
|
|
261
286
|
absdy = torch.abs(dy)
|
|
262
287
|
xc1 = int((dx + absdx) / 2.)
|
|
@@ -278,45 +303,41 @@ def shadow(amaxvalue, a, vegdem, vegdem2, bush, azimuth, altitude, scale):
|
|
|
278
303
|
|
|
279
304
|
fabovea = tempvegdem > a
|
|
280
305
|
gabovea = tempvegdem2 > a
|
|
281
|
-
|
|
306
|
+
|
|
307
|
+
templastfabovea[xp1:xp2, yp1:yp2] = vegdem[xc1:xc2, yc1:yc2] - dzprev
|
|
308
|
+
templastgabovea[xp1:xp2, yp1:yp2] = vegdem2[xc1:xc2, yc1:yc2] - dzprev
|
|
309
|
+
lastfabovea = templastfabovea > a
|
|
310
|
+
lastgabovea = templastgabovea > a
|
|
311
|
+
dzprev = dz
|
|
312
|
+
|
|
313
|
+
# Match UMEP's pergola-aware shadowingfunction_20. A ray intersects
|
|
314
|
+
# vegetation when any, but not all, of the current/previous canopy
|
|
315
|
+
# and trunk-base tests are above the target surface.
|
|
316
|
+
vegetation_states = (
|
|
317
|
+
fabovea.float()
|
|
318
|
+
+ gabovea.float()
|
|
319
|
+
+ lastfabovea.float()
|
|
320
|
+
+ lastgabovea.float()
|
|
321
|
+
)
|
|
322
|
+
vegsh2 = ((vegetation_states > 0) & (vegetation_states < 4)).float()
|
|
282
323
|
|
|
283
324
|
vegsh = torch.max(vegsh, vegsh2)
|
|
284
325
|
vegsh[(vegsh * sh > 0.)] = 0.
|
|
285
326
|
|
|
286
327
|
vbshvegsh = vegsh + vbshvegsh
|
|
287
328
|
|
|
288
|
-
if index == 1.:
|
|
289
|
-
firstvegdem = tempvegdem - temp
|
|
290
|
-
firstvegdem[firstvegdem <= 0.] = 1000.
|
|
291
|
-
vegsh[firstvegdem < dz] = 1.
|
|
292
|
-
vegsh = vegsh * (vegdem2 > a).float()
|
|
293
|
-
vbshvegsh.zero_()
|
|
294
|
-
|
|
295
|
-
if bush.max() > 0. and torch.max(fabovea * bush) > 0.:
|
|
296
|
-
tempbush.zero_()
|
|
297
|
-
tempbush[int(xp1):int(xp2), int(yp1):int(yp2)] = bush[int(xc1):int(xc2), int(yc1):int(yc2)] - dz
|
|
298
|
-
g = torch.max(g, tempbush)
|
|
299
|
-
g *= bushplant
|
|
300
|
-
|
|
301
329
|
index += 1.
|
|
302
330
|
|
|
303
331
|
sh = 1. - sh
|
|
304
332
|
vbshvegsh[vbshvegsh > 0.] = 1.
|
|
305
333
|
vbshvegsh = vbshvegsh - vegsh
|
|
306
334
|
|
|
307
|
-
if bush.max() > 0.:
|
|
308
|
-
g = g - bush
|
|
309
|
-
g[g > 0.] = 1.
|
|
310
|
-
g[g < 0.] = 0.
|
|
311
|
-
vegsh = vegsh - bushplant + g
|
|
312
|
-
vegsh[vegsh < 0.] = 0.
|
|
313
|
-
|
|
314
|
-
vegsh[vegsh > 0.] = 1.
|
|
315
335
|
vegsh = 1. - vegsh
|
|
316
336
|
vbshvegsh = 1. - vbshvegsh
|
|
317
337
|
|
|
318
|
-
|
|
319
|
-
del
|
|
338
|
+
del tempvegdem, tempvegdem2, temp
|
|
339
|
+
del templastfabovea, templastgabovea
|
|
340
|
+
del fabovea, gabovea, lastfabovea, lastgabovea, vegsh2
|
|
320
341
|
torch.cuda.empty_cache()
|
|
321
342
|
# Changed here
|
|
322
343
|
return sh, vegsh, vbshvegsh
|
|
@@ -293,7 +293,7 @@ def calculate_svf(base_path: str, patch_option: int = 2, overwrite: bool = False
|
|
|
293
293
|
import torch
|
|
294
294
|
from tqdm import tqdm
|
|
295
295
|
|
|
296
|
-
from .shadow import svf_calculator
|
|
296
|
+
from .shadow import is_current_svf_cache, svf_calculator
|
|
297
297
|
|
|
298
298
|
building_dsm_dir = os.path.join(base_path, "Building_DSM")
|
|
299
299
|
dem_dir = os.path.join(base_path, "DEM")
|
|
@@ -369,7 +369,7 @@ def calculate_svf(base_path: str, patch_option: int = 2, overwrite: bool = False
|
|
|
369
369
|
not overwrite
|
|
370
370
|
and os.path.exists(expected_tif)
|
|
371
371
|
and os.path.exists(expected_zip)
|
|
372
|
-
and
|
|
372
|
+
and is_current_svf_cache(expected_npz)
|
|
373
373
|
):
|
|
374
374
|
continue
|
|
375
375
|
|
|
@@ -30,7 +30,7 @@ import pytz
|
|
|
30
30
|
import datetime
|
|
31
31
|
from .Tgmaps_v1 import Tgmaps_v1
|
|
32
32
|
from .sun_position import Solweig_2015a_metdata_noload
|
|
33
|
-
from .shadow import
|
|
33
|
+
from .shadow import create_patches, is_current_svf_cache, svf_calculator
|
|
34
34
|
from .solweig import Solweig_2022a_calc, clearnessindex_2013b
|
|
35
35
|
from .calculate_utci import utci_calculator
|
|
36
36
|
from .calculate_wbgt import isobaric_wet_bulb_temperature_from_rh, black_globe_temperature
|
|
@@ -245,7 +245,7 @@ def _svf_cache_exists(building_dsm_path: str, number: str) -> bool:
|
|
|
245
245
|
return (
|
|
246
246
|
os.path.isfile(svftotal_path)
|
|
247
247
|
and os.path.isfile(zip_path)
|
|
248
|
-
and
|
|
248
|
+
and is_current_svf_cache(npz_path)
|
|
249
249
|
)
|
|
250
250
|
|
|
251
251
|
def _load_raster_to_tensor_from_path(raster_path: str):
|
|
@@ -411,7 +411,7 @@ def compute_utci(building_dsm_path, tree_path, dem_path, walls_path, aspect_path
|
|
|
411
411
|
f"Building DSM shape {tuple(a.shape)}"
|
|
412
412
|
)
|
|
413
413
|
|
|
414
|
-
#
|
|
414
|
+
# Landcover warnings
|
|
415
415
|
landcover = 0
|
|
416
416
|
|
|
417
417
|
if landcover_path is not None:
|
|
@@ -439,7 +439,7 @@ def compute_utci(building_dsm_path, tree_path, dem_path, walls_path, aspect_path
|
|
|
439
439
|
lc_class = np.empty((len(lines), 6), dtype=float)
|
|
440
440
|
for i, ln in enumerate(lines):
|
|
441
441
|
lc_class[i, :] = [float(x) for x in ln.split()[1:]] # cols 1-6
|
|
442
|
-
#
|
|
442
|
+
# Landcover warnings
|
|
443
443
|
|
|
444
444
|
base_date = datetime.datetime.strptime(selected_date_str, "%Y-%m-%d")
|
|
445
445
|
rows, cols = a.shape
|
|
@@ -518,7 +518,7 @@ def compute_utci(building_dsm_path, tree_path, dem_path, walls_path, aspect_path
|
|
|
518
518
|
Tgmap1N = torch.zeros((rows, cols), device=device)
|
|
519
519
|
TgOut1 = torch.zeros((rows, cols), device=device)
|
|
520
520
|
|
|
521
|
-
#
|
|
521
|
+
# Landcover classification based calculations
|
|
522
522
|
if landcover == 1:
|
|
523
523
|
(TgK_np, Tstart_np, alb_np, emis_np, TgK_wall_np, Tstart_wall_np, TmaxLST_np,
|
|
524
524
|
TmaxLST_wall_np) = Tgmaps_v1(lcgrid_np, lc_class)
|
|
@@ -540,7 +540,6 @@ def compute_utci(building_dsm_path, tree_path, dem_path, walls_path, aspect_path
|
|
|
540
540
|
Tstart_wall = -3.41
|
|
541
541
|
TmaxLST = 15.
|
|
542
542
|
TmaxLST_wall = 15.
|
|
543
|
-
# Added
|
|
544
543
|
|
|
545
544
|
transVeg = 3. / 100.
|
|
546
545
|
# landcover = 1 # Modified
|
|
@@ -929,4 +928,3 @@ def compute_utci(building_dsm_path, tree_path, dem_path, walls_path, aspect_path
|
|
|
929
928
|
time_taken = end_time - start_time
|
|
930
929
|
print(f"Time taken to execute tile {number}: {time_taken:.2f} seconds")
|
|
931
930
|
|
|
932
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solweig-gpu
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: GPU-accelerated SOLWEIG model for urban thermal comfort simulation
|
|
5
5
|
Home-page: https://github.com/nvnsudharsan/SOLWEIG-GPU
|
|
6
6
|
Author: Harsh Kamath, Naveen Sudharsan
|
|
@@ -56,7 +56,7 @@ Dynamic: summary
|
|
|
56
56
|
<a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPLv3-%230ab5b3.svg" alt="License: GPL v3"></a>
|
|
57
57
|
<a href="https://pepy.tech/projects/solweig-gpu"><img src="https://static.pepy.tech/personalized-badge/solweig-gpu?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=GREEN&left_text=downloads" alt="PyPI Downloads"></a>
|
|
58
58
|
<a href="https://joss.theoj.org/papers/27faa2bf5f6058d981df8b565f8e9a34"><img src="https://joss.theoj.org/papers/27faa2bf5f6058d981df8b565f8e9a34/status.svg"></a>
|
|
59
|
-
<a href="https://doi.org/10.5194/gmd-19-7389-2026"><img src="https://
|
|
59
|
+
<a href="https://doi.org/10.5194/gmd-19-7389-2026"><img src="https://img.shields.io/badge/DOI-10.5194%2Fgmd--19--7389--2026-blue" alt="GMD"></a>
|
|
60
60
|
<a href="https://github.com/nvnsudharsan/solweig-gpu/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/Tests-Passing-%23ffb703.svg" alt="Tests"></a>
|
|
61
61
|
</p>
|
|
62
62
|
|
|
@@ -77,7 +77,7 @@ Dynamic: summary
|
|
|
77
77
|
|
|
78
78
|
1. Kamath, H. G., Sudharsan, N., Singh, M., Wallenberg, N., Lindberg, F., & Niyogi, D. (2026). SOLWEIG-GPU: GPU-Accelerated Thermal Comfort Modeling Framework for Urban Digital Twins. Journal of Open Source Software, 11(118), 9535. https://doi.org/10.21105/joss.09535
|
|
79
79
|
|
|
80
|
-
2. Zonato, A., Kamath, H.G., Sudharsan, N., Monaco, L., Kittner, J., Wolf, L., Demuzere, M
|
|
80
|
+
2. Zonato, A., Kamath, H. G., Sudharsan, N., Monaco, L., Kittner, J., Wolf, L., Demuzere, M., Middel, A., Bechtel, B., and Milelli, M.: GLIDE-SOL: a GPU-accelerated global lightweight infrastructure for diagnostic environmental modeling with SOLWEIG, Geosci. Model Dev., 19, 7389–7413, https://doi.org/10.5194/gmd-19-7389-2026, 2026.
|
|
81
81
|
|
|
82
82
|
**SOLWEIG** was originally developed by Dr. Fredrik Lindberg's group. Journal reference: Lindberg, F., Holmer, B. & Thorsson, S. SOLWEIG 1.0 – Modelling spatial variations of 3D radiant fluxes and mean radiant temperature in complex urban settings. *Int J Biometeorol* 52, 697–713 (2008). https://doi.org/10.1007/s00484-008-0162-7
|
|
83
83
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from solweig_gpu.shadow import SVF_CACHE_VERSION, is_current_svf_cache, shadow
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _isolated_tree_scene(size=31):
|
|
8
|
+
dsm = torch.zeros((size, size), dtype=torch.float32)
|
|
9
|
+
canopy = torch.zeros_like(dsm)
|
|
10
|
+
trunk = torch.zeros_like(dsm)
|
|
11
|
+
bush = torch.zeros_like(dsm)
|
|
12
|
+
|
|
13
|
+
center = size // 2
|
|
14
|
+
canopy[center, center] = 10.0
|
|
15
|
+
trunk[center, center] = 2.5
|
|
16
|
+
return dsm, canopy, trunk, bush
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_shadow_tracks_previous_ray_step_for_thin_canopies():
|
|
20
|
+
dsm, canopy, trunk, bush = _isolated_tree_scene()
|
|
21
|
+
|
|
22
|
+
_, vegetation_visible, _ = shadow(
|
|
23
|
+
torch.tensor(10.0),
|
|
24
|
+
dsm,
|
|
25
|
+
canopy,
|
|
26
|
+
trunk,
|
|
27
|
+
bush,
|
|
28
|
+
torch.tensor(5.0),
|
|
29
|
+
torch.tensor(42.0),
|
|
30
|
+
1.0,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
shaded = set(map(tuple, torch.nonzero(vegetation_visible == 0).tolist()))
|
|
34
|
+
assert shaded == {
|
|
35
|
+
(18, 15),
|
|
36
|
+
(19, 15),
|
|
37
|
+
(20, 15),
|
|
38
|
+
(21, 14),
|
|
39
|
+
(22, 14),
|
|
40
|
+
(23, 14),
|
|
41
|
+
(24, 14),
|
|
42
|
+
(25, 14),
|
|
43
|
+
(26, 14),
|
|
44
|
+
(27, 14),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_zenith_patch_does_not_cast_horizontal_building_shadows():
|
|
49
|
+
dsm = torch.zeros((9, 9), dtype=torch.float32)
|
|
50
|
+
dsm[4, 4] = 10.0
|
|
51
|
+
vegetation = torch.zeros_like(dsm)
|
|
52
|
+
|
|
53
|
+
building_visible, _, _ = shadow(
|
|
54
|
+
torch.tensor(10.0),
|
|
55
|
+
dsm,
|
|
56
|
+
vegetation,
|
|
57
|
+
vegetation,
|
|
58
|
+
vegetation,
|
|
59
|
+
torch.tensor(0.0),
|
|
60
|
+
torch.tensor(90.0),
|
|
61
|
+
1.0,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
assert torch.all(building_visible == 1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_pre_pergola_shadow_cache_is_invalidated(tmp_path):
|
|
68
|
+
old_cache = tmp_path / "old.npz"
|
|
69
|
+
current_cache = tmp_path / "current.npz"
|
|
70
|
+
np.savez_compressed(old_cache, shadowmat=np.ones((1, 1, 1)))
|
|
71
|
+
np.savez_compressed(current_cache, cache_version=np.asarray(SVF_CACHE_VERSION))
|
|
72
|
+
|
|
73
|
+
assert not is_current_svf_cache(old_cache)
|
|
74
|
+
assert is_current_svf_cache(current_cache)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|