Skip to content

Object-Oriented Programming

Object-oriented programming (OOP) is a style of programming adopted by many programming languages (e.g. C++,Java, Python). It allows developers to create a user-defined data type known as a class. A user may create instances of these classes, known as objects, and use them to store and manipulate data. The contents of the class will define the data that is stored in instances of the class in member variables, and member functions which can access, manipulate, and perform calculations on the data.

A class can be very simple, such as a variable type (i.e. integer, double, array etc.) or very complex, such as a solver for non-linear problems. Every class comprises several components which are the data members, the member functions, the constructors and the destructor.

Inheritance

Depending on the application, a developer can create families of objects which usually have conceptual dependency and coherence among them. A class-family consists of a base class and its derived classes, which may further have their own subclasses. The base class serves as a representation of a general concept, such as a "human" with attributes like name, age, and height. The derived classes, in turn, embody more specific instances of that concept. For example, a child class might represent a "student" with additional attributes like grades and the number of extracurricular activities. These supplementary details, which may be irrelevant when characterizing another type of human, such as a retired professional, are encapsulated as member variables within the student class. The process of deriving a new child class based on a base class is called inheritance.

Object orientation in the SPH solver

In this project we chose object orientation which is widely supported by C++ in order to facilitate the implementation of the SPH algorithm and exploit all the benefits that accompany the use of OOP. The basic design idea is built around three classes.

Class Particles

The first class is used to represent the building blocks of the SPH approach, which are the particles. A particle, however, is not only relevant in the context of SPH, but it can also be used in other applications such as in the representation of multiphase flows where the particles can be droplets whose motion is two-way coupled with a carrier gas phase. Therefore, the Particles class was decided to be kept as simple (and generic) as possible and to encapsulate only the information which can be applicable to every application that incorporates the use of cluster of particles. Therefore, the members of the "particles" class are only related to the particles' positions and velocities.

It is important to emphasize that an instance of the Particles class contains data pertaining to all the particles involved in the simulation (and therefore represents the entirety of the cluster), rather than information exclusive to a single particle.

/* **************************** particles.h **************************** */
class Particles {

  ...

 protected:
  unsigned int nbParticles;  // number of particles and characteristic size of
                             // the class arrays

  // Positions
  std::vector<double> positionX;
  std::vector<double> positionY;

  // Velocities
  std::vector<double> velocityX;
  std::vector<double> velocityY;

  std::vector<double> particleSpeedSq;  // u(i)^2+v(i)^2


};

The particles class is initialised in the "user defined constructor" by using the number of particles (nb_particles) which is required to determine the size of the arrays.

/* **************************** particles.cpp **************************** */

// User defined constructor
Particles::Particles(const unsigned nNew)
    : nbParticles(nNew),
      positionX(nNew, 0.0),
      positionY(nNew, 0.0),
      velocityX(nNew, 0.0),
      velocityY(nNew, 0.0),
      particleSpeedSq(nNew, 0.0) {}

Class Fluid

A cluster of particles, depending on its behaviour, can represent a variety of concepts. One of these concepts is what we will refer to herein as a "fluid", which apart from being discretized in particles also has other attributes such as density, mass and pressure. Therefore, the class Fluid is a child class of the particles class which is extended in order to encapsulate more members to fully characterize the simulated fluid, and its state during every timestep. In the main program the base class will never be invoked explicitly, but only implicitly through the instantiation of a Fluid object. However, from the developer's perspective, using this approach makes the code more modular and allows for the derivation of multiple children from the base class if needed.

/* **************************** fluid.h **************************** */

class Fluid : public Particles {

 ...

 private:
 // Constants of the problem
 double gasConstant;
 double densityResting;
 double viscosity;
 double accelerationGravity;
 double radiusOfInfluence;
 // Helper member variables
 double hInverse;
 double fourPih2;

 // Mass
 double mass = 1.0;

 // Density
 std::vector<double> density;

 // Pressure
 std::vector<double> pressure;

}

Class SphSolver

The SphSolver class contains the implementation of the steps of the algorithm described in this project. The main function, which is called by the main program, is the sphSolver::timeIntegration(Fluid &data, std::ofstream &finalPositionsFile, std::ofstream &energiesFile); where the member functions of the class are invoked and perform calculations on the data members of the Fluid object in order to update the positions and the velocities of the particles. Because the members of the Fluid class have been declared either as protected (from the base class) or private, the solver class does not have direct access to its members and therefore the use of setter and getter functions and the overloaded () operator is required. This is a good practice when working with OOP techniques because it promotes the idea of data hiding by the classes, and increases the robustness of the code, since the object's members cannot be directly modified from anywhere in the code, apart from inside the class. This is an example of encapsulation. Below an example on how the Fluid members are manipulated by one of the SphSolver's member functions is presented.

/* **************************** sph_solver.cpp **************************** */

void SphSolver::updatePosition(Fluid &data, int particleIndex) {
  double newVelocity;
  double newPosition;
  double integrationCoeff = 1.0;

  // First step to initialise the scheme
  if (t == 0) {
    integrationCoeff = 0.5;
  }

  // x-direction
  newVelocity = data.getVelocityX(particleIndex) +
                integrationCoeff *
                    velocityIntegration(data, particleIndex, forcePressureX,
                                        forceViscousX, forceGravityX);
  data.setVelocityX(particleIndex, newVelocity);

  newPosition = data.getPositionX(particleIndex) + newVelocity * dt;

  data.setPositionX(particleIndex, newPosition);

  if (adaptiveTimestepBool) {
    maxVelocity = std::max(maxVelocity, std::abs(newVelocity));
  }

  // y-direction
  newVelocity = data.getVelocityY(particleIndex) +
                integrationCoeff *
                    velocityIntegration(data, particleIndex, forcePressureY,
                                        forceViscousY, forceGravityY);

  data.setVelocityY(particleIndex, newVelocity);

  newPosition = data.getPositionY(particleIndex) + newVelocity * dt;
  data.setPositionY(particleIndex, newPosition);

  if (adaptiveTimestepBool) {
    maxVelocity = std::max(maxVelocity, std::abs(newVelocity));
  }
}