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[ ...

equation solving - Invert and fit implicitly defined curve

I need to fit an implicitly defined curve. I thought I could get some data out of Solve , and then using FindFit . Therefore, I would like to find the relation the parametric curve defined by $F(x,y)=0$: Solve[-(1/2) + 1/2 (0.41202 BesselK[0, 0.1 Sqrt[x^2 + y^2]] + (0.101483 x BesselK[1, 0.1 Sqrt[x^2 + y^2]])/Sqrt[x^2 + y^2]) == 0, y] But I can't get an output: Solve was unable to solve the system with inexact coefficients or the system obtained by direct rationalization of inexact numbers present in the system. Since many of the methods used by Solve require exact input, providing Solve with an exact version of the system may help. >> Edit: In particular, I would like to fit the data coming from the curve with the expression of another curve, and not with a function $f(x)$. In particular, since this clearly looks like a cardioid , I would like it to fit to something like it. What other strategies could I try?

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...