@@ -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