class ShortestPaths: def __init__(self, sg, Dst): self.sg = sg self.Dst = Dst self.mod = mod = sg.mod self._hiding_tag_ = mod._hiding_tag_ self.srcname = sg.srcname self.top = self self.IG = IG = mod.nodegraph() Edges = [] Y = Dst.nodes while Y: R = sg.G.domain_restricted(Y) R.invert() IG.update(R) Edges.append(R) Y = R.get_domain() if Edges: Edges.pop() Edges.reverse() self.Src = mod.idset(Edges[0].get_domain()) else: self.Src = mod.iso() self.edges = tuple(Edges) sets = [] for i, e in enumerate(Edges): if i == 0: sets.append(mod.idset(e.get_domain())) sets.append(mod.idset(e.get_range())) self.sets = tuple(sets) mod.OutputHandling.setup_printing(self) self.maxpaths = 10 def __getitem__(self, idx): try: return next(self.iter(start=idx)) except StopIteration: raise IndexError def __iter__(self): return self.iter() def iter(self, start=0, stop=None): return PathsIter(self, start, stop) def aslist(self): return list(self) def copy_but_avoid_edges_at_levels(self, *args): avoid = self.edges_at(*args).updated(self.sg.AvoidEdges) assert avoid._hiding_tag_ is self.mod._hiding_tag_ return self.mod.shpaths(self.Dst, self.Src, avoid_edges=avoid) # return self.mod.shpaths(self.dst, self.src, avoid_edges=avoid) avoided = copy_but_avoid_edges_at_levels # The builtin __len__ doesn't always work due to builtin Python restriction to int result: # so we don't provide it at all to avoid unsuspected errors sometimes. # Use .numpaths attribute instead. # def __len__(self): # return self.numpaths def depth(self): pass def edges_at(self, *args): E = self.mod.nodegraph() for col in args: E.update(self.edges[col]) assert E._hiding_tag_ == self.mod._parent.View._hiding_tag_ return E def numpaths_from(self, Src): try: NP = self.NP except AttributeError: NP = self.mod.nodegraph(is_mapping=True) NP.add_edges_n1(self.IG.get_domain(), None) for dst in self.Dst.nodes: NP.add_edge(dst, 1) self.NP = NP numedges = self.mod.hv.numedges IG = self.IG def np(y): n = NP[y] if n is None: n = 0 for z in IG[y]: sn = NP[z] if sn is None: sn = np(z) n += sn * numedges(y, z) NP[y] = n return n num = 0 for src in Src.nodes: num += np(src) return num def _get_numpaths(self): num = self.numpaths_from(self.Src) self.numpaths = num return num numpaths = property_nondata(fget=_get_numpaths) @property def maxpaths(self): return self.printer.max_more_lines @maxpaths.setter def maxpaths(self, value): self.printer.max_more_lines = value def _oh_get_num_lines(self): return self.numpaths def _oh_get_line_iter(self): for el in self: yield from el._get_line_iter() def _oh_get_more_msg(self, start_lineno, end_lineno): nummore = self.numpaths - (end_lineno + 1) return '<... %d more paths ...>' % nummore def _oh_get_empty_msg(self): if self.numpaths: return '<No more paths>' return None
class Classifier: def __init__(self, mod, name, cli=None, supers=(), depends=(), with_referrers=False): self.mod = mod self.name = name if cli is not None: self.cli = cli # Set of all super-classifiers (including self). # The partial order is defined in Notes Aug 30 2005. self.super_classifiers = mod.ImpSet.immnodeset([self]) if supers: for s in supers: self.super_classifiers |= s.super_classifiers else: # The Unity classifier is super of all, but we must add it only # if not supers specified; init of ByUnity itself depends on this. self.super_classifiers |= [mod.Use.Unity.classifier] # The classifiers that self depends on.https://wx2.qq.com/?&lang=en for d in depends: if d.with_referrers: with_referrers = True break # True if we need to setup referrers before calling (the) low-level classifier. self.with_referrers = with_referrers if with_referrers: self.call_with_referrers = mod.View.call_with_referrers def call_with_referrers(self, x, f): # Default is to not use referrers. return f(x) # This is not redefined by subclass unless they set cli property. def _get_cli(self): # This may be defined by subclass w/o setting cli property. return self.get_cli() cli = property_nondata(_get_cli) def get_alt(self, kind, alt): # Get alternative kind for a kind with self as fam.classifier. return self.mod.alt(kind, alt) def get_dictof(self, kind): name = '%s.dictof' % self.name er = self.mod.mker_memoized( name, lambda: self.mod._er_by_(ByDictOwner, self.mod, name, self)) return er.classifier.dictof(kind) def get_kind(self, k): # Make an equivalence class from low-level classification return self.family(k) def get_kindarg(self, kind): # Inverse of get_kind cla, ka, cmp = kind.get_ckc() if cla is not self: raise ValueError( 'get_kindarg: argument with classifier %r expected' % self) return ka def get_reprname(self): return '%s%s' % (self.mod.Use.reprefix, self.name) def get_sokind(self, er, *args, **kwds): k = er(*args, **kwds) return CallableSoKind(er, (k,)) def get_sokindrepr(self, sokind): # Get the representation of a set of kinds # from this classifier / eqv. relation. return '%s.sokind%s' % (self.get_reprname(), ''.join(['(%s)' % self.get_userkindargrepr(k) for k in sokind.kinds])) def get_tabheader(self, ctx=''): # If ctx = 'and', get the table header when used as a part of the 'and' classifier. # It is sometimes a more compact or parenthesised version of the usual tab header. return self.get_byname() def get_tabrendering(self, cla, ctx=''): # If ctx = 'and', get the table rendering when used as a part of the 'and' classifier # sometimes we want to enclose something in parenthesises. return cla.brief def get_userkind(self, *args, **kwds): # Make a kind from user-level arguments return self.family(*args, **kwds) def get_userkindarg(self, kind): return kind.arg def get_userkindargrepr(self, kind): return repr(self.get_userkindarg(kind)) def partition(self, iterable): items = [] for k, v in self.partition_cli(iterable): k = self.get_kind(k) v = self.mod.Use.idset(v, er=self.er) items.append((k, v)) return items def partition_cli(self, a): ep = self.call_with_referrers( a, self.cli.epartition) return [(k, ep[k]) for k in ep.get_domain()] def relimg(self, X): p = self.partition_cli(X) kinds = [self.get_kind(k) for k, v in p] # could be more efficient return self.mod.Use.union(kinds, maximized=1) def select_cli(self, a, b, cmp='=='): return self.call_with_referrers( a, lambda a: self.cli.select(a, b, cmp)) def select_ids(self, X, k, alt=None): r = self.mod.Use.idset(self.select_cli(X.nodes, k, alt)) return r
class ShortestPaths: firstpath = 0 maxpaths = 10 def __init__(self, sg, Dst): self.sg = sg self.Dst = Dst self.mod = mod = sg.mod self._hiding_tag_ = mod._hiding_tag_ self.srcname = sg.srcname self.output = mod.output self.moreiterator = None self.top = self self.IG = IG = mod.nodegraph() Edges = [] Y = Dst.nodes while Y: R = sg.G.domain_restricted(Y) R.invert() IG.update(R) Edges.append(R) Y = R.get_domain() if Edges: Edges.pop() Edges.reverse() self.Src = mod.idset(Edges[0].get_domain()) else: self.Src = mod.iso() self.edges = tuple(Edges) sets = [] for i, e in enumerate(Edges): if i == 0: sets.append(mod.idset(e.get_domain())) sets.append(mod.idset(e.get_range())) self.sets = tuple(sets) self.more = MorePrinter(self) def __getitem__(self, idx): try: return next(self.iter(start=idx)) except StopIteration: raise IndexError def __iter__(self): return self.iter() def __repr__(self): f = self.mod._root.io.StringIO() self.pp(output=f) return f.getvalue().rstrip() def __str__(self): return self.__repr__() def iter(self, start=0, stop=None): return PathsIter(self, start, stop) def aslist(self, maxpaths=None, firstpath=None): if maxpaths is None: maxpaths = self.maxpaths if firstpath is None: firstpath = self.firstpath li = list(self.iter(firstpath, firstpath + maxpaths)) if len(li) >= maxpaths: more = (self.numpaths - (firstpath + len(li))) if more: li.append('<... %d more paths ...>' % more) return li def copy_but_avoid_edges_at_levels(self, *args): avoid = self.edges_at(*args).updated(self.sg.AvoidEdges) assert avoid._hiding_tag_ is self.mod._hiding_tag_ return self.mod.shpaths(self.Dst, self.Src, avoid_edges=avoid) # return self.mod.shpaths(self.dst, self.src, avoid_edges=avoid) avoided = copy_but_avoid_edges_at_levels # The builtin __len__ doesn't always work due to builtin Python restriction to int result: # so we don't provide it at all to avoid unsuspected errors sometimes. # Use .numpaths attribute instead. # def __len__(self): # return self.numpaths def depth(self): pass def edges_at(self, *args): E = self.mod.nodegraph() for col in args: E.update(self.edges[col]) assert E._hiding_tag_ == self.mod._parent.View._hiding_tag_ return E def numpaths_from(self, Src): try: NP = self.NP except AttributeError: NP = self.mod.nodegraph(is_mapping=True) NP.add_edges_n1(self.IG.get_domain(), None) for dst in self.Dst.nodes: NP.add_edge(dst, 1) self.NP = NP numedges = self.mod.hv.numedges IG = self.IG def np(y): n = NP[y] if n is None: n = 0 for z in IG[y]: sn = NP[z] if sn is None: sn = np(z) n += sn * numedges(y, z) NP[y] = n return n num = 0 for src in Src.nodes: num += np(src) return num def _get_numpaths(self): num = self.numpaths_from(self.Src) self.numpaths = num return num numpaths = property_nondata(fget=_get_numpaths) def pp(self, start=None, output=None): self.moreiterator = None self.more(start, output=output) def printiter(self, it, output=None): if output is None: output = self.output self.moreiterator = it i = 0 lastindex = None while i < self.maxpaths: try: el = next(it) except StopIteration: it.reset(0) break el.pp(output=output) i += 1 lastindex = el.index else: if lastindex is not None: nummore = self.numpaths - (lastindex + 1) if nummore == 1: it.next().pp(output=output) elif nummore > 1: print('<... %d more paths ...>' % nummore, file=output)