@@ -391,7 +391,7 @@ def refine(self, n):
391391 n = n [0 ]
392392 return self if n <= 0 else self .refined .refine (n - 1 )
393393
394- def trim (self , levelset , maxrefine , ndivisions = 8 , name = 'trimmed' , leveltopo = None , * , arguments = None ):
394+ def _trim (self , levelset , maxrefine , ndivisions = 8 , leveltopo = None , * , arguments = None ):
395395 'trim element along levelset'
396396
397397 if arguments is None :
@@ -425,7 +425,19 @@ def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None
425425 mask [indices ] = False
426426 refs .append (ref .trim (levels , maxrefine = maxrefine , ndivisions = ndivisions ))
427427 log .debug ('cache' , fcache .stats )
428- return SubsetTopology (self , refs , newboundary = name )
428+ return refs
429+
430+ def trim (self , levelset , maxrefine , ndivisions = 8 , name = 'trimmed' , leveltopo = None , * , arguments = None ):
431+ refs = self ._trim (levelset , maxrefine , ndivisions , leveltopo , arguments = arguments )
432+ return SubsetTopology (self , refs , newboundary = name )
433+
434+ @log .withcontext
435+ @types .apply_annotations
436+ def partition (self , levelset :function .asarray , maxrefine :types .strictint , posname :types .strictstr , negname :types .strictstr , * , ndivisions = 8 , arguments = None ):
437+ partsroot = function .Root ('parts' , 0 )
438+ pos = self ._trim (levelset , maxrefine = maxrefine , ndivisions = ndivisions , arguments = arguments )
439+ refs = tuple ((pref , bref - pref ) for bref , pref in zip (self .references , pos ))
440+ return PartitionedTopology (self , partsroot , refs , (posname , negname ))
429441
430442 def subset (self , topo , newboundary = None , strict = False ):
431443 'intersection'
@@ -1883,7 +1895,7 @@ def basis(self, name, *args, **kwargs):
18831895 if name == 'discont' :
18841896 return super ().basis (name , * args , ** kwargs )
18851897 else :
1886- return function .DisjointUnionBasis (topo .basis (name , * args , ** kwargs ) for topo in self ._topos )
1898+ return function .DisjointUnionBasis (tuple ( topo .basis (name , * args , ** kwargs ) for topo in self ._topos ), function . SelectChain ( self . roots ) )
18871899
18881900class SubsetTopology (Topology ):
18891901 'trimmed'
@@ -2070,6 +2082,26 @@ def getitem(self, item):
20702082 def boundary (self ):
20712083 return self .basetopo .boundary .refined
20722084
2085+ @property
2086+ def interfaces (self ):
2087+ references = []
2088+ transforms = []
2089+ opposites = []
2090+ for ref , trans in zip (self .basetopo .references , self .basetopo .transforms ):
2091+ for ichild , (childconn , (ctrans , cref )) in enumerate (zip (ref .connectivity , ref .children )):
2092+ for iedge , (ioppchild , (etrans , eref )) in enumerate (zip (childconn , cref .edges )):
2093+ if ioppchild >= 0 :
2094+ references .append (eref )
2095+ transforms .append (trans + (ctrans ,etrans ))
2096+ ioppedge = ref .connectivity [ioppchild ].index (ichild )
2097+ oppctrans , oppcref = ref .children [ioppchild ]
2098+ oppetrans = oppcref .edge_transforms [ioppedge ]
2099+ opposites .append (trans + (oppctrans ,oppetrans ))
2100+ newifaces = Topology (elementseq .asreferences (references , self .ndims - 1 ),
2101+ transformseq .PlainTransforms (transforms , self .ndims - 1 ),
2102+ transformseq .PlainTransforms (opposites , self .ndims - 1 ))
2103+ return DisjointUnionTopology ([self .basetopo .interfaces .refined , newifaces ])
2104+
20732105 @property
20742106 def connectivity (self ):
20752107 offsets = numpy .cumsum ([0 ] + [ref .nchildren for ref in self .basetopo .references ])
@@ -2569,6 +2601,256 @@ def interfaces(self):
25692601 def getitem (self , item ):
25702602 return WithIdentifierTopology (self ._parent .getitem (item ), self ._root , self ._identifier )
25712603
2604+ class PartitionedTopology (DisjointUnionTopology ):
2605+
2606+ __slots__ = 'basetopo' , 'refs' , 'names' , 'nparts' , 'partsroot' , '_parts' , '_partstransforms' , '_nrefined'
2607+ __cache__ = 'boundary' , 'interfaces' , 'refined'
2608+
2609+ @types .apply_annotations
2610+ def __init__ (self , basetopo :stricttopology , partsroot :function .strictroot , refs :types .tuple [types .tuple [element .strictreference ]], names :types .tuple [types .strictstr ], * , _nrefined = 0 ):
2611+ if len (refs ) != len (basetopo ):
2612+ raise ValueError ('Expected {} refs tuples but got {}.' .format (len (basetopo ), len (refs )))
2613+ self .nparts = len (refs [0 ]) if refs else len (names )
2614+ if not all (len (r ) == self .nparts for r in refs ):
2615+ raise ValueError ('Variable number of parts.' )
2616+ if len (names ) != self .nparts :
2617+ raise ValueError ('Expected {} names, one for every part, but got {}.' .format (self .nparts , len (names )))
2618+ if any (':' in name for name in names ):
2619+ raise ValueError ('Names may not contain colons.' )
2620+ if self .nparts == 0 :
2621+ raise ValueError ('A partition consists of at least one part, but got zero.' )
2622+ 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'
2623+
2624+ self .basetopo = basetopo
2625+ self .refs = refs
2626+ self .names = names
2627+ self .partsroot = partsroot
2628+ self ._nrefined = _nrefined
2629+
2630+ self ._partstransforms = transformseq .IdentifierTransforms (0 , partsroot .name , self .nparts )
2631+ for i in range (_nrefined ):
2632+ self ._partstransforms = self ._partstransforms .refined (elementseq .asreferences ([element .PointReference ()], 0 ))
2633+ indices = tuple (types .frozenarray (numpy .where (list (map (bool , prefs )))[0 ]) for prefs in zip (* refs ))
2634+ self ._parts = tuple (WithIdentifierTopology (SubsetTopology (basetopo , prefs ), partsroot , self ._partstransforms [i :i + 1 ]) for i , prefs in enumerate (zip (* refs )))
2635+ super ().__init__ (self ._parts , names )
2636+
2637+ def getitem (self , item ):
2638+ if item in self .names :
2639+ return _SubsetOfPartitionedTopology (self , {item })
2640+ else :
2641+ topo = self .basetopo .getitem (item )
2642+ if not topo :
2643+ return topo * EmptyTopology ((self .partsroot ,), 0 )
2644+ refs = tuple (tuple (ref & bref for ref in self .refs [self .basetopo .transforms .index (trans )]) for bref , trans in zip (topo .references , topo .transforms ))
2645+ return PartitionedTopology (topo , self .partsroot , refs , self .names )
2646+
2647+ @property
2648+ def boundary (self ):
2649+ baseboundary = self .basetopo .boundary
2650+ brefs = []
2651+ for bref , btrans in zip (baseboundary .references , baseboundary .transforms ):
2652+ ielem , etrans = self .basetopo .transforms .index_with_tail (btrans )
2653+ todims = tuple (t [- 1 ].fromdims for t in self .basetopo .transforms [ielem ])
2654+ brefs .append (tuple (pref .edge_refs [transform .index_edge_transforms (pref .edge_transforms , etrans , todims )] for pref in self .refs [ielem ]))
2655+ return PartitionedTopology (baseboundary , self .partsroot , brefs , self .names , _nrefined = self ._nrefined )
2656+
2657+ @property
2658+ def interfaces (self ):
2659+ baseifaces = self .basetopo .interfaces
2660+ basereferences = {(a , b ): [] for a in self .names for b in self .names }
2661+ baseindices = {(a , b ): [] for a in self .names for b in self .names }
2662+ for ieelem , (eref , etrans , oppetrans ) in enumerate (zip (baseifaces .references , baseifaces .transforms , baseifaces .opposites )):
2663+ ielem , tail = self .basetopo .transforms .index_with_tail (etrans )
2664+ ioppelem , opptail = self .basetopo .transforms .index_with_tail (oppetrans )
2665+ todims = tuple (t [- 1 ].fromdims for t in self .basetopo .transforms [ielem ])
2666+ 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 ]))))
2667+ 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 ]))))
2668+ checkeref = eref .empty
2669+ for aname , aeref in erefs :
2670+ for bname , beref in opperefs :
2671+ parteref = aeref & beref
2672+ if parteref :
2673+ basereferences [aname , bname ].append (parteref )
2674+ baseindices [aname , bname ].append (ieelem )
2675+ checkeref |= parteref
2676+ assert checkeref == eref
2677+ baseindices = {p : types .frozenarray (i , dtype = int ) for p , i in baseindices .items ()}
2678+
2679+ newedges = {}
2680+ def addnewedge (ielem , etrans ):
2681+ edges = newedges .setdefault (ielem , [])
2682+ assert etrans not in edges
2683+ iedge = len (edges )
2684+ edges .append (etrans )
2685+ return ielem , iedge
2686+ newreferences = {(a , b ): [] for i , a in enumerate (self .names ) for b in self .names [i + 1 :]}
2687+ newtransforms = {(a , b ): [] for i , a in enumerate (self .names ) for b in self .names [i + 1 :]}
2688+ newopposites = {(a , b ): [] for i , a in enumerate (self .names ) for b in self .names [i + 1 :]}
2689+ for ibase , (baseref , partrefs , basetrans ) in enumerate (zip (self .basetopo .references , self .refs , self .basetopo .transforms )):
2690+ todims = tuple (t [- 1 ].fromdims for t in basetrans )
2691+ pool = {}
2692+ for aname , aref in zip (self .names , partrefs ):
2693+ if not aref :
2694+ continue
2695+ for aetrans , aeref in aref .edges [baseref .nedges :]:
2696+ if not aeref :
2697+ continue
2698+ points = types .frozenarray (aetrans .apply (aeref .getpoints ('bezier' , 2 ).coords ), copy = False )
2699+ bname , beref , betrans = pool .pop (points , (None , None , None ))
2700+ if beref is None :
2701+ pool [points ] = aname , aeref , aetrans
2702+ else :
2703+ assert aname != bname , 'elements are not supposed to count internal interfaces as edges'
2704+ # assert aeref == beref # disabled: aeref.trans is beref.trans.flipped if aeref is a ManifoldReference
2705+ if self .names .index (aname ) <= self .names .index (bname ):
2706+ iface = aname , bname
2707+ else :
2708+ iface = bname , aname
2709+ aetrans , betrans , aeref , beref = betrans , aetrans , beref , aeref
2710+ newreferences [iface ].append (aeref )
2711+ newtransforms [iface ].append (addnewedge (ibase , aetrans .separate (todims )))
2712+ newopposites [iface ].append (addnewedge (ibase , betrans .separate (todims )))
2713+
2714+ assert not pool , 'some interal edges have no opposites'
2715+
2716+ if newedges :
2717+ newielems , newedges = zip (* sorted (newedges .items (), key = lambda item : item [0 ]))
2718+ newoffsets = dict (zip (newielems , numpy .cumsum ([0 , * map (len , newedges )])))
2719+ newedges = transformseq .TrimmedEdgesTransforms (self .basetopo .transforms [numpy .asarray (newielems )], newedges )
2720+ itopos = []
2721+ inames = []
2722+ T = lambda i , j : transformseq .PlainTransforms (((* self ._partstransforms [i ][0 ], self ._partstransforms [j ][0 ][0 ]),), 0 , 0 )
2723+ for i , a in enumerate (self .names ):
2724+ itopos .append (Topology (self .roots ,
2725+ elementseq .asreferences (basereferences [a , a ], self .ndims - 1 ),
2726+ baseifaces .transforms [baseindices [a , a ]]* T (i , i ),
2727+ baseifaces .opposites [baseindices [a , a ]]* T (i , i )))
2728+ inames .append ('{0}:{0}' .format (a ))
2729+ for j , b in enumerate (self .names [i + 1 :], i + 1 ):
2730+ base = Topology (self .roots ,
2731+ elementseq .asreferences (basereferences [a , b ] + basereferences [b , a ], self .ndims - 1 ),
2732+ transformseq .chain ((baseifaces .transforms [baseindices [a , b ]], baseifaces .opposites [baseindices [b , a ]]), self .basetopo .transforms .todims )* T (i , j ),
2733+ transformseq .chain ((baseifaces .opposites [baseindices [a , b ]], baseifaces .transforms [baseindices [b , a ]]), self .basetopo .transforms .todims )* T (j , i ))
2734+ if newreferences [a , b ]:
2735+ newreferencesab = elementseq .asreferences (newreferences [a , b ], self .ndims - 1 )
2736+ newtransformsab = newedges [numpy .fromiter ((newoffsets [ielem ]+ iedge for ielem , iedge in newtransforms [a , b ]), dtype = int )]
2737+ newoppositesab = newedges [numpy .fromiter ((newoffsets [ielem ]+ iedge for ielem , iedge in newopposites [a , b ]), dtype = int )]
2738+ new = Topology (self .roots , newreferencesab , newtransformsab * T (i , j ), newoppositesab * T (j , i ))
2739+ itopos .append (DisjointUnionTopology ((base , new )))
2740+ else :
2741+ itopos .append (base )
2742+ inames .append ('{}:{}' .format (a , b ))
2743+ return DisjointUnionTopology (itopos , inames )
2744+
2745+ def __sub__ (self , other ):
2746+ if self == other :
2747+ return self .empty
2748+ elif isinstance (other , _SubsetOfPartitionedTopology ) and other ._partition == self :
2749+ remainder = frozenset (self .names ) - frozenset (other ._names )
2750+ if remainder :
2751+ return _SubsetOfPartitionedTopology (self , remainder )
2752+ else :
2753+ return self .empty
2754+ else :
2755+ return super ().__sub__ (other )
2756+
2757+ @property
2758+ def refined (self ):
2759+ refbasetopo = self .basetopo .refined
2760+ refbindex = refbasetopo .transforms .index
2761+ refinedrefs = [crefs for refs in self .refs for crefs in zip (* (ref .child_refs for ref in refs ))]
2762+ indices = numpy .argsort ([refbindex (ctrans )
2763+ for trans , ref in zip (self .basetopo .transforms , self .references )
2764+ for ctrans in transform .child_transforms (trans , ref )])
2765+ refinedrefs = tuple (map (refinedrefs .__getitem__ , indices ))
2766+ return PartitionedTopology (refbasetopo , self .partsroot , refinedrefs , self .names , _nrefined = self ._nrefined + 1 )
2767+
2768+ class _SubsetOfPartitionedTopology (DisjointUnionTopology ):
2769+
2770+ __slots__ = '_partition' , '_names'
2771+ __cache__ = 'boundary' , 'interfaces'
2772+
2773+ @types .apply_annotations
2774+ def __init__ (self , partition : stricttopology , names : frozenset ):
2775+ self ._partition = partition
2776+ if not names <= frozenset (partition .names ):
2777+ raise ValueError ('Not a subset of the partition.' )
2778+ if not all (isinstance (name , str ) for name in names ):
2779+ raise ValueError ('All names should be str objects.' )
2780+ self ._names = tuple (sorted (names , key = partition .names .index ))
2781+ super ().__init__ (tuple (self ._partition ._parts [self ._partition .names .index (name )] for name in self ._names ), self ._names )
2782+
2783+ def __getitem__ (self , item ):
2784+ if item in self ._names :
2785+ return _SubsetOfPartitionedTopology (self ._partition , {item })
2786+ elif item in self ._partition .names :
2787+ return self .empty
2788+ else :
2789+ topo = self ._partition .getitem (item )
2790+ assert not isinstance (topo , _SubsetOfPartitionedTopology ) # this is covered by the above two conditionals
2791+ if not topo :
2792+ return topo
2793+ elif isinstance (topo , PartitionedTopology ):
2794+ return _SubsetOfPartitionedTopology (topo , self ._names )
2795+ else :
2796+ raise NotImplementedError
2797+
2798+ @property
2799+ def boundary (self ):
2800+ # The boundary of this subset consists of the boundary of the base that
2801+ # touches this subset and the interfaces between all parts in this subset
2802+ # and all parts not in this subset. All interfaces are grouped and named by
2803+ # the parts not in this subset: given a partition A, B of Ω, then
2804+ # `Ω['A'].boundary['B']` is the same as `Ω.interfaces['A:B']` or
2805+ # `~Ω.interfaces['B:A']`, whichever exists.
2806+ topos = []
2807+ names = []
2808+ for b in self ._partition .names : # parts not in this subset
2809+ if b in self ._names :
2810+ continue
2811+ btopos = []
2812+ for a in self ._names : # parts in this subset
2813+ if self ._partition .names .index (a ) <= self ._partition .names .index (b ):
2814+ btopos .append (self ._partition .interfaces .getitem ('{}:{}' .format (a , b )))
2815+ else :
2816+ btopos .append (~ self ._partition .interfaces .getitem ('{}:{}' .format (b , a )))
2817+ topos .append (DisjointUnionTopology (btopos ))
2818+ names .append (b )
2819+ for name in self ._names :
2820+ topos .append (self ._partition .boundary .getitem (name ))
2821+ groups = {}
2822+ return DisjointUnionTopology (topos , names )
2823+
2824+ @property
2825+ def interfaces (self ):
2826+ topos = []
2827+ names = []
2828+ for i , a in enumerate (self ._names ):
2829+ for b in self ._names [i :]:
2830+ topos .append (self ._partition .interfaces .getitem ('{}:{}' .format (a , b )))
2831+ names .append ('{}:{}' .format (a , b ))
2832+ return DisjointUnionTopology (topos , names )
2833+
2834+ def __or__ (self , other ):
2835+ if isinstance (other , _SubsetOfPartitionedTopology ) and other ._partition == self ._partition :
2836+ return _SubsetOfPartitionedTopology (self ._partition , frozenset (self ._names ) | frozenset (other ._names ))
2837+ else :
2838+ return super ().__or__ (other )
2839+
2840+ def __rsub__ (self , other ):
2841+ if self ._partition == other or self ._partition .basetopo == other :
2842+ remainder = frozenset (self ._partition .names ) - frozenset (self ._names )
2843+ if remainder :
2844+ return _SubsetOfPartitionedTopology (self ._partition , remainder )
2845+ else :
2846+ return self .empty
2847+ else :
2848+ return super ().__rsub__ (other )
2849+
2850+ @property
2851+ def refined (self ):
2852+ return _SubsetOfPartitionedTopology (self ._partition .refined , self ._names )
2853+
25722854class PatchBoundary (types .Singleton ):
25732855
25742856 __slots__ = 'id' , 'dim' , 'side' , 'reverse' , 'transpose'
0 commit comments