Given an N x N symmetric matrix C and an N x N diagonal matrix I, find the solutions of the equation det(λI-C)=0. In other words, the (generalized) eigenvalues of C are to be found.
I know few ways how to solve this in MATLAB using build-in functions:
1st way:
function lambdas=eigenValues(C,I)
syms x;
lambdas=sort(roots(double(fliplr(coeffs(det(C-I*x))))));
2nd way:
[V,D]=eig(C,I);
However, I need to use Python. There are similar function in NumPy and SymPy, but, according to docs (numpy, sympy), they take only one matrix C, as the input. Though, the result's different from the result produced by Matlab. Also, symbolic solutions produced by SymPy aren't helpful. Maybe I am doing something wrong? How to find solution?
Example
MATLAB:
%INPUT
I =
2 0 0
0 6 0
0 0 5
C =
4 7 0
7 8 -4
0 -4 1
[v,d]=eig(C,I)
%RESULT
v =
-0.3558 -0.3109 -0.5261
0.2778 0.1344 -0.2673
0.2383 -0.3737 0.0598
d =
-0.7327 0 0
0 0.4876 0
0 0 3.7784
Python 3.5:
%INPUT
I=np.matrix([[2,0,0],
[0,6,0],
[0,0,5]])
C=np.matrix([[4,7,0],[7,8,-4],[0,-4,1]])
np.linalg.eigh(C)
%RESULT
(array([-3., 1.91723747, 14.08276253]),
matrix(
[[-0.57735027, 0.60061066, -0.55311256],
[ 0.57735027, -0.1787042 , -0.79670037],
[ 0.57735027, 0.77931486, 0.24358781]]))
At least if I has positive diagonal entries you can simply solve a transformed system:
# example problem
>>> A = np.random.random((3, 3))
>>> A = A.T # A
>>> I = np.identity(3) * np.random.random((3,))
# transform
>>> J = np.sqrt(np.einsum('ii->i', I))
>>> B = A / np.outer(J, J)
# solve
>>> eval_, evec = np.linalg.eigh(B)
# back transform result
>>> evec /= J[:, None]
# check
>>> A # evec
array([[ -1.43653725e-02, 4.14643550e-01, -2.42340866e+00],
[ -1.75615960e-03, -4.17347693e-01, -8.19546081e-01],
[ 1.90178603e-02, 1.34837899e-01, -1.69999003e+00]])
>>> eval_ * (I # evec)
array([[ -1.43653725e-02, 4.14643550e-01, -2.42340866e+00],
[ -1.75615960e-03, -4.17347693e-01, -8.19546081e-01],
[ 1.90178603e-02, 1.34837899e-01, -1.69999003e+00]])
OP's example. IMPORTANT: must use np.arrays for I and C, np.matrix will not work.
>>> I=np.array([[2,0,0],[0,6,0],[0,0,5]])
>>> C=np.array([[4,7,0],[7,8,-4],[0,-4,1]])
>>>
>>> J = np.sqrt(np.einsum('ii->i', I))
>>> B = C / np.outer(J, J)
>>> eval_, evec = np.linalg.eigh(B)
>>> evec /= J[:, None]
>>>
>>> evec
array([[-0.35578356, -0.31094779, -0.52605088],
[ 0.27778714, 0.1343625 , -0.267297 ],
[ 0.23826117, -0.37371199, 0.05975754]])
>>> eval_
array([-0.73271478, 0.48762792, 3.7784202 ])
If I has positive and negative entries use eig instead of eigh and before taking the square root cast to complex dtype.
Differing from other answers, I assume that by the symbol I you mean the identity matrix, Ix=x.
What you want to solve, Cx=λIx, is the so-called standard eigenvalue problem,
and most eigenvalue solvers tackle the problem described in that format, hence the
Numpy function has the signature eig(C).
If your C matrix is a symmetric matrix and your problem is indeed a standard eigenvalue problem I'd recommend the use of numpy.linalg.eigh, that is optimized for this type of problems.
On the contrary if your problem is really a generalized eigenvalue problem, as, e.g., the frequency equation Kx=ω²Mx you could use scipy.linalg.eigh, that supports that type of problem statement for symmetric matrices.
eigvals, eigvecs = scipy.linalg.eigh(C, I)
With respect to the discrepancies in eigenvalues, the Numpy implementation gives no guarantees w/r to their ordering, so it could be just a different ordering, but if your problem is indeed a generalized problem (I not being the identity matrix...) the solution is of course different and you have to use the Scipy implementation of eigh.
If the discrepancies is within the eigenvectors, please remember that the eigenvectors are known within an arbitrary scale factor and, again, the ordering could be undefined (but, of course, their order is the same order in which you have the eigenvalues) — the situation is a little different for scipy.linalg.eigh because in this case the eigenvalues are sorted and the eigenvectors are normalized with respect to the second matrix argument (I in your example).
Ps: scipy.linalg.eigh behaviour (i.e., sorted eigenvalues and normalized eigenvectors) is so convenient for my use cases that I use to use it also to solve standard eigenvalue problems.
Using SymPy:
>>> from sympy import *
>>> t = Symbol('t')
>>> D = diag(2,6,5)
>>> S = Matrix([[ 4, 7, 0],
[ 7, 8,-4],
[ 0,-4, 1]])
>>> (t*D - S).det()
60*t**3 - 212*t**2 - 77*t + 81
Computing the exact roots:
>>> roots = solve(60*t**3 - 212*t**2 - 77*t + 81,t)
>>> roots
[53/45 + (-1/2 - sqrt(3)*I/2)*(312469/182250 + sqrt(797521629)*I/16200)**(1/3) + 14701/(8100*(-1/2 - sqrt(3)*I/2)*(312469/182250 + sqrt(797521629)*I/16200)**(1/3)), 53/45 + 14701/(8100*(-1/2 + sqrt(3)*I/2)*(312469/182250 + sqrt(797521629)*I/16200)**(1/3)) + (-1/2 + sqrt(3)*I/2)*(312469/182250 + sqrt(797521629)*I/16200)**(1/3), 53/45 + 14701/(8100*(312469/182250 + sqrt(797521629)*I/16200)**(1/3)) + (312469/182250 + sqrt(797521629)*I/16200)**(1/3)]
Computing floating-point approximations of the roots:
>>> for r in roots:
... r.evalf()
...
0.487627918145732 + 0.e-22*I
-0.73271478047926 - 0.e-22*I
3.77842019566686 - 0.e-21*I
Note that the roots are real.
Related
I have two NumPy array (two variables), which contains complex numbers. Since they have been defined in NumPy, so the complex notation is denoted by "j".
for i in range(0, 8):
cg1 = np.array(eigVal1[i])
det_eval = eval(det)
AA = det_eval[0:2,0:2]
bb = det_eval[0:2,2] * (-1)
roots = np.append(roots, solve(AA, bb))
a = np.append(a, roots[i])
b = np.append(b, roots[i+1])
Output:
a = array([-9.03839731e-04+0.00091541j, 3.02435614e-07-0.00043776j,
-9.03839731e-04-0.00091541j, 3.02435614e-07+0.00043776j,
9.03812649e-04+0.00092323j, 4.17553402e-07+0.00043764j,
9.03812649e-04-0.00092323j, 4.17553402e-07-0.00043764j])
b = array([ 3.02435614e-07-0.00043776j, -9.03839731e-04-0.00091541j,
3.02435614e-07+0.00043776j, 9.03812649e-04+0.00092323j,
4.17553402e-07+0.00043764j, 9.03812649e-04-0.00092323j,
4.17553402e-07-0.00043764j, -5.53769989e-05-0.00243369j])
I also have a long equation which some variables have been defined symbolic (y).
u_n = A0*y**(1322.5696672125 + 1317.38942049453*I) + A1*y**(1322.5696672125 - 1317.38942049453*I) + A2*y**(-1322.5696672125 + 1317.38942049453*I) + A3*y**(-1322.5696672125 - 1317.38942049453*I) + ..
My problem is when I want to substitute the two variables (a and b) into the equation, all complex numbers change to "I" and it makes the equation more complex, because I am not able to simplify the equation further.
Is there any solution to convert "I" to "j" in sympy.
for i in range(0, 8):
u_n = u_n.subs(A[i], (a[i] * C[i]))
The result is:
u_n = C0*y**(1322.5696672125 + 1317.38942049453*I)*(-0.000903839731101097 + 0.000915407724097998*I) + C1*y**(1322.5696672125 - 1317.38942049453*I)*(3.02435613673241e-7 - 0.000437760318205723*I) +..
As you see I can not simplify it further, even if I use simplify(u_n). However, in numpy for example,(2+3j)(5+6j) will be reduced to (-8+27j), but when a symbolic notation comes to my equation it won't be simplified further. y**(2+3j)(5+6j) -->> y**(2+3I)(5+6*I).
I would like to have y**(-8+27j) which y is symbolic.
I would appreciate it if someone help me with that.
From you last paragraph:
The python expression:
In [38]: (2+3j)*(5+6j)
Out[38]: (-8+27j)
sympy: (y is a sympy symbol):
In [39]: y**(2+3j)*(5+6j)
Out[39]:
With corrective () to group the multiply before the power:
In [40]: y**((2+3j)*(5+6j))
Out[40]:
Even in plain python the operator order matters:
In [44]: 1**(2+3j)*(5+6j)
Out[44]: (5+6j)
In [45]: 1**((2+3j)*(5+6j))
Out[45]: (1+0j)
I have the following system of equations and I'm trying to get the solution with sympy:
from sympy import *
x,y = symbols('x,y')
rea1 = (1.0*10**(-4)*(x+2)*(4*y+3*x+1)**3) - 1.3 * ((-2*y-x+1)*(-y-x+1)*(2*y+2*x+6)**2)
rea2 = (1.0*10**(-4)*(y+1)*(4*y+3*x+1)**4) - 2.99 * ((-2*y-x+1)**2*(-y-x+1)*(2*y+2*x+6)**2)
solu = solve([rea1,rea2],[x,y])
sol = nsolve([rea1,rea2],[x,y],[-0.1,1])
print(solu)
print(sol)
with results
[(-11.0, 8.0), (-3.0, 2.0), (-2.0, -1.0), (5.0, -4.0)]
and
Matrix([[1.32358494772278], [-0.324195048403443]])
However I know from Maxima the following solutions:
[[x=-11,y=8],
[x=-3,y=2],
[x=5,y=-4],
[x=-2,y=-1],
[x=1.3236,y=-0.3242],
[x=-2.0091,y=-0.98836],
[x=-3.8143,y=0.84582],
[x=3.004,y=-1.0016],
[x=-4.0297,y=0.9959],
[x=-8.4744,y=9.4724]]
How to get all the solutions with Python?
Okay, this is a second answer. I think I know why SymPy's main solvers don't seem to work for this case. I have written some better code to do this and posted it in the SymPy issue here:
https://github.com/sympy/sympy/issues/23637#issuecomment-1159205521
I'll also show it here:
from sympy import *
def solve_poly_numeric(polys, syms, exact=False, prec=None):
"""Solve a system of polynomials having rational coefficients."""
_, info = parallel_poly_from_expr(polys)
domain = info['domain']
if domain not in (ZZ, QQ):
raise ValueError("Poly should have rational coefficients")
# Compute a preliminary Groebner basis
gb = groebner(polys, syms)
# Handle inconsistent or infinite cases
if 1 in gb:
return []
elif not gb.is_zero_dimensional:
raise ValueError("Infinitely many solutions")
# Split the system by factorising the final polynomial
c, fms = factor_list(gb[-1])
gbs = []
for factor, m in fms:
gb_new = groebner(gb[:-1] + [factor], syms)
gbs.append(gb_new)
# Now solve each subsystem
solutions = []
for gbi in gbs:
solutions.extend(solve_separating(gbi))
# Make the solutions approximate (this is because otherwise you'll see
# complicated RootOf expressions).
if not exact:
solutions = [[s.evalf(prec) for s in sol] for sol in solutions]
return solutions
def solve_separating(gb):
syms = gb.gens
N = len(syms)
s = Dummy('s')
i = 0
while True:
eq_s = s - sum(j**i*syms[j] for j in range(N))
gb = groebner(list(gb) + [eq_s], syms + (s,))
if is_separated(gb):
return solve_rur(gb)
i += 1
def is_separated(gb):
"""Test if a Groebner basis is separated"""
for p in gb.polys[:-1]:
if sum(p.degree_list()[:-1]) != 1:
return False
return sum(gb.polys[-1].degree_list()[:-1]) == 0
def solve_rur(gb):
[sol] = linsolve(gb[:-1], gb.gens[:-1])
s = gb.gens[-1]
s_sols = set(gb.polys[-1].as_poly(s).all_roots())
return [sol.subs(s, s_sol) for s_sol in s_sols]
With that you get all the roots you were expecting:
In [100]: solve_poly_numeric([nsimplify(rea1), nsimplify(rea2)], [x, y])
Out[100]:
[[-2.0, -1.0], [5.0, -4.0], [-11.0, 8.0], [-3.0, 2.0], [-4.02970802196492, 0.995904370231472], [-8.
47443484879371, 9.47242621738025], [3.00401593628982, -1.00159247934001], [-3.81434166231536, 0.845
816419487192], [-2.00911635093861, -0.988359479355467], [1.32358494772277, -0.32419504840344]]
With a nonlinear system with multiple solutions, the trick is to make a guess near the solution. So nsolve((rea1,rea2),(x,y),(1.3,-0.3)) will give you a solution. But if you don't know that you have a solution there, how can you get it? One method is to use a "continuation parameter" whose value you can change from 0 to 1 to "shut off" the highly nonlinear part of the equation(s). In your case I replace 4*y+3*x+1 with z*(4*y+3*x+1) and solve for z=0
>>> from sympy import Tuple,nsimplify
>>> eqs = Tuple(*[nsimplify(i) for i in (rea1,rea2)]) # get rid of floats
>>> eqs = eqs.subs(4*y+3*x+1, z*(4*y+3*x+1)) # introduce continuation param
>>> z0 = solve(eqs.subs(z,0)); z0
[{x:1−2y}, {x:1−y}, {x:−y−3}]
With these three hints as to how x is related to y I then just fully turn on the non-linear term and hope that I am close enough as I calculate values of x for y values of interest -- and for that you have to have some idea of what you are looking for. I will just assume we are looking in the range of [-11,10] and collect the answers as they are obtained (and ignore failed attempts):
>>> saw = set()
>>> for yg in range(-11,11):
... for xs in z0:
... guess = xs[x].subs(y,yg),yg
... try:
... ans = nsolve(eqs.subs(z,1),(x,y),guess)
... except:continue
... saw.add((ans[0,0],ans[1,0]))
And let's see what we got:
>>> from sympy import Point
>>> for i in sorted({Point(i).n(3) for i in saw},key=lambda x:x[0]):print(i)
Point2D(-11.0, 8.0)
Point2D(-8.47, 9.47)
Point2D(-4.03, 0.996)
Point2D(-3.81, 0.846)
Point2D(-3.0, 2.0)
Point2D(-2.0, -1.0)
Point2D(1.32, -0.324)
Point2D(3.0, -1.0)
Point2D(5.0, -4.0)
Due to slight differences in the original solutions, 2 of the solutions appear to be duplicates; they were removed by evaluating to 3 digits of precision. Whether there is a different solution near (-2,-1) is something you would have to investigate. But using a guess near the solution already found would be a way to confirm that.
I'm not sure why solve fails here. That should be considered a bug and reported to the SymPy project on GitHub rather than here on SO.
You can get the solutions like this:
In [128]: eq1 = nsimplify(rea1)
In [129]: eq2 = nsimplify(rea2)
In [130]: r = resultant(eq1, eq2, x)
In [131]: rs = list(set(real_roots(r)))
In [132]: [root.n(5) for root in rs]
Out[132]: [-0.98836, 2.0, 0.9959, 8.0, -1.0016, 0.84582, -0.32419, 9.4724, -4.0, -1.0]
That's giving the values for y so it just remains to determine the corresponding values for x e.g.:
In [133]: xvals = nroots(eq1.subs(y, rs[0].n()))
In [134]: xvals
Out[134]: [-2.0148693245473, -2.00911635093857, 1.98822706053502, 2.97754996447177]
Only one of these values is correct (i.e. also satisfies eq2):
In [135]: [eq2.subs({y:rs[0], x:xval}).n(3) for xval in xvals]
Out[135]: [-0.00481, -2.55e-13, -0.0247, 0.00169]
Really solve should be able to handle all of this though so a bug should be raised.
Your case is a system of nonlinear equations. Sympy provides the solveset module for system of equations, depending on the system type.
You can try solveset.nonlinsolve()
https://docs.sympy.org/latest/modules/solvers/solveset.html#sympy.solvers.solveset.nonlinsolve
i am trying to solve a matrix that has 6x6 matrices as it's entries(elements)
i tried multiplying the inverse of gen to the solution matrix, but i don't trust the correctness of the answer am getting.
from sympy import Eq, solve_linear_system, Matrix,count_ops,Mul,horner
import sympy as sp
a, b, c, d, e,f = sp.symbols('a b c d e f')
ad = Matrix(([43.4,26.5,115,-40.5,52.4,0.921],
[3.78,62.9,127,-67.6,110,4.80],
[41.25,75.0,213,-88.9, 131, 5.88],
[-10.6,-68.4,-120,64.6,-132,-8.49],
[6.5,74.3,121,-72.8,179,29.7],
[1.2,30.7,49.7,-28.7,91,29.9]))
fb= Matrix(([1,0,0,0,0,0],
[0,1,0,0,0,0],
[0,0,1,0,0,0],
[0,0,0,1,0,0],
[0,0,0,0,1,0],
[0,0,0,0,0,1]))
ab = Matrix(([-0.0057],
[0.0006],
[-0.0037],
[0.0009],
[0.0025],
[0.0042]))
az = sp.symbols('az')
bz = sp.symbols('bz')
fz = sp.symbols('fz')
gen = Matrix(([az, fz, 0, 0, 0, 0,bz],
[fz,az,fz,0,0,0,bz],
[0,fz,az,fz,0,0,bz],
[0,0,fz,az,fz,0,bz],
[0,0,0,fz,az,fz,bz],
[0,0,0,0,fz,az,bz]))
answer = solve_linear_system(gen,a,b,c,d,e,f)
first_solution = answer[a]
df = count_ops(first_solution)
print(df,first_solution)
disolved = zip(first_solution.simplify().as_numer_denom(),(1,-1))
dft = Mul(*[horner(b)**e for b,e in disolved])
dff = count_ops(dft)
print(dff,dft)
_1st_solution = dft.subs({az:ad,fz:fb,bz:ab},simultaneous = True).doit()
print(_1st_solution)
when i ran my code it raised sympy.matrices.common.ShapeError
You have to be careful when using horner with expressions containing commutative symbols that are actually noncommutative (in your case because they represent matrices). Your dft expression is
(az**2*bz - bz*fz**2)/(az*(az*(az + fz) - 2*fz**2) - fz**3)
but should maybe be
(az**2 - fz**2)*(az*(az*(az + fz) - 2*fz**2) - fz**3)**(-1)*bz
You would have received a correct expression if you had created the symbols as noncommutative (as shown below).
But you can't use horner with non-commutative symbols, so I just rearranged the expression by hand; you will have to check to see that the ordering is right. As an alternative to doing the factoring by hand you might also try using factor_nc to help you -- but it won't handle horner like expression factoring:
>>> ax, bz, fz = symbols('az bz fz, commutative=False)
>>> (az**2*bz - fz**2*bz)
az**2*bz - fz**2*bz
>>> factor_nc(_)
(az**2 - fz**2)*bz
I am doing a project on encrypting data using RSA algo and for that, I have taken a .wav file as an input and reading it by using wavfile and I can apply the key (3, 25777) but when I am applying the decryption key (16971,25777) it is giving wrong output like this:
The output I'm getting:
[[ 0 -25777]
[ 0 -25777]
[ 0 -25777]
...
[-25777 -25777]
[-15837 -15837]
[ -8621 1]]
output i want:
[[ 0 -1]
[ 2 -1]
[ 2 -3]
...
[-9 -5]
[-2 -2]
[-4 1]]
This was happening only with the decryption part of the array so I decided to convert the 2d array to a 2d list. After that, it is giving me the desired output but it is taking a lot of time to apply the keys to all the elements of the list(16min, in case of array it was 2sec). I don't understand why it is happening and if there is any other solution to this problem ?
here is the encryption and decryption part of the program:
#encryption
for i in range(0, tup[0]): #tup[0] is the no of rows
for j in range(0, tup[1]): #tup[1] is the no of cols
x = data[i][j]
x = ((pow(x,3)) % 25777) #applying the keys
data[i][j] = x #storing back the updated value
#decryption
data= data.tolist() #2d array to list of lists
for i1 in (range(len(data)):
for j1 in (range(len(data[i1]))):
x1 = data[i1][j1]
x1 = (pow(x1, 16971)%25777) #applying the keys
data[i1][j1] = x1
Looking forward to suggestions. Thank you.
The occurrence of something like pow(x1, 16971) should give you pause. This will for almost any integer x1 yield a result which a 64 bit int cannot hold. Which is the reason numpy gives the wrong result, because numpy uses 64 bit or 32 bit integers on the most common platforms. It is also the reason why plain python is slow, because while it can handle large integers this is costly.
A way around this is to apply the modulus in between multiplications, that way numbers remain small and can be readily handled by 64 bit arithmetic.
Here is a simple implementation:
def powmod(b, e, m):
b2 = b
res = 1
while e:
if e & 1:
res = (res * b2) % m
b2 = (b2*b2) % m
e >>= 1
return res
For example:
>>> powmod(2000, 16971, 25777)
10087
>>> (2000**16971)%25777
10087
>>> timeit(lambda: powmod(2000, 16971, 25777), number=100)
0.00031936285085976124
>>> timeit(lambda: (2000**16971)%25777, number=100)
0.255017823074013
how to simplify exponents in equations in sympy
from sympy import symbols
a,b,c,d,e,f=symbols('abcdef')
j=(a**b**5)**(b**10)
print j
(a**(b**5))**(b**10) #ans even after using expand simplify
# desired output
a**(b**15)
and if it is not possible with sympy which module should i import in python?
edit
even if i define 'b' as real,and also all other symbols
b=symbols('b',real=True)
not getting simplified exponents
it simplifies only if exponents are constants
a=symbols('a',real=True)
b=symbols('b',real=True)
(a**5)**10
a**50 #simplifies only if exp are numbers
(a**b**5)**b**10
(a**(b**5))**b**10 #no simplification
(xm)n = xmn is true only if m, n are real.
>>> import math
>>> x = math.e
>>> m = 2j*math.pi
>>> (x**m)**m # (e^(2πi))^(2πi) = 1^(2πi) = 1
(1.0000000000000016+0j)
>>> x**(m*m) # e^(2πi×2πi) = e^(-4π²) ≠ 1
(7.157165835186074e-18-0j)
AFAIK, sympy supports complex numbers, so I believe this simplification should not be done unless you can prove b is real.
Edit: It is also false if x is not positive.
>>> x = -2
>>> m = 2
>>> n = 0.5
>>> (x**m)**n
2.0
>>> x**(m*n)
-2.0
Edit(by gnibbler): Here is the original example with Kenny's restrictions applied
>>> from sympy import symbols
>>> a,b=symbols('ab', real=True, positive=True)
>>> j=(a**b**5)**(b**10)
>>> print j
a**(b**15)
a,b,c=symbols('abc',real=True,positive=True)
(a**b**5)**b**10
a**(b**15)#ans
This may be related to this bug.