Commit 61185822 authored by BRAMAS Berenger's avatar BRAMAS Berenger

Update quick start

parent 5685a154
/*! \page quick Quick Start
* In this section, we present the data structure organization and the
* classes design to understand fully ScalFmm.
* classes design to understand fully ScalFmm and customized it.
* Remark : There is a big difference between the version 1.0 and 2.0
* since we do not store array of particles anymore but rather several arrays.
* This was needed in order to be able to vectorize the P2P code.
* \tableofcontents
* \section prerequisite Prerequisite
* In it is better to have built the library or at minimum to have
* downloaded the sources. The user needs to be comfortable with 'C++'
* It is recommanded to have built the library or at minimum to have
* downloaded the sources code. The user needs to be comfortable with 'C++'
* language and if possible templates.
* If you want to browse the code, you may want to see first our \ref
......@@ -22,8 +26,8 @@
* In ScalFmm we proceed the Fast Multipole Method. New users should see
* this process has a way to estimate far interactions and compute
* accurately the close interactions in a group of particles. We then
* have some particles that we insert in a octree. The octree stores the
* accurately the close interactions in a group of particles. We start
* with some particles that we insert in a octree. The octree stores the
* particles in its leaves. From the root to the leaves there are the
* cells. At this point we only express primitives classes which hold
* data or primitives classes.
......@@ -31,44 +35,133 @@
* Then, we need a kernel which is computational part of the FMM. It is a
* class that is able to compute the interactions between particles or
* cells, etc. There is several possible kernels depending on what we
* want to compute.
* want to compute and it is easy to implement your own.
* Finally, the FMM Core algorithm is a class that takes the primitives
* classes and calls the kernel with the correct arguments. In our
* implementation, the user has to choose between sequential FMM or
* OpenMP FMM.
* OpenMP FMM or even MPI FMM.
* \section primitivesclasses Primitives Classes
* \subsection particles Particles
* To be stored in the octree, a particle must inherites
* FAbstractParticleContainer. This is the class needed:
* In order to put the particles in the right leaf, the octree needs to know its spatial position.
* Then, then once the right leaf is found it is allocated (using the given template LeafClass of the octree),
* and the particles is pushed into the leaf. If a basic leaf is used, this one only push to a particles container
* what it has received. So a particles container is nothing more than a class that has a push method
* which matches the one you call on the octree. To ensure that,
* a particle container should inherit from FAbstractParticleContainer.
* <pre class='brush: cpp'>
* class FAbstractParticleContainer{
* class FAbstractParticleContainer{
* template<typename... Args>
* void push(const FPoint& , Args ... ){
* // This method should be specialed
* };
* </pre>
* Here is how we can print the index of the particles that is inserted from a particles containers :
* <pre class='brush: cpp'>
* class MyCustomContainer : public FAbstractParticleContainer{
* template<typename... Args>
* void push(const FPoint& , int particleIndex, double anythingElse){
* std::cout << "The particle " << particleIndex << " has just been inserted with " << anythingElse << "\n";
* };
*
* // In the main
* typedef MyCustomContainer ContainerClass;
* typedef FSimpleLeaf< ContainerClass > LeafClass;
* typedef FOctree< FBasicCell, ContainerClass , LeafClass > OctreeClass;
* // From your system properties
* OctreeClass tree(treeHeight, subHeight, loader.getBoxWidth(), loader.getCenterOfBox());
*
* // Add a particle
* tree.push(FPoint(x, y, z), particleIndex, anythingElse);
* // The octree will push in the FSimpleLeaf which will push in the MyCustomContainer which will print the message.
* </pre>
* In the same way you can sort your particles in different buffer by passing a flag which will be passed to your container:
* <pre class='brush: cpp'>
* class MyCustomContainer : public FAbstractParticleContainer{
* std::vector<int> bigParticles;
* std::vector<int> smallParticles;
* template<typename... Args>
* void push(const FPoint& , bool isBig, int particleIndex){
* if(isBig) bigParticles.push_back(particleIndex);
* else smallParticles.push_back(particleIndex);
* };
*
* // In the main
* typedef MyCustomContainer ContainerClass;
* typedef FSimpleLeaf< ContainerClass > LeafClass;
* typedef FOctree< FBasicCell, ContainerClass , LeafClass > OctreeClass;
* // From your system properties
* OctreeClass tree(treeHeight, subHeight, loader.getBoxWidth(), loader.getCenterOfBox());
*
* // Add a particle
* tree.push(FPoint(x, y, z), boolIsBigParticle, particleIndex);
* // The octree will push in the FSimpleLeaf which will push in the MyCustomContainer which will store idx in the correct vector
* </pre>
* A class implements this minimum required methods, it is
* FBasicParticleContainer. This is what MUST proposes a particle
* class to be able to be inserted in the tree. Then, the user can add
* other methods to match the kernel requirement. For example, some
* kernel may need a particle to hold a physical value, a forces
* vector and a potential. See FRotationParticleContainer if you want
* an example of a Particle class.
* The FBasicParticleContainer class is given for those who would like to store one or several data type
* of the same kind per particles (and their position).
* For example if some one want to store one (or several) integers for the particles or
* one (or several) double values per particles.
* <pre class='brush: cpp'>
* // Declaration
* template <unsigned NbAttributesPerParticle, class AttributeClass = FReal >
* class FBasicParticleContainer : public FAbstractParticleContainer, public FAbstractSerializable;
* </pre>
* If for example you would like to store 2 doubles per particles (one intialized during the push
* wherease the other if set to 0) you can use the following code:
* <pre class='brush: cpp'>
* typedef FBasicParticleContainer< 2, double> ContainerClass;
* typedef FSimpleLeaf< ContainerClass > LeafClass;
* typedef FOctree< FBasicCell, ContainerClass , LeafClass > OctreeClass;
* // From your system properties
* OctreeClass tree(treeHeight, subHeight, loader.getBoxWidth(), loader.getCenterOfBox());
*
* // Add a particle
* tree.push(FPoint(x, y, z), myFirstDouble); //, mySecondValueIfNotZero);
*
* // Then to print all the doubles value :
* tree.forEachLeaf([&](LeafClass* lf){
* ContainerClass* container = lf->getSrc();
* int nbParticlesInLeaf = container->getNbParticles();
* double* x_pos = container->getPositions()[0];
* double* y_pos = container->getPositions()[1];
* double* z_pos = container->getPositions()[2];
* double* firstDoubleArray = container->getAttribute(0); // same as getAttribute<0>()
* double* secondDoubleArray = container->getAttribute(1); // same as getAttribute<1>()
*
* for(int idxPart = 0 ; idxPart < nbParticlesInLeaf ; ++idxPart){
* std::cout << "Particle inserted " << idxPart << " in the leaf\n";
* std::cout << "Has position " << x_pos[idxPart] << " " << y_pos[idxPart] << " " << z_part[idxPart] << "\n";
* std::cout << "And values " << firstDoubleArray[idxPart] << " and " << secondDoubleArray[idxPart] << "\n";
* }
* });
* </pre>
* Therefor, we propose a particle container called FP2PParticleContainer to store the position,
* a force vector, a potential and a physical value per particle.
* This container is one used in our kernels and you can read our P2P (or P2M/L2P) in order to catch
* the way it works.
* \subsection cells Cells
* The same principle apply to cells. There is a minimum sets of
* methods that must propose a cell class to be able to be used in the
* octree. And then, there are some other methods that you can add to
* make it usable per the kernel.
* make it usable per your kernel.
* The class Src/Components/FAbstractCell.hpp shows what should
* implement a cell:
......@@ -83,12 +176,11 @@
* virtual void setPosition(const FPoint& inPosition) = 0;
* virtual const FTreeCoordinate& getCoordinate() const = 0;
* virtual void setCoordinate(const long inX, const long inY, const long inZ) = 0;
* virtual bool hasSrcChild() const = 0;
* virtual bool hasTargetsChild() const = 0;
* virtual void setSrcChildTrue() = 0;
* virtual void setTargetsChildTrue() = 0;
* virtual bool hasSrcChild() const = 0; // Needed if TSM (target source model) is used
* virtual bool hasTargetsChild() const = 0; // Needed if TSM (target source model) is used
* virtual void setSrcChildTrue() = 0; // Needed if TSM (target source model) is used
* virtual void setTargetsChildTrue() = 0; // Needed if TSM (target source model) is used
* };
* </pre>
* The FBasicCell class provides an implementation of all these
......@@ -105,47 +197,31 @@
* In the following class, FAbstractLeaf, one can see what is required
* by the algorithm :
* <pre>
* <pre class='brush: cpp'>
* template< class ParticleClass, class ContainerClass >
* class FAbstractLeaf {
* public:
* // Default destructor
* virtual ~FAbstractLeaf(){
* }
* virtual void push(const ParticleClass& particle) = 0;
* }
* template<typename... Args>
* void push(const FPoint& inParticlePosition, Args ... args){
* FLOG( FLog::Controller.write("Warning, push is not implemented!").write(FLog::Flush) );
* }
* virtual ContainerClass* getSrc() = 0;
* virtual ContainerClass* getTargets() = 0;
* };
* </pre>
* The FSimpleLeaf class provides an implementation of all thes
* methods.
* \section octree Octree
* The octree is templatized and then can host particles, cells and
* leaves. It also needs some information about the simulation like the
* size and the center of the box. Moreover, the user has to precise
* the height of the octree. The root is the level 0, so giving a
* height of 3 creates the root level, a cells level and the leaves
* level. The usual way of declaring the octree, taken from
* Tests/Utils/testOctree.cpp, is as follow:
* <pre>
* typedef FVector<FBasicParticle> ContainerClass;
* typedef FSimpleLeaf<FBasicParticle, ContainerClass > LeafClass;
* typedef FOctree<FBasicParticle, FBasicCell, ContainerClass , LeafClass > OctreeClass;
* OctreeClass tree(HEIGHT, SUBHEIGHT, BoxWidth, CenterOfBox);
* </pre>
* The FSimpleLeaf class provides an implementation of all these methods.
* \subsection loading Loading Particle
* Once the octree is created, we need to put some particles in
* it. This is perform using classes called 'loader'.
* A loader should proposes theses methods :
* In most of our examples, we are using "loaders" which are classes used to manage the files.
* They returned the physical properties (box width, center of box, ...) which are used to build the octree.
* Then they are used to get the particle positions (and their physical values if appropriate).
* <pre>
* <pre class='brush: cpp'>
* template <class ParticleClass>
* class FAbstractLoader {
* public:
......@@ -156,43 +232,30 @@
* virtual FPoint getCenterOfBox() const = 0;
* virtual FReal getBoxWidth() const = 0;
* virtual bool isOpen() const = 0;
* virtual void fillParticle(ParticleClass& inParticle) = 0;
* template <class OctreeClass>
* void fillTree(OctreeClass& tree){
* ParticleClass particleToFill;
* for(int idxPart = 0 ; idxPart < getNumberOfParticles() ; ++idxPart){
* fillParticle(particleToFill);
* tree.insert(particleToFill);
* }
* }
* void fillTree(FPoint& particlesPos);
* };
* </pre>
* There exist several loaders; one per file format. Depending of the
* loader, the particle class should implement special methods. For
* example, the basic loader only fill the position of the
* particles. Whereas, the FMA loader also fill the physical value of
* the particles.
* The usual way of loading the particle is as follow :
* <pre>
* FRandomLoader<ParticleClass> loader(NbPart, 1, FPoint(0.5,0.5,0.5), 1);
* There exist several loaders; one per file format.
* Usually we do as the following:
* <pre class='brush: cpp'>
* FRandomLoader loader(NbPart, 1, FPoint(0.5,0.5,0.5), 1);
* OctreeClass tree(10, 3, loader.getBoxWidth(), loader.getCenterOfBox());
* loader.fillTree(tree);
* FPoint particlePosition;
* for(int idxPart = 0 ; idxPart < loader.getNumberOfParticles() ; ++idxPart){
* loader.fillParticle(&particlePosition);
* tree.insert(particlePosition);
* }
* </pre>
* \subsection octreeIterator Iterating on an Octree
*If the user wants to iterate on the tree and access the particles or
*the cells. To do so, he needs to declare an iterator and use it to
*move from top to bottom and from left to right. It is critical that
*the octree is not empty!
* There are two ways to iterate on the data of an octree :
* Using an iterator, or using a lambda function.
* This next sample is taken from Tests/Utils/testOctreeIter.cpp and
* count the leaves :
* This next sample is taken from Tests/Utils/testOctreeIter.cpp and count the leaves :
* <pre>
* <pre class='brush: cpp'>
* OctreeClass::Iterator octreeIterator(&tree);
* octreeIterator.gotoBottomLeft();
* int counter = 0;
......@@ -200,9 +263,14 @@
* ++counter;
* } while(octreeIterator.moveRight());
* </pre>
* But here is the equivalent using lambda function:
* long counter = 0;
* tree.forEachLeaf([&](LeafClass* leaf){
* ++counter;
* });
* To iterate on the cells we can proceed as follow :
* <pre>
* <pre class='brush: cpp'>
* OctreeClass::Iterator octreeIterator(&tree);
* octreeIterator.gotoBottomLeft();
* for(int idxLevel = NbLevels - 1 ; idxLevel >= 1 ; --idxLevel ){
......@@ -215,18 +283,22 @@
* std::cout << "Cells at level " << idxLevel << " = " << counter << " ...\n";
* }
* </pre>
* Here is an equivalent:
* <pre class='brush: cpp'>
* long nbCells[TreeHeight];
* tree.forEachCellWithLevel([&nbCells](CellClass* cell, int idxLevel){
* nbCells[idxLevel] += 1;
* });
* \section kernel The kernel
* The kernel is a class that should perform the usual FMM
* operators. Each kind of kernel may require special methods and
* needs on the particles and the cells.
* Kernel refers to the class that perform the computation.
* An empty kernel can be found in Src/Components/FBasicKernels.hpp,
* it implements the class definition FAbstractKernels :
* <pre>
* template< class ParticleClass, class CellClass, class ContainerClass> class FBasicKernels : public FAbstractKernels<ParticleClass,CellClass,ContainerClass> {
* <pre class='brush: cpp'>
* template< class CellClass, class ContainerClass> class FBasicKernels : public FAbstractKernels<CellClass,ContainerClass> {
* public:
*
* // Default destructor
......@@ -251,8 +323,8 @@
* components type but usually do not call any method manually since
* this is performed per the FMM core.
* <pre>
* typedef FTestKernels<ParticleClass, CellClass, ContainerClass > KernelClass;
* <pre class='brush: cpp'>
* typedef FTestKernels<CellClass, ContainerClass > KernelClass;
* KernelClass kernels;
* </pre>
......@@ -266,8 +338,8 @@
* The next sample is taken from Tests/Utils/testFmmAlgorithm.cpp and
* use the basic sequential FMM :
* <pre>
* typedef FFmmAlgorithm<OctreeClass, ParticleClass, CellClass, ContainerClass, KernelClass, LeafClass > FmmClass;
* <pre class='brush: cpp'>
* typedef FFmmAlgorithm<OctreeClass, CellClass, ContainerClass, KernelClass, LeafClass > FmmClass;
* FmmClass algo(&tree,&kernels);
* algo.execute();
* </pre>
......@@ -275,8 +347,8 @@
* To move to the OpenMP threaded FMM we can use the fallowing code by
* changing 'FFmmAlgorithm' per 'FFmmAlgorithmThread' :
* <pre>
* typedef FFmmAlgorithmThread<OctreeClass, ParticleClass, CellClass, ContainerClass, KernelClass, LeafClass > FmmClass;
* <pre class='brush: cpp'>
* typedef FFmmAlgorithmThread<OctreeClass, CellClass, ContainerClass, KernelClass, LeafClass > FmmClass;
* FmmClass algo(&tree,&kernels);
* algo.execute();
* </pre>
......@@ -291,29 +363,20 @@
* <blockquote>
* The reason is to avoid the use of virtual and abstract class. In
* this page we present some abstract classes, but they are not really
* use. They only define the need, the minimum required to implement a
* particle or a cell. But the kernels should not work on an abstract
* use. They only define what we need, the minimum required to implement a
* particle container or a cell. But the kernels should not work on an abstract
* type but on the real data. This enable lots of compiler
* optimizations and avoid the use of V-Table.
* </blockquote>
* </li>
* <li>
* Some destructors are not virtual :
* <blockquote>
* As we said, the objective of the class are not to be inherited. So
* a virtual destructor is not needed.
* </blockquote>
* </li>
* <li>
* Typedef is used like this :
* <blockquote>
* It can take some time to understand how it works. But all our users
* finally like the way of using typedef and template. As you will see
* in most of the example the struct is the same and you will not be
* lost since in any example 'ParticleClass' is used for the particle
* type and so on.
* in most of the examples the struct is the same and you will not be
* lost.
* </blockquote>
* </li>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment