# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
import os
import sys
import numpy as np
from .util import banded_jacobian, sparse_jacobian_csc, sparse_jacobian_csr
def _DenseMatrix(be, *args, **kwargs):
if len(args) == 1:
return be.Matrix(len(args[0]), 1, args[0], **kwargs)
else:
nr, nc, elems = args
return be.Matrix(nr, nc, elems, **kwargs)
class _Base(object):
def __getattr__(self, key):
return getattr(self.__sym_backend__, key)
def banded_jacobian(self, exprs, dep, ml, mu):
""" Wraps Matrix around result of .util.banded_jacobian """
exprs = banded_jacobian(exprs, dep, ml, mu)
return self.Matrix(ml+mu+1, len(dep), list(exprs.flat))
def sparse_jacobian_csc(self, exprs, dep):
""" Wraps Matrix/ndarray around results of .util.sparse_jacobian_csc """
jac_exprs, colptrs, rowvals = sparse_jacobian_csc(exprs, dep)
nnz = len(jac_exprs)
return (
self.Matrix(1, nnz, jac_exprs),
np.asarray(colptrs, dtype=int),
np.asarray(rowvals, dtype=int)
)
def sparse_jacobian_csr(self, exprs, dep):
""" Wraps Matrix/ndarray around results of .util.sparse_jacobian_csr """
jac_exprs, rowptrs, colvals = sparse_jacobian_csr(exprs, dep)
nnz = len(jac_exprs)
return (
self.Matrix(1, nnz, jac_exprs),
np.asarray(rowptrs, dtype=int),
np.asarray(colvals, dtype=int)
)
class _SymPy(_Base):
def __init__(self):
self.__sym_backend__ = __import__('sympy')
from ._sympy_Lambdify import _Lambdify
self.Lambdify = _Lambdify
def real_symarray(self, prefix, shape):
return self.symarray(prefix, shape, real=True)
DenseMatrix = _DenseMatrix
class _SymPySymEngine(_SymPy):
def __init__(self):
self.__sym_backend__ = __import__('sympy')
from symengine import Lambdify
self.Lambdify = Lambdify
class _Diofant(_SymPy):
def __init__(self):
self.__sym_backend__ = __import__('diofant')
from ._sympy_Lambdify import _Lambdify
class DiofantLambdify(_Lambdify):
def __init__(self, args, *exprs, **kwargs):
kwargs['backend'] = 'diofant'
super().__init__(args, *exprs, **kwargs)
self.Lambdify = DiofantLambdify
DenseMatrix = _DenseMatrix
__sym_backend_name__ = 'diofant'
class _SymEngine(_Base):
_dummy_counter = [0]
def __init__(self):
self.__sym_backend__ = __import__('symengine')
# cse isn't in any symengine release yet; only in dev version
# this will allow backend use with older symengine versions,
# failing gracefully only if cse is invoked
self._cse = getattr(self.__sym_backend__, 'cse', None)
self._ccode = getattr(self.__sym_backend__.lib.symengine_wrapper, 'ccode', None)
def Matrix(self, *args, **kwargs):
return self.DenseMatrix(*args, **kwargs)
def real_symarray(self, prefix, shape):
return self.symarray(prefix, shape)
def Dummy(self):
self._dummy_counter[0] += 1
return self.Symbol('Dummy_'+str(self._dummy_counter[0] - 1))
def ccode(self, *args, **kwargs):
# need to wrap, as ccode does not exist within root
# symengine namespace
return self.__sym_backend__.lib.symengine_wrapper.ccode(*args, **kwargs)
def numbered_symbols(self, prefix='x', cls=None, start=0, exclude=None, *args):
exclude = set(exclude or [])
if cls is None:
cls = self.Symbol
while True:
name = '%s%s' % (prefix, start)
s = cls(name, *args)
if s not in exclude:
yield s
start += 1
def cse(self, exprs, symbols=None):
# symengine's cse, but augmented with custom cse symbols ala sympy
if self._cse is None:
raise NotImplementedError("CSE not yet supported in symengine version %s" %
self.__sym_backend__.__version__)
if symbols is None:
symbols = self.numbered_symbols()
else:
symbols = iter(symbols)
old_repl, old_reduced = self._cse(exprs)
if not old_repl:
return old_repl, old_reduced
old_cse_symbols, old_cses = zip(*old_repl)
cse_symbols = [next(symbols) for _ in range(len(old_cse_symbols))]
subsd = dict(zip(old_cse_symbols, cse_symbols))
cses = [c.xreplace(subsd) for c in old_cses]
reduced = [e.xreplace(subsd) for e in old_reduced]
return list(zip(cse_symbols, cses)), reduced
class _PySym(_Base):
def __init__(self):
self.__sym_backend__ = __import__('pysym')
def real_symarray(self, prefix, shape):
return self.symarray(prefix, shape)
DenseMatrix = _DenseMatrix
class _SymCXX(_Base):
def __init__(self):
self.__sym_backend__ = __import__('symcxx').NameSpace()
def real_symarray(self, prefix, shape):
return self.symarray(prefix, shape)
DenseMatrix = _DenseMatrix
[docs]def Backend(name=None, envvar='SYM_BACKEND', default='sympy'):
""" Backend for the underlying symbolic manipulation packages
Parameters
----------
name: str (default: None)
Name of package e.g. 'sympy'
envvar: str (default: 'SYM_BACKEND')
name of environment variable to read name from (when name is ``None``)
default: str
name to use when the environment variable described by ``envvar`` is
unset or empty (default: 'sympy')
Examples
--------
>>> be = Backend('sympy') # or e.g. 'symengine'
>>> x, y = map(be.Symbol, 'xy')
>>> exprs = [x + y + 1, x*y**2]
>>> lmb = be.Lambdify([x, y], exprs)
>>> import numpy as np
>>> lmb(np.arange(6.0).reshape((3, 2))) # doctest: +NORMALIZE_WHITESPACE
array([[ 2., 0.],
[ 6., 18.],
[ 10., 100.]])
"""
if name is None:
name = os.environ.get(envvar, '') or default
if isinstance(name, _Base):
return name
else:
return Backend.backends[name]()
Backend.backends = {
'sympy': _SymPy,
'symengine': _SymEngine,
'sympysymengine': _SymPySymEngine, # uses selected parts from SymEngine to augment SymPy
'pysym': _PySym,
'symcxx': _SymCXX,
}
if sys.version_info[0] > 2:
Backend.backends['diofant'] = _Diofant