Skip to content

Commit 2a0355d

Browse files
WIP: partition
1 parent 87524d2 commit 2a0355d

File tree

3 files changed

+310
-12
lines changed

3 files changed

+310
-12
lines changed

nutils/topology.py

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

25012784
__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)