#10667: Morphisms and Objects of Categories
---------------------------+------------------------------------------------
   Reporter:  SimonKing    |       Owner:  nthiery                      
       Type:  enhancement  |      Status:  new                          
   Priority:  major        |   Milestone:                               
  Component:  categories   |    Keywords:  objects morphisms containment
     Author:  Simon King   |    Upstream:  N/A                          
   Reviewer:               |      Merged:                               
Work_issues:               |  
---------------------------+------------------------------------------------
Changes (by newvalueoldvalue):

  * author:  => Simon King


Comment:

 Depends on #10496, #10659, #8611, #10467

 I wont to get the patch finally off my plate. So, here it is, although it
 isn't finished yet.

 My patch provides the following:
 {{{
 sage: C = Rings()
 sage: P.<x,y> = QQ[]
 sage: f = P.hom(reversed(P.gens()))
 sage: C.has_morphism(f)
 True
 sage: C.morphisms()
 Class of morphisms in category of rings
 sage: f in C.morphisms()
 True
 # Currently, a category is acknowledged as "small"
 # iff it is a sub-category of FiniteEnumeratedSets()
 sage: FiniteFields().morphisms()
 Set of morphisms in category of finite fields
 sage: f in FiniteFields().morphisms()
 False
 sage: P in C
 True
 sage: C.objects()
 Class of objects in category of rings
 sage: P in C.objects()
 True
 sage: FiniteFields().objects()
 Set of objects in category of finite fields
 }}}

 Note that I interprete `Objects()` (the top-category in Sage) as the
 "category of all classes", although this definition probably is not water-
 proof:
 {{{
 sage: C.objects().category()
 Category of objects
 sage: FiniteFields().objects().category()
 Category of sets
 }}}

 Some categories have a custom containment test, e.g., the category of
 fields. The containment test of `C.objects()` automatically tests whether
 `C` has a custom containment, and uses it if it is the case:
 {{{
 sage: PolynomialRing(QQ,[]).category()
 Category of commutative rings
 sage: PolynomialRing(QQ,[]) in Fields()
 True
 sage: PolynomialRing(QQ,[]) in Fields().objects()
 True
 }}}

 The documentation for the new containers for objects and morphisms are
 added to the Sage reference manual -- please have a look.

 '''__`SageObject` versus `CategoryObject`__'''

 `SageObject` and `CategoryObject` were almost identical. In particular,
 `SageObject` provided a method `category()`, that by default returned the
 "category of objects". In addition, the specifition says that `X` is an
 object of `X.category()`, i.e., `X in X.category()`.

 But that approach yields to quite unnatural constructions. For example,
 `1.category()` used to be the "category of elements of Integer Ring",
 whatever that means. Worse, one used to have
 {{{
 # Unpatched behaviour: Bug
 sage: ZZ.hom([1]) in Rings().hom_category()
 True
 }}}
 In other words, a ''ring homomorphism'' is considered a ''homset'' of the
 category of rings - of course, it should just be an ''element'' of a
 homset:
 {{{
 # With the patch:
 sage: ZZ.hom([1]) in Rings().hom_category()
 False
 sage: ZZ.hom([1]) in ZZ.Hom(ZZ)
 True
 sage: ZZ.hom([1]) in Rings().morphisms()
 True
 }}}

 Fixing that bug required to re-structure `SageObject` and
 `CategoryObject`:

  * I removed `category()` and `_test_category()` from `SageObject` and
 moved it to `CategoryObject` (which directly inherits from `SageObject`
 anyway).
  * I made `Element` and `Map` inherit from `SageObject`, not from
 `CategoryObject`, and removed the custom `category()` for `Element`. Of
 course, `Parent` still inherits from `CategoryObject`.

 Note that by this change, it is now impossible to define nonsense such as
 `Hom(2,3)` (2 and 3 used to be objects in a category, so, there was a hom-
 set!).

 '''__Hom-categories__'''

 '''Compare #10668''': This part of my patch is not finished, yet. I
 suppose that eventually this ticket here will depend on #10668.

 Just for now, I implemented what I described in my previous post.
 Otherwise, many tests from the new test suites described below would fail.

 '''__Test Suites__'''

 The test suites of categories have been extended to test against the
 specification of the new features. In particular, the containers for
 morphisms and objects provide a test suite. The test suites for
 `C.morphisms()` and `C.objects()` and `C.hom_category()` are added to the
 test suite of `C`.

 The patch adds a method `an_object()` to categories, that is used for
 additional tests. The default is to return `example()`, but this is not
 provided in all cases. The purpose of `an_object()` is narrower than that
 of `example()`, which is supposed to provide a concise instructive (but
 not necessarily very efficient) implementation. In contrast, `an_object()`
 may return an object of a subcategory, if nothing else is available. The
 test suite of `C.an_object()` becomes part of the test suite of `C`.

 Similarly, I introduce a method `a_morphism`. By default, it takes the
 output of `an_object()`, tries to create an automorphism by reverting the
 list of generators, and if that fails then it returns the identity
 automorphism. The latter sounds trivial, but actually I found several
 cases where the identity automorphism was provided with the wrong
 category. This led to the following bug fixes:

  * In `sage.categories.homset.Hom`, there was an assymmetry between the
 categories of the domain and the codomain. I suggest to choose the
 ''meet'' of both categories. However, note that there was a comment like
 this:
  {{{
     To avoid creating superfluous categories, homsets are in the
     homset category of the lowest category which currently says
     something specific about its homsets.
  }}}
  which meant that the endomorphisms of the rational field used to belong
 to the "Category of hom sets in Category of rings". I didn't observe any
 problems changing it into the "Category of hom sets in Category of
 quotient fields".

  * Without the patch:
    {{{
 sage: F = GF(5); MS = MatrixSpace(F,2,2)
 sage: G = MatrixGroup([MS([1,1,0,1])])
 sage: H = MatrixGroup([MS([1,0,1,1])])
 sage: phi = G.hom(H.gens())
 sage: phi.category_for()
 Category of groups
 sage: H.category()
 Category of finite groups
 sage: G.category()
 Category of finite groups
   }}}
   Hence, the morphism belongs to a category that is too broad. With the
 patch:
   {{{
 sage: F = GF(5); MS = MatrixSpace(F,2,2)
 sage: G = MatrixGroup([MS([1,1,0,1])])
 sage: H = MatrixGroup([MS([1,0,1,1])])
 sage: phi = G.hom(H.gens())
 sage: phi.category_for()
 Category of finite groups
   }}}
   In several cases I have not been able to find any proper use case in
 Sage for a given category. In these cases, I have not been able to provide
 `an_object()`, so that in these cases I have to skip some tests from the
 test suites:

  * `JoinCategory` (I guess it is impossible to construct a generic object
 of the join of two arbitrary categories)

  * `AbstractCategory` (Do I understand correctly that the abstract
 category for the category of `ZZ`-modules would be the catogory of
 modules? I.e., one abstracts the base ring away?)

  * `Schemes`:
   {{{
 # Bug, not fixed in the patch
 sage: Spec(QQ).category()
 Category of sets
   }}}

  * `UniqueFactorizationDomains`
   {{{
 # Bug, not fixed in the patch
 sage: ZZ in UniqueFactorizationDomains()
 False
   }}}

  * `AlgebraModules`:
   {{{
 sage: QQ['x'] in Algebras(QQ)
 True
 # Bug?
 sage: QQ['x']^3 in AlgebraModules(QQ['x'])
 False
   }}}

  * `GSets` (Is there any G-set in Sage that knows that it is a G-set?)

  * `DualObjectsCategory`

  * `Sets().Subquotients()`

  * `FiniteDimensional...`: Most categories whose name starts with
 `FiniteDimensional` are not in use.

  * `PartiallyOrderedMonoids`

  * `MonoidAlgebras`

  * `TensorProductsCategory`


  * I added a minimal implementation of pointed sets:
   {{{
 sage: from sage.categories.examples.pointed_sets import PointedSet
 sage: S = PointedSet([1,2,3],2)
 sage: S
 {1, 2, 3} -> 2
 sage: S is loads(dumps(S))
 True
 sage: S == PointedSet([1,2,3],3)
 False
   }}}

  * I fixed the category of partially ordered sets.

   Without patch:
   {{{
 sage: from sage.combinat.posets.posets import Poset
 sage: elms = [1,2,3,4,5,6,7]
 sage: rels = [[1,2],[3,4],[4,5],[2,5]]
 sage: Poset((elms, rels), cover_relations = True).category()
 Category of sets
   }}}
   With the patch, we obtain `Category of partially ordered sets`.

  * The category of matrix algebras has not been used. I added the obvious
 example:
   {{{
 sage: MatrixSpace(QQ,2).category()
 Category of matrix algebras over Rational Field
   }}}
   which used to be the category of algebras (not: matrix algebras).

 '''__Groupoids__'''

 Groupoids are considered as a category with a single object. However, this
 object did not exist. The patch provides it, modeled as an empty set:
 {{{
 sage: G = SymmetricGroup(5)
 sage: C = Groupoid(G)
 sage: O = C.an_object(); O
 Unique object of Groupoid with underlying set SymmetricGroup(5)
 sage: len(O)
 0
 sage: O.an_element()
 Traceback (most recent call last):
 ...
 EmptySetError:
 }}}

 The elements of `G` correspond to morphisms of its groupoid. I suggest to
 actually consider them as morphisms (which is stronger than saying they
 ''correspond to'' morphisms):
 {{{
 sage: G.an_element() in C.morphisms()
 True
 }}}

 Note that this point of view is needed in order to have a functorial
 approach towards actions, namely: If we want to view a group action of `G`
 on a set `S` as a functor from the groupoid of `G` to the category
 containing `S` as an object, then

  1. we need that functors can map morphisms (see #8807), and

  2. we need that group elements are considered as morphisms.

 Actually, that was the starting point for my work on this ticket.

 On the other hand, I do think that considering `G` as a homset of
 `Groupoid(G)` is not a very clean solution. But I believe this could be
 addressed on a different ticket, as this one is already too long.

 '''__Need for Speed__'''

 Of course, testing containment in a category `C` or in `C.objects()` or in
 `C.morphisms()` should be as fast as possible. I did the following:

  * I added a shortpath to `C.__contains__` and `C.objects().__contains__`
 for the common case that the category of the argument is `C`.

  * `C.objects()` and `C.morphisms()` are cached methods. By #8611, the
 overhead is now pretty small anyway.

  * Containment of an object `O` in a category `C` relies on testing
 whether `O.category()` is a sub-category of `C`. This is cached, by #8611.
 In addition, I remove the overhead entirely, by directly accessing the
 cache.

  * The containers for objects and morphisms are implemented in Cython. The
 default containment test is copied from the category, in order to reduce
 the overhead of calling a Python function. Therefore, `F in O` (where `O =
 C.objects()`) is sometimes even a little quicker than `F in C`:
  {{{
 sage: F = GF(5)
 sage: C = Rings()
 sage: O = C.objects()
 sage: F in O
 True
 sage: timeit('F in O',number=100000)
 100000 loops, best of 3: 5.2 µs per loop
 sage: timeit('F in C',number=100000)
 100000 loops, best of 3: 5.38 µs per loop
  }}}

 Here are some timings. Their purpose is to show that `X in C` did not slow
 down (in fact, there is a speed-up in one special case), and that `X in
 C.objects()` has almost no overhead compared to `X in C`.

 Setting:
 {{{
 sage: G = SymmetricGroup(5)
 sage: P.<x,y> = QQ[]
 sage: F = PolynomialRing(QQ,[])
 sage: C1 = Rings()
 sage: C2 = G.category()
 sage: C3 = Fields()
 }}}

 Sanity tests:
 {{{
 # test that X in C.objects() is the same as X in C
 # For C1:
 sage: P in C1
 True
 sage: P in C1.objects()
 True
 sage: G in C1
 False
 sage: G in C1.objects()
 False
 sage: F in C1
 True
 sage: F in C1.objects()
 True

 # For C2, which is a join (that's a special case):
 sage: P in C2
 False
 sage: P in C2.objects()
 False
 sage: G in C2
 True
 sage: G in C2.objects()
 True
 sage: F in C2
 False
 sage: F in C2.objects()
 False

 # For C3 (having a custom containment test):
 sage: P in C3
 False
 sage: P in C3.objects()
 False
 sage: G in C3
 False
 sage: G in C3.objects()
 False
 sage: F in C3
 True
 sage: F in C3.objects()
 True
 }}}

 Timings without the new patch (but with #10496, #10659, #8611 and #10467):
 {{{
 # containment in C1
 sage: timeit('P in C1',number=100000)
 100000 loops, best of 3: 11.1 µs per loop
 sage: timeit('G in C1',number=100000)
 100000 loops, best of 3: 4.32 µs per loop
 sage: timeit('F in C1',number=100000)
 100000 loops, best of 3: 11.9 µs per loop
 # containment in C2
 sage: timeit('P in C2',number=100000)
 100000 loops, best of 3: 11.1 µs per loop
 sage: timeit('G in C2',number=100000)
 100000 loops, best of 3: 4.2 µs per loop
 sage: timeit('F in C2',number=100000)
 100000 loops, best of 3: 11.5 µs per loop
 # containment in C3 (custom test for fields!)
 sage: timeit('P in C3',number=100000)
 100000 loops, best of 3: 16.1 µs per loop
 sage: timeit('G in C3',number=100000)
 100000 loops, best of 3: 20.5 µs per loop
 sage: timeit('F in C3',number=100000)
 100000 loops, best of 3: 17.9 µs per loop
 }}}

 Timings with the patch, including the new syntax `X in C.objects()`:
 {{{
 # containment in C1
 sage: timeit('P in C1',number=100000)
 100000 loops, best of 3: 9.29 µs per loop
 sage: timeit('P in C1.objects()',number=100000)
 100000 loops, best of 3: 9.85 µs per loop
 sage: timeit('G in C1',number=100000)
 100000 loops, best of 3: 1.91 µs per loop
 sage: timeit('G in C1.objects()',number=100000)
 100000 loops, best of 3: 2.45 µs per loop
 sage: timeit('F in C1',number=100000)
 100000 loops, best of 3: 9.51 µs per loop
 sage: timeit('F in C1.objects()',number=100000)
 100000 loops, best of 3: 10.2 µs per loop
 # containment in C2
 sage: timeit('P in C2',number=100000)
 100000 loops, best of 3: 9.2 µs per loop
 sage: timeit('P in C2.objects()',number=100000)
 100000 loops, best of 3: 9.85 µs per loop
 # using the shortpath, as G.category() is C2
 sage: timeit('G in C2',number=100000)
 100000 loops, best of 3: 836 ns per loop
 sage: timeit('G in C2.objects()',number=100000)
 100000 loops, best of 3: 1.52 µs per loop
 sage: timeit('F in C2',number=100000)
 100000 loops, best of 3: 9.52 µs per loop
 sage: timeit('F in C2.objects()',number=100000)
 100000 loops, best of 3: 10.3 µs per loop
 # containment in C3 (custom test for fields!)
 sage: timeit('P in C3',number=100000)
 100000 loops, best of 3: 14 µs per loop
 sage: timeit('P in C3.objects()',number=100000)
 100000 loops, best of 3: 15.9 µs per loop
 sage: timeit('G in C3',number=100000)
 100000 loops, best of 3: 15.7 µs per loop
 sage: timeit('G in C3.objects()',number=100000)
 100000 loops, best of 3: 18.3 µs per loop
 sage: timeit('F in C3',number=100000)
 100000 loops, best of 3: 15.6 µs per loop
 sage: timeit('F in C3.objects()',number=100000)
 100000 loops, best of 3: 17.3 µs per loop
 }}}

 Or, directly testing containment in the class of objects:
 {{{
 sage: O1 = C1.objects()
 sage: O2 = C2.objects()
 sage: O3 = C3.objects()
 sage: timeit('P in O1',number=100000)
 100000 loops, best of 3: 8.88 µs per loop
 sage: timeit('G in O1',number=100000)
 100000 loops, best of 3: 1.56 µs per loop
 sage: timeit('F in O1',number=100000)
 100000 loops, best of 3: 9.31 µs per loop
 sage: timeit('P in O2',number=100000)
 100000 loops, best of 3: 8.94 µs per loop
 sage: timeit('G in O2',number=100000)
 100000 loops, best of 3: 704 ns per loop
 sage: timeit('F in O2',number=100000)
 100000 loops, best of 3: 9.29 µs per loop
 sage: timeit('P in O3',number=100000)
 100000 loops, best of 3: 14.9 µs per loop
 sage: timeit('G in O3',number=100000)
 100000 loops, best of 3: 17.1 µs per loop
 sage: timeit('F in O3',number=100000)
 100000 loops, best of 3: 16.5 µs per loop
 }}}

-- 
Ticket URL: <http://trac.sagemath.org/sage_trac/ticket/10667#comment:4>
Sage <http://www.sagemath.org>
Sage: Creating a Viable Open Source Alternative to Magma, Maple, Mathematica, 
and MATLAB

-- 
You received this message because you are subscribed to the Google Groups 
"sage-trac" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/sage-trac?hl=en.

Reply via email to