Displaying solutions within a certain range when using sympy solve command - python

I can be considered pretty much new to python and coding in general so forgive me for my ignorance.
I'm trying to solve a system of trigonometric functions in python, and I'm doing so using the solve command from sympy. However, this method returns only a finite number of solutions, two in this particular case.
I've read through the documentation and it seems that to get an expression for all the solutions solveset is to be used instead. However, I do not want all the solutions to be displayed, but rather only a finite amount which is contained within a certain range.
Here's the example:
from sympy import *
x, y = symbols('x, y')
eq1 = Eq(y - sin(x), 0)
eq2 = Eq(y - cos(x), 0)
sol = solve([eq1, eq2], [x, y])
print(sol)
which only returns the first two solutions in the positive x range.
How could I do to, for example, display all the solutions within the x range [-2pi, 2pi]?
I'd want them in explicit form rather than written in term of some multiplier since I then need to convert them into numerical form.
Thank you in advance.

SymPy can really take you down rabbit holes. I agree with kampmani's solution, only if you can easily solve for y on your own. However, in more general cases and in higher dimensions, his solution does not hold.
For example, the following will be slightly more tricky:
eq1 = Eq(z - x*y, 0)
eq2 = Eq(z - cos(x) - sin(y), 0)
eq3 = Eq(z + x*y, 0)
So here I am; killing a fly with a bazooka. The problem is that one was able to simplify the set of equations into a single equation with a single variable. But what if you can't do that (for example, if it was a larger system)?
In this case, one needs to use nonlinsolve to solve the system of equations. But this does not provide numeric solutions directly and does not have a domain argument.
So the following code unpacks the solutions. It goes through each tuple in the set of solutions and finds the numeric solutions for each component in the tuple. Then in order to get the full list, you need a Cartesian Product of each of those components. Repeat this for each tuple in the set of solutions.
The following should work for almost any system of equations in any dimension greater than 1. It produces numeric solutions in the cube whose boundaries are the domains variable.
from sympy import *
import itertools # used for cartesian product
x, y, z = symbols('x y z', real=True)
domains = [Interval(-10, 10), Interval(-10, 10), Interval(-10, 10)] # The domain for each variable
eq1 = z - x*y
eq2 = z - cos(x) - sin(y)
eq3 = z + x*y
solutions = nonlinsolve([eq1, eq2, eq3], [x, y, z]) # the recommended function for this situation
print("---------Solution set----------")
print(solutions) # make sure the solution set is reasonable. If not, assertion error will occur
_n = Symbol("n", integer=True) # the solution set often seems to contain these symbols
numeric_solutions = []
assert isinstance(solutions, Set) # everything that I had tried resulted in a FiniteSet output
for solution in solutions.args: # loop through the different kinds of solutions
assert isinstance(solution, Tuple) # each solution should be a Tuple if in 2D or higher
list_of_numeric_values = [] # the list of lists of a single numerical value
for i, element in enumerate(solution):
if isinstance(element, Set):
numeric_values = list(element.intersect(domains[i]))
else: # assume it is an Expr
assert isinstance(element, Expr)
if _n.name in [s.name for s in element.free_symbols]: # if n is in the expression
# change our own _n to the solutions _n since they have different hidden
# properties and they cannot be substituted without having the same _n
_n = [s for s in element.free_symbols if s.name == _n.name][0]
numeric_values = [element.subs(_n, n)
for n in range(-10, 10) # just choose a bunch of sample values
if element.subs(_n, n) in domains[i]]
elif len(element.free_symbols) == 0: # we just have a single, numeric number
numeric_values = [element] if element in domains[i] else []
else: # otherwise we just have an Expr that depends on x or y
# we assume this numerical value is in the domain
numeric_values = [element]
# note that we may have duplicates, so we remove them with `set()`
list_of_numeric_values.append(set(numeric_values))
# find the resulting cartesian product of all our numeric_values
numeric_solutions += itertools.product(*list_of_numeric_values)
# remove duplicates again to be safe with `set()` but then retain ordering with `list()`
numeric_solutions = list(set(numeric_solutions))
print("--------`Expr` values----------")
for i in numeric_solutions:
print(list(i)) # turn it into a `list` since the output below is also a `list`.
print("--------`float` values---------")
for i in numeric_solutions:
print([N(j) for j in i]) # could have been converted into a `tuple` instead
In particular, it produces the following output for the new problem:
---------Solution set----------
FiniteSet((0, ImageSet(Lambda(_n, 2*_n*pi + 3*pi/2), Integers), 0))
--------`Expr` values----------
[0, -5*pi/2, 0]
[0, -pi/2, 0]
[0, 3*pi/2, 0]
--------`float` values---------
[0, -7.85398163397448, 0]
[0, -1.57079632679490, 0]
[0, 4.71238898038469, 0]
It was a lot of effort and probably not worth it but oh well.

By using solveset you can restrict the solutions with domain argument. For evaluating numerical results use .evalf() or another similar method.
from sympy import Interval, symbols, solveset, sin, cos, pi
x = symbols('x')
sol = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
print(sol)
print(sol.evalf())
Output
FiniteSet(-7*pi/4, -3*pi/4, pi/4, 5*pi/4)
FiniteSet(-5.49778714378214, -2.35619449019234, 0.785398163397448, 3.92699081698724)
I hope this helps!

Thanks to the brilliant suggestion from #kampmani it is possible to achieve the desired result.
For start, the FiniteSet elements are not indexed and cannot be used, so the FiniteSet has to be converted into a list:
solx_array = []
#
#
#
solx = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
solx_array=list(solx)
The next step is to find the y coordinate of the intersection point given its x coordinate. The final code should look somewhat similar to this:
from sympy import Interval, symbols, solveset, sin, cos, pi
sol_array = []
x = symbols('x')
solx = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
solx_array=list(solx)
for i in range(len(solx_array)):
soly = cos(solx_array[i])
sol_array.append(str(solx_array[i] + soly))
for i in range(len(sol_array)):
print(sol_array[i])
Still don't know how to convert the results into numerical form though, any idea is very appreciated.

Related

Minimize function with scipy

I'm trying to do one task, but I just can't figure it out.
This is my function:
1/(x**1/n) + 1/(y**1/n) + 1/(z**1/n) - 1
I want that sum to be as close to 1 as possible.
And these are my input variables (x,y,z):
test = np.array([1.42, 5.29, 7.75])
So n is the only decision variable.
To summarize:
I have a situation like this right now:
1/(1.42**1/1) + 1/(5.29**1/1) + 1/(7.75**1/1) = 1.02229
And I want to get the following:
1/(1.42^(1/0.972782944446024)) + 1/(5.29^(1/0.972782944446024)) + 1/(7.75^(1/0.972782944446024)) = 0.999625
So far I have roughly nothing, and any help is welcome.
import numpy as np
from scipy.optimize import minimize
def objectiv(xyz):
x = xyz[0]
y = xyz[1]
z = xyz[2]
n = 1
return 1/(x**(1/n)) + 1/(y**(1/n)) + 1/(z**(1/n))
test = np.array([1.42, 5.29, 7.75])
print(objectiv(test))
OUTPUT: 1.0222935270013889
How to properly define a constraint?
def conconstraint(xyz):
x = xyz[0]
y = xyz[1]
z = xyz[2]
n = 1
return 1/(x**(1/n)) + 1/(y**(1/n)) + 1/(z**(1/n)) - 1
And it is not at all clear to me how and what to do with n?
EDIT
I managed to do the following:
def objective(n,*args):
x = odds[0]
y = odds[1]
z = odds[2]
return abs((1/(x**(1/n)) + 1/(y**(1/n)) + 1/(z**(1/n))) - 1)
odds = [1.42,5.29,7.75]
solve = minimize(objective,1.0,args=(odds))
And my output:
fun: -0.9999999931706812
x: array([0.01864994])
And really when put in the formula:
(1/(1.42^(1/0.01864994)) + 1/(5.29^(1/0.01864994)) + 1/(7.75^(1/0.01864994))) -1 = -0.999999993171
Unfortunately I need a positive 1 and I have no idea what to change.
We want to find n that gets our result for a fixed x, y, and z as close as possible to 1. minimize tries to get the lowest possible value for something, without negative bound; -3 is better than -2, and so on.
So what we actually want is called least-squares optimization. Similar idea, though. This documentation is a bit hard to understand, so I'll try to clarify:
All these optimization functions have a common design where you pass in a callable that takes at least one parameter, the one you want to optimize for (in your case, n). Then you can have it take more parameters, whose values will be fixed according to what you pass in.
In your case, you want to be able to solve the optimization problem for different values of x, y and z. So you make your callback accept n, x, y, and z, and pass the x, y, and z values to use when you call scipy.optimize.least_squares. You pass these using the args keyword argument (notice that it is not *args). We can also supply an initial guess of 1 for the n value, which the algorithm will refine.
The rest is customization that is not relevant for our purposes.
So, first let us make the callback:
def objective(n, x, y, z):
return 1/(x**(1/n)) + 1/(y**(1/n)) + 1/(z**(1/n))
Now our call looks like:
best_n = least_squares(objective, 1.0, args=np.array([1.42, 5.29, 7.75]))
(You can call minimize the same way, and it will instead look for an n value to make the objective function return as low a value as possible. If I am thinking clearly: the guess for n should trend towards zero, making the denominators increase without bound, making the sum of the reciprocals go towards zero; negative values are not possible. However, it will stop when it gets close to zero, according to the default values for ftol, xtol and gtol. To understand this part properly is beyond the scope of this answer; please try on math.stackexchange.com.)

Factoring a SymPy expression into groups that involve one symbol only

Suppose I have an expression in sympy that only consists of a single term. This expression either has subexpressions that depend on a symbol x, on a symbol y, or on neither x nor y. I would like sympy to return three expressions, the first depends only on x, the second only on y, and the third on neither, such that the product of the three expressions is the original expression. E.g.
expr = x^2*cos(x)*2/sin(y)/y
should return x^2 * cos(x) and 1/sin(y)/y and 2. Is this possible?
In general, this is impossible: for example, sqrt(x+y) cannot be separated into a function of x times a function of y. But when factorization is possible, the method as_independent can help in finding it:
expr = x**2*cos(x)*2/sin(y)/y
temp, with_x = expr.as_independent(x, as_Mul=True)
const, with_y = temp.as_independent(y, as_Mul=True)
print((with_x, with_y, const))
prints (x**2*cos(x), 1/(y*sin(y)), 2)
With the hint as_Mul, the method is trying to separate the expression into a factor that does not depend on the given variable, and the rest. So, the first step isolates a term without x (called temp), from which the second step isolates a term without y (constant).
Such a thing can also be done for sums instead of products, with the hint as_Add=True.
Assuming you have a single term expr made up by multiplying terms in x, y and other symbols or constants you can do something like below:
from sympy import sin, cos, Mul, init_printing
from sympy.abc import x,y
init_printing()
expr = x**2*cos(x)*2/sin(y)/y
def splitXYC(expr):
xterm = Mul(*[t for t in expr.args if t.has(x)])
yterm = Mul(*[t for t in expr.args if t.has(y)])
others = Mul(*[t for t in expr.args if not (t.has(x) or t.has(y)) ])
return xterm, yterm, others
X,Y,C = splitXYC(expr)
print(X) # Prints x**2*cos(x)
print(Y) # Prints 1/(y*sin(y))
print(C) # Prints 2
Is this as you wanted?

solve particular symbolic equation in python

I want to solve the following problem with python, if possible with sympy.
Let n be a fixed positive number. Let p=(p_1,...p_n) be a fixed known vector of positive integers. Let d be a fixed, known positive integer. Let q=(q_1,...,q_n) be a vector of unknown nonnegative integers.
How can I get all the solutions of p.q=d?
Where . means dot product.
Actually I can solve this for each individual n. But I want to create a function
def F(n,p,d):
...
return result
Such that result is a, e.g., list of all solutions. Note that from the restrictions made above, there is a finite number of solutions for each triplet of data (n,p,d).
I can't figure a way to do this, so any suggestion will be appreciated.
Added.
Example: suppose n=3 (the case n=2 is trivial), p=(2,1,3), d=3. Then I would do something like
res=[]
for i in range (d):
for j in range (d):
k=d-p[0]*i-p[2]*j
if k>=0:
res.append([i,k,j])
Then res=[[0, 3, 0], [0, 0, 1], [1, 1, 0]] which is correct.
As you can imagine, the bigger n is, the more for loops I need if I want to follow the same idea. So I do not think this is a good way to do it for arbitrary n, say n=57 or whatever big enough...
Following the algorithm you provided:
from itertools import product
dot = lambda X, Y: sum(x * y for x, y in zip(X, Y))
p = [1, 2, 3, ...] # Whatever fixed value you have for `p`
d = 100 # Fixed d
results = []
for q in product(range(0, d+1), repeat=len(p)):
if dot(p, q) == d:
results.append(q)
However this is slightly inefficient since it is possible to determine prior to computing the entire dot product, whether k will be positive. So let's define the dot product like this:
def dot(X, Y, d):
total = 0
for x, y in zip(X, Y):
total += x * y
if total > d:
return -1
return total
Now, as soon as the total exceeds d, the calculation exits. You can also express this as a list comprehension:
results = [q for q in product(range(0, d+1), repeat=len(p)) if dot(p, q, d) == d]

Defining a range of symbols whose bounds are OTHER symbols

I'm trying to express a summation over an arbitrary (but finite) number of symbols, which I wish to be given by another symbol. For instance, is it possible to say:
N,ci,cj = symbols('N,c_i,c_j')
# pseudocode
k = sum(ci+cj,(ci,0,N),(cj,0,N))
or, more literally,
k = sum(ci+cj, (ci != cj))
My instinct is that it isn't, but I do wish sympy would implement support for it!
UPDATE
It appears sympy offers provisions for indexed variables. Namely:
x = IndexedBase('x')
i,j = symbols('i j',cls=Idx)
however, the you can an error when attempting:
y = Sum(x[i], (i, 0, 2))
Which is:
ValueError: Invalid limits given: ((i, 1, 5),)
You can use a Function, like x = symbols('x', cls=Function) and x(i). Indexed should also work, but it looks like Sum has a bug that disallows Idx. It works if you just use i = symbols('i'), though.

Python: reduce on tuple of tuples

I am trying to compute in Python the length of the path from a point A to a point B going through a list of intermediary points. I know how to do it but I do want to use the reduce Built-in function.
Why I tried so far, please note that it is completely wrong, is this:
reduce(lambda x,y: math.sqrt((y[1]-y[0])**2+(x[1]-x[0])**2) , ((1,2),(3,4),(1,8)))
Any idea?
Thanks.
You should map before you reduce.
points = [(1, 2), (3, 4), (1, 8)]
distances = (math.hypot(b[0]-a[0], b[1]-a[1])
for a, b in zip(points, points[1:]))
total_distance = sum(distances)
or, if you must use reduce(), although sum() is better for this purpose:
import operator
total_distance = reduce(operator.add, distances)
If you have a lot of points, you might find NumPy helpful in doing this all at once, quickly:
import numpy
total_distance = numpy.hypot(*numpy.diff(numpy.array(points), axis=0)).sum()
Edit: use math.hypot() and add NumPy method.
It isn't pretty but it can be done :-)
>>> tot = ((1,2),(3,4),(1,8))
>>> reduce(lambda d,((x0,y0),(x1,y1)): d + ((x1-x0)**2+(y1-y0)**2)**0.5, zip(tot[1:], tot[0:]), 0.0)
7.3005630797457695
reduce() is simply the wrong tool for this purpose. It is possible to do it with reduce(), but it is a bit weird:
def distance((x, d), y):
return y, d + math.hypot(y[0] - x[0], y[1] - x[1])
print reduce(distance, [(3,4),(1,8)], ((1, 2), 0.0))[1]
prints
7.30056307975
The last parameter passed to the reduce() call is the starting point and the initial value for the distance.
reduce does not work that way, you start with an initial value a, which you specify or is taken as first element from your iterable. afterwards, you pass a,next_element to the function (lambda) provided and store the result in a, repeat until all elements are iterated.
You can do what you want with sum and map by first calculating all distances from one point to the next and then summing them:
path = [(1,2),(3,4),(1,8)]
sum(map(lambda x,y: math.sqrt((x[0]-y[0])**2+(x[1]-y[1])**2), path[:-1],path[1:]))
edit: or with the hypot function (thx #ralu):
sum(map(lambda x,y: math.hypot(x[0]-y[0],x[1]-y[1]), path[:-1],path[1:]))
This is just not the sort of code you want to write.
Reduce won't be a good solution.
I suggest a iterative one.
It will be the most readable, pythonic and maintainable solution.
import math
path = [(1,2),(3,4),(1,8)]
def calc_dist(waypoints):
dist = 0.0
for i in range(len(waypoints) - 1):
a = waypoints[i]
b = waypoints[i+1]
dist += math.hypot(a[0]-b[0], b[1]-a[1])
return dist
print calc_dist( path )
Here is a redux meta-iterator that can be combined with the built-in reduce to get the result you want. This implementation avoids all buffering of the input sequence.
def redux(f):
def execute(iterable):
iterable = iter(iterable)
try:
state = iterable.next()
except StopIteration:
raise ValueError, 'empty sequences not supported'
while True:
newstate = iterable.next()
yield f(state, newstate)
state = newstate
return execute
f = redux(lambda x, y: math.sqrt((y[0] - x[0])**2 + (y[1] - x[1])**2))
print reduce(operator.add, f(((1,2),(3,4),(1,8))))
The above prints 7.30056307975.
The redux function can be generalized to support more than two arguments at a time in a sliding window, by using inspect.getargspec to count the number of arguments required by its function argument.
I'm aware that what I'm about to suggest is not ideal, but I think this is as close as I can get for my contribution. This is a fun problem to solve, even if it isn't the most traditional application of reduce.
The key issue seems to be keeping track of the distance from point to point without overwriting the points themselves- adding another 'dimension' to each point gives you a field with which you can track the running distance.
iterable = ((1,2,0), (3,4,0), (1,8,0))
# originally ((1,2), (3,4), (1,8))
from math import sqrt
def func(tup1, tup2):
'''function to pass to reduce'''
# extract coordinates
x0 = tup1[0]
x1 = tup2[0]
y0 = tup1[1]
y1 = tup2[1]
dist = tup1[2] # retrieve running total for distance
dx = x1 - x0 # find change in x
dy = y1 - y0 # find change in y
# add new distance to running total
dist += sqrt(dx**2 + dy**2)
# return 2nd point with the updated distance
return tup2[:-1] + (dist,) # e.g. (3, 4, 2.828)
Now reduce:
reduce(func, iterable)[-1]
# returns 7.3005630797457695
This way, the intermediate tuple of tuples (i.e., after one 'reduction') becomes:
((3, 4, 2.8284271247461903), (1,8,0))
Just for fun, here is an alternate solution with a slightly different approach than the reduce(sum, map(hypot, zip(...))) approach.
tot = ((1,2),(3,4),(1,8))
reduce(lambda (d,(x,y)),b: (d+math.hypot(x-b[0],y-b[1]), b), tot, (0, tot[0]))[0]
Note that the reduce actually returns the tuple (distance, last point), hence the [0] at the end. I think this would be more efficient than zip solutions but haven't actually checked.

Categories