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.
- voxcity/__init__.py +14 -14
- voxcity/downloader/ocean.py +559 -0
- voxcity/exporter/__init__.py +12 -12
- voxcity/exporter/cityles.py +633 -633
- voxcity/exporter/envimet.py +733 -728
- voxcity/exporter/magicavoxel.py +333 -333
- voxcity/exporter/netcdf.py +238 -238
- voxcity/exporter/obj.py +1480 -1480
- voxcity/generator/__init__.py +47 -44
- voxcity/generator/api.py +727 -675
- voxcity/generator/grids.py +394 -379
- voxcity/generator/io.py +94 -94
- voxcity/generator/pipeline.py +582 -282
- voxcity/generator/update.py +429 -0
- voxcity/generator/voxelizer.py +18 -6
- voxcity/geoprocessor/__init__.py +75 -75
- voxcity/geoprocessor/draw.py +1494 -1219
- voxcity/geoprocessor/merge_utils.py +91 -91
- voxcity/geoprocessor/mesh.py +806 -806
- voxcity/geoprocessor/network.py +708 -708
- voxcity/geoprocessor/raster/__init__.py +2 -0
- voxcity/geoprocessor/raster/buildings.py +435 -428
- voxcity/geoprocessor/raster/core.py +31 -0
- voxcity/geoprocessor/raster/export.py +93 -93
- voxcity/geoprocessor/raster/landcover.py +178 -51
- voxcity/geoprocessor/raster/raster.py +1 -1
- voxcity/geoprocessor/utils.py +824 -824
- voxcity/models.py +115 -113
- voxcity/simulator/solar/__init__.py +66 -43
- voxcity/simulator/solar/integration.py +336 -336
- voxcity/simulator/solar/sky.py +668 -0
- voxcity/simulator/solar/temporal.py +792 -434
- voxcity/simulator_gpu/__init__.py +115 -0
- voxcity/simulator_gpu/common/__init__.py +9 -0
- voxcity/simulator_gpu/common/geometry.py +11 -0
- voxcity/simulator_gpu/core.py +322 -0
- voxcity/simulator_gpu/domain.py +262 -0
- voxcity/simulator_gpu/environment.yml +11 -0
- voxcity/simulator_gpu/init_taichi.py +154 -0
- voxcity/simulator_gpu/integration.py +15 -0
- voxcity/simulator_gpu/kernels.py +56 -0
- voxcity/simulator_gpu/radiation.py +28 -0
- voxcity/simulator_gpu/raytracing.py +623 -0
- voxcity/simulator_gpu/sky.py +9 -0
- voxcity/simulator_gpu/solar/__init__.py +178 -0
- voxcity/simulator_gpu/solar/core.py +66 -0
- voxcity/simulator_gpu/solar/csf.py +1249 -0
- voxcity/simulator_gpu/solar/domain.py +561 -0
- voxcity/simulator_gpu/solar/epw.py +421 -0
- voxcity/simulator_gpu/solar/integration.py +2953 -0
- voxcity/simulator_gpu/solar/radiation.py +3019 -0
- voxcity/simulator_gpu/solar/raytracing.py +686 -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 +1151 -0
- voxcity/simulator_gpu/solar/voxcity.py +2953 -0
- voxcity/simulator_gpu/temporal.py +13 -0
- voxcity/simulator_gpu/utils.py +25 -0
- voxcity/simulator_gpu/view.py +32 -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/utils/__init__.py +11 -0
- voxcity/utils/classes.py +194 -0
- voxcity/utils/lc.py +80 -39
- voxcity/utils/shape.py +230 -0
- voxcity/visualizer/__init__.py +24 -24
- voxcity/visualizer/builder.py +43 -43
- voxcity/visualizer/grids.py +141 -141
- voxcity/visualizer/maps.py +187 -187
- voxcity/visualizer/renderer.py +1146 -928
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/METADATA +56 -52
- voxcity-1.0.13.dist-info/RECORD +116 -0
- voxcity-0.7.0.dist-info/RECORD +0 -77
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/WHEEL +0 -0
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared domain definition for simulator_gpu.
|
|
3
|
+
|
|
4
|
+
Represents the 3D computational domain with:
|
|
5
|
+
- Grid cells (dx, dy, dz spacing)
|
|
6
|
+
- Topography (terrain height)
|
|
7
|
+
- Building geometry (3D obstacles)
|
|
8
|
+
- Plant canopy (Leaf Area Density - LAD)
|
|
9
|
+
- Tree mask for view analysis
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import taichi as ti
|
|
13
|
+
import numpy as np
|
|
14
|
+
from typing import Tuple, Optional, Union
|
|
15
|
+
from .core import Vector3, Point3, EXT_COEF
|
|
16
|
+
from .init_taichi import ensure_initialized
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Surface direction indices (matching PALM convention)
|
|
20
|
+
IUP = 0 # Upward facing (horizontal roof/ground)
|
|
21
|
+
IDOWN = 1 # Downward facing
|
|
22
|
+
INORTH = 2 # North facing (positive y)
|
|
23
|
+
ISOUTH = 3 # South facing (negative y)
|
|
24
|
+
IEAST = 4 # East facing (positive x)
|
|
25
|
+
IWEST = 5 # West facing (negative x)
|
|
26
|
+
|
|
27
|
+
# Direction normal vectors (x, y, z)
|
|
28
|
+
DIR_NORMALS = {
|
|
29
|
+
IUP: (0.0, 0.0, 1.0),
|
|
30
|
+
IDOWN: (0.0, 0.0, -1.0),
|
|
31
|
+
INORTH: (0.0, 1.0, 0.0),
|
|
32
|
+
ISOUTH: (0.0, -1.0, 0.0),
|
|
33
|
+
IEAST: (1.0, 0.0, 0.0),
|
|
34
|
+
IWEST: (-1.0, 0.0, 0.0),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@ti.data_oriented
|
|
39
|
+
class Domain:
|
|
40
|
+
"""
|
|
41
|
+
3D computational domain for simulation.
|
|
42
|
+
|
|
43
|
+
The domain uses a regular grid with:
|
|
44
|
+
- x: West to East
|
|
45
|
+
- y: South to North
|
|
46
|
+
- z: Ground to Sky
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
nx, ny, nz: Number of grid cells in each direction
|
|
50
|
+
dx, dy, dz: Grid spacing in meters
|
|
51
|
+
origin: (x, y, z) coordinates of domain origin
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
nx: int,
|
|
57
|
+
ny: int,
|
|
58
|
+
nz: int,
|
|
59
|
+
dx: float = 1.0,
|
|
60
|
+
dy: float = 1.0,
|
|
61
|
+
dz: float = 1.0,
|
|
62
|
+
origin: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
63
|
+
origin_lat: Optional[float] = None,
|
|
64
|
+
origin_lon: Optional[float] = None
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Initialize the domain.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
nx, ny, nz: Grid dimensions
|
|
71
|
+
dx, dy, dz: Grid spacing (m)
|
|
72
|
+
origin: Domain origin coordinates
|
|
73
|
+
origin_lat: Latitude for solar calculations (degrees)
|
|
74
|
+
origin_lon: Longitude for solar calculations (degrees)
|
|
75
|
+
"""
|
|
76
|
+
# Ensure Taichi is initialized before creating any fields
|
|
77
|
+
ensure_initialized()
|
|
78
|
+
|
|
79
|
+
self.nx = nx
|
|
80
|
+
self.ny = ny
|
|
81
|
+
self.nz = nz
|
|
82
|
+
self.dx = dx
|
|
83
|
+
self.dy = dy
|
|
84
|
+
self.dz = dz
|
|
85
|
+
self.origin = origin
|
|
86
|
+
self.origin_lat = origin_lat if origin_lat is not None else 0.0
|
|
87
|
+
self.origin_lon = origin_lon if origin_lon is not None else 0.0
|
|
88
|
+
|
|
89
|
+
# Domain bounds
|
|
90
|
+
self.x_min = origin[0]
|
|
91
|
+
self.x_max = origin[0] + nx * dx
|
|
92
|
+
self.y_min = origin[1]
|
|
93
|
+
self.y_max = origin[1] + ny * dy
|
|
94
|
+
self.z_min = origin[2]
|
|
95
|
+
self.z_max = origin[2] + nz * dz
|
|
96
|
+
|
|
97
|
+
# Grid cell volume
|
|
98
|
+
self.cell_volume = dx * dy * dz
|
|
99
|
+
|
|
100
|
+
# Topography: terrain height at each (i, j) column
|
|
101
|
+
self.topo_top = ti.field(dtype=ti.i32, shape=(nx, ny))
|
|
102
|
+
|
|
103
|
+
# Building mask: 1 if cell is solid (building), 0 if air
|
|
104
|
+
self.is_solid = ti.field(dtype=ti.i32, shape=(nx, ny, nz))
|
|
105
|
+
|
|
106
|
+
# Tree mask: 1 if cell is tree canopy, 0 otherwise
|
|
107
|
+
self.is_tree = ti.field(dtype=ti.i32, shape=(nx, ny, nz))
|
|
108
|
+
|
|
109
|
+
# Leaf Area Density (m^2/m^3) for plant canopy
|
|
110
|
+
self.lad = ti.field(dtype=ti.f32, shape=(nx, ny, nz))
|
|
111
|
+
|
|
112
|
+
# Plant canopy top index for each column
|
|
113
|
+
self.plant_top = ti.field(dtype=ti.i32, shape=(nx, ny))
|
|
114
|
+
|
|
115
|
+
# Surface count
|
|
116
|
+
self.n_surfaces = ti.field(dtype=ti.i32, shape=())
|
|
117
|
+
|
|
118
|
+
# Initialize arrays
|
|
119
|
+
self._init_arrays()
|
|
120
|
+
|
|
121
|
+
@ti.kernel
|
|
122
|
+
def _init_arrays(self):
|
|
123
|
+
"""Initialize all arrays to default values."""
|
|
124
|
+
for i, j in self.topo_top:
|
|
125
|
+
self.topo_top[i, j] = 0
|
|
126
|
+
self.plant_top[i, j] = 0
|
|
127
|
+
|
|
128
|
+
for i, j, k in self.is_solid:
|
|
129
|
+
self.is_solid[i, j, k] = 0
|
|
130
|
+
self.is_tree[i, j, k] = 0
|
|
131
|
+
self.lad[i, j, k] = 0.0
|
|
132
|
+
|
|
133
|
+
def set_flat_terrain(self, height: float = 0.0):
|
|
134
|
+
"""Set flat terrain at given height."""
|
|
135
|
+
k_top = int(height / self.dz)
|
|
136
|
+
self._set_flat_terrain_kernel(k_top)
|
|
137
|
+
|
|
138
|
+
def initialize_terrain(self, height: float = 0.0):
|
|
139
|
+
"""Alias for set_flat_terrain."""
|
|
140
|
+
self.set_flat_terrain(height)
|
|
141
|
+
|
|
142
|
+
@ti.kernel
|
|
143
|
+
def _set_flat_terrain_kernel(self, k_top: ti.i32):
|
|
144
|
+
for i, j in self.topo_top:
|
|
145
|
+
self.topo_top[i, j] = k_top
|
|
146
|
+
for k in range(k_top + 1):
|
|
147
|
+
self.is_solid[i, j, k] = 1
|
|
148
|
+
|
|
149
|
+
def set_terrain_from_array(self, terrain_height: np.ndarray):
|
|
150
|
+
"""
|
|
151
|
+
Set terrain from 2D numpy array of heights.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
terrain_height: 2D array (nx, ny) of terrain heights in meters
|
|
155
|
+
"""
|
|
156
|
+
terrain_k = (terrain_height / self.dz).astype(np.int32)
|
|
157
|
+
self._set_terrain_kernel(terrain_k)
|
|
158
|
+
|
|
159
|
+
@ti.kernel
|
|
160
|
+
def _set_terrain_kernel(self, terrain_k: ti.types.ndarray()):
|
|
161
|
+
for i, j in self.topo_top:
|
|
162
|
+
k_top = terrain_k[i, j]
|
|
163
|
+
self.topo_top[i, j] = k_top
|
|
164
|
+
for k in range(self.nz):
|
|
165
|
+
if k <= k_top:
|
|
166
|
+
self.is_solid[i, j, k] = 1
|
|
167
|
+
else:
|
|
168
|
+
self.is_solid[i, j, k] = 0
|
|
169
|
+
|
|
170
|
+
def add_building(
|
|
171
|
+
self,
|
|
172
|
+
x_range: Optional[Tuple[int, int]] = None,
|
|
173
|
+
y_range: Optional[Tuple[int, int]] = None,
|
|
174
|
+
z_range: Optional[Tuple[int, int]] = None,
|
|
175
|
+
*,
|
|
176
|
+
x_start: Optional[int] = None,
|
|
177
|
+
x_end: Optional[int] = None,
|
|
178
|
+
y_start: Optional[int] = None,
|
|
179
|
+
y_end: Optional[int] = None,
|
|
180
|
+
height: Optional[float] = None
|
|
181
|
+
):
|
|
182
|
+
"""
|
|
183
|
+
Add a rectangular building to the domain.
|
|
184
|
+
"""
|
|
185
|
+
# Handle convenience parameters
|
|
186
|
+
if x_start is not None and x_end is not None:
|
|
187
|
+
x_range = (x_start, x_end)
|
|
188
|
+
if y_start is not None and y_end is not None:
|
|
189
|
+
y_range = (y_start, y_end)
|
|
190
|
+
if height is not None and z_range is None:
|
|
191
|
+
k_top = int(height / self.dz) + 1
|
|
192
|
+
z_range = (0, k_top)
|
|
193
|
+
|
|
194
|
+
if x_range is None or y_range is None or z_range is None:
|
|
195
|
+
raise ValueError("Must provide either range tuples or individual parameters")
|
|
196
|
+
|
|
197
|
+
self._add_building_kernel(x_range[0], x_range[1], y_range[0], y_range[1], z_range[0], z_range[1])
|
|
198
|
+
|
|
199
|
+
@ti.kernel
|
|
200
|
+
def _add_building_kernel(self, i_min: ti.i32, i_max: ti.i32, j_min: ti.i32, j_max: ti.i32, k_min: ti.i32, k_max: ti.i32):
|
|
201
|
+
for i, j, k in ti.ndrange((i_min, i_max), (j_min, j_max), (k_min, k_max)):
|
|
202
|
+
self.is_solid[i, j, k] = 1
|
|
203
|
+
|
|
204
|
+
def add_tree(
|
|
205
|
+
self,
|
|
206
|
+
x_range: Tuple[int, int],
|
|
207
|
+
y_range: Tuple[int, int],
|
|
208
|
+
z_range: Tuple[int, int],
|
|
209
|
+
lad_value: float = 1.0
|
|
210
|
+
):
|
|
211
|
+
"""
|
|
212
|
+
Add a tree canopy region to the domain.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
x_range, y_range, z_range: Grid index ranges
|
|
216
|
+
lad_value: Leaf Area Density value
|
|
217
|
+
"""
|
|
218
|
+
self._add_tree_kernel(x_range[0], x_range[1], y_range[0], y_range[1], z_range[0], z_range[1], lad_value)
|
|
219
|
+
|
|
220
|
+
@ti.kernel
|
|
221
|
+
def _add_tree_kernel(self, i_min: ti.i32, i_max: ti.i32, j_min: ti.i32, j_max: ti.i32, k_min: ti.i32, k_max: ti.i32, lad: ti.f32):
|
|
222
|
+
for i, j, k in ti.ndrange((i_min, i_max), (j_min, j_max), (k_min, k_max)):
|
|
223
|
+
self.is_tree[i, j, k] = 1
|
|
224
|
+
self.lad[i, j, k] = lad
|
|
225
|
+
|
|
226
|
+
def set_from_voxel_data(self, voxel_data: np.ndarray, tree_code: int = -2, solid_codes: Optional[list] = None):
|
|
227
|
+
"""
|
|
228
|
+
Set domain from a 3D voxel data array.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
voxel_data: 3D numpy array with voxel class codes
|
|
232
|
+
tree_code: Class code for trees (default -2)
|
|
233
|
+
solid_codes: List of codes that are solid (default: all non-zero except tree_code)
|
|
234
|
+
"""
|
|
235
|
+
if solid_codes is None:
|
|
236
|
+
# All non-zero codes except tree are solid
|
|
237
|
+
solid_codes = []
|
|
238
|
+
|
|
239
|
+
self._set_from_voxel_data_kernel(voxel_data, tree_code)
|
|
240
|
+
|
|
241
|
+
@ti.kernel
|
|
242
|
+
def _set_from_voxel_data_kernel(self, voxel_data: ti.types.ndarray(), tree_code: ti.i32):
|
|
243
|
+
for i, j, k in ti.ndrange(self.nx, self.ny, self.nz):
|
|
244
|
+
val = voxel_data[i, j, k]
|
|
245
|
+
if val == tree_code:
|
|
246
|
+
self.is_tree[i, j, k] = 1
|
|
247
|
+
self.is_solid[i, j, k] = 0
|
|
248
|
+
elif val != 0:
|
|
249
|
+
self.is_solid[i, j, k] = 1
|
|
250
|
+
self.is_tree[i, j, k] = 0
|
|
251
|
+
else:
|
|
252
|
+
self.is_solid[i, j, k] = 0
|
|
253
|
+
self.is_tree[i, j, k] = 0
|
|
254
|
+
|
|
255
|
+
def get_max_dist(self) -> float:
|
|
256
|
+
"""Get maximum ray distance (domain diagonal)."""
|
|
257
|
+
import math
|
|
258
|
+
return math.sqrt(
|
|
259
|
+
(self.nx * self.dx)**2 +
|
|
260
|
+
(self.ny * self.dy)**2 +
|
|
261
|
+
(self.nz * self.dz)**2
|
|
262
|
+
)
|
|
@@ -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
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""VoxCity-style `integration` module (toplevel) for compatibility."""
|
|
2
|
+
|
|
3
|
+
from .solar.integration import (
|
|
4
|
+
get_global_solar_irradiance_using_epw,
|
|
5
|
+
get_building_global_solar_irradiance_using_epw,
|
|
6
|
+
save_irradiance_mesh,
|
|
7
|
+
load_irradiance_mesh,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"get_global_solar_irradiance_using_epw",
|
|
12
|
+
"get_building_global_solar_irradiance_using_epw",
|
|
13
|
+
"save_irradiance_mesh",
|
|
14
|
+
"load_irradiance_mesh",
|
|
15
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""VoxCity-style `kernels` module (toplevel) for compatibility.
|
|
2
|
+
|
|
3
|
+
Implements a minimal subset of `voxcity.simulator.solar.kernels` so code that
|
|
4
|
+
imports these symbols continues to run when using:
|
|
5
|
+
|
|
6
|
+
import simulator_gpu as simulator
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Tuple
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compute_direct_solar_irradiance_map_binary(
|
|
17
|
+
voxel_data,
|
|
18
|
+
sun_direction,
|
|
19
|
+
view_point_height,
|
|
20
|
+
hit_values,
|
|
21
|
+
meshsize,
|
|
22
|
+
tree_k,
|
|
23
|
+
tree_lad,
|
|
24
|
+
inclusion_mode,
|
|
25
|
+
):
|
|
26
|
+
"""Approximate VoxCity kernel: return direct-beam transmittance map (0..1).
|
|
27
|
+
|
|
28
|
+
Signature matches `voxcity.simulator.solar.kernels.compute_direct_solar_irradiance_map_binary`.
|
|
29
|
+
|
|
30
|
+
Notes:
|
|
31
|
+
- `hit_values` and `inclusion_mode` are accepted for compatibility but
|
|
32
|
+
are not currently used in the GPU path.
|
|
33
|
+
- Output is flipped with `np.flipud`, matching VoxCity.
|
|
34
|
+
"""
|
|
35
|
+
from .solar.integration import _compute_direct_transmittance_map_gpu
|
|
36
|
+
|
|
37
|
+
sd = np.array(sun_direction, dtype=np.float64)
|
|
38
|
+
L = float(np.sqrt((sd * sd).sum()))
|
|
39
|
+
if L == 0.0:
|
|
40
|
+
nx, ny = voxel_data.shape[0], voxel_data.shape[1]
|
|
41
|
+
return np.flipud(np.full((nx, ny), np.nan, dtype=np.float64))
|
|
42
|
+
sd /= L
|
|
43
|
+
|
|
44
|
+
trans = _compute_direct_transmittance_map_gpu(
|
|
45
|
+
voxel_data=np.asarray(voxel_data),
|
|
46
|
+
sun_direction=(float(sd[0]), float(sd[1]), float(sd[2])),
|
|
47
|
+
view_point_height=float(view_point_height),
|
|
48
|
+
meshsize=float(meshsize),
|
|
49
|
+
tree_k=float(tree_k),
|
|
50
|
+
tree_lad=float(tree_lad),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return np.flipud(trans)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ["compute_direct_solar_irradiance_map_binary"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""VoxCity-style `radiation` module (toplevel) for compatibility."""
|
|
2
|
+
|
|
3
|
+
from .solar.integration import (
|
|
4
|
+
get_direct_solar_irradiance_map,
|
|
5
|
+
get_diffuse_solar_irradiance_map,
|
|
6
|
+
get_global_solar_irradiance_map,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def compute_solar_irradiance_for_all_faces(*args, **kwargs):
|
|
11
|
+
"""Compatibility stub.
|
|
12
|
+
|
|
13
|
+
VoxCity's public API sometimes re-exports this symbol; if your workflow
|
|
14
|
+
depends on it, we can map it to a GPU equivalent (or implement it) once the
|
|
15
|
+
expected inputs/outputs are confirmed.
|
|
16
|
+
"""
|
|
17
|
+
raise NotImplementedError(
|
|
18
|
+
"compute_solar_irradiance_for_all_faces is not implemented in simulator_gpu yet. "
|
|
19
|
+
"Use get_*_solar_irradiance_map functions or open an issue with the expected signature."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"get_direct_solar_irradiance_map",
|
|
25
|
+
"get_diffuse_solar_irradiance_map",
|
|
26
|
+
"get_global_solar_irradiance_map",
|
|
27
|
+
"compute_solar_irradiance_for_all_faces",
|
|
28
|
+
]
|