Absolute Value Simplification

712 views
Skip to first unread message

Nolan Dyck

unread,
Feb 23, 2016, 12:24:30 AM2/23/16
to sympy
Hi Everyone,

I've been poking around in the sympy source, and I've noticed that the `simplify` command does not deal with expressions like the following:

>>> from sympy import *
>>> from sympy.abc import x
>>> simplify(abs(cosh(x)))
Abs(cosh(x))

A simple glance at the graph of cosh(x) reveals that Abs(cosh(x)) == cosh(x). So, in case it's not obvious the above expression should return:

cosh(x)

I'd like to take a stab at implementing this but I need some direction. I hope this isn't duplicating something I have stupidly missed (the inequality solvers don't seem to identify cases where absolute value brackets are unnecessary. There are three key cases (as far as I can see):
  1. The argument within the abs brackets is non-negative over all combinations of all independent variables. Therefore, the absolute value brackets redundant / unnecessary and may be removed.
  2. The argument within the abs brackets is non-positive over all combinations of all independent variables. Therefore, the absolute value brackets may be removed and the expression may be multiplied by -1 for the same effect.
  3. The argument within the abs brackets contains both positive and negative values depending on the values of the independent variables.
Ok, so to implement the above rules in the general sense it makes sense to me to perform the following steps given the expression Abs(f(x_1,x_2,...x_n)):
  1. Determine the number of real roots of f(x).
    1. If there are one or more real roots then for each root:
      1. Determine whether the gradient of f(x) is zero:
        1. If non-zero slope at root, then argument expression obtains opposite signed value at some point, so return Abs(f(x))
      2. Slope of f(x) at root is 0.
      3. Determine if the root is an inflection point (need to figure out exactly how to test for this over multiple variables in the expression)
        1. If at inflection point then the expression will still become opposite signed on either side of the root, so return Abs(f(x))
    2. Any and all roots coincide with extrema values of f(x). Therefore f(x) may be represented without absolute value brackets.
  2. If f(x) >= 0 remove the absolute value brackets and return the argument expression.
  3. If f(x) < 0 remove the absolute value brackets, multiply the expression by -1 and return it.
There are a few things which I'm not sure how it will work out:
  • Imaginary numbers. Does anyone know if I will need to write special code for this, or should the above procedure work out anyway?
  • The case where a symbol in the expression has been defined with the positive flag:
>>> y = Symbol('y')
>>> simplify(abs(sinh(y)))
Abs(sinh(y))

>>> y = Symbol('y',positive=True)
>>> simplify(abs(sinh(y)))
sinh(y)
  • Are there sneaky ways of determining in a precise manner whether a function which cannot be reduced (e.g. cosh(x)+cos(x)) has real roots, even if finding those roots would only be possible numerically? Is there another sympy module which can help with this?
  • What about variables which produce no real roots over a given range? Is there a way to handle those? E.g.
>>> y = Symbol('y',range=[0,pi])
>>> simplify(abs(sin(y)))
sin(y)
  • Redundant absolute value brackets are removed somewhere. Can anyone tell me where exactly in the code this happens in the simplify function? I can't seem to find it:
>>> from sympy.abc import x
>>> simplify(abs(abs(x)+1))
Abs(x) + 1
>>> simplify(abs(x+1))
Abs(x + 1)

So, right now I have forked the sympy repo (see here) and set up my own little function in sympy.symplify called abssimp.py (just copied combsimp.py and started from there), and added an appropriate if-absolute check in the main simplify function. Is this the right way to go about adding such a feature? Would the code that I write here also be used in solve or something?

Any guidance / advice would be appreciated.

Thanks!
Nolan Dyck

Alan Bromborsky

unread,
Feb 23, 2016, 7:43:34 AM2/23/16
to sy...@googlegroups.com
Sympy assumes symbols to be complex.  For a real symbol you need

x = Symbol('x',real=True)

--
You received this message because you are subscribed to the Google Groups "sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.
To post to this group, send email to sy...@googlegroups.com.
Visit this group at https://groups.google.com/group/sympy.
To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/aeb34566-d4b8-46f4-8eb5-f7acabbd12f4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Nolan Dyck

unread,
Feb 23, 2016, 11:19:04 AM2/23/16
to sympy
I understand that symbols are assumed complex by default. But when I tell sympy the symbol is real, it does not make the appropriate simplification.

Here is what the dev branch produces for me right now:

>>> from sympy import *
>>> x = Symbol('x',real=True)
>>> simplify(abs(cosh(x)))
Abs(cosh(x))

Is it not possible to assess the 'realness' of the variable within the simplify function?

Nolan

Alan Bromborsky

unread,
Feb 23, 2016, 11:49:46 AM2/23/16
to sy...@googlegroups.com
I think assumptions only work with variables and not functions of variables.  You can specify x>0 so that |x| will simplify to x, but you cannot specify that cosh(x) > 0 for all real x.  The cosh case is simple, consider the norm of a vector in a Minkowski space.  It would be great if one could specify ahead of time that x_0**2-(x_1**2+x_2**2+x_3**2) was greater than zero or less than zero or zero as three separate cases.

Aaron Meurer

unread,
Feb 23, 2016, 3:19:09 PM2/23/16
to sy...@googlegroups.com
For cosh, it should just be handled by the assumptions system. That
is, cosh should have

def _eval_is_nonnegative(self):
if self.args[0].is_real:
return True

(and similar for the new assumptions _eval_refine).

On Mon, Feb 22, 2016 at 9:44 PM, Nolan Dyck <nolan....@gmail.com> wrote:
> Hi Everyone,
>
> I've been poking around in the sympy source, and I've noticed that the
> `simplify` command does not deal with expressions like the following:
>
>>>> from sympy import *
>>>> from sympy.abc import x
>>>> simplify(abs(cosh(x)))
> Abs(cosh(x))
>
> A simple glance at the graph of cosh(x) reveals that Abs(cosh(x)) ==
> cosh(x). So, in case it's not obvious the above expression should return:
>
> cosh(x)
>
> I'd like to take a stab at implementing this but I need some direction. I
> hope this isn't duplicating something I have stupidly missed (the inequality
> solvers don't seem to identify cases where absolute value brackets are
> unnecessary. There are three key cases (as far as I can see):

To do anything nontrivial in this area the inequality solvers will
definitely need to be improved.
This algorithm only works if the function is real and differentiable
over the given domain.

>
> Imaginary numbers. Does anyone know if I will need to write special code for
> this, or should the above procedure work out anyway?
> The case where a symbol in the expression has been defined with the positive
> flag:
>
>>>> y = Symbol('y')
>>>> simplify(abs(sinh(y)))
> Abs(sinh(y))
>
>>>> y = Symbol('y',positive=True)
>>>> simplify(abs(sinh(y)))
> sinh(y)
>
> Are there sneaky ways of determining in a precise manner whether a function
> which cannot be reduced (e.g. cosh(x)+cos(x)) has real roots, even if
> finding those roots would only be possible numerically? Is there another
> sympy module which can help with this?

solveset aims to give a full set of solutions to an equation over an
equation (compared with solve(), which makes no guarantees about
giving all solutions). It's a difficult problem, though, and so far it
is limited in what it can do.

> What about variables which produce no real roots over a given range? Is
> there a way to handle those? E.g.
>
>>>> y = Symbol('y',range=[0,pi])
>>>> simplify(abs(sin(y)))
> sin(y)

For a continuous function, you only need to check the intervals
between the zeros. You can find all the zeros of sin(y) in the range
[0, pi] (0 and pi), so you only have to check a value between them.

I don't know if this is necessarily the best way to implement this in
SymPy (one needs the ability to determine if a function is continuous,
and to find all its roots). At any rate, Symbol doesn't have a "range"
argument currently. You can use solveset(sin(y), y, domain=Interval(0,
pi)) (note there is a bug with this in master
https://github.com/sympy/sympy/issues/10671).

>
> Redundant absolute value brackets are removed somewhere. Can anyone tell me
> where exactly in the code this happens in the simplify function? I can't
> seem to find it:

It happens in the Abs constructor, i.e., when you create the Abs. Abs
is defined in sympy.functions.elementary.complexes.

>
>>>> from sympy.abc import x
>>>> simplify(abs(abs(x)+1))
> Abs(x) + 1
>>>> simplify(abs(x+1))
> Abs(x + 1)
>
> So, right now I have forked the sympy repo (see here) and set up my own
> little function in sympy.symplify called abssimp.py (just copied combsimp.py
> and started from there), and added an appropriate if-absolute check in the
> main simplify function. Is this the right way to go about adding such a
> feature? Would the code that I write here also be used in solve or
> something?
>
> Any guidance / advice would be appreciated.
>
> Thanks!
> Nolan Dyck
>

Nolan Dyck

unread,
Feb 23, 2016, 5:43:39 PM2/23/16
to sympy
Great. Thanks Aaron and Brombo for your replies. I have a few follow up questions:
  1. I'd like to add in those checks into the assumptions system for hyperbolic functions at least. Is that a reasonable entry level task?
  2. I noticed that code sample you gave checks the is_real field on the first argument. Is there an is_positive field I could check so that sinh  and other functions could also be simplified in some cases (i.e. x is positive, Abs(sinh(x)) -> sinh(x))?
Nolan

Aaron Meurer

unread,
Feb 23, 2016, 9:40:31 PM2/23/16
to sy...@googlegroups.com
On Tue, Feb 23, 2016 at 5:43 PM, Nolan Dyck <nolan....@gmail.com> wrote:
> Great. Thanks Aaron and Brombo for your replies. I have a few follow up
> questions:
>
> I'd like to add in those checks into the assumptions system for hyperbolic
> functions at least. Is that a reasonable entry level task?

Yes. It's pretty much just what I showed here.

> I noticed that code sample you gave checks the is_real field on the first
> argument. Is there an is_positive field I could check so that sinh and
> other functions could also be simplified in some cases (i.e. x is positive,
> Abs(sinh(x)) -> sinh(x))?

Yes. These are all the assumptions
http://docs.sympy.org/latest/modules/core.html?highlight=assumptions#module-sympy.core.assumptions.

Aaron Meurer
> https://groups.google.com/d/msgid/sympy/f41292b5-6d52-4ae5-b7bb-87b7c6d9b807%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages