I'm trying to solve a diffusion problem with reflecting boundaries, using various SDE integrators from DifferentialEquations.jl. I thought I could use the FunctionCallingCallback to handle the boundaries, by reflecting the solution about the domain boundaries after every integrator step.
This is my code
using DifferentialEquations
K0 = 1e-3
K1 = 5e-3
alpha = 0.5
K(z) = K0 + z*K1*exp(-z*alpha)
dKdz(z) = K1*exp(-alpha*z) - K1*alpha*z*exp(-alpha*z)
a(z,p,t) = dKdz(z)
b(z,p,t) = sqrt(2*K(z))
dt = 0.1
tspan = (0.0,1.0)
z0 = 1.0
prob = SDEProblem(a,b,z0,tspan)
function reflect(z, p, t, integrator)
bottom = 2.0
if z < 0
# Reflect from surface
z = -z
elseif z > bottom
# Reflect from bottom
z = 2*bottom - z
end
return z
end
cb = FunctionCallingCallback(reflect;
func_everystep = true,
func_start = true,
tdir=1)
sol = solve(prob, EM(), dt = dt, callback = cb)
Edit: After solving my initial problem thanks to the comment by Chris Rackauckas, I modified my reflect function. Now the code runs, but the solution contains negative values, which should have been prevented by reflection about 0 after every step.
Any ideas as to what's going wrong here would be greatly appreciated.
Note by the way that the FunctionCallingCallback example found here contains two different function signatures for the callback function, but I get the same problem with both. It's also not clear to me if the callback should modify the value of z in place, or return the new value.
Edit 2:
Based on Chris Rackauckas' answer, and looking at this example, I've modified by reflect function thus:
function reflect(z, t, integrator)
bottom = 2.0
if integrator.u < 0
# Reflect from surface
integrator.u = -integrator.u
elseif integrator.u > bottom
# Reflect from bottom
integrator.u = 2*bottom - integrator.u
end
# Not sure if the return statement is required
return integrator.u
end
Running this with initial condition z0 = -0.1 produces the following output:
retcode: Success
Interpolation: 1st order linear
t: 11-element Array{Float64,1}:
0.0
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
1.0
u: 11-element Array{Float64,1}:
-0.1
-0.08855333388147717
0.09862543518953905
0.09412012313587219
0.11409372573454478
0.10316400521980074
0.06491042188420941
0.045042097789392624
0.040565317051189105
0.06787136817395374
0.055880083559589955
It seems to me that what's happening here is:
The first output value is just z0. I expected the reflection to be applied first, given that I set func_start = true.
The second value also being negative indicates two things:
The callback was not called prior to the first integrator call.
The callback was not called prior to storing the output after the first integrator call.
I would have expected all the values in the output to be positive (i.e., have the callback applied to them before storing the output). Am I doing something wrong, or should I simply adjust my expectations?
The FunctionCallingCallback is a function (u,t,integrator), so I'm not sure how your code didn't error for you. It should be:
using DifferentialEquations
K0 = 1e-3
K1 = 5e-3
alpha = 0.5
K(z) = K0 + z*K1*exp(-z*alpha)
dKdz(z) = K1*exp(-alpha*z) - K1*alpha*z*exp(-alpha*z)
a(z,p,t) = dKdz(z)
b(z,p,t) = sqrt(2*K(z))
dt = 0.1
tspan = (0.0,1.0)
z0 = 1.0
prob = SDEProblem(a,b,z0,tspan)
function reflect(z, t, integrator)
bottom = 2.0
if z < 0
# Reflect from surface
z = -z
elseif z > bottom
# Reflect from bottom
z = 2*bottom - z
end
return z
end
cb = FunctionCallingCallback(reflect;
func_everystep = true,
func_start = true,
tdir=1)
sol = solve(prob, EM(), dt = dt, callback = cb)
Edit
You don't want the function calling callback. Just use the normal callback:
using DifferentialEquations
K0 = 1e-3
K1 = 5e-3
alpha = 0.5
K(z) = K0 + z*K1*exp(-z*alpha)
dKdz(z) = K1*exp(-alpha*z) - K1*alpha*z*exp(-alpha*z)
a(z,p,t) = dKdz(z)
b(z,p,t) = sqrt(2*K(z))
dt = 0.1
tspan = (0.0,1.0)
z0 = 1.0
prob = SDEProblem(a,b,z0,tspan)
condition(u,t,integrator) = true
function affect!(integrator)
bottom = 2.0
if integrator.u < 0
# Reflect from surface
integrator.u = -integrator.u
elseif integrator.u > bottom
# Reflect from bottom
integrator.u = 2*bottom - integrator.u
end
end
cb = DiscreteCallback(condition,affect!;save_positions=(false,false))
sol = solve(prob, EM(), dt = dt, callback = cb)
Related
I have a working Matlab algorithm for the evolution of a pulse via solving the 1D wave equation. I know the code works and I can see the pulse traveling to the sides and bouncing off the walls, when plotting; but when I translate the corresponding steps as Julia code, however, the results are completely different for the same set of inputs. I insert a simple pulse that should propagate across a string in both directions but that isn't exactly the result that I'm getting in Julia... and I don't know why.
Could anybody help point out what I'm doing wrong?
Thank you,
This is the corresponding Julia code:
using Plots,OhMyREPL
#-solving a PDE.
#NOTE: DOMAIN
# space
Lx = 20
dx = 0.1
nx = Int(trunc(Lx/dx))
x = range(0,Lx,nx)#0:dx:Lx-1
#NOTE: TIME
T = 10
#NOTE: Field variable.
#-variables.
wn = zeros(nx)#-current value.
wnm1 = wn#zeros(nx)#-w at time n-1.
wnp1 = wn#zeros(nx)#-w at time n+1.
# wnp1[1:end] = wn[1:end]#-w at time n+1.
#NOTE: PARAMETERS.
CFL = 1#-CFL = c.dt/dx
c = 1#-the propagation speed.
dt = CFL * dx/c
# #NOTE: Initial Conditions.
wn[49] = 0.1; wn[50] = 0.2; wn[51] = 0.1
wnp1[1:end] = wn[1:end]#--assumption that they are being equaled.
wnp1 = wn#--assumption that they are being equaled.
run = true
if run == true
#NOTE: Initial Conditions.
wn[49] = 0.1; wn[50] = 0.2; wn[51] = 0.1
wnp1 = wn#--wnp1[1:end] = wn[1:end]#--assumption that they are being equaled.
# NOTE: Time stepping loop.
t = 0
while t<T
#-apply reflecting boundary conditions.
wn[1]=0.0;wn[end]=0.0
#NOTE: solution::
global t = t+dt
global wnm1 = wn; global wn = wnp1#-save CURRENT and PREVIOUS arrays.
for i=2:nx-1
global wnp1[i] = 2*wn[i] - wnm1[i] + CFL^2 * ( wn[i+1] -2*wn[i] + wn[i-1] )
end
end
plot(x,wnp1, yrange=[-0.2,0.2])
end
Only one peak
This is the corresponding Matlab code:
%Domain
%Space.
Lx = 20;
dx = 0.1;
nx = fix(Lx/dx);
x = linspace(0,Lx,nx);
%Time
T = 10;
%Field Variables
wn = zeros(nx,1);
wnm1 = wn;
wnp1 = wn;
%Parameters...
CFL = 1;
c = 1;
dt = CFL*dx/c;
%Initial conditions
wn(49:51) = [0.1 0.2 0.1];
wnp1(:) = wn(:);
run = true;
if run == true
%Initial conditions
wn(49:51) = [0.1 0.2 0.1];
wnp1(:) = wn(:);
%Time stepping loop
t = 0;
while t<T
%Reflecting boundary conditions:
wn([1 end]) = 0.0;
%Solution:
t=t+dt;
wnm1=wn;wn = wnp1;%-save current and previous arrays.
for i=2:nx-1
wnp1(i) = 2*wn(i) - wnm1(i) + CFL^2 * ( wn(i+1) - 2*wn(i) + wn(i-1) );
endfor
end
plot(x,wnp1)
end
Two peaks, as it should be.
If anybody could point me in the right direction, I'd greatly appreciate it.
Julia arrays are designed to be fast. So, when an array is assigned to another array, the default behaviour is make the two arrays the same - same variable address, occupying the same area of memory. Editing one array does the exact same edit on the other array:
x = [1,2,3]
y = x
x[1] = 7
# x is [7,2,3]
# y is [7,2,3] also
To copy data from one array to another an explicit copy is required:
x = [1,2,3]
y = copy(x)
x[1] = 7
# x is [7,2,3]
# y is [1,2,3] still
The same thing happens with Python lists:
x = [1,2,3]
y = x
x[0] = 7 # Julia indexes arrays 1+, Python indexes lists 0+
# x is [7,2,3]
# y is [7,2,3] also
It is done this way to minimise the effort of unnecessary copying, which can be expensive.
The upshot is that if the code is running but gives the wrong result, a possible cause is assigning an array when a copy is intended.
The question author indicated that this was indeed the cause of the code's behaviour.
For me, it seems like the estimated hstep takes quite a long time and long iteration to converge.
I tried it with this first ODE.
Basically, you perform the difference between RK4 with stepsize of h with h/2.Please note that to reach the same timestep value, you will have to use the y value after two timestep of h/2 so that it reaches h also.
frhs=#(x,y) x.^2*y;
Is my code correct?
clear all;close all;clc
c=[]; i=1; U_saved=[]; y_array=[]; y_array_alt=[];
y_arr=1; y_arr_2=1;
frhs=#(x,y) 20*cos(x);
tol=0.001;
y_ini= 1;
y_ini_2= 1;
c=abs(y_ini-y_ini_2)
hc=1
all_y_values=[];
for m=1:500
if (c>tol || m==1)
fprintf('More')
y_arr
[Unew]=vpa(Runge_Kutta(0,y_arr,frhs,hc))
if (m>1)
y_array(m)=vpa(Unew);
y_array=y_array(logical(y_array));
end
[Unew_alt]=Runge_Kutta(0,y_arr_2,frhs,hc/2);
[Unew_alt]=vpa(Runge_Kutta(hc/2,Unew_alt,frhs,hc/2))
if (m>1)
y_array_alt(m)=vpa(Unew_alt);
y_array_alt=y_array_alt(logical(y_array_alt));
end
fprintf('More')
%y_array_alt(m)=vpa(Unew_alt);
c=vpa(abs(Unew_alt-Unew) )
hc=abs(tol/c)^0.25*hc
if (c<tol)
fprintf('Less')
y_arr=vpa(y_array(end) )
y_arr_2=vpa(y_array_alt(end) )
[Unew]=Runge_Kutta(0,y_arr,frhs,hc)
all_y_values(m)=Unew;
[Unew_alt]=Runge_Kutta(0,y_arr_2,frhs,hc/2);
[Unew_alt]=Runge_Kutta(hc/2,Unew_alt,frhs,hc/2)
c=vpa( abs(Unew_alt-Unew) )
hc=abs(tol/c)^0.2*hc
end
end
end
all_y_values
A better structure for the time loop has only one place where the time step is computed.
x_array = [x0]; y_array = [y0]; h = h_init;
x = x0; y = y0;
while x < x_end
[y_new, err] = RK4_step_w_error(x,y,rhs,h);
factor = abs(tol/err)^0.2;
if factor >= 1
y_array(end+1) = y = y_new;
x_array(end+1) = x = x+h;
end
h = factor*h;
end
For the data given in the code
rhs = #(x,y) 20*cos(x);
x0 = 0; y0 = 1; x_end = 6.5; tol = 1e-3; h_init = 1;
this gives the result against the exact solution
The computed points lie exactly on the exact solution, for the segments between them one would need to use a "dense output" interpolation. Or as a first improvement, just include the middle value from the half-step computation.
function [ y_next, err] = RK4_step_w_error(x,y,rhs,h)
y2 = RK4_step(x,y,rhs,h);
y1 = RK4_step(x,y,rhs,h/2);
y1 = RK4_step(x+h/2,y1,rhs,h/2);
y_next = y1;
err = (y2-y1)/15;
end
function y_next = RK4_step(x,y,rhs,h)
k1 = h*rhs(x,y);
k2 = h*rhs(x+h/2,y+k1);
k3 = h*rhs(x+h/2,y+k2);
k4 = h*rhs(x+h,y+k3);
y_next = y + (k1+2*k2+2*k3+k4)/6;
end
Revision 1
The error returned is the actual step error. The error that is required for the step size control however is the unit step error or error density, which is the step error with divided by h
function [ y_next, err] = RK4_step_w_error(x,y,rhs,h)
y2 = RK4_step(x,y,rhs,h);
y1 = RK4_step(x,y,rhs,h/2);
y1 = RK4_step(x+h/2,y1,rhs,h/2);
y_next = y1;
err = (y2-y1)/15/h;
end
Changing the example to a simple bi-stable model oscillating between two branches of stable equilibria
rhs = #(x,y) 3*y-y^3 + 3*cos(x);
x0 = 0; y0 = 1; x_end = 13.5; tol = 5e-3; h_init = 5e-2;
gives plots of solution, error (against an ode45 integration) and step sizes
Red crosses are the step sizes of rejected steps.
Revision 2
The error in the function values can be used as an error guidance for the extrapolation value which is of 5th order, making the method a 5th order method in extrapolation mode. As it uses the 4th order error to predict the 5th order optimal step size, a caution factor is recommended, the code changes in the appropriate places to
factor = 0.75*abs(tol/err)^0.2;
...
function [ y_next, err] = RK4_step_w_error(x,y,rhs,h)
y2 = RK4_step(x,y,rhs,h);
y1 = RK4_step(x,y,rhs,h/2);
y1 = RK4_step(x+h/2,y1,rhs,h/2);
y_next = y1+(y1-y2)/15;
err = (y1-y2)/15;
end
In the plots the step size is appropriately larger, but the error shows sharper and larger spikes, this version of the method is apparently less stable.
I am using MATLAB to calculate the numerical integral of a complex function including natural exponent.
I get a warning:
Infinite or Not-a-Number value encountered
if I use the function integral, while another error is thrown:
Output of the function must be the same size as the input
if I use the function quadgk.
I think the reason could be that the integrand is infinite when the variable ep is near zero.
Code shown below. Hope you guys can help me figure it out.
close all
clear
clc
%%
N = 10^5;
edot = 10^8;
yita = N/edot;
kB = 8.6173324*10^(-5);
T = 300;
gamainf = 0.115;
dTol = 3;
K0 = 180;
K = K0/160.21766208;
nu = 3*10^12;
i = 1;
data = [];
%% lambda = ec/ef < 1
for ef = 0.01:0.01:0.1
for lambda = 0.01:0.01:0.08
ec = lambda*ef;
f = #(ep) exp(-((32/3)*pi*gamainf^3*(0.5+0.5*sqrt(1+2*dTol*K*(ep-ec)/gamainf)-dTol*K*(ep-ec)/gamainf).^3/(K*(ep-ec)).^2-16*pi*gamainf^3*(0.5+0.5*sqrt(1+2*dTol*K*(ep-ec)/gamainf)-dTol*K*(ep-ec)/gamainf).^2/((1+dTol*K*(ep-ec)/(gamainf*(0.5+0.5*sqrt(1+2*dTol*K*(ep-ec)/gamainf)-dTol*K*(ep-ec)/gamainf)))*(K*(ep-ec)).^2))/(kB*T));
q = integral(f,0,ef,'ArrayValued',true);
% q = quadgk(f,0,ef);
prob = 1-exp(-yita*nu*q);
data(i,1) = ef;
data(i,2) = lambda;
data(i,3) = q;
i = i+1;
end
end
I've rewritten your equations so that a human can actually understand it:
function integration
N = 1e5;
edot = 1e8;
yita = N/edot;
kB = 8.6173324e-5;
T = 300;
gamainf = 0.115;
dTol = 3;
K0 = 180;
K = K0/160.21766208;
nu = 3e12;
i = 1;
data = [];
%% lambda = ec/ef < 1
for ef = 0.01:0.01:0.1
for lambda = 0.01:0.01:0.08
ec = lambda*ef;
q = integral(#f,0,ef,'ArrayValued',true);
% q = quadgk(f,0,ef);
prob = 1 - exp(-yita*nu*q);
data(i,:) = [ef lambda q];
i = i+1;
end
end
function y = f(ep)
G = K*(ep - ec);
r = dTol*G/gamainf;
S = sqrt(1 + 2*r);
x = (1 + S)/2 - r;
Z = 16*pi*gamainf^3;
y = exp( -Z*x.^2.*( 2*x/(3*G.^2) - 1/(G.^2*(1 + r/x))) ) /...
(kB*T));
end
end
Now, for the first iteration, ep = 0.01, the value of the argument of the exp() function inside f is huge. In fact, if I rework the function to return the argument to the exponent (not the value):
function y = f(ep)
% ... all of the above
% NOTE: removed the exp() to return the argument
y = -Z*x.^2.*( 2*x/(3*G.^2) - 1/(G.^2*(1 + r/x))) ) /...
(kB*T);
end
and print its value at some example nodes like so:
for ef = 0.01 : 0.01 : 0.1
for lambda = 0.01 : 0.01 : 0.08
ec = lambda*ef;
zzz(i,:) = [f(0) f(ef/4) f(ef)];
i = i+1;
end
end
zzz
I get this:
% f(0) f(ef/4) f(ef)
zzz =
7.878426438111721e+07 1.093627454284284e+05 3.091140080273912e+03
1.986962280947140e+07 1.201698288371587e+05 3.187767404903769e+03
8.908646053687230e+06 1.325435523124976e+05 3.288027743119838e+03
5.055141696747510e+06 1.467952125661714e+05 3.392088351112798e+03
...
3.601790797707676e+04 2.897200140791236e+02 2.577170427480841e+01
2.869829209254144e+04 3.673888685004256e+02 2.404148067956737e+01
2.381082059148755e+04 4.671147785149462e+02 2.238181495716831e+01
So, integral() has to deal with things like exp(10^7). This may not be a problem per se if the argument would fall off quickly enough, but as shown above, it doesn't.
So basically you're asking for the integral of a function that ranges in value between exp(~10^7) and exp(~10^3). Needless to say, The d(ef) in the order of 0.01 isn't going to compensate for that, and it'll be non-finite in floating point arithmetic.
I suspect you have a scaling problem. Judging from the names of your variables as well as the equations, I would think that this has something to do with thermodynamics; a reworked form of Planck's law? In that case, I'd check if you're working in nanometers; a few factors of 10^(-9) will creep in, rescaling your integrand to the compfortably computable range.
In any case, it'll be wise to check all your units, because it's something like that that's messing up the numbers.
NB: the maximum exp() you can compute is around exp(709.7827128933840)
I am trying to do a set of integrations for a particular value of psi and theta. These integrations are then combined into a formula to give P. The idea is then to do this for a set of psi and theta from 0 to pi/2 and then plot the results of P against a function of theta and psi.
I get two errors, firstly
integral2Calc>integral2t/tensor
not sure how to change the integral to make it the right dimensions to psi no longer being a scalar.
I also get Warning: Reached the maximum number of function evaluations (10000) for i3, is this a serious error, (as in has the integration not been computed) I tried changing the type of integration or changing the error tolerance and this seemed to have no effect on removing the actual error..
eta = input('Enter Dielectric Constant 1.5-4: ');
sdev = input('Enter STD DEV (roughness) maybe 0.1: ');
psi = [0:0.01:pi/2];
theta = [0:0.01:pi/2];
r = sqrt((sin(psi)).^2+(sin(theta).*(cos(psi))).^2);
calpha = (cos(theta+dtheta)).*(cos(psi+dpsi));
rp01 = calpha-sqrt(eta-1+((calpha).^2));
rp02 = calpha+sqrt(eta-1+((calpha).^2));
rperp = (rp01./rp02).^2;
rp11 = ((eta.*calpha)-sqrt(eta-1+((calpha).^2)));
rp12 = ((eta.*calpha)+sqrt(eta-1+((calpha).^2)));
rpar = (rp11./rp12).^2;
qthingy1 = ((sin(psi+dpsi)).^2)-((sin(theta+dtheta)).^2).*((cos(psi+dpsi)).^2);
qthingy2 = ((sin(psi+dpsi)).^2)+((sin(theta+dtheta)).^2).*((cos(psi+dpsi)).^2);
qthingy = qthingy1./qthingy2;
wthingy1 = (sin(2*(psi+dpsi))).*(sin(theta+dtheta));
wthingy2 = ((sin(psi+dpsi)).^2)+(sin((theta+dtheta)).^2).*((sin(psi+dpsi)).^2);
wthingy = wthingy1./wthingy2;
roughness = (cos(psi)./(2*pi*(sdev.^2))).*exp(-((cos(psi).*dtheta).^2+(dpsi).^2)/(2*sdev.^2));
fun = matlabFunction((rpar+rperp).*roughness,'vars',{dtheta,dpsi});
fun2 = matlabFunction(roughness,'vars',{dtheta,dpsi});
fun3 = matlabFunction((rpar+rperp).*roughness.*qthingy,'vars',{dtheta,dpsi});
fun4 = matlabFunction((rpar+rperp).*roughness.*wthingy,'vars',{dtheta,dpsi});
thetamax = (pi/2) - theta;
psimax = (pi/2) - psi;
A = integral2(fun2,-pi/2,pi/2,-pi/2,pi/2);
i1 = (integral2(fun,-pi/2,thetamax,-pi/2,psimax))./A;
q = 2 - i1;
i2 = (integral2(fun3,-pi/2,thetamax,-pi/2,psimax))./A;
i3 = (integral2(fun4,-pi/2,thetamax,-pi/2,psimax))./A;
P = sqrt(i2.^2+i3.^2)./i1
plot(P, r);
I wrote a python function to get the parameters of the following cosine function:
param = Parameters()
param.add( 'amp', value = amp_guess, min = 0.1 * amp_guess, max = amp_guess )
param.add( 'off', value = off_guess, min = -10, max = 10 )
param.add( 'shift', value = shift_guess[0], min = 0, max = 2 * np.pi, )
fit_values = minimize( self.residual, param, args = ( azi_unique, los_unique ) )
def residual( self, param, azi, data ):
"""
Parameters
----------
Returns
-------
"""
amp = param['amp'].value
off = param['off'].value
shift = param['shift'].value
model = off + amp * np.cos( azi - shift )
return model - data
In Matlab how can get the amplitude, offset and shift of the cosine function?
My experience tells me that it's always good to depend as little as possible on toolboxes. For your particular case, the model is simple and doing it manually is pretty straightforward.
Assuming that you have the following model:
y = B + A*cos(w*x + phi)
and that your data is equally-spaced, then:
%// Create some bogus data
A = 8;
B = -4;
w = 0.2;
phi = 1.8;
x = 0 : 0.1 : 8.4*pi;
y = B + A*cos(w*x + phi) + 0.5*randn(size(x));
%// Find kick-ass initial estimates
L = length(y);
N = 2^nextpow2(L);
B0 = (max(y(:))+min(y(:)))/2;
Y = fft(y-B0, N)/L;
f = 5/(x(2)-x(1)) * linspace(0,1,N/2+1);
[A0,I] = max( 2*abs(Y(1:N/2+1)) );
w0 = f(I);
phi0 = 2*imag(Y(I));
%// Refine the fit
sol = fminsearch(#(t) sum( (y(:)-t(1)-t(2)*cos(t(3)*x(:)+t(4))).^2 ), [B0 A0 w0 phi0])
Results:
sol = %// B was -4 A was 8 w was 0.2 phi was 1.8
-4.0097e+000 7.9913e+000 1.9998e-001 1.7961e+000
MATLAB has a function called lsqcurvefit in the optimisation toolbox:
lsqcurvefit(fun,X0,xdata,ydata,lbound,ubound);
where fun is the function to fit, x0 is the initial parameter guess, xdata and ydata are self-explanatory, and lbound and ubound are the lower and upper bounds to the parameters. So, for instance, you might have a function:
% x(1) = amp
% x(2) = shift
% x(3) = offset
% note cosd instead of cos, because your data appears to be in degrees
cosfit = #(x,xdata) x(1) .* cosd(xdata - x(2)) + x(3);
You would then call the lsqcurvefit function as follows:
guess = [7,150,0.5];
lbound = [-10,0,-10]
ubound = [10,360,10]
fit_values = lsqcurvefit(cosfit,guess,azi_unique,los_unique,lbound,ubound);