How to round specific parts of symbolic expression using sympy? - sympy

I'm new to python and sympy and am a little lost. What's the easiest way to round all of the numbers except 0.268994781998603, 0.525103332486078, and 0.2357023740927390 in equations that look like this:
0.268994781998603*x**0.24883285 + 0.525103332486078*exp(-Abs(2.011218*x - 1.101318)) + 0.2357023740927390*x**0.25234357
Would it have to do with using srepr?
Ultimately, I'd like to round the exponents 0.24883285 and 0.25234357 to .25 so sympy will combine those respective terms when using sympify.
Thanks!

It looks like what you want to do is keep the high precision Float but round the lower precision ones. You can discriminated based on the associated precision. I defined 'eq' to be the equation you gave above:
>>> for i in sorted(eq.atoms(Float)):
... print(i._prec, i)
...
27 -1.101318
53 0.235702374092739
30 0.24883285
30 0.25234357
53 0.268994781998603
53 0.525103332486078
27 2.011218
So let's get the lower precision floats in a list:
>>> lp = [i for i in eq.atoms(Float) if i._prec <= 30]
And let's define a replacement dictionary that rounds to two decimal places:
>>> reps = {k: k.round(2) for k in lp}
And now use it to replace those Floats in eq
>>> eq.subs(reps)
>>> eq.subs(reps)
0.504697156091342*x**0.25 + 0.525103332486078*exp(-Abs(2.01*x - 1.1))
The exponents, now being the same, caused the two terms to join.
If you rounded at two significant figures you would get:
>>> reps = {k: k.n(2) for k in lp}
>>> eq.subs(reps)
0.268994781998603*x**0.25 + 0.235702374092739*x**0.25 + 0.525103332486078*exp(-Abs(2.0*x - 1.1))
The terms don't join because these 2-sig-fig values are not exactly the same. Conversion to a string and re-sympification will work, however. (But I would stick to the round version.)
>>> eq2 = _
>>> from sympy import S
>>> S(str(eq2))
0.504697156091342*x**0.25 + 0.525103332486078*exp(-Abs(2.0*x - 1.1))
To just replace Floats in a given region of the expression there are lots of ways to parse up the expression: coefficients of Mul, constant terms of Add, etc... In the comments below you say that you want to make the change in sin, sign, exp and exponents (Pow) so something like this can work:
>>> from sympy import sin, sign, exp, Pow
>>> eq.replace(
... lambda x: isinstance(x, (sin, sign, exp, Pow)),
... lambda x: x.xreplace(dict([(i,i.round(2)) for i in x.atoms(Float)])))
0.504697156091342*x**0.25 + 0.525103332486078*exp(-Abs(2.01*x - 1.1))

Related

Python - Rounding to two specific digits EXACTLY

The number I have = 52.003
The number I want = 52.00
The number I get after rounding to 2 decimal places:
round(52.003, 2)
>>> 52.0
How do I keep the second digit without Python automatically rounding it?
You can use the format() function in Python.
"{0:.2f}".format(round(52.003, 2))
You can also use the string formatting operator.
'%.2f' % 52.003
You can modify the output format like this:
a = 52.003
print "%.2f" % a
Try this function :
import numpy as np
Round = lambda x, n: eval('"%.' + str(int(n)) + 'f" % ' + repr(x))
a = Round(52.003,2)
print a
>>> 52.00
Just indicate the number of decimals you want as a kwarg. However the result will be a string.

Python code to convert decimal to binary

I need to write a Python script that will convert and number x in base 10 to binary with up to n values after the decimal point. And I can't just use bin(x)! Here's what I have:
def decimal_to_binary(x, n):
x = float(x)
test_str = str(x)
dec_at = test_str.find('.')
#This section will work with numbers in front of the decimal
p=0
binary_equivalent = [0]
c=0
for m in range(0,100):
if 2**m <= int(test_str[0:dec_at]):
c += 1
else:
break
for i in range(c, -1, -1):
if 2**i + p <= (int(test_str[0:dec_at])):
binary_equivalent.append(1)
p = p + 2**i
else:
binary_equivalent.append(0)
binary_equivalent.append('.')
#This section will work with numbers after the decimal
q=0
for j in range(-1, -n-1, -1):
if 2**j + q <= (int(test_str[dec_at+1:])):
binary_equivalent.append(1)
q = q + 2**j
else:
binary_equivalent.append(0)
print float((''.join(map(str, binary_equivalent))))
So say you call the function by decimal_to_binary(123.456, 4) it should convert 123.456 to binary with 4 places after the decimal, yielding 1111011.0111.
The first portion is fine - it will take the numbers in front of the decimal, in this case 123, and convert it to binary, outputting 1111011
However, the second portion, which deals with values after the decimal, is not doing what I think it should. The output it gives is not .0111, but rather .1111
I ran through the code with pen and paper writing down the value for each variable and it should work. But it doesn't. Can anyone help me fix this?
I call the function as decimal_to_binary(123.456, 4) and it prints out 1111011.1111
You're close, but there's an issue with your comparison when you go beyond the decimal:
if 2**j + q <= (int(test_str[dec_at+1:])):
What you're doing here is comparing a fractional value (since j is always negative) to a whole integer value. This comparison will, for all practical purposes, always be true.
Based on the surrounding logic, my guess would be that you're attempting to compare it to the actual decimal value here. Using your data, that would be 0.4 on the first iteration, so you expect the statement to be evaluated as:
0.5 <= 0.4
The actual comparison in your code is:
0.5 <= 4
There are two separate issues here:
You're taking all of the numbers after the decimal point, but not actually including the decimal point itself in your extraction. This is primarily why you are getting whole numbers in your test incorrectly. This is fixed simply by referencing test_str[dec_at:] rather than test_str[dec_at+1:]
You're casting to int. Even if you applied the change in the first point, your code would still not run correctly. However, in that case it would be because the cast would truncate the value down to 0 on every iteration. Cast to a float instead: float(test_str[dec_at:])
Your comparison line thus becomes if 2**j + q <= (float(test_str[dec_at:])):, which provides the correct output on my machine.
Note that floating point comparisons can be "finicky" in some situations, depending on rounding and the like. There are ways to mitigate this if needed.

Mod function fails in python for large numbers

This python code
for x in range(20, 50):
print(x,math.factorial(x),math.pow(2,x), math.factorial(x) % math.pow(2,x) )
calculates fine up to x=22 but the mod when x>22 is always 0.
Wolframalpha says the results for x>22 are nonzero.
For example, when x=23 we get 6815744.
I guess this problem results from how python actually calculates the mod function but was wondering if anyone actually knew.
You are running into floating point limitations; math.pow() returns a floating point number, so both operands are coerced to floats. For x = 23, math.factorial(x) returns an integer larger than what a float can model:
>>> math.factorial(23)
25852016738884976640000
>>> float(math.factorial(23))
2.585201673888498e+22
The right-hand-side operator is a much smaller floating point number (only 7 digits), it is that difference in exponents that causes the modulus operator error out.
Use ** to stick to integers:
for x in range(20, 50):
print(x, math.factorial(x), 2 ** x, math.factorial(x) % (2 ** x))
Integer operations are only limited to how much memory is available, and for x = 23 the correct value is calculated, continuing to work correctly all the way to x = 49:
>>> x = 23
>>> print(x, math.factorial(x), 2 ** x, math.factorial(x) % (2 ** x))
23 25852016738884976640000 8388608 6815744
>>> x = 49
>>> print(x, math.factorial(x), 2 ** x, math.factorial(x) % (2 ** x))
49 608281864034267560872252163321295376887552831379210240000000000 562949953421312 492581209243648
Note that for even for smaller floating point modulus calculations, you really should be using the math.fmod() function, for reasons explained in the documentation. It too fails for this case however, again because you are reaching beyond the limits of floating point math:
>>> print(x, math.factorial(x), math.pow(2, x), math.fmod(math.factorial(x), math.pow(2, x)))
23 25852016738884976640000 8388608.0 0.0
Yes, You are correct for large numbers modulus gives wrong numbers especially with factorial numbers.
for example :
import math
def comb(n,r):
res= math.factorial(n)/(math.factorial(n-r)*math.factorial(r))
return(float(res))
sum1=0
num=888
for r in range(0,num+1):
sum1 +=comb(num,r)
print(sum1 % 1000000)
gives wrong answer 252480 but the correct answer is 789056 .

Python arithmetic operation returns 0

read = True
while read:
my_input = int(raw_input())
print my_input
result = (1/6) * my_input * (my_input + 1) * (my_input +2)
if result == 0:
print ''
read = False
break
else:
print result
I wrote this little code snippet to solve 1 + (1+2) + (1+2+3+)... without looping over anything but the result is always 0 for some reason. I am using PyDev on Eclipse but I do not think that's even remotely the issue
Thank you
Multiplying by zero always results in zero.
>>> a = (1/6)
>>> print a
0
This is happening because Python is casting the resulting operation to integer.
In order to get a float result you can specify the values in decimal notation.
>>> a = 1.0/6.0
>>> print a
0.166666666667
Integer division.
When you divide (1/6) it comes out to 0 because of integer division.
When two ints are divided, they come out to the normal answer, minus anything after the decimal point.
For example, 1/4 would usually equal 0.25.
However, everything after the decimal point is dropped, so it comes out to 0.

Rounding Decimal to configurable decimal places in python

Is there a better (faster, more efficient, or "just more pythonic") way than my way in rounding Decimals in Python? I came up with the following:
sign, digits, exponent = the_number.as_tuple()
Decimal((sign, digits[:exponent+len(digits)+decimal_places],-decimal_places))
edit:
I ended up using yet another solution that is faster[1] and also "fills" the decimal to the wanted precision:
decimal.Decimal('%.*f' % (decimal_places, number))
[1] It is faster up to ~200 decimal places. In my case I get a random float-value I want to "cast" to decimal, so the original precision is already limited and << 200.
What about round (builtin):
>>> the_number = decimal.Decimal(1.23456789)
>>> round(the_number, 2)
Decimal('1.23')
>>> d=decimal.Decimal("31.100")
>>> d
Decimal('31.100')
>>> round(d, 10)
Decimal('31.1000000000')
>>> round(d, 20)
Decimal('31.10000000000000000000')
>>> round(d, 24)
Decimal('31.100000000000000000000000')
>>> round(d, 26)
Decimal('31.10000000000000000000000000')
>>> round(d, 1)
Decimal('31.1')
>>> round(d, 0)
Decimal('31')
May try:
with decimal.localcontext() as ctx:
ctx.prec = aWantedPRECISION # temporarily adapt precision to aWantedPRECISION
result = +the_number # set
if this is Pythonic-enough