diff --git a/src/doc/en/reference/manifolds/manifold.rst b/src/doc/en/reference/manifolds/manifold.rst index f11d8135500..02afb2460b4 100644 --- a/src/doc/en/reference/manifolds/manifold.rst +++ b/src/doc/en/reference/manifolds/manifold.rst @@ -21,3 +21,5 @@ Topological Manifolds sage/manifolds/topological_submanifold vector_bundle + + sage/manifolds/family diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index eecd1bed77b..160795f5c4b 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -3317,12 +3317,28 @@ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options): ... ValueError: `self` should be an acyclic graph + TESTS: + + :trac:`31681` is fixed:: + + sage: H = DiGraph({0: [1], 'X': [1]}, format='dict_of_lists') + sage: pos = H.layout_acyclic_dummy(rankdir='up') + sage: pos['X'][1] == 0 and pos[0][1] == 0 + True + sage: pos[1][1] == 1 + True """ if heights is None: if not self.is_directed_acyclic(): raise ValueError("`self` should be an acyclic graph") levels = self.level_sets() - levels = [sorted(z) for z in levels] + # Sort vertices in each level in best effort mode + for i in range(len(levels)): + try: + l = sorted(levels[i]) + levels[i] = l + except: + continue if rankdir=='down' or rankdir=='left': levels.reverse() heights = {i: levels[i] for i in range(len(levels))} diff --git a/src/sage/manifolds/family.py b/src/sage/manifolds/family.py new file mode 100644 index 00000000000..9637fa0b0a5 --- /dev/null +++ b/src/sage/manifolds/family.py @@ -0,0 +1,236 @@ +r""" +Families of Manifold Objects + +The class :class:`ManifoldObjectFiniteFamily` is a subclass of :class:`FiniteFamily` +that provides an associative container of manifold objects, indexed by their +``_name`` attributes. + +:class:`ManifoldObjectFiniteFamily` instances are totally ordered according +to their lexicographically ordered element names. + +The subclass :class:`ManifoldSubsetFiniteFamily` customizes the print +representation further. + +""" +#***************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from functools import total_ordering +from sage.sets.family import FiniteFamily + +@total_ordering +class ManifoldObjectFiniteFamily(FiniteFamily): + + r""" + Finite family of manifold objects, indexed by their names. + + The class :class:`ManifoldObjectFiniteFamily` inherits from + :class:`FiniteFamily`. Therefore it is an associative container. + + It provides specialized ``__repr__`` and ``_latex_`` methods. + + :class:`ManifoldObjectFiniteFamily` instances are totally ordered + according to their lexicographically ordered element names. + + EXAMPLES:: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: C = B.subset('C') + sage: F = ManifoldObjectFiniteFamily([A, B, C]); F + Set {A, B, C} of objects of the 2-dimensional topological manifold M + sage: latex(F) + \{A, B, C\} + sage: F['B'] + Subset B of the 2-dimensional topological manifold M + + All objects must have the same base manifold:: + + sage: N = Manifold(2, 'N', structure='topological') + sage: ManifoldObjectFiniteFamily([M, N]) + Traceback (most recent call last): + ... + TypeError: all objects must have the same manifold + + """ + def __init__(self, objects=(), keys=None): + r""" + Initialize a new instance of :class:`ManifoldObjectFiniteFamily`. + + TESTS: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: C = B.subset('C') + sage: F = ManifoldObjectFiniteFamily([A, B, C]); F + Set {A, B, C} of objects of the 2-dimensional topological manifold M + sage: TestSuite(F).run(skip='_test_elements') + + Like ``frozenset``, it can be created from any iterable:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: I = M.subset('I') + sage: gen = (subset for subset in (M, I, M, I, M, I)); gen + + sage: ManifoldSubsetFiniteFamily(gen) + Set {I, M} of subsets of the 2-dimensional topological manifold M + + """ + if isinstance(objects, dict): + dictionary = objects + else: + dictionary = {object._name: object for object in objects} + if keys is None: + keys = sorted(dictionary.keys()) + FiniteFamily.__init__(self, dictionary, keys) + names_and_latex_names = sorted((object._name, object._latex_name) + for object in self) + self._name = '{' + ', '.join(keys) + '}' + latex_names = (latex_name for name, latex_name in names_and_latex_names) + self._latex_name = r'\{' + ', '.join(latex_names) + r'\}' + try: + object_iter = iter(self) + self._manifold = next(object_iter)._manifold + except StopIteration: + self._manifold = None + else: + if not all(object._manifold == self._manifold for object in object_iter): + raise TypeError(f'all {self._repr_object_type()} must have the same manifold') + + def _repr_object_type(self): + r""" + String that describes the type of the elements (plural). + + TESTS:: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldObjectFiniteFamily([A, B]).__repr__() # indirect doctest + 'Set {A, B} of objects of the 2-dimensional topological manifold M' + + """ + return "objects" + + def __lt__(self, other): + r""" + Implement the total order on instances of :class:`ManifoldObjectFiniteFamily`. + + TESTS:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: sorted([ManifoldSubsetFiniteFamily([A, B]), ManifoldSubsetFiniteFamily([]), + ....: ManifoldSubsetFiniteFamily([B]), ManifoldSubsetFiniteFamily([A])]) + [{}, + Set {A} of subsets of the 2-dimensional topological manifold M, + Set {A, B} of subsets of the 2-dimensional topological manifold M, + Set {B} of subsets of the 2-dimensional topological manifold M] + """ + if not isinstance(other, ManifoldSubsetFiniteFamily): + return NotImplemented + return self.keys() < other.keys() + + def __repr__(self): + r""" + String representation of the object. + + TESTS:: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: ManifoldObjectFiniteFamily().__repr__() + '{}' + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldObjectFiniteFamily([A, B]).__repr__() + 'Set {A, B} of objects of the 2-dimensional topological manifold M' + + """ + if self: + return "Set {} of {} of the {}".format(self._name, self._repr_object_type(), self._manifold) + else: + return "{}" + + def _latex_(self): + r""" + LaTeX representation of ``self``. + + TESTS:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldSubsetFiniteFamily([B, A])._latex_() + '\\{A, B\\}' + """ + return self._latex_name + +class ManifoldSubsetFiniteFamily(ManifoldObjectFiniteFamily): + + r""" + Finite family of subsets of a topological manifold, indexed by their names. + + The class :class:`ManifoldSubsetFiniteFamily` inherits from + :class:`ManifoldObjectFiniteFamily`. It provides an associative + container with specialized ``__repr__`` and ``_latex_`` methods. + + :class:`ManifoldSubsetFiniteFamily` instances are totally ordered according + to their lexicographically ordered element (subset) names. + + EXAMPLES:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: C = B.subset('C') + sage: ManifoldSubsetFiniteFamily([A, B, C]) + Set {A, B, C} of subsets of the 2-dimensional topological manifold M + sage: latex(_) + \{A, B, C\} + + All subsets must have the same base manifold:: + + sage: N = Manifold(2, 'N', structure='topological') + sage: ManifoldSubsetFiniteFamily([M, N]) + Traceback (most recent call last): + ... + TypeError: all... subsets must have the same manifold + + """ + + def _repr_object_type(self): + r""" + String that describes the type of the elements (plural). + + TESTS:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldSubsetFiniteFamily([A, B]).__repr__() # indirect doctest + 'Set {A, B} of subsets of the 2-dimensional topological manifold M' + + """ + if all(subset.is_open() for subset in self): + return "open subsets" + else: + return "subsets" diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py index ac8aa262669..ba6b79e4f09 100644 --- a/src/sage/manifolds/subset.py +++ b/src/sage/manifolds/subset.py @@ -76,6 +76,7 @@ from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.sets_cat import Sets +from sage.manifolds.family import ManifoldSubsetFiniteFamily from sage.manifolds.point import ManifoldPoint class ManifoldSubset(UniqueRepresentation, Parent): @@ -646,6 +647,195 @@ def list_of_subsets(self): """ return sorted(self._subsets, key=lambda x: x._name) + def subset_digraph(self, loops=False, open_covers=False, lower_bound=None): + r""" + Return the digraph whose arcs represent subset relations among the subsets of ``self``. + + INPUT: + + - ``loops`` -- (default: ``False``) whether to include the trivial containment + of each subset in itself as loops of the digraph + - ``lower_bound`` -- (default: ``None``) only include supersets of this + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + + OUTPUT: + + A digraph. Each vertex of the digraph is either: + + - a :class:`ManifoldSubsetFiniteFamily` containing one instance of :class:`ManifoldSubset`. + - (if ``open_covers`` is ``True``) a tuple of :class:`ManifoldSubsetFiniteFamily` instances, + representing an open cover. + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: D = M.subset_digraph(); D + Digraph on 4 vertices + sage: D.edges(key=lambda e: (e[0]._name, e[1]._name)) + [(Set {U} of open subsets of the 3-dimensional differentiable manifold M, + Set {M} of open subsets of the 3-dimensional differentiable manifold M, + None), + (Set {V} of open subsets of the 3-dimensional differentiable manifold M, + Set {M} of open subsets of the 3-dimensional differentiable manifold M, + None), + (Set {W} of open subsets of the 3-dimensional differentiable manifold M, + Set {M} of open subsets of the 3-dimensional differentiable manifold M, + None)] + sage: D.plot(layout='acyclic') # not tested + sage: def label(element): + ....: try: + ....: return element._name + ....: except AttributeError: + ....: return '[' + ', '.join(sorted(x._name for x in element)) + ']' + sage: D.relabel(label, inplace=False).plot(layout='acyclic') # not tested + + sage: VW = V.union(W) + sage: D = M.subset_digraph(); D + Digraph on 5 vertices + sage: D.relabel(label, inplace=False).plot(layout='acyclic') # not tested + + sage: D = M.subset_digraph(open_covers=True) + sage: D.relabel(label, inplace=False).plot(layout='acyclic') # not tested + """ + from sage.graphs.digraph import DiGraph + D = DiGraph(multiedges=False, loops=loops) + + def vertex(subset): + return ManifoldSubsetFiniteFamily([subset]) + + if lower_bound is not None: + if not lower_bound.is_subset(self): + return D + visited = set() + to_visit = [self] + while to_visit: + S = to_visit.pop() + if S in visited: + continue + visited.add(S) + + if lower_bound is None: + subsets = S._subsets + else: + subsets = [subset for subset in S._subsets + if lower_bound.is_subset(subset)] + subsets_without_S = [subset for subset in subsets + if subset is not S] + if loops: + D.add_edges((vertex(subset), vertex(S)) for subset in subsets) + else: + D.add_edges((vertex(subset), vertex(S)) for subset in subsets_without_S) + + to_visit.extend(subsets_without_S) + + if open_covers: + + def open_cover_vertex(open_cover): + return tuple(sorted(ManifoldSubsetFiniteFamily([subset]) for subset in open_cover)) + + for S in visited: + D.add_edges((vertex(S), open_cover_vertex(open_cover)) + for open_cover in S._open_covers + if open_cover != [S]) + + return D + + def subset_poset(self, open_covers=False, lower_bound=None): + r""" + Return the poset of the subsets of ``self``. + + INPUT: + + - ``lower_bound`` -- (default: ``None``) only include supersets of this + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: VW = V.union(W) + sage: P = M.subset_poset(); P + Finite poset containing 5 elements + sage: P.maximal_elements() + [Set {M} of open subsets of the 3-dimensional differentiable manifold M] + sage: sorted(P.minimal_elements(), key=lambda v: v._name) + [Set {U} of open subsets of the 3-dimensional differentiable manifold M, + Set {V} of open subsets of the 3-dimensional differentiable manifold M, + Set {W} of open subsets of the 3-dimensional differentiable manifold M] + sage: from sage.manifolds.subset import ManifoldSubsetFiniteFamily + sage: sorted(P.lower_covers(ManifoldSubsetFiniteFamily([M])), key=str) + [Set {U} of open subsets of the 3-dimensional differentiable manifold M, + Set {V_union_W} of open subsets of the 3-dimensional differentiable manifold M] + sage: P.plot(element_labels={element: element._name for element in P}) # not tested + + If ``open_covers`` is ``True``, the poset includes a special vertex for + each nontrivial open cover of a subset:: + + sage: P = M.subset_poset(open_covers=True); P + Finite poset containing 6 elements + sage: from sage.manifolds.subset import ManifoldSubsetFiniteFamily + sage: P.upper_covers(ManifoldSubsetFiniteFamily([VW])) + [(Set {V} of open subsets of the 3-dimensional differentiable manifold M, + Set {W} of open subsets of the 3-dimensional differentiable manifold M), + Set {M} of open subsets of the 3-dimensional differentiable manifold M] + sage: def label(element): + ....: try: + ....: return element._name + ....: except AttributeError: + ....: return '[' + ', '.join(sorted(x._name for x in element)) + ']' + sage: P.plot(element_labels={element: label(element) for element in P}) # not tested + """ + from sage.combinat.posets.posets import Poset + return Poset(self.subset_digraph(open_covers=open_covers, lower_bound=lower_bound)) + + def superset_digraph(self, loops=False, open_covers=False, upper_bound=None): + """ + Return the digraph whose arcs represent subset relations among the supersets of ``self``. + + INPUT: + + - ``loops`` -- (default: ``False``) whether to include the trivial containment + of each subset in itself as loops of the digraph + - ``upper_bound`` -- (default: ``None``) only include subsets of this + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: VW = V.union(W) + sage: P = V.superset_digraph(loops=False, upper_bound=VW); P + Digraph on 2 vertices + + """ + if upper_bound is None: + upper_bound = self._manifold + return upper_bound.subset_digraph(loops=loops, open_covers=open_covers, lower_bound=self) + + def superset_poset(self, open_covers=False, upper_bound=None): + r""" + Return the poset of the supersets of ``self``. + + INPUT: + + - ``upper_bound`` -- (default: ``None``) only include subsets of this + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: VW = V.union(W) + sage: P = V.superset_poset(); P + Finite poset containing 3 elements + sage: P.plot(element_labels={element: element._name for element in P}) # not tested + + """ + if upper_bound is None: + upper_bound = self._manifold + return upper_bound.subset_poset(open_covers=open_covers, lower_bound=self) + def get_subset(self, name): r""" Get a subset by its name. diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index d391cbaa49f..7ad6dbf17e0 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -581,6 +581,25 @@ def __hash__(self): return hash(frozenset(self.keys() + [repr(v) for v in self.values()])) + def __bool__(self): + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: from sage.sets.family import TrivialFamily + sage: f = Family(["c", "a", "b"], lambda x: x+x) + sage: bool(f) + True + sage: g = Family({}) + sage: bool(g) + False + sage: h = Family([], lambda x: x+x) + sage: bool(h) + False + """ + return bool(self._dictionary) + def keys(self): """ Returns the index set of this family @@ -909,6 +928,25 @@ def __init__(self, set, function, name=None): self.function = function self.function_name = name + def __bool__(self): + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: from sage.sets.family import LazyFamily + sage: f = LazyFamily([3,4,7], lambda i: 2*i) + sage: bool(f) + True + sage: g = LazyFamily([], lambda i: 2*i) + sage: bool(g) + False + sage: h = Family(ZZ, lambda x: x+x) + sage: bool(h) + True + """ + return bool(self.set) + @cached_method def __hash__(self): """ @@ -1165,6 +1203,22 @@ def __init__(self, enumeration): Parent.__init__(self, category = FiniteEnumeratedSets()) self._enumeration = tuple(enumeration) + def __bool__(self): + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: from sage.sets.family import TrivialFamily + sage: f = TrivialFamily((3,4,7)) + sage: bool(f) + True + sage: g = Family([]) + sage: bool(g) + False + """ + return bool(self._enumeration) + def __eq__(self, other): """ TESTS::