How to use the Rings system

To use the rings system we need to do the following:

  1. Define dynamical parameters (interaction parameters such as springs stiffness, activity, etc)
  2. Create initial condition (positions and polarizations at initial time)
  3. Define space configuration (geometry plus boundary behavior)
  4. Set numerical parameters (time step size, box size for space partitioning, etc)

let's address one item at a time.

1. Dynamical parameters

We need to configure the interaction potential between particles and the rings properties, in code this looks like this

# Using the module which contains things related
# to model configurations
using Mavi.Rings.Configs

# Creating an object which contains the parameters related
# to how particles interact. Specifically, we are
# creating configurations for a harmonic truncated
# potential.
interaction_cfg = HarmTruncCfg(
    k_rep=20,
    k_atr=4,
    dist_eq=1,
    dist_max=1 + 0.2,
)

# Creating the object which contains all
# dynamical configurations
dynamic_cfg = RingsCfg(
    p0=3.5,
    relax_time=1,
    vo=1,
    mobility=1,
    rot_diff=0.05,
    k_area=1,
    k_spring=20,
    l_spring=1,
    interaction_finder=interaction_cfg,
)

2. Initial Condition

We need to specify the initial particles positions and rings polarizations. These two quantities are used to create an object of type RingsState. We can do that manually or use a helper function such as rectangle_grid, which returns rings positioned in the vertices of a rectangular grid (this function also returns a geometry configuration, which we will talk about in the next section). Use example

using Mavi.Rings.States
using Mavi.Rings.InitStates

num_cols = 3
num_rows = 3

rings_pos, geometry_cfg = rectangular_grid(
    num_cols=num_cols, # How many columns the grid has
    num_rows=num_rows, # How many rows the grid has
    num_particles=10, # How many particles each ring has
    p_radius=1, # Rings particles radius
    pad_x=0.1, pad_y=0.1, # Spacing between rings in the x and y direction
)

# Creating the initial state
num_rings = num_cols * num_rows
state = RingsState(
    rings_pos=rings_pos,
    pol=random_pol(num_rings), # creating random polarizations using the helper function `random_pol` 
)

OBS: Usually, the particle radius is determined by the dynamical configuration, so one can use the function Configs.particle_radius (which is available if we are using Mavi.Rings.Configs) to get the particle radius. Ex: pr = Configs.particle_radius(dynamic_cfg) where dynamic_cfg is the object we created in section 1

3. Space configuration

A space configuration in the combination of a geometry configuration and boundary behavior, for instance, if we want a rectangular geometry with periodic boundaries, we would do the following

using Mavi.Rings.Configs

geometry_cfg = Configs.RectangleCfg(
    length=10,
    height=10,
)

wall_type = Configs.PeriodicWalls()

space_cfg = Configs.SpaceCfg(
    geometry_cfg=geometry_cfg,
    wall_type=wall_type,
)

the helper function rectangular_grid also returns a rectangular geometry configuration which contains all rings created, so we can use that and only set the boundary behavior


rings_pos, geometry_cfg = rectangular_grid(...)

space_cfg = Configs.SpaceCfg(
    geometry_cfg=geometry_cfg,
    wall_type=Configs.PeriodicWalls(),
)

OBS: three dots in rectangular_grid represent omitted code.

4. Numerical parameters

The last thing we need to do is to set numerical parameters (parameters used to perform the numerical integration). The minimum set of parameters we need to configure is the time step

using Mavi.Rings.Configs

int_cfg = RingsIntCfg(dt=0.01)

but it is very beneficial to performance the use of a chunks technique (space is partitioned into chunks and interactions are only computed between neighboring chunks). For that, we only need to specify the chunks size, which should be at least the size of the maximum interaction distance (which we can get from dynamic_cfg)

using Mavi.Rings.Configs

interaction_cfg = dynamic_cfg.interaction_finder # This is the interaction potential configuration between particles

bbox = space_cfg.geometry_cfg # bbox stands for bounding box
max_size = interaction_cfg.dist_max * 1.1 # Setting the size to be a little bit bigger than the maximum interaction distance just to be safe

# Calculating how many chunks we want in each direction
num_x_chunks = floor(Int, bbox.length / max_size)
num_y_chunks = floor(Int, bbox.height / max_size)

p_chunks_cfg = Configs.ChunksCfg(num_x_chunks, num_y_chunks)

# Creating the integration configurations object
int_cfg = RingsIntCfg(
    dt=0.01,
    p_chunks_cfg=p_chunks_cfg,
)

Visualizing the simulation

Now we can put everything together in a RingsSystem and start the simulation with real-time rendering

# Importing everything we need
using Mavi.Rings
using Mavi.Rings.Configs
using Mavi.Rings.InitStates

using Mavi.Visualization

# =
# Dynamical Configuration
# =

interaction_cfg = HarmTruncCfg(
    k_rep=20,
    k_atr=4,
    dist_eq=1,
    dist_max=1 + 0.2,
)

dynamic_cfg = RingsCfg(
    p0=3.5,
    relax_time=1,
    vo=1,
    mobility=1,
    rot_diff=0.05,
    k_area=1,
    k_spring=20,
    l_spring=1,
    interaction_finder=interaction_cfg,
)

# =
# Initial State
# =

num_cols = 3
num_rows = 3

rings_pos, geometry_cfg = rectangular_grid(
    num_cols=num_cols, # How many columns the grid has
    num_rows=num_rows, # How many rows the grid has
    num_particles=10, # How many particles each ring has
    p_radius=Configs.particle_radius(dynamic_cfg), # Rings particles radius
    pad_x=0.1, pad_y=0.1, # Spacing between rings in the x and y direction
)

num_rings = num_cols * num_rows
state = RingsState(
    rings_pos=rings_pos,
    pol=random_pol(num_rings), # creating random polarizations using the helper function `random_pol` 
)

# =
# Space Configuration
# =

space_cfg = Configs.SpaceCfg(
    geometry_cfg=geometry_cfg,
    wall_type=Configs.PeriodicWalls(),
)

# =
# Integration Configuration
# =

interaction_cfg = dynamic_cfg.interaction_finder # This is the interaction potential configuration between particles

bbox = space_cfg.geometry_cfg # bbox stands for bounding box
max_size = interaction_cfg.dist_max * 1.1 # Setting the size to be a little big bigger than the maximum interaction distance just to be safe

# Calculating how many chunks we want in each direction
num_x_chunks = floor(Int, bbox.length / max_size)
num_y_chunks = floor(Int, bbox.height / max_size)

p_chunks_cfg = Configs.ChunksCfg(num_x_chunks, num_y_chunks)


# Creating the integration configurations object
int_cfg = RingsIntCfg(
    dt=0.01,
    p_chunks_cfg=p_chunks_cfg,
)

# = 
# Creating the System
# = 

system = RingsSystem(
    state=state,
    space_cfg=space_cfg,
    dynamic_cfg=dynamic_cfg,
    int_cfg=Configs.RingsIntCfg(
        dt=0.01,
        p_chunks_cfg=Configs.ChunksCfg(num_x_chunks, num_y_chunks),
        device=Configs.Threaded(),
    ),
)

# =
# Animating the system
# = 

animate(system)