1

I already know that the solution to a system of four equations are these:

  • z1
  • z2
  • w1
  • w2.

The SymPy code I'm using:

z1, z2, w1, w2 = symbols( 'z1, z2, w1, w2', real=True )
ex1 = 2 * (z1*w1 + z2*w2)
ex2 = w1**2 + w2**2 + 4*z1*z2*w1*w2
ex3 = 2*w1*w2 * (z1*w2 + z2*w1)
ex4 = w1**2 * w2**2
eq1 = Eq( ex1, 10 )
eq2 = Eq( ex2, 45 )
eq3 = Eq( ex3, 105 )
eq4 = Eq( ex4, 105 )
solve( [ eq1, eq2, eq3, eq4 ], [ z1, z2, w1, w2 ] )
[]                      # Note that the result is empty.

I can demonstrate the solution I have, though:

solution = { z1:0.620702965049498, z2:0.957974461859108, w1:3.38936579272158, w2:3.02326493881663 }
for i in [ ex1, ex2, ex3, ex4 ]: i.subs( solution )
9.99999999999999        # Very close to 10 in eq1
44.9999999999999        # Very close to 45 in eq2
105.000000000000        # Matches 105 in eq3
105.000000000000        # Matches 105 in eq4

I'm wondering what I may have done wrong in setting this up for SymPy. Or if there's a known solver problem? (My SymPy version is '1.10.1'.)

Note: I'm working on the expression of a homogeneous solution for a 4th order Bessel equation as used in electronics filter design, where I've factored it into two 2nd order expressions.

jonk
  • 225
  • 4
  • 11

2 Answers2

2

Since you are looking for numerical solutions, you probably don't need SymPy.

Here I'm going to use scipy's fsolve to solve your system of non-linear equations:

from scipy.optimize import fsolve
# convert your equations to numerical functions
eqns = [lambdify([z1, z2, w1, w2], eq.rewrite(Add)) for eq in [eq1, eq2, eq3, eq4]]

def func(p):
    # p contains the current iteration parameters: z1, z2, w1, w2
    return [eq(*p) for eq in eqns]

sol = fsolve(func, (1, 1, 1, 1))
print(sol)
# [0.62070297 0.95797446 3.38936579 3.02326494]

Note that you can also use Sympy's nsolve to solve a system of equations for numerical values, but you need to provide some good initial guess, which for your case might not be trivial:

nsolve([eq1, eq2, eq3, eq4], [z1, z2, w1, w2], [0.5, 1, 3, 3])
Davide_sd
  • 10,578
  • 3
  • 18
  • 30
  • Thanks. That does work with nsolve. But after adding symbolics for the constants I provided, b1, b2, b3, and b4, and re-attempting to use solve (not nsolve) I still got an empty array as a result. I can, by hand, create the symbolic solution. Perhaps I should have re-phrased my question. (And I may do that but as another question, not an edit to this one.) But I think I will accept your answer, soon, as it is sufficiently within the scope of my question to qualify. (Unless something a little more direct shows up soon. I won't wait a long time.) Thanks! – jonk Dec 18 '22 at 21:03
  • I'm not sure why solve doesn't work for the original equations but if you intend to make the right hand sides symbolic then it's unlikely that an explicit analytic expression for the solutions will be possible. – Oscar Benjamin Dec 18 '22 at 23:04
  • @OscarBenjamin Agreed. That's why I didn't do it in the first place. And I don't know why it doesn't work. Still. That said, I will accept Davide_sd's answer now as I need to move on. I can always come back to this question once I find a better way to phrase it. – jonk Dec 19 '22 at 04:34
  • 2
    You might be interested in my answer to a different question: https://stackoverflow.com/a/72190701/9450991 – Oscar Benjamin Dec 19 '22 at 11:35
2

Oscar has given several nice answers using the Groebner basis; beside the one above see also this one. If you apply that approach to your equations you will find that there are 8 solutions (but mainly 2, with permutations of signs on terms...and mainly 1 when you consider symmetry of exchanging z1 and z2 while exchanging w1 and w2).

First compute the basis of the equations in form f(X) = 0; X = (z1,z2,w1,w2)

from sympy import *
...
...your work from above
...
F = Tuple(*[i.rewrite(Add) for i in (eq1,eq2,eq3,eq4)])
X = (z1,z2,w1,w2)
G, Y = groebner(F, X)
G = Tuple(*G)  # to make substitution easier below

G[-1] is a 12th order polynomial in w2 for which the real roots can be found: there are 4.

w2is = real_roots(G[-1])
assert len(w2is) == 4  # [i.n(3) for i in w2is] -> [-3.39, -3.02, 3.02, 3.39]

G[0] and G[1] are linear in z1 and z2, respectively, and G[2] is quadratic in w1. So substituting in a value for w2 and solving should give 2 solutions for each value of w2 for a total of 8 solutions.

sols = []
for _w2 in w2is:
    for si in solve(G[:-1].subs(w2,_w2.n()), dict=True):
        si.update({w2:_w2})
        sols.append(si)
        print({k: v.n(2) for k,v in si.items()})
{w1: -3.0, z1: -0.96, z2: -0.62, w2: -3.4}
{w1: 3.0, z1: 0.96, z2: -0.62, w2: -3.4}
{w1: -3.4, z1: -0.62, z2: -0.96, w2: -3.0}
{w1: 3.4, z1: 0.62, z2: -0.96, w2: -3.0}
{w1: -3.4, z1: -0.62, z2: 0.96, w2: 3.0}
{w1: 3.4, z1: 0.62, z2: 0.96, w2: 3.0}
{w1: -3.0, z1: -0.96, z2: 0.62, w2: 3.4}
{w1: 3.0, z1: 0.96, z2: 0.62, w2: 3.4}
smichr
  • 16,948
  • 2
  • 27
  • 34
  • I don't have time today to go over this. But I love the education it offers. I'll spend some time this week gathering up what I can. If I have a question or two, I'll ask. But for now thanks so much for your time. I still need to learn to use asserts. (I knew about them quite vaguely.) I'm still fairly new using Python and SymPy, but enjoying it a lot. :) Thanks again! +1 of course. – jonk Dec 19 '22 at 20:25
  • You can still use the Groebner approach if you replace the constants with variables. Getting a solution, once numbers are known, then, only requires getting the real roots for the final equation in the basis and back substituting as I did above. – smichr Dec 19 '22 at 23:37