Skip to content

Commit e380dd9

Browse files
committed
Merge branch 'issue/improve-isoAM' into 'develop'
Resolve "isoAMの精度による誤差" Closes #21 See merge request ricos/machine_learning/graphlow!31
2 parents 29dd567 + 6efb5af commit e380dd9

2 files changed

Lines changed: 108 additions & 67 deletions

File tree

src/graphlow/processors/geometry_processor.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ def compute_area_vecs(self, mesh: IReadOnlyGraphlowMesh) -> torch.Tensor:
191191
pv.CellType.POLYGON: self._poly_area_vecs,
192192
}
193193
area_vecs = torch.empty(
194-
(mesh.n_cells, mesh.points.shape[1]), device=mesh.device
194+
(mesh.n_cells, mesh.points.shape[1]),
195+
device=mesh.device,
196+
dtype=mesh.dtype,
195197
)
196198
celltypes = mesh.pvmesh.celltypes
197199

@@ -267,7 +269,9 @@ def compute_volumes(
267269
pv.CellType.HEXAHEDRON: self._hex_volumes,
268270
pv.CellType.POLYHEDRON: self._poly_volumes,
269271
}
270-
volumes = torch.empty(mesh.n_cells, device=mesh.device)
272+
volumes = torch.empty(
273+
mesh.n_cells, device=mesh.device, dtype=mesh.dtype
274+
)
271275
celltypes = mesh.pvmesh.celltypes
272276

273277
# non-polyhedron cells
@@ -335,7 +339,9 @@ def _poly_area_vecs(
335339
self, points: torch.Tensor, polys: IReadOnlyGraphlowMesh
336340
) -> torch.Tensor:
337341
area_vecs = torch.empty(
338-
(polys.n_cells, points.shape[1]), device=points.device
342+
(polys.n_cells, points.shape[1]),
343+
device=points.device,
344+
dtype=points.dtype,
339345
)
340346
for i in range(polys.n_cells):
341347
cell = polys.pvmesh.get_cell(i)

src/graphlow/processors/isoAM_processor.py

Lines changed: 99 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,32 @@ def compute_isoAM(
6464
i_indices, j_indices = adj.indices() # (2, nnz)
6565
diag_mask = i_indices == j_indices
6666

67-
# Compute differences x_j - x_i
67+
# Compute differences: x_j - x_i
6868
diff = points[j_indices] - points[i_indices] # (nnz, dim)
6969

70-
# Compute squared norms ||x_j - x_i||^2
71-
norm_sq = torch.norm(diff, dim=1) ** 2 # (nnz,)
70+
# Compute squared norms: ||x_j - x_i||^2
71+
squared_norm = torch.norm(diff, dim=1) ** 2 # (nnz,)
7272

73-
# Compute (x_j - x_i) / ||x_j - x_i||^2
74-
p = diff / norm_sq.unsqueeze(1) # (nnz, dim)
75-
p[diag_mask] = 0.0
76-
if torch.isinf(p).any():
77-
raise ZeroDivisionError("Input mesh contains duplicate points")
78-
79-
# Compute weights
73+
# Compute weights: w_ij
8074
weights = torch.ones(
8175
i_indices.shape[0], device=points.device, dtype=points.dtype
8276
) # (nnz,)
8377

8478
if consider_volume:
8579
weights = self._compute_weights_nnz_from_volume(mesh)
8680

81+
# Compute weighted inverse of squared norms w_ij / ||x_j - x_i||^2
82+
weighted_inv_squarenorm = weights / squared_norm # (nnz,)
83+
weighted_inv_squarenorm[diag_mask] = 0.0
84+
if torch.isinf(weighted_inv_squarenorm).any():
85+
raise ZeroDivisionError("Input mesh contains duplicate points")
86+
87+
# Compute element tensor: w_ij (x_j - x_i) / ||x_j - x_i||^2
88+
element = diff * weighted_inv_squarenorm.unsqueeze(1) # (nnz, dim)
89+
8790
if not with_moment_matrix:
88-
nnz_tensor = p * weights.unsqueeze(1) # (nnz, dim)
8991
isoAM = self._create_grad_operator_from(
90-
i_indices, j_indices, n_points, nnz_tensor
92+
i_indices, j_indices, n_points, element
9193
)
9294
return isoAM, None
9395

@@ -111,13 +113,14 @@ def compute_isoAM(
111113
# Get M_i^{-1} for each edge (i,j)
112114
moment_inv_i = moment_inv[i_indices] # (nnz, dim, dim)
113115

114-
# Compute M_i^{-1} @ p for each edge
115-
temp = torch.bmm(moment_inv_i, p.unsqueeze(2)).squeeze(2) # (nnz, dim)
116+
# Compute element tensor: M_i^{-1} w_ij (x_j - x_i) / ||x_j - x_i||^2
117+
element_with_moment = torch.bmm(
118+
moment_inv_i, element.unsqueeze(2)
119+
).squeeze(2) # (nnz, dim)
116120

117-
# Compute D_{k,ij} = w_ij * (M_i^{-1} @ p)_k
118-
nnz_tensor = weights.unsqueeze(1) * temp # (nnz, dim)
121+
# Compute D_{k,ij}
119122
isoAM = self._create_grad_operator_from(
120-
i_indices, j_indices, n_points, nnz_tensor
123+
i_indices, j_indices, n_points, element_with_moment
121124
)
122125
return isoAM, moment_inv
123126

@@ -183,30 +186,32 @@ def compute_isoAM_with_neumann(
183186
1
184187
) # (n_points, dim, dim)
185188

186-
# Compute differences x_j - x_i
189+
# Compute differences: x_j - x_i
187190
diff = points[j_indices] - points[i_indices] # (nnz, dim)
188191

189-
# Compute squared norms ||x_j - x_i||^2
190-
norm_sq = torch.norm(diff, dim=1) ** 2 # (nnz,)
191-
192-
# Compute (x_j - x_i) / ||x_j - x_i||^2
193-
p = diff / norm_sq.unsqueeze(1) # (nnz, dim)
194-
p[diag_mask] = 0.0
195-
if torch.isinf(p).any():
196-
raise ZeroDivisionError("Input mesh contains duplicate points")
192+
# Compute squared norms: ||x_j - x_i||^2
193+
squared_norm = torch.norm(diff, dim=1) ** 2 # (nnz,)
197194

198-
# Compute weights
195+
# Compute weights: w_ij
199196
weights = torch.ones(
200197
i_indices.shape[0], device=points.device, dtype=points.dtype
201198
) # (nnz,)
202199

203200
if consider_volume:
204201
weights = self._compute_weights_nnz_from_volume(mesh)
205202

203+
# Compute weighted inverse of squared norms: w_ij / ||x_j - x_i||^2
204+
weighted_inv_squarenorm = weights / squared_norm # (nnz,)
205+
weighted_inv_squarenorm[diag_mask] = 0.0
206+
if torch.isinf(weighted_inv_squarenorm).any():
207+
raise ZeroDivisionError("Input mesh contains duplicate points")
208+
209+
# Compute element tensor: w_ij (x_j - x_i) / ||x_j - x_i||^2
210+
element = diff * weighted_inv_squarenorm.unsqueeze(1) # (nnz, dim)
211+
206212
if not with_moment_matrix:
207-
nnz_tensor = p * weights.unsqueeze(1) # (nnz, dim)
208213
isoAM = self._create_grad_operator_from(
209-
i_indices, j_indices, n_points, nnz_tensor
214+
i_indices, j_indices, n_points, element
210215
)
211216
return isoAM, weighted_normals, None
212217

@@ -221,13 +226,14 @@ def compute_isoAM_with_neumann(
221226
# Get M_i^{-1} for each edge (i,j)
222227
moment_inv_i = moment_inv[i_indices] # (nnz, dim, dim)
223228

224-
# Compute M_i^{-1} @ p for each edge
225-
temp = torch.bmm(moment_inv_i, p.unsqueeze(2)).squeeze(2) # (nnz, dim)
229+
# Compute element tensor: M_i^{-1} w_ij (x_j - x_i) / ||x_j - x_i||^2
230+
element_with_moment = torch.bmm(
231+
moment_inv_i, element.unsqueeze(2)
232+
).squeeze(2) # (nnz, dim)
226233

227-
# Compute D_{k,ij} = w_ij * (M_i^{-1} @ p)_k
228-
nnz_tensor = weights.unsqueeze(1) * temp # (nnz, dim)
234+
# Compute D_{k,ij}
229235
isoAM = self._create_grad_operator_from(
230-
i_indices, j_indices, n_points, nnz_tensor
236+
i_indices, j_indices, n_points, element_with_moment
231237
)
232238
return isoAM, weighted_normals, moment_inv
233239

@@ -243,45 +249,52 @@ def _compute_moment_matrix(
243249
244250
Parameters
245251
----------
246-
mesh : GraphlowMesh
247-
The mesh to compute the moment matrix for.
252+
i_indices: torch.Tensor
253+
The row indices of adjacency coo matrix. (nnz,)
254+
j_indices: torch.Tensor
255+
The column indices of adjacency coo matrix. (nnz,)
256+
points: torch.Tensor
257+
The points to compute the moment matrix for. (n_points, dim)
258+
weights: torch.Tensor
259+
The weights of the points. (nnz,)
248260
249261
Returns
250262
-------
251263
torch.Tensor
252264
(n_points, dim, dim)-shaped tensor sparse coo tensor
253265
"""
266+
n_points, dim = points.shape
254267
diag_mask = i_indices == j_indices
255268

256-
# Compute differences
269+
# Compute differences: x_j - x_i
257270
diff = points[j_indices] - points[i_indices] # (nnz, dim)
258271

259-
# Compute norms
260-
norms = torch.norm(diff, dim=1) # (nnz,)
272+
# Compute squared norms: ||x_j - x_i||^2
273+
squared_norm = torch.norm(diff, dim=1) ** 2 # (nnz,)
261274

262-
# Compute unit vectors
263-
u = diff / norms.unsqueeze(1) # (nnz, dim)
264-
u[diag_mask] = 0.0
265-
if torch.isinf(u).any():
275+
# Compute weighted inverse of squared norms: w_ij / ||x_j - x_i||^2
276+
weighted_inv_squarenorm = weights / squared_norm # (nnz,)
277+
weighted_inv_squarenorm[diag_mask] = 0.0
278+
if torch.isinf(weighted_inv_squarenorm).any():
266279
raise ZeroDivisionError("Input mesh contains duplicate points")
267280

268-
# Compute tensor products: (nnz, dim, dim)
269-
u_otimes_u = u.unsqueeze(2) * u.unsqueeze(1) # (nnz, dim, dim)
281+
# Compute tensor products: (x_j - x_i) \otimes (x_j - x_i)
282+
d_otimes_d = diff.unsqueeze(2) * diff.unsqueeze(1) # (nnz, dim, dim)
270283

271-
# Compute weighted tensor products: weights * u_otimes_u
272-
weighted_u_otimes_u = u_otimes_u * weights.unsqueeze(1).unsqueeze(
284+
# Compute weighted tensor products:
285+
# w_ij * (x_j - x_i) \otimes (x_j - x_i) / ||x_j - x_i||^2
286+
element = d_otimes_d * weighted_inv_squarenorm.unsqueeze(1).unsqueeze(
273287
2
274288
) # (nnz, dim, dim)
275289

276-
# Initialize M_i as (n_points, dim, dim)
277-
n_points, dim = points.shape
278-
M = torch.zeros(
279-
n_points, dim, dim, dtype=points.dtype, device=points.device
290+
# Initialize moment matrix as (n_points, dim, dim)
291+
moment_matrix = torch.zeros(
292+
n_points, dim, dim, dtype=diff.dtype, device=diff.device
280293
)
281294

282295
# Sum each row
283-
M.index_add_(0, i_indices, weighted_u_otimes_u)
284-
return M
296+
moment_matrix.index_add_(0, i_indices, element)
297+
return moment_matrix
285298

286299
def _compute_weights_nnz_from_volume(
287300
self, mesh: IReadOnlyGraphlowMesh
@@ -310,33 +323,40 @@ def _create_grad_operator_from(
310323
i_indices: torch.Tensor,
311324
j_indices: torch.Tensor,
312325
n_points: int,
313-
nnz_tensor: torch.Tensor,
326+
element: torch.Tensor,
314327
) -> torch.Tensor:
315328
"""Create a grad operator from a given tensor
316329
317330
Parameters
318331
----------
319-
nnz_tensor: (nnz, dim)-shaped torch tensor
332+
i_indices: torch.Tensor
333+
The row indices of adjacency coo matrix. (nnz,)
334+
j_indices: torch.Tensor
335+
The column indices of adjacency coo matrix. (nnz,)
336+
n_points: int
337+
The number of points.
338+
element: torch.Tensor
339+
The non-zero elements to create the grad operator from. (nnz, dim)
320340
321341
Returns
322342
-------
323343
(dim, n_points, n_points)-shaped torch sparse coo tensor
324344
"""
325-
dim = nnz_tensor.shape[1]
345+
dim = element.shape[1]
326346

327-
# Compute sum_D_per_i_k: (n_points, dim)
328-
sum_D_per_i_k = torch.zeros(
329-
n_points, dim, dtype=nnz_tensor.dtype, device=nnz_tensor.device
347+
# Compute \sum_l D_{k,il}: (n_points, dim)
348+
sum_by_row = torch.zeros(
349+
n_points, dim, dtype=element.dtype, device=element.device
330350
)
331-
sum_D_per_i_k.index_add_(0, i_indices, nnz_tensor)
351+
sum_by_row.index_add_(0, i_indices, element)
332352

333353
# Identify self-loop edges (i == j)
334-
diag_mask = i_indices == j_indices
354+
diag_mask = i_indices == j_indices # (nnz,)
335355

336-
# Adjust tilde_D by subtracting sum_D_per_i_k for self-loop edges
356+
# substract diagonal elements: D_{k,ij} - \delta_{ij} \sum_l D_{k,il}
337357
grad_adj = (
338-
nnz_tensor
339-
- diag_mask.float().unsqueeze(1) * sum_D_per_i_k[i_indices]
358+
element
359+
- diag_mask.to(element.dtype).unsqueeze(1) * sum_by_row[i_indices]
340360
)
341361

342362
# nnz, dim -> dim, n_points, n_points
@@ -361,6 +381,21 @@ def _compute_normals_on_surface_points(
361381
Parameters
362382
----------
363383
mesh: GraphlowMesh
384+
The mesh to compute the normals for.
385+
mode: Literal["mean", "conservative"], \
386+
default: "conservative" \
387+
The way to interpolate normals. cf. convert_elemental2nodal.
388+
- "mean": For each node, \
389+
we consider all the elements that share this node \
390+
and compute the average of their values.
391+
This approach provides \
392+
a smoothed representation at each node.
393+
- "conservative": For each element,
394+
we consider all the nodes that share this element \
395+
and distribute the element value to them equally.
396+
The values are then summed at each node. \
397+
This approach ensures that the total quantity \
398+
(such as mass or volume) is conserved.
364399
365400
Returns
366401
-------

0 commit comments

Comments
 (0)