voxcity 0.7.0__py3-none-any.whl → 1.0.13__py3-none-any.whl

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 (81) hide show
  1. voxcity/__init__.py +14 -14
  2. voxcity/downloader/ocean.py +559 -0
  3. voxcity/exporter/__init__.py +12 -12
  4. voxcity/exporter/cityles.py +633 -633
  5. voxcity/exporter/envimet.py +733 -728
  6. voxcity/exporter/magicavoxel.py +333 -333
  7. voxcity/exporter/netcdf.py +238 -238
  8. voxcity/exporter/obj.py +1480 -1480
  9. voxcity/generator/__init__.py +47 -44
  10. voxcity/generator/api.py +727 -675
  11. voxcity/generator/grids.py +394 -379
  12. voxcity/generator/io.py +94 -94
  13. voxcity/generator/pipeline.py +582 -282
  14. voxcity/generator/update.py +429 -0
  15. voxcity/generator/voxelizer.py +18 -6
  16. voxcity/geoprocessor/__init__.py +75 -75
  17. voxcity/geoprocessor/draw.py +1494 -1219
  18. voxcity/geoprocessor/merge_utils.py +91 -91
  19. voxcity/geoprocessor/mesh.py +806 -806
  20. voxcity/geoprocessor/network.py +708 -708
  21. voxcity/geoprocessor/raster/__init__.py +2 -0
  22. voxcity/geoprocessor/raster/buildings.py +435 -428
  23. voxcity/geoprocessor/raster/core.py +31 -0
  24. voxcity/geoprocessor/raster/export.py +93 -93
  25. voxcity/geoprocessor/raster/landcover.py +178 -51
  26. voxcity/geoprocessor/raster/raster.py +1 -1
  27. voxcity/geoprocessor/utils.py +824 -824
  28. voxcity/models.py +115 -113
  29. voxcity/simulator/solar/__init__.py +66 -43
  30. voxcity/simulator/solar/integration.py +336 -336
  31. voxcity/simulator/solar/sky.py +668 -0
  32. voxcity/simulator/solar/temporal.py +792 -434
  33. voxcity/simulator_gpu/__init__.py +115 -0
  34. voxcity/simulator_gpu/common/__init__.py +9 -0
  35. voxcity/simulator_gpu/common/geometry.py +11 -0
  36. voxcity/simulator_gpu/core.py +322 -0
  37. voxcity/simulator_gpu/domain.py +262 -0
  38. voxcity/simulator_gpu/environment.yml +11 -0
  39. voxcity/simulator_gpu/init_taichi.py +154 -0
  40. voxcity/simulator_gpu/integration.py +15 -0
  41. voxcity/simulator_gpu/kernels.py +56 -0
  42. voxcity/simulator_gpu/radiation.py +28 -0
  43. voxcity/simulator_gpu/raytracing.py +623 -0
  44. voxcity/simulator_gpu/sky.py +9 -0
  45. voxcity/simulator_gpu/solar/__init__.py +178 -0
  46. voxcity/simulator_gpu/solar/core.py +66 -0
  47. voxcity/simulator_gpu/solar/csf.py +1249 -0
  48. voxcity/simulator_gpu/solar/domain.py +561 -0
  49. voxcity/simulator_gpu/solar/epw.py +421 -0
  50. voxcity/simulator_gpu/solar/integration.py +2953 -0
  51. voxcity/simulator_gpu/solar/radiation.py +3019 -0
  52. voxcity/simulator_gpu/solar/raytracing.py +686 -0
  53. voxcity/simulator_gpu/solar/reflection.py +533 -0
  54. voxcity/simulator_gpu/solar/sky.py +907 -0
  55. voxcity/simulator_gpu/solar/solar.py +337 -0
  56. voxcity/simulator_gpu/solar/svf.py +446 -0
  57. voxcity/simulator_gpu/solar/volumetric.py +1151 -0
  58. voxcity/simulator_gpu/solar/voxcity.py +2953 -0
  59. voxcity/simulator_gpu/temporal.py +13 -0
  60. voxcity/simulator_gpu/utils.py +25 -0
  61. voxcity/simulator_gpu/view.py +32 -0
  62. voxcity/simulator_gpu/visibility/__init__.py +109 -0
  63. voxcity/simulator_gpu/visibility/geometry.py +278 -0
  64. voxcity/simulator_gpu/visibility/integration.py +808 -0
  65. voxcity/simulator_gpu/visibility/landmark.py +753 -0
  66. voxcity/simulator_gpu/visibility/view.py +944 -0
  67. voxcity/utils/__init__.py +11 -0
  68. voxcity/utils/classes.py +194 -0
  69. voxcity/utils/lc.py +80 -39
  70. voxcity/utils/shape.py +230 -0
  71. voxcity/visualizer/__init__.py +24 -24
  72. voxcity/visualizer/builder.py +43 -43
  73. voxcity/visualizer/grids.py +141 -141
  74. voxcity/visualizer/maps.py +187 -187
  75. voxcity/visualizer/renderer.py +1146 -928
  76. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/METADATA +56 -52
  77. voxcity-1.0.13.dist-info/RECORD +116 -0
  78. voxcity-0.7.0.dist-info/RECORD +0 -77
  79. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/WHEEL +0 -0
  80. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/AUTHORS.rst +0 -0
  81. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,115 @@
1
+ """simulator_gpu: GPU-accelerated simulation modules using Taichi.
2
+
3
+ Compatibility goal:
4
+ Allow the common VoxCity pattern to work without code changes beyond the
5
+ import alias:
6
+
7
+ import simulator_gpu as simulator
8
+
9
+ by flattening a VoxCity-like public namespace (view/visibility/solar/utils).
10
+ """
11
+
12
+ import os
13
+
14
+ # Disable Numba caching to prevent stale cache issues when module paths change.
15
+ # This avoids "ModuleNotFoundError: No module named 'simulator_gpu'" errors
16
+ # that can occur when Numba tries to load cached functions with old module paths.
17
+ os.environ.setdefault("NUMBA_CACHE_DIR", "") # Disable disk caching
18
+ os.environ.setdefault("NUMBA_DISABLE_JIT", "0") # Keep JIT enabled for performance
19
+
20
+ # Import Taichi initialization utilities first
21
+ from .init_taichi import ( # noqa: F401
22
+ init_taichi,
23
+ ensure_initialized,
24
+ is_initialized,
25
+ )
26
+
27
+ # Check if Taichi is available
28
+ try:
29
+ import taichi as ti
30
+ _TAICHI_AVAILABLE = True
31
+ except ImportError:
32
+ _TAICHI_AVAILABLE = False
33
+
34
+ # VoxCity-style flattening
35
+ from .view import * # noqa: F401,F403
36
+ from .solar import * # noqa: F401,F403
37
+ from .utils import * # noqa: F401,F403
38
+
39
+ # Export submodules for explicit access
40
+ from . import solar # noqa: F401
41
+ from . import visibility # noqa: F401
42
+ from . import view # noqa: F401
43
+ from . import utils # noqa: F401
44
+ from . import common # noqa: F401
45
+
46
+ # VoxCity-flattened module names that some code expects to exist on the toplevel
47
+ from . import sky # noqa: F401
48
+ from . import kernels # noqa: F401
49
+ from . import radiation # noqa: F401
50
+ from . import temporal # noqa: F401
51
+ from . import integration # noqa: F401
52
+
53
+ # Commonly re-exported VoxCity solar helpers
54
+ from .kernels import compute_direct_solar_irradiance_map_binary # noqa: F401
55
+ from .radiation import compute_solar_irradiance_for_all_faces # noqa: F401
56
+
57
+ # Backward compatibility: some code treats `simulator.view` as `simulator.visibility`
58
+ # (VoxCity provides `view.py` wrapper; we also provide that module).
59
+
60
+ # Export shared modules (kept; extra symbols are fine)
61
+ from .core import ( # noqa: F401
62
+ Vector3, Point3,
63
+ PI, TWO_PI, DEG_TO_RAD, RAD_TO_DEG,
64
+ SOLAR_CONSTANT, EXT_COEF,
65
+ )
66
+ from .domain import Domain, IUP, IDOWN, INORTH, ISOUTH, IEAST, IWEST # noqa: F401
67
+
68
+
69
+ def clear_numba_cache():
70
+ """Clear Numba's compiled function cache to resolve stale cache issues.
71
+
72
+ Call this function if you encounter errors like:
73
+ ModuleNotFoundError: No module named 'simulator_gpu'
74
+
75
+ After calling this function, restart your Python kernel/interpreter.
76
+ """
77
+ import shutil
78
+ import glob
79
+ from pathlib import Path
80
+
81
+ cleared = []
82
+
83
+ # Clear .nbc and .nbi files in the package directory
84
+ package_dir = Path(__file__).parent
85
+ for pattern in ["**/*.nbc", "**/*.nbi"]:
86
+ for cache_file in package_dir.glob(pattern):
87
+ try:
88
+ cache_file.unlink()
89
+ cleared.append(str(cache_file))
90
+ except Exception:
91
+ pass
92
+
93
+ # Clear __pycache__ directories
94
+ for pycache in package_dir.glob("**/__pycache__"):
95
+ try:
96
+ shutil.rmtree(pycache)
97
+ cleared.append(str(pycache))
98
+ except Exception:
99
+ pass
100
+
101
+ # Try to clear user's .numba_cache if it exists
102
+ home = Path.home()
103
+ numba_cache = home / ".numba_cache"
104
+ if numba_cache.exists():
105
+ try:
106
+ shutil.rmtree(numba_cache)
107
+ cleared.append(str(numba_cache))
108
+ except Exception:
109
+ pass
110
+
111
+ print(f"Cleared {len(cleared)} cache items. Please restart your Python kernel.")
112
+ return cleared
113
+
114
+
115
+ __version__ = "0.1.0"
@@ -0,0 +1,9 @@
1
+ """VoxCity-style `common` namespace.
2
+
3
+ VoxCity exposes helpers under `voxcity.simulator.common.*`.
4
+ `simulator_gpu` implements only the subset needed for drop-in compatibility.
5
+ """
6
+
7
+ from .geometry import rotate_vector_axis_angle
8
+
9
+ __all__ = ["rotate_vector_axis_angle"]
@@ -0,0 +1,11 @@
1
+ """Geometry helpers for VoxCity compatibility.
2
+
3
+ This module is intended to satisfy imports like:
4
+ from simulator.common.geometry import rotate_vector_axis_angle
5
+
6
+ It forwards to the implementation used by `simulator_gpu.visibility`.
7
+ """
8
+
9
+ from ..visibility.geometry import rotate_vector_axis_angle
10
+
11
+ __all__ = ["rotate_vector_axis_angle"]
@@ -0,0 +1,322 @@
1
+ """
2
+ Shared core utilities for simulator_gpu.
3
+
4
+ Vector and ray utilities using Taichi for GPU acceleration.
5
+ Based on ray-tracing-one-weekend-taichi patterns.
6
+
7
+ GPU Optimization Notes:
8
+ - All functions use @ti.func for GPU inlining
9
+ - Branchless operations preferred where possible
10
+ - Memory coalescing friendly access patterns
11
+ - done-flag pattern for early termination (reduces warp divergence)
12
+ """
13
+
14
+ import taichi as ti
15
+ import math
16
+
17
+ # Type aliases for clarity
18
+ Vector3 = ti.math.vec3
19
+ Point3 = ti.math.vec3
20
+ Color3 = ti.math.vec3
21
+
22
+ # Constants - using Taichi static for compile-time optimization
23
+ PI = math.pi
24
+ TWO_PI = 2.0 * math.pi
25
+ HALF_PI = math.pi / 2.0
26
+ DEG_TO_RAD = math.pi / 180.0
27
+ RAD_TO_DEG = 180.0 / math.pi
28
+
29
+ # Solar constant (W/m^2) - matching PALM's solar_constant
30
+ SOLAR_CONSTANT = 1361.0
31
+
32
+ # Default extinction coefficient for vegetation
33
+ # PALM: ext_coef = 0.6_wp (radiation_model_mod.f90 line ~890)
34
+ EXT_COEF = 0.6
35
+
36
+ # Minimum stable cosine of zenith angle
37
+ MIN_STABLE_COSZEN = 0.0262
38
+
39
+ # GPU block size hint for optimal thread occupancy
40
+ GPU_BLOCK_SIZE = 256
41
+
42
+
43
+ @ti.func
44
+ def normalize(v: Vector3) -> Vector3:
45
+ """Normalize a vector."""
46
+ return v / v.norm()
47
+
48
+
49
+ @ti.func
50
+ def normalize_safe(v: Vector3) -> Vector3:
51
+ """Normalize a vector with safety check for zero-length."""
52
+ len_sq = v.dot(v)
53
+ if len_sq > 1e-10:
54
+ return v / ti.sqrt(len_sq)
55
+ return Vector3(0.0, 0.0, 1.0)
56
+
57
+
58
+ @ti.func
59
+ def dot(v1: Vector3, v2: Vector3) -> ti.f32:
60
+ """Dot product of two vectors."""
61
+ return v1.dot(v2)
62
+
63
+
64
+ @ti.func
65
+ def cross(v1: Vector3, v2: Vector3) -> Vector3:
66
+ """Cross product of two vectors."""
67
+ return v1.cross(v2)
68
+
69
+
70
+ @ti.func
71
+ def reflect(v: Vector3, n: Vector3) -> Vector3:
72
+ """Reflect vector v around normal n."""
73
+ return v - 2.0 * v.dot(n) * n
74
+
75
+
76
+ @ti.func
77
+ def ray_at(origin: Point3, direction: Vector3, t: ti.f32) -> Point3:
78
+ """Get point along ray at parameter t."""
79
+ return origin + t * direction
80
+
81
+
82
+ @ti.func
83
+ def length_squared(v: Vector3) -> ti.f32:
84
+ """Compute squared length of vector (avoids sqrt)."""
85
+ return v.dot(v)
86
+
87
+
88
+ @ti.func
89
+ def distance_squared(p1: Point3, p2: Point3) -> ti.f32:
90
+ """Compute squared distance between two points (avoids sqrt)."""
91
+ diff = p2 - p1
92
+ return diff.dot(diff)
93
+
94
+
95
+ @ti.func
96
+ def min3(a: ti.f32, b: ti.f32, c: ti.f32) -> ti.f32:
97
+ """Branchless minimum of three values."""
98
+ return ti.min(a, ti.min(b, c))
99
+
100
+
101
+ @ti.func
102
+ def max3(a: ti.f32, b: ti.f32, c: ti.f32) -> ti.f32:
103
+ """Branchless maximum of three values."""
104
+ return ti.max(a, ti.max(b, c))
105
+
106
+
107
+ @ti.func
108
+ def clamp(x: ti.f32, lo: ti.f32, hi: ti.f32) -> ti.f32:
109
+ """Clamp value to range [lo, hi]."""
110
+ return ti.max(lo, ti.min(hi, x))
111
+
112
+
113
+ @ti.func
114
+ def random_in_unit_sphere() -> Vector3:
115
+ """Generate random point in unit sphere."""
116
+ theta = ti.random() * TWO_PI
117
+ v = ti.random()
118
+ phi = ti.acos(2.0 * v - 1.0)
119
+ r = ti.random() ** (1.0 / 3.0)
120
+ return Vector3(
121
+ r * ti.sin(phi) * ti.cos(theta),
122
+ r * ti.sin(phi) * ti.sin(theta),
123
+ r * ti.cos(phi)
124
+ )
125
+
126
+
127
+ @ti.func
128
+ def random_in_hemisphere(normal: Vector3) -> Vector3:
129
+ """Generate random vector in hemisphere around normal."""
130
+ vec = random_in_unit_sphere()
131
+ if vec.dot(normal) < 0.0:
132
+ vec = -vec
133
+ return vec
134
+
135
+
136
+ @ti.func
137
+ def random_cosine_hemisphere(normal: Vector3) -> Vector3:
138
+ """
139
+ Generate random vector with cosine-weighted distribution in hemisphere.
140
+ Used for diffuse radiation sampling.
141
+ """
142
+ u1 = ti.random()
143
+ u2 = ti.random()
144
+ r = ti.sqrt(u1)
145
+ theta = TWO_PI * u2
146
+
147
+ x = r * ti.cos(theta)
148
+ y = r * ti.sin(theta)
149
+ z = ti.sqrt(1.0 - u1)
150
+
151
+ # Create orthonormal basis around normal
152
+ up = Vector3(0.0, 1.0, 0.0)
153
+ if ti.abs(normal.y) > 0.999:
154
+ up = Vector3(1.0, 0.0, 0.0)
155
+
156
+ tangent = normalize(cross(up, normal))
157
+ bitangent = cross(normal, tangent)
158
+
159
+ return normalize(x * tangent + y * bitangent + z * normal)
160
+
161
+
162
+ @ti.func
163
+ def spherical_to_cartesian(azimuth: ti.f32, elevation: ti.f32) -> Vector3:
164
+ """
165
+ Convert spherical coordinates to Cartesian unit vector.
166
+
167
+ Args:
168
+ azimuth: Angle from north (y-axis), clockwise, in radians
169
+ elevation: Angle from horizontal, in radians (0 = horizontal, pi/2 = zenith)
170
+
171
+ Returns:
172
+ Unit vector (x, y, z) where z is vertical (up)
173
+ """
174
+ cos_elev = ti.cos(elevation)
175
+ sin_elev = ti.sin(elevation)
176
+ cos_azim = ti.cos(azimuth)
177
+ sin_azim = ti.sin(azimuth)
178
+
179
+ # x = east, y = north, z = up
180
+ x = cos_elev * sin_azim
181
+ y = cos_elev * cos_azim
182
+ z = sin_elev
183
+
184
+ return Vector3(x, y, z)
185
+
186
+
187
+ @ti.func
188
+ def cartesian_to_spherical(v: Vector3) -> ti.math.vec2:
189
+ """
190
+ Convert Cartesian unit vector to spherical coordinates.
191
+
192
+ Returns:
193
+ vec2(azimuth, elevation) in radians
194
+ """
195
+ elevation = ti.asin(ti.math.clamp(v.z, -1.0, 1.0))
196
+ azimuth = ti.atan2(v.x, v.y)
197
+ if azimuth < 0.0:
198
+ azimuth += TWO_PI
199
+ return ti.math.vec2(azimuth, elevation)
200
+
201
+
202
+ @ti.func
203
+ def rotate_vector_axis_angle(vec: Vector3, axis: Vector3, angle: ti.f32) -> Vector3:
204
+ """
205
+ Rotate vector around an axis by a given angle using Rodrigues' rotation formula.
206
+
207
+ Args:
208
+ vec: Vector to rotate
209
+ axis: Rotation axis (will be normalized)
210
+ angle: Rotation angle in radians
211
+
212
+ Returns:
213
+ Rotated vector
214
+ """
215
+ axis_len = axis.norm()
216
+ if axis_len < 1e-12:
217
+ return vec
218
+
219
+ k = axis / axis_len
220
+ c = ti.cos(angle)
221
+ s = ti.sin(angle)
222
+
223
+ # Rodrigues' rotation formula: v_rot = v*cos(θ) + (k×v)*sin(θ) + k*(k·v)*(1-cos(θ))
224
+ v_rot = vec * c + cross(k, vec) * s + k * dot(k, vec) * (1.0 - c)
225
+
226
+ return v_rot
227
+
228
+
229
+ @ti.func
230
+ def build_face_basis(normal: Vector3):
231
+ """
232
+ Build orthonormal basis for a face with given normal.
233
+
234
+ Returns:
235
+ Tuple of (tangent, bitangent, normal) vectors
236
+ """
237
+ n_len = normal.norm()
238
+ if n_len < 1e-12:
239
+ return Vector3(1.0, 0.0, 0.0), Vector3(0.0, 1.0, 0.0), Vector3(0.0, 0.0, 1.0)
240
+
241
+ n = normal / n_len
242
+
243
+ # Choose helper vector not parallel to normal
244
+ helper = Vector3(0.0, 0.0, 1.0)
245
+ if ti.abs(n.z) > 0.999:
246
+ helper = Vector3(1.0, 0.0, 0.0)
247
+
248
+ # Compute tangent via cross product
249
+ u = cross(helper, n)
250
+ u_len = u.norm()
251
+ if u_len < 1e-12:
252
+ u = Vector3(1.0, 0.0, 0.0)
253
+ else:
254
+ u = u / u_len
255
+
256
+ # Bitangent
257
+ v = cross(n, u)
258
+
259
+ return u, v, n
260
+
261
+
262
+ @ti.data_oriented
263
+ class Rays:
264
+ """
265
+ Array of rays for batch processing.
266
+ Similar to ray-tracing-one-weekend-taichi but adapted for view/solar tracing.
267
+ """
268
+
269
+ def __init__(self, n_rays: int):
270
+ self.n_rays = n_rays
271
+ self.origin = ti.Vector.field(3, dtype=ti.f32, shape=(n_rays,))
272
+ self.direction = ti.Vector.field(3, dtype=ti.f32, shape=(n_rays,))
273
+ self.transparency = ti.field(dtype=ti.f32, shape=(n_rays,))
274
+ self.active = ti.field(dtype=ti.i32, shape=(n_rays,))
275
+
276
+ @ti.func
277
+ def set(self, idx: ti.i32, origin: Point3, direction: Vector3, transp: ti.f32):
278
+ self.origin[idx] = origin
279
+ self.direction[idx] = direction
280
+ self.transparency[idx] = transp
281
+ self.active[idx] = 1
282
+
283
+ @ti.func
284
+ def get(self, idx: ti.i32):
285
+ return self.origin[idx], self.direction[idx], self.transparency[idx]
286
+
287
+ @ti.func
288
+ def deactivate(self, idx: ti.i32):
289
+ self.active[idx] = 0
290
+
291
+ @ti.func
292
+ def is_active(self, idx: ti.i32) -> ti.i32:
293
+ return self.active[idx]
294
+
295
+
296
+ @ti.data_oriented
297
+ class HitRecord:
298
+ """
299
+ Store ray-surface intersection results.
300
+ """
301
+
302
+ def __init__(self, n_rays: int):
303
+ self.n_rays = n_rays
304
+ self.hit = ti.field(dtype=ti.i32, shape=(n_rays,))
305
+ self.t = ti.field(dtype=ti.f32, shape=(n_rays,))
306
+ self.point = ti.Vector.field(3, dtype=ti.f32, shape=(n_rays,))
307
+ self.normal = ti.Vector.field(3, dtype=ti.f32, shape=(n_rays,))
308
+ self.surface_id = ti.field(dtype=ti.i32, shape=(n_rays,))
309
+
310
+ @ti.func
311
+ def set(self, idx: ti.i32, hit: ti.i32, t: ti.f32, point: Point3,
312
+ normal: Vector3, surface_id: ti.i32):
313
+ self.hit[idx] = hit
314
+ self.t[idx] = t
315
+ self.point[idx] = point
316
+ self.normal[idx] = normal
317
+ self.surface_id[idx] = surface_id
318
+
319
+ @ti.func
320
+ def get(self, idx: ti.i32):
321
+ return (self.hit[idx], self.t[idx], self.point[idx],
322
+ self.normal[idx], self.surface_id[idx])