I am attempting to solve four different differential equations. After googling and researching I was able to finally understand how the solver works but I can't get this problem specifically to run correctly. Code compiles but the graphs are incorrect.
I think the problem lies in the volume expression inside the function, which will change depending on how much time has passed. That volume at a specific time will then be used to solve the right hand side of the differential equations.
The intervals, starting point and ending point for the time vector is correct. Constants are also correct.
import numpy as np
import scipy.integrate as integrate
import matplotlib.pyplot as plt
#defining all constants and initial conditions
k=2.2
CB0_inlet=.025
V_flow_inlet=.05
V_reactor_initial=5
CA0_reactor=.05
FB0=CB0_inlet*V_flow_inlet
def dcdt(C,t):
#expression of how volume in reactor varies with time
V=V_flow_inlet*t+C[4] #C[4] is the initial reactor volume ###we dont need things C to be C0 correct?
#calculating right hand side of the four differential equations
dadt=-k*C[0]*C[1]-((V_flow_inlet*C[0])/V)
dbdt=((V_flow_inlet*(CB0_inlet-C[1]))/V)-k*C[0]*C[1]
dcdt=k*C[0]*C[1]-((V_flow_inlet*C[2])/V)
dddt=k*C[0]*C[1]-((V_flow_inlet*C[3])/V)
return [dadt,dbdt,dcdt,dddt,V]
#creating time array, initial conditions array, and calling odeint
t=np.linspace(0,500,100)
initial_conditions=[.05,0,0,0,V_reactor_initial] # [CA0 CB0 CC0 CD0
#V0_reactor]
C=integrate.odeint(dcdt,initial_conditions,t)
plt.plot(t,C)
Taking the hints of the variable names and equation structure, you are considering a chemical reaction
A + B -> C + D
There are 2 sources of changes in the concentration a,b,c,d of reactants A,B,C,D,
the reaction itself with reaction speed k*a*b and
the inflow of reactant B in a solution with concentration b0_in and volume rate V_in, which results in a relative concentration change of V_in/V in all components and an addition of V_in*b0_in/V in B.
This is all well reflected in the first 4 equations of your system. In the treatment of the volume, you are mixing two approaches in an inconsistent way. Either V is a known function of t and thus not a component of the state vector, then
V = V_reactor_initial + V_flow_inlet * t
or you treat it as a component of the state, then the current volume is
V = C[4]
and the rate of volume change is
dVdt = V_flow_inlet.
Modifying your code for the second approach looks like
import numpy as np
import scipy.integrate as integrate
import matplotlib.pyplot as plt
#defining all constants and initial conditions
k=2.2
CB0_inlet=.025
V_flow_inlet=.05
V_reactor_initial=5
CA0_reactor=.05
FB0=CB0_inlet*V_flow_inlet
def dcdt(C,t):
#expression of how volume in reactor varies with time
a,b,c,d,V = C
#calculating right hand side of the four differential equations
dadt=-k*a*b-(V_flow_inlet/V)*a
dbdt=-k*a*b+(V_flow_inlet/V)*(CB0_inlet-b)
dcdt= k*a*b-(V_flow_inlet/V)*c
dddt= k*a*b-(V_flow_inlet/V)*d
return [dadt,dbdt,dcdt,dddt,V_flow_inlet]
#creating time array, initial conditions array, and calling odeint
t=np.linspace(0,500,100)
initial_conditions=[.05,0,0,0,V_reactor_initial] # [CA0 CB0 CC0 CD0
#V0_reactor]
C=integrate.odeint(dcdt,initial_conditions,t)
plt.plot(t,C[:,0:4])
with the result
Related
I'm trying to simulate the 2D Schrödinger equation using the explicit algorithm proposed by Askar and Cakmak (1977). I define a 100x100 grid with a complex function u+iv, null at the boundaries. The problem is, after just a few iterations the absolute value of the complex function explodes near the boundaries.
I post here the code so, if interested, you can check it:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
#Initialization+meshgrid
Ntsteps=30
dx=0.1
dt=0.005
alpha=dt/(2*dx**2)
x=np.arange(0,10,dx)
y=np.arange(0,10,dx)
X,Y=np.meshgrid(x,y)
#Initial Gaussian wavepacket centered in (5,5)
vargaussx=1.
vargaussy=1.
kx=10
ky=10
upre=np.zeros((100,100))
ucopy=np.zeros((100,100))
u=(np.exp(-(X-5)**2/(2*vargaussx**2)-(Y-5)**2/(2*vargaussy**2))/(2*np.pi*(vargaussx*vargaussy)**2))*np.cos(kx*X+ky*Y)
vpre=np.zeros((100,100))
vcopy=np.zeros((100,100))
v=(np.exp(-(X-5)**2/(2*vargaussx**2)-(Y-5)**2/(2*vargaussy**2))/(2*np.pi*(vargaussx*vargaussy)**2))*np.sin(kx*X+ky*Y)
#For the simple scenario, null potential
V=np.zeros((100,100))
#Boundary conditions
u[0,:]=0
u[:,0]=0
u[99,:]=0
u[:,99]=0
v[0,:]=0
v[:,0]=0
v[99,:]=0
v[:,99]=0
#Evolution with Askar-Cakmak algorithm
for n in range(1,Ntsteps):
upre=np.copy(ucopy)
vpre=np.copy(vcopy)
ucopy=np.copy(u)
vcopy=np.copy(v)
#For the first iteration, simple Euler method: without this I cannot have the two steps backwards wavefunction at the second iteration
#I use ucopy to make sure that for example u[i,j] is calculated not using the already modified version of u[i-1,j] and u[i,j-1]
if(n==1):
upre=np.copy(ucopy)
vpre=np.copy(vcopy)
for i in range(1,len(x)-1):
for j in range(1,len(y)-1):
u[i,j]=upre[i,j]+2*((4*alpha+V[i,j]*dt)*vcopy[i,j]-alpha*(vcopy[i+1,j]+vcopy[i-1,j]+vcopy[i,j+1]+vcopy[i,j-1]))
v[i,j]=vpre[i,j]-2*((4*alpha+V[i,j]*dt)*ucopy[i,j]-alpha*(ucopy[i+1,j]+ucopy[i-1,j]+ucopy[i,j+1]+ucopy[i,j-1]))
#Calculate absolute value and plot
abspsi=np.sqrt(np.square(u)+np.square(v))
fig=plt.figure()
ax=fig.add_subplot(projection='3d')
surf=ax.plot_surface(X,Y,abspsi)
plt.show()
As you can see the code is extremely simple: I cannot see where this error is coming from (I don't think is a stability problem because alpha<1/2). Have you ever encountered anything similar in your past simulations?
I'd try setting your dt to a smaller value (e.g. 0.001) and increase the number of integration steps (e.g fivefold).
The wavefunction looks in shape also at Ntsteps=150 and well beyond when trying out your code with dt=0.001.
Checking integrals of the motion (e.g. kinetic energy here?) should also confirm that things are going OK (or not) for different choices of dt.
Consider an optimal control problem as follows:
min int(0,1) x(t).u(t)+u(t)**2
x.dt()= x(t)+u(t), x(0)=1
0 <= u(t) <= t**2+(1-t)**3 for 0<=t<=1
My first question is how to define the upper bound of control in Gekko. Also, suppose we want to compare this problem with the case the control is constant during the planning horizon, i.e., u(0)=...=u(t)=...=u(1). How can we define it?
In another case, how is it possible to have fixed but unknown control in different sub-intervals? For example, in [0,t1], control should be fixed, in [t1,t2] control should be fixed but can be different from control in [0,t1] (e.g. t1=0.5, t2=1, Tf=t2=1).
I would be thankful to know if it is possible to study a case where t1, t2, ... are also control and should be determined?
Here is code that gives a solution to the problem:
# min int(0,1) x(t).u(t)+u(t)**2
# x.dt()= x(t)+u(t), x(0)=1
# 0 <= u(t) <= t**2+(1-t)**3 for 0<=t<=1
import numpy as np
from gekko import GEKKO
m = GEKKO(remote=False); m.time=np.linspace(0,1,101)
t = m.Var(0); m.Equation(t.dt()==1)
ub = m.Intermediate(t**2+(1-t)**3)
u = m.MV(0,lb=0,ub=1); m.Equation(u<=ub)
u.STATUS=1; u.DCOST=0; m.free_initial(u)
x = m.Var(0); m.Equation(x.dt()==x+u)
p = np.zeros(101); p[-1]=1; final=m.Param(p)
m.Minimize(m.integral(x*u+u**2)*final)
m.options.IMODE=6; m.options.NODES=3; m.solve()
print(m.options.OBJFCNVAL)
import matplotlib.pyplot as plt
plt.plot(m.time,x.value,'b--',label='x')
plt.plot(m.time,u.value,'k-',label='u')
plt.plot(m.time,ub.value,'r--',label='ub')
plt.legend()
plt.show()
The solution isn't very interesting because the optimal objective is u(t)=0 and x(t)=0. If you add a final condition like x(1)=0.75 then the solution is more interesting.
m.Equation(final*(x-0.75)==0)
If you want all of the interval to be one value then I recommend that you use a u=m.FV() type. The u=m.MV() type is adjustable by the optimizer at every interval when you set u.STATUS=1. You can also reduce degrees of freedom with the m.options.MV_STEP_HOR=5 as a global option in Gekko for all MVs or else u.MV_STEP_HOR=5 to adjust it just for that MV. There is more information on the different Gekko types.
You can set the final time by using m.time = [0,...,1] and then scale it with time final tf. The derivatives in your problem need to be divided by tf as well. Here is a related rocket launch problem or the Jennings optimal control problem that minimize the final time. You can also set up multiple time intervals and then connect them with m.Connection().
I am trying to use "shooting method" for solving Schrodinger's equation for a reasonably arbitrary potential in 1D. But the eigenvalues so evaluated in the case of potentials which do not have hard boundaries(where potential becomes infinite) are not much accurate compared to analytical results.
I thought that the problem could be solved,by making the spatial grid finer,but changing the spatial grid does not practically have any effect on the eigenvalues.I am not making the energy-grid finer,because the job of fining down to the correct eigenvalue is tackled by bisection method from scipy,and the wavefunction is evaluated by solving the concerned ivp by odeint from scipy-these functions are accurate enough.
Finally,changing the 2nd boundary to make the wavefunction die out at deeper part of classically forbidden region also did not bring a practical improvement in the eigenvalue(Changes found only in 9th or 10th place of decimal but made wavefunctions of lower energy state divergent at endpoints to make things worse).
I cannot find what to modify to obtain more accurate eigenvalues.Boundary condition or stepsize? Did my implementation go wrong,or is it due to rounding errors or other "Python things"?
Example--Morse potential(https://en.wikipedia.org/wiki/Morse_potential)
import numpy as np
from scipy.integrate import odeint,simps
from scipy.optimize import bisect
from math import e,floor
xe,lam=1.0,6.0 # parameters for potential
def V(x):return (lam**2)*(e**(-2*(x-xe))-2*(e**-(x-xe))) # morse potential definition
bound1,bound2,bval1,bval2=0,xe+15,0,0 # Bval,Bval2=wavefunction values at x=bound1,bound2
X=np.linspace(bound1,bound2,1000) # region of integration
Erange=np.geomspace(-(lam**2),-0.0001,100) # region of Energy level searching
def func(y,x): # utility function for returning RHS of the differential equation
psi,phi=y # psi=eigenfunction,phi=spatial derivative of psi
return np.array([phi,-(E-V(x))*psi])
def ivp(f,initial1,initial2,X): # solves an ivp with odeint
y0=np.array([initial1,initial2])
return odeint(f,y0,X)[:,0]
def psiboundval(E1): # finds out value of eigenfunction at bound2 for energy E1,by solving ivp
global E;E=E1
S=ivp(func,bval1,E1,X)
return S[(len(S))-1]-bval2
def shoot(Erange): # finds out accurate eigenvalues from approximate ones in Erange by bisect
global E
Y=np.array([psiboundval(E) for E in Erange])
eigval=np.array([bisect(psiboundval,Erange[i],Erange[i+1]) for i in np.where(np.diff(np.signbit(Y)))[0]])
return eigval
print "Numerical results:",shoot(Erange)
print "Analytical results:",[-(lam-n-0.5)**2 for n in range(0,int(floor(lam-0.5)+1))]
Output
>>> Numerical results: [-30.24833663 -20.24320174 -12.23610971 -6.23177902 -2.23431356
-0.24381032]
Analytical results: [-30.25, -20.25, -12.25, -6.25, -2.25, -0.25]
For higher energy states,accuracy is seen to decrease.It is desirable that accuracy is of atleast upto 4th decimal place(if not more), for all states.
The pendulum bob has a mass m and a positive electric charge q. The capacitor plates are parallel to the surface of the Earth. The electric field inside the capacitor is directed vertically up and its magnitude is modulated by the pendulum motion as E(t) = E_0*|sin(θ)(t)where qE_0/mg=𝛿<1 and θ is the angle between the pendulum and the vertical line. The initial conditions are θ(0) = pi/2 rad and dθ/dt = 0 rad/s.
Let L = 1.0m and g = 9.8 m/s^2
(a) First, taking 𝛿 = 0, estimate at which initial angle θ(0) the numerically obtained period is equal to the period predicted by the formula T = 2pi*sqrt(L/g) with the accuracy better than 1%.
(b) Find and plot the dependence of the period of oscillations of this pendulum on the parameter 𝛿.
(c) What will happen with the pendulum if 𝛿 = 1?
So for a) this is what I did
#import
%pylab nbagg
import numpy as np
from scipy.integrate import odeint
#Solve for T first
#let the length L be equal to 1
L = 1
g=9.8
T = 2*np.pi*np.sqrt(g)
print(T)
#let delta = d
#if d = 0 then the ODE becomes
#Define the ODE
w_0 = np.sqrt(g/L)
def dy_dt(y,t):
y1 = y
y2 = -w_0**2*sin(y1)
dydt = (y1,y2)
return dydt
#Integration values and interval
t_0 = 0
t_f = T
nt = 10000
t = linspace(t_0, t_f, nt)
Now I'm not sure how I can proceed as I am trying to solve for dθ/dt but it's given that dθ/dt is just 0.
So, I've looked this over a few times. It seems like a pretty fun problem, but I don't think that StackOverflow is the correct place for help just yet. I see that you're new to this site, so I'd like to encourage you to break this up into multiple questions and direct them to the appropriate groups.
Since your code is actually working at this point in time, StackOverflow isn't quite the right venue for this question. I would recommend seeking help on the physics stack exchange for any questions relating to the physics behind this problem (such as how to set up the system of differential equations you need), and the computational science stack exchange site for questions on how to solve the differential equations you get numerically.
If you have issues with getting the code itself to work when you are further along in the problem, StackOverflow is definitely the best place to ask for help.
There seem to be many questions addressing pendulum modelling and odeint. I believe this question is specific enough to stand by itself. It's concerned with passing a time array to odeint.
I'm modelling a driven, damped pendulum. I expect transient behavior to die off after some number of periods and have been plotting my angular velocity vs time to observe this. My problem is that varying the number of periods does not seem to provide consistent results. I don't see where the code or my assumptions fail.
from numpy import *
from scipy.integrate import odeint
import matplotlib.pyplot as plt
#pendulum diff eq
def pendulum(y,t,b,gamma,drivefreq):
phi,omega=y
dydt = [omega,-b*omega - sin(phi) + g*cos(drivefreq*t)]
return dydt
#pendulum parameters: dampening, force amplitude, drivefreq
b=0.05; g=0.4; drivefreq=0.7
args=(b,g,drivefreq)
#num pts per period, num periods, time array
N=256; nT=200;
t=linspace(0,nT*2*pi/drivefreq,nT*N)
Is the above line problematic? Is it bad form to use non-integral values here? Linspace should still give a constantly spaced array. I've seen other examples do this with success... My idea was to base the time off of the driving period and set some number, 256, points per period. Is this faulty?
#initial conditions
y0= [0,0] #[phi0,omega0]
#run odeint
out=odeint(pendulum, y0,t,args)
omega = out[:,1]
#plot ang velocity vs time
fig=plt.figure('ang velocity vs time')
plt.plot(t,omega)
Below are plots for number of periods (nT) equalling 140,180,and 200. I'd expect to see the continuation of the same behavior, but instead the 180 period result doesn't lose its transient and the 200 result reaches steady state behavior the most quickly! Where is the fault in my logic?
You have a Lipschitz constant of about L=1, which gives an error magnification factor of exp(L*dT)=exp(dT) for time differences dT. Only considering the normal numerical noise of about 1e-16 it only needs dT=37 to magnify this initial error to a contribution of about 1, as exp(37)*1e-16 = 1.17.
As you see, over the time span from 0 to 1200 or larger, even the slightest variation in the execution of the algorithm will lead to seemingly random variations in the trajectory. You only have the guarantee of at least graphical similarity under these procedural variations for a time span from 0 to about 30.