From 9cd91c1ef098802269cc9d2d8b4d29bec90607c7 Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Sat, 25 May 2013 17:36:57 -0400
Subject: [PATCH] Finish up documentation.

---
 doc/geometric-algebra.rst          |   4 +
 doc/index.rst                      |   4 +-
 doc/mappers.rst                    | 123 +++++++++++++++++++++++++++++
 doc/misc.rst                       |  11 +++
 doc/primitives.rst                 |   7 +-
 pymbolic/__init__.py               |   2 +
 pymbolic/algorithm.py              |   2 +-
 pymbolic/geometric_algebra.py      |   4 +-
 pymbolic/mapper/__init__.py        |  84 ++++++++++++++++----
 pymbolic/mapper/c_code.py          |  35 ++++++++
 pymbolic/mapper/collector.py       |   8 +-
 pymbolic/mapper/constant_folder.py |  18 -----
 pymbolic/mapper/dependency.py      |  15 +++-
 pymbolic/mapper/differentiator.py  |  28 ++++++-
 pymbolic/mapper/evaluator.py       |  19 +++++
 pymbolic/mapper/expander.py        |  12 +++
 pymbolic/mapper/stringifier.py     |  46 ++++++++++-
 pymbolic/primitives.py             |  14 ++++
 setup.py                           |   1 -
 19 files changed, 387 insertions(+), 50 deletions(-)
 create mode 100644 doc/mappers.rst

diff --git a/doc/geometric-algebra.rst b/doc/geometric-algebra.rst
index e52ec88..4688dea 100644
--- a/doc/geometric-algebra.rst
+++ b/doc/geometric-algebra.rst
@@ -3,6 +3,8 @@ Geometric Algebra
 
 .. automodule:: pymbolic.geometric_algebra
 
+Also see :ref:`ga-examples`.
+
 Spaces
 ------
 
@@ -57,6 +59,8 @@ Multivectors
     .. automethod:: as_scalar
     .. automethod:: as_vector
 
+.. _ga-examples:
+
 Example usage
 -------------
 
diff --git a/doc/index.rst b/doc/index.rst
index ad8d411..706b9f8 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -1,5 +1,5 @@
-Welcome to pymbolic's documentation!
-====================================
+Welcome to pymbolic!
+====================
 
 .. automodule:: pymbolic
 
diff --git a/doc/mappers.rst b/doc/mappers.rst
new file mode 100644
index 0000000..11a0ef2
--- /dev/null
+++ b/doc/mappers.rst
@@ -0,0 +1,123 @@
+Mappers
+=======
+
+.. automodule:: pymbolic.mapper
+
+Basic dispatch
+--------------
+
+.. autoclass:: Mapper
+
+    .. automethod:: __call__
+
+    .. method:: rec(expr, *args, **kwargs)
+
+        Identical to :meth:`__call__`, but intended for use in recursive dispatch
+        in mapper methods.
+
+    .. automethod:: handle_unsupported_expression
+
+    .. rubric:: Handling objects that don't declare mapper methods
+
+    In particular, this includes many non-subclasses of 
+    :class:`pymbolic.primitives.Expression`.
+
+    .. automethod:: map_foreign
+
+    These are abstract methods for foreign objects that should be overridden
+    in subclasses:
+
+    .. method:: map_constant(expr, *args, **kwargs)
+
+        Mapper method for constants.
+        See :func:`pymbolic.primitives.register_constant_class`.
+
+    .. method:: map_list(expr, *args, **kwargs)
+
+    .. method:: map_tuple(expr, *args, **kwargs)
+
+    .. method:: map_numpy_array(expr, *args, **kwargs)
+
+Base classes for new mappers
+----------------------------
+
+.. autoclass:: CombineMapper
+
+.. autoclass:: IdentityMapper
+
+.. autoclass:: WalkMapper
+
+.. autoclass:: CSECachingMapperMixin
+
+More specialized mappers
+------------------------
+
+Converting to strings and code
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: pymbolic.mapper.stringifier
+
+.. _prec-constants:
+
+Precedence constants
+********************
+
+.. data:: PREC_CALL
+.. data:: PREC_POWER
+.. data:: PREC_UNARY
+.. data:: PREC_PRODUCT
+.. data:: PREC_SUM
+.. data:: PREC_COMPARISON
+.. data:: PREC_LOGICAL_AND
+.. data:: PREC_LOGICAL_OR
+.. data:: PREC_NONE
+
+Mappers
+*******
+
+.. autoclass:: StringifyMapper
+
+    .. automethod:: __call__
+
+.. autoclass:: CSESplittingStringifyMapperMixin
+
+.. automodule:: pymbolic.mapper.c_code
+
+.. autoclass:: CCodeMapper
+
+Some minimal mathematics
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: pymbolic.mapper.evaluator
+
+.. autoclass:: EvaluationMapper
+
+.. automodule:: pymbolic.mapper.differentiator
+
+.. autoclass:: DifferentiationMapper
+
+.. automodule:: pymbolic.mapper.expander
+
+.. autoclass:: ExpandMapper
+
+.. automodule:: pymbolic.mapper.collector
+
+.. autoclass:: TermCollector
+
+.. automodule:: pymbolic.mapper.constant_folder
+
+.. autoclass:: ConstantFoldingMapper
+.. autoclass:: CommutativeConstantFoldingMapper
+
+Finding expression properties
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: pymbolic.mapper.dependency
+
+.. autoclass:: DependencyMapper
+
+.. automodule:: pymbolic.mapper.flop_counter
+
+.. autoclass:: FlopCounter
+
+.. vim: sw=4
diff --git a/doc/misc.rst b/doc/misc.rst
index 21f0ae1..6a4c34e 100644
--- a/doc/misc.rst
+++ b/doc/misc.rst
@@ -67,3 +67,14 @@ Frequently Asked Questions
 The FAQ is maintained collaboratively on the
 `Wiki FAQ page <http://wiki.tiker.net/Pymbolic/FrequentlyAskedQuestions>`_.
 
+Glossary
+========
+
+.. glossary::
+
+    mix-in
+        See `Wikipedia article <https://en.wikipedia.org/wiki/Mixin>`_.
+
+        Be sure to mention the mix-in before the base classe being mixed in the
+        list of base classes. This way, the mix-in can override base class
+        behavior.
diff --git a/doc/primitives.rst b/doc/primitives.rst
index 19f4d1c..497759e 100644
--- a/doc/primitives.rst
+++ b/doc/primitives.rst
@@ -17,6 +17,7 @@ Expression base class
 
     .. automethod:: __eq__
     .. automethod:: __hash__
+    .. automethod:: __str__
     .. automethod:: __repr__
 
 Sums, products and such
@@ -92,13 +93,15 @@ Code generation helpers
     :undoc-members:
     :members: mapper_method
 
-Predicates, Constants
----------------------
+Helper functions
+----------------
 
 .. autofunction:: is_zero
 .. autofunction:: is_constant
 .. autofunction:: register_constant_class
 .. autofunction:: unregister_constant_class
+.. autofunction:: variables
+.. autofunction:: make_common_subexpression
 
 Interaction with :mod:`numpy` arrays
 ------------------------------------
diff --git a/pymbolic/__init__.py b/pymbolic/__init__.py
index 69c5d0b..fcd1cc7 100644
--- a/pymbolic/__init__.py
+++ b/pymbolic/__init__.py
@@ -65,6 +65,8 @@ you put in. It has a few of those built in, but that's not really the point:
     >>> print pmbl.differentiate(u, 'x')
     5*(x + 1)**4
 
+.. _custom-manipulation:
+
 Manipulating expressions
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/pymbolic/algorithm.py b/pymbolic/algorithm.py
index 6104264..7443a49 100644
--- a/pymbolic/algorithm.py
+++ b/pymbolic/algorithm.py
@@ -72,7 +72,7 @@ def extended_euclidean(q, r):
     """Return a tuple *(p, a, b)* such that :math:`p = aq + br`,
     where *p* is the greatest common divisor of *q* and *r*.
 
-    See also `Wikipedia <https://en.wikipedia.org/wiki/Euclidean_algorithm>`_.
+    See also the `Wikipedia article on the Euclidean algorithm <https://en.wikipedia.org/wiki/Euclidean_algorithm>`_.
     """
     import pymbolic.traits as traits
 
diff --git a/pymbolic/geometric_algebra.py b/pymbolic/geometric_algebra.py
index 43cefe7..d8556df 100644
--- a/pymbolic/geometric_algebra.py
+++ b/pymbolic/geometric_algebra.py
@@ -329,8 +329,8 @@ class MultiVector(object):
         ``A*B``,             Geometric product :math:`AB`
         ``A^B``,             Outer product :math:`A\wedge B` of multivectors
         ``A|B``,             Inner product :math:`A\cdot B` of multivectors
-        ``A<<B``,            Left contraction of multivectors
-        ``A>>B``,            Right contraction of multivectors
+        ``A<<B``,            Left contraction :math:`A\lrcorner B` of multivectors
+        ``A>>B``,            Right contraction :math:`A\llcorner B` of multivectors
 
     .. warning ::
 
diff --git a/pymbolic/mapper/__init__.py b/pymbolic/mapper/__init__.py
index 67eb165..0a2ef0f 100644
--- a/pymbolic/mapper/__init__.py
+++ b/pymbolic/mapper/__init__.py
@@ -46,12 +46,33 @@ class UnsupportedExpressionError(ValueError):
 # {{{ mapper base
 
 class Mapper(object):
+    """A visitor for trees of :class:`pymbolic.primitives.Expression`
+    subclasses. Each expression-derived object is dispatched to the
+    method named by the :attr:`pymbolic.primitives.Expression.mapper_method`
+    attribute.
+    """
+
     def handle_unsupported_expression(self, expr, *args):
+        """Mapper method that is invoked for
+        :class:`pymbolic.primitives.Expression` subclasses for which a mapper
+        method does not exist in this mapper.
+        """
+
         raise UnsupportedExpressionError(
                 "%s cannot handle expressions of type %s" % (
-                    self.__class__, expr.__class__))
+                    type(self), type(expr)))
 
     def __call__(self, expr, *args, **kwargs):
+        """Dispatch *expr* to its corresponding mapper method. Pass on ``*args``
+        and ``**kwargs`` unmodified.
+
+        This method is intended as the top-level dispatch entry point and may
+        be overridden by subclasses to present a different/more convenient
+        interface. :meth:`rec` on the other hand is intended as the recursive
+        dispatch method to be used to recurse within mapper method
+        implementations.
+        """
+
         try:
             method = getattr(self, expr.mapper_method)
         except AttributeError:
@@ -65,6 +86,8 @@ class Mapper(object):
 
         return method(expr, *args, **kwargs)
 
+    rec = __call__
+
     def map_variable(self, expr, *args):
         return self.map_algebraic_leaf(expr, *args)
 
@@ -84,6 +107,8 @@ class Mapper(object):
         return self.map_quotient(expr, *args)
 
     def map_foreign(self, expr, *args):
+        """Mapper method dispatch for non-:mod:`pymbolic` objects."""
+
         if isinstance(expr, primitives.VALID_CONSTANT_CLASSES):
             return self.map_constant(expr, *args)
         elif isinstance(expr, list):
@@ -102,14 +127,26 @@ class Mapper(object):
 
 
 
-class RecursiveMapper(Mapper):
-    rec = Mapper.__call__
-
-
+RecursiveMapper = Mapper
 
 # {{{ combine mapper
 
 class CombineMapper(RecursiveMapper):
+    """A mapper whose goal it is to *combine* all branches of the expression tree
+    into one final result. The default implementation of all mapper methods simply
+    recurse (:meth:`Mapper.rec`) on all branches emanating from the current expression,
+    and then call :meth:`combine` on a tuple of results.
+
+    .. method:: combine(values)
+
+        Combine the mapped results of multiple expressions (given in *values*)
+        into a single result, often by summing or taking set unions.
+
+    The :class:`pymbolic.mapper.flop_counter.FlopCounter` is a very simple example.
+    (Look at its source for an idea of how to derive from :class:`CombineMapper`.)
+    The :class:`pymbolic.mapper.dependency.DependencyMapper` is another example.
+    """
+
     def map_call(self, expr, *args):
         return self.combine(
                 (self.rec(expr.function, *args),) +
@@ -192,7 +229,13 @@ class CombineMapper(RecursiveMapper):
 
 # {{{ identity mapper
 
-class IdentityMapperBase(object):
+class IdentityMapper(Mapper):
+    """A :class:`Mapper` whose default mapper methods
+    make a deep copy of each subexpression.
+
+    See :ref:`custom-manipulation` for an example of the
+    manipulations that can be implemented this way.
+    """
     def map_constant(self, expr, *args):
         # leaf -- no need to rebuild
         return expr
@@ -321,19 +364,17 @@ class IdentityMapperBase(object):
                 self.rec(expr.then, *args),
                 self.rec(expr.else_, *args))
 
-
-
-class IdentityMapper(IdentityMapperBase, RecursiveMapper):
-    pass
-
-class NonrecursiveIdentityMapper(IdentityMapperBase, Mapper):
-    pass
-
 # }}}
 
 # {{{ walk mapper
 
 class WalkMapper(RecursiveMapper):
+    """A mapper whose default mapper method implementations simply recurse
+    without propagating any result. Also calls :meth:`visit` for each
+    visited subexpression.
+
+    .. method:: visit(expr, *args)
+    """
     def map_constant(self, expr, *args):
         self.visit(expr)
 
@@ -506,6 +547,21 @@ class CallbackMapper(RecursiveMapper):
 # {{{ cse caching mixin
 
 class CSECachingMapperMixin(object):
+    """A :term:`mix-in` that helps
+    subclassed mappers implement caching for 
+    :class:`pymbolic.primitives.CommonSubexpression`
+    instances.
+
+    Instead of the common mapper method for 
+    :class:`pymbolic.primitives.CommonSubexpression`,
+    subclasses should implement the following method:
+
+    .. method:: map_common_subexpression_uncached(expr)
+
+    This method deliberately does not support extra arguments in mapper dispatch,
+    to avoid spurious dependencies of the cache on these arguments.
+    """
+
     def map_common_subexpression(self, expr):
         try:
             ccd = self._cse_cache_dict
diff --git a/pymbolic/mapper/c_code.py b/pymbolic/mapper/c_code.py
index 12fdf02..df4de2d 100644
--- a/pymbolic/mapper/c_code.py
+++ b/pymbolic/mapper/c_code.py
@@ -28,6 +28,41 @@ from pymbolic.mapper.stringifier import SimplifyingSortingStringifyMapper
 
 
 class CCodeMapper(SimplifyingSortingStringifyMapper):
+    """Generate C code for expressions, while extracting 
+    :class:`pymbolic.primitives.CommonSubexpression` instances.
+
+    As an example, define a fairly simple expression *expr*:
+
+    .. doctest::
+
+        >>> import pymbolic.primitives as p
+        >>> x = p.Variable("x")
+        >>> CSE = p.CommonSubexpression
+        >>> u = CSE(3*x**2-5, "u")
+        >>> expr = u/(u+3)*(u+5)
+        >>> print expr
+        CSE(3*x**2 + -5) / (CSE(3*x**2 + -5) + 3)*(CSE(3*x**2 + -5) + 5)
+
+    Notice that if we were to directly generate code from this, the
+    subexpression *u* would be evaluated multiple times.
+
+    .. doctest::
+
+        >>> from pymbolic.mapper.c_code import CCodeMapper as CCM
+        >>> ccm = CCM()
+        >>> result = ccm(expr)
+
+        >>> for name, value in ccm.cse_name_list:
+        ...     print "%s = %s;" % (name, value)
+        ...
+        _cse_u = 3 * x * x + -5;
+        >>> print result
+        _cse_u / (_cse_u + 3) * (_cse_u + 5)
+
+    See :class:`pymbolic.mapper.stringifier.CSESplittingStringifyMapperMixin`
+    for the ``cse_*`` attributes.
+    """
+
     def __init__(self, constant_mapper=repr, reverse=True, 
             cse_prefix="_cse", complex_constant_base_type="double",
             cse_name_list=[]):
diff --git a/pymbolic/mapper/collector.py b/pymbolic/mapper/collector.py
index 1fb313a..4c7f289 100644
--- a/pymbolic/mapper/collector.py
+++ b/pymbolic/mapper/collector.py
@@ -30,6 +30,10 @@ from pymbolic.mapper import IdentityMapper
 
 class TermCollector(IdentityMapper):
     """A term collector that assumes that multiplication is commutative.
+
+    Allows specifying *parameters* (a set of
+    :class:`pymbolic.primitive.Variable` instances) that are viewed as being
+    coefficients and are not used for term collection.
     """
 
     def __init__(self, parameters=set()):
@@ -43,7 +47,7 @@ class TermCollector(IdentityMapper):
         """Returns  a pair consisting of:
         - a frozenset of (base, exponent) pairs
         - a product of coefficients (i.e. constants and parameters)
-        
+
         The set takes care of order-invariant comparison for us and is hashable.
 
         The argument `product' has to be fully expanded already.
@@ -102,6 +106,6 @@ class TermCollector(IdentityMapper):
         def rep2term(rep):
             return pymbolic.flattened_product(base**exp for base, exp in rep)
 
-        result = pymbolic.flattened_sum(coeff*rep2term(termrep) 
+        result = pymbolic.flattened_sum(coeff*rep2term(termrep)
                 for termrep, coeff in term2coeff.iteritems())
         return result
diff --git a/pymbolic/mapper/constant_folder.py b/pymbolic/mapper/constant_folder.py
index 303fc61..d682369 100644
--- a/pymbolic/mapper/constant_folder.py
+++ b/pymbolic/mapper/constant_folder.py
@@ -24,7 +24,6 @@ THE SOFTWARE.
 
 from pymbolic.mapper import \
         IdentityMapper, \
-        NonrecursiveIdentityMapper, \
         CSECachingMapperMixin
 
 
@@ -95,14 +94,6 @@ class ConstantFoldingMapper(
     map_common_subexpression_uncached = \
             IdentityMapper.map_common_subexpression
 
-class NonrecursiveConstantFoldingMapper(
-        CSECachingMapperMixin,
-        NonrecursiveIdentityMapper, 
-        ConstantFoldingMapperBase):
-
-    map_common_subexpression_uncached = \
-            NonrecursiveIdentityMapper.map_common_subexpression
-
 class CommutativeConstantFoldingMapper(
         CSECachingMapperMixin,
         CommutativeConstantFoldingMapperBase,
@@ -110,12 +101,3 @@ class CommutativeConstantFoldingMapper(
 
     map_common_subexpression_uncached = \
             IdentityMapper.map_common_subexpression
-
-class NonrecursiveCommutativeConstantFoldingMapper(
-        CSECachingMapperMixin,
-        CommutativeConstantFoldingMapperBase,
-        NonrecursiveIdentityMapper,):
-
-    map_common_subexpression_uncached = \
-            NonrecursiveIdentityMapper.map_common_subexpression
-
diff --git a/pymbolic/mapper/dependency.py b/pymbolic/mapper/dependency.py
index c8661e0..0acd62e 100644
--- a/pymbolic/mapper/dependency.py
+++ b/pymbolic/mapper/dependency.py
@@ -28,12 +28,23 @@ from pymbolic.mapper import CombineMapper, CSECachingMapperMixin
 
 
 class DependencyMapper(CSECachingMapperMixin, CombineMapper):
-    def __init__(self, 
-            include_subscripts=True, 
+    """Maps an expression to the :class:`set` of expressions it
+    is based on. The ``include_*`` arguments to the constructor
+    determine which types of objects occur in this output set.
+    If all are *False*, only :class:`pymbolic.primitives.Variable`
+    instances are included.
+    """
+
+    def __init__(self,
+            include_subscripts=True,
             include_lookups=True,
             include_calls=True,
             include_cses=False,
             composite_leaves=None):
+        """
+        :arg composite_leaves: Setting this is equivalent to setting
+            all preceding ``include_*`` flags.
+        """
 
         if composite_leaves == False:
             include_subscripts = False
diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py
index a6ca688..4b7cedb 100644
--- a/pymbolic/mapper/differentiator.py
+++ b/pymbolic/mapper/differentiator.py
@@ -58,7 +58,27 @@ def map_math_functions_by_name(i, func, pars):
 
 
 class DifferentiationMapper(pymbolic.mapper.RecursiveMapper):
+    """Example usage:
+
+    .. doctest::
+
+        >>> import pymbolic.primitives as p
+        >>> x = p.Variable("x")
+        >>> expr = x*(x+5)**3/(x-1)**2
+
+        >>> from pymbolic.mapper.differentiator import DifferentiationMapper as DM
+        >>> print DM(x)(expr)
+        (((x + 5)**3 + x*3*(x + 5)**2)*(x + -1)**2 + (-1)*2*(x + -1)*x*(x + 5)**3) / (x + -1)**2**2
+    """
+
     def __init__(self, variable, func_map=map_math_functions_by_name):
+        """
+        :arg variable: A :class:`pymbolic.primitives.Variable` instance
+            by which to differentiate.
+        :arg func_map: A function for computing derivatives of function
+            calls, signature ``(arg_index, function_variable, parameters)``.
+        """
+
         self.variable = variable
         self.function_map = func_map
 
@@ -86,7 +106,7 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper):
     def map_product(self, expr):
         return pymbolic.flattened_sum(
             pymbolic.flattened_product(
-                expr.children[0:i] + 
+                expr.children[0:i] +
                 (self.rec(child),) +
                 expr.children[i+1:])
             for i, child in enumerate(expr.children))
@@ -143,7 +163,7 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper):
         return \
                 Polynomial(expr.base, tuple(deriv_coeff), expr.unit) + \
                 Polynomial(expr.base, tuple(deriv_base), expr.unit)
-  
+
     def map_numpy_array(self, expr):
         import numpy
         result = numpy.empty(expr.shape, dtype=object)
@@ -155,8 +175,8 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper):
 
 
 
-def differentiate(expression, 
-                  variable, 
+def differentiate(expression,
+                  variable,
                   func_mapper=map_math_functions_by_name):
     if not isinstance(variable, (primitives.Variable, primitives.Subscript)):
         variable = primitives.make_variable(variable)
diff --git a/pymbolic/mapper/evaluator.py b/pymbolic/mapper/evaluator.py
index da4532f..d745709 100644
--- a/pymbolic/mapper/evaluator.py
+++ b/pymbolic/mapper/evaluator.py
@@ -34,7 +34,26 @@ class UnknownVariableError(Exception):
 
 
 class EvaluationMapper(RecursiveMapper):
+    """Example usage:
+
+    .. doctest::
+
+        >>> import pymbolic.primitives as p
+        >>> x = p.Variable("x")
+
+        >>> u = 5*x**2 - 3*x
+        >>> print u
+        5*x**2 + (-1)*3*x
+
+        >>> from pymbolic.mapper.evaluator import EvaluationMapper as EM
+        >>> EM(context={"x": 5})(u)
+        110
+    """
+
     def __init__(self, context={}):
+        """
+        :arg context: a mapping from variable names to values
+        """
         self.context = context
         self.common_subexp_cache = {}
 
diff --git a/pymbolic/mapper/expander.py b/pymbolic/mapper/expander.py
index 4129b46..457094c 100644
--- a/pymbolic/mapper/expander.py
+++ b/pymbolic/mapper/expander.py
@@ -33,6 +33,18 @@ from pymbolic.primitives import \
 
 
 class ExpandMapper(IdentityMapper):
+    """Example usage:
+
+    .. doctest::
+
+        >>> import pymbolic.primitives as p
+        >>> x = p.Variable("x")
+        >>> expr = (x+1)**7
+        >>> from pymbolic.mapper.expander import ExpandMapper as EM
+        >>> print EM()(expr) # doctest: +SKIP
+        7*x**6 + 21*x**5 + 21*x**2 + 35*x**3 + 1 + 35*x**4 + 7*x + x**7
+    """
+
     def __init__(self, collector=TermCollector()):
         self.collector = collector
 
diff --git a/pymbolic/mapper/stringifier.py b/pymbolic/mapper/stringifier.py
index cf0ed99..18a4d4d 100644
--- a/pymbolic/mapper/stringifier.py
+++ b/pymbolic/mapper/stringifier.py
@@ -39,8 +39,22 @@ PREC_NONE = 0
 
 
 
-class StringifyMapper(pymbolic.mapper.RecursiveMapper):
+class StringifyMapper(pymbolic.mapper.Mapper):
+    """A mapper to turn an expression tree into a string.
+
+    :class:`pymbolic.primitives.Expression.__str__` is often implemented using
+    this mapper.
+
+    When it encounters an unsupported :class:`pymbolic.primitives.Expression`
+    subclass, it calls its :meth:`pymbolic.primitives.Expression.stringifier`
+    method to get a :class:`StringifyMapper` that potentially does.
+    """
+
     def __init__(self, constant_mapper=str):
+        """
+        :arg constant_mapper: A function of a single *expr* argument being used
+            to map constants into strings.
+        """
         self.constant_mapper = constant_mapper
 
     # replaceable string composition interface --------------------------------
@@ -266,13 +280,41 @@ class StringifyMapper(pymbolic.mapper.RecursiveMapper):
 
 
     def __call__(self, expr, prec=PREC_NONE):
-        return pymbolic.mapper.RecursiveMapper.__call__(self, expr, prec)
+        """Return a string corresponding to *expr*. If the enclosing
+        precedence level *prec* is higher than *prec* (see :ref:`prec-constants`),
+        parenthesize the result.
+        """
+
+        return pymbolic.mapper.Mapper.__call__(self, expr, prec)
 
 
 
 
 
 class CSESplittingStringifyMapperMixin(object):
+    """A :term:`mix-in` for subclasses of
+    :class:`StringifyMapper` that collects
+    "variable assignments" for
+    :class:`pymbolic.primitives.CommonSubexpression` objects.
+
+    .. attribute:: cse_to_name
+
+        A :class:`dict` mapping expressions to CSE variable names.
+
+    .. attribute:: cse_names
+
+        A :class:`set` of names already assigned.
+
+    .. attribute:: cse_name_list
+
+        A :class:`list` of tuples of names and their string representations,
+        in order of their dependencies. When generating code, walk down these names
+        in order, and the generated code will never reference
+        an undefined variable.
+
+    See :class:`pymbolic.mapper.c_code.CCodeMapper` for an example
+    of the use of this mix-in.
+    """
     def map_common_subexpression(self, expr, enclosing_prec):
         try:
             self.cse_to_name
diff --git a/pymbolic/primitives.py b/pymbolic/primitives.py
index d379c87..b2c7599 100644
--- a/pymbolic/primitives.py
+++ b/pymbolic/primitives.py
@@ -194,6 +194,10 @@ class Expression(object):
         return StringifyMapper
 
     def __str__(self):
+        """Use the :meth:`stringifier` to return a human-readable
+        string representation of *self*.
+        """
+
         from pymbolic.mapper.stringifier import PREC_NONE
         return self.stringifier()()(self, PREC_NONE)
 
@@ -803,6 +807,8 @@ class CommonSubexpression(Expression):
 
     .. attribute:: child
     .. attribute:: prefix
+
+    See :class:`pymbolic.mapper.c_code.CCodeMapper` for an example.
     """
 
     def __init__(self, child, prefix=None):
@@ -1079,6 +1085,11 @@ def wrap_in_cse(expr, prefix=None):
 
 
 def make_common_subexpression(field, prefix=None):
+    """Wrap *field* in a :class:`CommonSubexpression` with
+    *prefix*. If *field* is a :mod:`numpy` object array,
+    each individual entry is instead wrapped.
+    """
+
     try:
         from pytools.obj_array import log_shape
     except ImportError:
@@ -1131,6 +1142,9 @@ def make_sym_vector(name, components):
 
 
 def variables(s):
+    """Return a list of variables for each (space-delimited) identifier
+    in *s*.
+    """
     return [Variable(s_i) for s_i in s.split() if s_i]
 
 # }}}
diff --git a/setup.py b/setup.py
index 20d63ff..71f5bda 100644
--- a/setup.py
+++ b/setup.py
@@ -34,7 +34,6 @@ setup(name="pymbolic",
         'Natural Language :: English',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        # We use conditional expressions, so 2.5 is the bare minimum.
         'Programming Language :: Python :: 2.5',
         'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
-- 
GitLab