diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py index e01fbbb99fa..152703075c5 100644 --- a/src/sage/manifolds/point.py +++ b/src/sage/manifolds/point.py @@ -182,7 +182,10 @@ def __init__(self, parent, coords=None, chart=None, name=None, sage: TestSuite(q).run() """ + if parent.is_empty(): + raise TypeError(f'cannot define a point on the {parent} because it has been declared empty') Element.__init__(self, parent) + parent._has_defined_points = True self._manifold = parent.manifold() # a useful shortcut self._coordinates = {} # dictionary of the point coordinates in various # charts, with the charts as keys diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py index 64b8a9c9c62..46c9ab1f6c4 100644 --- a/src/sage/manifolds/subset.py +++ b/src/sage/manifolds/subset.py @@ -63,11 +63,12 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from collections import defaultdict from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.misc.superseded import deprecation from sage.categories.sets_cat import Sets -from sage.manifolds.family import ManifoldSubsetFiniteFamily +from sage.manifolds.family import ManifoldObjectFiniteFamily, ManifoldSubsetFiniteFamily from sage.manifolds.point import ManifoldPoint class ManifoldSubset(UniqueRepresentation, Parent): @@ -197,6 +198,7 @@ def __init__(self, manifold, name, latex_name=None, category=None): self._open_covers = [] # list of open covers of self self._is_open = False # a priori (may be redefined by subclasses) self._manifold = manifold # the ambient manifold + self._has_defined_points = False def _repr_(self): r""" @@ -515,7 +517,7 @@ def is_open(self): """ return False - def open_covers(self, trivial=True): + def open_covers(self, trivial=True, supersets=False): r""" Generate the open covers of the current subset. @@ -534,10 +536,18 @@ def open_covers(self, trivial=True): A = \bigcup_{U \in F} U. + .. NOTE:: + + To get the open covers as a family, sorted lexicographically by the + names of the subsets forming the open covers, use the method + :meth:`open_cover_family` instead. + INPUT: - ``trivial`` -- (default: ``True``) if ``self`` is open, include the trivial open cover of ``self`` by itself + - ``supersets`` -- (default: ``False``) if ``True``, include open covers of + all the supersets; it can also be an iterable of supersets to include EXAMPLES:: @@ -565,11 +575,75 @@ def open_covers(self, trivial=True): Set {A, B, V} of open subsets of the 2-dimensional topological manifold M] """ - for oc in self._open_covers: - if not trivial: - if len(oc) == 1 and next(iter(oc)) is self: - continue - yield ManifoldSubsetFiniteFamily(oc) + if supersets is False: + supersets = [self] + elif supersets is True: + supersets = self._supersets + for superset in supersets: + for oc in superset._open_covers: + if not trivial: + if any(x in supersets for x in oc): + continue + yield ManifoldSubsetFiniteFamily(oc) + + def open_cover_family(self, trivial=True, supersets=False): + r""" + Return the family of open covers of the current subset. + + If the current subset, `A` say, is a subset of the manifold `M`, an + *open cover* of `A` is a :class:`ManifoldSubsetFiniteFamily` `F` + of open subsets `U \in F` of `M` such that + + .. MATH:: + + A \subset \bigcup_{U \in F} U. + + If `A` is open, we ask that the above inclusion is actually an + identity: + + .. MATH:: + + A = \bigcup_{U \in F} U. + + The family is sorted lexicographically by the names of the subsets + forming the open covers. + + .. NOTE:: + + If you only need to iterate over the open covers in arbitrary + order, you can use the generator method :meth:`open_covers` + instead. + + INPUT: + + - ``trivial`` -- (default: ``True``) if ``self`` is open, include the trivial + open cover of ``self`` by itself + - ``supersets`` -- (default: ``False``) if ``True``, include open covers of + all the supersets; it can also be an iterable of supersets to include + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.open_cover_family() + Set {{M}} of objects of the 2-dimensional topological manifold M + sage: U = M.open_subset('U') + sage: U.open_cover_family() + Set {{U}} of objects of the 2-dimensional topological manifold M + sage: A = U.open_subset('A') + sage: B = U.open_subset('B') + sage: U.declare_union(A,B) + sage: U.open_cover_family() + Set {{A, B}, {U}} of objects of the 2-dimensional topological manifold M + sage: U.open_cover_family(trivial=False) + Set {{A, B}} of objects of the 2-dimensional topological manifold M + sage: V = M.open_subset('V') + sage: M.declare_union(U,V) + sage: M.open_cover_family() + Set {{A, B, V}, {M}, {U, V}} of objects of the 2-dimensional topological manifold M + + """ + return ManifoldObjectFiniteFamily(self.open_covers( + trivial=trivial, supersets=supersets)) def open_supersets(self): r""" @@ -739,7 +813,7 @@ def subset_family(self): """ return ManifoldSubsetFiniteFamily(self.subsets()) - def subset_digraph(self, loops=False, open_covers=False, lower_bound=None): + def subset_digraph(self, loops=False, open_covers=False, points=False, lower_bound=None): r""" Return the digraph whose arcs represent subset relations among the subsets of ``self``. @@ -747,8 +821,10 @@ def subset_digraph(self, loops=False, open_covers=False, lower_bound=None): - ``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 + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``lower_bound`` -- (default: ``None``) only include supersets of this OUTPUT: @@ -830,16 +906,41 @@ def open_cover_vertex(open_cover): D.add_edges((vertex(S), open_cover_vertex(open_cover)) for open_cover in S.open_covers(trivial=False)) + if points is not False: + subset_to_points = defaultdict(list) + if points is not True: + # Manifolds do not keep track of the points defined on them. + # Use the provided iterator. + def point_vertex(point): + return point + + for point in points: + S = point.parent() + subset_to_points[S].append(point) + D.add_edge((point_vertex(point), vertex(S))) + + # Add a placeholder vertex under each subset that has a defined + # point that we do not know about. + def anonymous_point_vertex(S): + return f"p{S._name}" + + D.add_edges((anonymous_point_vertex(S), vertex(S)) + for S in visited + if S.has_defined_points(subsets=False) + and S not in subset_to_points) + return D - def subset_poset(self, open_covers=False, lower_bound=None): + def subset_poset(self, open_covers=False, points=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 + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``lower_bound`` -- (default: ``None``) only include supersets of this EXAMPLES:: @@ -866,7 +967,7 @@ def subset_poset(self, open_covers=False, lower_bound=None): 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])) + sage: sorted(P.upper_covers(ManifoldSubsetFiniteFamily([VW])), key=str) [(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] @@ -878,7 +979,8 @@ def subset_poset(self, open_covers=False, lower_bound=None): 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)) + return Poset(self.subset_digraph(open_covers=open_covers, points=points, + lower_bound=lower_bound)) def supersets(self): r""" @@ -928,7 +1030,7 @@ def superset_family(self): """ return ManifoldSubsetFiniteFamily(self.supersets()) - def superset_digraph(self, loops=False, open_covers=False, upper_bound=None): + def superset_digraph(self, loops=False, open_covers=False, points=False, upper_bound=None): """ Return the digraph whose arcs represent subset relations among the supersets of ``self``. @@ -936,8 +1038,10 @@ def superset_digraph(self, loops=False, open_covers=False, upper_bound=None): - ``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 + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``upper_bound`` -- (default: ``None``) only include subsets of this EXAMPLES:: @@ -950,16 +1054,19 @@ def superset_digraph(self, loops=False, open_covers=False, upper_bound=None): """ if upper_bound is None: upper_bound = self._manifold - return upper_bound.subset_digraph(loops=loops, open_covers=open_covers, lower_bound=self) + return upper_bound.subset_digraph(loops=loops, open_covers=open_covers, points=points, + lower_bound=self) - def superset_poset(self, open_covers=False, upper_bound=None): + def superset_poset(self, open_covers=False, points=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 + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``upper_bound`` -- (default: ``None``) only include subsets of this EXAMPLES:: @@ -973,7 +1080,8 @@ def superset_poset(self, open_covers=False, upper_bound=None): """ if upper_bound is None: upper_bound = self._manifold - return upper_bound.subset_poset(open_covers=open_covers, lower_bound=self) + return upper_bound.subset_poset(open_covers=open_covers, points=points, + lower_bound=self) def get_subset(self, name): r""" @@ -1097,6 +1205,111 @@ def declare_union(self, dom1, dom2): oc.append(s) self._open_covers.append(oc) + def declare_empty(self): + r""" + Declare that ``self`` is the empty set. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A', is_open=True) + sage: AA = A.subset('AA') + sage: A + Open subset A of the 2-dimensional topological manifold M + sage: A.declare_empty() + sage: A.is_empty() + True + + Empty sets do not allow to define points on them:: + + sage: A.point() + Traceback (most recent call last): + ... + TypeError: cannot define a point on the + Open subset A of the 2-dimensional topological manifold M + because it has been declared empty + + Emptiness transfers to subsets:: + + sage: AA.is_empty() + True + sage: AA.point() + Traceback (most recent call last): + ... + TypeError: cannot define a point on the + Subset AA of the 2-dimensional topological manifold M + because it has been declared empty + sage: AD = A.subset('AD') + sage: AD.is_empty() + True + + If points have already been defined on ``self`` (or its subsets), + it is an error to declare it to be empty:: + + sage: B = M.subset('B') + sage: b = B.point(name='b'); b + Point b on the 2-dimensional topological manifold M + sage: B.declare_empty() + Traceback (most recent call last): + ... + TypeError: cannot be empty because it has defined points + + Emptiness is recorded as empty open covers:: + + sage: P = M.subset_poset(open_covers=True, points=[b]) + sage: def label(element): + ....: if isinstance(element, str): + ....: return 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}) + Graphics object consisting of 14 graphics primitives + + .. PLOT:: + + def label(element): + if isinstance(element, str): + return element + try: + return element._name + except AttributeError: + return '[' + ', '.join(sorted(x._name for x in element)) + ']' + M = Manifold(2, 'M', structure='topological') + A = M.subset('A', is_open=True) + AA = A.subset('AA') + A.declare_empty() + AD = A.subset('AD') + B = M.subset('B') + b = B.point(name='b') + P = M.subset_poset(open_covers=True, points=[b]) + g1 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1]), figsize=(8, 3)) + + """ + if self.has_defined_points(): + raise TypeError('cannot be empty because it has defined points') + for subset in self.subsets(): + if not subset.is_empty(): + subset._open_covers.append([]) + + def is_empty(self): + if self._has_defined_points: + return False + return any(not cover + for cover in self.open_covers(trivial=False, supersets=True)) + + def declare_nonempty(self): + if not self.has_defined_points(): + self._has_defined_points = True + + def has_defined_points(self, subsets=True): + if subsets: + return any(subset._has_defined_points for subset in self.subsets()) + else: + return self._has_defined_points + def point(self, coords=None, chart=None, name=None, latex_name=None): r""" Define a point in ``self``.