Crank Nicolson Method on Wave Function Python - python

I am trying to propagate a gaussian wave packet using the crank nicolson method in imaginary time (multiply the time step by the unit imaginary). The code that I have written in attempt to achieve this is shown here:
import matplotlib.pyplot as plt #this allows you to plot, and changes the name to plt
import numpy as np #this allows you to do math, and changes the name to np
import math
import scipy.linalg as la
def V(x):
# k = 1
# v = k*x**4
v = 0.25*(x-3)**2+0.15*(x-3)**4
return v
def Psi(x):
psi = np.exp(-2*(x-3)**2)
return psi
#Function for computing integral using trapezoid method
def TrapInt(y, h):
trap = [(float(y[ii]) + float(y[ii+1])) for ii in range(0, len(y)-1)]
return float(h)/2*sum(trap)
N = 1000
L = 3;
h = 0.01
x = np.arange(0,6,h);
t = np.linspace(0,L,300);
t = 1j*t;
dt = t[1] - t[0]
dx = x[1] - x[0]
A = 1j*dt/(2*dx**2)
pot = V(x)
Q = np.zeros([len(x),len(x)],dtype = complex)
P = np.zeros([len(x),len(x)],dtype = complex)
wave = np.zeros([len(x),len(t)],dtype = complex)
wave[:,0] = Psi(x)
B = (1- 2*A - 1j*dt*pot)
for ii in range(0,len(x)-1):
Q[ii][ii] = -(B[ii])
P[ii][ii] = (B[ii])
Q[ii][ii+1] = (2-A)
P[ii][ii+1] = A
if ii >= 1:
Q[ii][ii-1] = -A
P[ii][ii-1] = A
plt.plot(wave[:,0])
for ii in range(0,len(t)-1):
one = np.matmul(P,wave[:,ii])
wave[:,ii+1] = np.matmul(la.inv(Q),one)
I can't seem to find any mathematical errors in my implementation of the crank nicolson method; however, whenever I try to run this it gives an error saying that Q is singular (has no inverse). I'm not sure why this is occurring. Any help is appreciated. Thanks

You never assign to Q[-1]. Zero rows have been known to produce singular matrices in some cases.
Also, don’t repeatedly invert the matrix. Probably don’t invert it at all, but rather store some decomposition of it to allow efficient calculation of Q-1x.

Related

solve_ivp discards imaginary part of complex solution

I am computing a solution to the free basis expansion of the dirac equation for electron-positron pairproduction. For this i need to solve a system of equations that looks like this:
Equation for pairproduction, from Mocken at al.
EDIT: This has been solved by passing y0 as complex type into the solver. As is stated in this issue: https://github.com/scipy/scipy/issues/8453 I would definitely consider this a bug but it seems like it has gone under the rock for at least 4 years
for this i am using SciPy's solve_ivp integrator in the following way:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from scipy.integrate import solve_ivp
import scipy.constants as constants
#Impulse
px, py = 0 , 0
#physics constants
e = constants.e
m = constants.m_e # electronmass
c = constants.c
hbar = constants.hbar
#relativistic energy
E = np.sqrt(m**2 *c**4 + (px**2+py**2) * c**2) # E_p
#adiabatic parameter
xi = 1
#Parameter of the system
w = 0.840 #frequency in 1/m_e
N = 8 # amount of amplitudes in window
T = 2* np.pi/w
#unit system
c = 1
hbar = 1
m = 1
#strength of electric field
E_0 = xi*m*c*w/e
print(E_0)
#vectorpotential
A = lambda t,F: -E_0/w *np.sin(t)*F
def linearFenster2(t):
conditions = [t <=0, (t/w>=0) and (t/w <= T/2), (t/w >= T/2) and (t/w<=T*(N+1/2)), (t/w>=T*(N+1/2)) and (t/w<=T*(N+1)), t/w>=T*(N+1)]
funcs = [lambda t: 0, lambda t: 1/np.pi *t, lambda t: 1, lambda t: 1-w/np.pi * (t/w-T*(N+1/2)), lambda t: 0]
return np.piecewise(t,conditions,funcs)
#Coefficient functions
nu = lambda t: -1j/hbar *e*A(w*t,linearFenster2(w*t)) *np.exp(2*1j/hbar * E*t) *(px*py*c**2 /(E*(E+m*c**2)) + 1j*(1- c**2 *py**2/(E*(E+m*c**2))))
kappa = lambda t: 1j*e*A(t,linearFenster2(w*t))* c*py/(E * hbar)
#System to solve
def System(t, y, nu, kappa):
df = kappa(t) *y[0] + nu(t) * y[1]
dg = -np.conjugate(nu(t)) * y[0] + np.conjugate(kappa(t))*y[1]
return np.array([df,dg], dtype=np.cdouble)
def solver(tmin, tmax,teval=None,f0=0,g0=1):
'''solves the system.
#tmin: starttime
#tmax: endtime
#f0: starting percentage of already present electrons of positive energy usually 0
#g0: starting percentage of already present electrons of negative energy, usually 1, therefore full vaccuum
'''
y0=[f0,g0]
tspan = np.array([tmin, tmax])
koeff = np.array([nu,kappa])
sol = solve_ivp(System,tspan,y0,t_eval= teval,args=koeff)
return sol
#Plotting of windowfunction
amount = 10**2
t = np.arange(0, T*(N+1), 1/amount)
vlinearFenster2 = np.array([linearFenster2(w*a) for a in t ], dtype = float)
fig3, ax3 = plt.subplots(1,1,figsize=[24,8])
ax3.plot(t,E_0/w * vlinearFenster2)
ax3.plot(t,A(w*t,vlinearFenster2))
ax3.plot(t,-E_0 /w * vlinearFenster2)
ax3.xaxis.set_minor_locator(ticker.AutoMinorLocator())
ax3.set_xlabel("t in s")
ax3.grid(which = 'both')
plt.show()
sol = solver(0, 70,teval = t)
ts= sol.t
f=sol.y[0]
fsquared = 2* np.absolute(f)**2
plt.plot(ts,fsquared)
plt.show()
The plot for the window function looks like this (and is correct)
window function
however the plot for the solution looks like this:
Plot of pairproduction probability
This is not correct based on the papers graphs (and further testing using mathematica instead).
When running the line 'sol = solver(..)' it says:
\numpy\core\_asarray.py:102: ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
I simply do not know why solve_ivp discard the imaginary part. Its absolutely necessary.
Can someone enlighten me who knows more or sees the mistake?
According to the documentation, the y0 passed to solve_ivp must be of type complex in order for the integration to be over the complex domain. A robust way of ensuring this is to add the following to your code:
def solver(tmin, tmax,teval=None,f0=0,g0=1):
'''solves the system.
#tmin: starttime
#tmax: endtime
#f0: starting percentage of already present electrons of positive energy usually 0
#g0: starting percentage of already present electrons of negative energy, usually 1, therefore full vaccuum
'''
f0 = complex(f0) # <-- added
g0 = complex(g0) # <-- added
y0=[f0,g0]
tspan = np.array([tmin, tmax])
koeff = np.array([nu,kappa])
sol = solve_ivp(System,tspan,y0,t_eval= teval,args=koeff)
return sol
I tried the above, and it indeed made the warning disappear. However, the result of the integration seems to be the same regardless.

Discrepancy between analytic solution and solution by relaxation method

So I am trying to solve the differential equation $\frac{d^2y}{dx^2} = -y(x)$ subject to boundary conditions y(0) = 0 and y(1) = 1 ,the analytic solution is y(x) = sin(x)/sin(1).
I am using three point stencil to approximate the double derivative.
The curves obtained through these ways should match at least at the boundaries ,but my solutions have small differences even at the boundaries.
I am attaching the code, Please tell me what is wrong.
import numpy as np
import scipy.linalg as lg
from scipy.sparse.linalg import eigs
from scipy.sparse.linalg import inv
from scipy import sparse
import matplotlib.pyplot as plt
a = 0
b = 1
N = 1000
h = (b-a)/N
r = np.arange(a,b+h,h)
y_a = 0
y_b = 1
def lap_three(r):
h = r[1]-r[0]
n = len(r)
M_d = -2*np.ones(n)
#M_d = M_d + B_d
O_d = np.ones(n-1)
mat = sparse.diags([M_d,O_d,O_d],offsets=(0,+1,-1))
#print(mat)
return mat
def f(r):
h = r[1]-r[0]
n = len(r)
return -1*np.ones(len(r))*(h**2)
def R_mat(f,r):
r_d = f(r)
R_mat = sparse.diags([r_d],offsets=[0])
#print(R_mat)
return R_mat
#def R_mat(r):
# M_d = -1*np.ones(len(r))
def make_mat(r):
main = lap_three(r) - R_mat(f,r)
return main
main = make_mat(r)
main_mat = main.toarray()
print(main_mat)
'''
eig_val , eig_vec = eigs(main, k = 20,which = 'SM')
#print(eig_val)
Val = eig_vec.T
plt.plot(r,Val[0])
'''
main_inv = inv(main)
inv_mat = main_inv.toarray()
#print(inv_mat)
#print(np.dot(main_mat,inv_mat))
n = len(r)
B_d = np.zeros(n)
B_d[0] = 0
B_d[-1] = 1
#print(B_d)
#from scipy.sparse.linalg import spsolve
A = np.abs(np.dot(inv_mat,B_d))
plt.plot(r[0:10],A[0:10],label='calculated solution')
real = np.sin(r)/np.sin(1)
plt.plot(r[0:10],real[0:10],label='analytic solution')
plt.legend()
#plt.plot(r,real)
#plt.plot(r,A)
'''diff = A-real
plt.plot(r,diff)'''
There is no guarantee of what the last point in arange(a,b+h,h) will be, it will mostly be b, but could in some cases also be b+h. Better use
r,h = np.linspace(a,b,N+1,retstep=True)
The linear system consists of the equations for the middle positions r[1],...,r[N-1]. These are N-1 equations, thus your matrix size is one too large.
You could keep the matrix construction shorter by including the h^2 term already in M_d.
If you use sparse matrices, you can also use the sparse solver A = spsolve(main, B_d).
The equations that make up the system are
A[k-1] + (-2+h^2)*A[k] + A[k+1] = 0
The vector on the right side thus needs to contain the values -A[0] and -A[N]. This should clear up the sign problem, no need to cheat with the absolute value.
The solution vector A corresponds, as constructed from the start, to r[1:-1]. As there are no values for postitions 0 and N inside, there can also be no difference.
PS: There is no relaxation involved here, foremost because this is no iterative method. Perhaps you meant a finite difference method.

Issue specifying datatype for the Mandelbrot set

I have optimized a bit on calculating the Mandelbrot set, & I now wish to be able to specify whether my arrays should be float64 or float32 instead of the easier implementation with type complex128 or complex64. I use the fact that for a complex number (a+jb)^2 = a^2-b^2 + (2ab)j, but this seems to give me a slightly different wrong mandelbrot set. The code is seen below:
from timeit import default_timer as timer
import numpy as np
from numexpr import evaluate
import matplotlib.pyplot as plt
#%% Inputs
N = 5000
I = 20
T = 2 #Thresholdenter code here
#%% Functions
def mandel_brot_vector(I,C,T,datatype):
Cre = np.array(C.real,dtype=datatype)
Cim = np.array(C.imag,dtype=datatype)
M = np.zeros(Cre.shape,dtype=datatype)
zreal=0
zimag=0
for i in range(I):
M[zreal*zreal+zimag*zimag<T**2] = i/I
zreal = evaluate("zreal*zreal-zimag*zimag+Cre") #complex multiplication rule
zimag = evaluate("2*zreal*zimag+Cim") #complex multiplication rule
N = len(M[0])
M = np.reshape(np.array(M),(N//2,N)).astype(datatype)
M = np.concatenate((M,M[::-1]),axis=0)
return M
def create_C(N,split):
C_re = np.linspace(np.full((1,N),-2)[0],np.full((1,N),1)[0],N).T
C_im = np.linspace(np.full((1,N),1.5*1j)[0],np.full((1,N),-1.5*1j)[0],N)
C = C_re+C_im
C = C[:N//2,:]
if split != 0:
C_split = np.array_split(C,split)
else:
C_split = C
return np.array(C_split)
C = create_C(N, 0)
t0_32 = timer()
M32 = mandel_brot_vector(I,C,T,np.float32)
t_32 = timer() - t0_32
t0_64 = timer()
M64 = mandel_brot_vector(I,C,T,np.float64)
t_64 = timer() - t0_64
plt.matshow(M64,cmap="hot")
print(" "*10,f"N={N}")
print(f"{'Float 32':<20}{t_32:<40}",
f"\n{'Float 64':<20}{t_64:<40}"
)
Currently the image I get: wrong mandelbrot. For reference, the following function will produce the correct mandelbrot set but with complex128:
def mandel_brot(I,C,T):
M = np.zeros(C.shape)
z=0
for i in range(I):
M[np.abs(z)<T] = i/I
z = evaluate("z*z+C")
N = len(M[0])
M = np.reshape(np.array(M),(N//2,N)).astype(datatype)
M = np.concatenate((M,M[::-1]),axis=0)
return M
Hope someone can help solve this issue, thanks in advance. Btw do not bother with the split of the C array, it is set up to run with multiprocessing which I am not using in the code attached.

Having problems with scipy.integrate.solve_ivp

I am trying to use scipy.integrate.solve_ivp to calculate the solutions to newton's gravitation equation for my n body simulation, however I am confused how the function is passed into solve_ivp. I have the following code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
G = 6.67408e-11
m_sun = 1988500e24
m_jupiter = 1898.13e24
m_earth = 5.97219e24
au = 149597870.700e3
v_factor = 1731460
year = 31557600.e0
init_s = np.array([-6.534087946884256E-03*au, 6.100454846284101E-03*au, 1.019968145073305E-04*au, -6.938967653087248E-06*v_factor, -5.599052606952444E-06*v_factor, 2.173251724105919E-07*v_factor])
init_j = np.array([2.932487231769548E+00*au, -4.163444383137574E+00*au, -4.833604407653648E-02*au, 6.076788230491844E-03*v_factor, 4.702729516645153E-03*v_factor, -1.554436340872727E-04*v_factor])
variables_s = init_s
variables_j = init_j
N = 2
tStart = 0e0
t_End = 25*year
Nt = 2000
dt = t_End/Nt
temp_end = dt
t=tStart
domain = (t, temp_end)
planetsinit = np.vstack((init_s, init_j))
planetspos = planetsinit[:,0:3]
mass = np.vstack((1988500e24, 1898.13e24))
def weird_division(n, d):
return n / d if d else 0
variables_save = np.zeros((N,6,Nt))
variables_save[:,:,0] = planetsinit
pos_s = planetspos[0]
pos_j = planetspos[1]
while t < t_End:
t_index = int(weird_division(t, dt))
for index in range(len(planetspos)):
for otherindex in range(len(planetspos)):
if index != otherindex:
x1_p1, x2_p1, x3_p1 = planetsinit[index, 0:3]
x1_p2, x2_p2, x3_p2 = planetsinit[otherindex, 0:3]
m = mass[otherindex]
def f_grav(t, y):
x1_p1, x2_p1, x3_p1, v1_p1, v2_p1, v3_p1 = y
x1_diff = x1_p1 - x1_p2
x2_diff = x2_p1 - x2_p2
x3_diff = x3_p1 - x3_p2
dydt = [v1_p1,
v2_p1,
v3_p1,
-(x1_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2),
-(x2_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2),
-(x3_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)]
return dydt
solution = solve_ivp(fun=f_grav, t_span=domain, y0=planetsinit[index])
planetsinit[index] = solution['y'][0:6, -1]
variables_save[index,:,t_index] = solution['y'][0:6, -1]
planetspos[index] = planetsinit[index][0:3]
t += dt
temp_end += dt
domain = (t,temp_end)
pos_s = variables_save[0,0:3,:]
pos_j = variables_save[1,0:3,:]
plt.plot(variables_save[0,0:3,:][0], variables_save[0,0:3,:][1])
plt.plot(variables_save[1,0:3,:][0], variables_save[1,0:3,:][1])
The code above works very nicely and produces a stable orbit. However when I calculate the acceleration outside the function and feed that through into the f_grav function, something goes wrong and produces an orbit which is no longer stable. However I am perplexed as I don't know why the data is different as to be it seems like that I have passed through the exactly same inputs. Which leads me to think that maybe its the way the the function f_grav is interpolated by the solve_ivp integrator? To calculate the acceleration outside all I do is change the following code in the loop to:
x1_p1, x2_p1, x3_p1 = planetsinit[index, 0:3]
x1_p2, x2_p2, x3_p2 = planetsinit[otherindex, 0:3]
m = mass[otherindex]
x1_diff = x1_p1 - x1_p2
x2_diff = x2_p1 - x2_p2
x3_diff = x3_p1 - x3_p2
ax = -(x1_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)
ay = -(x2_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)
az = -(x3_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)
def f_grav(t, y):
x1_p1, x2_p1, x3_p1, v1_p1, v2_p1, v3_p1 = y
dydt = [v1_p1,
v2_p1,
v3_p1,
ax,
ay,
az]
return dydt
solution = solve_ivp(fun=f_grav, t_span=domain, y0=planetsinit[index])
planetsinit[index] = solution['y'][0:6, -1]
variables_save[index,:,t_index] = solution['y'][0:6, -1]
planetspos[index] = planetsinit[index][0:3]
As I said I don't know why different orbits are produces which are shown below and any hints as to why or how to solve it would me much appreciated. To clarify why I can't use the working code as it is, as when more bodies are involved I aim to sum the accelerations contribution of all the other planets which isn't possible this way where the acceleration is calculated in the function itself.
Sorry for the large coding chunks but I did feel it was appropriate as then it could be run and the problem itself is clearer.
Both have the same time period, dt, however the orbit on the left is stable and the one on the right is not

numpy, same calculation different result

I want to calculate this fomula.
I think the result is A.
So I write a python code using numpy.
But depending on the computation sequence, result is not A.
What brought this on?
import numpy as np
from numpy import *
from numpy.random import *
import decimal
#generate matrix A
A = randn(180,240)
A = np.array(A, dtype = decimal.Decimal )
#generate matrix P
h,w=A.shape
P = randn(0.9*h,h)
P = np.array(P, dtype = decimal.Decimal )
#it's OK. IA = A
PP = dot(P.T,P)
PPinv = np.linalg.inv(PP)
PPinvPP = dot(PPinv,PP)
PPinvPPinv = np.linalg.inv(PPinvPP)
I = dot(PPinvPPinv,PPinvPP)
IA = dot(I, A)
#I think IA2 must be A. but not A. Why?
PA = dot(P,A)
PPA = dot(P.T,PA)
PPinvPPA = dot(PPinv,PPA)
IA2 = dot(PPinvPPinv, PPinvPPA)
#print result
print "A;%0.2f" % A[0,0]
print "IA:%0.2f" % IA[0,0]
print "IA2:%0.2f" % IA2[0,0]
What happens here is quite interesting:
In general your formula is only correct if PP is non-singular.
So why then AI == A?
PP = dot(P.T,P)
PPinv = np.linalg.inv(PP)
PPinvPP = dot(PPinv,PP)
PPinvPPinv = np.linalg.inv(PPinvPP)
I = dot(PPinvPPinv,PPinvPP)
IA = dot(I, A)
There are a couple of things to note here:
PP = dot(P.T,P) is singular
=> PPinv is not a true inverse
but PPinvPP is invertible, so I is indeed the identity matrix
Note: You only get AI == A because of your special order of evaluation of the terms.
In the second calculation of IA2 term you don't have this special evaluation order that gives you A as result.
The main reason is when you use non-square matrix P, where height is less than width, determinant of the PP always has a zero value, but because of a calc error it's != 0. So after this it's impossible to calculate the real PPinv and any forward steps are meaningless.
P = randn(2,3)
P = np.array(P, dtype = decimal.Decimal )
PP = dot(P.T,P)
np.linalg.det(PP) #-5.2536080570332981e-34
So why is IA == A?
I think it's a situation when error*error gives you a normal result.
How to solve it?
Do not use Python for theoretical questions :)
Change P = randn(0.9*h,h) to P = randn(1.1*h,h)
Not a direct answer to your question, but you could use Sympy for these kind of problems:
from IPython.display import display
import sympy as sy
sy.init_printing() # For LaTeX-like pretty printing in IPython
n = 5
A = sy.MatrixSymbol('A', 162, 240) # dimension 162x240
P = sy.MatrixSymbol('P', 162, 180) # dimensions 162x180
PTP = P*P.T
ex1 = (PTP.inverse() * PTP).inverse() * PTP.inverse() * PTP * A
display(ex1) # displays: A

Categories