Skip to content

Commit e1e208d

Browse files
WIP: partition
1 parent 87524d2 commit e1e208d

File tree

3 files changed

+311
-12
lines changed

3 files changed

+311
-12
lines changed

nutils/topology.py

Lines changed: 288 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def refine(self, n):
369369
n = n[0]
370370
return self if n <= 0 else self.refined.refine(n-1)
371371

372-
def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None, *, arguments=None):
372+
def _trim(self, levelset, maxrefine, ndivisions=8, leveltopo=None, *, arguments=None):
373373
'trim element along levelset'
374374

375375
if arguments is None:
@@ -403,7 +403,19 @@ def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None
403403
mask[indices] = False
404404
refs.append(ref.trim(levels, maxrefine=maxrefine, ndivisions=ndivisions))
405405
log.debug('cache', fcache.stats)
406-
return SubsetTopology(self, refs, newboundary=name)
406+
return refs
407+
408+
def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None, *, arguments=None):
409+
refs = self._trim(levelset, maxrefine, ndivisions, leveltopo, arguments=arguments)
410+
return SubsetTopology(self, refs, newboundary=name)
411+
412+
@log.withcontext
413+
@types.apply_annotations
414+
def partition(self, levelset:function.asarray, maxrefine:types.strictint, posname:types.strictstr, negname:types.strictstr, *, ndivisions=8, arguments=None):
415+
partsroot = function.Root('parts', 0)
416+
pos = self._trim(levelset, maxrefine=maxrefine, ndivisions=ndivisions, arguments=arguments)
417+
refs = tuple((pref, bref-pref) for bref, pref in zip(self.references, pos))
418+
return PartitionedTopology(self, partsroot, refs, (posname, negname))
407419

408420
def subset(self, topo, newboundary=None, strict=False):
409421
'intersection'
@@ -770,7 +782,9 @@ def interfaces(self):
770782
if isinstance(topo, Topology):
771783
# last minute orientation fix
772784
s = []
773-
for transs in zip(topo.transforms, topo.opposites):
785+
for ref, *transs in zip(topo.references, topo.transforms, topo.opposites):
786+
if not ref:
787+
continue
774788
for trans in transs:
775789
try:
776790
ielem = baseitopo.transforms.index(trans)
@@ -1819,7 +1833,7 @@ def basis(self, name, *args, **kwargs):
18191833
if name == 'discont':
18201834
return super().basis(name, *args, **kwargs)
18211835
else:
1822-
return function.DisjointUnionBasis(topo.basis(name, *args, **kwargs) for topo in self._topos)
1836+
return function.DisjointUnionBasis(tuple(topo.basis(name, *args, **kwargs) for topo in self._topos), function.SelectChain(self.roots))
18231837

18241838
class SubsetTopology(Topology):
18251839
'trimmed'
@@ -2006,6 +2020,26 @@ def getitem(self, item):
20062020
def boundary(self):
20072021
return self.basetopo.boundary.refined
20082022

2023+
@property
2024+
def interfaces(self):
2025+
references = []
2026+
transforms = []
2027+
opposites = []
2028+
for ref, trans in zip(self.basetopo.references, self.basetopo.transforms):
2029+
for ichild, (childconn, (ctrans, cref)) in enumerate(zip(ref.connectivity, ref.children)):
2030+
for iedge, (ioppchild, (etrans, eref)) in enumerate(zip(childconn, cref.edges)):
2031+
if ioppchild >= 0:
2032+
references.append(eref)
2033+
transforms.append(trans+(ctrans,etrans))
2034+
ioppedge = ref.connectivity[ioppchild].index(ichild)
2035+
oppctrans, oppcref = ref.children[ioppchild]
2036+
oppetrans = oppcref.edge_transforms[ioppedge]
2037+
opposites.append(trans+(oppctrans,oppetrans))
2038+
newifaces = Topology(elementseq.asreferences(references, self.ndims-1),
2039+
transformseq.PlainTransforms(transforms, self.ndims-1),
2040+
transformseq.PlainTransforms(opposites, self.ndims-1))
2041+
return DisjointUnionTopology([self.basetopo.interfaces.refined, newifaces])
2042+
20092043
@property
20102044
def connectivity(self):
20112045
offsets = numpy.cumsum([0] + [ref.nchildren for ref in self.basetopo.references])
@@ -2496,6 +2530,256 @@ def interfaces(self):
24962530
def getitem(self, item):
24972531
return WithIdentifierTopology(self._parent.getitem(item), self._root, self._identifier)
24982532

2533+
class PartitionedTopology(DisjointUnionTopology):
2534+
2535+
__slots__ = 'basetopo', 'refs', 'names', 'nparts', 'partsroot', '_parts', '_partstransforms', '_nrefined'
2536+
__cache__ = 'boundary', 'interfaces', 'refined'
2537+
2538+
@types.apply_annotations
2539+
def __init__(self, basetopo:stricttopology, partsroot:function.strictroot, refs:types.tuple[types.tuple[element.strictreference]], names:types.tuple[types.strictstr], *, _nrefined=0):
2540+
if len(refs) != len(basetopo):
2541+
raise ValueError('Expected {} refs tuples but got {}.'.format(len(basetopo), len(refs)))
2542+
self.nparts = len(refs[0]) if refs else len(names)
2543+
if not all(len(r) == self.nparts for r in refs):
2544+
raise ValueError('Variable number of parts.')
2545+
if len(names) != self.nparts:
2546+
raise ValueError('Expected {} names, one for every part, but got {}.'.format(self.nparts, len(names)))
2547+
if any(':' in name for name in names):
2548+
raise ValueError('Names may not contain colons.')
2549+
if self.nparts == 0:
2550+
raise ValueError('A partition consists of at least one part, but got zero.')
2551+
assert all(functools.reduce(operator.or_, prefs) == bref for bref, prefs in zip(basetopo.references, refs)), 'not a partition: union of parts is smaller than base'
2552+
2553+
self.basetopo = basetopo
2554+
self.refs = refs
2555+
self.names = names
2556+
self.partsroot = partsroot
2557+
self._nrefined = _nrefined
2558+
2559+
self._partstransforms = transformseq.IdentifierTransforms(0, partsroot.name, self.nparts)
2560+
for i in range(_nrefined):
2561+
self._partstransforms = self._partstransforms.refined(elementseq.asreferences([element.PointReference()], 0))
2562+
indices = tuple(types.frozenarray(numpy.where(list(map(bool, prefs)))[0]) for prefs in zip(*refs))
2563+
self._parts = tuple(WithIdentifierTopology(SubsetTopology(basetopo, prefs), partsroot, self._partstransforms[i:i+1]) for i, prefs in enumerate(zip(*refs)))
2564+
super().__init__(self._parts, names)
2565+
2566+
def getitem(self, item):
2567+
if item in self.names:
2568+
return _SubsetOfPartitionedTopology(self, {item})
2569+
else:
2570+
topo = self.basetopo.getitem(item)
2571+
if not topo:
2572+
return topo * EmptyTopology((self.partsroot,), 0)
2573+
refs = tuple(tuple(ref & bref for ref in self.refs[self.basetopo.transforms.index(trans)]) for bref, trans in zip(topo.references, topo.transforms))
2574+
return PartitionedTopology(topo, self.partsroot, refs, self.names)
2575+
2576+
@property
2577+
def boundary(self):
2578+
baseboundary = self.basetopo.boundary
2579+
brefs = []
2580+
for bref, btrans in zip(baseboundary.references, baseboundary.transforms):
2581+
ielem, etrans = self.basetopo.transforms.index_with_tail(btrans)
2582+
todims = tuple(t[-1].fromdims for t in self.basetopo.transforms[ielem])
2583+
brefs.append(tuple(pref.edge_refs[transform.index_edge_transforms(pref.edge_transforms, etrans, todims)] for pref in self.refs[ielem]))
2584+
return PartitionedTopology(baseboundary, self.partsroot, brefs, self.names, _nrefined=self._nrefined)
2585+
2586+
@property
2587+
def interfaces(self):
2588+
baseifaces = self.basetopo.interfaces
2589+
basereferences = {(a, b): [] for a in self.names for b in self.names}
2590+
baseindices = {(a, b): [] for a in self.names for b in self.names}
2591+
for ieelem, (eref, etrans, oppetrans) in enumerate(zip(baseifaces.references, baseifaces.transforms, baseifaces.opposites)):
2592+
ielem, tail = self.basetopo.transforms.index_with_tail(etrans)
2593+
ioppelem, opptail = self.basetopo.transforms.index_with_tail(oppetrans)
2594+
todims = tuple(t[-1].fromdims for t in self.basetopo.transforms[ielem])
2595+
erefs = tuple(filter(lambda item: item[1], ((i, ref.edge_refs[transform.index_edge_transforms(ref.edge_transforms, tail, todims)]) for i, ref in zip(self.names, self.refs[ielem]))))
2596+
opperefs = tuple(filter(lambda item: item[1], ((i, ref.edge_refs[transform.index_edge_transforms(ref.edge_transforms, opptail, todims)]) for i, ref in zip(self.names, self.refs[ioppelem]))))
2597+
checkeref = eref.empty
2598+
for aname, aeref in erefs:
2599+
for bname, beref in opperefs:
2600+
parteref = aeref & beref
2601+
if parteref:
2602+
basereferences[aname, bname].append(parteref)
2603+
baseindices[aname, bname].append(ieelem)
2604+
checkeref |= parteref
2605+
assert checkeref == eref
2606+
baseindices = {p: types.frozenarray(i, dtype=int) for p, i in baseindices.items()}
2607+
2608+
newedges = {}
2609+
def addnewedge(ielem, etrans):
2610+
edges = newedges.setdefault(ielem, [])
2611+
assert etrans not in edges
2612+
iedge = len(edges)
2613+
edges.append(etrans)
2614+
return ielem, iedge
2615+
newreferences = {(a, b): [] for i, a in enumerate(self.names) for b in self.names[i+1:]}
2616+
newtransforms = {(a, b): [] for i, a in enumerate(self.names) for b in self.names[i+1:]}
2617+
newopposites = {(a, b): [] for i, a in enumerate(self.names) for b in self.names[i+1:]}
2618+
for ibase, (baseref, partrefs, basetrans) in enumerate(zip(self.basetopo.references, self.refs, self.basetopo.transforms)):
2619+
todims = tuple(t[-1].fromdims for t in basetrans)
2620+
pool = {}
2621+
for aname, aref in zip(self.names, partrefs):
2622+
if not aref:
2623+
continue
2624+
for aetrans, aeref in aref.edges[baseref.nedges:]:
2625+
if not aeref:
2626+
continue
2627+
points = types.frozenarray(aetrans.apply(aeref.getpoints('bezier', 2).coords), copy=False)
2628+
bname, beref, betrans = pool.pop(points, (None, None, None))
2629+
if beref is None:
2630+
pool[points] = aname, aeref, aetrans
2631+
else:
2632+
assert aname != bname, 'elements are not supposed to count internal interfaces as edges'
2633+
# assert aeref == beref # disabled: aeref.trans is beref.trans.flipped if aeref is a ManifoldReference
2634+
if self.names.index(aname) <= self.names.index(bname):
2635+
iface = aname, bname
2636+
else:
2637+
iface = bname, aname
2638+
aetrans, betrans, aeref, beref = betrans, aetrans, beref, aeref
2639+
newreferences[iface].append(aeref)
2640+
newtransforms[iface].append(addnewedge(ibase, aetrans.separate(todims)))
2641+
newopposites[iface].append(addnewedge(ibase, betrans.separate(todims)))
2642+
2643+
assert not pool, 'some interal edges have no opposites'
2644+
2645+
if newedges:
2646+
newielems, newedges = zip(*sorted(newedges.items(), key=lambda item: item[0]))
2647+
newoffsets = dict(zip(newielems, numpy.cumsum([0, *map(len, newedges)])))
2648+
newedges = transformseq.TrimmedEdgesTransforms(self.basetopo.transforms[numpy.asarray(newielems)], newedges)
2649+
itopos = []
2650+
inames = []
2651+
T = lambda i, j: transformseq.PlainTransforms(((*self._partstransforms[i][0], self._partstransforms[j][0][0]),), 0, 0)
2652+
for i, a in enumerate(self.names):
2653+
itopos.append(Topology(self.roots,
2654+
elementseq.asreferences(basereferences[a, a], self.ndims-1),
2655+
baseifaces.transforms[baseindices[a, a]]*T(i, i),
2656+
baseifaces.opposites[baseindices[a, a]]*T(i, i)))
2657+
inames.append('{0}:{0}'.format(a))
2658+
for j, b in enumerate(self.names[i+1:], i+1):
2659+
base = Topology(self.roots,
2660+
elementseq.asreferences(basereferences[a, b] + basereferences[b, a], self.ndims-1),
2661+
transformseq.chain((baseifaces.transforms[baseindices[a, b]], baseifaces.opposites[baseindices[b, a]]), self.basetopo.transforms.todims)*T(i, j),
2662+
transformseq.chain((baseifaces.opposites[baseindices[a, b]], baseifaces.transforms[baseindices[b, a]]), self.basetopo.transforms.todims)*T(j, i))
2663+
if newreferences[a, b]:
2664+
newreferencesab = elementseq.asreferences(newreferences[a, b], self.ndims-1)
2665+
newtransformsab = newedges[numpy.fromiter((newoffsets[ielem]+iedge for ielem, iedge in newtransforms[a, b]), dtype=int)]
2666+
newoppositesab = newedges[numpy.fromiter((newoffsets[ielem]+iedge for ielem, iedge in newopposites[a, b]), dtype=int)]
2667+
new = Topology(self.roots, newreferencesab, newtransformsab*T(i, j), newoppositesab*T(j, i))
2668+
itopos.append(DisjointUnionTopology((base, new)))
2669+
else:
2670+
itopos.append(base)
2671+
inames.append('{}:{}'.format(a, b))
2672+
return DisjointUnionTopology(itopos, inames)
2673+
2674+
def __sub__(self, other):
2675+
if self == other:
2676+
return self.empty
2677+
elif isinstance(other, _SubsetOfPartitionedTopology) and other._partition == self:
2678+
remainder = frozenset(self.names) - frozenset(other._names)
2679+
if remainder:
2680+
return _SubsetOfPartitionedTopology(self, remainder)
2681+
else:
2682+
return self.empty
2683+
else:
2684+
return super().__sub__(other)
2685+
2686+
@property
2687+
def refined(self):
2688+
refbasetopo = self.basetopo.refined
2689+
refbindex = refbasetopo.transforms.index
2690+
refinedrefs = [crefs for refs in self.refs for crefs in zip(*(ref.child_refs for ref in refs))]
2691+
indices = numpy.argsort([refbindex(ctrans)
2692+
for trans, ref in zip(self.basetopo.transforms, self.references)
2693+
for ctrans in transform.child_transforms(trans, ref)])
2694+
refinedrefs = tuple(map(refinedrefs.__getitem__, indices))
2695+
return PartitionedTopology(refbasetopo, self.partsroot, refinedrefs, self.names, _nrefined=self._nrefined+1)
2696+
2697+
class _SubsetOfPartitionedTopology(DisjointUnionTopology):
2698+
2699+
__slots__ = '_partition', '_names'
2700+
__cache__ = 'boundary', 'interfaces'
2701+
2702+
@types.apply_annotations
2703+
def __init__(self, partition: stricttopology, names: frozenset):
2704+
self._partition = partition
2705+
if not names <= frozenset(partition.names):
2706+
raise ValueError('Not a subset of the partition.')
2707+
if not all(isinstance(name, str) for name in names):
2708+
raise ValueError('All names should be str objects.')
2709+
self._names = tuple(sorted(names, key=partition.names.index))
2710+
super().__init__(tuple(self._partition._parts[self._partition.names.index(name)] for name in self._names), self._names)
2711+
2712+
def __getitem__(self, item):
2713+
if item in self._names:
2714+
return _SubsetOfPartitionedTopology(self._partition, {item})
2715+
elif item in self._partition.names:
2716+
return self.empty
2717+
else:
2718+
topo = self._partition.getitem(item)
2719+
assert not isinstance(topo, _SubsetOfPartitionedTopology) # this is covered by the above two conditionals
2720+
if not topo:
2721+
return topo
2722+
elif isinstance(topo, PartitionedTopology):
2723+
return _SubsetOfPartitionedTopology(topo, self._names)
2724+
else:
2725+
raise NotImplementedError
2726+
2727+
@property
2728+
def boundary(self):
2729+
# The boundary of this subset consists of the boundary of the base that
2730+
# touches this subset and the interfaces between all parts in this subset
2731+
# and all parts not in this subset. All interfaces are grouped and named by
2732+
# the parts not in this subset: given a partition A, B of Ω, then
2733+
# `Ω['A'].boundary['B']` is the same as `Ω.interfaces['A:B']` or
2734+
# `~Ω.interfaces['B:A']`, whichever exists.
2735+
topos = []
2736+
names = []
2737+
for b in self._partition.names: # parts not in this subset
2738+
if b in self._names:
2739+
continue
2740+
btopos = []
2741+
for a in self._names: # parts in this subset
2742+
if self._partition.names.index(a) <= self._partition.names.index(b):
2743+
btopos.append(self._partition.interfaces.getitem('{}:{}'.format(a, b)))
2744+
else:
2745+
btopos.append(~self._partition.interfaces.getitem('{}:{}'.format(b, a)))
2746+
topos.append(DisjointUnionTopology(btopos))
2747+
names.append(b)
2748+
for name in self._names:
2749+
topos.append(self._partition.boundary.getitem(name))
2750+
groups = {}
2751+
return DisjointUnionTopology(topos, names)
2752+
2753+
@property
2754+
def interfaces(self):
2755+
topos = []
2756+
names = []
2757+
for i, a in enumerate(self._names):
2758+
for b in self._names[i:]:
2759+
topos.append(self._partition.interfaces.getitem('{}:{}'.format(a, b)))
2760+
names.append('{}:{}'.format(a, b))
2761+
return DisjointUnionTopology(topos, names)
2762+
2763+
def __or__(self, other):
2764+
if isinstance(other, _SubsetOfPartitionedTopology) and other._partition == self._partition:
2765+
return _SubsetOfPartitionedTopology(self._partition, frozenset(self._names) | frozenset(other._names))
2766+
else:
2767+
return super().__or__(other)
2768+
2769+
def __rsub__(self, other):
2770+
if self._partition == other or self._partition.basetopo == other:
2771+
remainder = frozenset(self._partition.names) - frozenset(self._names)
2772+
if remainder:
2773+
return _SubsetOfPartitionedTopology(self._partition, remainder)
2774+
else:
2775+
return self.empty
2776+
else:
2777+
return super().__rsub__(other)
2778+
2779+
@property
2780+
def refined(self):
2781+
return _SubsetOfPartitionedTopology(self._partition.refined, self._names)
2782+
24992783
class PatchBoundary(types.Singleton):
25002784

25012785
__slots__ = 'id', 'dim', 'side', 'reverse', 'transpose'

nutils/transform.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,10 @@ def __init__(self, ndims:types.strictint, trans:stricttransformitem):
635635
def flipped(self):
636636
return Manifold(self.fromdims, self.trans.flipped)
637637

638+
@property
639+
def isflipped(self):
640+
return self.trans.isflipped
641+
638642
def swapdown(self, other):
639643
if isinstance(other, (TensorChild, SimplexChild)):
640644
return ScaledUpdim(other, self), Identity(self.fromdims)

0 commit comments

Comments
 (0)