Automatic reduction and cancellation of units with SymPy - python

I am having issues with getting units to cleanly simplify. The program I am writing is involved with the calculation of spring properties, requiring 10/11 user controlled variables and about a dozen equations, all of which deal with mixed sets of units. The one missing variable can be any of the non-material properties (1/5), so I am trying to use symbolic equations that can solve for whatever my one missing variable is. I tried setting the variables with pint, which could reduce properly and did not have this problem, but they could not Sympify properly, so I had to switch back to SymPy's unit system.
Here is some code that demonstrates the problem:
from sympy.physics.units import *
from sympy import pi, sqrt, N, Eq, symbols, solve
lbf=Quantity('lbf', abbrev='lbf')
lbf.set_global_relative_scale_factor((convert_to(pound*acceleration_due_to_gravity,newton))/newton, newton)
F, Y, L, A, m, ns, xi, d = symbols('F Y L A m ns xi d')
ssy, alpha, beta, C = symbols('ssy alpha beta C')
F= 20*lbf
Y= 2*inch
L= 3.25*inch
d= .08*inch
m= .145
A= 201.00
A*=kilo*psi*inch**m
ns= 1.20
xi= .15
eqSsy=Eq(ssy,.45*A/(d**m))
ssy=solve(eqSsy)[0]
eqAlpha=Eq(alpha,eqSsy.rhs/ns)
alpha=solve(eqAlpha)[0]
eqBeta=Eq(beta,(8*(1+xi)*F)/(N(pi)*(d**2)))
beta=solve(eqBeta)[0]
eqC=Eq(C,((2*eqAlpha.rhs-eqBeta.rhs)/(4*eqBeta.rhs))+sqrt((((2*eqAlpha.rhs-eqBeta.rhs)/(4*eqBeta.rhs))**2)-(3*eqAlpha.rhs)/(4*eqBeta.rhs)))
C=solve(eqC)[0]
print(ssy, '\n', alpha, '\n', beta, '\n', C)
This issue is not related to the lbf unit I had to create, it still happened when I had it using the raw units before I cleaned it up. This leads to C coming out as 1.62e-28*(3.66645442100299e+28*inch**2*psi - 1.54320987654321e+27*lbf + 3.666454421003e+28*sqrt(-0.252539870841386*inch**2*lbf*psi + (inch**2*psi - 0.0420899784735643*lbf)**2))/lbf instead of 10.5334875999498, because none of the units are cancelled through the calculation process.
The "fix" to this problem that I want to avoid is changing line 27, the creation of eqBeta, I have to hard convert the output units to be in psi to prevent the units from coming out as lbf/inch**2 instead of the pressure unit.
eqBeta=Eq(beta,convert_to((8*(1+xi)*F)/(N(pi)*(d**2)),psi))
Is there any way I can make beta automatically reduce to the appropriate pressure unit? The input values are given through a PyQt5 program, not like in this demo, and they can be given either imperial or metric units, so I don't want to be manually forcing a conversion into psi (or forcibly converting C into being unitless).
I would also appreciate if someone knew of a cleaner or better way to do these calculations, as I have just been bashing my head against SymPy because I haven't found another solution. These equations and variable names are taken from a machine design textbook, and I don't want to have to manually create a step-by-step solving process for each possible missing variable.

You might try listing the base units as the quantities to be reduced to:
>>> convert_to(C, [kg, meter, second]).n(2)
11.0
Or if you don't know which ones to use,
>>> from sympy.physics.units import UnitSystem
>>> sibase = UnitSystem.get_unit_system("SI")._base_units
>>> convert_to(C, sibase).n(2)
11.0
cf this issue

Related

Is there a convenient way to add complex numbers in polar form in sympy (python)?

I had some trouble adding complex numbers in polar form in sympy.
The following code
from sympy import I, exp, pi, re, im
a = exp(2*pi/3*I)
b = exp(-2*pi/3*I)
c = a+b
print(c)
print(c.simplify())
print(c.as_real_imag())
print(re(c)+im(c)*I)
print(int(c))
print(complex(c))
gives
exp(-2*I*pi/3) + exp(2*I*pi/3)
-(-1)**(1/3) + (-1)**(2/3)
(-1, 0)
-1
-1
(-1+6.776263578034403e-21j)
What I want, is to get the simplest answer to a+b, which is -1. I can obtain this, by manually rebuilding c=a+b with re(c)+im(c)*I. Why is this necessary? And is there a better way to do this?
Simply printing c retains the polar forms, obfuscating the answer, c.simplify() leaves the polar form, but is not really helpful, and c.as_real_imag() returns a tuple. int(c) does the job, but requires the knowledge, that c is real (otherwise it throws an error) and integer (otherwise, this is not the answer I want). complex(c) kind of works, but I don't want to leave symbolic calculation. Note, that float(c) does not work, since complex(c) has a non-zero imaginary part.
https://stackoverflow.com/users/9450991/oscar-benjamin has given you the solution. If you are in polar coordinates, your expression may have exponential functions. If you don't want these you have to rewrite into trigonometric functions where special values are known for many values. For example, consider a's 2*pi/3 angle:
>>> cos(2*pi/3)
-1/2
>>> sin(2*pi/3)
sqrt(3)/2
When you rewrite a in terms of cos (or sin) it becomes the sum of those two values (with I on the sin value):
>>> a.rewrite(cos)
-1/2 + sqrt(3)*I/2
When you rewrite a more complex expression, you will get the whole expression rewritten in that way and any terms that cancel/combine will do so (or might need some simplification):
>>> c.rewrite(cos)
-1

Why is my sympy Simult eqn calc not working

I have a simultaneous equation calculator that takes in a lst which contains the strings of the two equations and is supposed to output the solutions to X and Y. Here is the code for it.
x, y = symbols('x,y')
transformations=(standard_transformations + (implicit_multiplication_application,))
eqs_sympy = [Eq(parse_expr(e.split('=')[0], transformations=transformations),
parse_expr(e.split('=')[1], transformations=transformations))
for e in final_lst]
sol = solve(eqs_sympy)
An example of final_lst : ["5x^2 + 1y^5 = 12", "5x^3 + 18y^2 = 42"] (Replace ^ with **)
However, sol just outputs a blank list, why is this so?
These highly nonlinear equations are too high of order for SymPy to give an explicit solution. You can get a numerical solution, however, if you have a good initial guess for the solution. You can get a reasonable guess by graphing the equations or by solving the related equations that don't have the highest powered terms. Use nsolve (e.g. here). Here is one of the 3 roots:
>>> from sympy import nsolve
>>> tuple(nsolve(eqs,(x,y),(-1,1)))
(-0.731937744431011, 1.56277202986898)

Unexpected result for solving ordinary linear differential equation of second order with SymPy

I am trying to solve this ordinary linear differential equation of second order with SymPy and get an unexpected result.
import sympy as sym
k, t = sym.symbols('k, t')
s = sym.Function('s')
diff_eq = sym.Eq(s(t).diff(t, 2) + s(t) * k**2, 0) # everything fine here, when I print this I get what I expected.
solution_diff_eq = sym.dsolve(diff_eq, s(t))
print(solution_diff_eq)
Which prints
Eq(s(t), C1*exp(-I*k*t) + C2*exp(I*k*t))
However, the solution I expected is
Any ideas what I have done wrong?
The result prints as
Eq(s(t), C1*exp(-I*k*t) + C2*exp(I*k*t))
which is correct, as I is the imaginary unit. You might prefer the real form, but sympy was not notified of that and produced the most simple form as sum of exponential terms, especially as it is not clear if k is actually real.
If you make it explicit that k is a positive real number via
k = sym.Symbol('k', real=True, positive=True)
the solution is actually in real form, as you were expecting
Eq(s(t), C1*sin(k*t) + C2*cos(k*t))

Python -- Optimize system of inequalities

I am working on a program in Python in which a small part involves optimizing a system of equations / inequalities. Ideally, I would have wanted to do as can be done in Modelica, write out the equations and let the solver take care of it.
The operation of solvers and linear programming is a bit out of my comfort zone, but I decided to try anyway. The problem is that the general design of the program is object-oriented, and there are many different possibilities of combinations to form up the equations, as well as some non-linearities, so I have not been able to translate this into a linear programming problem (but I might be wrong) .
After some research I found that the Z3 solver seemed to do what I wanted. I came up with this (this looks like a typical case of what I would like to optimize):
from z3 import *
a = Real('a')
b = Real('b')
c = Real('c')
d = Real('d')
e = Real('e')
g = Real('g')
f = Real('f')
cost = Real('cost')
opt = Optimize()
opt.add(a + b - 350 == 0)
opt.add(a - g == 0)
opt.add(c - 400 == 0)
opt.add(b - d * 0.45 == 0)
opt.add(c - f - e - d == 0)
opt.add(d <= 250)
opt.add(e <= 250)
opt.add(cost == If(f > 0, f * 50, f * 0.4) + e * 40 + d * 20 +
If(g > 0, g * 50, g * 0.54))
h = opt.minimize(cost)
opt.check()
opt.lower(h)
opt.model()
Now this works, and gives me the result I want, despite it not being extremely fast (I need to solve such systems several thousands of times).
But I am not sure I am using the right tool for the job (Z3 is a "theorem prover").
The API is basically exactly what I need, but I would be curious if other packages allow a similar syntax. Or should I try to formulate the problem in a different way to allow a standard LP approach? (although I have no idea how)
Z3 is the most powerful solver I have found for such flexible systems of equations. Z3 is an excellent choice now that it is released under the MIT license.
There are a lot of different types of tools with overlapping use cases. You mentioned linear programming -- there are also theorem provers, SMT solvers, and many other types of tools. Despite marketing itself as a theorem prover, Z3 is often marketed as an SMT solver. At the moment, SMT solvers are leading the pack for the flexible and automated solution of coupled algebraic equations and inequalities over the booleans, reals, and integers, and in the world of SMT solvers, Z3 is king. Take a look at the results of the last SMT comp if you want evidence of this. That being said, if your equations are all linear, then you might also find better performance with CVC4. It doesn't hurt to shop around.
If your equations have a very controlled form (for example, minimize some function subject to some constraints) then you might be able to get better performance using a numerical library such as GSL or NAG. However, if you really need the flexibility, then I doubt you are going to find a better tool than Z3.
The best solution will probably be to use an ILP solver. Your problem can be formulated as an integer linear programming (ILP) instance. There are many ILP solvers, and some might perform better than Z3. For only 7 variables, any decent ILP solver should find a solution very rapidly.
The only tricky bit are the conditional expressions (If(...)). However, as #Erwin Kalvelagen suggests, the conditionals can be handled using variable splitting. For instance, introduce variables fplus and fminus, with the constraints f = fplus - fminus and fplus >= 0 and fminus >= 0. Now you can replace If(f > 0, f * 50, f * 0.4) with 50 * fplus - 0.4 * fminus. In this case, that will be equivalent.
Variable splitting doesn't always work. You have to think about whether it might introduce spurious solutions (where both fplus > 0 and fminus > 0). In this case, though, spurious solutions will never be optimal -- one can show that the optimal solution will never be optimal. Consequently, variable splitting works fine here.
If you have a situation where you do have conditional statements but variable splitting doesn't work, you can often use the techniques at https://cs.stackexchange.com/q/12102/755 to formulate the problem as an instance of ILP.

Why does this Python code give me the wrong answer?

I wrote a simple Python code to solve a certain Hydraulic formula (The Manning's equation):
import math
def mannings(units,A,P,S,n):
if units=='SI':
k=1.0
elif units=='US':
k=1.49
R=A/P
V=(k/n)*(math.pow(R,(2/3)))*(math.sqrt(S))
Q=A*V
return R,V,Q
In the code above, the velocity V is calculated from the k, n, R and S. The velocity is then used to calculate the discharge Q by multiplying with Area A. The user inputs the unit convention, the A, P, S and n. k is decided on the basis of unit convention.
When I run the function using mannings('US',1.0618,2.7916,0.02,0.015), I get (0.38035535176959456, 14.047854719572745, 14.916012141242343). The R value matches the R calculated in a spreadsheet, but the V and Q are way off. The actual V should be 7.374638178
and the Q should be 7.830634155.
It'd be great if someone can tell me what's going wrong here. This is a pretty straightforward formula and I was guessing it should work easily.
Your problem is that 2/3 is an integer division and therefore evaluates to 0. You want 2.0/3 to force a floating-point division. Or else include from __future__ import division at the top of your file to use the Python 3-style division in Python 2.x.
Assuming you don't use the __future__ solution, you will also want to write your R = A / P as e.g. R = float(A) / P because otherwise, if A and P are both integers, R will also be an integer.

Categories

Resources