FTCS Solution of the Wave Equation - Issues with Vpython - python

I am attempting to make an animation of the motion of the piano string
using the facilities provided by the vpython package. There are
various ways you could do this, but my goal is to do this with using
the curve object within the vpython package. Below is my code for
solution of the initial problem of solving the complete sets of
simultaneous 1st-order equation. Thanks in advance, I am really
uncertain as to where to start with the vpython animation.
# Key Module and Function Import(s):
import numpy as np
import math as m
import pylab as py
import matplotlib
from time import time
import scipy
# Variable(s) and Constant(s):
L = 1.0 # Length on string in m
C = 1.0 # velocity of the hammer strike in ms^-1
d = 0.1 # Hammer distance from 0 to point of impact with string
N = 100 # Number of divisions in grid
sigma = 0.3 # sigma value in meters
a = L/N # Grid spacing
v = 100.0 # Initial velocity of wave on the string
h = 1e-6 # Time-step
epsilon = h/1000
# Computation(s):
def initialpsi(x):
return (C*x*(L-x)/(L**2))*m.exp((-(x-d)**2)/(2*sigma**2)) # Definition of the function
phibeg = 0.0 # Beginning - fixed point
phimiddle = 0.0 # Initial x
phiend = 0.0 # End fixed point
psibeg = 0.0 # Initial v at beg
psiend = 0.0 # Initial v at end
t2 = 2e-3 # string at 2ms
t50 = 50e-3 # string at 50ms
t100 = 100e-3 # string at 100ms
tend = t100 + epsilon
# Creation of empty array(s)
phi = np.empty(N+1,float)
phi[0] = phibeg
phi[N] = phiend
phi[1:N] = phimiddle
phip = np.empty(N+1,float)
phip[0] = phibeg
phip[N] = phiend
psi = np.empty(N+1,float)
psi[0] = psibeg
psi[N] = psiend
for i in range(1,N):
psi[i] = initialpsi(i*a)
psip = np.empty(N+1,float)
psip[0] = psibeg
psip[N] = psiend
# Main loop
t = 0.0
D = h*v**2 / (a*a)
timestart = time()
while t<tend:
# Calculation the new values of T
for i in range(1,N):
phip[i] = phi[i] + h*psi[i]
psip[i] = psi[i] + D*(phi[i+1]+phi[i-1]-2*phi[i])
phip[1:N] = phi[1:N] + h*psi[1:N]
psip[1:N] = psi[1:N] + D*(phi[0:N-1] + phi[2:N+1] -2*phi[1:N])
phi= np.copy(phip)
psi= np.copy(psip)
#phi,phip = phip,phi
#psi,psip = psip,psi
t += h
# Plot creation in step(s)
if abs(t-t2)<epsilon:
t2array = np.copy(phi)
py.plot(phi, label = "2 ms")
if abs(t-t50)<epsilon:
t50array = np.copy(phi)
py.plot(phi, label = "50 ms")
if abs(t-t100)<epsilon:
t100array = np.copy(phi)
py.plot(phi, label = "100 ms")

See the curve documentation at
https://www.glowscript.org/docs/VPythonDocs/curve.html
Use the "modify" method to change the individual points along the curve object, inside a loop that contains a rate statement:
https://www.glowscript.org/docs/VPythonDocs/rate.html

Related

Generate a graph for the diffusion equation

I have a code that represents the diffusion equation (Concentration as a function of time and space):
∂²C/∂x² - ∂C/∂t= 0
I discretized to the following form:
C[n+1,j] = C[n,j] + (dt/dx²)(C[n,j+1] - 2(C[n,j]) + C[n,j-1])
I am trying to generate the following graph, however I haven't had much success. Is there anyone who could help me with this? Many thanks!
The graph that I obtain:
The code that I have to reproduce the diffusion equation:
import numpy as np
import matplotlib.pyplot as plt
dt = 0.001 # grid size for time (s)
dx = 0.05 # grid size for space (m)
x_max = 1 # in m
t_max = 1 # total time in s
C0 = 1 # concentration
# function to calculate concentration profiles based on a
# finite difference approximation to the 1D diffusion
# equation:
def diffusion(dt,dx,t_max,x_max,C0):
# diffusion number:
s = dt/dx**2
x = np.arange(0,x_max+dx,dx)
t = np.arange(0,t_max+dt,dt)
r = len(t)
a = len(x)
C = np.zeros([r,a]) # initial condition
C[:,0] = C0 # boundary condition on left side
C[:,-1] = 0 # boundary condition on right side
for n in range(0,r-1): # time
for j in range(1,a-1): # space
C[n+1,j] = C[n,j] + s*(C[n,j-1] -
2*C[n,j] + C[n,j+1])
return x,C,r,a
# note that this can be written without the for-loop
# in space, but it is easier to read it this way
x,C,r,a = diffusion(dt,dx,t_max,x_max,C0)
# plotting:
plt.figure()
plt.xlim([0,1])
plt.ylim([0,1])
plot_times = np.arange(0,1,0.02)
for t in plot_times:
plt.plot(x,C[int(t/dt),:],'Gray',label='numerical')
plt.xlabel('Membrane position x',fontsize=12)
plt.ylabel('Concentration',fontsize=12)

Function not callable anymore after one try

I'm coding a function right now which has a really weird problem. When I define the function Psi(t) and call it to be plotted, it works fine. But, when you call it again to be plotted, it sends an error 'numpy.ndarray' object is not callable. When you click play (on Jupyter notebook) on Psi(t) to define it again then call it to be plotted, it works fine again. You'd have to define it again if you wanna change a parameter then plot Psi(t) again. I don't know if it's with the code or the software that I use for python (VS code). Anyhow, here's the code:
# Constants
m = 9.109e-31 # mass of electron in kg
L = 1e-8 # length of box in m
hbar = 1.0546e-34 # hbar in J/s
x0 = L/2 # midpoint of box
sigma = 1e-10 # width of wave packet in m
kappa = 5e10 # wave number in 1/m
N = 1000 # number of grid slices
def psi0(x):
return np.exp( -(x - x0)**2/(2*sigma**2) )*np.exp(-1j*kappa*x)
#Discrete sine transform
def dst(y):
N = len(y)
y2 = np.empty(2*N,float)
y2[0] = y2[N] = 0.0
y2[1:N] = y[1:]
y2[:N:-1] = -y[1:]
a = -np.imag(rfft(y2))[:N]
a[0] = 0.0
return a
#Inverse discrete sine transform
def idst(a):
N = len(a)
c = np.empty(N+1,complex)
c[0] = c[N] = 0.0
c[1:N] = -1j*a[1:]
y = irfft(c)[:N]
y[0] = 0.0
return y
x_n = np.zeros(N, complex)
xgrid = range(N)
for i in range(N):
x_n[i] = psi0(i*L/N)
alpha = dst(np.real(x_n))
eta = dst(np.imag(x_n))
def Psi(t):
k = np.arange(1, N+1)
energy_k = (k**2*np.pi**2*hbar)/(2*m*L**2)
cos, sin = np.cos(energy_k*t), np.sin(energy_k*t)
re_psi = alpha*cos - eta*sin
im_psi = eta*cos + alpha*sin
psi = re_psi + im_psi
return idst(psi)
Psi = Psi(2e-16)
plt.plot(xgrid,Psi)
plt.show()
I'm hoping someone can help.
On the third-to-last line:
Psi = Psi(2e-16)
You are updating the reference to Psi from the function to the return value. Upon doing so, Psi can no longer be used as a function. It is advisory to never use variables with the same names as functions or classes in your code. Solutions are to either rename the variable, or rename the function and function call.

Is it possible to loop to a certain value and carry on further calculations with this value?

I am new here and new in programming, so excuse me if the question is not formulated clearly enough.
For a uni assignment, my labpartner and I are programming a predator-prey system.
In this predator-prey system, there is a certain load factor 'W0'.
We want to find a load factor W0, accurate to 5 significant digits, for which applies that there will never be less than 250 predators (wnum[1] in our code). We want to find this value of W0 and we need the code to carry on further calculations with this found value of W0. Here is what we've tried so far, but python does not seem to give any response:
# Import important stuff and settings
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
print ('Results of Group 4')
def W0():
W0 = 2.0
while any(wnum[1])<250:
W0 = W0-0.0001
return W0
def W(t):
if 0 <= t < 3/12:
Wt = 0
elif 3/12 <= t <= 8/12:
Wt = W0
elif 8/12 < t < 1:
Wt = 0
else:
Wt = W(t - 1)
return Wt
# Define the right-hand-side function
def rhsf(t,y):
y1 = y[0]
y2 = y[1]
f1 = (2-2*10**-3*y2)*y1-W(t)*y1
f2 = (-3.92+7*10**-3*y1)*y2
return np.array([f1,f2])
# Define one step of the RK4 method
def RK4Step(tn,wn,Dt,f):
# tn = current time
# wn = known approximation at time tn
# Dt = the time step to use
# f = the right-hand-side function to use
# wnplus1 = the new approximation at time tn+Dt
k1 = Dt*f(tn,wn)
k2 = Dt*f(tn+0.5*Dt,wn+0.5*k1)
k3 = Dt*f(tn+0.5*Dt,wn+0.5*k2)
k4 = Dt*f(tn+Dt,wn+k3)
wnplus1 = wn + 1/6*(k1 +2*k2 +2*k3 +k4)
return wnplus1
# Define the complete RK4 method
def RK4Method(t0,tend,Dt,f,y0):
# t0 = initial time of simulation
# tend = final time of simulation
# Dt = the time step to use
# f = the right-hand-side function to use
# y0 = the initial values
# calculate the number of time steps to take
N = int(np.round((tend-t0)/Dt))
# make the list of times t which we want the solution
time = np.linspace(t0,tend,num=N+1)
# make sure Dt matches with the number of time steps
Dt = (tend-t0)/N
# Allocate memory for the approximations
# row i represents all values of variable i at all times
# column j represents all values of all variables at time t_j
w = np.zeros((y0.size,N+1))
# store the (given) initial value
w[:,0] = y0
# Perform all time steps
for n,tn in enumerate(time[:-1]):
w[:,n+1] = RK4Step(tn,w[:,n],Dt,f)
return time, w
# Set all known values and settings
t0 = 0.0
tend = 10.0
y0 = np.array([600.0,1000.0])
Dt = 0.5/(2**7)
# Execute the method
tnum, wnum = RK4Method(t0,tend,Dt,rhsf,y0)
# Make a nice table
alldata = np.concatenate(([tnum],wnum),axis=0).transpose()
table = pd.DataFrame(alldata,columns=['t','y1(t)','y2(t)'])
print('\nA nice table of the simulation:\n')
print(table)
# Make a nice picture
plt.close('all')
plt.figure()
plt.plot(tnum,wnum[0,:],label='$y_1$',marker='o',linestyle='-')
plt.plot(tnum,wnum[1,:],label='$y_2$',marker='o',linestyle='-')
plt.xlabel('$t$')
plt.ylabel('$y(t)$')
plt.title('Simulation')
plt.legend()
# Do an error computation
# Execute the method again with a doubled time step
tnum2, wnum2 = RK4Method(t0,tend,2.0*Dt,rhsf,y0)
# Calculate the global truncation errors at the last simulated time
errors = (wnum[:,-1] - wnum2[:,-1])/(2**4-1)
print('\nThe errors are ',errors[0],' for y1 and ',errors[1],' for y2 at time t=',tnum[-1])

Floating RMS in Python

I'm trying to implement a floating window RMS in python. I'm simulating an incoming stream of measurement data by simpling iterating over time and calculating the sine wave. Since it's a perfect sine wave, its easy to compare the results using math. I also added a numpy calculation to confirm my arrays are populated correctly.
However my floating RMS is not returning the right values, unrelated to my sample size.
Code:
import matplotlib.pyplot as plot
import numpy as np
import math
if __name__ == '__main__':
# sine generation
time_array = []
value_array = []
start = 0
end = 6*math.pi
steps = 100000
amplitude = 10
#rms calc
acc_load_current = 0
sample_size = 1000
for time in np.linspace(0, end, steps):
time_array.append(time)
actual_value = amplitude * math.sin(time)
value_array.append(actual_value)
# rms calc
acc_load_current -= (acc_load_current/sample_size)
# square here
sq_value = actual_value * actual_value
acc_load_current += sq_value
# mean and then root here
floating_rms = np.sqrt(acc_load_current/sample_size)
fixed_rms = np.sqrt(np.mean(np.array(value_array)**2))
math_rms = 1/math.sqrt(2) * amplitude
print(floating_rms)
print(fixed_rms)
print(math_rms)
plot.plot(time_array, value_array)
plot.show()
Result:
2.492669969708522
7.071032456438027
7.071067811865475
I solved the issue by usin a recursive average with zero crossing detection:
import matplotlib.pyplot as plot
import numpy as np
import math
def getAvg(prev_avg, x, n):
return (prev_avg * n + x) / (n+1)
if __name__ == '__main__':
# sine generation
time_array = []
value_array = []
used_value_array = []
start = 0
end = 6*math.pi + 0.5
steps = 10000
amplitude = 325
#rms calc
rms_stream = 0
stream_counter = 0
#zero crossing
in_crossing = 0
crossing_counter = 0
crossing_limits = [-5,5]
left_crossing = 0
for time in np.linspace(0, end, steps):
time_array.append(time)
actual_value = amplitude * math.sin(time) + 4 * np.random.rand()
value_array.append(actual_value)
# detect zero crossing, by checking the first time we reach the limits
# and then not counting until we left it again
is_crossing = crossing_limits[0] < actual_value < crossing_limits[1]
# when we are at amp/2 we can be sure the noise is not causing zero crossing
left_crossing = abs(actual_value) > amplitude/2
if is_crossing and not in_crossing:
in_crossing = 1
crossing_counter += 1
elif not is_crossing and in_crossing and left_crossing:
in_crossing = 0
# rms calc
# square here
if 2 <= crossing_counter <= 3:
sq_value = actual_value * actual_value
rms_stream = getAvg(rms_stream, sq_value, stream_counter)
stream_counter += 1
# debugging by recording the used values
used_value_array.append(actual_value)
else:
used_value_array.append(0)
# mean and then root here
stream_rms_sqrt = np.sqrt(rms_stream)
fixed_rms_sqrt = np.sqrt(np.mean(np.array(value_array)**2))
math_rms_sqrt = 1/math.sqrt(2) * amplitude
print(stream_rms_sqrt)
print(fixed_rms_sqrt)
print(math_rms_sqrt)
plot.plot(time_array, value_array, time_array, used_value_array)
plot.show()

Plotting an orbit in Python, but when x<0, trajectory suddenly goes linear

I'm plotting in a simple 2D plane the path taken by a body in motion past another gravitationally attractive body.
Every loop, the time is incremented by a second and the body's new position is calculated and printed out.
I then paste the results into a spreadsheet and graph it.
Things look ok until the body's x component becomes negative - and then the trajectory goes linear, and scoots off to the top left.
This is all for my own solo entertainment, I'm no student. So after scratching my head for a bit I've finally lumped for asking someone for help.
I've probably missed something obvious. I suspect my trigonometry is lacking something.
I'm using Python 2.7.10
import sys
import os
import math
mu = 4.0*(10**14)
massAst = 1
earthRadius = 6371000.
alt = 100000.
r = earthRadius+ alt
rTheta = 270.
rAngtoX = math.radians(rTheta)
tInc = 1 ## increment time by 1 seconds - one update of pos&velocity per second of travel
calcTime = 1100 ## simulation runtime (86400 seconds = 1 day) 10 mins
t = 1 ## integral of time t to use in the calcs in the loop.
printbell = 120 ## print results now
printclock = 0
hourCount = 0
## Initialise velocity vectors for Asteroid:
uAstX = 1500.
uAstY = 0.
vAstX = 0.
vAstY = 0.
## Displacement
dAstX = r*math.cos(rAngtoX)
dAstY = r*math.sin(rAngtoX)
for i in range(0, calcTime):
acc = -1*(mu/r**2)
accX = acc*math.cos(rAngtoX)
accY = acc*math.sin(rAngtoX)
vAstX = uAstX + accX*t ## new value for velocity in X direction
vAstY = uAstY + accY*t ## and in Y
deltaDAstX = uAstX*t + 0.5*accX*(t**2) ## change in position over this time interval
deltaDAstY = uAstY*t + 0.5*accY*(t**2)
dAstX = dAstX + deltaDAstX
dAstY = dAstY + deltaDAstY
uAstX = vAstX
uAstY = vAstY
## Now calculate new angle and range
## tan(theta) = dAstY/dAstX, so:
rAngtoX = math.atan(dAstY/dAstX) ##+(2*3.141592654)
##print 'theta:', math.degrees(rAngtoX)
r = dAstY/math.sin(rAngtoX)
## if i == print
print dAstX, ' ', dAstY
As dAstX approaches zero, dAstY/dAstX will approach a division-by-zero... Which will cause all sorts of problems (roundoff issues at the very least).
I'd recommend keeping the x/y components of distance/velocity/acceleration separate. The distance between the objects is important, of course, but that can be calculated using r=sqrt(dAstX**2 + dAstY**2).
I'm not familiar with the math, but I took your code, modified it to run on my machine, plottet the data using the seaborn library and I came up with this:
This is the code I used:
import math
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
def calc(calcTime):
mu = 4.0*(10**14)
earthRadius = 6371000.0
alt = 100000.0
r = earthRadius+ alt
rTheta = 270.0
rAngtoX = math.radians(rTheta)
t = 1 # integral of time t to use in the calcs in the loop.
# Initialise velocity vectors for Asteroid:
uAstX = 1500.0
uAstY = 0.0
# Displacement
dAstX = r*math.cos(rAngtoX)
dAstY = r*math.sin(rAngtoX)
point_list = []
for i in range(0, calcTime):
acc = -1*(mu/r**2)
accX = acc*math.cos(rAngtoX)
accY = acc*math.sin(rAngtoX)
vAstX = uAstX + accX*t # new value for velocity in X direction
vAstY = uAstY + accY*t # and in Y
deltaDAstX = uAstX*t + 0.5*accX*(t**2) # change in pos over time interval
deltaDAstY = uAstY*t + 0.5*accY*(t**2)
dAstX = dAstX + deltaDAstX
dAstY = dAstY + deltaDAstY
uAstX = vAstX
uAstY = vAstY
# Now calculate new angle and range
# tan(theta) = dAstY/dAstX, so:
rAngtoX = math.atan(dAstY/dAstX) #+(2*3.141592654)
# print 'theta:', math.degrees(rAngtoX)
r = dAstY/math.sin(rAngtoX)
# if i == print
if i % 5 == 0:
print('{:05d} | {:15.2f} | {:15.2f}'.format(i, dAstX, dAstY))
point_list.append((i, dAstX, dAstY))
df = pd.DataFrame(data=point_list, columns=['i', 'x', 'y'])
return df
if __name__ == '__main__':
df = calc(950)
sns.scatterplot(data=df, x='x', y='y')
plt.show()
My analysis: the spaces between dots gets bigger each time (note: I only plottet every 5th point to make the image more readabl,e in my opinion). From a physics perspective, that would indicate that the object is gaining speed.
Isn't it possible that your calculations are correct and that the object is leaving the orbit because it gained enough speed (aka energy) to leave the gravitational field of the center mass (aka the earth) ?
As I said, I'm not familiar with the specific math, but to me it makes sense that the object could break free from the orbit with the speed it is gaining in the half turn.

Categories