reduce_on_subface#

iskra.topology.reduce_on_subface(data: Tensor, faces: Tensor, n_subfaces: int, reduce: Literal['sum', 'prod', 'mean', 'amax', 'amin'], data_ndim: int | None = None, batch_ndim: int = 0) Tensor[SOURCE]#

Scatter-reduce data from faces onto constitutive subfaces.

This function distributes, i.e., scatters, data (scalar, vector, tensor, etc.) defined on faces to each face’s subfaces. A naive scatter would result in each subface receiving multiple competing values, one for each face that contains it. Therefore, each operation is actually a scatter-reduce (see reduce argument). So, e.g., a triangle face can scatter its value to its 3 edges. That value is then, e.g., averaged onto each edge with the values of the other triangles that share that same edge.

The function slices up the data tensor into three parts: [Bs, Fs, Ds], where Bs is the batch shape, Ds are the data payload dimensions; Fs represents the “domain” of the data. Most commonly Fs is either [F] or [F, FS]. For example, on a triangle mesh, Fs = [F] implies one data payload per triangle, whereas Fs = [F, 3] implies one data payload per triangle-corner or triangle-side.

Data dimensions are greedy when data_ndim is None: we assume Fs = [F] and that everything to the right of Fs is the data payload. E.g., if data.shape = [B, F, 3, 3] and Fs = [F], we have one data payload per face, whereas if Fs = [F, 3], we have one data payload per triangle-corner.

Caution

The function should generalize to nested subfaces indices, i.e., Fs=[F, FSs]. For example, it should handle data defined on a tetrahedron’s side-triangles’ corners (Fs=[Tets, 3, 3]), but this is untested! If you have a real-world example of data stored on subfaces of subfaces, let me know and I can look into it!

Tip

This function is one of the three building blocks of iskra’s scatter-gather framework, together with iskra.topology.get_subfaces() which constructs the face hierarchy and iskra.topology.face_index(), which performs the gather operation.

This function moves data down the face hierarchy.

See Scatter-Gather Dataflow in iskra ✨ for an explanation of iskra’s tensor-based scatter-gather framework.

Parameters:
  • data (Tensor[DType, [Bs, Fs, Ds]]) – Data defined on mesh faces (Fs=[F]) or face-subfaces (Fs=[F, FS]).

  • faces (Tensor[Int64, [Bs, F, FS]]) – Face-subface indices.

  • n_subfaces (int) – Total number of subfaces in the mesh (e.g., total number of vertices or edges in a triangle mesh).

  • reduce (Literal["sum", "prod", "mean", "amax", "amin"]) – Reduction operation, see torch.scatter_reduce() for more details.

  • data_ndim (int | None) – Num. dimensions of the per-face (or per-face-subface) payload. See above for more details on default behavior.

  • batch_ndim (int) – Num. batch dimensions.

Returns:
  • (Tensor[DType, [Bs, S, Ds]]) – Data reduced onto the S subfaces,

  • ​​ (where :abbr:`S (Number of subfaces in the mesh. Usually S is documented together with some F as point of reference. E.g., if F is the number of triangles, S could be the number of edges or vertices in the mesh.)` equals the value of the argument n_subfaces.)

Example

data.shape

faces.shape

data_ndim

result.shape

Example: from → to

[B, F]

[B, F, 3]

0

[B, V]

face scalars → vertices

[B, F, 3]

[B, F, 3]

0

[B, V]

corner scalars → vertices

[B, F, 2]

[B, F, 3]

1 | None

[B, V, 2]

face 2-vectors → vertices

[B, F, 3, 2]

[B, F, 3]

1

[B, V, 2]

corner 2-vectors → vertices

[B, F, 3]

[B, F, 3]

1 | None

[B, V, 3]

face normals → vertices

[B, F, 3, 3]

[B, F, 3]

1

[B, V, 3]

corner normals → vertices

[B, F, 3, 3]

[B, F, 3]

2 | None

[B, V, 3, 3]

face covariances → vertices