Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance topoints( values, region, average=True, mean=False) #713

Merged
merged 2 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 54 additions & 30 deletions src/felupe/tools/_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from ..region import Region


def topoints(values, region):
def topoints(values, region, average=True, mean=False):
"""Shift array of values located at quadrature points of cells to mesh-points.

Parameters
Expand All @@ -44,6 +44,13 @@ def topoints(values, region):
Array with values located at the quadrature points ``q`` of cells ``c``.
region : Region
A region used to shift the values to the mesh-points.
average : bool, optional
A flag to return values averaged at mesh-points if True and the mesh of the
region is not already disconnected or to return values on a disconnected mesh
with discontinuous values at cell transitions if False (default is True).
mean : bool, optional
A flag to shift the cell-means of the values to the mesh-points (default is
False).

Returns
-------
Expand All @@ -52,10 +59,17 @@ def topoints(values, region):

Notes
-----
If the number of quadrature-points per cell is greater than the number of points-per-cell, then
the values are trimmed.
If the number of quadrature-points per cell is greater than the number of points-
per-cell, then the values are trimmed.
"""

if mean:
weights = region.quadrature.weights

# np.average(keepdims=True) requires numpy >= 1.23.0
values = np.average(values, axis=-2, weights=weights)
values = np.expand_dims(values, axis=-2)

shape = values.shape[:-2] # tensor-components
size = np.prod(shape).astype(int) # tensor-size (enforce int for empty tuple)
points_per_cell = region.mesh.cells.shape[1]
Expand All @@ -68,19 +82,35 @@ def topoints(values, region):
# trim the values to the number of points-per-cell
values = values[..., :points_per_cell, :]

axes = values.shape[-2:] # trailing axes (of broadcasted and trimmed values)

field = Field(region, dim=size)
field.values = field.values.reshape(-1, 1)
field.values = sparsematrix(
(values.reshape(-1, *axes).T.ravel(), field.indices.ai),
shape=field.indices.shape,
).toarray(out=field.values)
field.values = field.values.reshape(-1, *shape)
if average:
# trailing axes (of broadcasted and trimmed values)
axes = values.shape[-2:]

# create a field to output the values at mesh-points
field = Field(region, dim=size)

# field-values as column vector for result of sparse-matrix
field.values = field.values.reshape(-1, 1)
field.values = sparsematrix(
(values.reshape(-1, *axes).T.ravel(), field.indices.ai),
shape=field.indices.shape,
).toarray(out=field.values)

# reshape field-values to original shape
field.values = field.values.reshape(-1, *shape)

# divide the result by the number of cells per mesh-point
out = np.einsum(
"a...,a->a...",
field.values,
1 / region.mesh.cells_per_point,
out=field.values,
)
else:
out = np.einsum("...qc->cq...", values)
out = out.reshape(-1, *shape)

return np.einsum(
"a...,a->a...", field.values, 1 / region.mesh.cells_per_point, out=field.values
)
return out


def extrapolate(values, region, average=True, mean=False):
Expand Down Expand Up @@ -111,27 +141,21 @@ def extrapolate(values, region, average=True, mean=False):
size = int(np.prod(dim))
weights = region.quadrature.weights

# transpose values
idx = np.arange(len(values.shape))
idx[: len(dim)] = idx[: len(dim)][::-1]
values = values.transpose(idx)

if mean:
# evaluate how often the values must be repeated to match the number
# of element-points
reps = np.ones(len(values.shape), dtype=int)
reps[-2] = len(region.element.points)
weights = region.quadrature.weights

# np.average(keepdims=True) requires numpy >= 1.23.0
values = np.average(values, axis=-2, weights=weights)
values = np.expand_dims(values, axis=-2)

# workaround for np.average(keepdims=True)
shape = values.shape
shape = np.insert(shape, -1, 1)
values = values.reshape(*shape)
# broadcast mean values to number of points-per-cell
points_per_cell = region.mesh.cells.shape[1]
values = np.broadcast_to(values, (*dim, points_per_cell, values.shape[-1]))

# broadcast averaged values to match the number of element-points
values = np.broadcast_to(values, shape=np.broadcast_shapes(shape, reps))
# transpose values
idx = np.arange(len(values.shape))
idx[: len(dim)] = idx[: len(dim)][::-1]
values = values.transpose(idx)

# reshape from (shape, nint.points, nelements) to 1d-values
u = values.T.reshape(-1, size)
Expand Down
11 changes: 10 additions & 1 deletion tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ def test_topoints():
data = fem.topoints(values, region)
assert data.shape == (mesh.npoints, 3, 3)

mesh = fem.Rectangle(n=2).convert(2, 1)
mesh = fem.Rectangle(n=3).convert(2, 1)
region = fem.RegionQuadraticQuad(mesh)
field = fem.FieldAxisymmetric(region, dim=2)
values = field.extract()
Expand All @@ -402,6 +402,15 @@ def test_topoints():
data = fem.topoints(values, region)
assert data.shape == (mesh.npoints, 3, 3)

data = fem.topoints(values, region, average=False)
assert data.shape == (mesh.cells.size, 3, 3)

data = fem.topoints(values, region, mean=True)
assert data.shape == (mesh.npoints, 3, 3)

data = fem.topoints(values, region, average=False, mean=True)
assert data.shape == (mesh.cells.size, 3, 3)


def test_extrapolate():
# rectangle (triangle)
Expand Down
Loading