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