From 5be1bdfe0269312d4bed32d4d0ff2903ed93a117 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Aug 2022 11:13:56 -0700 Subject: [PATCH] src/sage/categories/families.py: New --- src/sage/categories/families.py | 156 ++++++++++++++++++++++++++++++++ src/sage/sets/family.py | 32 ++++--- 2 files changed, 174 insertions(+), 14 deletions(-) create mode 100644 src/sage/categories/families.py diff --git a/src/sage/categories/families.py b/src/sage/categories/families.py new file mode 100644 index 00000000000..6b43c697e3b --- /dev/null +++ b/src/sage/categories/families.py @@ -0,0 +1,156 @@ +r""" +Families +""" + +#***************************************************************************** +# Copyright (C) 2022 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. +# https://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.sets_cat import Sets + + +class Families(Category_singleton): + r""" + The category of families + + A *family* is a set together with a map from a set of "keys" onto it. + + Morphisms of :class:`Families` preserve this map. This is the additional + structure compared to :class:`Sets`. Hence, equality of families takes + this map into account. + + The standard methods for a family ``F`` are: + + - ``F.keys()``, ``F.values()``, ``F.items()``: methods similar to those of + :class:`dict` (or :class:`collections.abc.Mapping`). + + - ``F[key]``: the value (element) indexed by ``key``. + + A family is not necessarily enumerated, and the map is not necessarily injective. + However, if it is iterable, then: + + - ``iter(F)``: returns an iterator for the elements (values) of ``F`` as a set, + i.e., no value appears twice. + + Stronger guarantees are given by the category :class:`EnumeratedFamilies`. + + EXAMPLES:: + + sage: from sage.categories.families import Families + + TESTS:: + + sage: C = Families() + sage: TestSuite(C).run() + """ + + def super_categories(self): + r""" + EXAMPLES:: + + sage: from sage.categories.families import Families + sage: Families().super_categories() + [Category of sets] + """ + return [Sets()] + + ## def additional_structure(self): + ## r""" + ## Return ``self``. + + ## Indeed, the category of families defines an + ## additional structure, namely the map from keys onto it, + ## which shall be preserved by morphisms. + + ## .. SEEALSO:: :meth:`Category.additional_structure` + + ## EXAMPLES:: + + ## sage: from sage.categories.families import Families + ## sage: Families().additional_structure() + ## Category of families + ## """ + ## return self + + class ParentMethods: + + #def __getitem__(self, i): + + @abstract_method + def keys(self): + """ + Return the keys of the family. + + This may or may not be an iterable. + + EXAMPLES:: + + sage: f = Family({3: 'a', 4: 'b', 7: 'd'}) + sage: sorted(f.keys()) + [3, 4, 7] + """ + + @abstract_method(optional=True) + def values(self): + """ + Return the elements (values) of the family. + + If :meth:`keys` returns an iterable, then :meth:`values` will + return an iterable parallel to that. When the family is not injective, + values will appear multiple times in the iteration. + + EXAMPLES:: + + sage: f = Family(["c", "a", "b"], lambda x: x + x) + sage: sorted(f.values()) + ['aa', 'bb', 'cc'] + """ + + def items(self): + """ + Return the key-value pairs of the family. + + This may or may not be an iterable. + + A key can only appear once, but if the function is not injective, values will + appear multiple times. + + EXAMPLES:: + + sage: f = Family(["a", "ab", "bc", "def"], len) + sage: sorted(f.items()) + [('a', 1), ('ab', 2), ('bc', 2), ('def', 3)] + """ + return zip(self.keys(), self.values()) + + @cached_method + def as_set(self): + """ + Return the elements (values) of this family as a set. + + EXAMPLES:: + + sage: f = Family({1: 'a', 2: 'b', 3: 'c'}) + sage: g = Family({1: 'b', 2: 'c', 3: 'a'}) + sage: f == g + False + sage: f.as_set() == g.as_set() + True + + This is the same as calling :func:`~sage.sets.set.Set` on ``self``:: + + sage: f.as_set() + {...} + sage: Set(f) + {...} + """ + return Set(self.values()) diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index a90e2307b70..f3380363cc6 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -19,7 +19,7 @@ sage: P = Partitions(3) sage: Family(P, lambda x: x).category() - Category of finite enumerated sets + Category of finite enumerated families """ #***************************************************************************** @@ -45,6 +45,7 @@ from collections.abc import Iterable, Mapping, Sequence from sage.categories.enumerated_sets import EnumeratedSets +from sage.categories.families import Families from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.categories.sets_cat import Sets @@ -366,14 +367,14 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, sage: PA[(4, 10, 5, 0, 0)] A 2-dimensional polyhedron in RDF^2 defined as the convex hull of 4 vertices sage: PA.category() - Category of sets + Category of families We can refine the category:: sage: PA = Family(b_set, polyhedron_Ax_le_b, is_injective=False, ....: category=PolyhedralSets(RDF)) sage: PA.category() - Category of polyhedral sets over Real Double Field + Join of Category of polyhedral sets over Real Double Field and Category of families The map is not injective:: @@ -568,9 +569,9 @@ def as_set(self): This is the same as calling :func:`~sage.sets.set.Set`:: sage: f.as_set() - {'c', 'a', 'b'} + {...} sage: Set(f) - {'c', 'a', 'b'} + {...} """ return Set(self.values()) @@ -731,7 +732,7 @@ def __init__(self, dictionary, keys=None, category=None): Finite family {1: 'a', 3: 'b', 4: 'c'} """ # TODO: use keys to specify the order of the elements - super().__init__(category=FiniteEnumeratedSets().or_subcategory(category)) + super().__init__(category=Families() & FiniteEnumeratedSets().or_subcategory(category)) self._dictionary = dict(dictionary) self._keys = keys @@ -1098,15 +1099,17 @@ def __init__(self, set, function, name=None, category=None, is_injective=None, i Lazy family ((i))_{i in [3, 4, 7]} """ if set in FiniteEnumeratedSets(): - category = FiniteEnumeratedSets().or_subcategory(category) + set_category = FiniteEnumeratedSets() elif set in InfiniteEnumeratedSets(): - category = InfiniteEnumeratedSets().or_subcategory(category) + set_category = InfiniteEnumeratedSets() elif isinstance(set, (list, tuple, range, CombinatorialClass)): - category = FiniteEnumeratedSets().or_subcategory(category) + set_category = FiniteEnumeratedSets() elif set in Sets(): - category = Sets().or_subcategory(category) + set_category = Sets() elif category is None: - category = EnumeratedSets() + set_category = EnumeratedSets() + + category = Families() & set_category.or_subcategory(category) set = copy(set) @@ -1129,6 +1132,7 @@ def __init__(self, set, function, name=None, category=None, is_injective=None, i self.function_name = name self._is_injective = is_injective self._inverse = inverse + self._set_category = set_category def __bool__(self): r""" @@ -1296,7 +1300,7 @@ def as_set(self): """ Return the set of values of ``self`` as an :class:`~sage.sets.image_set.ImageSet`. """ - return ImageSubobject(self.function, self.set, category=self.category(), + return ImageSubobject(self.function, self.set, category=self._set_category, is_injective=self._is_injective, inverse=self._inverse) def cardinality(self): @@ -1456,7 +1460,7 @@ def __init__(self, enumeration, category=None): Family (3, 4, 7) sage: TestSuite(f).run() """ - category = FiniteEnumeratedSets().or_subcategory(category) + category = Families() & FiniteEnumeratedSets().or_subcategory(category) super().__init__(category=category) self._enumeration = tuple(enumeration) @@ -1642,7 +1646,7 @@ def __init__(self, enumset, category=None): sage: from sage.sets.family import EnumeratedFamily sage: f = EnumeratedFamily(Permutations(4)) sage: f.category() - Category of finite enumerated sets + Category of finite enumerated families sage: list(f.keys()) == list(range(f.cardinality())) True sage: Family(Permutations()).keys()