Skip to content

Commit 45f27b3

Browse files
add Topology.partition_interfaces
1 parent ffe0dee commit 45f27b3

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
@@ -977,6 +977,35 @@ def interfaces_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology':
977977

978978
raise NotImplementedError
979979

980+
def partition_interfaces(self, part_indices: Sequence[int]) -> 'Topology':
981+
'''Return the interfaces between all parts of an element partition.
982+
983+
Given a partition of elements, this function returns the subset of
984+
interfaces that face two different parts. The orientation of the
985+
interfaces is arbitrary.
986+
987+
Parameters
988+
----------
989+
part_indices : sequence or :class:`numpy.ndarray` of :class:`int`
990+
For each element the index of the part the element belongs to.
991+
992+
Returns
993+
-------
994+
:class:`Topology`
995+
The interfaces between all parts of the element partition, a subset
996+
of :attr:`Topology.interfaces`.
997+
'''
998+
999+
part_indices = function.Array.cast(part_indices)
1000+
if part_indices.dtype not in (bool, int):
1001+
raise ValueError(f'expected a sequence of integer part indices but got a sequence of type {part_indices.dtype}')
1002+
if part_indices.shape != (len(self),):
1003+
raise ValueError(f'expected a sequence of {len(self)} integer part indices but got an array with shape {part_indices.shape}')
1004+
interfaces = self.interfaces
1005+
part_indices = numpy.take(part_indices, self.f_index)
1006+
isnt_partition_interface = interfaces.elementwise_stack(part_indices == function.opposite(part_indices))
1007+
return interfaces.compress(~function.eval(isnt_partition_interface))
1008+
9801009
def basis_discont(self, degree: int) -> function.Basis:
9811010
'discontinuous shape functions'
9821011

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

@@ -1280,6 +1301,11 @@ def setUp(self):
12801301
self.desired_references = [element.LineReference()**2]*4
12811302
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))]
12821303

1304+
def test_partition_interfaces(self):
1305+
centers = self.topo.partition_interfaces([0, 0, 1, 2]).sample('gauss', 0).eval(self.geom).tolist()
1306+
centers.sort()
1307+
self.assertAllAlmostEqual(centers, [[1.0, 0.5], [1.0, 1.5], [1.5, 1.0]])
1308+
12831309

12841310
class UnionTopology(TestCase, CommonTests, TransformChainsTests):
12851311

0 commit comments

Comments
 (0)