sympy icon indicating copy to clipboard operation
sympy copied to clipboard

Preferring negative exponents

Open hanspi42 opened this issue 1 year ago • 11 comments

There are several fields where negative exponents are preferrable. Fort example,

(s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1))

is the Laplace transform from the Wester tests Y5 and Y6; the expression

(s**2 + 4*exp(-s) - 4*exp(-2*s))/(s*(s**2 + 1))

would be much simpler to read. Under the z transform, the delayed integrator would be easy to read as

z**(-1)/(1-z**(-1))

but will immediately be written as

1/(z*(1 - 1/z))

I suggest that we make it somehow possible that sympy can be told "please prefer negative exponents". Where is the proper place to do this? And how could it be done?

hanspi42 avatar Mar 03 '23 12:03 hanspi42

May I try this issue?

samithkavishke avatar Mar 04 '23 14:03 samithkavishke

May I try this issue?

Of course, do you have an idea how to do it?

hanspi42 avatar Apr 06 '23 09:04 hanspi42

You can replace your desired inverse symbol, e.g. dealyed_integrator.subs(1/z, Symbol('z^-1')). Or in case of the first example, simplification will help:

>>> expr = (s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1))
>>> i,d=expr.as_independent(exp)
>>> i*expand(d)
(x**2 + 4*exp(-x) - 4*exp(-2*x))/(x*(x**2 + 1))

smichr avatar Apr 07 '23 16:04 smichr

.subs(1/z, Symbol('z^-1'))

Thanks, but it should work for arbitrary integer exponents, e.g., z**(-2)/(1-z**(-2))

What I wondered: somewhere in the codebase something makes exponents positive. Could that something have an option to make exponents negative?

hanspi42 avatar Apr 07 '23 19:04 hanspi42

SymPy internally represents x**-1 and 1/x in the same way. There is no Division class. This is just a question of how things are printed in the printers. So in the case of z**(-2)/(1-z**(-2)) this is exactly the same expression as 1/(z**2*(1 - 1/z**2)).

Specifically in the Pow printer (for 1/x) and Mul printer (for x/y) for whatever printer you care about (in your example, the str printer). There aren't currently any options for controlling this, but I think one could be added if you can come up with a reasonable heuristic for it. There's already some heuristics for printing exponentials in the numerator with negative denominator rather than as a fraction:

>>> y/exp(x)
y*exp(-x)

If we can improve the general heuristics without a flag, that would be best, but if it's a case where both types of output might be desired, a flag would be best.

On the other hand, (s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1)) and (s**2 + 4*exp(-s) - 4*exp(-2*s))/(s*(s**2 + 1)) are distinct expressions and both are representable.

asmeurer avatar Apr 07 '23 20:04 asmeurer

The z-expression can also be rewritten as

>>> expand(1/(z*(1 - 1/z)))
1/(z - 1)

And that looks pretty clean to me. For purposes of discussion, it would be better to focus on expressions which can't be rewritten in a nice way already.

smichr avatar Apr 08 '23 03:04 smichr

Ok,

The z-expression can also be rewritten as

>>> expand(1/(z*(1 - 1/z)))
1/(z - 1)

And that looks pretty clean to me. For purposes of discussion, it would be better to focus on expressions which can't be rewritten in a nice way already.

Yes, it is clean, but if it were written as $\frac{z^{-1}}{1-z^{-1}}$, it would be in the standard form for digital filter transfer functions (see https://en.wikipedia.org/wiki/Digital_filter#Characterization) and directly implementable (see https://en.wikipedia.org/wiki/Digital_filter#Direct_form_I) because $z^{-1}$ is just a delay.

So the reason why I am asking this is that we have a whole important field out there, digital signal processing, whose practitioners would love it so much if computer algebra tools could print functions in the standard form

$\displaystyle \frac{{b_{0}+b_{1}z^{-1}+b_{2}z^{-2} + \cdots + b_{N}z^{-N}}}{{1+a_{1}z^{-1}+a_{2}z^{-2} + \cdots +a_{M}z^{-M}}}$

with $z$ only having negative exponents.

hanspi42 avatar Apr 14 '23 18:04 hanspi42

By the way, Mathematica can do this somewhat, there you can define a z1 as Power[z, HoldForm[-1]], and then z1^4 will remain z^(-4).

hanspi42 avatar Apr 14 '23 18:04 hanspi42

Something that we do need is better Laurent polynomial and rational functions of Laurent polynomials support. See https://github.com/sympy/sympy/issues/5131. If z**-1/(1 - z**-1) were represented as a Laurent rational function in the polys, then such an expression would print and canonicalize to that form (and more importantly, there would be Laurent polynomial-specific routines implemented for it).

To fix the printing I think what we need is a flag in the printers to print monomials with negative powers as powers instead of using division. I'm not sure if we can change the default behavior because I expect most people will still prefer x + 1/x instead of x + x**-1.

asmeurer avatar Apr 14 '23 23:04 asmeurer

Something that we do need is better Laurent polynomial and rational functions of Laurent polynomials support. See #5131. If z**-1/(1 - z**-1) were represented as a Laurent rational function in the polys, then such an expression would print and canonicalize to that form (and more importantly, there would be Laurent polynomial-specific routines implemented for it).

This would help tremendously with everything that is discussed in #25040 !

hanspi42 avatar Apr 15 '23 17:04 hanspi42

I'm sorry I missed this issue. I do feel it would be worth considering #23589 again, as a way to give users better control over printing.

In short, make expression elements addressable, and make a way to set style rules, just like HTML + CSS. I'd be interested in continuing to work on this, if there was a will to have it in SymPy.

In #23589 an alternative system, involving rewrite rules, was also discussed.

skieffer avatar Apr 27 '23 23:04 skieffer