How would I code the equation and the initial and boundary conditions of the problem below? I did them by hand but I'm unsure of how to code them. I also attached the code I have so far. I know what I coded below is incorrect, but it is somewhat similar.
enter image description here
enter image description here
import numpy as np
import matplotlib.pyplot as plt
# Constants
r1 = 0.06 # Inner radius (m)
r2 = 0.15 # Outer radius (m)
r = r2-r1
a1 = 45.4*10**-6 # Inner thermal diffusivity (m^2/s)
a2 = 101.2*10**-6 # Outer thermal diffusivity (m^2/s)
a = (a1+a2)/2
T1 = 303 # Inner uniform temperature (K), where t = 0 s
T2 = 393 # Outer uniform temperature (K), where t = 0 s
tt = 200 # Total time (s)
dr = 0.001 # (m)
dt = 0.05 # (s)
tol = 0.1 # Absolute tolerance (K)
lam = a*dt/dr/dr # Parameter for convergence rate
print('lambda = % f ' %(lam));
nt = int(tt/dt)+1 # Number of time segments
nr = int(r/dr)+1
rr = np.linspace(0,r,num=nr,endpoint=True)
nn = nr-1 # One fixed boundary
# Create tridiagonal matrix
Ea = np.ones(nn)
Eb = np.ones(nn-1)
# Implicit euler
IE = np.diagflat(-lam*Eb,k=-1) \
+ np.diagflat((1+2*lam)*Ea,k=0) \
+ np.diagflat(-lam*Eb,k=1);
IE[-1,-2] = -2*lam;
# Vector of constants via BCs
ie = np.zeros(nn);
ie[0] = -lam*T1;
# Space and time matrix for T
M = T1*np.ones(nn); # Initial condition
# Implicit
MM_ie = np.zeros((nn,nt))
MM_ie[:,0] = M
# Solve inverse mats here
IE_inv = np.linalg.inv(IE)
for i in range(0,nt-1):
MM_ie[:,i+1] = np.matmul(IE_inv,MM_ie[:,i]-ie);
wa = T1*np.ones((1,nt));
MM_ie = np.concatenate((wa,MM_ie))
def show_plot(time) :
tind = int(time/dt)
print("time index: %d" % tind)
print(MM_ie.shape)
fig = plt.figure()
ax = plt.axes(xlim=(0, r), ylim=(0, 2*T1))
ax.plot(rr, MM_ie[:,tind], lw=2)
time_template = 'time = %4.2f s'
time_text = ax.text(0.75, 0.90,time_template % time,transform=ax.transAxes)
plt.xlabel('Radius (m)')
plt.ylabel('Temperature, K')
plt.title('Temperature Profile')
plt.show()
show_plot(1)
show_plot(10)
show_plot(30)
show_plot(70)
show_plot(200)
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
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()
Whilst running a simulation of a discrete-time system, I'm having trouble getting the equivalent scalar expressions of the original matrix equation to generate identical output (I need to convert all matrix expressions to scalars in order to implement the code in my embedded C project).
Please run the function with the following input parameters.
torque_sim_ctest(0.006,0,0.01,12)
Click the hyperlinks to see screen-shots of the expected output computed using the matrix expression (correct) vs. the output using the (supposedly) equivalent scalar expressions (incorrect).
Below is the code, which should give the correct output by default. To test the scalar computations, please comment line # 98 (qo_next = np.dot(Ado,qo) + np.dot(Bo,np.vstack([hallf, v[:,k]]))), and uncomment 100 through 102, run the script, and then call the function with the same parameters as shown above.
Lines to uncomment in tandem with the qo_next = ... matrix expression highlighted above:
# qo_next[0] = Ado[0,0]*qo[0] + Ado[0,1]*qo[1] + Ado[0,2]*qo[2] + Bo[0,0]*hallf
# qo_next[1] = Ado[1,0]*qo[0] + Ado[1,1]*qo[1] + Ado[1,2]*qo[2] + Bo[1,0]*hallf + Bo[1,1]*v[:,k]
# qo_next[2] = Ado[2,0]*qo[0] + Ado[2,1]*qo[1] + Ado[2,2]*qo[2] + Bo[2,0]*hallf
I would really appreciate it if someone could point out the bug in the scalar version of the code and help me fix this (numerical?) issue. Thank you!
import numpy as np
import scipy.signal as sig
import matplotlib.pyplot as plt
def torque_sim_ctest(K,Kp,Kd,tau_ampl):
# Example parameter values: torque_sim_ctest(0.006,0,0.01,12)
# -----------------------------------------------------------------------------
# Motor & System Parameters in continuous time:
bm, Jm, K_e, K_t = 1.5e-6, 3.507e-6, 12.5e-3, 12.5e-3
R, L = 0.476, 0.334e-3
gr, Jl, bl = 16.348, 10, 20
Jeq, beq = Jl + Jm*gr**2, bl + bm*gr**2
A = np.array([ [ 0, 1 ],
[ 0, -beq/Jeq ] ])
Ac = np.array( [[ 0, 1, 0 ],
[ 0, 0, 1 ],
[ 0, 0, 0 ]])
B = np.array([ [ 0, 0 ],
[ gr/Jeq, gr**2*K_t/(Jeq*R) ]])
Bc = np.array([ 0, 1, 0 ]).reshape(3,1)
C = np.array([ 1, 0 ])
Co = np.array([ 1, 0, 0 ]).reshape(1,3)
# -----------------------------------------------------------------------------
# Controller update interval Tc and simulation time step Td
Tc = 0.01
Td = 0.1*Tc
# Now discretise the continuous-time system model
Ad, Bd, Cd, Dd, dt = sig.cont2discrete((A,B,C,0),Td,method='bilinear')
# Output smoothing: filter parameters
f_ord = 2
num, den = sig.cheby1(f_ord,5,2*np.pi*10,'low',analog=True)
Af, Bf, Cf, Df = sig.tf2ss(num,den)
Afd, Bfd, Cfd, Dfd, dt = sig.cont2discrete((Af,Bf,Cf,Df),Tc,
method='bilinear')
# -----------------------------------------------------------------------------
# Observer Parameters
eps = 0.01
alph = Tc/eps
a1, a2, a3 = 3, 3, 1
Ho = np.array([a1, a2, a3]).reshape(3,1)
Ao = Ac - np.dot(Ho,Co)
D = np.diag([1, eps, eps**2])
Do = np.zeros([3,1])
Ado, Bdo, Cdo, Ddo, dt = sig.cont2discrete((Ao,Ho,np.linalg.inv(D),Do),
alph,method='bilinear')
Bo = np.hstack([Bdo.reshape(3,1), Bc.reshape(3,1)])
# -----------------------------------------------------------------------------
# Total simulation steps
n = 5001
# Define initial conditions prior to running simulation
x, q = np.zeros([2,n]), np.zeros([2,1])
q_next = np.zeros([2,1])
y, v = 0, np.zeros([1,n])
tau_ext = np.zeros([1,n])
hallc = 0
qf, qf_next, hallf = np.zeros([f_ord,1]), np.zeros([f_ord,1]), 0
qo, qo_next, xhat = np.zeros([3,1]), np.zeros([3,1]), np.zeros([3,1])
# The for loop below simulates the closed-loop system in discrete time steps Td
print('Simulation running; please wait ...')
for k in range(n-1):
# External torque input with peaks at 0.5 s and 1.6 s
tau_ext[:,k] = tau_ampl*(0.5*np.exp(-10*(k*Td-0.5)**2) \
+ np.exp(-5*(k*Td-1.6)**2))
# y is the output angle in SI units (radians)
y = gr*np.dot(C,x[:,k])
# Conversion of rotor angle to ideal hall-count
hallc = int(150/np.pi*y+0.5*np.sign(y))
# Now apply a smoothing filter
# qf_next = Afd # qf + Bfd # np.matrix([hallc[:,k]/gr])
# hallf = Cf # (qf + qf_next)
qf_next[0] = 0.661939208333454*qf[0] - 19.836230603818*qf[1] + 0.00830969604166727*hallc/gr
qf_next[1] = 0.00830969604166727*qf[0] + 0.90081884698091*qf[1] + 4.15484802083364e-5*hallc/gr
# Filtered hall-count
hallf = 1342.37547903*(qf[1] + qf_next[1])
# -----------------------------------------------------------------------------
# State Observer & Control Law
# Update the controller states only every Tc = 10*Td time step
if np.mod(k,int(Tc/Td+0.5)) == 0:
qo_next = np.dot(Ado,qo) + np.dot(Bo,np.vstack([hallf, v[:,k]]))
# qo_next[0] = Ado[0,0]*qo[0] + Ado[0,1]*qo[1] + Ado[0,2]*qo[2] + Bo[0,0]*hallf
# qo_next[1] = Ado[1,0]*qo[0] + Ado[1,1]*qo[1] + Ado[1,2]*qo[2] + Bo[1,0]*hallf + Bo[1,1]*v[:,k]
# qo_next[2] = Ado[2,0]*qo[0] + Ado[2,1]*qo[1] + Ado[2,2]*qo[2] + Bo[2,0]*hallf
# qo_next[0] = 1.40740740740741*hallf - 0.407407407407407*qo[0] + 0.296296296296296*qo[1] + 0.148148148148148*qo[2]
# qo_next[1] = 1.03703703703704*hallf - 1.03703703703704*qo[0] + 0.481481481481481*qo[1] + 0.740740740740741*qo[2] + v[:,k]
# qo_next[2] = 0.296296296296296*hallf - 0.296296296296296*qo[0] - 0.148148148148148*qo[1] + 0.925925925925926*qo[2]
xhat[0] = 0.703703703703704*hallf + 0.296296296296296*qo[0] + 0.148148148148148*qo[1] + 0.0740740740740741*qo[2]
xhat[1] = 51.8518518518519*hallf - 51.8518518518518*qo[0] + 74.0740740740741*qo[1] + 37.037037037037*qo[2]
xhat[2] = 1481.48148148148*hallf - 1481.48148148148*qo[0] - 740.740740740741*qo[1] + 9629.62962962963*qo[2]
# xhat = Cdo # qo + Ddo # np.matrix(hallf)
# Update the control voltage v[:,k]
v[:,k] = K*xhat[2] + Kp*hallf + Kd*xhat[1]
if v[:,k] < 0:
v[:,k] = 0
elif v[:,k] >= 12:
v[:,k] = 12
else:
# Wait and hold; don't update in this cycle
qo_next = qo
v[:,k] = v[:,k-1]
# -----------------------------------------------------------------------------
# Now calculate the system dynamics in discrete time steps Td
q_next = np.dot(Ad,q) + np.dot(Bd,np.vstack([tau_ext[:,k], v[:,k]]))
x[:,k+1:k+2] = q + q_next
q = q_next
qf[0] = qf_next[0]
qf[1] = qf_next[1]
qo[0] = qo_next[0]
qo[1] = qo_next[1]
qo[2] = qo_next[2]
# ** End For Loop **
# Plot the simulation output
# NOTE: System velocity signal is x[1,:], and position is y = x[0,:]
print('Done.')
tau_spr = gr*K_t/R*v[0,0:n-1]
tau_net = tau_spr + tau_ext[0,0:n-1]
t = np.linspace(0.0,(n-1)*Td,num=n-1)
fig1, ax1 = plt.subplots(2,2)
ax1[0,0].plot(t,tau_ext[0,0:n-1],'r--',t,tau_spr,'b-')
ax1[0,0].set_xlabel('Time (s)')
ax1[0,0].set_ylabel('Torque (N m)')
ax1[0,0].set_title('Torque Comparison')
ax1[0,1].plot(t,tau_ext[0,0:n-1]/tau_net,'r--',t,tau_spr/tau_net,'b-')
ax1[0,1].set_xlabel('Time (s)')
ax1[0,1].set_ylabel('Torque (Normalised)')
ax1[0,1].set_title('Torque Comparison')
ax1[1,0].step(t,v[0,0:n-1],'g-')
ax1[1,0].set_xlabel('Time (s)')
ax1[1,0].set_ylabel('Drive Voltage (V)')
ax1[1,0].set_title('Motor Voltage')
ax1[1,1].step(t,30/np.pi*x[1,0:n-1],'m-')
ax1[1,1].set_xlabel('Time (s)')
ax1[1,1].set_ylabel('Motor Speed (rpm)')
ax1[1,1].set_title('Motor Speed vs Time')
Phew, I may have finally got to the bottom of my issue, even though I don't fully understand the nature of the problem with array vs. scalar assignments.
When I use the vector computation: qo_next = np.dot(Ado,qo) + np.dot(Bo,np.vstack([hallf, v[:,k]])), then my implementation of the Zero-Order-Hold further down: qo_next = qo appears to work as one would expect.
When I convert the first assignment to a set of scalar expressions, however, it appears I must also do an element-by-element assignment to implement the Z.O.H., i.e.:
qo_next[0] = qo[0]
qo_next[1] = qo[1]
qo_next[2] = qo[2]
When I do that, the scalar conversion magically works and generates output equivalent to the vector version.
That was a real head-scratcher -- perhaps someone can explain to me how assignment works in Python so I can make sense of this. Thank you for reading.