'How to recognize exp or trig polynomials/fractions in SymPy

Let me define an exponential polynomial as a linear combination of terms exp(a*X) where the as are distinct complex numbers. By taking only imaginary as we obtain a trig polynomial. Beware, though, that a term of the form exp(X*X) or cos(X*X) is not an exp/trig polynomial.

We define in the same way an exp/trig rational fraction as the quotient of two "polynomials" of the respective type described previously.

My question: given a sympy expression, how to recognize it is an exp/trig poly/frac?

My aim is to write a function is_exp_poly(expr) returning a Boolean.

So far I've tried the following code, which rewrites my starting expression in a promising way:

X= Symbol('X')
expr= cos(2*X) * sin(pi*X)
trans= powsimp(expand(expr.rewrite(exp).subs(X : log(X)).rewrite(sympy.Pow)))
# results in: -I*X**(-2*I + I*pi)/4 - I*X**(2*I + I*pi)/4 + I*X**(-I*pi - 2*I)/4 + I*X**(-I*pi + 2*I)/4

From here I feel I should be able to answer my question. But I don't quite know how to identify an expression which is a linear combination of (non-int) powers (or a fraction thereof). By that, I mean using high-level sympy functions as opposed to analyzing directly the formula structure.

Would anyone be so kind as to help me solving my problem? Somehow I stumbled on this question which might be related, although the feature of Laurent Polynomials is still not implemented (as far as I understood the git issue's thread). It seems that I'm dealing with more complex expressions (although I don't plan to do anything as fancyful as factoring them).



Solution 1:[1]

Based on @smichr answer, this is how I solved the problem of recognizing an exp poly. Further obvious modifications completely answer my question.

x = numbered_symbols('x')
reps={}
trans=cancel(expr.rewrite(exp).expand().replace(lambda e: e.func is exp, lambda e: reps.setdefault(e, next(x))))

if trans.is_polynomial():
    result=all([diff(e.args[0],X,2).is_zero  and not "_rec_replace" in str(e) for e in reps.keys()])
else:
    result=False

Solution 2:[2]

You can do something like this:

In [9]: X= Symbol('X')
   ...: expr= cos(2*X) * sin(pi*X)

In [10]: expr.atoms(sin, cos, exp)
Out[10]: {sin(??X), cos(2?X)}

In [11]: expr.as_poly(*expr.atoms(sin, cos, exp))
Out[11]: Poly((cos(2*X))*(sin(pi*X)), cos(2*X), sin(pi*X), domain='ZZ')

For rational functions you should be able to just use ratsimp, together, as_numer_denom etc to get the numerator and denominator separately.

Solution 3:[3]

The following collects distinct exponentials and assigns a variable to them, replacing them in the expression which can then be tested to see if it meets your requirements:

>>> x = numbered_symbols('x')
>>> reps={}
>>> powsimp(expr.rewrite(exp).expand()).replace(lambda x: x.func is exp,
...     lambda e: reps.setdefault(e, next(x)))
-I*x0/4 - I*x1/4 + I*x2/4 + I*x3/4

So the expression you gave is a linear combination of exponentials. The mapping between symbols and exponentials in in the reps.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Oscar Benjamin
Solution 3 smichr