Skip to content

Commit 4669bf7

Browse files
add Topology.partition_interfaces
1 parent 258ce7f commit 4669bf7

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

nutils/topology.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,35 @@ def interfaces_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology':
10491049

10501050
raise NotImplementedError
10511051

1052+
def partition_interfaces(self, part_indices: Sequence[int]) -> 'Topology':
1053+
'''Return the interfaces between all parts of an element partition.
1054+
1055+
Given a partition of elements, this function returns the subset of
1056+
interfaces that face two different parts. The orientation of the
1057+
interfaces is arbitrary.
1058+
1059+
Parameters
1060+
----------
1061+
part_indices : sequence or :class:`numpy.ndarray` of :class:`int`
1062+
For each element the index of the part the element belongs to.
1063+
1064+
Returns
1065+
-------
1066+
:class:`Topology`
1067+
The interfaces between all parts of the element partition, a subset
1068+
of :attr:`Topology.interfaces`.
1069+
'''
1070+
1071+
part_indices = function.Array.cast(part_indices)
1072+
if part_indices.dtype not in (bool, int):
1073+
raise ValueError(f'expected a sequence of integer part indices but got a sequence of type {part_indices.dtype}')
1074+
if part_indices.shape != (len(self),):
1075+
raise ValueError(f'expected a sequence of {len(self)} integer part indices but got an array with shape {part_indices.shape}')
1076+
interfaces = self.interfaces
1077+
part_indices = numpy.take(part_indices, self.f_index)
1078+
isnt_partition_interface = interfaces.elementwise_stack(part_indices == function.opposite(part_indices))
1079+
return interfaces.compress(~function.eval(isnt_partition_interface))
1080+
10521081
def basis_discont(self, degree: int) -> function.Basis:
10531082
'discontinuous shape functions'
10541083

tests/test_topology.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ def test_select(self):
174174
self.assertVertices(self.topo.select(((self.geom - center) * direction).sum()), desired_vertices)
175175
direction = numpy.roll(direction, shift=1)
176176

177+
def test_elementwise_stack(self):
178+
self.assertEqual(function.eval(self.topo.elementwise_stack(self.topo.f_index)).tolist(), list(range(len(self.topo))))
179+
177180

178181
class ConformingTests:
179182

@@ -456,8 +459,8 @@ class NewDisjointUnion(TestCase, CommonTests, ConformingTests):
456459

457460
def setUp(self):
458461
super().setUp()
459-
topo, self.geom = mesh.newrectilinear([8, 3], spaces='XY')
460-
self.topo = topology.Topology.disjoint_union(topo.slice(slice(0, 3), 0), topo.slice(slice(4, 8), 0).slice(slice(0, 2), 1))
462+
self.basetopo, self.geom = mesh.newrectilinear([8, 3], spaces='XY')
463+
self.topo = topology.Topology.disjoint_union(self.basetopo.slice(slice(0, 3), 0), self.basetopo.slice(slice(4, 8), 0).slice(slice(0, 2), 1))
461464
self.desired_spaces = 'X', 'Y'
462465
self.desired_space_dims = 1, 1
463466
self.desired_ndims = 2
@@ -501,6 +504,12 @@ def test_trim(self):
501504
self.assertEqual(as_rounded_list(topo.trim(x-2.5, maxrefine=0).volume(x[None])), 0.5)
502505
self.assertEqual(as_rounded_list(topo.trim(0.5-x, maxrefine=0).volume(x[None])), 0.5)
503506

507+
def test_elementwise_stack(self):
508+
self.assertEqual(
509+
function.eval(self.topo.elementwise_stack(self.basetopo.f_index)).tolist(),
510+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 15, 16, 18, 19, 21, 22],
511+
)
512+
504513

505514
class NewMul(TestCase, CommonTests, ConformingTests):
506515

@@ -611,6 +620,18 @@ def test_basis(self):
611620
with self.assertRaises(ValueError):
612621
self.topo.basis('spline', degree=1, knotmultiplicities=['a', 'b'])
613622

623+
def test_elementwise_stack_mul(self):
624+
desired = numpy.mgrid[:len(self.topo1), :len(self.topo2)].reshape(2, len(self.topo))
625+
stack = self.topo.elementwise_stack(numpy.stack([self.topo1.f_index, self.topo2.f_index]))
626+
self.assertEqual(function.eval(stack).tolist(), desired.T.tolist())
627+
stack = self.topo.elementwise_stack(numpy.stack([self.topo1.f_index, self.topo2.f_index]), axis=1)
628+
self.assertEqual(function.eval(stack).tolist(), desired.tolist())
629+
630+
def test_partition_interfaces(self):
631+
centers = self.topo.partition_interfaces([0, 0, 1, 2, 1, 1]).sample('gauss', 0).eval(self.geom).tolist()
632+
centers.sort()
633+
self.assertAllAlmostEqual(centers, [[0.5, 2.0], [1.0, 0.5], [1.0, 1.5], [1.5, 1.0]])
634+
614635

615636
class NewWithGroupAliases(TestCase, CommonTests, ConformingTests):
616637

@@ -1337,6 +1358,11 @@ def setUp(self):
13371358
self.desired_references = [element.LineReference()**2]*4
13381359
self.desired_vertices = [[[x, y] for x in X for y in Y] for X in pairwise(range(3)) for Y in pairwise(range(3))]
13391360

1361+
def test_partition_interfaces(self):
1362+
centers = self.topo.partition_interfaces([0, 0, 1, 2]).sample('gauss', 0).eval(self.geom).tolist()
1363+
centers.sort()
1364+
self.assertAllAlmostEqual(centers, [[1.0, 0.5], [1.0, 1.5], [1.5, 1.0]])
1365+
13401366

13411367
class UnionTopology(TestCase, CommonTests, TransformChainsTests):
13421368

0 commit comments

Comments
 (0)