@@ -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
18241838class 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+
24992782class PatchBoundary (types .Singleton ):
25002783
25012784 __slots__ = 'id' , 'dim' , 'side' , 'reverse' , 'transpose'
0 commit comments