classDiagram direction LR class System{ %% particle_number %% noise_strength %%list~Particle~ particles %%update_rule() } class Box { %%size %%boundary_conditions } class Particle { %%position %%orientation } System --> Box : contains System "1" --> "N" Particle : contains
Completing the OOP design of the project
Aims
- Complete the skeleton of the object-oriented structure of the project
Learning outcomes
- Constructing combinations of objects
- Constructing vectors of objects
More classes
Up to now, we only create one class, the System
class. We could maybe write all out code inside the system class, but this would largely defeat the point of object-oriented programming.
The idea behind this programming paradigm is to organise the code into smaller units that can be quite autonomous but which can interact with each other.
Earlier we drew the following sketch
In the previous part of this workshop we addressed only one of these objects, the System
class.
We now proceed more quickly, using the same ideas, to construct the two remaining objects and see how to integrate them with our original System
class.
Here, there will be less detailed code and more high level description of what the classes do: it will be for you to translate these design choices int actual code.
Constructing the Box
The system is simulated in a two-dimensional domain. We decide the domain to be a square box. However, for the sake of generality, let our Box
class represent an arbitrary rectangular domain.
The rectangle should have are
- the side in the x direction
- the side in the y direction
- the coordinates of the origin (bottom left corner)
In this case we want to make these properties accessible only via the usage of member functions: we want them to be private member variables. This avoids possible accidental changes, making the code more robust.
This means that, when we define the Box
class, we will use the following syntax
private:
double sidex;
double sidey;
double x0;
double y0;
To set the values of these variables or get their values we need dedicated member functions, conventionally called setters and getters. These, obviously, have to be public.
public:
void setSides(double lx, double ly);
void setOrigin(double x, double y);
double getSidex();
double getSidey();
Multiple constructors
C++ can be initialised i various ways using different constructors. Indeed, in the same class definition, we can have both a default constructor (e.g. without parameters) and various parametrised constructors. A minimal example follows:
public:
(); //default constructor with no parameters
Box(double lx, double ly, double x0, double y0); //parametrised constructor Box
Both will require a suitable implementation in the cpp
file:
::Box(){
Box// an empty constructor
}
::Box(double lx, double ly, double x0, double y0){
Box//this constructor actually assigns values
this->sidex = lx;
// ...
}
Constructing a Particle
The final object we are interested in possibly the most important: it is the particle, representing the individual agent of the Vicsek model.
The model uses point particles with an associated velocity vector of fixed magnitude but variable angle. This vectors move in space and eventually interact with each other within a radius of interaction \(r\).
Therefore, we simply want a new class that describes objects that contain:
- an
x
coordinate - a
y
coordinate - an angle
theta
- a velocity magnitude
v
- a radius of interaction
r
We shall have all these variables public
for simplicity.
We should also decide how these properties get initialised when we create a new particle. We have many options, but the simplest choice could be to assign default values via the default constructor.
Reasonable choices are the following:
- zero both
x
,y
andtheta
- set
r
as the unit of length - set
v
to0.5
This can be directly done in the implementation of the default constructor Particle()
, with no parameters.
Creating a vector
of particles
C++ has an excellent standard library with multiple data structures that simplify coding in C++. One kwy datastructure is the vector
which is accessed via
#include <vector>
As you have seen in the Introduction to C++ part of the course, we can create vectors of any kind:
- vectors of integers:
std::vector<int> vec; // Creates a
- vectors of floating point numbers:
std::vector<double> vec(3, 5.0); // Creates a vector of size 10, all elements initialized to 5.0
In principle, however, we can make vectors out of any type, including the Particle
class that we have just defined, using
std::vector<Particle> particles;
We can also just declare a vector and then resize it:
std::vector<Particle> particles; // Creates an empty vector of particles
.resize(10); // Resizes the vector to hold 10 particles, all initialised by their default constructor particles
Vectors are useful for two reasons:
- they manage memory allocation and deallocation automatically
- they can resized dynamically, unlike raw arrays which have fixed sizes.
From our point of view, they are also conceptually very close to Python’s lists. Therefore we are using vectors to simplify or coding.
Random initial state
The last thing that our initialisation of the model needs is a correctly initialise pseudo-random number generator.
Again, the standard library provides us with good tools. A <random>
model of the library exists with very good generators.
The paradigm is similar to random number generators in other languages such as Python with numpy: we declare a genertor object and associate it with a particular kind of pseudo-random generator (in this case the Mersenne twister, which has a long period )
#include <random>
int seed = 1234;
std::mt19937 gen = std::mt19937(seed);
Random numbers from arbitrary distributions are then sampled by first creating a distribution object (for example a uniform distribution between 0 and 1)
std::uniform_real_distribution<double> uniformDist;
and then sampling values from it by pass the random generator in input
double randomValue = uniformDist(gen);
In our code, we need a sampler of the uniform distribution to sample the particle position (and also to perform the noisy dynamics of the Vicsek model). So, it is useful to expand our System
class to include a random number generator, initialised in the constructor and a suitable member function to sample uniformly distributed random numbers.