Mavi's Space System
Here's a quick post explain how Mavi deals with systems in spaces with different geometries and border conditions.
Defining a Space
A Space is defined by its geometry and how the borders (or walls) should behave, so there is a clear separation between geometry and wall behavior. Geometries are types derived from GeometryCfg, while walls are types derived from WallType. A space is simply the combination of a GeometryCfg and WallType, in code, this look like this
abstract type GeometryCfg end
abstract type WallType end
struct SpaceCfg{W<:WallType, G<:GeometryCfg}
wall_type::W
geometry_cfg::G
endwhen creating a system, one needs to provide some SpaceCfg.
For instance, we can create a rectangular geometry (with an arbitrary number of dimension) and periodic walls in the fallowing way
using StaticArrays # Needed to use SVector
# "Rectangle" with N dimensions using the number type T
struct RectangleCfg{N, T<:Number} <: GeometryCfg
bottom_left::SVector{N, T}
size::SVector{N, T} # size[i] is the rectangle's length in the i'th dimension
end
struct PeriodicWalls <: WallType end the actual code dealing with this kind of space should be in a specialized function, see next section.
Dealing with Walls Behavior
Walls collisions should be resolved in the function walls!(system, space_cfg::SpaceCfg), this function should dispatch on every kind of space there exists, for instance, with a rectangular geometry and periodic walls, the following method should be created.
function walls!(system, space_cfg::SpaceCfg{PeriodicWalls, RectangleCfg{N, T}}) where {N, T}
# code to resolve walls collisions herewalls! must modify particles positions when necessary. Usually, walls! should be called after the update of positions by the equations of motion.
Calculating Distances
Sometimes we need to know the information about the space to calculate distances (such as periodic boundaries with rectangle geometry), so there exists the function calc_diff(r1, r2, space_cfg), which calculates the difference between positions r1 and r2, and should dispatch on space_cfg. For instance,
# Default behavior
function calc_diff(r1, r2, space_cfg)
@inbounds dr = r1 - r2
return dr
end
# Periodic boundaries with rectangle geometry
function calc_diff(r1, r2, space_cfg::SpaceCfg{PeriodicWalls, G}) where G <: RectangleCfg
size_vec = space_cfg.geometry_cfg.size
dr = r1 - r2
dr = dr - (abs.(dr) .> (size_vec / 2)) .* copysign.(size_vec, dr)
return dr
endUsing Multiple Spaces
It is possible to use multiple spaces, this is useful to insert obstacles in the simulation or create complex geometries using simple shapes. This functionality is accomplished by defining the types
struct ManyGeometries{G <: Tuple} <: GeometryCfg
list::G
end
struct ManyWalls{W <: Tuple} <: WallType
list::W
end then, the walls behavior can simply be done like this
function walls!(system::System, space_cfg::SpaceCfg{W, G}) where {W <: ManyWalls, G <: ManyGeometries}
for (wall_cfg, geom_cfg) in zip(space_cfg.wall_type.list, space_cfg.geometry_cfg.list)
walls!(system, SpaceCfg(wall_cfg, geom_cfg))
end
endto make life easier for users, there exists a constructor for SpaceCfg to create composite spaces
# `spaces_cfg_list` must be a list of pairs of type (WallType, GeometryCfg).
function SpaceCfg(spaces_cfg_list)
walls = []
geometries = []
for (w, g) in spaces_cfg_list
push!(walls, w)
push!(geometries, g)
end
SpaceCfg(
wall_type=ManyWalls(Tuple(walls)),
geometry_cfg=ManyGeometries(Tuple(geometries)),
)
endin fact, users should always use this constructor, because it ensures that the list of geometries and walls are in the correct order.
OBS: Some functions in Mavi needs to know information about the space where particles are, therefore, when using multiple spaces, this information becomes not defied (because some spaces might just be obstacles). To mitigate this, the first space, in a space with multiple spaces, is considered as the "main" one.