16

I made this Manipulate code which shows a pack of particles randomly moving inside a box. It has a huge performance issue. What should be the best way in doing "hard" bounces on the walls ? I used an oscillator trick to do the bounces (there's surely a better way). The code below is working but is way too slow for just a few particles, and yet the particles don't even interact ! So I need advices/suggestions to do a better "gaz of particles in a box".

L1 = 20; (* Box size along X *)
L2 = 10; (* Box size along Y *)
L3 = 5;  (* Box size along Z *)

r = 1; (* randomizer parameter *)

box = Graphics3D[{Opacity[0.1], Cuboid[{-L1/2, -L2/2, -L3/2}, {L1/2, L2/2, L3/2}]}];

(* Random initial conditions : *)
X0[n_, r_] := X0[n, r] = {RandomReal[{-1, 1}] L1/2, RandomReal[{-1, 1}] L2/2, RandomReal[{-1, 1}] L3/2}
V0[n_, r_] := V0[n, r] = RandomReal[{-1, 1}, 3]

(* Solving the equations of motion.  Surely a better way of doing this :  *)
motion[n_, r_] := NDSolve[{
    x''[t] == If[-L1/2 < x[t] < L1/2, 0, -10 x[t]],
    y''[t] == If[-L2/2 < y[t] < L2/2, 0, -10 y[t]],
    z''[t] == If[-L3/2 < z[t] < L3/2, 0, -10 z[t]],

    x[0] == {1, 0, 0}.X0[n, r],
    y[0] == {0, 1, 0}.X0[n, r],
    z[0] == {0, 0, 1}.X0[n, r],
    x'[0] == {1, 0, 0}.V0[n, r],
    y'[0] == {0, 1, 0}.V0[n, r],
    z'[0] == {0, 0, 1}.V0[n, r]

    }, {x, y, z}, {t, 0, 50},
    Method -> Automatic, MaxSteps -> Automatic
]

color[n_] := color[n] = RGBColor[RandomReal[{0, 1}, 3]]

particles[t_, Np_, r_] := Graphics3D[Table[{color[n], PointSize -> 0.01,
    Point[Evaluate[{x[t], y[t], z[t]}/.motion[n, r]]]
    }, {n, 1, Np}]]

trajectory[t_, n_, r_] := ParametricPlot3D[
    Evaluate[{x[s], y[s], z[s]}/.motion[n, r]], {s, 0.001, t}, 
    PlotStyle -> color[n]]

Manipulate[
    Show[{box, particles[t, Np, r], 
        Table[trajectory[t, n, r], {n, 1, Np}]},
        PlotRange -> {{-1, 1} L1/2, {-1, 1} L2/2, {-1, 1} L3/2},
        Boxed -> False, Axes -> False,
        ImageSize -> {600, 600},
        SphericalRegion -> True,
        Method -> {"RotationControl" -> "Globe"}],
    {{t, 0, Style["Time", 10]}, 0, 50, 0.01, ImageSize -> Large},
    {{Np, 1, Style["Number or particles", 10]}, 1, 100, 1, ImageSize -> Large},
    Delimiter,
    Button[Style["Randomize", Bold, Red, 12], {r = RandomReal[]},
        Appearance -> "Palette", ImageSize -> {100, 24}],
    ControlPlacement -> Bottom
]

Preview : box of particles


EDIT : I know that the analytical solution to the equations of motion is simply a straight line (with the random initial conditions defined above) :

X[t_, n_, r_] := X0[n, r] + V0[n, r] t

which describes the motion between the start (at t = 0) and the first collision through a wall. It's certainly preferable to use this function instead of solving the differential equations. But I'm still struggling with the collisions. How to reverse the velocity at each collision, without solving the diff equations ?


EDIT 2 :

I've adapted the code from tsuresuregusa, and here's a preview of the sweet results :

a sweet box of candies

I've added a fade effect on the trails. It's really fun to see the animation.

Here's an animation of 6 particles :

6 particles

I used ViewPoint -> {Sin[0.05t], Cos[0.05t], 1} for the revolution around the box. What would you suggest for a better effect ?

Oleksandr R.
  • 23,023
  • 4
  • 87
  • 125
Cham
  • 4,093
  • 21
  • 36
  • the best way to solve this is with an event-driven simulation code. Since the particles move in straight lines between collisions you can write an analytical function for them, piecewise between collisions. Check here http://artemis.wszib.edu.pl/~sloot/1_4.html or here http://www2.msm.ctw.utwente.nl/sluding/PAPERS/luding_md1.pdf my ex-boss. Let me see if I have time to code a minimal example in the next few days, should be fun. – tsuresuregusa Apr 05 '16 at 04:35
  • Do you need to visualize just particles or trajectories as well? – BlacKow Apr 05 '16 at 18:25
  • @BlacKow, both. The trajectories would be turned on/off using a switch in the Manipulate code. – Cham Apr 05 '16 at 18:30
  • @Cham add an animated GIF, so we can see the fun animation. – BlacKow Apr 06 '16 at 14:32
  • @Cham you replace your Animation with Table and then export it as a GIF file. See here – BlacKow Apr 06 '16 at 14:41
  • @Cham you don't really need to specify all that. You literally replace your Animate with Table and do Export["coolAnimation.gif",Table[...]] Also it will be nice if you post your final result including code as an update. – BlacKow Apr 06 '16 at 15:17
  • Okay, I'm now able to make gifs. But the output is large ! 1.4 MB for the file ! Need to make less frames. – Cham Apr 06 '16 at 15:18
  • My animated gif doesn't show here. Is it too big at 1.4 MB ?? The Stack Exchange picture upload interface sucks ! – Cham Apr 06 '16 at 15:30
  • 1
    You should use Imgur and not Postimage if you want the animation to work. Postimage serves your animation as HTML (containing a number of "adult themed" adverts) rather than GIF. StackExchange has a contract with Imgur to host all the images on the site and there is a better chance that the posts won't be rendered useless by the images disappearing (or being supplanted by porn ads) if you use them. The image upload thing does suck, but that's life I'm afraid. By the way, thanks for supplying the alt text. So few people bother with it. – Oleksandr R. May 02 '16 at 02:06

3 Answers3

19

You can use FoldList to generate evolution of your system. You need a function that propagates your particles in time. Every time you apply your function to state at time $t$ you obtain your state at time $t+dt$. Let's make such function for one particle in 1D.

Tr1D[{x_, v_}, dt_, L_] := Module[{u, w},
   u = x + v dt;
   {u, w} = If[u < L, {u, v}, {L - (u - L), -v}];
   {u, w} = If[u > -L, {u, w}, {(u - L) - u, -w}]
   ];

L is your 1D box.

Now let's make such function for one particle in 3D

Tr3D[p_, dt_, L_] := 
  Flatten@{Tr1D[p[[#]], dt, L[[#]]]} & /@ {1, 2, 3};

and then we can generalize it to $N$ particles in 3D.

box = {1, 1, 1};
numOfPart = 50;
Tr3ND[p_, dt_, L_] := Tr3D[#, dt, L] & /@ p;
g[p_, dt_] := Tr3ND[p, dt, box];

g translates your system in time.

ipos = Transpose@(RandomReal[#, numOfPart] & /@ Transpose@{-box, box});
ivel = Transpose@(RandomReal[#, numOfPart] & /@ 
     Transpose@{-2 box, 2 box});
init = (Transpose@{#[[1]], #[[2]]}) & /@ Transpose@{ipos, ivel};

You initial condition for every particle is its position and velocity at $t=0$. So your system at any moment is fully described by $6N$ values. We apply FoldList to run our propagation function recursively:

traj = FoldList[g, init, ConstantArray[0.1, 300]];

And we obtain the trajectories of all particles (with 300 steps). Now we can draw them

Animate[Graphics3D[#, PlotRange -> Transpose@{-box, box}, 
    Axes -> True] &@
  Riffle[Point /@ traj[[t, All, All, 1]], {Red, Blue, Green, 
    Orange}], {t, 1, Length@traj, 1}]

enter image description here

This approach is quite general, because now you can modify your g function to add external field or interaction between particles.

Update: Adding fading trajectories

colors = Table[#[[Mod[i, Length@#] + 1]] &@ #, {i, 1, 
      numOfPart}] &@{Red, Blue, Green, Orange};
distColor[col_, fade_, lines_] := 
  Reverse@FoldList[{Lighter[#1[[1]], fade], #2} &,  {col, 0}, 
     Reverse@lines][[2 ;; -1]];
Animate[Graphics3D[{PointSize[0.01], Thick,  #}, 
    PlotRange -> Transpose@{-box, box}, 
    Axes -> True] &@(distColor[#1, 0.2, #2] & @@@ 
    Transpose@{colors, (Line /@ Partition[#, 2, 1] & /@ 
        Transpose[
         traj[[Max[1, t - 20] ;; t, All, All, 1]], {2, 1}])}), {t, 1, 
  Length@traj, 1}]

enter image description here

BlacKow
  • 6,428
  • 18
  • 32
  • It's working, but it is very elaborate, and I don't understand much of the code. Maybe too general for what I want to do ? I was thinking of using the analytical solution, for all time $t$, which is $x_{k+1}(t) = (-1)^k , \big(x_0 + v_{0 x}(, t - k L_1/v_0) \big)$ (similar expressions for axes $y$ and $z$), for time in the intervall $t_k < t < t_{k + 1}$, where $k$ is the collision time : $t_{k + 1} = t_1 + k L_1/v_0$ ($t_1$ is the time of the first collision on the $x$ axis). – Cham Apr 05 '16 at 20:51
  • Oh, and how can you trace the trajetories with your method ? And I don't see any Random initial conditions in the code. – Cham Apr 05 '16 at 20:57
  • @Cham If you final goal is non-interacting particles, then you should stick to @tsuresuregusa's solution. I'm pretty sure that you will want some interaction later. Also keep in mind that to you need to evaluate your positions at $t_i$ to make animation. So even if you have analytical solution you will still need to calculate the points. My method already gives the positions to you. Not sure how elaborate it is. A lot of it is just problem setup, the g function is pretty straightforward. – BlacKow Apr 05 '16 at 20:58
  • @Cham Instead of plotting Points you will need to plot Lines between them. My initial conditions init are generated with RandomReal – BlacKow Apr 05 '16 at 21:01
  • Actually, you're right : at first, I was interested in interacting (colliding) hard balls. But I guessed that it would be too slow for a `Manipulate', and thus reduced my original problem to a much simpler gas of non-interacting bouncing particles. – Cham Apr 05 '16 at 21:01
  • Another similar problem I was interested is a gaz of non-interacting particles in a box with periodic conditions : a particle that go out of the box reappears at the other side. Is this simpler to do with my original code above (without the NDSolve) ? – Cham Apr 05 '16 at 21:05
  • @Cham With all due respect to non-interacting gas ... It's not very interesting, you won't be able to see anything interest, unless you introduce some external forces etc – BlacKow Apr 05 '16 at 21:05
  • While I agree that non-interacting particles may be boring, I was intereseted in calculating the average kinetic energy density, and make a graph of it in time, and make a connection with the classical kinetik theory of gas. – Cham Apr 05 '16 at 21:07
  • If your collision with box's sides are elastic, then the particle's energy never changes. So the average energy will be flatline over time – BlacKow Apr 05 '16 at 21:08
  • Flat line, yes. But this line depends on the initial conditions, which in turn depends on the temperature (i.e. statistical distribution used). That's what I want to show with the finished project. – Cham Apr 05 '16 at 21:09
  • Sure. But there is no evolution. You are probably more interested in particle energy distribution. E.g. implement collisions and show that the particle velocity will have Maxwell distribution eventually (regardless initial condition). – BlacKow Apr 05 '16 at 21:13
  • On another (related) subject : how can we define a box with periodic conditions ? – Cham Apr 05 '16 at 21:16
  • Look into my Tr1D. If you want to wrap particles i.e magically reappear at the opposite side, you just change expression in If statement: now the velocity will stay the same, but position will be incremented/decremented by L – BlacKow Apr 05 '16 at 21:20
  • Very elegant way to code it @BlacKow however the euler integration you are does not conserve energy and it's not time reversible. Always the problems with the time integration methods. Cham, you are not going to get any connection with kinetic theory if your gas in non-interacting. The distribution of velocity that you put in is what you get out. If you'd like to get something closer to a maxwellian you should try montecarlo methods, which are easier to code and work fine in equilibrium systems. – tsuresuregusa Apr 06 '16 at 00:11
  • @tsuresuregusa You think it's time to blow off dust of symplectic integrator? I'm pretty sure that in this particular case of non-interacting gas the Euler method conserves energy :) – BlacKow Apr 06 '16 at 00:21
  • @BlacKow bet it does in fact, was just my brain hardcoded to hate euler XD – tsuresuregusa Apr 06 '16 at 00:34
  • @BlacKow, suppose we would like to use the random functions X0 and V0 that I defined in my question. Could we define a "bounce operator" (using conditions, If, Which, Switch, etc) such that the following would automatically produce the collisions (and reverse the proper velocity component) on any wall, at any time "t" ? x[t_, n_, r_] := Bounce[{1, 0, 0}.(X0[n, r] + V0[n, r] t)]; y[t_, n_, r_] := Bounce[{0, 1, 0}.(X0[n, r] + V0[n, r] t)]; z[t_, n_, r_] := Bounce[{0, 0, 1}.(X0[n, r] + V0[n, r] t)]; – Cham Apr 06 '16 at 03:02
  • @Cham Yes, you can implement Bounce look into my Tr1D – BlacKow Apr 06 '16 at 14:30
  • @BlacKow, it is not clear. How can you adapt it to my code ? Also, is the condition u < 0 taken into account ? (what is "u" anyway ?). – Cham Apr 06 '16 at 14:33
  • @Cham what's so special about $u<0$? My box goes from $-L$ to $L$. what's r_ in your code? – BlacKow Apr 06 '16 at 14:35
  • My "new" box (because of the solution of tsuresuregusa) now goes from x = 0 to x = L1, y = 0 to y = L2, and z = 0 to z = L3. The "r" parameter is just a randomizer. I defined a "randomize" button in my manipulate box (see my code in the question). The randomize Button is defined at the end of my code. – Cham Apr 06 '16 at 14:37
  • What's up with the single black particle in the first animation? It seems to be controlled by Maxwell's demon... – Oleksandr R. May 02 '16 at 02:21
  • @OleksandrR. The slow one? – BlacKow May 02 '16 at 20:05
  • The fading plot is very slow in Mathematica 12, is that fixable or is it inherently slow? – kungfooman Nov 16 '20 at 15:31
12

ok, this is cheating but since your gas is non-interacting it works.

3 dimensions or 1 dimensions is the same since the collisions only change momentum in the normal direction, ie we assume point particles and no friction.

A collision with a wall the only thing it does is to invert the velocity. So you can think of the particle moving at a constant speed from its starting position to infinity. The only thing you need to do is to map it onto the box in the correct way.

L = 1
a[x_] := -1 + 2 Boole@OddQ@Quotient[x, L];
Plot[Mod[a[x] x , L], {x, 0, 10}]

enter image description here

EDIT:

Maybe there is a nicer way of doing it, but what the quotient does is to "count" how many times the particle has crossed the boundary. Remember the whole idea is based on that the particle moves from its initial position to infinity. When you know how many times had "crossed" a boundary you know when to change the velocity. That's what the Bole@OddQ does, which gives you 0 or 1, but you want -1 and 1 (the velocity is reflected completely with each collision) so in fact a is the sign of the velocity as a function of time.

Btw, this is a trick used in real world simulations.

ADDENDUM:

Clear[a, x]
L = 1
a[x_] := -1 + 2 Boole@OddQ@Quotient[x, L];
x[t_] := t + .3;
x2[t_] := 2.43 t;
x3[t_] := \[Pi] t;
ParametricPlot3D[{Mod[a[ x3[t]]  x3[t], L], Mod[a[ x2[t]]  x2[t], L], 
  Mod[a[x[t]]   x[t], L]}, {t, 0, 3}]

enter image description here

Update 2:

It may be that the notation is not too clear, but seemed to me that adding .3 as an example of x0 was enough. The same with pi for the velocity of the particles... simple dimensional analysis tells you to what they correspond. I think your problem is that you still don't get what the function a[x] does, which is to map back into the box the particles as I explained at the beginning of my answer.

This one has colours and manipulate:

Clear[a, x, x0]
L = 1
a[x_] := -1 + 2 Boole@OddQ@Quotient[x, L];
x[t_, v_, x0_] := v t + x0;
xBox [t_, v_, x0_] := Mod[a[ x[t, v, x0]]  x[t, v, x0], L];

vel = RandomReal[{-1, 1}, {10, 3}];
pos =  RandomReal[{0, L}, {10, 3}];

Manipulate[
 Show[Table[
   ParametricPlot3D[
    Table[xBox[t, vel[[i, j]], pos[[i, j]]], {j, 3}], {t, 0, tf}, 
    PlotStyle -> Hue[i/10], PlotRange -> {0, L}], {i, 3}]], {tf, 1, 
  10}] 

last addendum:

enter image description here

tsuresuregusa
  • 2,661
  • 2
  • 13
  • 31
  • 2
    MMA has TriangleWave... – LLlAMnYP Apr 05 '16 at 05:12
  • What is the logic of the a[x] function ? I don't understand that code. How do you add the random position and velocity ? And is x the time t ? – Cham Apr 05 '16 at 12:31
  • a[x] is the "velocity" of the particle, the slope of the straight line. The velocity changes direction each time it collides (or passes) through a boundary. The velocity is either positive or negative depending on how many times it "collides". x is time, to start in another position just start from another random time. The random velocity you can get it by multiplying x by some constant. For one particle you can always adimensionalise such that v = 1 as my case. The logic of the code is that a gas of non-interacting particles is boring since they actually move in straight lines. Somehow. – tsuresuregusa Apr 05 '16 at 13:05
  • I'm not sure to understand. In 3D, use the random functions X0 and V0 that I defined in my code above. Then the trajectory is given by this instantaneous position (before the first collision) : X[t_, n_, r_] := X0[n, r] + V0[n, r] t (instead of using an inefficient NDSolve code). How should I modify this to take all collisions into account ? – Cham Apr 05 '16 at 13:12
  • 3D is just 3 independent 1D problems, so I keep focusing on 1D. The trajectory of the particles is as you say x0 + v t in between collisions. When particles collide with the walls they invert their velocity (that's what a[x] is for in my code). If you want to account for the collision between particles, you need to move your system from collision to collision, since between collision you know the analytical solution. Furthermore, the collision time between particles is easy to compute, just a quadratic equation, so those times are also analytic. If I find time today I try to code this. – tsuresuregusa Apr 05 '16 at 13:27
  • @tsuresuregusa, how can I adapt your method to my case, with my specific random initial conditions (position and velocity, all in 3D) ? It's really not obvious to me. – Cham Apr 06 '16 at 01:29
  • Clear[a, x] L = 1 a[x_] := -1 + 2 Boole@OddQ@Quotient[x, L]; x[t_] := t + .3; x2[t_] := 2.43 t; x3[t_] := \[Pi] t; ParametricPlot3D[{Mod[a[ x3[t]] x3[t], L], Mod[a[ x2[t]] x2[t], L], Mod[a[x[t]] x[t], L]}, {t, 0, 3}] does that give you the idea? – tsuresuregusa Apr 06 '16 at 01:52
  • @tsuresuregusa, the problem with your code is I cannot understand it. Currently, it's all gribberish to me. I don't see its physical sense. Please, use some physical symbols : "v" for velocity, instead of "a", "t" for time, instead of "x". "x", "y" and "z" for the cartesian coordinates, etc. Or else, it's very hard to know what your code is doing. – Cham Apr 06 '16 at 02:32
  • @tsuresuregusa, please, could you explain in details what is a[x_] := -1 + 2 Boole[OddQ[Quotient[x, L]]]? Why -1 ? Why Boole[OddQ[, etc ? – Cham Apr 06 '16 at 02:37
  • @tsuresuregusa, another important point : Can we show the time evolution with your code ? Not just the static trajectories ? We should be able to see the particles moving from their initial conditions, with the same time parameter. It is not clear that adding a random value to "t", for a particle, and multiplying another random value to "t" for another particle, would give the proper evolution in time. The interpretation is very difficult. Your code should allow to use the X0and V0 I defined in my question above, since each particle has a label. – Cham Apr 06 '16 at 02:42
  • @tsuresuregusa, your last code is easier to understand. But the particles don't start at the same time in the manipulate box (tf = 0). I still don't see how to use my X0 and V0 (initial conditions, at t = 0). Why can't I set t = 0 in the manipulate box ? – Cham Apr 06 '16 at 03:23
  • X0 = pos, V0 = vel, thought was clear enough. They don't start at tf=0 because there is singularity there. Make tf = 0.0001 or so – tsuresuregusa Apr 06 '16 at 03:25
  • let me correct, there is a singularity in the animate. The functions are all well defined, try it yourself. shall we move to chat? – tsuresuregusa Apr 06 '16 at 03:30
  • @tsuresuregusa, it's working ! It's alive ! 8-) I found the mistake this morning, in my adaptation of last night. A variable was missing, and also the particles weren't evolving inside my centered box ! My box was centered on the axes origin, while your code place the box with a corner on the origin. I've fixed this. The results are pretty good ! – Cham Apr 06 '16 at 13:24
  • @tsuresuregusa, now I need to understand what your a[x_, L_] is doing in details. Please, could you explain the Quotient, the OddQ and the Boole? Why this particular russian dolls configuration ? I would like to give this special function a proper name, instead of just "a" ! – Cham Apr 06 '16 at 13:27
  • 1
    there is maybe a nicer way of doing it, but what the quotient does is to "count" how many times the particle has crossed the boundary. Remember the whole idea is based the particle moves from its initial position to infinity. When you know how many times had "crossed" a boundary you know when to change the velocity. That's what the Bole@OddQ does, whit gives you 0 or 1, but you want -1 and 1 (the velocity is reflected completely with each collision) so in fact a is the sign of the velocity as a function of time. Maybe plot a[t] as a function of t so you get the idea. – tsuresuregusa Apr 06 '16 at 13:34
  • @tsuresuregusa, thanks for the explanations. I think it is clearer now. You should add this to your answer. The results are really superb ! If there's a nicer way of doing this, please, let us know ! – Cham Apr 06 '16 at 14:02
  • And see the second picture I've added to my question ! ;-) – Cham Apr 06 '16 at 14:03
  • glad to hear you got it. I will try to do the event-driven version but seems slightly more complicated than what I first thought (to make it elegantly that is), doing a for loop is kind of trivial. I let you know. – tsuresuregusa Apr 06 '16 at 15:04
  • @tsuresuregusa, how would you modify your function Mod[a[xx[t, u, v], L]xx[t, u, v], L]to allow *periodic boundaries conditions* ? I.e. one particle that hit a wall disapear and reappears imediately on the opposite wall ? It may be cool to mix bounces on some walls, and periodic conditions on other walls. – Cham Apr 07 '16 at 20:43
  • 1
    skip a[x] in that dimension and add a Mod to the coordinate. With PBC the velocity doesn't change sign so only the position changes when crossing borders – tsuresuregusa Apr 07 '16 at 21:05
  • @tsuresuregusa, I now believe that you reversed the sign in your function "a". It should read 1 - 2 Bool... instead of -1 + 2Bool.... Am I right ? – Cham Apr 08 '16 at 03:18
  • @tsuresuregusa, how do you tell the total number of collisions between time $t = 0$ and time $t > 0$, from your function a ? – Cham Apr 09 '16 at 14:13
  • just take out the OddQ and the Quotient should be the answer you are looking for. About the sign of a, I may have made a mistake, just check that for x = L+eps the sign is negative. – tsuresuregusa Apr 09 '16 at 14:29
  • @tsuresuregusa, it doesn't work always. Somtimes it gives a negative value, and sometimes it adds up to some value, then decreases to 0. – Cham Apr 09 '16 at 14:37
  • I had to add the absolute value. It appears to have solved the issue. – Cham Apr 09 '16 at 14:43
1

There's a very nice (and more complete) solution here :

http://community.wolfram.com/groups/-/m/t/490130

However, I don't understand that code. Maybe someone could built another solution from it, to be exposed here ?

Cham
  • 4,093
  • 21
  • 36