Skip to content
This repository

Implement CT Base Classes #1338

Merged
merged 57 commits into from almost 2 years ago

4 participants

Sergiu Ivanov Stefan Krastanov Tom Bachmann Ronan Lamy
Sergiu Ivanov

In this pull request I create the module categories, the base classes of this module, tests for them, documentation, and pretty printing.

Stefan Krastanov Krastanov commented on the diff June 10, 2012
doc/src/modules/categories.txt
... ...
@@ -0,0 +1,27 @@
  1
+Category Theory Module
2
Stefan Krastanov Collaborator
Krastanov added a note June 10, 2012

There is an effort to minimize the number of warnings generated by Sphinx. Could you check that the number of warnings with and without your new documentation stays the same (do not forget to run make clean between tests). Be aware that the warnings can come from docstrings and not from the txt files in the doc folder.

Sergiu Ivanov
scolobb added a note June 10, 2012

I get 16 warnings in master and 16 in my branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@scolobb: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYjtYaDA

Interpreter: /opt/pym32/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (32-bit)
Cache: yes
Test command: setup.py test
master hash: 8086d7f
branch hash: 26ea1dc

Automatic review by SymPy Bot.

Sergiu Ivanov

Oh, sorry, I was lazy to run the tests on my computer :-(

Fixing the failures right now :-(

doc/src/modules/categories.txt
... ...
@@ -0,0 +1,27 @@
  1
+Category Theory Module
  2
+======================
  3
+
  4
+.. module:: sympy.categories
  5
+
  6
+Introduction
  7
+------------
  8
+
  9
+The category theory module for SymPy will allow manipulating diagrams
  10
+within a single category, including drawing them in TikZ and deciding
  11
+whether they are commutative or not.
  12
+
  13
+The module is yet in its pre-embryonic stage.
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

s/yet/still/ ?

Sergiu Ivanov
scolobb added a note June 11, 2012

Sure. Wanted to say both "still" and "as yet", perhaps :-D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
... ...
@@ -0,0 +1,654 @@
  1
+from sympy.core import Set, Basic, FiniteSet, EmptySet, Dict
  2
+
  3
+class Class(Set):
  4
+    """
  5
+    The base class for any kind of class in set-theoretic sense.
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

"the" after "in"

Sergiu Ivanov
scolobb added a note June 11, 2012

OK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
... ...
@@ -0,0 +1,654 @@
  1
+from sympy.core import Set, Basic, FiniteSet, EmptySet, Dict
  2
+
  3
+class Class(Set):
  4
+    """
  5
+    The base class for any kind of class in set-theoretic sense.
  6
+
  7
+    In axiomatic set theories, everything is a class.  A class which
  8
+    can is a member of another class is a set.  A class which is not a
4
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

"can be"? or just "is"?

Sergiu Ivanov
scolobb added a note June 11, 2012

I don't think there's a formal difference; however, in intuitive explanations I usually prefer "can be" because it is slightly more casual and easier to grasp for some people. If I say "can be", people say: "OK"; if I say "is", people say: "Hm; what's the container class?". Of course, there always is a container class, but with "can be" the reader is not forced to think of it :-)

Anyway, the use of "can be" is rather arbitrary here; I can switch to "is" if you think it's better.

Tom Bachmann Collaborator
ness01 added a note June 12, 2012

I'm referring to the "can is" ...

Sergiu Ivanov
scolobb added a note June 12, 2012

Oh wow :-( Sorry, fixing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
... ...
@@ -0,0 +1,654 @@
  1
+from sympy.core import Set, Basic, FiniteSet, EmptySet, Dict
  2
+
  3
+class Class(Set):
  4
+    """
  5
+    The base class for any kind of class in set-theoretic sense.
  6
+
  7
+    In axiomatic set theories, everything is a class.  A class which
  8
+    can is a member of another class is a set.  A class which is not a
  9
+    member of another class is a proper class.  The class {1, 2} is a
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

latex the {1, 2}

Sergiu Ivanov
scolobb added a note June 11, 2012

Done.

There's a (minor?) problem, however. I cannot get .. autoclass:: Class to work in categories.txt:

Traceback (most recent call last):categories
  File "/usr/lib64/python2.7/site-packages/sphinx/ext/autodoc.py", line 326, in import_object
    obj = self.get_attr(obj, part)
  File "/usr/lib64/python2.7/site-packages/sphinx/ext/autodoc.py", line 232, in get_attr
    return safe_getattr(obj, name, *defargs)
  File "/usr/lib64/python2.7/site-packages/sphinx/util/inspect.py", line 67, in safe_getattr
    raise AttributeError(name)
AttributeError: Class

I understand that Class isn't the best name for a Python class, and Sphinx is the first to bail out at me because of that. Initially I hoped that hiding Class under sympy.categories would address such problems. Do you think I should rename Class?

Note that Class is absolutely rudimentary at the moment and I expect to actually see it implemented in the set theory module, when the time comes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((30 lines not shown))
  30
+
  31
+    >>> from sympy.categories import Object
  32
+    >>> Object("A") == Object("A")
  33
+    True
  34
+
  35
+    """
  36
+    def __new__(cls, name):
  37
+        if not name:
  38
+            raise ValueError("Anonymous Objects are not allowed.")
  39
+
  40
+        return Basic.__new__(cls, name)
  41
+
  42
+    @property
  43
+    def name(self):
  44
+        """
  45
+        Returns the name of this object.
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

This needs a doctest. [I feel pedantic in pointing it out, and it annoys me to no end in writing my own code, but all new user-facin methods should come with a doctest.

Sergiu Ivanov
scolobb added a note June 11, 2012

I have added doctest for all user-facing methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((34 lines not shown))
  34
+
  35
+    """
  36
+    def __new__(cls, name):
  37
+        if not name:
  38
+            raise ValueError("Anonymous Objects are not allowed.")
  39
+
  40
+        return Basic.__new__(cls, name)
  41
+
  42
+    @property
  43
+    def name(self):
  44
+        """
  45
+        Returns the name of this object.
  46
+        """
  47
+        return self._args[0]
  48
+
  49
+    def __eq__(self, obj):
4
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

apparently the second argument of such methods should be "other"

Tom Bachmann Collaborator
ness01 added a note June 10, 2012

In fact, why do you need this? Shouldn't basic provide these methods?

Sergiu Ivanov
scolobb added a note June 11, 2012

Now, when I store the names of objects in Dummy symbols, Basic.__eq__ will never return True for objects with the same name, because all Dummy's are different.

Changed all (un)equality operators to use "other" to refer to their argument.

Sergiu Ivanov
scolobb added a note June 12, 2012

I'm now using Symbols when the supplied name is not empty, which allows me to fully rely on Basic's comparison and hash functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((47 lines not shown))
  47
+        return self._args[0]
  48
+
  49
+    def __eq__(self, obj):
  50
+        if not isinstance(obj, Object):
  51
+            return False
  52
+
  53
+        return self.name == obj.name
  54
+
  55
+    def __ne__(self, obj):
  56
+        if not isinstance(obj, Object):
  57
+            return True
  58
+
  59
+        return self.name != obj.name
  60
+
  61
+    def __hash__(self):
  62
+        return hash(self.name)
4
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

You should hash in the type as well. But actually, I think this should just re-use the hash method of basic. But actually, if you don't implement __eq__ and __ne__ (see above), then you don't need __hash__ either.

Sergiu Ivanov
scolobb added a note June 11, 2012

Unfortunately, the consequence of using dummies:

In [7]: hash(Dummy("A")) == hash(Dummy("A"))
Out[7]: False

I'm still reluctant to pull in Symbol for just storing a name, though.

Tom Bachmann Collaborator
ness01 added a note June 12, 2012

I think you should use symbol. What's the point in not using a class that is available? My suggestion was:

  • if the user provides a name, store that as a symbol
  • otherwise, store a dummy
Sergiu Ivanov
scolobb added a note June 12, 2012

Well, Symbol is a rather large class, I was reluctant to use it just to store a string. I've changed the code to use Symbol and Dummy now, according to your recommendation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@scolobb: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYlfIbDA

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes
Test command: setup.py test
master hash: 8086d7f
branch hash: 26ea1dc

Automatic review by SymPy Bot.

sympy/categories/baseclasses.py
((60 lines not shown))
  60
+
  61
+    def __hash__(self):
  62
+        return hash(self.name)
  63
+
  64
+class Morphism(Basic):
  65
+    """
  66
+    The base class for any kind of morphism in an abstract category.
  67
+
  68
+    In abstract categories, a morphism is an arrow between two
  69
+    category objects.  The object where the arrow starts is called the
  70
+    domain, while the object where the arrow ends is called the
  71
+    codomain.
  72
+
  73
+    Two simple (not composed) morphisms with the same name, domain,
  74
+    and codomain are the same morphisms.  A simple unnamed morphism is
  75
+    not equal to any other morphism.
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

why are there anonymous morphisms but no anonymous objects?

Sergiu Ivanov
scolobb added a note June 11, 2012

That's because an object is essentially a name, so an object without a name is essentially nothing. On the other hand, a morphism, is an arrow between two objects, which may also have a name. Anonymous morphisms are useful when talking about identities (doesn't matter which name you chose, of course). Also, Diagram makes some use of such Morphisms: when constructing all possible compositions, the new composites are anonymous and are unique.

However, I fixed this:

In [5]: a = Morphism(A, A)
In [6]: a == a
Out[6]: True

which used to be False.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((91 lines not shown))
  91
+    >>> A = Object("A")
  92
+    >>> B = Object("B")
  93
+    >>> C = Object("C")
  94
+    >>> f = Morphism(A, B, "f")
  95
+    >>> g = Morphism(B, C, "g")
  96
+    >>> f == Morphism(A, B, "f")
  97
+    True
  98
+    >>> f == Morphism(A, B, "")
  99
+    False
  100
+    >>> g * f
  101
+    Morphism(Object("B"), Object("C"), "g") *
  102
+    Morphism(Object("A"), Object("B"), "f")
  103
+    >>> id_A = Morphism(A, A, identity=True)
  104
+    >>> id_A == Morphism(A, A, identity=True)
  105
+    True
  106
+    >>> f * id_A == f
3
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

I think you should explain what the __eq__ operator does. [I.e. to what extend does it flatten composed morphisms, use commutative diagrams, whatever.]

Sergiu Ivanov
scolobb added a note June 11, 2012

It's the job of __mul__ (compose, in fact) to discard identity morphisms, in this case. As for __eq__, I believe I did some explanation in the docstring of Morphism (paragraphs 3 and 4). Do you think I should add something more?

Tom Bachmann Collaborator
ness01 added a note June 12, 2012

Hm I think this is ok ... I just wasn't sure (as a user, without looking at the code), how the identity morphisms are taken care of. Since you have a doctest demonstrating this, I think you can leave it as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((102 lines not shown))
  102
+    Morphism(Object("A"), Object("B"), "f")
  103
+    >>> id_A = Morphism(A, A, identity=True)
  104
+    >>> id_A == Morphism(A, A, identity=True)
  105
+    True
  106
+    >>> f * id_A == f
  107
+    True
  108
+
  109
+    """
  110
+    def __new__(cls, domain, codomain, name="", identity=False):
  111
+        if identity and (domain != codomain):
  112
+            raise ValueError(
  113
+                "identity morphisms must have the same domain and codomain")
  114
+
  115
+        # The last component of self.args represents the components of
  116
+        # this morphism.
  117
+        return Basic.__new__(cls, domain, codomain, name, identity, [])
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

The .args in a basic should be Basics as well (I'm not entirely sure, but I think strings are allowd ... @rlamy ?). My point is: the last argument should not be a list, but rather a (sympy) Tuple.

Sergiu Ivanov
scolobb added a note June 11, 2012

For the record: according to the recent discussion on the list, strings are not allowed.

I've changed to list to Tuple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((126 lines not shown))
  126
+    @property
  127
+    def codomain(self):
  128
+        """
  129
+        Returns the codomain of this morphism.
  130
+        """
  131
+        return self.args[1]
  132
+
  133
+    @property
  134
+    def name(self):
  135
+        """
  136
+        Returns the name of this morphism.
  137
+        """
  138
+        return self.args[2]
  139
+
  140
+    @property
  141
+    def identity(self):
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

I think this should be called is_identity.

Sergiu Ivanov
scolobb added a note June 12, 2012

Done. I was having a similar inkling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((147 lines not shown))
  147
+
  148
+    @property
  149
+    def components(self):
  150
+        """
  151
+        Returns the components of this morphisms.
  152
+
  153
+        Examples
  154
+        ========
  155
+
  156
+        >>> from sympy.categories import Object, Morphism
  157
+        >>> A = Object("A")
  158
+        >>> B = Object("B")
  159
+        >>> C = Object("C")
  160
+        >>> f = Morphism(A, B, "f")
  161
+        >>> g = Morphism(B, C, "g")
  162
+        >>> (g * f).components == [f, g]
4
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

Why not just do (g * f).components and doctest the resulting printout?

Sergiu Ivanov
scolobb added a note June 12, 2012

Because

In [13]: str((g * f).components)
Out[13]: (Morphism(Object("A"), Object("B"), "f"), Morphism(Object("B"), Object("C"), "g"))

I think directly comparing to [f, g] (or actually Tuple(f, g) as it is now) is easier to grasp. Do you think I should still show (g * f).components?

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sergiu Ivanov
scolobb added a note June 12, 2012

Showing (g * f).components now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((187 lines not shown))
  187
+
  188
+        >>> from sympy.categories import Object, Morphism
  189
+        >>> A = Object("A")
  190
+        >>> B = Object("B")
  191
+        >>> C = Object("C")
  192
+        >>> f = Morphism(A, B, "f")
  193
+        >>> g = Morphism(B, C, "g")
  194
+        >>> f.compose(g) is None
  195
+        True
  196
+        >>> g.compose(f)
  197
+        Morphism(Object("B"), Object("C"), "g") *
  198
+        Morphism(Object("A"), Object("B"), "f")
  199
+        >>> (g.compose(f, "h")).name
  200
+        'h'
  201
+
  202
+        """
2
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

You should check for the type of g and raise an exception if it is wrong.

Sergiu Ivanov
scolobb added a note June 12, 2012

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((215 lines not shown))
  215
+                             new_name, False, g.components +
  216
+                             self.components)
  217
+
  218
+    def __mul__(self, g):
  219
+        """
  220
+        Returns the result of the composition of ``self`` with the
  221
+        argument, if this composition is defined.
  222
+
  223
+        The semantics of multiplication is as follows: ``f * g =
  224
+        f.compose(g)``.
  225
+
  226
+        See Also
  227
+        =======
  228
+        compose
  229
+        """
  230
+        return self.compose(g)
4
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

Catch the type exception here and return NotImplemented.

Sergiu Ivanov
scolobb added a note June 12, 2012

I read this as "... and raise NotImplemented". Feel free to correct.

Tom Bachmann Collaborator
ness01 added a note June 12, 2012

No, return NotImplemented. That signals to python that your code cannot deal with the other object. Then the interpreter may call other.__rmul__(self), or something along these lines.

Sergiu Ivanov
scolobb added a note June 12, 2012

Oh, I didn't know about that constant and this functionality. Thank you :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((275 lines not shown))
  275
+            return (self.name == g.name) and \
  276
+                   (self.domain == g.domain) and \
  277
+                   (self.codomain == g.codomain)
  278
+        else:
  279
+            # One of the morphisms is composed.  Compare the
  280
+            # components.
  281
+            for (self_component, g_component) in zip(self.components, g.components):
  282
+                if self_component != g_component:
  283
+                    return False
  284
+            return True
  285
+
  286
+    def __ne__(self, g):
  287
+        return not (self == g)
  288
+
  289
+    def __hash__(self):
  290
+        return hash((self.name, self.domain, self.codomain))
6
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

again, hash in the type. Probably safest to just reuse basic.hash.

Sergiu Ivanov
scolobb added a note June 12, 2012

Basic.hash doesn't work nicely with anonymous morphisms (note the composite morphism g * f):

In [20]: f = Morphism(A, B, "f"); g = Morphism(B, C, "g")

In [21]: g * f == g * f
Out[21]: True

In [22]: hash(g * f) == hash(g * f)
Out[22]: False

That's because anonymous morphisms store instances of Dummy, just as it (now) happens in Object. I can only use Symbol's in Morphism at all times; in this case an explicit __hash__ will not be necessary. Do you think I should do that?

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sergiu Ivanov
scolobb added a note June 12, 2012

Are you making it clear somewhere that the name of a morphisms has no relevance (other than for printing) for composite
morphisms?

The docstring in Morphism says:

Two composed morphisms are equal if they have the same components, in the same order (which guarantees the equality of domains and codomains). The names of such composed morphisms are not taken in consideration at comparison.

Sergiu Ivanov
scolobb added a note June 12, 2012

Actually (sorry for not thinking this through earlier): why do composed morphisms have names at all? I see why it can be useful to name morphisms in a diagram (also composed ones, presumably), but that's not really the point. The kinds of names we attach to both morphisms and objects are "internal" - they are used purely to distinguish them, and are not necessarily related to user-visible names.

Yes, agreed.

I would think that composed morphisms are identified by their components, and as such don't need names. In fact, I think composed and named morphisms should live in separate classes!

That's an unexpected point of view, for me :-) And you do raise important points. I'll try to muse a bit here, and I'll then sleep it over.

Initially, I thought that what you suggested sounded like removing the name from Morphism and deriving a class NamedMorphism from Morphism. However, in this case NamedMorphism inherits the components from Morphism. If we do if the other way round (derive Morphism from NamedMorphism), then Morphism will have a name, which defeats the whole point. This means that there should be a third class, MorphismBase, which, uhm, has only a domain and a codomain. CompositeMorphism derives from MorphismBase and can have components, while NamedMorphism derives from MorphismBase and adds a name. Then, hm, we derive IdentityMorphism from MorphismBase(?), because names don't really make much sense for identities, and identities cannot be composite.

Does this go along the lines you were thinking of?

If yes, I can't say I am really enthusiastic, because, to factor out the name of a morphism, we add two classes. Also, there is essentially no way of treating a collection of morphisms uniformly, since some morphisms don't have names, others don't have components; one will always have to check the type. Not that I say it's critical or even necessary; just throwing about ideas.

Generally, I like it when the class model follows the real-world model with great granularity; but in this case I'm afraid whether this isn't going to be an overkill.

Perhaps we could move this discussion to the mailing list so that more people can pronounce their opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((277 lines not shown))
  277
+                   (self.codomain == g.codomain)
  278
+        else:
  279
+            # One of the morphisms is composed.  Compare the
  280
+            # components.
  281
+            for (self_component, g_component) in zip(self.components, g.components):
  282
+                if self_component != g_component:
  283
+                    return False
  284
+            return True
  285
+
  286
+    def __ne__(self, g):
  287
+        return not (self == g)
  288
+
  289
+    def __hash__(self):
  290
+        return hash((self.name, self.domain, self.codomain))
  291
+
  292
+class Category(Basic):
1
Tom Bachmann Collaborator
ness01 added a note June 10, 2012

I feel my concentration fading. I will pick up from here tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@scolobb: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYj9YaDA

Interpreter: /usr/bin/python3 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes
Test command: setup.py test
master hash: 8086d7f
branch hash: 26ea1dc

Automatic review by SymPy Bot.

Tom Bachmann ness01 commented on the diff June 11, 2012
sympy/categories/baseclasses.py
((288 lines not shown))
  288
+
  289
+    def __hash__(self):
  290
+        return hash((self.name, self.domain, self.codomain))
  291
+
  292
+class Category(Basic):
  293
+    r"""
  294
+    An (abstract) category.
  295
+
  296
+    A category [JoyOfCats] is a quadruple `\mbox{K} = (O, \hom, id,
  297
+    \circ)` consisting of
  298
+
  299
+    * a (set-theoretical) class `O`, whose members are called
  300
+      `K`-objects,
  301
+
  302
+    * for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose
  303
+      members are called `K`-morphisms from `A` to `B`,
4
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

so your categories are locally small?

Sergiu Ivanov
scolobb added a note June 12, 2012

Nope, that's the canonical definition of a category.

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sergiu Ivanov
scolobb added a note June 12, 2012

Hm, that's right. However, in the majority of textbooks I've seen (including an edition of McLane's "Categories for the Working Mathematician"), hom-classes are defined to be sets. I will indeed stick with what I have now, but I'll keep the term "locally-small" in mind. Thanks for pointing out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((301 lines not shown))
  301
+
  302
+    * for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose
  303
+      members are called `K`-morphisms from `A` to `B`,
  304
+
  305
+    * for a each `K`-object `A`, a morphism `id:A\rightarrow A`,
  306
+      called the `K`-identity of `A`,
  307
+
  308
+    * a composition law `\circ` associating with every `K`-morphisms
  309
+      `f:A\rightarrow B` and `g:B\rightarrow C` a `K`-morphism `g\circ
  310
+      f:A\rightarrow C`, called the composite of `f` and `g`.
  311
+
  312
+    Composition is associative, `K`-identities are identities with
  313
+    respect to composition, and the sets `\hom(A, B)` are pairwise
  314
+    disjoint.
  315
+
  316
+    This class nothing about its objects and morphisms.  Concrete
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

This sentence seems to be missing a verb.

Sergiu Ivanov
scolobb added a note June 12, 2012

Indeed; fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((321 lines not shown))
  321
+    commutative in a :class:`Category` by supplying the argument
  322
+    ``commutative`` in the constructor.
  323
+
  324
+    Examples
  325
+    ========
  326
+
  327
+    >>> from sympy.categories import Object, Morphism, Diagram, Category
  328
+    >>> from sympy import FiniteSet
  329
+    >>> A = Object("A")
  330
+    >>> B = Object("B")
  331
+    >>> C = Object("C")
  332
+    >>> f = Morphism(A, B, "f")
  333
+    >>> g = Morphism(B, C, "g")
  334
+    >>> d = Diagram([f, g])
  335
+    >>> K = Category("K", commutative=[d])
  336
+    >>> K.commutative == FiniteSet(d)
5
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Again, just do '>>> K.commutative' and test the output.

Sergiu Ivanov
scolobb added a note June 12, 2012

K.commutative gives

 {Diagram({IdentityMorphism(Object("A"), ""): EmptySet(), IdentityMorphism(Object("B"), ""): EmptySet(), IdentityMorphism(Object("C"), ""): EmptySet(), Morphism(Object("A"), Object("B"), "f"): EmptySet(), Morphism(Object("B"), Object("C"), "g") * Morphism(Object("A"), Object("B"), "f"): EmptySet(), Morphism(Object("B"), Object("C"), "g"): EmptySet()}, {}, {Object("B"), Object("C"), Object("A")})}

Should I still go for it?

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sergiu Ivanov
scolobb added a note June 12, 2012

Well, in isympy:

In [6]: A = Object("A"); B = Object("B"); C = Object("C")

In [7]: f = Morphism(A, B, "f"); g = Morphism(B, C, "g")

In [8]: d = Diagram([f, g])

In [9]: d.premises
Out[9]: {A→A: ∅, B→B: ∅, C→C: ∅, f:A→B: ∅, g∘f:A→C: ∅, g:B→C: ∅}

which is fairly not bad IMHO. Do you think I should be using the pretty in the doctests? (use_unicode=False, maybe)

I felt reluctant to do that at the time of writing :-)

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((329 lines not shown))
  329
+    >>> A = Object("A")
  330
+    >>> B = Object("B")
  331
+    >>> C = Object("C")
  332
+    >>> f = Morphism(A, B, "f")
  333
+    >>> g = Morphism(B, C, "g")
  334
+    >>> d = Diagram([f, g])
  335
+    >>> K = Category("K", commutative=[d])
  336
+    >>> K.commutative == FiniteSet(d)
  337
+    True
  338
+
  339
+    See Also
  340
+    ========
  341
+    Diagram
  342
+    """
  343
+    def __new__(cls, name, objects=EmptySet(), commutative=EmptySet()):
  344
+        new_category = Basic.__new__(cls, name, objects, FiniteSet(commutative))
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Why are objects and commutative treated differently. (My guess would be that FiniteSet(commutative) raises an exception if it is not a finite set. If so, please add a short comment. Also I hope this is tested ;).)

Sergiu Ivanov
scolobb added a note June 12, 2012

Hm, that's a good point. I thought of the argument objects as of a (set theoretic) class, in which case it seemed that I didn't have to explicitly convert objects to Class. I now do Class(objects). And yeah, I've added the tests :-)

There were no tests before because I don't really do anything to objects in Category and neither do I plan to. Nevertheless, I've now added the tests :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((347 lines not shown))
  347
+    @property
  348
+    def name(self):
  349
+        """
  350
+        Returns the name of this category.
  351
+        """
  352
+        return self.args[0]
  353
+
  354
+    @property
  355
+    def objects(self):
  356
+        """
  357
+        Returns the class of objects of this category.
  358
+        """
  359
+        return self.args[1]
  360
+
  361
+    @property
  362
+    def commutative(self):
4
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

I think this should have a better name (like commutative_diagrams, but feel free to pick)

Sergiu Ivanov
scolobb added a note June 12, 2012

I totally agree that commutative_diagrams is better, but isn't it too long?

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sergiu Ivanov
scolobb added a note June 12, 2012

Changed to commutative_diagrams, which is definitely better than just commutative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((401 lines not shown))
  401
+
  402
+    No checks are carried out of whether the supplied object and
  403
+    morphisms do belong to one and the same category.
  404
+
  405
+    Examples
  406
+    ========
  407
+
  408
+    >>> from sympy.categories import Object, Morphism, Diagram
  409
+    >>> from sympy import FiniteSet
  410
+    >>> A = Object("A")
  411
+    >>> B = Object("B")
  412
+    >>> C = Object("C")
  413
+    >>> f = Morphism(A, B, "f")
  414
+    >>> g = Morphism(B, C, "g")
  415
+    >>> d = Diagram([f, g])
  416
+    >>> Morphism(A, A, identity=True) in d.premises.keys()
4
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

If it's not too long, why not just show d.premises?

Sergiu Ivanov
scolobb added a note June 12, 2012

d.premises gives

 {IdentityMorphism(Object("A"), ""): EmptySet(), IdentityMorphism(Object("B"), ""): EmptySet(), IdentityMorphism(Object("C"), ""): EmptySet(), Morphism(Object("A"), Object("B"), "f"): EmptySet(), Morphism(Object("B"), Object("C"), "g") * Morphism(Object("A"), Object("B"), "f"): EmptySet(), Morphism(Object("B"), Object("C"), "g"): EmptySet()}

d.premises.keys:

[Morphism(Object("B"), Object("C"), "g"), IdentityMorphism(Object("C"), ""), Morphism(Object("A"), Object("B"), "f"), IdentityMorphism(Object("A"), ""), IdentityMorphism(Object("B"), ""), Morphism(Object("B"), Object("C"), "g") * Morphism(Object("A"), Object("B"), "f")]

In my book that qualifies as too long :-) However, I can try to break it down into lines so that it gets more readable.

Tom Bachmann Collaborator
ness01 added a note June 12, 2012
Sergiu Ivanov
scolobb added a note June 12, 2012

I use pretty now, so it looks like

>>> print pretty(d.premises, use_unicode=False)                                                                                                                       
{A->A: EmptySet(), B->B: EmptySet(), C->C: EmptySet(), f:A->B: EmptySet(), g*f                                                                                        
:A->C: EmptySet(), g:B->C: EmptySet()}                                  

and

{g*f:A->C: {unique}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((444 lines not shown))
  444
+        if key in dictionary:
  445
+            dictionary[key] = dictionary[key] | value
  446
+            return True
  447
+        else:
  448
+            dictionary[key] = value
  449
+            return False
  450
+
  451
+    @staticmethod
  452
+    def _add_morphism(morphisms, morphism, props, add_identities=True):
  453
+        """
  454
+        Adds a morphism and its attributes to the supplied dictionary
  455
+        ``morphisms``.  If ``add_identities`` is True, also adds the
  456
+        identity morphisms for the domain and the codomain of
  457
+        ``morphism``.
  458
+        """
  459
+        if Diagram._set_dict_union(morphisms, morphism, props) == False:
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

never compare to True or False if you know the result already is True or False. I.e. make this if not ...:

Sergiu Ivanov
scolobb added a note June 12, 2012

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tom Bachmann ness01 commented on the diff June 11, 2012
sympy/categories/baseclasses.py
((467 lines not shown))
  467
+
  468
+                id_dom = Morphism(morphism.domain, morphism.domain, identity=True)
  469
+                id_cod = Morphism(morphism.codomain, morphism.codomain, identity=True)
  470
+                Diagram._set_dict_union(morphisms, id_dom, empty)
  471
+                Diagram._set_dict_union(morphisms, id_cod, empty)
  472
+
  473
+            for existing_morphism, existing_props in morphisms.items():
  474
+                new_props = existing_props & props
  475
+                if morphism.domain == existing_morphism.codomain:
  476
+                    left = morphism * existing_morphism
  477
+                    Diagram._set_dict_union(morphisms, left, new_props)
  478
+                if morphism.codomain == existing_morphism.domain:
  479
+                    right = existing_morphism * morphism
  480
+                    Diagram._set_dict_union(morphisms, right, new_props)
  481
+
  482
+    def __new__(cls, *args):
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Add a docstring here explaining args.

Sergiu Ivanov
scolobb added a note June 12, 2012

Oh yes, sure. Added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((538 lines not shown))
  538
+    @property
  539
+    def premises(self):
  540
+        """
  541
+        Returns the premises of this diagram.
  542
+
  543
+        Examples
  544
+        ========
  545
+        >>> from sympy.categories import Object, Morphism, Diagram
  546
+        >>> from sympy import EmptySet, Dict
  547
+        >>> A = Object("A")
  548
+        >>> B = Object("B")
  549
+        >>> f = Morphism(A, B, "f")
  550
+        >>> id_A = Morphism(A, A, identity=True)
  551
+        >>> id_B = Morphism(B, B, identity=True)
  552
+        >>> d = Diagram([f])
  553
+        >>> d.premises == Dict({f:EmptySet(), id_A:EmptySet(), id_B:EmptySet()})
3
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

again, don't test for equality, just test the printed output. [I'll stop pointing this out now :).]

Sergiu Ivanov
scolobb added a note June 12, 2012

I've fixed those occurrences of this habit about which I had no questions :-)

Sergiu Ivanov
scolobb added a note June 12, 2012

Well, I've left some "old-style" examples somewhere, just for the sake of diversity in examples (since they often show one almost one and the same thing).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((636 lines not shown))
  636
+
  637
+        for morphism in self.premises.keys():
  638
+            if (morphism.domain == A) and (morphism.codomain == B):
  639
+                premises |= FiniteSet(morphism)
  640
+        for morphism in self.conclusions.keys():
  641
+            if (morphism.domain == A) and (morphism.codomain == B):
  642
+                conclusions |= FiniteSet(morphism)
  643
+
  644
+        return (premises, conclusions)
  645
+
  646
+    def __eq__(self, other):
  647
+        if not isinstance(other, Diagram):
  648
+            return False
  649
+
  650
+        return (self.premises == other.premises) and \
  651
+               (self.conclusions == other.conclusions)
6
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Why don't you also compare objects? [I guess this is why you don't want to use Basic.eq?] Also, you need to provide hash.

Sergiu Ivanov
scolobb added a note June 12, 2012

objects fully depends on premises and I only store it with the goal of not rebuilding the list every time it is needed. Actually, I'm not that sure whether it's a good idea to keep objects in .args. Since I am using it for caching purposes, perhaps I should employ just a private attribute?

If we decide that objects stays in .args, I'll write the __hash__ method. Slipped by me back then :-(

Tom Bachmann Collaborator
ness01 added a note June 13, 2012

But even if the objects are essentially just cached, they are determined deterministically from premises. So it wouldn't change the behaviour to compare them as well? [Don't bother about the performance difference.]

Sergiu Ivanov
scolobb added a note June 13, 2012

Nope, when I remove the comparison operators, the tests still pass. Do you think I should just remove custom comparison?

Tom Bachmann Collaborator
ness01 added a note June 13, 2012

I think that is best.

Sergiu Ivanov
scolobb added a note June 13, 2012

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/tests/test_baseclasses.py
((65 lines not shown))
  65
+
  66
+    u1 = u.flatten()
  67
+
  68
+    assert u1.domain == A
  69
+    assert u1.codomain == D
  70
+    assert u1.name == ""
  71
+    assert u1.components == [u1]
  72
+
  73
+    u1 = u.flatten("u")
  74
+
  75
+    assert u1.domain == A
  76
+    assert u1.codomain == D
  77
+    assert u1.name == "u"
  78
+    assert u1.components == [u1]
  79
+
  80
+    assert f == Morphism(A, B, "f")
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

This was already tested above?

Sergiu Ivanov
scolobb added a note June 12, 2012

Oh, absolutely. Fixing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/tests/test_baseclasses.py
((70 lines not shown))
  70
+    assert u1.name == ""
  71
+    assert u1.components == [u1]
  72
+
  73
+    u1 = u.flatten("u")
  74
+
  75
+    assert u1.domain == A
  76
+    assert u1.codomain == D
  77
+    assert u1.name == "u"
  78
+    assert u1.components == [u1]
  79
+
  80
+    assert f == Morphism(A, B, "f")
  81
+    assert f != g
  82
+    assert f != Morphism(A, B, "")
  83
+    assert Morphism(A, B, "") != Morphism(A, B, "")
  84
+
  85
+    assert hash(f) == hash(Morphism(A, B, "f"))
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

This as well.

Sergiu Ivanov
scolobb added a note June 12, 2012

I cannot see where I test it once more :-)

I have however done some reshuffling to test_morphism and dropped a few more repetitions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/tests/test_baseclasses.py
((77 lines not shown))
  77
+    assert u1.name == "u"
  78
+    assert u1.components == [u1]
  79
+
  80
+    assert f == Morphism(A, B, "f")
  81
+    assert f != g
  82
+    assert f != Morphism(A, B, "")
  83
+    assert Morphism(A, B, "") != Morphism(A, B, "")
  84
+
  85
+    assert hash(f) == hash(Morphism(A, B, "f"))
  86
+
  87
+    id_A = Morphism(A, A, identity=True)
  88
+    id_B = Morphism(B, B, identity=True)
  89
+
  90
+    assert id_A.identity == True
  91
+    assert id_A == Morphism(A, A, name="f", identity=True)
  92
+    assert id_A != Morphism(A, A, name="f")
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Maybe also test id_A != id_B

Sergiu Ivanov
scolobb added a note June 12, 2012

Added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/tests/test_baseclasses.py
((95 lines not shown))
  95
+    assert f * id_A == f
  96
+    assert id_B * f == f
  97
+
  98
+    raises(ValueError, lambda: Morphism(A, B, identity=True))
  99
+
  100
+def test_diagram():
  101
+    A = Object("A")
  102
+    B = Object("B")
  103
+    C = Object("C")
  104
+
  105
+    f = Morphism(A, B, "f")
  106
+    g = Morphism(B, C, "g")
  107
+    id_A = Morphism(A, A, "1_A")
  108
+    id_B = Morphism(B, B, "1_B")
  109
+
  110
+    empty = EmptySet()
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Note that, here and elsewhere, you can also access the empty set as S.EmptySet (but admittedly that's a little long, so what you do is fine.)

Sergiu Ivanov
scolobb added a note June 12, 2012

I don't really know why I didn't use S.EmptySet, since it seems that I knew about it at the time of writing :-) Thanks for pointing out :-) (Since you say it's fine as it is now, I'm not going to change it, though.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/tests/test_baseclasses.py
((107 lines not shown))
  107
+    id_A = Morphism(A, A, "1_A")
  108
+    id_B = Morphism(B, B, "1_B")
  109
+
  110
+    empty = EmptySet()
  111
+
  112
+    # Test the addition of identities.
  113
+    d1 = Diagram([f])
  114
+
  115
+    assert d1.objects == FiniteSet(A, B)
  116
+    assert d1.hom(A, B) == (FiniteSet(f), empty)
  117
+    assert d1.hom(A, A) == (FiniteSet(Morphism(A, A, identity=True)), empty)
  118
+    assert d1.hom(B, B) == (FiniteSet(Morphism(B, B, identity=True)), empty)
  119
+
  120
+    # Test the addition of composites.
  121
+    d2 = Diagram([f, g])
  122
+    homAC = d2.hom(A, C)[0]
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

you should do something with homAC, shouldn't you? ^^

Sergiu Ivanov
scolobb added a note June 12, 2012

Oh yes, sure :-D Sorry, fixing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tom Bachmann ness01 commented on the diff June 11, 2012
sympy/categories/baseclasses.py
((385 lines not shown))
  385
+    the diagram.  For a more formal approach to this notion see
  386
+    [Pare1970].
  387
+
  388
+    A commutative diagram is often accompanied by a statement of the
  389
+    following kind: "if such morphisms with such properties exist,
  390
+    then such morphisms which such properties exist and the diagram is
  391
+    commutative".  To represent this, an instance of :class:`Diagram`
  392
+    includes a collection of morphisms which are the premises and
  393
+    another collection of conclusions.  ``premises`` and
  394
+    ``conclusions`` associate morphisms belonging to the corresponding
  395
+    categories with the :class:`FiniteSet`'s of their properties.
  396
+
  397
+    The set of properties of a composite morphism is the intersection
  398
+    of the sets of properties of its components.  The domain and
  399
+    codomain of a conclusion morphism should be among the domains and
  400
+    codomains of the morphisms listed as the premises of a diagram.
3
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

Where do properties come from? Are they just ad-hoc string names?

Sergiu Ivanov
scolobb added a note June 12, 2012

Yep, they're just ad hoc string names for now. I envision two situations in which properties will work:

  • In drawing the diagram, certain properties will condition different arrow styles.

  • In deciding the commutativity of a diagram, the inference code will rely on properties when pattern-matching diagrams.

In the former case, the corresponding properties will be documented in the drawing code. In the latter case, it's up to the user to decide which properties to use; the only requirement is to utilise them consistently so that pattern-matching shouldn't break.

Tom Bachmann Collaborator
ness01 added a note June 13, 2012

Makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/printing/pretty/pretty.py
((22 lines not shown))
  1467
+            for component in reversed(morphism.components):
  1468
+                if not component.name:
  1469
+                    # Composition with an anonymous morphism is an
  1470
+                    # anonymous morphism.
  1471
+                    return prettyForm(tail)
  1472
+
  1473
+                pretty_name += pretty_symbol(component.name) + circle
  1474
+
  1475
+            pretty_name = pretty_name[:-1]
  1476
+
  1477
+        pretty_name_form = self._print(pretty_name)
  1478
+        return prettyForm(pretty_name_form.right(":", tail)[0])
  1479
+
  1480
+    def _print_Category(self, category):
  1481
+        return self._print(pretty_symbol(category.name))
  1482
+
5
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

I suppose _print_Diagram will come only later?

Sergiu Ivanov
scolobb added a note June 12, 2012

Well, I believe that the default pretty printing an example of which I showed in this comment is quite OK. As for LaTeX pretty printing, I'm not sure to what extent IPython notebooks support LaTeX and its packages. It would be great if the full package set is supported, but if it isn't, I don't think implementing some custom drawing is worth it.

Sergiu Ivanov
scolobb added a note June 12, 2012

Oh, OK, I missed the fact that the default printing of Diagram uses string representations of Morphism and Object, not pretty representations :-(

I think I should write a simple _print_Diagram which would show the diagram as two dictionaries, formatted similarly to what is show in this comment (which I have already invoked). Do you have adverse arguments?

Tom Bachmann Collaborator
ness01 added a note June 13, 2012

Yes this sounds like a good idea.

Note that ipython notebooks are not the only reason to implement latex printing. But I agree that there seems no reasonable way (at the moment) to print diagrams in latex, so don't bother. When the real diagram printing code is there we can see if it is practical to invoke during latex printing.

Sergiu Ivanov
scolobb added a note June 13, 2012

For the sake of completeness, I've nevertheless added LaTeX pretty printing which exactly mimics Unicode printing. It's not going to be hard to remove should need happen :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/printing/pretty/tests/test_pretty.py
((5 lines not shown))
  3695
+def test_categories():
  3696
+    from sympy.categories import Object, Morphism, Category
  3697
+    A1 = Object("A1")
  3698
+    A2 = Object("A2")
  3699
+    A3 = Object("A3")
  3700
+
  3701
+    f1 = Morphism(A1, A2, "f1")
  3702
+    f2 = Morphism(A2, A3, "f2")
  3703
+    anonymous = Morphism(A1, A3, "")
  3704
+
  3705
+    K1 = Category("K1")
  3706
+
  3707
+    assert pretty(A1) == "A1"
  3708
+    assert upretty(A1) == u"A₁"
  3709
+
  3710
+    assert pretty(f1) == "f1:A1->A2"
4
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

I personally think this looks a bit crowded. How about a space after the colon?

Sergiu Ivanov
scolobb added a note June 12, 2012

Let me try it right here: f: A->B. I've also had this idea: f:A-->B.

I think I like the longer arrow slightly better. Do you think not having a space after the colon is a bad thing if we make the arrow longer?

Sergiu Ivanov
scolobb added a note June 12, 2012

There's a slight advantage to a longer arrow in the fact that it also works for anonymous morphisms: A-->B :-) Here comes a real-life example, formatted according to both suggestions:

{A->A: EmptySet(), B->B: EmptySet(), C->C: EmptySet(), f: A->B: EmptySet(), g*f: A->C: EmptySet(), g: B->C: EmptySet()}
{A-->A: EmptySet(), B-->B: EmptySet(), C-->C: EmptySet(), f:A-->B: EmptySet(), g*f:A-->C: EmptySet(), g:B-->C: EmptySet()}
Tom Bachmann Collaborator
ness01 added a note June 13, 2012

What I don't like about the --> is that it looks like a rational map to me. But you can use unicode in pretty printing to get a non-broken long arrow, and I think this looks good. Here is how I print homomorphisms in agca:

In [2]: RepeatedIsomorphismComplex(QQ[x].free_module(1).identity_hom())
Out[2]: 
          1         1        
0 <── ℚ[x]  <── ℚ[x]  <── ...

In [4]: QQ[x].free_module(2).identity_hom()
Out[4]: 
⎡1  0⎤       2         2
⎢    ⎥ : ℚ[x]  ──> ℚ[x] 
⎣0  1⎦                

You see that I generally use quite a lot of spaces. But this is in real ipython pretty-printing. For dictionaries like you give example above, I think no space between "f:" and the arrow is best.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/printing/str.py
... ...
@@ -526,6 +526,18 @@ def _print_DMP(self, p):
526 526
     def _print_DMF(self, expr):
527 527
         return self._print_DMP(expr)
528 528
 
  529
+    def _print_Object(self, object):
  530
+        return 'Object("%s")' % object.name
  531
+
  532
+    def _print_Morphism(self, morphism):
  533
+        result = ""
  534
+        for component in reversed(morphism.components):
  535
+            result += 'Morphism(%s, %s, "%s") * ' % \
  536
+                      (component.domain, component.codomain, component.name)
  537
+        return result[:-3]
  538
+
  539
+    def _print_Category(self, category):
  540
+        return 'Category("%s")' % category.name
2
Tom Bachmann Collaborator
ness01 added a note June 11, 2012

_print_diagram?

Sergiu Ivanov
scolobb added a note June 12, 2012

Here I believe the default string representation will do:

Diagram({IdentityMorphism(Object("A"), ""): EmptySet(), IdentityMorphism(Object("B"), ""): EmptySet(), IdentityMorphism(Object("C"), ""): EmptySet(), Morphism(Object("A
"), Object("B"), "f"): EmptySet(), Morphism(Object("B"), Object("C"), "g") * Morphism(Object("A"), Object("B"), "f"): EmptySet(), Morphism(Object("B"), Object("C"), "g"
): EmptySet()}, {}, {Object("B"), Object("C"), Object("A")})

It is longish, that's right, but a lot of string representations of stuff are longish in SymPy ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tom Bachmann
Collaborator
ness01 commented June 11, 2012

I'm finished with the initial review pass. Let me know when you are done addressing the comments.

Sergiu Ivanov

The two new commits add IdentityMorphism and do other, more cosmetic modifications to the existing classes to them comply with the requirement of having only instances of Basic in .args. I expect the tests to pass now (2.7 and 2.5 pass on my box).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@scolobb: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY3r4aDA

Interpreter: /opt/pym32/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (32-bit)
Cache: yes
Test command: setup.py test
master hash: 2d16ab9
branch hash: 32996a7

Automatic review by SymPy Bot.

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@scolobb: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY6owbDA

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes
Test command: setup.py test
master hash: 2d16ab9
branch hash: 32996a7

Automatic review by SymPy Bot.

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@scolobb: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY990aDA

Interpreter: /usr/bin/python3 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes
Test command: setup.py test
master hash: 2d16ab9
branch hash: 32996a7

Automatic review by SymPy Bot.

Sergiu Ivanov

Judging by the character of failures, they are conditioned by the fact that my branch is slightly behind master. I'll finish complying with the comments and will rebase then.

See this thread.

sympy/categories/baseclasses.py
((26 lines not shown))
  26
+
  27
+    Two objects with the same name are the same object.
  28
+
  29
+    Examples
  30
+    ========
  31
+
  32
+    >>> from sympy.categories import Object
  33
+    >>> Object("A") == Object("A")
  34
+    True
  35
+
  36
+    """
  37
+    def __new__(cls, name):
  38
+        if not name:
  39
+            raise ValueError("Anonymous Objects are not allowed.")
  40
+
  41
+        return Basic.__new__(cls, Dummy(name))
5
Ronan Lamy Collaborator
rlamy added a note June 13, 2012

You shouldn't abuse Dummy like that, it will cause you trouble.

Stefan Krastanov Collaborator
Krastanov added a note June 13, 2012
Ronan Lamy Collaborator
rlamy added a note June 13, 2012

A Dummy() is a dummy variable with a complex value, which has basically nothing to do with the name of an abstract object. The semantic mismatch will cause problems and require hacks. It has actually already done so, see below.

Tom Bachmann Collaborator
ness01 added a note June 13, 2012

Note that Sergiu is about to update this part: the name will be stored as Symbol, unless the name is empty. The latter case is supposed to indicate an "anonymous" object, no two of which are supposed to be the same. I suggested Dummy for this case - it seems to match the behaviour of Dummy quite precisely. Is it still a bad idea?

Sergiu Ivanov
scolobb added a note June 13, 2012

I have already updated this, I just haven't pushed the updated branch because I have played with git rebase a little bit so I didn't want to mess up the comments.

Also note that if we do transition to a more complex class structure as discussed in this thread, Dummy will not be used at all, since anonymous Object's are not supported any more and the same fate is going to happen to anonymous Morphism's.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((6 lines not shown))
  6
+    The base class for any kind of class in set-theoretic sense.
  7
+
  8
+    In axiomatic set theories, everything is a class.  A class which
  9
+    can is a member of another class is a set.  A class which is not a
  10
+    member of another class is a proper class.  The class {1, 2} is a
  11
+    set; the class of all sets is a proper class.
  12
+
  13
+    This class is essentially a synonym for :class:`sympy.core.Set`.
  14
+    The goal of this class is to assure easier migration to the
  15
+    eventual proper implementation of set theory.
  16
+    """
  17
+    is_proper = False
  18
+
  19
+class Object(Basic):
  20
+    """
  21
+    The base class for any kind of object in an abstract category.
2
Ronan Lamy Collaborator
rlamy added a note June 13, 2012

If a category can contain any kind of object, then any instance of Basic should work. So do you actually need the Object class at all?

Sergiu Ivanov
scolobb added a note June 13, 2012

If a category can contain any kind of object, then any instance of Basic should work.

Yes, that was my plan.

So do you actually need the Object class at all?

Yes, because in the most abstract setting I need a class which will only hold a name. When thinking about this a week ago, I considered using Symbol instead, but then I figured that it would be better to use a narrower solution. I do think now that it may be possible to derive Object from Symbol directly, instead of Basic. I am still strongly inclined to think that I need a special Object class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tom Bachmann
Collaborator
ness01 commented June 14, 2012

@scolobb please let me know when you think you are done here.

Sergiu Ivanov

This is finally the new version of the code. @ness01, I think I'm done addressing the first wave of comments :-)

Tests with Python 2.7 pass. I'll now check 2.5 and 3.2 as well.

Tom Bachmann ness01 commented on the diff June 15, 2012
sympy/categories/baseclasses.py
((24 lines not shown))
  24
+    class is the recommended way to create abstract objects in
  25
+    abstract categories.
  26
+    """
  27
+
  28
+class Morphism(Basic):
  29
+    """
  30
+    The base class for any morphism in an abstract category.
  31
+
  32
+    In abstract categories, a morphism is an arrow between two
  33
+    category objects.  The object where the arrow starts is called the
  34
+    domain, while the object where the arrow ends is called the
  35
+    codomain.
  36
+
  37
+    Two morphisms between the same pair of objects are considered to
  38
+    be the same morphisms.  To distinguish between morphisms between
  39
+    the same objects use :class:`NamedMorphism`.
3
Tom Bachmann Collaborator
ness01 added a note June 15, 2012

Is it a good idea to allow instantiation of this class? Why not just make __new__ raise some sort of exception, making it clear that one of the derived classes has to be used? Or in fact make __new__ have the interface you wanted first (with name and identity options), but return a derived class.

Sergiu Ivanov
scolobb added a note June 15, 2012

Well, I had the impression that anonymous morphisms, which are instances of Morphism, could be useful. I'm not that sure now that you've asked. I use anonymous morphisms rather extensively in tests and examples, but I can't say whether it's such a good idea. Moreover, I start to think that anonymous moprhisms are bad for the same reason as anonymous objects are bad.

All right, I'll redo it right away, thank you for changing my mind :-)

Sergiu Ivanov
scolobb added a note June 15, 2012

Yeah, I've just went through the code once again; it looks like I only use instances of Morphism when I'm lazy to use NamedMorphism's. And it turns out that whenever I need something beyond creating a morphism, anonymous morphisms do not suffice, which confirms your point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((173 lines not shown))
  173
+    Examples
  174
+    ========
  175
+
  176
+    >>> from sympy.categories import Object, Morphism, NamedMorphism
  177
+    >>> A = Object("A")
  178
+    >>> B = Object("B")
  179
+    >>> f = NamedMorphism(A, B, "f")
  180
+    >>> f
  181
+    NamedMorphism(Object("A"), Object("B"), "f")
  182
+    >>> f.name
  183
+    'f'
  184
+
  185
+    See Also
  186
+    ========
  187
+
  188
+    Moprhism
1
Tom Bachmann Collaborator
ness01 added a note June 15, 2012

spelling ^^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((335 lines not shown))
  335
+        >>> B = Object("B")
  336
+        >>> C = Object("C")
  337
+        >>> f = Morphism(A, B)
  338
+        >>> g = Morphism(B, C)
  339
+        >>> (g * f).domain
  340
+        Object("A")
  341
+
  342
+        """
  343
+        return self.components[0].domain
  344
+
  345
+    @property
  346
+    def codomain(self):
  347
+        """
  348
+        Returns the codomain of this composite morphism.
  349
+
  350
+        The domain of the composite morphism is the codomain of its
1
Tom Bachmann Collaborator
ness01 added a note June 15, 2012

The codomain of ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
doc/src/modules/categories.txt
... ...
@@ -0,0 +1,36 @@
  1
+Category Theory Module
  2
+======================
  3
+
  4
+.. module:: sympy.categories
  5
+
  6
+Introduction
  7
+------------
  8
+
  9
+The category theory module for SymPy will allow manipulating diagrams
  10
+within a single category, including drawing them in TikZ and deciding
  11
+whether they are commutative or not.
  12
+
  13
+The module is still in its pre-embryonic stage.
1
Tom Bachmann Collaborator
ness01 added a note June 15, 2012

Mention here the reference [JoyOfCats] again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/categories/baseclasses.py
((545 lines not shown))
  545
+
  546
+    No checks are carried out of whether the supplied object and
  547
+    morphisms do belong to one and the same category.
  548
+
  549
+    Examples
  550
+    ========
  551
+
  552
+    >>> from sympy.categories import Object, NamedMorphism, Diagram
  553
+    >>> from sympy import FiniteSet, pretty
  554
+    >>> A = Object("A")
  555
+    >>> B = Object("B")
  556
+    >>> C = Object("C")
  557
+    >>> f = NamedMorphism(A, B, "f")
  558
+    >>> g = NamedMorphism(B, C, "g")
  559
+    >>> d = Diagram([f, g])
  560
+    >>> print pretty(d.premises.keys(), use_unicode=False)
1
Tom Bachmann Collaborator
ness01 added a note June 15, 2012

just use pprint [here and elsewhere]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/printing/pretty/pretty.py
... ...
@@ -1446,6 +1446,71 @@ def _print_DMP(self, p):
1446 1446
     def _print_DMF(self, p):
1447 1447
         return self._print_DMP(p)
1448 1448
 
  1449
+    def _print_Object(self, object):
  1450
+        return self._print(pretty_symbol(object.name))
  1451
+
  1452
+    def _print_Morphism(self, morphism):
  1453
+        arrow = "-->"
  1454
+        if self._use_unicode:
  1455
+            arrow = u"\u27f6  "
  1456
+            circle = u"\u2218"
3
Tom Bachmann Collaborator
ness01 added a note June 15, 2012

circle seems not to be used here?

Sergiu Ivanov
scolobb added a note June 15, 2012

Nope, it's not :-)

By the way, it is not possible to create a Morphism anymore, yet this function is used by _print_NamedMorphism and _print_CompositeMorphism, therefore I have left it here for now. If having such a function is not OK (after all, Morphism can never be instatiated), I'm not sure what would be the way out. I obviously feel against duplicating the functionality in the two functions.

Tom Bachmann Collaborator
ness01 added a note June 16, 2012

No that's completely fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tom Bachmann
Collaborator
ness01 commented June 15, 2012

I think this is almost good to go. The tests and doctests pass on my system. coverage_doctest reports 69% which is pretty good (in my experience this tool is extremely pedantic...); Note that running with --verbose shows you what is missing. Also the coverage (run bin/coverage_report sympy/categories) is pretty good and at 94%. I think it is probably a good idea to boost this to 99% or so (should take no longer than 5 minutes, I think).

added some commits May 28, 2012
Sergiu Ivanov Add Class, Object, and Morphism.
These classes represent the very bowels of the module.  They are
yet in their incipient form, which is sufficient, however, to start
writing the tests.
fe78af1
Sergiu Ivanov Write the tests for categories.Object and categories.Morphism.
The tests do not pass at the moment.  The goal is to define the desired
behaviour of the methods in Morphism by means of tests.
133bb56
Sergiu Ivanov Implement composing and flattening morphisms.
Composing two morphisms results in a morphism which knows which
morphisms it was composed from.  Flattening such a morphism results in
forgetting the information about components.
f888e7e
Sergiu Ivanov Extend the string printer to print Object and Morphism.
Also add the corresponding tests.
631cb3d
Sergiu Ivanov When composing morphisms, intersperse morphism names with '*'.
The initial approach to use spaces to separate morphisms names proved
inconsistent with the later idea to use '*' when printing morphism
composition.
1ee252d
Sergiu Ivanov Amend the string representation of composition of morphisms.
The initial version did not comply with the requirement that the
represented object be able to be reconstructed from the string
representation.
bab67ea
Sergiu Ivanov Don't automatically construct the name of the composed morphism.
This also concerns flattened morphisms.  While initially it seemed to be
a good idea to automatically construct new morphism names, it proved
later that trying to be clever when it's not necessary is never good.
ff04329
Sergiu Ivanov Add pretty printing for Object and Morphism.
Pretty printing supports anonymous objects and morphisms.  Also add the
corresponding tests.
2e81231
Sergiu Ivanov Add printing to LaTeX.
Also add the tests for printing to LaTeX.  Anonymous objects and
morphisms are supported.
9334d84
Sergiu Ivanov Add (un)equality operators to Object and Morphism.
Also extend the docstrings and the tests to cover the added semantics.
In morphism equality, composed morphisms are only compared by their
components.  Thus, the names of composed morphisms are insignificant at
comparison.
9cf15ea
Sergiu Ivanov Add some nice formatting to the docstrings.
Follow the formatting style found in, for example,
sympy.core.symbol.
044cb74
Sergiu Ivanov Add examples to Object and Morphism. 65da9ae
Sergiu Ivanov Update the comment in __init__.py to include the principal reference.
This book should be referenced by the notation [JoyOfCats].
7866fc8
Sergiu Ivanov Add Category.
This is the base class for all categories.  It will contain very little
functionality related to objects and morphisms.
d539ba1
Sergiu Ivanov Extend string, LaTeX, and pretty printers to support Category.
Only the name of the Category is currently printed.  It is improbable
that much additional information will ever be included in the pretty
printed representation of a Category, just because it is so in the books
on the topic.
629f648
Sergiu Ivanov Add the skeleton of Diagram.
This commit defines the class and its methods without functionality.
The functionality itself will be implemented after the tests have been
added.
fd0a694
Sergiu Ivanov Add some tests for Diagram.
The tests also attempt to cover some subtle aspects of how the diagram
treats its objects and morphism.
462a5e1
Sergiu Ivanov In Morphism, define __new__ to allow keyword arguments.
Since Morphism derives from Basic, and Basic.__new__ does not accept
keyword arguments, Morphism could not be constructed with keyword
arguments.  While this is all right for simple constructors like those
of Object and Category, the constructor of Morphism will quickly become
awkward to use if the user will be required to supply all arguments in
order.
c9b027b
Sergiu Ivanov Implement identity morphisms.
A Morphism can now be defined to be an identity.  Identity morphisms are
defined not to influence composition.  All identity morphisms of one and
the same object are equal.
fa74c09
Sergiu Ivanov Make Morphism and Object check for the type of the other object in co…
…mparisons.

Objects of these classes would previously throw exceptions when compared
to something improper (like None).
207ee8a
Sergiu Ivanov Fix the tests for Diagram to properly use identity morphism.
Identity morphisms were implemented after the tests for Diagram, so
there was a need to slightly correct them to properly use identities.
7fcee70
Sergiu Ivanov Implement Diagram.
Also add some examples to the docstrings.  Diagram currently relies on
the Basic to do the hashing, because Basic seems to get things right in
this regard.
0a33621
Sergiu Ivanov Have Category store a set of commutative diagrams.
Not much more beyond storing a set of diagrams is done at the moment.
26debd8
Sergiu Ivanov Add a documentation chapter about the category theory module.
The page currently doesn't currently do much beyond pulling in the
corresponding docstrings and, in fact, that's the purpose of adding the
chapter at this moment.
2723826
Sergiu Ivanov Redo the docstrings to make them look better.
This includes slight changes to examples, as well as (ab)using LaTeX
wherever necessary.
bdef398
Sergiu Ivanov Remove support for anonymous objects.
Anonymous objects were initially implemented as a way of failing
gracefully in the case when no explicit name was supplied.  However, an
anonymous object does not make much sense.
c60d285
Sergiu Ivanov Explicitly test the equality of Morphism's.
Previously the equality of morphisms was implicitly tested in other
contexts.
96f2c3f
Sergiu Ivanov Rewrite the existing classes to be fully immutable.
The previous version of the classes was "partially" mutable: one had to
use functions such as add_premises, assert_commutative, etc.  Now,
however, all setting up is done in constructors, the objects being
immutable.  This has made the code of the module simpler, easier to
use, better integrated into the Basic inheritance tree, and much more
consistent.
39ee393
Sergiu Ivanov Raise ValueError when composing uncomposable morphisms. 629c916
Sergiu Ivanov Fix a long string in test_str.py.
A long string in tests was split on two lines in an ugly way.
0ce0464
Sergiu Ivanov Fix some bogus errors in examples in the docstrings.
During a major rewrite a couple commits before, I botched up a couple
docstrings.  This commit fixes that problem.
baf2d46
Sergiu Ivanov Add IdentityMorphism.
Having a separate class instead of a flag probably fits better into
SymPy philosophy.  This allows to have Morphism cope without a flag
specifying whether it is an identity or not.  This commit also adds a
different string representation for IdentityMorphism.
d02283a
Sergiu Ivanov Make test_args.py pass.
This implies making everything stored in Basic.args to be an instance of
Basic and also adding the corresponding tests to test_args.py.  Instead
of storing string names, instances of Symbol or Dummy are now stored in
Basic.args.
cd926f2
Sergiu Ivanov Fix some formulations and formatting in docs.
Cosmetic fixes mostly.
bb7e1a7
Sergiu Ivanov Add doctests for all user-facing methods.
I suppose that some of the docstrings have now become a bit redundant; I
don't think that's a problem at all with docstrings, however.
d1f1440
Sergiu Ivanov Use "other" as argument name in equality relations.
Previously, in __eq__/__ne__ I would use some arbitrary name to refer to
the value compared to self.  The convention is however to use "other".
fca51f2
Sergiu Ivanov Make an anonymous Morphism equal to itself.
Previously, for a = Morphism(A, B), with A and B being Object's, a == a
was False, which would have been very inconvenient.  Now a == a is
correctly True.
3b85a22
Sergiu Ivanov Rename Morphism.identity to is_identity.
This name expresses the idea better.
8dedae3
Sergiu Ivanov Raise TypeError when composing a Morphism with a non-Morphism.
Invoking Morphism.compose on something that is not a Morphism or a
derived class now raises TypeError.  __mul__ will in this case raise a
NotImplementedError instead.
9fffc9b
Sergiu Ivanov Don't allow unnamed categories.
Should the user supply an empty name to a constructor of Category, raise
ValueError.
20c545d
Sergiu Ivanov In Category, explicitly construct a Class of objects.
Also test the way Category stores objects and fix some related doctests.
313246f
Sergiu Ivanov Directly show the output of certain statements in doctests.
This commit fixes a tendency to compare the results of certain
statements with their expected results instead of directly showing the
results in doctests.
d9ac130
Sergiu Ivanov Don't compare to True or False.
Despite my hopes that such comparison might be better in some cases, I
am hereby removing it.
46aef91
Sergiu Ivanov Add a docstring to Category.__new__.
This constructor required further documentation since it attempts to
cleverly interpret its arguments.
926781c
Sergiu Ivanov Fix redundancies in test_morphism.
I have been adding tests incrementally, which resulted in some mess and
redundancy in test_morphism.
488f051
Sergiu Ivanov Fix a typo in test_diagram.
test_diagram missed an assertion in testing whether composite morphisms
are properly added.
813eeb7
Sergiu Ivanov In Category, rename commutative to commutative_diagrams.
The second name is definitely more descriptive, and now that it's not
only me who thinks this it's OK to have long names, this commit changes
everything to use the new name.
47403e4
Sergiu Ivanov Add pretty printing to Diagram.
This pretty printing largely relies on the pretty printing routines for
Dict, Morphism, and Object.
0a2e132
Sergiu Ivanov Remove explicit comparison operators from Diagram.
My initial intention was to attempt to speed-up the comparison by not
actually comparing the objects of two diagrams, because objects fully
depend on the premises.
f8f429a
Sergiu Ivanov Add the new Morphism class hierarchy.
This includes adding the base class Morphism, and the classes
IdentityMorphism, NamedMorphism, and CompositeMorphism.  This also
includes doing necessary adjustments to the other classes in the module
and to the tests.
6de72a1
Sergiu Ivanov Update the printing system to fit the new Morphism class hierarchy.
Printing has become easier with the new class hierarchy.  This commit
also updates the relevant tests.
8eecc16
Sergiu Ivanov Add examples for the new Morphism class hierarchy.
Also fix the older examples to properly utilise the new classes.
47d9af7
Sergiu Ivanov Make Object a direct descendent of Symbol.
This is because Object has essentially become a Symbol.  However, an
Object class is still needed in order to avoid the abuse of the name
"symbol".  Note that the printing routines have remained the same for
Object, for the sake of convenience.
d03d55c
Sergiu Ivanov Use a longer arrows in pretty representation of morphisms.
Shorter arrows look somewhat too crowded.
25f746a
Sergiu Ivanov Forbid anonymous morphisms.
This makes the code even more streamlined and removes some extra checks.
5e28059
Sergiu Ivanov Fix minor typos and use pprint in the docstrings.
This commit fixes some typos in the docstrings and removes one
gratuitous line in pretty.py.  It also fixes the docstrings to use
pprint instead of doing print pretty(something).
7aa0545
Sergiu Ivanov Assure 100% test coverage.
This commit increases the test coverage from ~94% to full coverage.
9e0f2a1
Sergiu Ivanov

I think this is almost good to go. The tests and doctests pass on my system. coverage_doctest reports 69% which is pretty good (in my experience this tool is extremely pedantic...); Note that running with --verbose shows you what is missing.

Wow, that's a very cool tool! How lovely of the developers to have it bundled with SymPy :-)

I have removed the doctests from the docstring of Morphism, because Morphism cannot be instatiated any more. This, I suppose, has lowered the score to 61%. Do you think I should try to do something to please the tool?

Also the coverage (run bin/coverage_report sympy/categories) is pretty good and at 94%. I think it is probably a good idea to boost this to 99% or so (should take no longer than 5 minutes, I think).

And this one is another very cool tool! I guess I've never read the documentation on how to set up the development workflow sufficiently attentively :-/

Working on bumping up the test coverage proved a very nice exercise; I was carried away by enthusiasm and brought the test coverage to 100%. I'll push the latest version after I have run the full test suite (that's about 10 mins from now).

Tom Bachmann
Collaborator
ness01 commented June 16, 2012
Tom Bachmann
Collaborator
ness01 commented June 16, 2012

As far as I can tell this is now good to go.

@asmeurer any objections to pushing sergiu's branch? This is mostly new code, so we shouldn't destroy anything existing.

@Krastanov could you possibly run the tests on this branch? I know the bot no longer works automatically, but I also know you have many python versions set up.

Sergiu Ivanov

Python 2.5 and 2.7 tests all pass on my box. I'll run Python 3.2 later in the day.

Yet, it would of course be absolutely great if someone could test the thing on their computer as well.

Sergiu Ivanov

Python 3.2 tests fail with the errors I mentioned yesterday on the list. (These failures are not related to my changes.)

Tom Bachmann
Collaborator
ness01 commented June 17, 2012

In my opinion this can go in.

@scolobb If nobody complains till then, I'll push this on monday evening.

Sergiu Ivanov

Great news, thank you.

Tom Bachmann ness01 merged commit d1ff275 into from June 18, 2012
Tom Bachmann ness01 closed this June 18, 2012
Tom Bachmann
Collaborator
ness01 commented June 18, 2012

This is in. Good work!

Sergiu Ivanov

Oh yeah, thank you! :-) I appreciate the appreciation :-)

Tom Bachmann
Collaborator
ness01 commented June 26, 2012

Please investigate if we should have added something to "modules" and/or "tests" lists in setup.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 57 unique commits by 1 author.

Jun 16, 2012
Sergiu Ivanov Add Class, Object, and Morphism.
These classes represent the very bowels of the module.  They are
yet in their incipient form, which is sufficient, however, to start
writing the tests.
fe78af1
Sergiu Ivanov Write the tests for categories.Object and categories.Morphism.
The tests do not pass at the moment.  The goal is to define the desired
behaviour of the methods in Morphism by means of tests.
133bb56
Sergiu Ivanov Implement composing and flattening morphisms.
Composing two morphisms results in a morphism which knows which
morphisms it was composed from.  Flattening such a morphism results in
forgetting the information about components.
f888e7e
Sergiu Ivanov Extend the string printer to print Object and Morphism.
Also add the corresponding tests.
631cb3d
Sergiu Ivanov When composing morphisms, intersperse morphism names with '*'.
The initial approach to use spaces to separate morphisms names proved
inconsistent with the later idea to use '*' when printing morphism
composition.
1ee252d
Sergiu Ivanov Amend the string representation of composition of morphisms.
The initial version did not comply with the requirement that the
represented object be able to be reconstructed from the string
representation.
bab67ea
Sergiu Ivanov Don't automatically construct the name of the composed morphism.
This also concerns flattened morphisms.  While initially it seemed to be
a good idea to automatically construct new morphism names, it proved
later that trying to be clever when it's not necessary is never good.
ff04329
Sergiu Ivanov Add pretty printing for Object and Morphism.
Pretty printing supports anonymous objects and morphisms.  Also add the
corresponding tests.
2e81231
Sergiu Ivanov Add printing to LaTeX.
Also add the tests for printing to LaTeX.  Anonymous objects and
morphisms are supported.
9334d84
Sergiu Ivanov Add (un)equality operators to Object and Morphism.
Also extend the docstrings and the tests to cover the added semantics.
In morphism equality, composed morphisms are only compared by their
components.  Thus, the names of composed morphisms are insignificant at
comparison.
9cf15ea
Sergiu Ivanov Add some nice formatting to the docstrings.
Follow the formatting style found in, for example,
sympy.core.symbol.
044cb74
Sergiu Ivanov Add examples to Object and Morphism. 65da9ae
Sergiu Ivanov Update the comment in __init__.py to include the principal reference.
This book should be referenced by the notation [JoyOfCats].
7866fc8
Sergiu Ivanov Add Category.
This is the base class for all categories.  It will contain very little
functionality related to objects and morphisms.
d539ba1
Sergiu Ivanov Extend string, LaTeX, and pretty printers to support Category.
Only the name of the Category is currently printed.  It is improbable
that much additional information will ever be included in the pretty
printed representation of a Category, just because it is so in the books
on the topic.
629f648
Sergiu Ivanov Add the skeleton of Diagram.
This commit defines the class and its methods without functionality.
The functionality itself will be implemented after the tests have been
added.
fd0a694
Sergiu Ivanov Add some tests for Diagram.
The tests also attempt to cover some subtle aspects of how the diagram
treats its objects and morphism.
462a5e1
Sergiu Ivanov In Morphism, define __new__ to allow keyword arguments.
Since Morphism derives from Basic, and Basic.__new__ does not accept
keyword arguments, Morphism could not be constructed with keyword
arguments.  While this is all right for simple constructors like those
of Object and Category, the constructor of Morphism will quickly become
awkward to use if the user will be required to supply all arguments in
order.
c9b027b
Sergiu Ivanov Implement identity morphisms.
A Morphism can now be defined to be an identity.  Identity morphisms are
defined not to influence composition.  All identity morphisms of one and
the same object are equal.
fa74c09
Sergiu Ivanov Make Morphism and Object check for the type of the other object in co…
…mparisons.

Objects of these classes would previously throw exceptions when compared
to something improper (like None).
207ee8a
Sergiu Ivanov Fix the tests for Diagram to properly use identity morphism.
Identity morphisms were implemented after the tests for Diagram, so
there was a need to slightly correct them to properly use identities.
7fcee70
Sergiu Ivanov Implement Diagram.
Also add some examples to the docstrings.  Diagram currently relies on
the Basic to do the hashing, because Basic seems to get things right in
this regard.
0a33621
Sergiu Ivanov Have Category store a set of commutative diagrams.
Not much more beyond storing a set of diagrams is done at the moment.
26debd8
Sergiu Ivanov Add a documentation chapter about the category theory module.
The page currently doesn't currently do much beyond pulling in the
corresponding docstrings and, in fact, that's the purpose of adding the
chapter at this moment.
2723826
Sergiu Ivanov Redo the docstrings to make them look better.
This includes slight changes to examples, as well as (ab)using LaTeX
wherever necessary.
bdef398
Sergiu Ivanov Remove support for anonymous objects.
Anonymous objects were initially implemented as a way of failing
gracefully in the case when no explicit name was supplied.  However, an
anonymous object does not make much sense.
c60d285
Sergiu Ivanov Explicitly test the equality of Morphism's.
Previously the equality of morphisms was implicitly tested in other
contexts.
96f2c3f
Sergiu Ivanov Rewrite the existing classes to be fully immutable.
The previous version of the classes was "partially" mutable: one had to
use functions such as add_premises, assert_commutative, etc.  Now,
however, all setting up is done in constructors, the objects being
immutable.  This has made the code of the module simpler, easier to
use, better integrated into the Basic inheritance tree, and much more
consistent.
39ee393
Sergiu Ivanov Raise ValueError when composing uncomposable morphisms. 629c916
Sergiu Ivanov Fix a long string in test_str.py.
A long string in tests was split on two lines in an ugly way.
0ce0464
Sergiu Ivanov Fix some bogus errors in examples in the docstrings.
During a major rewrite a couple commits before, I botched up a couple
docstrings.  This commit fixes that problem.
baf2d46
Sergiu Ivanov Add IdentityMorphism.
Having a separate class instead of a flag probably fits better into
SymPy philosophy.  This allows to have Morphism cope without a flag
specifying whether it is an identity or not.  This commit also adds a
different string representation for IdentityMorphism.
d02283a
Sergiu Ivanov Make test_args.py pass.
This implies making everything stored in Basic.args to be an instance of
Basic and also adding the corresponding tests to test_args.py.  Instead
of storing string names, instances of Symbol or Dummy are now stored in
Basic.args.
cd926f2
Sergiu Ivanov Fix some formulations and formatting in docs.
Cosmetic fixes mostly.
bb7e1a7
Sergiu Ivanov Add doctests for all user-facing methods.
I suppose that some of the docstrings have now become a bit redundant; I
don't think that's a problem at all with docstrings, however.
d1f1440
Sergiu Ivanov Use "other" as argument name in equality relations.
Previously, in __eq__/__ne__ I would use some arbitrary name to refer to
the value compared to self.  The convention is however to use "other".
fca51f2
Sergiu Ivanov Make an anonymous Morphism equal to itself.
Previously, for a = Morphism(A, B), with A and B being Object's, a == a
was False, which would have been very inconvenient.  Now a == a is
correctly True.
3b85a22
Sergiu Ivanov Rename Morphism.identity to is_identity.
This name expresses the idea better.
8dedae3
Sergiu Ivanov Raise TypeError when composing a Morphism with a non-Morphism.
Invoking Morphism.compose on something that is not a Morphism or a
derived class now raises TypeError.  __mul__ will in this case raise a
NotImplementedError instead.
9fffc9b
Sergiu Ivanov Don't allow unnamed categories.
Should the user supply an empty name to a constructor of Category, raise
ValueError.
20c545d
Sergiu Ivanov In Category, explicitly construct a Class of objects.
Also test the way Category stores objects and fix some related doctests.
313246f
Sergiu Ivanov Directly show the output of certain statements in doctests.
This commit fixes a tendency to compare the results of certain
statements with their expected results instead of directly showing the
results in doctests.
d9ac130
Sergiu Ivanov Don't compare to True or False.
Despite my hopes that such comparison might be better in some cases, I
am hereby removing it.
46aef91
Sergiu Ivanov Add a docstring to Category.__new__.
This constructor required further documentation since it attempts to
cleverly interpret its arguments.
926781c
Sergiu Ivanov Fix redundancies in test_morphism.
I have been adding tests incrementally, which resulted in some mess and
redundancy in test_morphism.
488f051
Sergiu Ivanov Fix a typo in test_diagram.
test_diagram missed an assertion in testing whether composite morphisms
are properly added.
813eeb7
Sergiu Ivanov In Category, rename commutative to commutative_diagrams.
The second name is definitely more descriptive, and now that it's not
only me who thinks this it's OK to have long names, this commit changes
everything to use the new name.
47403e4
Sergiu Ivanov Add pretty printing to Diagram.
This pretty printing largely relies on the pretty printing routines for
Dict, Morphism, and Object.
0a2e132
Sergiu Ivanov Remove explicit comparison operators from Diagram.
My initial intention was to attempt to speed-up the comparison by not
actually comparing the objects of two diagrams, because objects fully
depend on the premises.
f8f429a
Sergiu Ivanov Add the new Morphism class hierarchy.
This includes adding the base class Morphism, and the classes
IdentityMorphism, NamedMorphism, and CompositeMorphism.  This also
includes doing necessary adjustments to the other classes in the module
and to the tests.
6de72a1
Sergiu Ivanov Update the printing system to fit the new Morphism class hierarchy.
Printing has become easier with the new class hierarchy.  This commit
also updates the relevant tests.
8eecc16
Sergiu Ivanov Add examples for the new Morphism class hierarchy.
Also fix the older examples to properly utilise the new classes.
47d9af7
Sergiu Ivanov Make Object a direct descendent of Symbol.
This is because Object has essentially become a Symbol.  However, an
Object class is still needed in order to avoid the abuse of the name
"symbol".  Note that the printing routines have remained the same for
Object, for the sake of convenience.
d03d55c
Sergiu Ivanov Use a longer arrows in pretty representation of morphisms.
Shorter arrows look somewhat too crowded.
25f746a
Sergiu Ivanov Forbid anonymous morphisms.
This makes the code even more streamlined and removes some extra checks.
5e28059
Sergiu Ivanov Fix minor typos and use pprint in the docstrings.
This commit fixes some typos in the docstrings and removes one
gratuitous line in pretty.py.  It also fixes the docstrings to use
pprint instead of doing print pretty(something).
7aa0545
Sergiu Ivanov Assure 100% test coverage.
This commit increases the test coverage from ~94% to full coverage.
9e0f2a1
Something went wrong with that request. Please try again.