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.
Files changed (47) hide show
  1. {solweig_gpu-2.0.0/solweig_gpu.egg-info → solweig_gpu-2.0.1}/PKG-INFO +3 -3
  2. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/README.md +2 -2
  3. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/setup.py +1 -1
  4. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/__init__.py +1 -1
  5. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/landcoverclasses_2016a.txt +1 -1
  6. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/shadow.py +57 -36
  7. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/solweig_gpu.py +2 -2
  8. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/utci_process.py +5 -7
  9. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1/solweig_gpu.egg-info}/PKG-INFO +3 -3
  10. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/SOURCES.txt +1 -0
  11. solweig_gpu-2.0.1/tests/test_shadow.py +74 -0
  12. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/LICENSE +0 -0
  13. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/Logo.png +0 -0
  14. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/MANIFEST.in +0 -0
  15. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/UTCI_New_Delhi.jpeg +0 -0
  16. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/pyproject.toml +0 -0
  17. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/requirements.txt +0 -0
  18. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/setup.cfg +0 -0
  19. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_diagram.png +0 -0
  20. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/Tgmaps_v1.py +0 -0
  21. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/calculate_utci.py +0 -0
  22. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/calculate_wbgt.py +0 -0
  23. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/cli.py +0 -0
  24. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/create_inputs.py +0 -0
  25. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/preprocessor.py +0 -0
  26. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/solweig.py +0 -0
  27. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/sun_position.py +0 -0
  28. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/walls_aspect.py +0 -0
  29. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu/wind_ext_coeff.py +0 -0
  30. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/dependency_links.txt +0 -0
  31. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/entry_points.txt +0 -0
  32. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/requires.txt +0 -0
  33. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/solweig_gpu.egg-info/top_level.txt +0 -0
  34. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/__init__.py +0 -0
  35. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/conftest.py +0 -0
  36. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_calculate_utci.py +0 -0
  37. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_cli.py +0 -0
  38. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_create_inputs_austin.py +0 -0
  39. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_era5_processing.py +0 -0
  40. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_gpu_smoke.py +0 -0
  41. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_integration.py +0 -0
  42. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_integration_end_to_end.py +0 -0
  43. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_preprocessor.py +0 -0
  44. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_sun_position.py +0 -0
  45. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_tiling_and_metfiles.py +0 -0
  46. {solweig_gpu-2.0.0 → solweig_gpu-2.0.1}/tests/test_walls_aspect.py +0 -0
  47. {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.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://doi.org/10.5194/gmd-19-7389-2026.svg" alt="EMD"></a>
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.A., Middel, A., Bechtel, B. and Milelli, M., 2026. GLIDE-SOL: A GPU-accelerated Global Lightweight Infrastructure for Diagnostic Environmental Modeling with SOLWEIG. EGUsphere, 2026, pp.1-30. https://doi.org/10.5194/egusphere-2026-776
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://doi.org/10.5194/gmd-19-7389-2026.svg" alt="EMD"></a>
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.A., Middel, A., Bechtel, B. and Milelli, M., 2026. GLIDE-SOL: A GPU-accelerated Global Lightweight Infrastructure for Diagnostic Environmental Modeling with SOLWEIG. EGUsphere, 2026, pp.1-30. https://doi.org/10.5194/egusphere-2026-776
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.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',
@@ -11,7 +11,7 @@
11
11
  #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
12
  #GNU General Public License for more details.
13
13
 
14
- __version__ = "2.0.0"
14
+ __version__ = "2.0.1"
15
15
  from .solweig_gpu import (
16
16
  thermal_comfort,
17
17
  preprocess,
@@ -5,4 +5,4 @@ Cobble_stone_2014a 0 0.20 0.95 0.37 -3.41 15.0
5
5
  Water 7 0.05 0.98 0.00 0.00 12.0
6
6
  Grass_unmanaged 5 0.16 0.94 0.21 -3.38 14.0
7
7
  bare_soil 6 0.25 0.94 0.33 -3.01 14.0
8
- Walls 99 0.20 0.90 0.58 -3.41 15.0
8
+ Walls 99 0.20 0.90 0.37 -3.41 15.0
@@ -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 = torch.device("cuda" if torch.cuda.is_available() else "cpu")
214
+ device = a.device
195
215
  degrees = torch.pi / 180.
196
- if azimuth == 0.0:
197
- azimuth = 1e-12
198
- azimuth = ensure_tensor(azimuth)
199
- altitude = ensure_tensor(altitude)
200
- azimuth = azimuth * degrees #torch.tensor(azimuth * degrees, device=a.device)
201
- altitude = altitude * degrees #torch.tensor(altitude * degrees, device=a.device)
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
- tempbush = torch.zeros((sizex, sizey), device=device)
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
- index = 1
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
- vegsh2 = fabovea.float() - gabovea.float()
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
- # Changed here
319
- del tempvegdem, tempvegdem2, temp, tempbush, fabovea, gabovea, vegsh2
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 os.path.exists(expected_npz)
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 svf_calculator, create_patches
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 os.path.isfile(npz_path)
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
- # Added
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
- # Added
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
- # Added
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.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://doi.org/10.5194/gmd-19-7389-2026.svg" alt="EMD"></a>
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.A., Middel, A., Bechtel, B. and Milelli, M., 2026. GLIDE-SOL: A GPU-accelerated Global Lightweight Infrastructure for Diagnostic Environmental Modeling with SOLWEIG. EGUsphere, 2026, pp.1-30. https://doi.org/10.5194/egusphere-2026-776
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
 
@@ -38,6 +38,7 @@ tests/test_gpu_smoke.py
38
38
  tests/test_integration.py
39
39
  tests/test_integration_end_to_end.py
40
40
  tests/test_preprocessor.py
41
+ tests/test_shadow.py
41
42
  tests/test_sun_position.py
42
43
  tests/test_tiling_and_metfiles.py
43
44
  tests/test_walls_aspect.py
@@ -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