Sympy doesn't find a solution for a system of four equations when I already know the solution exists - sympy

I already know that the solution to a system of four equations are these:
.
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.

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])

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}

Related

sympy substitution of occurences satisfying a given pattern

Is there a way to do a substitution in sympy of all occurences that satisfy a given pattern? For example, if I have an expression that contains multiple occurances of sqrt(anything), is there a way to substitute all those occurences with anything**0.51?
Here is a code example:
import sympy
from sympy import sqrt, exp
from sympy import *
x,y = symbols('x y')
test = sqrt (x+y) + sqrt (exp(y))
in this case I can do substitution manually:
test.subs(sqrt(x+y), (x + y)**0.51).subs(sqrt (exp(y)) , (exp(y))**0.51)
yielding the expected subsitution. But, is there a way to do it one shot so one can easily apply it to long epxressions?
You can use a Wild symbol to do pattern-matched replacements:
In [7]: w = Wild('w')
In [8]: test
Out[8]:
____
_______ ╱ y
╲╱ x + y + ╲╱ ℯ
In [9]: test.replace(sqrt(w), w**0.51)
Out[9]:
0.51
0.51 ⎛ y⎞
(x + y) + ⎝ℯ ⎠
You can use the replace method. Here is the documentation with a few examples.
In your particular case, the square root is a power with exponent 0.5, so we are going to look for those objects:
test.replace(lambda t: t.is_Pow and t.exp is S.Half, lambda t: t.base**0.51)

Plotting a sympy plot for simultanous ODE solutions

g, d = symbols("g d", cls=Function)
T, G= symbols("T G")
system = [Eq( d(t).diff(t), - d(t)/T), Eq( g(t).diff(t), - G*g(t) + d(t)/T )]
ics = {d(0): 1, g(0): 0}
e = sym.dsolve(system, [g(t), d(t)], ics=ics)
is the code I am using to find solutions. How do I plot the functions across time with G and T as constants ?
I tried using lambdify function, but pops up an error.
lmbd_sol = lambdify(t, e.rhs)

Sympy Differential Equation : How to mowe some lhs terms to rhs?

I have the following differential equation : eq1
t = sp.Symbol("t")
f1=sp.Function("f1")(t)
f2=sp.Function("f2")(t)
eq1=sp.Eq(f1+f1.diff(t,2)-f2+f2.diff(t,1),0)
To move f2 terms from the lhs to the rhs, I used this code :
eq2=sp.Eq(eq1.lhs.subs(f2,0).doit(),eq1.lhs.subs(f1,0).doit()*-1)
Is it the right way to do that or is there a simpler solution ?
Thanks for answer.
That might fail if e.g. you have a 1/f1 somewhere. I'd do it like this:
In [18]: eq1
Out[18]:
2
d d
f₁(t) - f₂(t) + ───(f₁(t)) + ──(f₂(t)) = 0
2 dt
dt
In [19]: lhs, neg_rhs = (eq1.lhs - eq1.rhs).as_independent(f2, as_Add=True)
In [20]: eq2 = Eq(lhs, -neg_rhs)
In [21]: eq2
Out[21]:
2
d d
f₁(t) + ───(f₁(t)) = f₂(t) - ──(f₂(t))
2 dt
dt
https://docs.sympy.org/latest/modules/core.html#sympy.core.expr.Expr.as_independent

Counterpart of stencil update in other languages

What I find really interesting is Fortrans's capability of stencil updates: instead of looping
t2 = v(1)
do i=2, n-1
t1 = v(i)
v(i) = 0.5 * (t2 + v(i+1))
t2 = t1
enddo
one can use a one-liner, without an explicit loop
v(2:n-1) = 0.5 * (v(1:n-2) + v(3:n))
(For this, and other examples see this slideshow)
I haven't anything similar in any other programming language. Is there any other language which supports a similar syntax?
It may be interesting to check the wiki page for Array programming, which says
Modern programming languages that support array programming are commonly used in scientific and engineering settings; these include Fortran 90, Mata, MATLAB, Analytica, TK Solver (as lists), Octave, R, Cilk Plus, Julia, and the NumPy extension to Python...
and also pages for array slicing and a list of array languages. So, several languages seem to have a similar syntax (which goes back to as old as ALGOL68 ?!)
Here are some examples (there may be mistakes so please check by yourself..):
Fortran :
program main
implicit none
real, allocatable :: v(:)
integer i, n
n = 8
v = [( real(i)**2, i=1,n )]
print *, "v = ", v
v(2:n-1) = 0.5 * ( v(1:n-2) + v(3:n) )
print *, "v = ", v
end
$ gfortran test.f90 && ./a.out
v = 1.00000000 4.00000000 9.00000000 16.0000000 25.0000000 36.0000000 49.0000000 64.0000000
v = 1.00000000 5.00000000 10.0000000 17.0000000 26.0000000 37.0000000 50.0000000 64.0000000
Python:
import numpy as np
n = 8
v = np.array( [ float(i+1)**2 for i in range( n ) ] )
print( "v = ", v )
v[1:n-1] = 0.5 * ( v[0:n-2] + v[2:n] )
print( "v = ", v )
$ python3 test.py
v = [ 1. 4. 9. 16. 25. 36. 49. 64.]
v = [ 1. 5. 10. 17. 26. 37. 50. 64.]
Julia:
n = 8
v = Float64[ i^2 for i = 1 : n ]
println( "v = ", v )
v[2:n-1] = 0.5 * ( v[1:n-2] + v[3:n] )
println( "v = ", v )
$ julia test.jl
v = [1.0,4.0,9.0,16.0,25.0,36.0,49.0,64.0]
v = [1.0,5.0,10.0,17.0,26.0,37.0,50.0,64.0]
Chapel:
var n = 8;
var v = ( for i in 1..n do (i:real)**2 );
writeln( "v = ", v );
var vtmp = 0.5 * ( v[1..n-2] + v[3..n] );
v[2..n-1] = vtmp;
writeln( "v = ", v );
$ chpl test.chpl && ./a.out
v = 1.0 4.0 9.0 16.0 25.0 36.0 49.0 64.0
v = 1.0 5.0 10.0 17.0 26.0 37.0 50.0 64.0
(please see wiki pages etc for other languages).
I think the array notation such as : or .. is very convenient, but it can give unexpected results (if not used properly, e.g., the meaning of indices, or a possible overlap of LHS/RHS) or cause run-time overhead (because of temporary arrays), depending on cases. So please take care when actually using it...

Error: Unclassifiable statement in if conditional

This is the program I wrote to calculate the distance between carbon and hydrogen atom coordinates as a practice. But some errors occurred and I don't know how to fix them.
program C_H_bdlength
implicit none
integer :: ia ! integer atoms number
integer :: na ! number of atoms
real*8 :: x , y , z ! x y z coordinates of all atoms
real*8 :: x1 , y1 , z1 ! x y z coordinates of C atoms
real*8 :: x2 , y2 , z2 ! x y z coordinates of H atoms
character(len=2) :: ele ! element name
real*8 :: dis ! distance between C and H
open(100,file='cnt.ini',action='read',status='old')
na = 0
do
read(100,*,end=101)
na = na + 1
end do
101 continue
open(200, file='cnt.ini',action='wtire',status='replace')
rewind(100)
write(200,*)
do ia = 1 , na
read(100,*) ele, x, y, z
if ele == 'C' then x1 = x, y1 = y, z1 = z
do ia = ia , na
read(100,*) ele, x, y, z
if ele == 'H' then x2 = x, y2 = y, z2 = z
dis = sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
write(200,*) dis
end if
end do
end if
end do
end program C_H_bdlength
Errors:
In file C_H_bdlength.f90:26
if ele == 'C' then x1 = x, y1 = y, z1 = z
1
Error: Unclassifiable statement at (1)
In file C_H_bdlength.f90:27
do ia = ia , na
1
In file C_H_bdlength.f90:24
do ia = 1 , na
2
Error: Variable 'ia' at (1) cannot be redefined inside loop beginning at (2)
In file C_H_bdlength.f90:29
if ele == 'H' then x2 = x, y2 = y, z2 = z
1
Error: Unclassifiable statement at (1)
In file C_H_bdlength.f90:33
end if
1
Error: Expecting END DO statement at (1)
In file C_H_bdlength.f90:35
end if
1
Error: Expecting END DO statement at (1)
You have a nested loop, and are using the same variable (ia) to control both the inner and outer loop. Use a variable with a different name in the inner loop.
The if <condition> then construct cannot have additional operations following the then on the same line.
In addition to #Peter's answer, another (not syntactic) problem is that your DO loops do not take into account all the C-H pairs. For example, if "cnt.ini" contains the following data
O 1.0 1.0 1.0
H 8.0 1.0 6.0 # ia=2
C 2.0 3.0 1.0 # ia=3
N 3.0 3.0 3.0
H 4.0 1.0 6.0 # ia=5
O 5.0 5.0 5.0
C 1.0 7.0 8.0 # ia=7
it only takes into account ia = 3 and 5 pair. Further, because you are writing output data onto the input file (cnt.ini) while reading the input from the latter, data transfer may be broken. So I suggest first reading in the input data into arrays, calculate C-H distances, and then write the result onto a different file. For example, the code may look like this.
integer :: ia, ja, na
real*8 :: dist
real*8, allocatable :: pos(:,:)
character(len=2), allocatable :: ele(:)
open(100,file='cnt.ini',status='old')
!! First, determine the number of atoms in the file.
na = 0
do
read( 100, *, end=101 )
na = na + 1
end do
101 continue
!! Then, allocate necessary arrays for atomic positions and element names.
allocate( pos( 3, na ), ele( na ) )
!! Now read the actual data in the file.
rewind( 100 )
do ia = 1, na
read( 100, * ) ele( ia ), pos( 1:3, ia )
! Alternative way using implied DO-loop.
! read( 100, * ) ele( ia ), ( pos( k, ia ), k = 1, 3 )
enddo
close( 100 )
!! Now calculate C-H distances.
open(200, file='dist.dat')
!! Loop over unique atom pairs.
do ia = 1 , na - 1
do ja = ia + 1, na
if ( ( ele(ia) == 'C' .and. ele(ja) == 'H' ) .or. &
( ele(ia) == 'H' .and. ele(ja) == 'C' ) ) then
dist = norm2( pos( :, ia ) - pos( :, ja ) )
! dist = sqrt( sum( (pos(:,ia) - pos(:,ja))**2 ) ) !! if norm2() not available
write(200,*) ia, ja, dist
endif
enddo
enddo