Examples
The example/test.jl
script provides a starting point for understanding how to set up and run a basic simulation.
Running the code typically consists of the following steps: initialize a small number of particles, define simulation parameters, and run the simulation for a set number of steps.
Here's a conceptual example of how to use the main module and run a simulation:
# Import necessary packages
using SelfPropelledVoronoi # The main package for the simulation
using StaticArrays # Provides SVector for efficient, statically-sized vectors (used here for 2D positions)
using Random # Provides random number generation capabilities, including MersenneTwister
# --- Define Simulation Domain and Particle Count ---
# N: Number of particles in the simulation
N = 20
# box_Lx: Length of the simulation box in the x-dimension
box_Lx = 10.0
# box_Ly: Length of the simulation box in the y-dimension
box_Ly = 10.0
# sim_box: Creates a SimulationBox object to store the dimensions of the 2D simulation domain.
# This object encapsulates the geometry (Lx, Ly) and implies periodic boundary conditions are used by the simulation logic.
sim_box = SimulationBox(box_Lx, box_Ly)
# --- Define Particle Properties (VoronoiCells) ---
# These parameters define the physical characteristics and behavior of the simulated particles,
# which are modeled as cells in a Voronoi tessellation.
# Each parameter is an array of length N, allowing for different properties for each particle if needed.
# The VoronoiCells struct groups these properties.
# p0: Target perimeter for each particle (Voronoi cell).
# Cells will experience an elastic force resisting deviations from this preferred perimeter.
# units: length units, consistent with box_Lx, box_Ly.
p0 = 3.8 * ones(N) # 'ones(N)' creates a vector of N elements, all set to 1.0, then scaled by 3.8.
# A0: Target area for each particle (Voronoi cell).
# Similar to p0, deviations from this preferred area will result in an elastic restoring force.
# units: area units (length units squared).
A0 = 1.0 * ones(N)
# KP: Perimeter spring constant (or stiffness).
# Determines the strength of the force resisting changes to the cell's perimeter. Higher KP means more resistance.
# units: energy / length^2 or force / length.
KP = 1.0 * ones(N)
# KA: Area spring constant (or stiffness).
# Determines the strength of the force resisting changes to the cell's area. Higher KA means more resistance.
# units: energy / area^2 or force / area.
KA = 1.0 * ones(N)
# f0: Active force strength (or self-propulsion force magnitude).
# The magnitude of the intrinsic force that drives each particle's movement. The direction is determined by the orientation of the particle.
# units: force units.
f0 = 1.0 * ones(N)
# Dr: Rotational diffusion rate.
# Controls the rate at which the orientation of particles changes randomly due to rotational noise.
# units: radians^2 / time
Dr = 0.1 * ones(N)
# particles: Creates a VoronoiCells object to store the collective properties of all particles.
# The arguments correspond to target_perimeters, target_areas, K_P, K_A, active_force_strengths, and rotational_diffusion_constants respectively.
particles = VoronoiCells(p0, A0, KP, KA, f0, Dr)
# --- Define Simulation Parameters ---
# params: A ParameterStruct structure holding various global simulation settings and constants.
# The constructor uses keyword arguments.
params = ParameterStruct(
# Number of particles (already defined)
N = N,
# Time step for the numerical integration of equations of motion.
# Smaller values generally lead to higher accuracy but increase computation time.
# units: time units.
dt = 0.001,
# Total number of simulation steps to perform. The total simulation time will be dt * N_steps.
N_steps = 1000,
# Thermal energy, product of Boltzmann constant (kB) and Temperature (T).
# This term scales the magnitude of random forces due to thermal fluctuations
# units: energy units.
kBT = 1.0,
# Friction coefficient (gamma). Affects the mobility of the cells (the ratio of the force on the cell and its velocity)
frictionconstant = 1.0,
# Depth of the layer used for handling interactions across periodic boundaries.
# Particles within this distance from a boundary will interact with periodic images of particles
# from the opposite side of the simulation box. This value should typically be larger than any interaction cutoffs.
# units: length units.
periodic_boundary_layer_depth = 2.5,
# If true, the simulation prints progress messages, warnings, or other information to the console.
verbose = true,
# The SimulationBox object defining the domain (defined above).
box = sim_box,
# The VoronoiCells object defining particle properties (defined above).
particles = particles,
# A DumpInfo structure for controlling data output (e.g., saving simulation snapshots).
# Here, 'save=false' indicates that no data will be written to disk during this example run.
# Default DumpInfo settings might otherwise enable saving.
dump_info = DumpInfo(save=false),
# A user-defined function that is called at the start of every time step during the simulation.
# 'p', 'a', 'o' typically refer to the parameters, arrays, and output structs, respectively.
# Useful for custom actions like live plotting, on-the-fly analysis, or complex data collection.
# Here, it's a no-operation (no-op) function, meaning nothing custom is done during simulation steps.
callback = (p,a,o) -> nothing,
# Random Number Generator (RNG) instance.
# `MersenneTwister` is a widely used pseudo-random number generator known for its good statistical properties.
# Initializing it with a specific seed (1234 here) ensures that the sequence of random numbers
# generated will be the same every time the simulation is run with this seed. This is crucial for reproducibility.
RNG = Random.MersenneTwister(1234)
)
# --- Initialize Particle Positions and Orientations ---
# arrays: An ArrayStruct structure to hold the dynamic state variables of particles (e.g., positions, orientations)
# that change over time during the simulation.
arrays = ArrayStruct(N)
# Initialize positions and orientations randomly:
for i in 1:N
# arrays.positions[i]: Stores the 2D position (x, y coordinates) of particle 'i'.
# `SVector` (StaticVector) from the `StaticArrays.jl` package is used here.
# SVectors are stack-allocated, fixed-size arrays that can provide performance benefits for small vectors
# (like 2D or 3D coordinates) due to better memory locality and enabling certain compiler optimizations.
# Positions are initialized randomly within the simulation box dimensions (box_Lx, box_Ly),
# using the provided RNG from the params struct for reproducibility.
arrays.positions[i] = SVector(rand(params.RNG)*box_Lx, rand(params.RNG)*box_Ly)
# arrays.orientations[i]: Stores the orientation of particle 'i'.
# This is represented as an angle in radians (e.g., from 0 to 2*pi).
# The orientation determines the direction of the self-propulsion force f0.
# Orientations are initialized randomly between 0 and 2*pi using the provided RNG.
arrays.orientations[i] = 2pi * rand(params.RNG)
end
# --- Initialize Output Structure ---
# output: An Output structure designed to store summary statistics
# accumulated during the simulation (e.g., total potential energy, steps completed).
output = Output()
# --- Run the Simulation ---
# run_simulation!: This function executes the main simulation loop.
# It takes the simulation parameters (params), the initial state of particle arrays (arrays),
# and the output structure (output) as input.
# The '!' at the end of the function name is a Julia convention indicating that
# the function is likely to modify one or more of its arguments in-place. In this case,
# 'arrays' (particle positions/orientations) will be updated at each step, and
# 'output' will be populated with simulation results like `steps_done`.
run_simulation!(params, arrays, output)
# --- Print Simulation Summary ---
# After the simulation completes, print a message indicating completion and
# the total number of steps that were actually performed (which should be stored in output.steps_done).
println("Simulation finished. Steps done: $(output.steps_done)")