voxcity 1.0.2__py3-none-any.whl → 1.0.15__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.
- voxcity/downloader/ocean.py +559 -0
- voxcity/generator/api.py +6 -0
- voxcity/generator/grids.py +45 -32
- voxcity/generator/pipeline.py +327 -27
- voxcity/geoprocessor/draw.py +14 -8
- voxcity/geoprocessor/raster/__init__.py +2 -0
- voxcity/geoprocessor/raster/core.py +31 -0
- voxcity/geoprocessor/raster/landcover.py +173 -49
- voxcity/geoprocessor/raster/raster.py +1 -1
- voxcity/models.py +2 -0
- voxcity/simulator/solar/__init__.py +13 -0
- voxcity/simulator_gpu/__init__.py +90 -0
- voxcity/simulator_gpu/core.py +322 -0
- voxcity/simulator_gpu/domain.py +36 -0
- voxcity/simulator_gpu/init_taichi.py +154 -0
- voxcity/simulator_gpu/raytracing.py +776 -0
- voxcity/simulator_gpu/solar/__init__.py +222 -0
- voxcity/simulator_gpu/solar/core.py +66 -0
- voxcity/simulator_gpu/solar/csf.py +1249 -0
- voxcity/simulator_gpu/solar/domain.py +618 -0
- voxcity/simulator_gpu/solar/epw.py +421 -0
- voxcity/simulator_gpu/solar/integration.py +4322 -0
- voxcity/simulator_gpu/solar/mask.py +459 -0
- voxcity/simulator_gpu/solar/radiation.py +3019 -0
- voxcity/simulator_gpu/solar/raytracing.py +182 -0
- voxcity/simulator_gpu/solar/reflection.py +533 -0
- voxcity/simulator_gpu/solar/sky.py +907 -0
- voxcity/simulator_gpu/solar/solar.py +337 -0
- voxcity/simulator_gpu/solar/svf.py +446 -0
- voxcity/simulator_gpu/solar/volumetric.py +2099 -0
- voxcity/simulator_gpu/visibility/__init__.py +109 -0
- voxcity/simulator_gpu/visibility/geometry.py +278 -0
- voxcity/simulator_gpu/visibility/integration.py +808 -0
- voxcity/simulator_gpu/visibility/landmark.py +753 -0
- voxcity/simulator_gpu/visibility/view.py +944 -0
- voxcity/visualizer/renderer.py +2 -1
- {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/METADATA +16 -53
- {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/RECORD +41 -16
- {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/WHEEL +0 -0
- {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -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])
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared domain definition for simulator_gpu.
|
|
3
|
+
|
|
4
|
+
This module re-exports the Domain class from solar.domain for backward compatibility.
|
|
5
|
+
The main implementation is in simulator_gpu.solar.domain which includes:
|
|
6
|
+
- Domain class with full grid, terrain, building, and vegetation support
|
|
7
|
+
- Surfaces class for radiation calculations
|
|
8
|
+
- Surface extraction utilities
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Re-export from solar.domain (the main implementation)
|
|
12
|
+
from .solar.domain import (
|
|
13
|
+
Domain,
|
|
14
|
+
Surfaces,
|
|
15
|
+
extract_surfaces_from_domain,
|
|
16
|
+
IUP,
|
|
17
|
+
IDOWN,
|
|
18
|
+
INORTH,
|
|
19
|
+
ISOUTH,
|
|
20
|
+
IEAST,
|
|
21
|
+
IWEST,
|
|
22
|
+
DIR_NORMALS,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'Domain',
|
|
27
|
+
'Surfaces',
|
|
28
|
+
'extract_surfaces_from_domain',
|
|
29
|
+
'IUP',
|
|
30
|
+
'IDOWN',
|
|
31
|
+
'INORTH',
|
|
32
|
+
'ISOUTH',
|
|
33
|
+
'IEAST',
|
|
34
|
+
'IWEST',
|
|
35
|
+
'DIR_NORMALS',
|
|
36
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Taichi initialization for simulator_gpu.
|
|
3
|
+
|
|
4
|
+
This module provides centralized Taichi initialization to ensure ti.init()
|
|
5
|
+
is called before any Taichi fields or kernels are used.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import taichi as ti
|
|
9
|
+
import os
|
|
10
|
+
import warnings
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# Track initialization state
|
|
14
|
+
_TAICHI_INITIALIZED = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def init_taichi(
|
|
18
|
+
arch: Optional[str] = None,
|
|
19
|
+
default_fp: type = ti.f32,
|
|
20
|
+
default_ip: type = ti.i32,
|
|
21
|
+
debug: bool = False,
|
|
22
|
+
suppress_fp16_warnings: bool = True,
|
|
23
|
+
**kwargs
|
|
24
|
+
) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Initialize Taichi runtime if not already initialized.
|
|
27
|
+
|
|
28
|
+
This function is idempotent - calling it multiple times is safe.
|
|
29
|
+
The first call will initialize Taichi, subsequent calls will be no-ops.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
arch: Architecture to use. Options:
|
|
33
|
+
- None (default): Auto-detect best available (GPU preferred)
|
|
34
|
+
- 'gpu': Use GPU (CUDA, Vulkan, Metal, etc.)
|
|
35
|
+
- 'cuda': Use CUDA specifically
|
|
36
|
+
- 'vulkan': Use Vulkan
|
|
37
|
+
- 'metal': Use Metal (macOS)
|
|
38
|
+
- 'cpu': Use CPU
|
|
39
|
+
default_fp: Default floating point type (ti.f32 or ti.f64)
|
|
40
|
+
default_ip: Default integer type (ti.i32 or ti.i64)
|
|
41
|
+
debug: Enable debug mode for better error messages
|
|
42
|
+
suppress_fp16_warnings: Suppress Taichi's fp16 precision loss warnings
|
|
43
|
+
(default: True). These warnings occur when using fp16 intermediate
|
|
44
|
+
buffers for memory bandwidth optimization.
|
|
45
|
+
**kwargs: Additional arguments passed to ti.init()
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if initialization was performed, False if already initialized.
|
|
49
|
+
"""
|
|
50
|
+
global _TAICHI_INITIALIZED
|
|
51
|
+
|
|
52
|
+
if _TAICHI_INITIALIZED:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
# Suppress fp16 precision warnings if requested
|
|
56
|
+
# These occur when assigning f32 values to f16 fields, which is intentional
|
|
57
|
+
# for memory bandwidth optimization in intermediate buffers
|
|
58
|
+
if suppress_fp16_warnings:
|
|
59
|
+
# Filter Taichi's precision loss warnings via Python warnings module
|
|
60
|
+
warnings.filterwarnings(
|
|
61
|
+
'ignore',
|
|
62
|
+
message='.*Assign a value with precision.*',
|
|
63
|
+
category=UserWarning
|
|
64
|
+
)
|
|
65
|
+
warnings.filterwarnings(
|
|
66
|
+
'ignore',
|
|
67
|
+
message='.*Atomic add may lose precision.*',
|
|
68
|
+
category=UserWarning
|
|
69
|
+
)
|
|
70
|
+
# Also set Taichi log level to ERROR to suppress warnings from Taichi's internal logging
|
|
71
|
+
# This is needed because Taichi uses its own logging system for some warnings
|
|
72
|
+
if 'log_level' not in kwargs:
|
|
73
|
+
kwargs['log_level'] = ti.ERROR
|
|
74
|
+
|
|
75
|
+
# Determine architecture
|
|
76
|
+
if arch is None:
|
|
77
|
+
# Auto-detect: prefer GPU, fall back to CPU
|
|
78
|
+
ti_arch = ti.gpu
|
|
79
|
+
elif arch == 'gpu':
|
|
80
|
+
ti_arch = ti.gpu
|
|
81
|
+
elif arch == 'cuda':
|
|
82
|
+
ti_arch = ti.cuda
|
|
83
|
+
elif arch == 'vulkan':
|
|
84
|
+
ti_arch = ti.vulkan
|
|
85
|
+
elif arch == 'metal':
|
|
86
|
+
ti_arch = ti.metal
|
|
87
|
+
elif arch == 'cpu':
|
|
88
|
+
ti_arch = ti.cpu
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(f"Unknown architecture: {arch}")
|
|
91
|
+
|
|
92
|
+
# Check environment variable for override
|
|
93
|
+
env_arch = os.environ.get('TAICHI_ARCH', '').lower()
|
|
94
|
+
if env_arch == 'cpu':
|
|
95
|
+
ti_arch = ti.cpu
|
|
96
|
+
elif env_arch == 'cuda':
|
|
97
|
+
ti_arch = ti.cuda
|
|
98
|
+
elif env_arch == 'vulkan':
|
|
99
|
+
ti_arch = ti.vulkan
|
|
100
|
+
elif env_arch == 'gpu':
|
|
101
|
+
ti_arch = ti.gpu
|
|
102
|
+
|
|
103
|
+
# Initialize Taichi
|
|
104
|
+
ti.init(
|
|
105
|
+
arch=ti_arch,
|
|
106
|
+
default_fp=default_fp,
|
|
107
|
+
default_ip=default_ip,
|
|
108
|
+
debug=debug,
|
|
109
|
+
**kwargs
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
_TAICHI_INITIALIZED = True
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def ensure_initialized():
|
|
117
|
+
"""
|
|
118
|
+
Ensure Taichi is initialized with default settings.
|
|
119
|
+
|
|
120
|
+
This is a convenience function for lazy initialization.
|
|
121
|
+
Call this before any Taichi operations if you're not sure
|
|
122
|
+
whether init_taichi() has been called.
|
|
123
|
+
"""
|
|
124
|
+
if not _TAICHI_INITIALIZED:
|
|
125
|
+
init_taichi()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def is_initialized() -> bool:
|
|
129
|
+
"""Check if Taichi has been initialized."""
|
|
130
|
+
return _TAICHI_INITIALIZED
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def reset():
|
|
134
|
+
"""
|
|
135
|
+
Reset initialization state.
|
|
136
|
+
|
|
137
|
+
Note: This does NOT reset Taichi itself (which cannot be reset once initialized).
|
|
138
|
+
This only resets the tracking flag for testing purposes.
|
|
139
|
+
"""
|
|
140
|
+
global _TAICHI_INITIALIZED
|
|
141
|
+
_TAICHI_INITIALIZED = False
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Convenience: expose ti for direct access
|
|
145
|
+
taichi = ti
|
|
146
|
+
|
|
147
|
+
__all__ = [
|
|
148
|
+
'init_taichi',
|
|
149
|
+
'ensure_initialized',
|
|
150
|
+
'is_initialized',
|
|
151
|
+
'reset',
|
|
152
|
+
'taichi',
|
|
153
|
+
'ti',
|
|
154
|
+
]
|