Simplifying expressions involving the Abs function

608 views
Skip to first unread message

B A

unread,
Mar 28, 2021, 5:14:57 AM3/28/21
to sympy
I am a sympy beginner, and fairly new to python, so I suspect that my question has a simple answer,  but have not been able to figure it out myself.

I have sympy expressions containing the built-in Abs function. The arguments of Abs() are polynomials in a=symbol('a', real=True,positive=True) . Here are a three examples: 

Abs(a**2 + 2)
sqrt(2)*Abs(2*a**2 - 1)/3
sqrt(2)*(a + 1)*Abs(a - 1)/3

Here's the point: I know that 'a' is approximately 0.6.  So in cases where the argument of Abs has an unambiguous sign (within floating-point precision) I would like to simplify the absolute value.  For example in the three cases above I would like:

Abs(a**2 + 2) -> a**2 + 2
sqrt(2)*Abs(2*a**2 - 1)/3  -> sqrt(2)*(1- 2*a**2)/3
sqrt(2)*(a + 1)*Abs(a - 1)/3 -> sqrt(2)*(a + 1)*(1-a)/3

where the right arrow '->' indicates replacement or simplification.  Note the change of sign in the second and third examples, because (for example) I know that (a-1) is negative.

Is there a (simple) way to implement this?  

Thank you!
Bruce

B A

unread,
Mar 30, 2021, 10:44:51 AM3/30/21
to sympy
Could anyone suggest a solution for this?  I can make a list of substitutions by hand (as below) and pass that to the .subs() method, but surely there is a better way.

s_list={
    Abs(a       - 1)  : S(1)  - a,
    Abs(a       - 2)  : S(2)  - a,
    Abs(2*a     - 3)  : S(3)  - S(2)*a,
    Abs(a**2    - 2)  : S(2)  - a**2,
    Abs(a**2    - 3)  : S(3)  - a**2,
    Abs(a**2    - 11) : S(11) - a**2,
    Abs(2*a**2  - 1)  : S(1)  - S(2)*a**2,
    Abs(2*a**2  - 3)  : S(3)  - S(2)*a**2,
    Abs(3*a**2  - 2)  : S(2)  - S(3)*a**2,
    Abs(3*a**2  - 16) : S(16) - S(3)*a**2,
    Abs(4*a**2  - 3)  : S(3)  - S(4)*a**2,
    Abs(5*a**2  - 4)  : S(4)  - S(5)*a**2,
    Abs(5*a**2  - 7)  : S(7)  - S(5)*a**2,
    Abs(5*a**2  - 16) : S(16) - S(5)*a**2,
...
}

Chris Smith

unread,
Mar 30, 2021, 11:27:44 AM3/30/21
to sympy
This is a case where you know the magnitude of a is less than 1 so for all the cases where you are working with constants in the integer range, replaceing `a` with a symbolic `1/2` will do the work for you. (Even stating that `a` is positive will work for several cases.)
```
>>> a = var('a',positive=True);eqs=Abs(a**2 + 2),Abs(2*a**2 - 1),Abs(a - 1)
>>> eqs
(a**2 + 2, Abs(2*a**2 - 1), Abs(a - 1))
>>> t = var('two',even=True,prime=True)
>>> [i.subs(a,1/two).subs(two,1/a) for i in eqs]
[a**2 + 2, 1 - 2*a**2, 1 - a]

>>> eqs = (Abs(a       - 1),
... Abs(a       - 2),
... Abs(2*a     - 3),
... Abs(a**2    - 2),
... Abs(a**2    - 3),
... Abs(a**2    - 11),
... Abs(2*a**2  - 1),
... Abs(2*a**2  - 3),
... Abs(3*a**2  - 2),
... Abs(3*a**2  - 16),
... Abs(4*a**2  - 3),
... Abs(5*a**2  - 4),
... Abs(5*a**2  - 7),
... Abs(5*a**2  - 16))
>>> eqs
(Abs(a - 1), Abs(a - 2), Abs(2*a - 3), Abs(a**2 - 2), Abs(a**2 - 3), Abs(a**2 -
11), Abs(2*a**2 - 1), Abs(2*a**2 - 3), Abs(3*a**2 - 2), Abs(3*a**2 - 16), Abs(4*
a**2 - 3), Abs(5*a**2 - 4), Abs(5*a**2 - 7), Abs(5*a**2 - 16))
>>> [i.subs(a,1/two).subs(two,1/a) for i in eqs]
[1 - a, 2 - a, 3 - 2*a, 2 - a**2, 3 - a**2, 11 - a**2, 1 - 2*a**2, 3 - 2*a**2, 2
 - 3*a**2, 16 - 3*a**2, 3 - 4*a**2, 4 - 5*a**2, 7 - 5*a**2, 16 - 5*a**2]
```

If you want to target `Abs` in an arbitrary expression, the using `replace` would be a good way to go: `expr.replace(lambda x: isinstance(x, Abs), lambda x: x.subs(a,1/two).subs(two,1/a)`

/c

B A

unread,
Mar 30, 2021, 11:39:51 AM3/30/21
to sympy
Here is one solution that seems to work.  To simplify Z I use Z.replace(Abs, MyAbs) with

def MyAbs(x):
    x1=symbols('x1',real=True,positive=True)
    x1 = x.evalf(subs={a:0.573})
    if x1 < 0.0:
        return S(-1)*x
    else:
        return x
Is this a reasonable way to go, or are there gotchas that I should be aware of?
On Sunday, March 28, 2021 at 11:14:57 AM UTC+2 B A wrote:

Aaron Meurer

unread,
Mar 30, 2021, 3:15:00 PM3/30/21
to sympy
On Tue, Mar 30, 2021 at 9:39 AM 'B A' via sympy <sy...@googlegroups.com> wrote:
>
> Here is one solution that seems to work. To simplify Z I use Z.replace(Abs, MyAbs) with
>
> def MyAbs(x):
> x1=symbols('x1',real=True,positive=True)
> x1 = x.evalf(subs={a:0.573})
> if x1 < 0.0:
> return S(-1)*x
> else:
> return x
> Is this a reasonable way to go, or are there gotchas that I should be aware of?

A few minor points.

- Defining x1 as a symbol does nothing in this code, as you
immediately overwrite it with x.evalf(...).
- S(-1) is unnecessary. You can just use -x.
- Your code assumes that the expression is a function in the variable
a (and only a). But perhaps that assumption is fine for your use-case.

The main gotcha however is that the code is wrong if the abs is
positive for that specific value of a but not for the general domain
of a. A more robust way would be to use the maximum() and minimum()
functions to get the maximum and minimum values of the abs argument on
the interval [0, 1]:

>>> maximum(2*a**2 - 1, a, Interval(0, 1))
1
>>> minimum(2*a**2 - 1, a, Interval(0, 1))
-1

This tells you that abs(2*a**2 - 1) cannot be simplified like this for
a in [0, 1], because it is both positive and negative on that
interval. To simplify it, you would need to factor it or write it as a
piecewise.

Aaron Meurer

> On Sunday, March 28, 2021 at 11:14:57 AM UTC+2 B A wrote:
>>
>> I am a sympy beginner, and fairly new to python, so I suspect that my question has a simple answer, but have not been able to figure it out myself.
>>
>> I have sympy expressions containing the built-in Abs function. The arguments of Abs() are polynomials in a=symbol('a', real=True,positive=True) . Here are a three examples:
>>
>> Abs(a**2 + 2)
>> sqrt(2)*Abs(2*a**2 - 1)/3
>> sqrt(2)*(a + 1)*Abs(a - 1)/3
>>
>> Here's the point: I know that 'a' is approximately 0.6. So in cases where the argument of Abs has an unambiguous sign (within floating-point precision) I would like to simplify the absolute value. For example in the three cases above I would like:
>>
>> Abs(a**2 + 2) -> a**2 + 2
>> sqrt(2)*Abs(2*a**2 - 1)/3 -> sqrt(2)*(1- 2*a**2)/3
>> sqrt(2)*(a + 1)*Abs(a - 1)/3 -> sqrt(2)*(a + 1)*(1-a)/3
>>
>> where the right arrow '->' indicates replacement or simplification. Note the change of sign in the second and third examples, because (for example) I know that (a-1) is negative.
>>
>> Is there a (simple) way to implement this?
>>
>> Thank you!
>> Bruce
>
> --
> 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 view this discussion on the web visit https://groups.google.com/d/msgid/sympy/5c6a7de4-e709-4a8c-88f1-ce3f44ee277an%40googlegroups.com.

Oscar Benjamin

unread,
Mar 30, 2021, 3:22:32 PM3/30/21
to sympy
On Tue, 30 Mar 2021 at 16:39, 'B A' via sympy <sy...@googlegroups.com> wrote:
>
> Here is one solution that seems to work. To simplify Z I use Z.replace(Abs, MyAbs) with
>
> def MyAbs(x):
> x1=symbols('x1',real=True,positive=True)
> x1 = x.evalf(subs={a:0.573})
> if x1 < 0.0:
> return S(-1)*x
> else:
> return x
> Is this a reasonable way to go, or are there gotchas that I should be aware of?

That's one way to do it. It will only work for the `Abs` function
though. It is possible to declare a symbol that has a numeric value
using NumberSymbol which is the superclass for both E and pi. You can
do it like this:

class A(NumberSymbol):
def _as_mpf_val(self, prec):
return Float('0.6', prec)._mpf_

All of the other expressions can simplify through evaluation which
implicitly uses the assumptions system which will in turn use
numerical evaluation when possible:

In [107]: a = A()

In [108]: Abs(a - 0.5)
Out[108]: -0.5 + A()

In [109]: Abs(a - 0.7)
Out[109]: 0.7 - A()

Perhaps there should be an easier way to do this like NumberSymbol('a', 0.6).


Oscar

Bruce Allen

unread,
Mar 30, 2021, 4:48:14 PM3/30/21
to sy...@googlegroups.com
Hi Aaron,

Thanks for your help!

>> def MyAbs(x):
>> x1=symbols('x1',real=True,positive=True)
>> x1 = x.evalf(subs={a:0.573})
>> if x1 < 0.0:
>> return S(-1)*x
>> else:
>> return x

> - Defining x1 as a symbol does nothing in this code, as you
> immediately overwrite it with x.evalf(...).

OK. I'm still hazy on when Python allocates new storage for an object.

> - S(-1) is unnecessary. You can just use -x.

OK.

> - Your code assumes that the expression is a function in the variable
> a (and only a). But perhaps that assumption is fine for your use-case.

This assumption is correct: all of my expressions are polynomials in a,
and rational powers of those polynomials. So 'a' is the only variable
that appears.

> The main gotcha however is that the code is wrong if the abs is
> positive for that specific value of a but not for the general domain
> of a.

I only need equations that are valid in an infinitesimal neighborhood
around a=0.57

> A more robust way would be to use the maximum() and minimum()
> functions to get the maximum and minimum values of the abs argument on
> the interval [0, 1]:

I can test at the endpoints of a small interval, say 0.56 < a < 0.58.
But the expressions are complicated enough that solving for the extrema
will be too expensive. Fortunately I do have a consistency test at the
end which should catch any mistakes.

>>>> maximum(2*a**2 - 1, a, Interval(0, 1))
> 1
>>>> minimum(2*a**2 - 1, a, Interval(0, 1))
> -1

> This tells you that abs(2*a**2 - 1) cannot be simplified like this for
> a in [0, 1], because it is both positive and negative on that
> interval. To simplify it, you would need to factor it or write it as a
> piecewise.

You are correct. Fortunately I only need results that are correct in an
open set around a specific value of a.

What I might do is to modify MyAbs so that it maintains a global
dictionary of all arguments it has been called with, adding new
arguments if they are not found in the dictionary. Later I can check
that the objects stored in the dictionary have no roots within the small
interval. Does that seem reasonable?

Cheers,
Bruce

Aaron Meurer

unread,
Mar 30, 2021, 5:56:24 PM3/30/21
to sympy
On Tue, Mar 30, 2021 at 2:48 PM 'Bruce Allen' via sympy
<sy...@googlegroups.com> wrote:
>
> Hi Aaron,
>
> Thanks for your help!
>
> >> def MyAbs(x):
> >> x1=symbols('x1',real=True,positive=True)
> >> x1 = x.evalf(subs={a:0.573})
> >> if x1 < 0.0:
> >> return S(-1)*x
> >> else:
> >> return x
>
> > - Defining x1 as a symbol does nothing in this code, as you
> > immediately overwrite it with x.evalf(...).
>
> OK. I'm still hazy on when Python allocates new storage for an object.

This section of the tutorial may help to clear things up
https://docs.sympy.org/latest/tutorial/gotchas.html. This blog post
also goes into more detail about how variables work in Python
https://nedbatchelder.com/text/names.html.

The important thing is that variable names (what is on the left-hand
side of the =) have no bearing whatsoever on SymPy expressions. So
what you are doing here is

- Defining a SymPy symbol named x1 and assigning it to a Python
variable, also named x1 (these two are unrelated and need not be the
same as one another).
- Creating a number with x.evalf(), and assigning that to the Python
variable x1, overwriting the symbol that was previously assigned to
it.

SymPy objects don't have any side effects when you create them, so the
x1=symbols('x1',real=True,positive=True) is effectively dead code.

>
> > - S(-1) is unnecessary. You can just use -x.
>
> OK.
>
> > - Your code assumes that the expression is a function in the variable
> > a (and only a). But perhaps that assumption is fine for your use-case.
>
> This assumption is correct: all of my expressions are polynomials in a,
> and rational powers of those polynomials. So 'a' is the only variable
> that appears.
>
> > The main gotcha however is that the code is wrong if the abs is
> > positive for that specific value of a but not for the general domain
> > of a.
>
> I only need equations that are valid in an infinitesimal neighborhood
> around a=0.57

If you are confident about these given assumptions, your code seems
fine. You could add guards to check that the argument of the abs
really is what you expect if you want to make it a little more robust,
in case any unrelated absolute values happen to appear at some point.
Oscar's suggestion of swapping a in and out with a custom object that
knows it evaluates to 0.57 should also work. I wouldn't really say one
is better than the other, it's more a question of style. Personally I
like the replace solution better because it's more robust (it doesn't
rely on Abs knowing that it can evaluate itself given the custom
object).

>
> > A more robust way would be to use the maximum() and minimum()
> > functions to get the maximum and minimum values of the abs argument on
> > the interval [0, 1]:
>
> I can test at the endpoints of a small interval, say 0.56 < a < 0.58.
> But the expressions are complicated enough that solving for the extrema
> will be too expensive. Fortunately I do have a consistency test at the
> end which should catch any mistakes.
>
> >>>> maximum(2*a**2 - 1, a, Interval(0, 1))
> > 1
> >>>> minimum(2*a**2 - 1, a, Interval(0, 1))
> > -1
>
> > This tells you that abs(2*a**2 - 1) cannot be simplified like this for
> > a in [0, 1], because it is both positive and negative on that
> > interval. To simplify it, you would need to factor it or write it as a
> > piecewise.
>
> You are correct. Fortunately I only need results that are correct in an
> open set around a specific value of a.
>
> What I might do is to modify MyAbs so that it maintains a global
> dictionary of all arguments it has been called with, adding new
> arguments if they are not found in the dictionary. Later I can check
> that the objects stored in the dictionary have no roots within the small
> interval. Does that seem reasonable?

That sounds fine. You can also use functools.lru_cache to get the same effect.

Aaron Meurer

>
> Cheers,
> Bruce
>
> --
> 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 view this discussion on the web visit https://groups.google.com/d/msgid/sympy/615015dd-29b3-539e-89a4-28fd7c2b879b%40googlemail.com.

Bruce Allen

unread,
Mar 30, 2021, 11:35:31 PM3/30/21
to sy...@googlegroups.com
Hi Aaron,

> This section of the tutorial may help to clear things up
> https://docs.sympy.org/latest/tutorial/gotchas.html. This blog post
> also goes into more detail about how variables work in Python
> https://nedbatchelder.com/text/names.html.

Thanks for the pointers. I'll study them.

>> What I might do is to modify MyAbs so that it maintains a global
>> dictionary of all arguments it has been called with, adding new
>> arguments if they are not found in the dictionary. Later I can check
>> that the objects stored in the dictionary have no roots within the small
>> interval. Does that seem reasonable?

> That sounds fine. You can also use functools.lru_cache to get the same effect.

I've thought about this a bit more.

I suspect that the number of distinct arguments to Abs() is at most a
few thousand. Each of these arguments is a polynomial P(a) in 'a',
with rational coefficients. If I maintain a global dictionary of these
arguments, then each time a new P(a) appears, I can check if it has any
roots along the real axis between (say) 0.56 and 0.58.

While P(a) might be high order and not factorizable, any respectable
numerical root-finder could do such a check in a fraction of a
millisecond. Since P(a) is polynomial, its derivative is easily
evaluated, which means that a Newton-Raphson (NR) root-finder could be
employed. Is there a NR or similar root-finder built in to sympy which
I can use for this?

While it is not bullet-proof, I can also do the first pass of NR in my
own code, before adding a new P(a) to the dictionary. Denote the
center-point of my small neighborhood by a_0. I can evaluate the
numerical values of P(a_0) and P'(a_0) and then form |P(a_0)/P'(a_0)|.
If that is significantly larger than the width of my interval around
a_0, then the closest root is very likely to lie outside the interval.

Cheers,
Bruce

Chris Smith

unread,
Mar 30, 2021, 11:48:51 PM3/30/21
to sympy
Oscar posted code at issue https://github.com/sympy/sympy/issues/19164 for a interva-based Newton solver.

/c

Bruce Allen

unread,
Mar 31, 2021, 3:03:08 AM3/31/21
to sy...@googlegroups.com
Dear Chris,

On 31.03.21 05:48, Chris Smith wrote:
> Oscar posted code at issue https://github.com/sympy/sympy/issues/19164
> for a interva-based Newton solver.

Thank you, that's very useful. I didn't know about interval arithmetic.

I just implemented the following, which works very well and helps to
increase my confidence that problems will be caught:

d_abs={}
d_problems={}

def MyAbs(x):
# check if this argument is already in dictionary
if x in d_abs:
return d_abs[x]
# see if there are any roots nearby
soln_list = nsolve_interval(x, 0.563, 0.583)
# nearby roots provide a warning message and get saved
if len(soln_list) > 0:
print('WARNING: ambiguous case found, argument of Abs() is', x)
d_problems[x]=soln_list
# Check sign, determine correct output
x1 = x.evalf(subs={a:0.573})
if x1 < 0.0:
out = -x
else:
out = x
# save into dictionary and return
d_abs[x] = out
return out

Cheers,
Bruce

B A

unread,
Apr 1, 2021, 10:43:35 AM4/1/21
to sympy
What is described above has worked well for me.  But there is a further simplification step that I need help with.

I have some long expressions containing terms contain terms which look like this example:
sqrt(4*a**2 + 1)*sqrt(1/(4*a**6 - 15*a**4 + 12*a**2 + 4))
How can I instruct sympy to combine such square roots and factor the arguments? In this example that would lead to:

sqrt(factor((4*a**2 + 1)/(4*a**6 - 15*a**4 + 12*a**2 + 4)))
=
1/Abs(a**2 - 2)

Oscar Benjamin

unread,
Apr 1, 2021, 12:53:49 PM4/1/21
to sympy
On Thu, 1 Apr 2021 at 15:43, 'B A' via sympy <sy...@googlegroups.com> wrote:
>
> What is described above has worked well for me. But there is a further simplification step that I need help with.
>
> I have some long expressions containing terms contain terms which look like this example:
> sqrt(4*a**2 + 1)*sqrt(1/(4*a**6 - 15*a**4 + 12*a**2 + 4))
> How can I instruct sympy to combine such square roots and factor the arguments? In this example that would lead to:
>
> sqrt(factor((4*a**2 + 1)/(4*a**6 - 15*a**4 + 12*a**2 + 4)))
> =
> 1/Abs(a**2 - 2)

You can declare a to be real:

In [12]: a = Symbol('a', real=True)

In [13]: expr = sqrt(factor((4*a**2 + 1)/(4*a**6 - 15*a**4 + 12*a**2 + 4)))

In [14]: expr
Out[14]:
1
────────
│ 2 │
│a - 2│


Oscar

Aaron Meurer

unread,
Apr 1, 2021, 3:25:21 PM4/1/21
to sympy
powsimp() would normally be the function to do this.

However, it currently can't work if a is defined as real because
sqrt() splits apart automatically

>>> var('a', real=True)
a
>>> sqrt((4*a**2 + 1)*(1/(4*a**6 - 15*a**4 + 12*a**2 + 4)))
sqrt(4*a**2 + 1)*sqrt(1/(4*a**6 - 15*a**4 + 12*a**2 + 4))

I think we should remove this automatic evaluation from sqrt(). If we
did, then simply calling powsimp() on your expression would do what
you want.

Aaron Meurer
> --
> 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 view this discussion on the web visit https://groups.google.com/d/msgid/sympy/44ef5b14-ea0a-45ca-a978-3b09f1cf82ddn%40googlegroups.com.

Aaron Meurer

unread,
Apr 1, 2021, 3:26:04 PM4/1/21
to sympy
On Thu, Apr 1, 2021 at 1:25 PM Aaron Meurer <asme...@gmail.com> wrote:
>
> powsimp() would normally be the function to do this.
>
> However, it currently can't work if a is defined as real because
> sqrt() splits apart automatically
>
> >>> var('a', real=True)
> a
> >>> sqrt((4*a**2 + 1)*(1/(4*a**6 - 15*a**4 + 12*a**2 + 4)))
> sqrt(4*a**2 + 1)*sqrt(1/(4*a**6 - 15*a**4 + 12*a**2 + 4))
>
> I think we should remove this automatic evaluation from sqrt(). If we
> did, then simply calling powsimp() on your expression would do what
> you want.

I believe this is the corresponding issue for this
https://github.com/sympy/sympy/issues/7376

Aaron Meurer

Chris Smith

unread,
Apr 1, 2021, 7:04:05 PM4/1/21
to sympy
You can do this by simply factoring the expression with keyword `deep`::

```
>>> var('a')
a
>>> factor(sqrt(((4*a**2 + 1)/(4*a**6 - 15*a**4 + 12*a**2 + 4))), deep=True)
1/Abs(a**2 - 2)
```

Bruce Allen

unread,
Apr 3, 2021, 2:21:04 AM4/3/21
to sy...@googlegroups.com
[NOTE: I mistakenly sent several replies to individuals rather than to
the group. Several are moot, but I did not want to leave them off-list.
So I am resending below. Sorry about that! Bruce]

Hi Oscar,

Thanks for your comments.

>> def MyAbs(x):
>> x1=symbols('x1',real=True,positive=True)
>> x1 = x.evalf(subs={a:0.573})
>> if x1 < 0.0:
>> return S(-1)*x
>> else:
>> return x

> That's one way to do it. It will only work for the `Abs` function
> though.

Abs() is the only function I have encountered where sympy's normal
simplifications are not sufficient. This is because I have additional
knowledge about the value and range of 'a'. So my only goal is to
incorporate that additional information into the simplification process.

It is possible to declare a symbol that has a numeric value
> using NumberSymbol which is the superclass for both E and pi. You can
> do it like this:
>
> class A(NumberSymbol):
> def _as_mpf_val(self, prec):
> return Float('0.6', prec)._mpf_

I see...

> All of the other expressions can simplify through evaluation which
> implicitly uses the assumptions system which will in turn use
> numerical evaluation when possible:
>
> In [107]: a = A()
>
> In [108]: Abs(a - 0.5)
> Out[108]: -0.5 + A()
>
> In [109]: Abs(a - 0.7)
> Out[109]: 0.7 - A()

I need to experiment with this, but am worried that you can only
simplify expressions that evaluate to floats. I need to simplify
expressions that are exact. So I need to try

Abs(a - Rational(1,2)) and Abs(a - Rational(7,10))

Will those work in the same way as your examples above?

> Perhaps there should be an easier way to do this like NumberSymbol('a', 0.6).

Something like that, which has limited scope (in my case, for
determining the sign of Abs(...), would be useful. But I'm not sure
that this occurs very often!

Cheers,
Bruce

Bruce Allen

unread,
Apr 3, 2021, 2:23:28 AM4/3/21
to sy...@googlegroups.com
[NOTE: I mistakenly sent several replies to individuals rather than to
the group. Several are moot, but I did not want to leave them off-list.
So I am resending below. Sorry about that! Bruce]


Hi Oscar,

Thanks for the quick reply.

The symbol 'a' is declared to be real and positive. But my example is
NOT automatically simplified:

>>> a=symbols('a', real=True, positive=True)

>>> expr = sqrt(4*a**2 + 1)*sqrt(1/(4*a**6 - 15*a**4 + 12*a**2 + 4))

>>> simplify(expr)

sqrt(4*a**2 + 1)*sqrt(1/(4*a**6 - 15*a**4 + 12*a**2 + 4))

Could you suggest how I might direct sympy to simplify it as shown
below? In my expressions, the arguments of the square roots are
typically polynomials in 'a', or ratios of such polynomials.

Cheers,
Bruce

Bruce Allen

unread,
Apr 3, 2021, 2:24:10 AM4/3/21
to sy...@googlegroups.com
[NOTE: I mistakenly sent several replies to individuals rather than to
the group. Several are moot, but I did not want to leave them off-list.
So I am resending below. Sorry about that! Bruce]


Dear Chris,

THANK YOU! That's an excellent solution for me. I did not know about
it and probably would not have found it on my own.

Cheers,
Bruce
> --
> 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
> <mailto:sympy+un...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/sympy/d417c1a4-60eb-460b-ad5d-d78ad854bf2bn%40googlegroups.com
> <https://groups.google.com/d/msgid/sympy/d417c1a4-60eb-460b-ad5d-d78ad854bf2bn%40googlegroups.com?utm_medium=email&utm_source=footer>.

Bruce Allen

unread,
Apr 3, 2021, 2:24:36 AM4/3/21
to sy...@googlegroups.com
[NOTE: I mistakenly sent several replies to individuals rather than to
the group. Several are moot, but I did not want to leave them off-list.
So I am resending below. Sorry about that! Bruce]


Dear Aaron,

Thank you again for your help. I found the solution to my problem
(which involves the Voronoi cells of a particular 9-dimensional lattice)
last night. It's the first time that I have used sympy, and I have been
impressed by its capabilities. I have been a Mathematica user since
before it existed as a formal product, but it is not appropriate for my
current problem.

>> I think we should remove this automatic evaluation from sqrt(). If we
>> did, then simply calling powsimp() on your expression would do what
>> you want.
>
> I believe this is the corresponding issue for this
> https://github.com/sympy/sympy/issues/7376

Yes, that's the issue. Indeed, after studying the documentation in the
past days, I had tried powsimp() and some of its friends, but they did
not have any effect.

A last comment to you and the other experts here.

I signed up to this mailing list to get some help with my issues, which
are now resolved. But of course I saw the other correspondence there,
and wanted to say that I am very impressed by the discussion that is
underway, regarding possible changes to the assumptions system. I'm
sure one of the reasons that sympy works well is exactly because such
discussions are being carried out, with the pros and cons hashed out
among the experts, before the code base gets modified.

I lack the CS competence to be of any use in this (truth be told, K&R C
is probably the most fluent of my ~20 different computer languages) but
did have one suggestion, which is entirely from the user perspective.

In your design choice, IMO the overriding concern should be run-time
efficiency: pick the solution which is potentially the most efficient in
terms of computer cycles. The reason is simple. We use computers to do
calculations because they are impossible to do by hand. So provided
that it does not lead to internal complications which are overwhelming,
efficiency and (user) simplicity should be paramount.

(Also, if possible, old code should not break. But that's second order
IMO.)

Cheers,
Bruce

Chris Smith

unread,
Apr 3, 2021, 8:55:49 AM4/3/21
to sympy
Another thought is that, for a real variable, `Abs` can be rewritten as `Piecewise`:

```
>>> var('a',real=True)
a
>>> Abs(a).rewrite(Piecewise)
Piecewise((a, a >= 0), (-a, True))
>>> eq = Abs(a**2 + 2) + sqrt(2)*Abs(2*a**2 - 1)/3 + sqrt(2)*(a + 1)*Abs(a - 1)/3
>>> eq.rewrite(Piecewise).simplify()
Piecewise(
(a**2 + sqrt(2)*a**2 - 2*sqrt(2)/3 + 2, a >= 1), 
(sqrt(2)*a**2/3 + a**2 + 2, a**2 >= 1/2), 
(-sqrt(2)*a**2 + a**2 + 2*sqrt(2)/3 + 2, True))
```
/c
Reply all
Reply to author
Forward
0 new messages