@@ -334,7 +334,7 @@ def refine(self, n):
334334 n = n [0 ]
335335 return self if n <= 0 else self .refined .refine (n - 1 )
336336
337- def trim (self , levelset , maxrefine , ndivisions = 8 , name = 'trimmed' , leveltopo = None , * , arguments = None ):
337+ def _trim (self , levelset , maxrefine , ndivisions = 8 , leveltopo = None , * , arguments = None ):
338338 'trim element along levelset'
339339
340340 if arguments is None :
@@ -367,7 +367,18 @@ def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None
367367 mask [indices ] = False
368368 refs .append (ref .trim (levels , maxrefine = maxrefine , ndivisions = ndivisions ))
369369 log .debug ('cache' , fcache .stats )
370- return SubsetTopology (self , refs , newboundary = name )
370+ return refs
371+
372+ def trim (self , levelset , maxrefine , ndivisions = 8 , name = 'trimmed' , leveltopo = None , * , arguments = None ):
373+ refs = self ._trim (levelset , maxrefine , ndivisions , leveltopo , arguments = arguments )
374+ return SubsetTopology (self , refs , newboundary = name )
375+
376+ @log .withcontext
377+ @types .apply_annotations
378+ def partition (self , levelset :function .asarray , maxrefine :types .strictint , posname :types .strictstr , negname :types .strictstr , * , ndivisions = 8 , arguments = None ):
379+ pos = self ._trim (levelset , maxrefine = maxrefine , ndivisions = ndivisions , arguments = arguments )
380+ refs = tuple ((pref , bref - pref ) for bref , pref in zip (self .references , pos ))
381+ return PartitionedTopology (self , refs , (posname , negname ))
371382
372383 def subset (self , topo , newboundary = None , strict = False ):
373384 'intersection'
@@ -2024,6 +2035,214 @@ def interfaces(self):
20242035 def getitem (self , item ):
20252036 return WithIdentifierTopology (self ._parent .getitem (item ), self ._token )
20262037
2038+ class PartitionedTopology (DisjointUnionTopology ):
2039+
2040+ __slots__ = 'basetopo' , 'refs' , 'names' , 'nparts' , '_parts'
2041+ __cache__ = 'boundary' , 'interfaces'
2042+
2043+ @types .apply_annotations
2044+ def __init__ (self , basetopo :stricttopology , refs :types .tuple [types .tuple [element .strictreference ]], names :types .tuple [types .strictstr ]):
2045+ if len (refs ) != len (basetopo ):
2046+ raise ValueError ('Expected {} refs tuples but got {}.' .format (len (basetopo ), len (refs )))
2047+ self .nparts = len (refs [0 ]) if refs else len (names )
2048+ if not all (len (r ) == self .nparts for r in refs ):
2049+ raise ValueError ('Variable number of parts.' )
2050+ if len (names ) != self .nparts :
2051+ raise ValueError ('Expected {} names, one for every part, but got {}.' .format (self .nparts , len (names )))
2052+ if any (':' in name for name in names ):
2053+ raise ValueError ('Names may not contain colons.' )
2054+ if self .nparts == 0 :
2055+ raise ValueError ('A partition consists of at least one part, but got zero.' )
2056+ assert all (functools .reduce (operator .or_ , prefs ) == bref for bref , prefs in zip (basetopo .references , refs )), 'not a partition: union of parts is smaller then base'
2057+
2058+ self .basetopo = basetopo
2059+ self .refs = refs
2060+ self .names = names
2061+
2062+ indices = tuple (types .frozenarray (numpy .where (list (map (bool , prefs )))[0 ]) for prefs in zip (* refs ))
2063+ self ._parts = tuple (WithIdentifierTopology (SubsetTopology (basetopo , prefs ), name ) for name , prefs in zip (names , zip (* refs )))
2064+ super ().__init__ (self ._parts , names )
2065+
2066+ def getitem (self , item ):
2067+ if item in self .names :
2068+ return _SubsetOfPartitionedTopology (self , {item })
2069+ else :
2070+ topo = self .basetopo .getitem (item )
2071+ refs = tuple (tuple (ref & bref for ref in self .refs [self .basetopo .transforms .index (trans )]) for bref , trans in zip (topo .references , topo .transforms ))
2072+ return PartitionedTopology (topo , refs , self .names )
2073+
2074+ @property
2075+ def boundary (self ):
2076+ baseboundary = self .basetopo .boundary
2077+ brefs = []
2078+ for bref , btrans in zip (baseboundary .references , baseboundary .transforms ):
2079+ ielem , etrans = self .basetopo .transforms .index_with_tail (btrans )
2080+ brefs .append (tuple (pref .get_from_trans (etrans ) for pref in self .refs [ielem ]))
2081+ return PartitionedTopology (baseboundary , brefs , self .names )
2082+
2083+ @property
2084+ def interfaces (self ):
2085+ baseifaces = self .basetopo .interfaces
2086+ basereferences = {(a , b ): [] for a in self .names for b in self .names }
2087+ baseindices = {(a , b ): [] for a in self .names for b in self .names }
2088+ for ieelem , (eref , etrans , oppetrans ) in enumerate (zip (baseifaces .references , baseifaces .transforms , baseifaces .opposites )):
2089+ ielem , tail = self .basetopo .transforms .index_with_tail (etrans )
2090+ ioppelem , opptail = self .basetopo .transforms .index_with_tail (oppetrans )
2091+ erefs = tuple (filter (lambda item : item [1 ], ((i , ref .get_from_trans (tail )) for i , ref in zip (self .names , self .refs [ielem ]))))
2092+ opperefs = tuple (filter (lambda item : item [1 ], ((i , ref .get_from_trans (opptail )) for i , ref in zip (self .names , self .refs [ioppelem ]))))
2093+ checkeref = eref .empty
2094+ for aname , aeref in erefs :
2095+ for bname , beref in opperefs :
2096+ parteref = aeref & beref
2097+ if parteref :
2098+ basereferences [aname , bname ].append (parteref )
2099+ baseindices [aname , bname ].append (ieelem )
2100+ checkeref |= parteref
2101+ assert checkeref == eref
2102+ baseindices = {p : types .frozenarray (i , dtype = int ) for p , i in baseindices .items ()}
2103+
2104+ newreferences = {(a , b ): [] for i , a in enumerate (self .names ) for b in self .names [i + 1 :]}
2105+ newtransforms = {(a , b ): [] for i , a in enumerate (self .names ) for b in self .names [i + 1 :]}
2106+ newopposites = {(a , b ): [] for i , a in enumerate (self .names ) for b in self .names [i + 1 :]}
2107+ for baseref , partrefs , basetrans in zip (self .basetopo .references , self .refs , self .basetopo .transforms ):
2108+ pool = {}
2109+ for aname , aref in zip (self .names , partrefs ):
2110+ if not aref :
2111+ continue
2112+ for aetrans , aeref in aref .edges [baseref .nedges :]:
2113+ if not aeref :
2114+ continue
2115+ points = types .frozenarray (aetrans .apply (aeref .getpoints ('bezier' , 2 ).coords ))
2116+ bname , beref , betrans = pool .pop ((points , not aetrans .isflipped ), (None , None , None ))
2117+ if beref is None :
2118+ pool [(points , aetrans .isflipped )] = aname , aeref , aetrans
2119+ else :
2120+ assert aname != bname , 'elements are not supposed to count internal interfaces as edges'
2121+ assert aeref == beref
2122+ atrans = basetrans + (aetrans , transform .Identifier (self .ndims - 1 , aname ))
2123+ btrans = basetrans + (betrans , transform .Identifier (self .ndims - 1 , bname ))
2124+ if self .names .index (aname ) <= self .names .index (bname ):
2125+ iface = aname , bname
2126+ else :
2127+ iface = bname , aname
2128+ atrans , btrans = btrans , atrans
2129+ newreferences [iface ].append (aeref )
2130+ newtransforms [iface ].append (atrans )
2131+ newopposites [iface ].append (btrans )
2132+ assert not pool , 'some interal edges have no opposites'
2133+
2134+ itopos = []
2135+ inames = []
2136+ for i , a in enumerate (self .names ):
2137+ itopos .append (Topology (elementseq .asreferences (basereferences [a , a ], self .ndims - 1 ),
2138+ transformseq .WithIdentifierTransforms (baseifaces .transforms [baseindices [a , a ]], a ),
2139+ transformseq .WithIdentifierTransforms (baseifaces .opposites [baseindices [a , a ]], a )))
2140+ inames .append ('{0}:{0}' .format (a ))
2141+ for b in self .names [i + 1 :]:
2142+ base = Topology (elementseq .asreferences (basereferences [a , b ] + basereferences [b , a ], self .ndims - 1 ),
2143+ transformseq .WithIdentifierTransforms (transformseq .chain ((baseifaces .transforms [baseindices [a , b ]], baseifaces .opposites [baseindices [b , a ]]), self .ndims - 1 ), a ),
2144+ transformseq .WithIdentifierTransforms (transformseq .chain ((baseifaces .opposites [baseindices [a , b ]], baseifaces .transforms [baseindices [b , a ]]), self .ndims - 1 ), b ))
2145+ new = Topology (elementseq .asreferences (newreferences [a , b ], self .ndims - 1 ),
2146+ transformseq .PlainTransforms (newtransforms [a , b ], self .ndims - 1 ),
2147+ transformseq .PlainTransforms (newopposites [a , b ], self .ndims - 1 ))
2148+ itopos .append (DisjointUnionTopology ((base , new )))
2149+ inames .append ('{}:{}' .format (a , b ))
2150+ return DisjointUnionTopology (itopos , inames )
2151+
2152+ def __sub__ (self , other ):
2153+ if self == other :
2154+ return EmptyTopology (self .ndims )
2155+ elif isinstance (other , _SubsetOfPartitionedTopology ) and other ._partition == self :
2156+ remainder = frozenset (self .names ) - frozenset (other ._names )
2157+ if remainder :
2158+ return _SubsetOfPartitionedTopology (self , remainder )
2159+ else :
2160+ return EmptyTopology (self .ndims )
2161+ else :
2162+ return super ().__sub__ (other )
2163+
2164+ class _SubsetOfPartitionedTopology (DisjointUnionTopology ):
2165+
2166+ __slots__ = '_partition' , '_names'
2167+ __cache__ = 'boundary' , 'interfaces'
2168+
2169+ @types .apply_annotations
2170+ def __init__ (self , partition : stricttopology , names : frozenset ):
2171+ self ._partition = partition
2172+ if not names <= frozenset (partition .names ):
2173+ raise ValueError ('Not a subset of the partition.' )
2174+ if not all (isinstance (name , str ) for name in names ):
2175+ raise ValueError ('All names should be str objects.' )
2176+ self ._names = tuple (sorted (names , key = partition .names .index ))
2177+ super ().__init__ (tuple (self ._partition ._parts [self ._partition .names .index (name )] for name in self ._names ), self ._names )
2178+
2179+ def __getitem__ (self , item ):
2180+ if item in self ._names :
2181+ return _SubsetOfPartitionedTopology (self ._partition , {item })
2182+ elif item in self ._partition .names :
2183+ return EmptyTopology (self .ndims )
2184+ else :
2185+ topo = self ._partition .getitem (item )
2186+ assert not isinstance (topo , _SubsetOfPartitionedTopology ) # this is covered by the above two conditionals
2187+ if isinstance (topo , EmptyTopology ):
2188+ return topo
2189+ elif isinstance (topo , PartitionedTopology ):
2190+ return _SubsetOfPartitionedTopology (topo , self ._names )
2191+ else :
2192+ raise NotImplementedError
2193+
2194+ @property
2195+ def boundary (self ):
2196+ # The boundary of this subset consists of the boundary of the base that
2197+ # touches this subset and the interfaces between all parts in this subset
2198+ # and all parts not in this subset. All interfaces are grouped and named by
2199+ # the parts not in this subset: given a partition A, B of Ω, then
2200+ # `Ω['A'].boundary['B']` is the same as `Ω.interfaces['A:B']` or
2201+ # `~Ω.interfaces['B:A']`, whichever exists.
2202+ topos = []
2203+ names = []
2204+ for b in self ._partition .names : # parts not in this subset
2205+ if b in self ._names :
2206+ continue
2207+ btopos = []
2208+ for a in self ._names : # parts in this subset
2209+ if self ._partition .names .index (a ) <= self ._partition .names .index (b ):
2210+ btopos .append (self ._partition .interfaces .getitem ('{}:{}' .format (a , b )))
2211+ else :
2212+ btopos .append (~ self ._partition .interfaces .getitem ('{}:{}' .format (b , a )))
2213+ topos .append (DisjointUnionTopology (btopos ))
2214+ names .append (b )
2215+ for name in self ._names :
2216+ topos .append (self ._partition .boundary .getitem (name ))
2217+ groups = {}
2218+ return DisjointUnionTopology (topos , names )
2219+
2220+ @property
2221+ def interfaces (self ):
2222+ topos = []
2223+ names = []
2224+ for i , a in enumerate (self ._names ):
2225+ for b in self ._names [i :]:
2226+ topos .append (self ._partition .interfaces .getitem ('{}:{}' .format (a , b )))
2227+ names .append ('{}:{}' .format (a , b ))
2228+ return DisjointUnionTopology (topos , names )
2229+
2230+ def __or__ (self , other ):
2231+ if isinstance (other , _SubsetOfPartitionedTopology ) and other ._partition == self ._partition :
2232+ return _SubsetOfPartitionedTopology (self ._partition , frozenset (self ._names ) | frozenset (other ._names ))
2233+ else :
2234+ return super ().__or__ (other )
2235+
2236+ def __rsub__ (self , other ):
2237+ if self ._partition == other or self ._partition .basetopo == other :
2238+ remainder = frozenset (self ._partition .names ) - frozenset (self ._names )
2239+ if remainder :
2240+ return _SubsetOfPartitionedTopology (self ._partition , remainder )
2241+ else :
2242+ return EmptyTopology (self .ndims )
2243+ else :
2244+ return super ().__rsub__ (other )
2245+
20272246class PatchBoundary (types .Singleton ):
20282247
20292248 __slots__ = 'id' , 'dim' , 'side' , 'reverse' , 'transpose'
0 commit comments