Remove SymPy dependency

Issue #8 new
Prof Garth Wells created an issue

Using SymPy in FIAT is like using a sledgehammer to crack a peanut.

It's a heavy dependency that imports a lot of files, which can slow import.

Also, the SymPy Debian/Ubuntu packages pull in a lot of very large package dependencies, e.g. texlive. This blows out the size of virtual images and Linux containers.

Comments (5)

  1. Johannes Ring

    It is possible to avoid installing texlive together with SymPy by running apt-get with --no-install-recommends.

  2. Lawrence Mitchell

    One option may be to use pymbolic (http://mathema.tician.de/software/pymbolic/) which is quite lightweight. The following patch to expansions.py appears to DTRT:

    diff --git a/FIAT/expansions.py b/FIAT/expansions.py
    index cd22748..ddb3110 100644
    --- a/FIAT/expansions.py
    +++ b/FIAT/expansions.py
    @@ -21,14 +21,19 @@ to allow users to get coordinates that they want."""
    
     import numpy
     import math
    -import sympy
    +import pymbolic
     from . import reference_element
     from . import jacobi
    
    
    -def _tabulate_dpts(tabulator, D, n, order, pts):
    -    X = sympy.DeferredVector('x')
    +class Differentiator(pymbolic.mapper.differentiator.DifferentiationMapper):
    +
    +    # Derivative of list is just list of derivative of entries
    +    def map_list(self, expr):
    +        return [self.rec(x) for x in expr]
    +
    
    +def _tabulate_dpts(tabulator, D, n, order, pts):
         def form_derivative(F):
             '''Forms the derivative recursively, i.e.,
             F               -> [F_x, F_y, F_z],
    @@ -37,45 +42,10 @@ def _tabulate_dpts(tabulator, D, n, order, pts):
                                 [F_zx, F_zy, F_zz]]
             and so forth.
             '''
    -        out = []
    -        try:
    -            out = [sympy.diff(F, X[j]) for j in range(D)]
    -        except AttributeError:
    -            # Intercept errors like
    -            #  AttributeError: 'list' object has no attribute
    -            #  'free_symbols'
    -            for f in F:
    -                out.append(form_derivative(f))
    -        return out
    -
    -    def numpy_lambdify(X, F):
    -        '''Unfortunately, SymPy's own lambdify() doesn't work well with
    -        NumPy in that simple functions like
    -            lambda x: 1.0,
    -        when evaluated with NumPy arrays, return just "1.0" instead of
    -        an array of 1s with the same shape as x. This function does that.
    -        '''
    -        try:
    -            lambda_x = [numpy_lambdify(X, f) for f in F]
    -        except TypeError:  # 'function' object is not iterable
    -            # SymPy's lambdify also works on functions that return arrays.
    -            # However, use it componentwise here so we can add 0*x to each
    -            # component individually. This is necessary to maintain shapes
    -            # if evaluated with NumPy arrays.
    -            lmbd_tmp = sympy.lambdify(X, F)
    -            lambda_x = lambda x: lmbd_tmp(x) + 0*x[0]
    -        return lambda_x
    -
    -    def evaluate_lambda(lmbd, x):
    -        '''Properly evaluate lambda expressions recursively for iterables.
    -        '''
    -        try:
    -            values = [evaluate_lambda(l, x) for l in lmbd]
    -        except TypeError:  # 'function' object is not iterable
    -            values = lmbd(x)
    -        return values
    +        return [Differentiator(X[j])(F) for j in range(D)]
    
         # Tabulate symbolically
    +    X = pymbolic.primitives.Variable('x')
         symbolic_tab = tabulator(n, X)
         # Make sure that the entries of symbolic_tab are lists so we can
         # append derivatives
    @@ -86,10 +56,8 @@ def _tabulate_dpts(tabulator, D, n, order, pts):
             shape = [len(symbolic_tab), len(pts)] + r * [D]
             data[r] = numpy.empty(shape)
             for i, phi in enumerate(symbolic_tab):
    -            # Evaluate the function numerically using lambda expressions
    -            deriv_lambda = numpy_lambdify(X, phi[r])
    -            data[r][i] = \
    -                numpy.array(evaluate_lambda(deriv_lambda, pts.T)).T
    +            # Evalute numerically, binding 'x' to the required points
    +            data[r][i] = numpy.array(pymbolic.evaluate(phi[r], {'x': pts.T})).T
                 # Symbolically compute the next derivative.
                 # This actually happens once too many here; never mind for
                 # now.
    
  3. Prof Garth Wells reporter

    Is pymbolic available as a Debian/Ubuntu package, and does it work with Python 3?

  4. Lawrence Mitchell

    It does, I believe, work with python 3 (at least setup.py claims so). It does not appear to be an ubuntu package in 14.04.

  5. Log in to comment