Skip to main content

differential equations - Detecting a collision in n-body simulation with NDSolve


Consider this three-body system:


g = 10; (* gravitational constant *)
m = {100, 1, 1}; (* masses *)
μ0 = g*m; (* gravitational parameters *)
r0 = {{0, 0, 0}, {1, 0, 0}, {-1.1, 0, 0}}; (* initial location vectors *)
v0 = {{0, 0, 0}, {0, 30, 0}, {0, -30.1, 0}}; (* initial velocity vectors *)


Differential equations for velocities and positions:


jmax = Length[m];
v = Evaluate[#] & /@
Table[ToExpression["v" <> ToString[j]], {j, jmax}];
r = Evaluate[#] & /@
Table[ToExpression["r" <> ToString[j]], {j, jmax}];
eqns[μ_List] := Flatten[Table[{
v[[j]]'[t] ==
Sum[(-Normalize[r[[j]][t] - r[[i]][t]]*μ[[i]])/
EuclideanDistance[r[[j]][t], r[[i]][t]]^2, {i,

Delete[Range[jmax], j]}],
r[[j]]'[t] == v[[j]][t],
v[[j]][0] == v0[[j]],
r[[j]][0] == r0[[j]]
}, {j, jmax}]]
fns = Flatten[Table[{v[[j]][t], r[[j]][t]}, {j, jmax}]]

The numerical integration stops when two bodies collide:


μ = μ0;
sol = NDSolve[eqns[μ], fns, {t, 0, 10}][[1]]

vel = (fns /. sol)[[1 ;; -2 ;; 2]];
pos = vel = (fns /. sol)[[2 ;; -1 ;; 2]];


NDSolve::ndsz: At t == 2.393483348087306`, step size is effectively zero; singularity or stiff system suspected. >>



The solution:


tmax = 2.393483348087306;
ParametricPlot3D[pos, {t, tmax - .1, tmax},
PlotRange -> {All, All, {-.01, .01}}, ImageSize -> 800,

PlotPoints -> 500, MaxRecursion -> 15]

collision plot


I would like to detect collisions and modify the computation so that the masses (and in effect, gravitational parameters) and velocities of the collided bodies change: one body's new mass becomes the sum of both masses and the new velocity becomes the sum of both velocities; the other body's new mass and velocity become zero.


I have tried to do it with WhenEvent:


μ = μ0;
sol = NDSolve[
Append[eqns[μ],
WhenEvent[Length[$MessageList] > 0, Print[tmax = t];
dist =

Table[EuclideanDistance[r[[j + 1]][t], r[[j]][t]], {j,
Length[r] - 1}];
distDif = Table[Abs[dist[[n + 1]] - dist[[n]]], {n, Length[dist] - 1}];
p = Position[distDif, Min[distDif]][[1, 1]];
μ[[p]] += μ[[p + 1]]; μ[[p + 1]] = 0;
v[[p]][t] += v[[p + 1]][t]; v[[p + 1]][t] = 0;
"RestartIntegration"]], fns, {t, 0, 10}][[1]]
vel = (fns /. sol)[[1 ;; -2 ;; 2]];
pos = vel = (fns /. sol)[[2 ;; -1 ;; 2]];


However, this does not work. It seems that I do not catch the error properly, the program aborts before the message can be caught. How to catch this properly?



Answer



As mentioned in my comment I think it makes more sense if you try to detect the collision by investigating the current positions than examining $Messages. Here is how this can be done by introducing additional discrete variables for the masses:


g = 10;
m0 = {100, 1, 1};
r0 = {{0, 0, 0}, {1, 0, 0}, {-1.1, 0, 0}};
v0 = {{0, 0, 0}, {0, 30, 0}, {0, -30.1, 0}};

numbodies = Length[m0];


odesys = Table[{
v[j]'[t] == UnitStep[m[j][t] - 0.01]*Sum[
(-Normalize[r[j][t]-r[i][t]]*g*m[i][t])/EuclideanDistance[r[j][t], r[i][t]]^2,
{i, Delete[Range[numbodies], j]}
],
r[j]'[t] == v[j][t],
v[j][0] == v0[[j]],
r[j][0] == r0[[j]]
},
{j, numbodies}

];

initialmasses = Table[m[k][0] == m0[[k]], {k, numbodies}];

whenevnt =
With[{rmin =
Min[EuclideanDistance @@@
Subsets[r[#][t] & /@ Range[numbodies], {2}]],
numbodies = numbodies},
WhenEvent[rmin < 0.0001,

pids =
SortBy[Subsets[Range[numbodies], {2}],
EuclideanDistance[r[#[[1]]][t], r[#[[2]]][t]] &][[1]];
Print["collision at t=", t, " bodies:", pids];
{m[pids[[1]]][t], v[pids[[1]]][t], m[pids[[2]]][t],
v[pids[[2]]][t]} -> {
Total[
m[#][t] & /@ pids], (m[#][t] & /@ pids).(v[#][t] & /@ pids)/
Total[m[#][t] & /@ pids],
0, {0, 0, 0}

}
]
];

depvars = Flatten[Table[{v[j], r[j]}, {j, numbodies}]];

sol = NDSolve[
Flatten[{odesys, initialmasses, whenevnt}],
depvars, {t, 0, 5},
DiscreteVariables -> Table[m[k], {k, numbodies}]

][[1]];

vel = Array[v, {3}] /. sol;
pos = Array[r, {3}] /. sol;
tmax = (Head[r[1][t] /. sol]@"Domain")[[1, 2]]

Animate[
Show[
ParametricPlot3D[Evaluate[#@tau & /@ pos], {tau, t - 0.1, t},
PlotRange -> {3*{-1, 1}, 3*{-1, 1}, {-.01, .01}},

ImageSize -> 800, PlotPoints -> 500, MaxRecursion -> 15,
Boxed -> False, Axes -> False,
Background -> Black
],
Graphics3D[{White, PointSize[Large],
Point[pos[[#]][t] & /@ Range[numbodies]]}]
],
{t, 0.1, tmax},
AnimationRepetitions -> 1, AnimationRate -> 0.5
]


Except for the mentioned change in the WhenEvent I have also made the following changes:


I am using expressions like r[1] instead of symbols like r1 as variables. It is one of the great things about NDSolve that this works and it of course makes the programmatic generation of variables much easier.


I have also changed the equations slightly: by setting one of the colliding masses to zero you want to make that effectively not interact anymore. That will fail because your equations implicitly were divided by m, which of course goes wrong when m=0. I have corrected that in a somewhat ad hoc way by using a UnitStep[m-eps], where you need to adjust eps so that it is smaller than any mass you want to use. Without that term, the zero-mass body would still see the forces of the others and that will make NDSolve stop because of "effectively zero stepsize" as before.


Of course for the general case you should add momentum, not velocities which I have also changed from your original code. For your particular case (nonrelativistic with equal masses colliding) it doesn't make a difference, though.


For the collision detection I have also used an arbitrary "small" number which in reality proably has to do something with the extent of the bodies and should be adjusted accordingly...


I also understand your units and the value of g to be arbitrary, and expect you are aware that you should use the gravitational constant (usually denoted G), not the free fall acceleration (usually g) in those equations...


Comments

Popular posts from this blog

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...

Is there a way to do conditional matrix loop using 'continue'

I have the following: n = 3; m = 5; ww = RandomReal[{0, 0.1}, {n, n}]; uu = RandomReal[{0, 1}, {m, n}]; pp = RandomReal[{0, 1}, {n, n}]; ss = RandomInteger[{0, 5}, {m, n}]; Grid[{{"ww", "uu", "pp", "ss"}, {ww // TableForm, uu // TableForm, pp // TableForm, ss // TableForm}}, Spacings -> {5, 2}, Dividers -> All] where I would like to look at every element of matrix ss and produce a matrix tt , with zeroes at the locations in ss which have zeroes, and in all other positions do the following: tt = (-1/Subscript[ww, m]) Log[(1 - uu)/(Subscript[pp, m - 1])], where Subscript[ww, m] is the value at index of ww matrix and where Subscript[pp, m - 1] is the value at index-1 of pp matrix. So for example if the first value ever read from matrix ss happens to be 2, then value taken from matrix ww would be from the row 2, but from pp would be from row 1. Also how to tell difference between a 0 as a valid value from within the matrix elemen...