Particle motion modeling, gravity visualization
Gravity is one of the fundamental forces of nature that determines the motion of celestial bodies and objects on their surface. However, gravity varies significantly on different planets of the Solar System: from 3.7 m/s2 on Mercury to 24.79 m/s2 on Jupiter. These differences are not just numbers in a textbook — they radically change the physics of the movement of objects.
In this article, we will analyze an interactive example that clearly demonstrates how the same particles behave under the influence of gravity of different planets. The example not only shows the physical principles, but also demonstrates the various possibilities of the language from calculations and visualization to optimization of calculations.
Let's take a step-by-step look at the code, starting with the basic physical constants and ending with the creation of an interactive visualization. This will help you understand not only the physics of gravity, but also the practical aspects of programming scientific simulations.
using Random
The first thing we'll look at is PLANET_GRAVITY, a dictionary is a constant containing the values of the acceleration of gravity (g) for various celestial bodies.
PLANET_GRAVITY = Dict(
"Mercury" => 3.7,
"Venus" => 8.87,
"Earth" => 9.81,
"Mars" => 3.711,
"Jupiter" => 24.79,
"Saturn" => 10.44,
"Uranium" => 8.69,
"Neptune" => 11.15,
"Moon" => 1.62
)
Further Particle It is a mutable structure that describes a single particle in a simulation.
Structure Fields:
-
x::Float32,y::Float32— coordinates of a particle in two-dimensional space- Used
Float32to save memory when processing multiple particles
- Used
-
vx::Float32,vy::Float32— components of the particle velocity along the X and Y axes- Determine the direction and speed of movement
-
color::Symbol— particle color for visualization- For example:
:red,:blue,:green
- For example:
mutable struct Particle
x::Float32
y::Float32
vx::Float32
vy::Float32
color::Symbol
end
The first function in the code is generate_same_particles() it creates 6 identical particles with fixed initial conditions:
-
Random.seed!(42)sets the seed of the random number generator, ensuring identical generation each time it is run -
Ranges of coordinates and velocities:
- X:
-3before3(rand() * 6 - 3) - Y:
2before4(rand() * 2 + 2) — the particles are created at the top - Vx:
-1before1(rand() * 2 - 1) — random horizontal velocity - Vy:
0— zero initial vertical velocity
- X:
-
Colors are assigned in order from a fixed list.
[:red, :blue, :green, :orange, :purple, :cyan]
The function returns an array of 6 particles Particle[], which will be used to compare gravity on different planets under the same initial conditions.
There is one interesting feature in the implementation, numbers with a suffix f0 (6f0, 3f0, 2f0, 0f0) is an explicit indication of the Float32 type to optimize performance. In Julia, floating-point numbers are the default (6.0) have a type Float64 (double precision). Using Float32 It provides two key advantages:
- Saving memory — everyone
Float32it takes 4 bytes instead of 8 bytes forFloat64 - Acceleration of calculations
For visualization and physical precision simulations Float32 (±7 decimal digits) is completely sufficient, which allows you to process more particles or animation frames in less time.
function generate_same_particles()
Random.seed!(42)
colors = [:red, :blue, :green, :orange, :purple, :cyan]
particles = Particle[]
for i in 1:6
push!(particles, Particle(
rand(Float32) * 6f0 - 3f0, # x: -3 to 3
rand(Float32) * 2f0 + 2f0, # y: 2 to 4
rand(Float32) * 2f0 - 1f0, # vx: -1 to 1
0f0, # vy: 0
colors[i] # fixed colors
))
end
return particles
end
Function simulate_planet_same_particles It is the core of a physical simulation that implements numerical integration of particle motion under the influence of gravity on a specific planet. It takes three key parameters: the value of gravitational acceleration g, an array of initial particles initial_particles and the time sampling parameters are a step dt and the number of steps steps.
At the beginning of the function, a deep copy of the original particles is created via deepcopy, which guarantees the independence of simulations for different planets when using the same initial conditions. Arrays are initialized to store complete motion paths x_traj and y_traj, the size of which corresponds to the total number of time slices. The main calculation is performed in a double cycle: the external one goes through time steps, the internal one goes through all the particles. For each particle, its current coordinates are first fixed, after which, if this is not the last step, a physical model is applied. The model includes two components: gravitational acceleration, which increases the vertical velocity of the particle according to the formula p.vy -= g * dt, and the kinematic update of the position via p.x += p.vx * dt and p.y += p.vy * dt.
Special attention is paid to the processing of boundary conditions. When colliding with the floor (when the coordinate y the particle is reflected with a loss of 20% of the vertical velocity and 10% of the horizontal velocity, which simulates an inelastic impact. Similarly, when the side walls are reached, the particle is reflected from them, losing 10% of the horizontal velocity. These coefficients ensure realistic attenuation of movement over time.
After processing all the particles in the current step, their coordinates are stored in the corresponding arrays of trajectories. At the end of the loop, the function returns three arrays: sequences of X- and Y-coordinates for all particles at all time steps, as well as a list of their colors for subsequent visualization. This data structure allows you not only to reproduce the motion animation, but also to analyze the evolution of the particle system under a given gravitational influence.
function simulate_planet_same_particles(g::Float32, initial_particles, dt::Float32, steps::Int)
particles = deepcopy(initial_particles)
n = length(particles)
x_traj = Vector{Vector{Float32}}(undef, steps+1)
y_traj = Vector{Vector{Float32}}(undef, steps+1)
colors = [p.color for p in particles]
for step in 0:steps
x_vals = Vector{Float32}(undef, n)
y_vals = Vector{Float32}(undef, n)
for i in 1:n
p = particles[i]
x_vals[i] = p.x
y_vals[i] = p.y
if step < steps
p.vy -= g * dt
p.x += p.vx * dt
p.y += p.vy * dt
if p.y < 0
p.y = 0
p.vy = -0.8f0 * p.vy
p.vx *= 0.9f0
end
if p.x > 4
p.x = 4
p.vx = -0.9f0 * p.vx
elseif p.x < -4
p.x = -4
p.vx = -0.9f0 * p.vx
end
end
end
x_traj[step+1] = copy(x_vals)
y_traj[step+1] = copy(y_vals)
end
return x_traj, y_traj, colors
end
The last block of code organizes the full cycle of the experiment, from setting parameters to generating the final animation. It starts with initializing the key variables: the number of particles n_particles it is set to 6, the time step dt it is fixed at 0.04 seconds (indicating the type Float32), and the total simulation time total_time it takes 10 seconds. Parameter skip_frames with a value of 2, it determines that every second frame will be used for animation, which reduces the size of the output file without significant loss of information content. List planets It includes nine celestial bodies for comparative analysis.
Based on the specified total time and the integration step, the number of time steps is calculated. steps according to the formula Int(floor(total_time / dt)). This ensures that the simulation will be completed exactly for the specified time period. Then using the function generate_same_particles() A reference set of six particles with fixed initial conditions is being created — this is the basis for a correct comparison of the effects of different gravity.
Next, the trajectory calculation cycle for each planet is started. For each celestial body from the dictionary PLANET_GRAVITY the corresponding value of the acceleration of free fall is extracted g, after which the function is called simulate_planet_same_particles. It returns arrays of trajectories, particle colors, and gravity values, which are stored in a list. planet_data. This creates a complete set of visualization data.
The final part of the block is responsible for creating animations using macros. @animate from the Plots library. The outer loop iterates through the frames, taking into account the skip, calculating the current model time. t. For each point in time, a general graph is constructed with a 3×3 layout, where each subgraph corresponds to one planet. Settings include a black background for contrast, a generic title, and dynamically updated time in the title.
The internal cycle processes each subgraph: fixed axis boundaries are set, a brown line representing the surface of the planet is added, and the particles are displayed in their current position while maintaining the color scheme. An important element is information annotations: the absolute value of gravity and its relation to terrestrial (the latter is highlighted in red for values above terrestrial and blue for lower values).
After generating all the frames, the animation is saved to a file. particles_all_planets.gif at a speed of 22 frames per second. The result is a visual comparative visualization that demonstrates how the same particle system behaves in different gravitational conditions of the Solar System.
println("A test for all planets")
dt = 0.04f0
total_time = 10.0f0
skip_frames = 2
planets = ["Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranium", "Neptune", "Moon"]
steps = Int(floor(total_time / dt))
println("Particle generation...")
same_particles = generate_same_particles()
println("Calculation of trajectories...")
planet_data = []
for planet in planets
g = Float32(PLANET_GRAVITY[planet])
data = simulate_planet_same_particles(g, same_particles, dt, steps)
push!(planet_data, (data..., g))
end
println("Creating animations...")
anim = @animate for frame in 0:skip_frames:steps
t = frame * dt
plot(layout = (3, 3),
size = (1200, 900),
title = "The same particles on different planets",
titlefontsize = 14,
plot_title = "Time: $(round(t, digits=2))",
plot_titlefontsize = 11,
legend = false,
bg = :black)
for (idx, planet) in enumerate(planets)
x_traj, y_traj, colors, g = planet_data[idx]
actual_frame = min(frame + 1, length(x_traj))
plot!(subplot = idx,
xlim = (-4.5, 4.5),
ylim = (-0.5, 5.5),
aspect_ratio = :equal,
title = planet,
titlefontsize = 10)
plot!([-4.5, 4.5], [0, 0],
linewidth = 3,
color = :brown,
subplot = idx)
scatter!(x_traj[actual_frame], y_traj[actual_frame],
markersize = 6,
markercolor = colors,
markerstrokecolor = :white,
alpha = 0.9,
subplot = idx)
gravity_ratio = round(g / 9.81, digits=2)
color_code = g > 9.81 ? :red : :lightblue
annotate!(3.0, 5.2,
text("g = $(g)", 8, :white),
subplot = idx)
annotate!(3.0, 4.8,
text("$(gravity_ratio)× Earth", 8, color_code),
subplot = idx)
end
end
gif(anim, "particles_all_planets.gif", fps = 22)
Conclusion
This example demonstrates how gravity determines the dynamics of physical systems: using numerical integration and optimized calculations, we have created a comparative simulation of particle motion on nine celestial bodies, where it is clearly visible that particles fall and bounce much faster on Jupiter than on the Moon or Mars.
