Is it possible to create a simple model of a bouncing ball, using Julia's equation solvers?
I started with this:
using ODE
function bb(t, f)
(y, v) = f
dy_dt = v
dv_dt = -9.81
[dy_dt, dv_dt]
end
const y0 = 50.0 # height
const v0 = 0.0 # velocity
const startpos = [y0; v0]
ts = 0.0:0.25:10 # time span
t, res = ode45(bb, startpos, ts)
which produces useful-looking numbers:
julia> t
44-element Array{Float64,1}:
0.0
0.0551392
0.25
0.5
0.75
1.0
⋮
8.75
9.0
9.25
9.5
9.75
10.0
julia> res
44-element Array{Array{Float64,1},1}:
[50.0,0.0]
[49.9851,-0.540915]
[49.6934,-2.4525]
[48.7738,-4.905]
[47.2409,-7.3575]
⋮
[-392.676,-93.195]
[-416.282,-95.6475]
[-440.5,-98.1]
But somehow it needs to intervene when the height is 0, and reverse the velocity. Or am I on the wrong track?
DifferentialEquations.jl offers sophisticated callbacks and event handling. Since the DifferentialEquations.jl algorithms are about 10x faster while offering a higher order interpolation, these algorithms are clearly the better choose here anyways.
The first link is the documentation which shows how to do the event handling. The easy interface uses the macros. I start by defining the function.
f = #ode_def BallBounce begin
dy = v
dv = -g
end g=9.81
Here I am showing ParameterizedFunctions.jl to make the syntax nicer, but you can define the function directly as an in-place update f(t,u,du) (like Sundials.jl). Next you define the function which determines when an event takes place. It can be any function which is positive and hits zero at the event time. Here, we are checking for when the ball hits the ground, or for when y=0, so:
function event_f(t,u) # Event when event_f(t,u,k) == 0
u[1]
end
Next you say what to do when the event occurs. Here we want to reverse the sign of the velocity:
function apply_event!(u,cache)
u[2] = -u[2]
end
You put these functions together to build the callback using the macros:
callback = #ode_callback begin
#ode_event event_f apply_event!
end
Now you solve as usual. You define the ODEProblem using f and the initial condition, and you call solve on a timespan. The only thing extra is you pass the callback along with the solver:
u0 = [50.0,0.0]
prob = ODEProblem(f,u0)
tspan = [0;15]
sol = solve(prob,tspan,callback=callback)
Then we can use the plot recipe to automatically plot the solution:
plot(sol)
The result is this:
A few things to notice here:
DifferentialEquations.jl will automatically use an interpolation to more safely check for the event. For example, if the event happened within a timestep but not at the ends, DifferentialEquations.jl will still find it. More or less interpolations points can be included as options to the #ode_event macro.
DifferentialEquations.jl used a rootfinding method to hone in on the moment of the event. Even though the adaptive solver steps past the event, by using rootfinding on the interpolation it finds the exact time of the event, and thus gets the discontinuity right. You can see that in the graph since the ball never goes negative.
There is a whole lot more this can do. Check out the docs. You can do pretty much anything with this. For example, have your ODE changing size over the run to model a population of cells with birth and deaths. This is something other solver packages can't do.
Even with all of these features, speed is not compromised.
Let me know if you need any extra functionality added to the "ease of use" interface macros.
Somewhat hacky:
function bb(t, f)
(y, v) = f
dy_dt = v
dv_dt = -9.81*sign(y)
[dy_dt, dv_dt]
end
where you just follow a convention where y and -y refer to the same heights. You can then plot the trajectory of the bouncing ball by just plotting abs(y).
Related
I'm trying to interpolate a Brownian motion. The function does not return me an error but it seems like Julia does not put the value on vector B. Here the codes.
function interpolation(i,j,N,BM)
if j-i>1
k = sqrt((j-i)/((2^N))/4)
d = (i+j)/2
BM[d] =((BM[i]+BM[j])/2)+k*randn(1)
BM = interpolation(i,d,N,BM)
BM = interpolation(d,j,N,BM)
end
end
plot(BM)
Thanks a lot!
I think that your code could be simplified by using array views. That eliminates all of the extra parameters from you code and makes it easier to see what it is doing. The normalization so that changes are smaller for interior steps could be simplified as well.
So here is a stab at this simplification:
function fractal(x)
if length(x) > 2
n = length(x)
mid = (n+1)÷2
x[mid] = (x[1] + x[n])/2 + randn() * sqrt(n)
fractal(#view x[1:mid])
fractal(#view x[mid:n])
end
end
And here is a result of this code running:
a = zeros(1024)
fractal(a)
plot(a, legend=false)
The point of the simplification is to highlight the idea that the algorithm involves:
Interpolating the middle value based on the end-points
Do the same to the left and right halves of the array
if we don't have a big enough array, just return
This approach avoids complicating the picture with all of the housekeeping and it worked first try, largely because, I think, I didn't have to keep all that stuff straight.
I would like to simulate the collision of particles inside a box.
To be more specific I want to create a function (lets call it collision!), that updates the particles velocities after each interaction, like shown in the image.
I defined the particles (with radius equal 1) as followed:
mutable struct Particle
pos :: Vector{Float64}
vel :: Vector{Float64}
end
p = Particle( rand(2) , rand(2) )
# example for the position
p.pos
> 2-element Vector{Float64}:
0.49339012018408135
0.11441734325871078
And for the collision
function collision!(p1::Particle, p2::Particle)
# ... #
return nothing
end
The main idea is that when two particles collide, they "exchange" their velocity vector that is parallel to the particles centers (vector n hat).
In order to do that, one would need to transform the velocity vectors to the orthonormal basis of the collision normal (n hat).
Then exchange the parallel component and rotate it in the original basis back.
I think I got the math right but I am not sure how to implement it in the code
With the caveat that I have not checked the math at all, one implementation for the 2d case you provide might be along the lines of:
struct Particle
pos :: Vector{Float64}
vel :: Vector{Float64}
end
p1 = Particle( rand(2) , rand(2) )
p2 = Particle( rand(2) , rand(2) )
function collision!(p1::Particle, p2::Particle)
# Find collision vector
n = p1.pos - p2.pos
# Normalize it, since you want an orthonormal basis
n ./= sqrt(n[1]^2 + n[2]^2)
# Construct M
M = [n[1] n[2]; -n[2] n[1]]
# Find transformed velocity vectors
v1ₙ = M*p1.vel
v2ₙ = M*p2.vel
# Swap first component (or should it be second? Depends on how M was constructed)
v1ₙ[1], v2ₙ[1] = v2ₙ[1], v1ₙ[1]
# Calculate and store new velocity vectors
p1.vel .= M'*v1ₙ
p2.vel .= M'*v2ₙ
return nothing
end
A few points:
You don't need a mutable struct; just a plain struct will work fine since the Vector itself is mutable
This implementation has a lot of excess allocations that you could avoid if you could work either in-place or perhaps more feasibly on the stack (for example, using StaticArrays of some sort instead of base Arrays as the basis for your position and velocity vectors). In-place actually might not be too hard either if you just make another struct (say "CollisionEvent") which holds preallocated buffers for M, n, v1n and v2n, and pass that to the collision! function as well.
While I have not dived in to see, one might be able to find useful reference implementations for this type of collision in a molecular dynamics package like https://github.com/JuliaMolSim/Molly.jl
I am new to Julia, I would like to solve this system:
where k1 and k2 are constant parameters. However, I=0 when y,0 or Ky otherwise, where k is a constant value.
I followed the tutorial about ODE. The question is, how to solve this piecewise differential equation in DifferentialEquations.jl?
Answered on the OP's cross post on Julia Discourse; copied here for completeness.
Here is a (mildly) interesting example $x''+x'+x=\pm p_1$ where the sign of $p_1$ changes when a switching manifold is encountered at $x=p_2$. To make things more interesting, consider hysteresis in the switching manifold such that $p_2\mapsto -p_2$ whenever the switching manifold is crossed.
The code is relatively straightforward; the StaticArrays/SVector/MVector can be ignored, they are only for speed.
using OrdinaryDiffEq
using StaticArrays
f(x, p, t) = SVector(x[2], -x[2]-x[1]+p[1]) # x'' + x' + x = ±p₁
h(u, t, integrator) = u[1]-integrator.p[2] # switching surface x = ±p₂;
g(integrator) = (integrator.p .= -integrator.p) # impact map (p₁, p₂) = -(p₁, p₂)
prob = ODEProblem(f, # RHS
SVector(0.0, 1.0), # initial value
(0.0, 100.0), # time interval
MVector(1.0, 1.0)) # parameters
cb = ContinuousCallback(h, g)
sol = solve(prob, Vern6(), callback=cb, dtmax=0.1)
Then plot sol[2,:] against sol[1,:] to see the phase plane - a nice non-smooth limit cycle in this case.
Note that if you try to use interpolation of the resulting solution (i.e., sol(t)) you need to be very careful around the points that have a discontinuous derivative as the interpolant goes a little awry. That's why I've used dtmax=0.1 to get a smoother solution output in this case. (I'm probably not using the most appropriate integrator either but it's the one that I was using in a previous piece of code that I copied-and-pasted.)
I'm porting a Matlab code into julia and so far i'm having amazing results:
A code that in Matlab runs in more than 5 hours, julia does it in a little more than 8 minutes! however i have a problem...
In matlab i have:
for xx=1:xlong
for yy = 1:ylong
U_alturas(xx,yy,:) = interp1(squeeze(NivelAltura_int(xx,yy,:)),squeeze(U(xx,yy,:)), interpolar_a);
V_alturas(xx,yy,:) = interp1(squeeze(NivelAltura_int(xx,yy,:)),squeeze(V(xx,yy,:)), interpolar_a);
end
end
that produces NaNs whenever a point in interpolar_a is outside the range in NivelAltura_int.
In Julia i'm trying to do the same with:
for xx in 1:xlong
for yy in 1:ylong
AltInterp = interpolate((Znw_r,),A_s_c_r,Gridded(Linear()));
NivelAltura_int[xx,yy,1:end] = AltInterp[Znu[1:end]]
Uinterp = interpolate((squeeze(NivelAltura_int[xx,yy,1:end],(1,2)),),squeeze(U[xx,yy,1:end],(1,2)),Gridded(Linear()));
Vinterp = interpolate((squeeze(NivelAltura_int[xx,yy,1:end],(1,2)),),squeeze(V[xx,yy,1:end],(1,2)),Gridded(Linear()));
U_alturas[xx,yy,1:end] = Uinterp[Alturas[1:end]];
V_alturas[xx,yy,1:end] = Vinterp[Alturas[1:end]];
end
end
using the package Interpolations.jl. Whenever the point is outside the domain, this package extrapolates, which is incorrect for my purposes.
I can add a few lines of code that check and substitutes the values outside the domain with NaNs, but i believe it would add some time to the computation and is not very elegant.
In the documentation of the package, it mentions a kind of object like this:
Uextrap = extrapolate(Uinterp,NaN)
To control the behavior outside the domain, but i haven't find how to use it, i've tried adding it under Uinterp, i've tried evaluating it but it, naturally, won't work that way.
Could you help me on this one?
Thanks!
It looks like you may be running into two issues here. First, there's been some recent work on gridded extrapolations (#101) that may not be in the tagged version yet. If you're willing to live on the edge, you can Pkg.checkout("Interpolations") to use the development version (Pkg.free("Interpolations") will put you back on the stable version again).
Secondly, it looks like there's a still a missing method for vector-valued gridded extrapolations (issue #24):
julia> using Interpolations
itp = interpolate((collect(1:10),), collect(.1:.1:1.), Gridded(Linear()))
etp = extrapolate(itp, NaN);
julia> etp[.5:1:10.5]
ERROR: BoundsError: # ...
in throw_boundserror at abstractarray.jl:156
in getindex at abstractarray.jl:488
As you can see, it's trying to use the generic definitions for all abstract arrays, which will of course throw bounds errors. Interpolations just needs to add its own definition.
In the mean time, you can use a comprehension with scalar indexing:
julia> [etp[x] for x=.5:1:10.5]
11-element Array{Any,1}:
NaN
0.15
0.25
0.35
0.45
0.55
0.65
0.75
0.85
0.95
NaN
The following sample (refer) shows how extrapolate works:
Preparation:
using Interpolations
f(x) = sin((x-3)*2pi/9 - 1)
xmax = 10
A = Float64[f(x) for x in 1:xmax] # domain .EQ. 1:10
itpg = interpolate(A, BSpline(Linear()), OnGrid())
The itpg object extrapolates outside points conforming its interpolation type:
itpg[2] # inside => -0.99190379965505
itpg[-2] # outside => 0.2628561875219271
Now we use extrapolat object to control extrapolation behavior:
etpg = extrapolate(itpg, NaN);
etpg[2]==itpg[2] # same result when point is inside => true
isnan(etpg[-2]) # NaN when the point is outside => true
So an extrapolate object does interpolation conforming its parent while extrapolates in a custom manner.
I've written a a little function that gives me out a value based on a sine wave when I put in a float between 0 and 1. I'm using it to lerp things around in a game.
public static class Utilities
{
public static float SineMe(float prop)
{
float output = (prop*180f)-90f;
output = Mathf.Sin(output*Mathf.Deg2Rad);
output = (output+1f)/2f;
return output;
}
}
It works fine.. But I was wondering is there a mathematical way of altering the sine wave so I can make it 'steeper' or 'shallower' in the middle?
In the diagram below the blue curve is a sine wave, I'm wondering if I can make it more like the green line.
What you're showing already isn't really sine - the range of sine is between -1 and +1. You're applying the linear function f(x) = (x+1)/2 to change that range. So place another function between the sine and that transform.
To change the shape, you need a non-linear function. So, here's a cubic equation you might try...
g(x) = Ax^3 + Bx^2 + Cx + D
D = 0
C = p
B = 3 - 3C
A = 1 - (B + C)
The parameter p should be given a value between 0.0 and 9.0. If it's 1.0, g(x) is the identity function (the output is the unmodified input). With values between 0.0 and 1.0, it will tend to "fatten" your sine wave (push it away from 0.0 and towards 1.0 or -1.0) which is what you seem to require.
I once "designed" this function as a way to get "fractal waveforms". Using values of p between 1.0 and 9.0 (and particularly between around 3.0 and 6.0) iterative application of this formula is chaotic. I stole the idea from the population fluctuation modelling chaotic function by R. M. May, but that's a quadratic - I wanted something symmetric, so I needed a cubic function. Not really relevant here, and a pretty aweful idea as it happens. Although you certainly get chaotic waveforms, what that really means is huge problems with aliassing - change the sample rate and you get a very different sound. Still, without the iteration, maybe this will give you what you need.
If you iterate enough times with p between 0.0 and 1.0, you end up with a square wave with slightly rounded corners.
Most likely you can just choose a value of p between 0.0 and 1.0, apply that function once, then apply your function to change the range and you'll get what you want.
By the way, there's already a comment suggesting a cheat sheet of "easing functions". "Easing" is a term from animation, and computer animation software often uses Bezier curves for that purpose - the same Bezier curves that vector graphics software often uses. Bezier curves come in quadratic and cubic variants, with cubic being the more common. So what this is doing probably isn't that different. However, cubic Bezier easing gives you more control - you can control the "ease-in" independently of the "ease-out", where my function only provides one parameter.
You can use the y(x) = 1-(1-x)^n function when x = [0..1], as a transform function.
You will just have to replace x by the absolute value of your sinus and report the sign of sinus to the result. In that way you can tweak the sinus slope by increasing n. So what you want is this:
float sinus = Mathf.Sin(output*Mathf.Deg2Rad);
int sign = (sinus >= 0 ? 1 : -1);
int n = 4; // slope parameter
float waveform = sign * ( 1-Mathf.Pow(1-Mathf.Abs(sinus), n) );
You can root the sine function to make it steeper (only working for positive values). The higher the root, the steeper the sine.
Graph of a steeper sine wave function
I discovered this nifty trick for a steeper sine wave (0..1).
f(x) = cos(sin(x)^3)^10
If you need (-1..1):
2 * (f(x) - 0.5)
I think I found the solution.
(0.5+sin(x*π-π/2)/2)^((2*(1-x))^k)
in the interval x = [0.0, 1.0]
with k that control the steepness.
k=0.0 for the unmodified sinus (purple)
k=1.0 (green)
k=2.0 (blue)
https://www.desmos.com/calculator/wdtfsassev
I was looking for a similar function, not for the whole sine but just half the period.
I bumped into the Logistic function:
f(x) = L / (1 + e^(-k(x-x0)))
where
e = the natural logarithm base (also known as Euler's number),
x0 = the x-value of the sigmoid's midpoint,
L = the curve's maximum value, and
k = the steepness of the curve.
See https://en.wikipedia.org/wiki/Logistic_function
Works for me
what about
sign(sin(x))*sqrt(abs(sin(x))
https://www.desmos.com/calculator/5nn34xqkfr