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

add Basis.discontinuous_at_partition_interfaces #839

Merged
merged 1 commit into from
Nov 29, 2023
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
61 changes: 61 additions & 0 deletions nutils/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import numbers
import inspect
import fractions
import treelog

IntoArray = Union['Array', numpy.ndarray, bool, int, float, complex]
Shape = Sequence[int]
Expand Down Expand Up @@ -2520,6 +2521,30 @@
else:
return super().__getitem__(index)

@treelog.withcontext
def discontinuous_at_partition_interfaces(self, part_indices: Sequence[int]):
'''Returns a basis that is discontinuous at element partition interfaces.

Given a partition of elements, this basis is made discontinuous at the
partition interfaces. All elements that have the same part index belong
to the same part.

The returned basis is formed by clipping each function of the basis to
each part individually and stacking all nonzero clipped functions. As a
consequence, if a basis function has support on three topologically
adjacent elements of which the first and the last element belong to one
part and the middle to another, this function will not be clipped to
each of the three elements individually, but to the first and the last
element and to the middle element.

Parameters
----------
part_indices : sequence or :class:`numpy.ndarray` of :class:`int`
For each element the index of the part the element belongs to.
'''

return _DiscontinuousPartitionBasis(self, part_indices)


class PlainBasis(Basis):
'''A general purpose implementation of a :class:`Basis`.
Expand Down Expand Up @@ -2776,6 +2801,42 @@
return dofs, p_coeffs


class _DiscontinuousPartitionBasis(Basis):

def __init__(self, parent: Basis, part_indices: Sequence[int]):
self._parent = parent

part_indices = numpy.array(part_indices).astype(int, casting='safe', copy=False)
if part_indices.shape != (parent.nelems,):
raise ValueError(f'expected a sequence of {self.nelems} integers but got an array with shape {part_indices.shape}')

Check warning on line 2811 in nutils/function.py

View check run for this annotation

Codecov / codecov/patch

nutils/function.py#L2811

Added line #L2811 was not covered by tests

# For each element we pair the parent dofs with the part indices and
# use that as partitioned dof. Then we renumber the partitioned dofs
# starting at 0, ordered by part index, then by parent dof.

ielem = evaluable.loop_index('ielem', parent.nelems)
parent_dofs, _ = parent.f_dofs_coeffs(ielem)
# Concatenate all parent dofs and stack all ndofs per element.
cc_parent_dofs = evaluable.loop_concatenate(parent_dofs, ielem)
cc_ndofs = evaluable.loop_concatenate(evaluable.insertaxis(parent_dofs.shape[0], 0, evaluable.asarray(1)), ielem)
cc_parent_dofs, cc_ndofs = evaluable.Tuple((cc_parent_dofs, cc_ndofs)).optimized_for_numpy.eval()
# Stack the part index for each element for each dof.
cc_part_indices = numpy.repeat(part_indices, cc_ndofs)
# Renumber and count all unique dofs.
unique_dofs, dofs = numpy.unique(numpy.stack([cc_part_indices, cc_parent_dofs], axis=1), axis=0, return_inverse=True)

self._dofs = evaluable.asarray(dofs)
self._ndofs = evaluable.asarray(cc_ndofs)
self._offsets = evaluable._SizesToOffsets(self._ndofs)

super().__init__(len(unique_dofs), parent.nelems, parent.index, parent.coords)

def f_dofs_coeffs(self, index):
dofs = evaluable.Take(self._dofs, evaluable.Range(evaluable.Take(self._ndofs, index)) + evaluable.Take(self._offsets, index))
_, coeffs = self._parent.f_dofs_coeffs(index)
return dofs, coeffs


def Namespace(*args, **kwargs):
from .expression_v1 import Namespace
return Namespace(*args, **kwargs)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,21 @@ def setUp(self):
super().setUp()


class DiscontinuousPartitionBasis(CommonBasis, TestCase):

def setUp(self):
self.checktransforms = transformseq.IndexTransforms(0, 4)
index, coords = self.mk_index_coords(0, self.checktransforms)
self.checkcoeffs = [[[1.], [2.], [3.], [4.]]] * 4
parent_dofs = [[0, 1, 3, 4], [1, 2, 4, 5], [3, 4, 6, 7], [4, 5, 7, 8]]
part_indices = [-1, 2, -1, 3]
self.basis = function.PlainBasis(self.checkcoeffs, parent_dofs, 9, index, coords).discontinuous_at_partition_interfaces(part_indices)
self.checkdofs = [[0, 1, 2, 3], [6, 7, 8, 9], [2, 3, 4, 5], [10, 11, 12, 13]]
self.checkndofs = 14
self.checkmasks = [[i in [0, 1, 7, 11] for i in range(self.checkndofs)]]
super().setUp()


@parametrize
class SurfaceGradient(TestCase):

Expand Down
Loading